diff options
Diffstat (limited to 'modules/mono/editor')
18 files changed, 761 insertions, 143 deletions
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props index 59ce1da17b..0459257106 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props @@ -62,7 +62,7 @@ </PropertyGroup> <PropertyGroup> - <GodotRealTIsDouble Condition=" '$(GodotRealTIsDouble)' == '' ">false</GodotRealTIsDouble> + <GodotFloat64 Condition=" '$(GodotFloat64)' == '' ">false</GodotFloat64> </PropertyGroup> <!-- Godot DefineConstants. --> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets index aad4ea4553..859ea52c93 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets @@ -10,14 +10,14 @@ <!-- Define constant to determine whether the real_t type in Godot is double precision or not. By default this is false, like the official Godot builds. If someone is using a custom - Godot build where real_t is double, they can override the GodotRealTIsDouble property. + Godot build where real_t is double, they can override the GodotFloat64 property. --> - <DefineConstants Condition=" '$(GodotRealTIsDouble)' == 'true' ">GODOT_REAL_T_IS_DOUBLE;$(DefineConstants)</DefineConstants> + <DefineConstants Condition=" '$(GodotFloat64)' == 'true' ">GODOT_REAL_T_IS_DOUBLE;$(DefineConstants)</DefineConstants> </PropertyGroup> <!-- C# source generators --> <ItemGroup Condition=" '$(DisableImplicitGodotGeneratorReferences)' != 'true' "> - <PackageReference Include="Godot.SourceGenerators" Version="$(PackageFloatingVersion_Godot)" /> + <PackageReference Include="Godot.SourceGenerators" Version="$(PackageVersion_Godot_SourceGenerators)" /> </ItemGroup> <!-- Godot API references --> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs index 7aaadb27be..98ca534c66 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs @@ -39,6 +39,11 @@ namespace Godot.SourceGenerators for (int i = 0; i < typeArgListSyntax.Arguments.Count; i++) { var typeSyntax = typeArgListSyntax.Arguments[i]; + + // Ignore omitted type arguments, e.g.: List<>, Dictionary<,>, etc + if (typeSyntax is OmittedTypeArgumentSyntax) + continue; + var typeSymbol = sm.GetSymbolInfo(typeSyntax).Symbol as ITypeSymbol; Debug.Assert(typeSymbol != null); diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs index 5ac4f4a47e..1ee31eb1a9 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs @@ -133,7 +133,9 @@ namespace Godot.SourceGenerators .Distinct(new MethodOverloadEqualityComparer()) .ToArray(); - source.Append(" private partial class GodotInternal {\n"); + source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); + + source.Append($" public new class MethodName : {symbol.BaseType.FullQualifiedName()}.MethodName {{\n"); // Generate cached StringNames for methods and properties, for fast lookup @@ -144,7 +146,7 @@ namespace Godot.SourceGenerators foreach (string methodName in distinctMethodNames) { - source.Append(" public static readonly StringName MethodName_"); + source.Append(" public new static readonly StringName "); source.Append(methodName); source.Append(" = \""); source.Append(methodName); @@ -157,8 +159,6 @@ namespace Godot.SourceGenerators if (godotClassMethods.Length > 0) { - source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); - const string listType = "System.Collections.Generic.List<global::Godot.Bridge.MethodInfo>"; source.Append(" internal new static ") @@ -179,10 +179,10 @@ namespace Godot.SourceGenerators source.Append(" return methods;\n"); source.Append(" }\n"); - - source.Append("#pragma warning restore CS0109\n"); } + source.Append("#pragma warning restore CS0109\n"); + // Generate InvokeGodotClassMethod if (godotClassMethods.Length > 0) @@ -242,7 +242,7 @@ namespace Godot.SourceGenerators private static void AppendMethodInfo(StringBuilder source, MethodInfo methodInfo) { - source.Append(" methods.Add(new(name: GodotInternal.MethodName_") + source.Append(" methods.Add(new(name: MethodName.") .Append(methodInfo.Name) .Append(", returnVal: "); @@ -350,7 +350,7 @@ namespace Godot.SourceGenerators source.Append(" "); if (!isFirstEntry) source.Append("else "); - source.Append("if (method == GodotInternal.MethodName_"); + source.Append("if (method == MethodName."); source.Append(methodName); source.Append(") {\n return true;\n }\n"); } @@ -362,7 +362,7 @@ namespace Godot.SourceGenerators { string methodName = method.Method.Name; - source.Append(" if (method == GodotInternal.MethodName_"); + source.Append(" if (method == MethodName."); source.Append(methodName); source.Append(" && argCount == "); source.Append(method.ParamTypes.Length); diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs index fc46d82dff..b331e2e794 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs @@ -122,14 +122,16 @@ namespace Godot.SourceGenerators var godotClassProperties = propertySymbols.WhereIsGodotCompatibleType(typeCache).ToArray(); var godotClassFields = fieldSymbols.WhereIsGodotCompatibleType(typeCache).ToArray(); - source.Append(" private partial class GodotInternal {\n"); + source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); + + source.Append($" public new class PropertyName : {symbol.BaseType.FullQualifiedName()}.PropertyName {{\n"); // Generate cached StringNames for methods and properties, for fast lookup foreach (var property in godotClassProperties) { string propertyName = property.PropertySymbol.Name; - source.Append(" public static readonly StringName PropName_"); + source.Append(" public new static readonly StringName "); source.Append(propertyName); source.Append(" = \""); source.Append(propertyName); @@ -139,7 +141,7 @@ namespace Godot.SourceGenerators foreach (var field in godotClassFields) { string fieldName = field.FieldSymbol.Name; - source.Append(" public static readonly StringName PropName_"); + source.Append(" public new static readonly StringName "); source.Append(fieldName); source.Append(" = \""); source.Append(fieldName); @@ -214,8 +216,6 @@ namespace Godot.SourceGenerators // Generate GetGodotPropertyList - source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); - string dictionaryType = "System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>"; source.Append(" internal new static ") @@ -289,7 +289,7 @@ namespace Godot.SourceGenerators if (!isFirstEntry) source.Append("else "); - source.Append("if (name == GodotInternal.PropName_") + source.Append("if (name == PropertyName.") .Append(propertyMemberName) .Append(") {\n") .Append(" ") @@ -313,7 +313,7 @@ namespace Godot.SourceGenerators if (!isFirstEntry) source.Append("else "); - source.Append("if (name == GodotInternal.PropName_") + source.Append("if (name == PropertyName.") .Append(propertyMemberName) .Append(") {\n") .Append(" value = ") @@ -342,7 +342,7 @@ namespace Godot.SourceGenerators { source.Append(" properties.Add(new(type: (Godot.Variant.Type)") .Append((int)propertyInfo.Type) - .Append(", name: GodotInternal.PropName_") + .Append(", name: PropertyName.") .Append(propertyInfo.Name) .Append(", hint: (Godot.PropertyHint)") .Append((int)propertyInfo.Hint) diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs index c7745391d0..65dadcb801 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs @@ -243,7 +243,7 @@ namespace Godot.SourceGenerators source.Append(" = "); source.Append(exportedMember.Value ?? "default"); source.Append(";\n"); - source.Append(" values.Add(GodotInternal.PropName_"); + source.Append(" values.Add(PropertyName."); source.Append(exportedMember.Name); source.Append(", "); source.Append(defaultValueLocalName); diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs index 39a99ff8ba..a40220565f 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs @@ -159,7 +159,7 @@ namespace Godot.SourceGenerators { string propertyName = property.PropertySymbol.Name; - source.Append(" info.AddProperty(GodotInternal.PropName_") + source.Append(" info.AddProperty(PropertyName.") .Append(propertyName) .Append(", ") .AppendManagedToVariantExpr(string.Concat("this.", propertyName), property.Type) @@ -172,7 +172,7 @@ namespace Godot.SourceGenerators { string fieldName = field.FieldSymbol.Name; - source.Append(" info.AddProperty(GodotInternal.PropName_") + source.Append(" info.AddProperty(PropertyName.") .Append(fieldName) .Append(", ") .AppendManagedToVariantExpr(string.Concat("this.", fieldName), field.Type) @@ -185,7 +185,7 @@ namespace Godot.SourceGenerators { string signalName = signalDelegate.Name; - source.Append(" info.AddSignalEventDelegate(GodotInternal.SignalName_") + source.Append(" info.AddSignalEventDelegate(SignalName.") .Append(signalName) .Append(", this.backing_") .Append(signalName) @@ -204,7 +204,7 @@ namespace Godot.SourceGenerators { string propertyName = property.PropertySymbol.Name; - source.Append(" if (info.TryGetProperty(GodotInternal.PropName_") + source.Append(" if (info.TryGetProperty(PropertyName.") .Append(propertyName) .Append(", out var _value_") .Append(propertyName) @@ -223,7 +223,7 @@ namespace Godot.SourceGenerators { string fieldName = field.FieldSymbol.Name; - source.Append(" if (info.TryGetProperty(GodotInternal.PropName_") + source.Append(" if (info.TryGetProperty(PropertyName.") .Append(fieldName) .Append(", out var _value_") .Append(fieldName) @@ -245,7 +245,7 @@ namespace Godot.SourceGenerators source.Append(" if (info.TryGetSignalEventDelegate<") .Append(signalDelegateQualifiedName) - .Append(">(GodotInternal.SignalName_") + .Append(">(SignalName.") .Append(signalName) .Append(", out var _value_") .Append(signalName) diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs index 6b06f10db1..1df41a905b 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs @@ -173,14 +173,16 @@ namespace Godot.SourceGenerators godotSignalDelegates.Add(new(signalName, signalDelegateSymbol, invokeMethodData.Value)); } - source.Append(" private partial class GodotInternal {\n"); + source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); + + source.Append($" public new class SignalName : {symbol.BaseType.FullQualifiedName()}.SignalName {{\n"); // Generate cached StringNames for methods and properties, for fast lookup foreach (var signalDelegate in godotSignalDelegates) { string signalName = signalDelegate.Name; - source.Append(" public static readonly StringName SignalName_"); + source.Append(" public new static readonly StringName "); source.Append(signalName); source.Append(" = \""); source.Append(signalName); @@ -193,8 +195,6 @@ namespace Godot.SourceGenerators if (godotSignalDelegates.Count > 0) { - source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); - const string listType = "System.Collections.Generic.List<global::Godot.Bridge.MethodInfo>"; source.Append(" internal new static ") @@ -215,10 +215,10 @@ namespace Godot.SourceGenerators source.Append(" return signals;\n"); source.Append(" }\n"); - - source.Append("#pragma warning restore CS0109\n"); } + source.Append("#pragma warning restore CS0109\n"); + // Generate signal event foreach (var signalDelegate in godotSignalDelegates) @@ -291,7 +291,7 @@ namespace Godot.SourceGenerators private static void AppendMethodInfo(StringBuilder source, MethodInfo methodInfo) { - source.Append(" signals.Add(new(name: GodotInternal.SignalName_") + source.Append(" signals.Add(new(name: SignalName.") .Append(methodInfo.Name) .Append(", returnVal: "); @@ -400,7 +400,7 @@ namespace Godot.SourceGenerators string signalName = signal.Name; var invokeMethodData = signal.InvokeMethodData; - source.Append(" if (signal == GodotInternal.SignalName_"); + source.Append(" if (signal == SignalName."); source.Append(signalName); source.Append(" && argCount == "); source.Append(invokeMethodData.ParamTypes.Length); diff --git a/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets b/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets index 4baae77b34..37bd4a0be0 100644 --- a/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets +++ b/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets @@ -21,10 +21,13 @@ Outputs="$(GeneratedGodotNupkgsVersionsFile)"> <PropertyGroup> <GenerateGodotNupkgsVersionsCode><![CDATA[ -namespace $(RootNamespace) { - public class GeneratedGodotNupkgsVersions { +namespace $(RootNamespace) +{ + public class GeneratedGodotNupkgsVersions + { public const string GodotNETSdk = "$(PackageVersion_Godot_NET_Sdk)"%3b public const string GodotSourceGenerators = "$(PackageVersion_Godot_SourceGenerators)"%3b + public const string GodotSharp = "$(PackageVersion_GodotSharp)"%3b } } ]]></GenerateGodotNupkgsVersionsCode> diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/DotNetFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/DotNetFinder.cs index 7bce53308c..b437c7e742 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/DotNetFinder.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/DotNetFinder.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Runtime.InteropServices; using JetBrains.Annotations; using OS = GodotTools.Utils.OS; @@ -16,6 +17,23 @@ namespace GodotTools.Build // In the future, this method may do more than just search in PATH. We could look in // known locations or use Godot's linked nethost to search from the hostfxr location. + if (OS.IsMacOS) + { + if (RuntimeInformation.OSArchitecture == Architecture.X64) + { + string dotnet_x64 = "/usr/local/share/dotnet/x64/dotnet"; // Look for x64 version, when running under Rosetta 2. + if (File.Exists(dotnet_x64)) + { + return dotnet_x64; + } + } + string dotnet = "/usr/local/share/dotnet/dotnet"; // Look for native version. + if (File.Exists(dotnet)) + { + return dotnet; + } + } + return OS.PathWhich("dotnet"); } diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs index 4041026426..237ac85267 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs @@ -122,7 +122,7 @@ namespace GodotTools.Build { base._Ready(); - CustomMinimumSize = new Vector2(0, 228) * EditorScale; + CustomMinimumSize = new Vector2i(0, (int)(228 * EditorScale)); SizeFlagsVertical = (int)SizeFlags.ExpandFill; var toolBarHBox = new HBoxContainer { SizeFlagsHorizontal = (int)SizeFlags.ExpandFill }; diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs b/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs index d2e0e128b5..fe309b8102 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs @@ -22,71 +22,13 @@ namespace GodotTools.Build public static string GodotFallbackFolderPath => Path.Combine(GodotSharpDirs.MonoUserDir, "GodotNuGetFallbackFolder"); - private static void AddFallbackFolderToNuGetConfig(string nuGetConfigPath, string name, string path) - { - var xmlDoc = new XmlDocument(); - xmlDoc.Load(nuGetConfigPath); - - const string nuGetConfigRootName = "configuration"; - - var rootNode = xmlDoc.DocumentElement; - - if (rootNode == null) - { - // No root node, create it - rootNode = xmlDoc.CreateElement(nuGetConfigRootName); - xmlDoc.AppendChild(rootNode); - - // Since this can be considered pretty much a new NuGet.Config, add the default nuget.org source as well - XmlElement nugetOrgSourceEntry = xmlDoc.CreateElement("add"); - nugetOrgSourceEntry.Attributes.Append(xmlDoc.CreateAttribute("key")).Value = "nuget.org"; - nugetOrgSourceEntry.Attributes.Append(xmlDoc.CreateAttribute("value")).Value = - "https://api.nuget.org/v3/index.json"; - nugetOrgSourceEntry.Attributes.Append(xmlDoc.CreateAttribute("protocolVersion")).Value = "3"; - rootNode.AppendChild(xmlDoc.CreateElement("packageSources")).AppendChild(nugetOrgSourceEntry); - } - else - { - // Check that the root node is the expected one - if (rootNode.Name != nuGetConfigRootName) - throw new FormatException("Invalid root Xml node for NuGet.Config. " + - $"Expected '{nuGetConfigRootName}' got '{rootNode.Name}'."); - } - - var fallbackFoldersNode = rootNode["fallbackPackageFolders"] ?? - rootNode.AppendChild(xmlDoc.CreateElement("fallbackPackageFolders")); - - // Check if it already has our fallback package folder - for (var xmlNode = fallbackFoldersNode.FirstChild; xmlNode != null; xmlNode = xmlNode.NextSibling) - { - if (xmlNode.NodeType != XmlNodeType.Element) - continue; - - var xmlElement = (XmlElement)xmlNode; - if (xmlElement.Name == "add" && - xmlElement.Attributes["key"]?.Value == name && - xmlElement.Attributes["value"]?.Value == path) - { - return; - } - } - - XmlElement newEntry = xmlDoc.CreateElement("add"); - newEntry.Attributes.Append(xmlDoc.CreateAttribute("key")).Value = name; - newEntry.Attributes.Append(xmlDoc.CreateAttribute("value")).Value = path; - - fallbackFoldersNode.AppendChild(newEntry); - - xmlDoc.Save(nuGetConfigPath); - } - /// <summary> - /// Returns all the paths where the user NuGet.Config files can be found. + /// Returns all the paths where the Godot.Offline.Config files can be found. /// Does not determine whether the returned files exist or not. /// </summary> - private static string[] GetAllUserNuGetConfigFilePaths() + private static string[] GetAllGodotNuGetConfigFilePaths() { - // Where to find 'NuGet/NuGet.Config': + // Where to find 'NuGet/config/Godot.Offline.Config': // // - Mono/.NETFramework (standalone NuGet): // Uses Environment.SpecialFolder.ApplicationData @@ -98,10 +40,12 @@ namespace GodotTools.Build string applicationData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + const string configFileName = "Godot.Offline.Config"; + if (Utils.OS.IsWindows) { // %APPDATA% for both - return new[] { Path.Combine(applicationData, "NuGet", "NuGet.Config") }; + return new[] { Path.Combine(applicationData, "NuGet", "config", configFileName) }; } var paths = new string[2]; @@ -111,20 +55,20 @@ namespace GodotTools.Build string dotnetCliHome = Environment.GetEnvironmentVariable("DOTNET_CLI_HOME"); if (!string.IsNullOrEmpty(dotnetCliHome)) { - paths[0] = Path.Combine(dotnetCliHome, ".nuget", "NuGet", "NuGet.Config"); + paths[0] = Path.Combine(dotnetCliHome, ".nuget", "NuGet", "config", configFileName); } else { string home = Environment.GetEnvironmentVariable("HOME"); if (string.IsNullOrEmpty(home)) throw new InvalidOperationException("Required environment variable 'HOME' is not set."); - paths[0] = Path.Combine(home, ".nuget", "NuGet", "NuGet.Config"); + paths[0] = Path.Combine(home, ".nuget", "NuGet", "config", configFileName); } // Mono/.NETFramework (standalone NuGet) // ApplicationData is $HOME/.config on Linux/macOS - paths[1] = Path.Combine(applicationData, "NuGet", "NuGet.Config"); + paths[1] = Path.Combine(applicationData, "NuGet", "config", configFileName); return paths; } @@ -141,28 +85,26 @@ namespace GodotTools.Build // The nuspec is not lower case inside the nupkg but must be made lower case when extracted. /// <summary> - /// Adds the specified fallback folder to the user NuGet.Config files, + /// Adds the specified fallback folder to the Godot.Offline.Config files, /// for both standalone NuGet (Mono/.NETFramework) and dotnet CLI NuGet. /// </summary> - public static void AddFallbackFolderToUserNuGetConfigs(string name, string path) + public static void AddFallbackFolderToGodotNuGetConfigs(string name, string path) { - foreach (string nuGetConfigPath in GetAllUserNuGetConfigFilePaths()) + // Make sure the fallback folder exists to avoid error: + // MSB4018: The "ResolvePackageAssets" task failed unexpectedly. + System.IO.Directory.CreateDirectory(path); + + foreach (string nuGetConfigPath in GetAllGodotNuGetConfigFilePaths()) { - if (!System.IO.File.Exists(nuGetConfigPath)) - { - // It doesn't exist, so we create a default one - const string defaultConfig = @"<?xml version=""1.0"" encoding=""utf-8""?> + string defaultConfig = @$"<?xml version=""1.0"" encoding=""utf-8""?> <configuration> - <packageSources> - <add key=""nuget.org"" value=""https://api.nuget.org/v3/index.json"" protocolVersion=""3"" /> - </packageSources> + <fallbackPackageFolders> + <add key=""{name}"" value=""{path}"" /> + </fallbackPackageFolders> </configuration> "; - System.IO.Directory.CreateDirectory(Path.GetDirectoryName(nuGetConfigPath)); - System.IO.File.WriteAllText(nuGetConfigPath, defaultConfig, Encoding.UTF8); // UTF-8 with BOM - } - - AddFallbackFolderToNuGetConfig(nuGetConfigPath, name, path); + System.IO.Directory.CreateDirectory(Path.GetDirectoryName(nuGetConfigPath)); + System.IO.File.WriteAllText(nuGetConfigPath, defaultConfig, Encoding.UTF8); // UTF-8 with BOM } } @@ -189,6 +131,7 @@ namespace GodotTools.Build string destDir = Path.Combine(fallbackFolder, packageIdLower, packageVersionLower); string nupkgDestPath = Path.Combine(destDir, $"{packageIdLower}.{packageVersionLower}.nupkg"); string nupkgSha512DestPath = Path.Combine(destDir, $"{packageIdLower}.{packageVersionLower}.nupkg.sha512"); + string nupkgMetadataDestPath = Path.Combine(destDir, ".nupkg.metadata"); if (File.Exists(nupkgDestPath) && File.Exists(nupkgSha512DestPath)) return; // Already added (for speed we don't check if every file is properly extracted) @@ -197,12 +140,18 @@ namespace GodotTools.Build // Generate .nupkg.sha512 file - using (var alg = SHA512.Create()) - { - alg.ComputeHash(File.ReadAllBytes(nupkgPath)); - string base64Hash = Convert.ToBase64String(alg.Hash); - File.WriteAllText(nupkgSha512DestPath, base64Hash); - } + byte[] hash = SHA512.HashData(File.ReadAllBytes(nupkgPath)); + string base64Hash = Convert.ToBase64String(hash); + File.WriteAllText(nupkgSha512DestPath, base64Hash); + + // Generate .nupkg.metadata file + // Spec: https://github.com/NuGet/Home/wiki/Nupkg-Metadata-File + + File.WriteAllText(nupkgMetadataDestPath, @$"{{ + ""version"": 2, + ""contentHash"": ""{base64Hash}"", + ""source"": null +}}"); // Extract nupkg ExtractNupkg(destDir, nupkgPath, packageId, packageVersion); @@ -251,7 +200,7 @@ namespace GodotTools.Build entryFullName.EndsWith(".nupkg.sha512", StringComparison.OrdinalIgnoreCase) || entryFullName.EndsWith(".nupkg.metadata", StringComparison.OrdinalIgnoreCase) || // Nuspec at root level. We already extracted it previously but in lower case. - entryFullName.IndexOf('/') == -1 && entryFullName.EndsWith(".nuspec")) + !entryFullName.Contains('/') && entryFullName.EndsWith(".nuspec")) { continue; } @@ -297,6 +246,8 @@ namespace GodotTools.Build { ("Godot.NET.Sdk", GeneratedGodotNupkgsVersions.GodotNETSdk), ("Godot.SourceGenerators", GeneratedGodotNupkgsVersions.GodotSourceGenerators), + ("GodotSharp", GeneratedGodotNupkgsVersions.GodotSharp), + ("GodotSharpEditor", GeneratedGodotNupkgsVersions.GodotSharp), }; } } diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index 1cfaea3ec9..89364d1c02 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -123,7 +123,7 @@ namespace GodotTools try { string fallbackFolder = NuGetUtils.GodotFallbackFolderPath; - NuGetUtils.AddFallbackFolderToUserNuGetConfigs(NuGetUtils.GodotFallbackFolderName, + NuGetUtils.AddFallbackFolderToGodotNuGetConfigs(NuGetUtils.GodotFallbackFolderName, fallbackFolder); NuGetUtils.AddBundledPackagesToFallbackFolder(fallbackFolder); } @@ -497,7 +497,7 @@ namespace GodotTools try { // At startup we make sure NuGet.Config files have our Godot NuGet fallback folder included - NuGetUtils.AddFallbackFolderToUserNuGetConfigs(NuGetUtils.GodotFallbackFolderName, + NuGetUtils.AddFallbackFolderToGodotNuGetConfigs(NuGetUtils.GodotFallbackFolderName, NuGetUtils.GodotFallbackFolderPath); } catch (Exception e) diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 15a40c8ca5..ce4ac9b796 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -113,7 +113,7 @@ StringBuilder &operator<<(StringBuilder &r_sb, const char *p_cstring) { #define C_METHOD_MANAGED_FROM_SIGNAL C_NS_MONOMARSHAL ".ConvertSignalToManaged" // Types that will be ignored by the generator and won't be available in C#. -const Vector<String> ignored_types = { "PhysicsServer3DExtension" }; +const Vector<String> ignored_types = { "PhysicsServer2DExtension", "PhysicsServer3DExtension" }; void BindingsGenerator::TypeInterface::postsetup_enum_type(BindingsGenerator::TypeInterface &r_enum_itype) { // C interface for enums is the same as that of 'uint32_t'. Remember to apply @@ -2229,6 +2229,26 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf // Generate signal { + if (p_isignal.is_deprecated) { + if (p_isignal.deprecation_message.is_empty()) { + WARN_PRINT("An empty deprecation message is discouraged. Signal: '" + p_isignal.proxy_name + "'."); + } + + p_output.append(MEMBER_BEGIN "[Obsolete(\""); + p_output.append(p_isignal.deprecation_message); + p_output.append("\")]"); + } + + String delegate_name = p_isignal.proxy_name; + delegate_name += "EventHandler"; // Delegate name is [SignalName]EventHandler + + // Generate delegate + p_output.append(MEMBER_BEGIN "public delegate void "); + p_output.append(delegate_name); + p_output.append("("); + p_output.append(arguments_sig); + p_output.append(");\n"); + if (p_isignal.method_doc && p_isignal.method_doc->description.size()) { String xml_summary = bbcode_to_xml(fix_doc_description(p_isignal.method_doc->description), &p_itype); Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>(); @@ -2247,25 +2267,11 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf } if (p_isignal.is_deprecated) { - if (p_isignal.deprecation_message.is_empty()) { - WARN_PRINT("An empty deprecation message is discouraged. Signal: '" + p_isignal.proxy_name + "'."); - } - p_output.append(MEMBER_BEGIN "[Obsolete(\""); p_output.append(p_isignal.deprecation_message); p_output.append("\")]"); } - String delegate_name = p_isignal.proxy_name; - delegate_name += "EventHandler"; // Delegate name is [SignalName]EventHandler - - // Generate delegate - p_output.append(MEMBER_BEGIN "public delegate void "); - p_output.append(delegate_name); - p_output.append("("); - p_output.append(arguments_sig); - p_output.append(");\n"); - // TODO: // Could we assume the StringName instance of signal name will never be freed (it's stored in ClassDB) before the managed world is unloaded? // If so, we could store the pointer we get from `data_unique_pointer()` instead of allocating StringName here. diff --git a/modules/mono/editor/hostfxr_resolver.cpp b/modules/mono/editor/hostfxr_resolver.cpp new file mode 100644 index 0000000000..ea5978b2cd --- /dev/null +++ b/modules/mono/editor/hostfxr_resolver.cpp @@ -0,0 +1,335 @@ +/*************************************************************************/ +/* hostfxr_resolver.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +/* +Adapted to Godot from the nethost library: https://github.com/dotnet/runtime/tree/main/src/native/corehost +*/ + +/* +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "hostfxr_resolver.h" + +#include "core/config/engine.h" +#include "core/io/dir_access.h" +#include "core/io/file_access.h" +#include "core/os/os.h" + +#ifdef WINDOWS_ENABLED +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#endif + +#include "../utils/path_utils.h" +#include "semver.h" + +// We don't use libnethost as it gives us issues with some compilers. +// This file tries to mimic libnethost's hostfxr_resolver search logic. We try to use the +// same function names for easier comparing in case we need to update this in the future. + +namespace { + +String get_hostfxr_file_name() { +#if defined(WINDOWS_ENABLED) || defined(UWP_ENABLED) + return "hostfxr.dll"; +#elif defined(MACOS_ENABLED) || defined(IOS_ENABLED) + return "libhostfxr.dylib"; +#else + return "libhostfxr.so"; +#endif +} + +bool get_latest_fxr(const String &fxr_root, String &r_fxr_path) { + godotsharp::SemVerParser sem_ver_parser; + + bool found_ver = false; + godotsharp::SemVer latest_ver; + String latest_ver_str; + + Ref<DirAccess> da = DirAccess::open(fxr_root); + da->list_dir_begin(); + for (String dir = da->get_next(); !dir.is_empty(); dir = da->get_next()) { + if (!da->current_is_dir() || dir == "." || dir == "..") { + continue; + } + + String ver = dir.get_file(); + + godotsharp::SemVer fx_ver; + if (sem_ver_parser.parse(ver, fx_ver)) { + if (!found_ver || fx_ver > latest_ver) { + latest_ver = fx_ver; + latest_ver_str = ver; + found_ver = true; + } + } + } + + if (!found_ver) { + return false; + } + + String fxr_with_ver = path::join(fxr_root, latest_ver_str); + String hostfxr_file_path = path::join(fxr_with_ver, get_hostfxr_file_name()); + + ERR_FAIL_COND_V_MSG(!FileAccess::exists(hostfxr_file_path), false, "Missing hostfxr library in directory: " + fxr_with_ver); + + r_fxr_path = hostfxr_file_path; + + return true; +} + +#ifdef WINDOWS_ENABLED +typedef BOOL(WINAPI *LPFN_ISWOW64PROCESS)(HANDLE, PBOOL); + +BOOL is_wow64() { + BOOL wow64 = FALSE; + + LPFN_ISWOW64PROCESS fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process"); + + if (fnIsWow64Process) { + if (!fnIsWow64Process(GetCurrentProcess(), &wow64)) { + wow64 = FALSE; + } + } + + return wow64; +} +#endif + +static const char *arch_name_map[][2] = { + { "arm32", "arm" }, + { "arm64", "arm64" }, + { "rv64", "riscv64" }, + { "x86_64", "x64" }, + { "x86_32", "x86" }, + { nullptr, nullptr } +}; + +String get_dotnet_arch() { + String arch = Engine::get_singleton()->get_architecture_name(); + + int idx = 0; + while (arch_name_map[idx][0] != nullptr) { + if (arch_name_map[idx][0] == arch) { + return arch_name_map[idx][1]; + } + idx++; + } + + return ""; +} + +bool get_default_installation_dir(String &r_dotnet_root) { +#if defined(WINDOWS_ENABLED) + String program_files_env; + if (is_wow64()) { + // Running x86 on x64, looking for x86 install + program_files_env = "ProgramFiles(x86)"; + } else { + program_files_env = "ProgramFiles"; + } + + String program_files_dir = OS::get_singleton()->get_environment(program_files_env); + + if (program_files_dir.is_empty()) { + return false; + } + +#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) || defined(_M_X64) + // When emulating x64 on arm + String dotnet_root_emulated = path::join(program_files_dir, "dotnet", "x64"); + if (FileAccess::exists(path::join(dotnet_root_emulated, "dotnet.exe"))) { + r_dotnet_root = dotnet_root_emulated; + return true; + } +#endif + + r_dotnet_root = path::join(program_files_dir, "dotnet"); + return true; +#elif defined(MACOS_ENABLED) + r_dotnet_root = "/usr/local/share/dotnet"; + +#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) || defined(_M_X64) + // When emulating x64 on arm + String dotnet_root_emulated = path::join(r_dotnet_root, "x64"); + if (FileAccess::exists(path::join(dotnet_root_emulated, "dotnet"))) { + r_dotnet_root = dotnet_root_emulated; + return true; + } +#endif + + return true; +#else + r_dotnet_root = "/usr/share/dotnet"; + return true; +#endif +} + +bool get_install_location_from_file(const String &p_file_path, String &r_dotnet_root) { + Error err = OK; + Ref<FileAccess> f = FileAccess::open(p_file_path, FileAccess::READ, &err); + + if (f.is_null() || err != OK) { + return false; + } + + String line = f->get_line(); + + if (line.is_empty()) { + return false; + } + + r_dotnet_root = line; + return true; +} + +bool get_dotnet_self_registered_dir(String &r_dotnet_root) { +#if defined(WINDOWS_ENABLED) + String sub_key = "SOFTWARE\\dotnet\\Setup\\InstalledVersions\\" + get_dotnet_arch(); + Char16String value = String("InstallLocation").utf16(); + + HKEY hkey = NULL; + LSTATUS result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, (LPCWSTR)(sub_key.utf16().get_data()), 0, KEY_READ | KEY_WOW64_32KEY, &hkey); + if (result != ERROR_SUCCESS) { + return false; + } + + DWORD size = 0; + result = RegGetValueW(hkey, nullptr, (LPCWSTR)(value.get_data()), RRF_RT_REG_SZ, nullptr, nullptr, &size); + if (result != ERROR_SUCCESS || size == 0) { + RegCloseKey(hkey); + return false; + } + + Vector<WCHAR> buffer; + buffer.resize(size / sizeof(WCHAR)); + result = RegGetValueW(hkey, nullptr, (LPCWSTR)(value.get_data()), RRF_RT_REG_SZ, nullptr, (LPBYTE)buffer.ptrw(), &size); + if (result != ERROR_SUCCESS) { + RegCloseKey(hkey); + return false; + } + + r_dotnet_root = String::utf16((const char16_t *)buffer.ptr()); + RegCloseKey(hkey); + return true; +#else + String install_location_file = path::join("/etc/dotnet", "install_location_" + get_dotnet_arch().to_lower()); + if (get_install_location_from_file(install_location_file, r_dotnet_root)) { + return true; + } + + if (FileAccess::exists(install_location_file)) { + // Don't try with the legacy location, this will fall back to the hard-coded default install location + return false; + } + + String legacy_install_location_file = path::join("/etc/dotnet", "install_location"); + return get_install_location_from_file(legacy_install_location_file, r_dotnet_root); +#endif +} + +bool get_file_path_from_env(const String &p_env_key, String &r_dotnet_root) { + String env_value = OS::get_singleton()->get_environment(p_env_key); + + if (!env_value.is_empty()) { + env_value = path::realpath(env_value); + + if (DirAccess::exists(env_value)) { + r_dotnet_root = env_value; + return true; + } + } + + return false; +} + +bool get_dotnet_root_from_env(String &r_dotnet_root) { + String dotnet_root_env = "DOTNET_ROOT"; + String arch_for_env = get_dotnet_arch(); + + if (!arch_for_env.is_empty()) { + // DOTNET_ROOT_<arch> + if (get_file_path_from_env(dotnet_root_env + "_" + arch_for_env.to_upper(), r_dotnet_root)) { + return true; + } + } + +#ifdef WINDOWS_ENABLED + // WoW64-only: DOTNET_ROOT(x86) + if (is_wow64() && get_file_path_from_env("DOTNET_ROOT(x86)", r_dotnet_root)) { + return true; + } +#endif + + // DOTNET_ROOT + return get_file_path_from_env(dotnet_root_env, r_dotnet_root); +} + +} //namespace + +bool godotsharp::hostfxr_resolver::try_get_path_from_dotnet_root(const String &p_dotnet_root, String &r_fxr_path) { + String fxr_dir = path::join(p_dotnet_root, "host", "fxr"); + ERR_FAIL_COND_V_MSG(!DirAccess::exists(fxr_dir), false, "The host fxr folder does not exist: " + fxr_dir); + return get_latest_fxr(fxr_dir, r_fxr_path); +} + +bool godotsharp::hostfxr_resolver::try_get_path(String &r_dotnet_root, String &r_fxr_path) { + if (!get_dotnet_root_from_env(r_dotnet_root) && + !get_dotnet_self_registered_dir(r_dotnet_root) && + !get_default_installation_dir(r_dotnet_root)) { + return false; + } + + return try_get_path_from_dotnet_root(r_dotnet_root, r_fxr_path); +} diff --git a/modules/mono/editor/hostfxr_resolver.h b/modules/mono/editor/hostfxr_resolver.h new file mode 100644 index 0000000000..0f029ab7ae --- /dev/null +++ b/modules/mono/editor/hostfxr_resolver.h @@ -0,0 +1,45 @@ +/*************************************************************************/ +/* hostfxr_resolver.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef HOSTFXR_RESOLVER_H +#define HOSTFXR_RESOLVER_H + +#include "core/string/ustring.h" + +namespace godotsharp { +namespace hostfxr_resolver { + +bool try_get_path_from_dotnet_root(const String &p_dotnet_root, String &r_out_fxr_path); +bool try_get_path(String &r_out_dotnet_root, String &r_out_fxr_path); + +} //namespace hostfxr_resolver +} //namespace godotsharp + +#endif // HOSTFXR_RESOLVER_H diff --git a/modules/mono/editor/semver.cpp b/modules/mono/editor/semver.cpp new file mode 100644 index 0000000000..1656d0932f --- /dev/null +++ b/modules/mono/editor/semver.cpp @@ -0,0 +1,149 @@ +/*************************************************************************/ +/* semver.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "semver.h" + +bool godotsharp::SemVer::parse_digit_only_field(const String &p_field, uint64_t &r_result) { + if (p_field.is_empty()) { + return false; + } + + int64_t integer = 0; + + for (int i = 0; i < p_field.length(); i++) { + char32_t c = p_field[i]; + if (is_digit(c)) { + bool overflow = ((uint64_t)integer > UINT64_MAX / 10) || ((uint64_t)integer == UINT64_MAX / 10 && c > '5'); + ERR_FAIL_COND_V_MSG(overflow, false, "Cannot represent '" + p_field + "' as a 64-bit unsigned integer, since the value is too large."); + integer *= 10; + integer += c - '0'; + } else { + return false; + } + } + + r_result = (uint64_t)integer; + return true; +} + +int godotsharp::SemVer::cmp(const godotsharp::SemVer &p_a, const godotsharp::SemVer &p_b) { + if (p_a.major != p_b.major) { + return p_a.major > p_b.major ? 1 : -1; + } + + if (p_a.minor != p_b.minor) { + return p_a.minor > p_b.minor ? 1 : -1; + } + + if (p_a.patch != p_b.patch) { + return p_a.patch > p_b.patch ? 1 : -1; + } + + if (p_a.prerelease.is_empty() && p_b.prerelease.is_empty()) { + return 0; + } + + if (p_a.prerelease.is_empty() || p_b.prerelease.is_empty()) { + return p_a.prerelease.is_empty() ? 1 : -1; + } + + if (p_a.prerelease != p_b.prerelease) { + // This could be optimized, but I'm too lazy + + Vector<String> a_field_set = p_a.prerelease.split("."); + Vector<String> b_field_set = p_b.prerelease.split("."); + + int a_field_count = a_field_set.size(); + int b_field_count = b_field_set.size(); + + int min_field_count = MIN(a_field_count, b_field_count); + + for (int i = 0; i < min_field_count; i++) { + const String &a_field = a_field_set[i]; + const String &b_field = b_field_set[i]; + + if (a_field == b_field) { + continue; + } + + uint64_t a_num; + bool a_is_digit_only = parse_digit_only_field(a_field, a_num); + + uint64_t b_num; + bool b_is_digit_only = parse_digit_only_field(b_field, b_num); + + if (a_is_digit_only && b_is_digit_only) { + // Identifiers consisting of only digits are compared numerically. + + if (a_num == b_num) { + continue; + } + + return a_num > b_num ? 1 : -1; + } + + if (a_is_digit_only || b_is_digit_only) { + // Numeric identifiers always have lower precedence than non-numeric identifiers. + return b_is_digit_only ? 1 : -1; + } + + // Identifiers with letters or hyphens are compared lexically in ASCII sort order. + return a_field > b_field ? 1 : -1; + } + + if (a_field_count != b_field_count) { + // A larger set of pre-release fields has a higher precedence than a smaller set, if all of the preceding identifiers are equal. + return a_field_count > b_field_count ? 1 : -1; + } + } + + return 0; +} + +bool godotsharp::SemVerParser::parse(const String &p_ver_text, godotsharp::SemVer &r_semver) { + if (!regex.is_valid() && regex.get_pattern().is_empty()) { + regex.compile("^(?P<major>0|[1-9]\\d*)\\.(?P<minor>0|[1-9]\\d*)\\.(?P<patch>0|[1-9]\\d*)(?:-(?P<prerelease>(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"); + ERR_FAIL_COND_V(!regex.is_valid(), false); + } + + Ref<RegExMatch> match = regex.search(p_ver_text); + + if (match.is_valid()) { + r_semver = SemVer( + match->get_string("major").to_int(), + match->get_string("minor").to_int(), + match->get_string("patch").to_int(), + match->get_string("prerelease"), + match->get_string("buildmetadata")); + return true; + } + + return false; +} diff --git a/modules/mono/editor/semver.h b/modules/mono/editor/semver.h new file mode 100644 index 0000000000..48ea8b043e --- /dev/null +++ b/modules/mono/editor/semver.h @@ -0,0 +1,106 @@ +/*************************************************************************/ +/* semver.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef SEMVER_H +#define SEMVER_H + +#include "core/string/ustring.h" +#include "modules/regex/regex.h" + +// <sys/sysmacros.h> is included somewhere, which defines major(dev) to gnu_dev_major(dev) +#if defined(major) +#undef major +#endif +#if defined(minor) +#undef minor +#endif + +namespace godotsharp { + +struct SemVer { +private: + static bool parse_digit_only_field(const String &p_field, uint64_t &r_result); + + static int cmp(const SemVer &p_a, const SemVer &p_b); + +public: + int major = 0; + int minor = 0; + int patch = 0; + String prerelease; + String build_metadata; + + bool operator==(const SemVer &b) const { + return cmp(*this, b) == 0; + } + + bool operator!=(const SemVer &b) const { + return !operator==(b); + } + + bool operator<(const SemVer &b) const { + return cmp(*this, b) < 0; + } + + bool operator>(const SemVer &b) const { + return cmp(*this, b) > 0; + } + + bool operator<=(const SemVer &b) const { + return cmp(*this, b) <= 0; + } + + bool operator>=(const SemVer &b) const { + return cmp(*this, b) >= 0; + } + + SemVer() {} + + SemVer(int p_major, int p_minor, int p_patch, + const String &p_prerelease, const String &p_build_metadata) : + major(p_major), + minor(p_minor), + patch(p_patch), + prerelease(p_prerelease), + build_metadata(p_build_metadata) { + } +}; + +struct SemVerParser { +private: + RegEx regex; + +public: + bool parse(const String &p_ver_text, SemVer &r_semver); +}; + +} //namespace godotsharp + +#endif // SEMVER_H |