initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
using GodotTools.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace GodotTools.ProjectEditor
|
||||
{
|
||||
public class DotNetSolution
|
||||
{
|
||||
private const string _solutionTemplate =
|
||||
@"Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 2012
|
||||
{0}
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
{1}
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{2}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
";
|
||||
|
||||
private const string _projectDeclaration =
|
||||
@"Project(""{{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}}"") = ""{0}"", ""{1}"", ""{{{2}}}""
|
||||
EndProject";
|
||||
|
||||
private const string _solutionPlatformsConfig =
|
||||
@" {0}|Any CPU = {0}|Any CPU";
|
||||
|
||||
private const string _projectPlatformsConfig =
|
||||
@" {{{0}}}.{1}|Any CPU.ActiveCfg = {1}|Any CPU
|
||||
{{{0}}}.{1}|Any CPU.Build.0 = {1}|Any CPU";
|
||||
|
||||
private readonly Dictionary<string, ProjectInfo> _projects = new Dictionary<string, ProjectInfo>();
|
||||
|
||||
public string Name { get; }
|
||||
public string DirectoryPath { get; }
|
||||
|
||||
public class ProjectInfo
|
||||
{
|
||||
public string Guid { get; }
|
||||
public string PathRelativeToSolution { get; }
|
||||
public List<string> Configs { get; }
|
||||
|
||||
public ProjectInfo(string guid, string pathRelativeToSolution, List<string> configs)
|
||||
{
|
||||
Guid = guid;
|
||||
PathRelativeToSolution = pathRelativeToSolution;
|
||||
Configs = configs;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddNewProject(string name, ProjectInfo projectInfo)
|
||||
{
|
||||
_projects[name] = projectInfo;
|
||||
}
|
||||
|
||||
public bool HasProject(string name)
|
||||
{
|
||||
return _projects.ContainsKey(name);
|
||||
}
|
||||
|
||||
public ProjectInfo GetProjectInfo(string name)
|
||||
{
|
||||
return _projects[name];
|
||||
}
|
||||
|
||||
public bool RemoveProject(string name)
|
||||
{
|
||||
return _projects.Remove(name);
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
if (!Directory.Exists(DirectoryPath))
|
||||
throw new FileNotFoundException("The solution directory does not exist.");
|
||||
|
||||
string projectsDecl = string.Empty;
|
||||
string slnPlatformsCfg = string.Empty;
|
||||
string projPlatformsCfg = string.Empty;
|
||||
|
||||
bool isFirstProject = true;
|
||||
|
||||
foreach (var pair in _projects)
|
||||
{
|
||||
string name = pair.Key;
|
||||
ProjectInfo projectInfo = pair.Value;
|
||||
|
||||
if (!isFirstProject)
|
||||
projectsDecl += "\n";
|
||||
|
||||
projectsDecl += string.Format(CultureInfo.InvariantCulture, _projectDeclaration,
|
||||
name, projectInfo.PathRelativeToSolution.Replace("/", "\\", StringComparison.Ordinal), projectInfo.Guid);
|
||||
|
||||
for (int i = 0; i < projectInfo.Configs.Count; i++)
|
||||
{
|
||||
string config = projectInfo.Configs[i];
|
||||
|
||||
if (i != 0 || !isFirstProject)
|
||||
{
|
||||
slnPlatformsCfg += "\n";
|
||||
projPlatformsCfg += "\n";
|
||||
}
|
||||
|
||||
slnPlatformsCfg += string.Format(CultureInfo.InvariantCulture, _solutionPlatformsConfig, config);
|
||||
projPlatformsCfg += string.Format(CultureInfo.InvariantCulture, _projectPlatformsConfig, projectInfo.Guid, config);
|
||||
}
|
||||
|
||||
isFirstProject = false;
|
||||
}
|
||||
|
||||
string solutionPath = Path.Combine(DirectoryPath, Name + ".sln");
|
||||
string content = string.Format(CultureInfo.InvariantCulture, _solutionTemplate, projectsDecl, slnPlatformsCfg, projPlatformsCfg);
|
||||
|
||||
File.WriteAllText(solutionPath, content, Encoding.UTF8); // UTF-8 with BOM
|
||||
}
|
||||
|
||||
public DotNetSolution(string name, string directoryPath)
|
||||
{
|
||||
Name = name;
|
||||
DirectoryPath = directoryPath.IsAbsolutePath() ? directoryPath : Path.GetFullPath(directoryPath);
|
||||
}
|
||||
|
||||
public static void MigrateFromOldConfigNames(string slnPath)
|
||||
{
|
||||
if (!File.Exists(slnPath))
|
||||
return;
|
||||
|
||||
string input = File.ReadAllText(slnPath);
|
||||
|
||||
if (!Regex.IsMatch(input, Regex.Escape("Tools|Any CPU")))
|
||||
return;
|
||||
|
||||
// This method renames old configurations in solutions to the new ones.
|
||||
//
|
||||
// This is the order configs appear in the solution and what we want to rename them to:
|
||||
// Debug|Any CPU = Debug|Any CPU -> ExportDebug|Any CPU = ExportDebug|Any CPU
|
||||
// Tools|Any CPU = Tools|Any CPU -> Debug|Any CPU = Debug|Any CPU
|
||||
//
|
||||
// But we want to move Tools (now Debug) to the top, so it's easier to rename like this:
|
||||
// Debug|Any CPU = Debug|Any CPU -> Debug|Any CPU = Debug|Any CPU
|
||||
// Release|Any CPU = Release|Any CPU -> ExportDebug|Any CPU = ExportDebug|Any CPU
|
||||
// Tools|Any CPU = Tools|Any CPU -> ExportRelease|Any CPU = ExportRelease|Any CPU
|
||||
|
||||
var dict = new Dictionary<string, string>
|
||||
{
|
||||
{"Debug|Any CPU", "Debug|Any CPU"},
|
||||
{"Release|Any CPU", "ExportDebug|Any CPU"},
|
||||
{"Tools|Any CPU", "ExportRelease|Any CPU"}
|
||||
};
|
||||
|
||||
var regex = new Regex(string.Join("|", dict.Keys.Select(Regex.Escape)));
|
||||
string result = regex.Replace(input, m => dict[m.Value]);
|
||||
|
||||
if (result != input)
|
||||
{
|
||||
// Save a copy of the solution before replacing it
|
||||
FileUtils.SaveBackupCopy(slnPath);
|
||||
|
||||
File.WriteAllText(slnPath, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}</ProjectGuid>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<LangVersion>12</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Build" Version="15.1.548" ExcludeAssets="runtime" />
|
||||
<PackageReference Include="Microsoft.Build.Locator" Version="1.2.6" />
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="NuGet.Frameworks" Version="6.12.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj" />
|
||||
<ProjectReference Include="..\GodotTools.Shared\GodotTools.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@@ -0,0 +1,205 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace GodotTools.ProjectEditor
|
||||
{
|
||||
public static class IdentifierUtils
|
||||
{
|
||||
public static string SanitizeQualifiedIdentifier(string qualifiedIdentifier, bool allowEmptyIdentifiers)
|
||||
{
|
||||
if (string.IsNullOrEmpty(qualifiedIdentifier))
|
||||
throw new ArgumentException($"{nameof(qualifiedIdentifier)} cannot be empty", nameof(qualifiedIdentifier));
|
||||
|
||||
string[] identifiers = qualifiedIdentifier.Split('.');
|
||||
|
||||
for (int i = 0; i < identifiers.Length; i++)
|
||||
{
|
||||
identifiers[i] = SanitizeIdentifier(identifiers[i], allowEmpty: allowEmptyIdentifiers);
|
||||
}
|
||||
|
||||
return string.Join(".", identifiers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Skips invalid identifier characters including decimal digit numbers at the start of the identifier.
|
||||
/// </summary>
|
||||
private static void SkipInvalidCharacters(string source, int startIndex, StringBuilder outputBuilder)
|
||||
{
|
||||
for (int i = startIndex; i < source.Length; i++)
|
||||
{
|
||||
char @char = source[i];
|
||||
|
||||
switch (char.GetUnicodeCategory(@char))
|
||||
{
|
||||
case UnicodeCategory.UppercaseLetter:
|
||||
case UnicodeCategory.LowercaseLetter:
|
||||
case UnicodeCategory.TitlecaseLetter:
|
||||
case UnicodeCategory.ModifierLetter:
|
||||
case UnicodeCategory.LetterNumber:
|
||||
case UnicodeCategory.OtherLetter:
|
||||
outputBuilder.Append(@char);
|
||||
break;
|
||||
case UnicodeCategory.NonSpacingMark:
|
||||
case UnicodeCategory.SpacingCombiningMark:
|
||||
case UnicodeCategory.ConnectorPunctuation:
|
||||
case UnicodeCategory.DecimalDigitNumber:
|
||||
// Identifiers may start with underscore
|
||||
if (outputBuilder.Length > startIndex || @char == '_')
|
||||
outputBuilder.Append(@char);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string SanitizeIdentifier(string identifier, bool allowEmpty)
|
||||
{
|
||||
if (string.IsNullOrEmpty(identifier))
|
||||
{
|
||||
if (allowEmpty)
|
||||
return "Empty"; // Default value for empty identifiers
|
||||
|
||||
throw new ArgumentException($"{nameof(identifier)} cannot be empty if {nameof(allowEmpty)} is false", nameof(identifier));
|
||||
}
|
||||
|
||||
if (identifier.Length > 511)
|
||||
identifier = identifier.Substring(0, 511);
|
||||
|
||||
var identifierBuilder = new StringBuilder();
|
||||
int startIndex = 0;
|
||||
|
||||
if (identifier[0] == '@')
|
||||
{
|
||||
identifierBuilder.Append('@');
|
||||
startIndex += 1;
|
||||
}
|
||||
|
||||
SkipInvalidCharacters(identifier, startIndex, identifierBuilder);
|
||||
|
||||
if (identifierBuilder.Length == startIndex)
|
||||
{
|
||||
// All characters were invalid so now it's empty. Fill it with something.
|
||||
identifierBuilder.Append("Empty");
|
||||
}
|
||||
|
||||
identifier = identifierBuilder.ToString();
|
||||
|
||||
if (identifier[0] != '@' && IsKeyword(identifier, anyDoubleUnderscore: true))
|
||||
identifier = '@' + identifier;
|
||||
|
||||
return identifier;
|
||||
}
|
||||
|
||||
private static bool IsKeyword(string value, bool anyDoubleUnderscore)
|
||||
{
|
||||
// Identifiers that start with double underscore are meant to be used for reserved keywords.
|
||||
// Only existing keywords are enforced, but it may be useful to forbid any identifier
|
||||
// that begins with double underscore to prevent issues with future C# versions.
|
||||
if (anyDoubleUnderscore)
|
||||
{
|
||||
if (value.Length > 2 && value[0] == '_' && value[1] == '_' && value[2] != '_')
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_doubleUnderscoreKeywords.Contains(value))
|
||||
return true;
|
||||
}
|
||||
|
||||
return _keywords.Contains(value);
|
||||
}
|
||||
|
||||
private static readonly HashSet<string> _doubleUnderscoreKeywords = new HashSet<string>
|
||||
{
|
||||
"__arglist",
|
||||
"__makeref",
|
||||
"__reftype",
|
||||
"__refvalue",
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> _keywords = new HashSet<string>
|
||||
{
|
||||
"as",
|
||||
"do",
|
||||
"if",
|
||||
"in",
|
||||
"is",
|
||||
"for",
|
||||
"int",
|
||||
"new",
|
||||
"out",
|
||||
"ref",
|
||||
"try",
|
||||
"base",
|
||||
"bool",
|
||||
"byte",
|
||||
"case",
|
||||
"char",
|
||||
"else",
|
||||
"enum",
|
||||
"goto",
|
||||
"lock",
|
||||
"long",
|
||||
"null",
|
||||
"this",
|
||||
"true",
|
||||
"uint",
|
||||
"void",
|
||||
"break",
|
||||
"catch",
|
||||
"class",
|
||||
"const",
|
||||
"event",
|
||||
"false",
|
||||
"fixed",
|
||||
"float",
|
||||
"sbyte",
|
||||
"short",
|
||||
"throw",
|
||||
"ulong",
|
||||
"using",
|
||||
"where",
|
||||
"while",
|
||||
"yield",
|
||||
"double",
|
||||
"extern",
|
||||
"object",
|
||||
"params",
|
||||
"public",
|
||||
"return",
|
||||
"sealed",
|
||||
"sizeof",
|
||||
"static",
|
||||
"string",
|
||||
"struct",
|
||||
"switch",
|
||||
"typeof",
|
||||
"unsafe",
|
||||
"ushort",
|
||||
"checked",
|
||||
"decimal",
|
||||
"default",
|
||||
"finally",
|
||||
"foreach",
|
||||
"partial",
|
||||
"private",
|
||||
"virtual",
|
||||
"abstract",
|
||||
"continue",
|
||||
"delegate",
|
||||
"explicit",
|
||||
"implicit",
|
||||
"internal",
|
||||
"operator",
|
||||
"override",
|
||||
"readonly",
|
||||
"volatile",
|
||||
"interface",
|
||||
"namespace",
|
||||
"protected",
|
||||
"unchecked",
|
||||
"stackalloc",
|
||||
};
|
||||
}
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Microsoft.Build.Construction;
|
||||
using Microsoft.Build.Evaluation;
|
||||
using GodotTools.Shared;
|
||||
|
||||
namespace GodotTools.ProjectEditor
|
||||
{
|
||||
public static class ProjectGenerator
|
||||
{
|
||||
public static string GodotSdkAttrValue => $"Godot.NET.Sdk/{GeneratedGodotNupkgsVersions.GodotNETSdk}";
|
||||
|
||||
public static string GodotMinimumRequiredTfm => "net8.0";
|
||||
|
||||
public static ProjectRootElement GenGameProject(string name)
|
||||
{
|
||||
if (name.Length == 0)
|
||||
throw new ArgumentException("Project name is empty.", nameof(name));
|
||||
|
||||
var root = ProjectRootElement.Create(NewProjectFileOptions.None);
|
||||
|
||||
root.Sdk = GodotSdkAttrValue;
|
||||
|
||||
var mainGroup = root.AddPropertyGroup();
|
||||
mainGroup.AddProperty("TargetFramework", GodotMinimumRequiredTfm);
|
||||
|
||||
// Non-gradle builds require .NET 9 to match the jar libraries included in the export template.
|
||||
var net9 = mainGroup.AddProperty("TargetFramework", "net9.0");
|
||||
net9.Condition = " '$(GodotTargetPlatform)' == 'android' ";
|
||||
|
||||
mainGroup.AddProperty("EnableDynamicLoading", "true");
|
||||
|
||||
string sanitizedName = IdentifierUtils.SanitizeQualifiedIdentifier(name, allowEmptyIdentifiers: true);
|
||||
|
||||
// If the name is not a valid namespace, manually set RootNamespace to a sanitized one.
|
||||
if (sanitizedName != name)
|
||||
mainGroup.AddProperty("RootNamespace", sanitizedName);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
public static string GenAndSaveGameProject(string dir, string name)
|
||||
{
|
||||
if (name.Length == 0)
|
||||
throw new ArgumentException("Project name is empty.", nameof(name));
|
||||
|
||||
string path = Path.Combine(dir, name + ".csproj");
|
||||
|
||||
var root = GenGameProject(name);
|
||||
|
||||
// Save (without BOM)
|
||||
root.Save(path, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
|
||||
|
||||
return Guid.NewGuid().ToString().ToUpperInvariant();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,226 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Build.Construction;
|
||||
using Microsoft.Build.Evaluation;
|
||||
using Microsoft.Build.Locator;
|
||||
using NuGet.Frameworks;
|
||||
|
||||
namespace GodotTools.ProjectEditor
|
||||
{
|
||||
public sealed class MSBuildProject
|
||||
{
|
||||
internal ProjectRootElement Root { get; set; }
|
||||
|
||||
public bool HasUnsavedChanges { get; set; }
|
||||
|
||||
public void Save() => Root.Save();
|
||||
|
||||
public MSBuildProject(ProjectRootElement root)
|
||||
{
|
||||
Root = root;
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class ProjectUtils
|
||||
{
|
||||
[GeneratedRegex(@"\s*'\$\(GodotTargetPlatform\)'\s*==\s*'(?<platform>[A-z]+)'\s*", RegexOptions.IgnoreCase)]
|
||||
private static partial Regex GodotTargetPlatformConditionRegex();
|
||||
|
||||
private static readonly string[] _platformNames =
|
||||
{
|
||||
"windows",
|
||||
"linuxbsd",
|
||||
"macos",
|
||||
"android",
|
||||
"ios",
|
||||
"web",
|
||||
};
|
||||
|
||||
public static void MSBuildLocatorRegisterLatest(out Version version, out string path)
|
||||
{
|
||||
var instance = MSBuildLocator.QueryVisualStudioInstances()
|
||||
.OrderByDescending(x => x.Version)
|
||||
.First();
|
||||
MSBuildLocator.RegisterInstance(instance);
|
||||
version = instance.Version;
|
||||
path = instance.MSBuildPath;
|
||||
}
|
||||
|
||||
public static void MSBuildLocatorRegisterMSBuildPath(string msbuildPath)
|
||||
=> MSBuildLocator.RegisterMSBuildPath(msbuildPath);
|
||||
|
||||
public static MSBuildProject? Open(string path)
|
||||
{
|
||||
var root = ProjectRootElement.Open(path, ProjectCollection.GlobalProjectCollection, preserveFormatting: true);
|
||||
return root != null ? new MSBuildProject(root) : null;
|
||||
}
|
||||
|
||||
public static void UpgradeProjectIfNeeded(MSBuildProject project, string projectName)
|
||||
{
|
||||
// NOTE: The order in which changes are made to the project is important.
|
||||
|
||||
// Migrate to MSBuild project Sdks style if using the old style.
|
||||
MigrateToProjectSdksStyle(project, projectName);
|
||||
|
||||
EnsureGodotSdkIsUpToDate(project);
|
||||
EnsureTargetFrameworkMatchesMinimumRequirement(project);
|
||||
}
|
||||
|
||||
private static void MigrateToProjectSdksStyle(MSBuildProject project, string projectName)
|
||||
{
|
||||
var origRoot = project.Root;
|
||||
|
||||
if (!string.IsNullOrEmpty(origRoot.Sdk))
|
||||
return;
|
||||
|
||||
project.Root = ProjectGenerator.GenGameProject(projectName);
|
||||
project.Root.FullPath = origRoot.FullPath;
|
||||
project.HasUnsavedChanges = true;
|
||||
}
|
||||
|
||||
public static void EnsureGodotSdkIsUpToDate(MSBuildProject project)
|
||||
{
|
||||
var root = project.Root;
|
||||
string godotSdkAttrValue = ProjectGenerator.GodotSdkAttrValue;
|
||||
|
||||
if (!string.IsNullOrEmpty(root.Sdk) &&
|
||||
root.Sdk.Trim().Equals(godotSdkAttrValue, StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
|
||||
root.Sdk = godotSdkAttrValue;
|
||||
project.HasUnsavedChanges = true;
|
||||
}
|
||||
|
||||
private static void EnsureTargetFrameworkMatchesMinimumRequirement(MSBuildProject project)
|
||||
{
|
||||
var root = project.Root;
|
||||
string minTfmValue = ProjectGenerator.GodotMinimumRequiredTfm;
|
||||
var minTfmVersion = NuGetFramework.Parse(minTfmValue).Version;
|
||||
|
||||
ProjectPropertyGroupElement? mainPropertyGroup = null;
|
||||
ProjectPropertyElement? mainTargetFrameworkProperty = null;
|
||||
|
||||
var propertiesToChange = new List<ProjectPropertyElement>();
|
||||
|
||||
foreach (var propertyGroup in root.PropertyGroups)
|
||||
{
|
||||
bool groupHasCondition = !string.IsNullOrEmpty(propertyGroup.Condition);
|
||||
|
||||
// Check if the property group should be excluded from checking for 'TargetFramework' properties.
|
||||
if (groupHasCondition && !ConditionMatchesGodotPlatform(propertyGroup.Condition))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Store a reference to the first property group without conditions,
|
||||
// in case we need to add a new 'TargetFramework' property later.
|
||||
if (mainPropertyGroup == null && !groupHasCondition)
|
||||
{
|
||||
mainPropertyGroup = propertyGroup;
|
||||
}
|
||||
|
||||
foreach (var property in propertyGroup.Properties)
|
||||
{
|
||||
// We are looking for 'TargetFramework' properties.
|
||||
if (property.Name != "TargetFramework")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bool propertyHasCondition = !string.IsNullOrEmpty(property.Condition);
|
||||
|
||||
// Check if the property should be excluded.
|
||||
if (propertyHasCondition && !ConditionMatchesGodotPlatform(property.Condition))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!groupHasCondition && !propertyHasCondition)
|
||||
{
|
||||
// Store a reference to the 'TargetFramework' that has no conditions
|
||||
// because it applies to all platforms.
|
||||
if (mainTargetFrameworkProperty == null)
|
||||
{
|
||||
mainTargetFrameworkProperty = property;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the 'TargetFramework' property is conditional, it may no longer be needed
|
||||
// when the main one is upgraded to the new minimum version.
|
||||
var tfmVersion = NuGetFramework.Parse(property.Value).Version;
|
||||
if (tfmVersion <= minTfmVersion)
|
||||
{
|
||||
propertiesToChange.Add(property);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mainTargetFrameworkProperty == null)
|
||||
{
|
||||
// We haven't found a 'TargetFramework' property without conditions,
|
||||
// we'll just add one in the first property group without conditions.
|
||||
if (mainPropertyGroup == null)
|
||||
{
|
||||
// We also don't have a property group without conditions,
|
||||
// so we'll add a new one to the project.
|
||||
mainPropertyGroup = root.AddPropertyGroup();
|
||||
}
|
||||
|
||||
mainTargetFrameworkProperty = mainPropertyGroup.AddProperty("TargetFramework", minTfmValue);
|
||||
project.HasUnsavedChanges = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var tfmVersion = NuGetFramework.Parse(mainTargetFrameworkProperty.Value).Version;
|
||||
if (tfmVersion < minTfmVersion)
|
||||
{
|
||||
mainTargetFrameworkProperty.Value = minTfmValue;
|
||||
project.HasUnsavedChanges = true;
|
||||
}
|
||||
}
|
||||
|
||||
var mainTfmVersion = NuGetFramework.Parse(mainTargetFrameworkProperty.Value).Version;
|
||||
foreach (var property in propertiesToChange)
|
||||
{
|
||||
// If the main 'TargetFramework' property targets a version newer than
|
||||
// the minimum required by Godot, we don't want to remove the conditional
|
||||
// 'TargetFramework' properties, only upgrade them to the new minimum.
|
||||
// Otherwise, it can be removed.
|
||||
if (mainTfmVersion > minTfmVersion)
|
||||
{
|
||||
var propertyTfmVersion = NuGetFramework.Parse(property.Value).Version;
|
||||
if (propertyTfmVersion == minTfmVersion)
|
||||
{
|
||||
// The 'TargetFramework' property already matches the minimum version.
|
||||
continue;
|
||||
}
|
||||
|
||||
property.Value = minTfmValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
property.Parent.RemoveChild(property);
|
||||
}
|
||||
|
||||
project.HasUnsavedChanges = true;
|
||||
}
|
||||
|
||||
static bool ConditionMatchesGodotPlatform(string condition)
|
||||
{
|
||||
// Check if the condition is checking the 'GodotTargetPlatform' for one of the
|
||||
// Godot platforms with built-in support in the Godot.NET.Sdk.
|
||||
var match = GodotTargetPlatformConditionRegex().Match(condition);
|
||||
if (match.Success)
|
||||
{
|
||||
string platform = match.Groups["platform"].Value;
|
||||
return _platformNames.Contains(platform, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user