diff options
20 files changed, 494 insertions, 26 deletions
diff --git a/doc/classes/TileMap.xml b/doc/classes/TileMap.xml index 371ba80723..c500052592 100644 --- a/doc/classes/TileMap.xml +++ b/doc/classes/TileMap.xml @@ -280,7 +280,7 @@ Position for tile origin. See [enum TileOrigin] for possible values. </member> <member name="cell_y_sort" type="bool" setter="set_y_sort_enabled" getter="is_y_sort_enabled" default="false"> - If [code]true[/code], the TileMap's children will be drawn in order of their Y coordinate. + If [code]true[/code], the TileMap's direct children will be drawn in order of their Y coordinate. </member> <member name="centered_textures" type="bool" setter="set_centered_textures" getter="is_centered_textures_enabled" default="false"> If [code]true[/code], the textures will be centered in the middle of each tile. This is useful for certain isometric or top-down modes when textures are made larger or smaller than the tiles (e.g. to avoid flickering on tile edges). The offset is still applied, but from the center of the tile. If used, [member compatibility_mode] is ignored. diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 601f0ad735..1837b23a0b 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -414,9 +414,9 @@ bool EditorPropertyRevert::get_instanced_node_original_property(Node *p_node, co node = node->get_owner(); } - if (!found && node) { + if (!found && p_node) { //if not found, try default class value - Variant attempt = ClassDB::class_get_default_property_value(node->get_class_name(), p_prop); + Variant attempt = ClassDB::class_get_default_property_value(p_node->get_class_name(), p_prop); if (attempt.get_type() != Variant::NIL) { found = true; value = attempt; diff --git a/modules/mono/SCsub b/modules/mono/SCsub index e8f3174a0a..3b94949470 100644 --- a/modules/mono/SCsub +++ b/modules/mono/SCsub @@ -40,6 +40,11 @@ if env_mono["tools"] and env_mono["mono_glue"] and env_mono["build_cil"]: godot_tools_build.build(env_mono, api_sln_cmd) + # Build Godot.NET.Sdk + import build_scripts.godot_net_sdk_build as godot_net_sdk_build + + godot_net_sdk_build.build(env_mono) + # Add sources env_mono.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/mono/build_scripts/godot_net_sdk_build.py b/modules/mono/build_scripts/godot_net_sdk_build.py new file mode 100644 index 0000000000..3bfba0f0f6 --- /dev/null +++ b/modules/mono/build_scripts/godot_net_sdk_build.py @@ -0,0 +1,45 @@ +# Build Godot.NET.Sdk solution + +import os + +from SCons.Script import Dir + + +def build_godot_net_sdk(source, target, env): + # source and target elements are of type SCons.Node.FS.File, hence why we convert them to str + + module_dir = env["module_dir"] + + solution_path = os.path.join(module_dir, "editor/Godot.NET.Sdk/Godot.NET.Sdk.sln") + build_config = "Release" + + from .solution_builder import build_solution + + extra_msbuild_args = ["/p:GodotPlatform=" + env["platform"]] + + build_solution(env, solution_path, build_config, extra_msbuild_args) + # No need to copy targets. The Godot.NET.Sdk csproj takes care of copying them. + + +def build(env_mono): + assert env_mono["tools"] + + output_dir = Dir("#bin").abspath + editor_tools_dir = os.path.join(output_dir, "GodotSharp", "Tools") + nupkgs_dir = os.path.join(editor_tools_dir, "nupkgs") + + module_dir = os.getcwd() + + package_version_file = os.path.join( + module_dir, "editor", "Godot.NET.Sdk", "Godot.NET.Sdk", "Godot.NET.Sdk_PackageVersion.txt" + ) + + with open(package_version_file, mode="r") as f: + version = f.read().strip() + + target_filenames = ["Godot.NET.Sdk.%s.nupkg" % version] + + targets = [os.path.join(nupkgs_dir, filename) for filename in target_filenames] + + cmd = env_mono.CommandNoCache(targets, [], build_godot_net_sdk, module_dir=module_dir) + env_mono.AlwaysBuild(cmd) diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 70b731d611..8928b6e5e3 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -940,8 +940,6 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { // Use a placeholder for now to avoid losing the state when saving a scene - obj->set_script(scr); - PlaceHolderScriptInstance *placeholder = scr->placeholder_instance_create(obj); obj->set_script_instance(placeholder); @@ -968,12 +966,12 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { for (List<Ref<CSharpScript>>::Element *E = to_reload.front(); E; E = E->next()) { Ref<CSharpScript> script = E->get(); - if (!script->get_path().empty()) { #ifdef TOOLS_ENABLED - script->exports_invalidated = true; + script->exports_invalidated = true; #endif - script->signals_invalidated = true; + script->signals_invalidated = true; + if (!script->get_path().empty()) { script->reload(p_soft_reload); if (!script->valid) { @@ -1949,6 +1947,7 @@ MonoObject *CSharpInstance::_internal_new_managed() { } void CSharpInstance::mono_object_disposed(MonoObject *p_obj) { + // Must make sure event signals are not left dangling disconnect_event_signals(); #ifdef DEBUG_ENABLED @@ -1964,6 +1963,9 @@ void CSharpInstance::mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_f CRASH_COND(gchandle.is_released()); #endif + // Must make sure event signals are not left dangling + disconnect_event_signals(); + r_remove_script_instance = false; if (_unreference_owner_unsafe()) { @@ -2223,6 +2225,9 @@ CSharpInstance::~CSharpInstance() { destructing_script_instance = true; + // Must make sure event signals are not left dangling + disconnect_event_signals(); + if (!gchandle.is_released()) { if (!predelete_notified && !ref_dying) { // This destructor is not called from the owners destructor. diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index cfe070a371..316fd78f2a 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -288,7 +288,7 @@ public: void mono_object_disposed(MonoObject *p_obj); /* - * If 'r_delete_owner' is set to true, the caller must memdelete the script instance's owner. Otherwise, ifevent_signal + * If 'r_delete_owner' is set to true, the caller must memdelete the script instance's owner. Otherwise, if * 'r_remove_script_instance' is set to true, the caller must destroy the script instance by removing it from its owner. */ void mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_finalizer, bool &r_delete_owner, bool &r_remove_script_instance); diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj index 86a0a4393e..8304d9e321 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj @@ -1,13 +1,13 @@ <Project Sdk="Microsoft.Build.NoTargets/2.0.1"> <PropertyGroup> <TargetFramework>netstandard2.0</TargetFramework> + <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> <Description>MSBuild .NET Sdk for Godot projects.</Description> <Authors>Godot Engine contributors</Authors> <PackageId>Godot.NET.Sdk</PackageId> <Version>4.0.0</Version> - <PackageVersion>4.0.0-dev2</PackageVersion> <PackageProjectUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk</PackageProjectUrl> <PackageType>MSBuildSdk</PackageType> <PackageTags>MSBuildSdk</PackageTags> @@ -19,7 +19,13 @@ <GenerateNuspecDependsOn>$(GenerateNuspecDependsOn);SetNuSpecProperties</GenerateNuspecDependsOn> </PropertyGroup> - <Target Name="SetNuSpecProperties" Condition=" Exists('$(NuspecFile)') "> + <Target Name="ReadGodotNETSdkVersion" BeforeTargets="BeforeBuild;BeforeRebuild;CoreCompile"> + <PropertyGroup> + <PackageVersion>$([System.IO.File]::ReadAllText('$(ProjectDir)Godot.NET.Sdk_PackageVersion.txt').Trim())</PackageVersion> + </PropertyGroup> + </Target> + + <Target Name="SetNuSpecProperties" Condition=" Exists('$(NuspecFile)') " DependsOnTargets="ReadGodotNETSdkVersion"> <PropertyGroup> <NuspecProperties> id=$(PackageId); @@ -32,4 +38,13 @@ </NuspecProperties> </PropertyGroup> </Target> + + <Target Name="CopyNupkgToSConsOutputDir" AfterTargets="Pack"> + <PropertyGroup> + <GodotSourceRootPath>$(SolutionDir)\..\..\..\..\</GodotSourceRootPath> + <GodotOutputDataDir>$(GodotSourceRootPath)\bin\GodotSharp\</GodotOutputDataDir> + </PropertyGroup> + <Copy SourceFiles="$(OutputPath)$(PackageId).$(PackageVersion).nupkg" + DestinationFolder="$(GodotOutputDataDir)Tools\nupkgs\" /> + </Target> </Project> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk_PackageVersion.txt b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk_PackageVersion.txt new file mode 100644 index 0000000000..34749489b9 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk_PackageVersion.txt @@ -0,0 +1 @@ +4.0.0-dev3 diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj index e4d6b2e010..37123ba2b2 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj @@ -10,6 +10,7 @@ </ItemGroup> <ItemGroup> <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj" /> + <ProjectReference Include="..\GodotTools.Shared\GodotTools.Shared.csproj" /> </ItemGroup> <!-- The Microsoft.Build.Runtime package is too problematic so we create a MSBuild.exe stub. The workaround described diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs index 01d7c99662..7d49d251dd 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs @@ -3,14 +3,13 @@ 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 const string GodotSdkVersionToUse = "4.0.0-dev2"; - - public static string GodotSdkAttrValue => $"Godot.NET.Sdk/{GodotSdkVersionToUse}"; + public static string GodotSdkAttrValue => $"Godot.NET.Sdk/{GeneratedGodotNupkgsVersions.GodotNETSdk}"; public static ProjectRootElement GenGameProject(string name) { diff --git a/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets b/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets new file mode 100644 index 0000000000..1d382dcb43 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets @@ -0,0 +1,36 @@ +<Project> + <!-- Generate C# file with the version of all the nupkgs bundled with Godot --> + + <Target Name="SetPropertiesForGenerateGodotNupkgsVersions"> + <PropertyGroup> + <GodotNETSdkPackageVersionFile>$(SolutionDir)..\Godot.NET.Sdk\Godot.NET.Sdk\Godot.NET.Sdk_PackageVersion.txt</GodotNETSdkPackageVersionFile> + <GeneratedGodotNupkgsVersionsFile>$(IntermediateOutputPath)GodotNupkgsVersions.g.cs</GeneratedGodotNupkgsVersionsFile> + </PropertyGroup> + </Target> + + <Target Name="GenerateGodotNupkgsVersionsFile" + DependsOnTargets="PrepareForBuild;_GenerateGodotNupkgsVersionsFile" + BeforeTargets="BeforeCompile;CoreCompile"> + <ItemGroup> + <Compile Include="$(GeneratedGodotNupkgsVersionsFile)" /> + <FileWrites Include="$(GeneratedGodotNupkgsVersionsFile)" /> + </ItemGroup> + </Target> + <Target Name="_GenerateGodotNupkgsVersionsFile" + DependsOnTargets="SetPropertiesForGenerateGodotNupkgsVersions" + Inputs="$(MSBuildProjectFile);@(GodotNETSdkPackageVersionFile)" + Outputs="$(GeneratedGodotNupkgsVersionsFile)"> + <PropertyGroup> + <GenerateGodotNupkgsVersionsCode><![CDATA[ +namespace $(RootNamespace) { + public class GeneratedGodotNupkgsVersions { + public const string GodotNETSdk = "$([System.IO.File]::ReadAllText('$(GodotNETSdkPackageVersionFile)').Trim())"%3b + } +} +]]></GenerateGodotNupkgsVersionsCode> + </PropertyGroup> + <WriteLinesToFile Lines="$(GenerateGodotNupkgsVersionsCode)" + File="$(GeneratedGodotNupkgsVersionsFile)" + Overwrite="True" WriteOnlyWhenDifferent="True" /> + </Target> +</Project> diff --git a/modules/mono/editor/GodotTools/GodotTools.Shared/GodotTools.Shared.csproj b/modules/mono/editor/GodotTools/GodotTools.Shared/GodotTools.Shared.csproj new file mode 100644 index 0000000000..3bc1698c15 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.Shared/GodotTools.Shared.csproj @@ -0,0 +1,6 @@ +<Project Sdk="Microsoft.NET.Sdk"> + <PropertyGroup> + <TargetFramework>netstandard2.0</TargetFramework> + </PropertyGroup> + <Import Project="GenerateGodotNupkgsVersions.targets" /> +</Project> diff --git a/modules/mono/editor/GodotTools/GodotTools.sln b/modules/mono/editor/GodotTools/GodotTools.sln index ba5379e562..d3107a69db 100644 --- a/modules/mono/editor/GodotTools/GodotTools.sln +++ b/modules/mono/editor/GodotTools/GodotTools.sln @@ -13,6 +13,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.IdeMessaging", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.OpenVisualStudio", "GodotTools.OpenVisualStudio\GodotTools.OpenVisualStudio.csproj", "{EAFFF236-FA96-4A4D-BD23-0E51EF988277}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.Shared", "GodotTools.Shared\GodotTools.Shared.csproj", "{2758FFAF-8237-4CF2-B569-66BF8B3587BB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -43,5 +45,9 @@ Global {EAFFF236-FA96-4A4D-BD23-0E51EF988277}.Debug|Any CPU.Build.0 = Debug|Any CPU {EAFFF236-FA96-4A4D-BD23-0E51EF988277}.Release|Any CPU.ActiveCfg = Release|Any CPU {EAFFF236-FA96-4A4D-BD23-0E51EF988277}.Release|Any CPU.Build.0 = Release|Any CPU + {2758FFAF-8237-4CF2-B569-66BF8B3587BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2758FFAF-8237-4CF2-B569-66BF8B3587BB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2758FFAF-8237-4CF2-B569-66BF8B3587BB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2758FFAF-8237-4CF2-B569-66BF8B3587BB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs index 9b512d90f5..b96b0c8175 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs @@ -7,7 +7,6 @@ using JetBrains.Annotations; using static GodotTools.Internals.Globals; using File = GodotTools.Utils.File; using OS = GodotTools.Utils.OS; -using Path = System.IO.Path; namespace GodotTools.Build { @@ -209,6 +208,16 @@ namespace GodotTools.Build if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) return true; // No solution to build + try + { + // Make sure our packages are added to the fallback folder + NuGetUtils.AddBundledPackagesToFallbackFolder(NuGetUtils.GodotFallbackFolderPath); + } + catch (Exception e) + { + Godot.GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message); + } + GenerateEditorScriptMetadata(); if (GodotSharpEditor.Instance.SkipBuildBeforePlaying) diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs index c22dce005c..708ec73454 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs @@ -33,6 +33,16 @@ namespace GodotTools.Build if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) return; // No solution to build + try + { + // Make sure our packages are added to the fallback folder + NuGetUtils.AddBundledPackagesToFallbackFolder(NuGetUtils.GodotFallbackFolderPath); + } + catch (Exception e) + { + GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message); + } + BuildManager.GenerateEditorScriptMetadata(); if (!BuildManager.BuildProjectBlocking("Debug")) @@ -54,6 +64,16 @@ namespace GodotTools.Build if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) return; // No solution to build + try + { + // Make sure our packages are added to the fallback folder + NuGetUtils.AddBundledPackagesToFallbackFolder(NuGetUtils.GodotFallbackFolderPath); + } + catch (Exception e) + { + GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message); + } + BuildManager.GenerateEditorScriptMetadata(); if (!BuildManager.BuildProjectBlocking("Debug", targets: new[] {"Rebuild"})) diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs b/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs new file mode 100644 index 0000000000..793ef7fd71 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs @@ -0,0 +1,296 @@ +using System; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Xml; +using Godot; +using GodotTools.Internals; +using GodotTools.Shared; +using Directory = GodotTools.Utils.Directory; +using Environment = System.Environment; +using File = GodotTools.Utils.File; + +namespace GodotTools.Build +{ + public static class NuGetUtils + { + public const string GodotFallbackFolderName = "Godot Offline Packages"; + + 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 Exception("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. + /// Does not determine whether the returned files exist or not. + /// </summary> + private static string[] GetAllUserNuGetConfigFilePaths() + { + // Where to find 'NuGet/NuGet.Config': + // + // - Mono/.NETFramework (standalone NuGet): + // Uses Environment.SpecialFolder.ApplicationData + // - Windows: '%APPDATA%' + // - Linux/macOS: '$HOME/.config' + // - CoreCLR (dotnet CLI NuGet): + // - Windows: '%APPDATA%' + // - Linux/macOS: '$DOTNET_CLI_HOME/.nuget' otherwise '$HOME/.nuget' + + string applicationData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + + if (Utils.OS.IsWindows) + { + // %APPDATA% for both + return new[] {Path.Combine(applicationData, "NuGet", "NuGet.Config")}; + } + + var paths = new string[2]; + + // CoreCLR (dotnet CLI NuGet) + + string dotnetCliHome = Environment.GetEnvironmentVariable("DOTNET_CLI_HOME"); + if (!string.IsNullOrEmpty(dotnetCliHome)) + { + paths[0] = Path.Combine(dotnetCliHome, ".nuget", "NuGet", "NuGet.Config"); + } + 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"); + } + + // Mono/.NETFramework (standalone NuGet) + + // ApplicationData is $HOME/.config on Linux/macOS + paths[1] = Path.Combine(applicationData, "NuGet", "NuGet.Config"); + + return paths; + } + + // nupkg extraction + // + // Exclude: (NuGet.Client -> NuGet.Packaging.PackageHelper.ExcludePaths) + // package/ + // _rels/ + // [Content_Types].xml + // + // Don't ignore files that begin with a dot (.) + // + // 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, + /// for both standalone NuGet (Mono/.NETFramework) and dotnet CLI NuGet. + /// </summary> + public static void AddFallbackFolderToUserNuGetConfigs(string name, string path) + { + foreach (string nuGetConfigPath in GetAllUserNuGetConfigFilePaths()) + { + 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""?> +<configuration> + <packageSources> + <add key=""nuget.org"" value=""https://api.nuget.org/v3/index.json"" protocolVersion=""3"" /> + </packageSources> +</configuration> +"; + System.IO.File.WriteAllText(nuGetConfigPath, defaultConfig, Encoding.UTF8); // UTF-8 with BOM + } + + AddFallbackFolderToNuGetConfig(nuGetConfigPath, name, path); + } + } + + private static void AddPackageToFallbackFolder(string fallbackFolder, + string nupkgPath, string packageId, string packageVersion) + { + // dotnet CLI provides no command for this, but we can do it manually. + // + // - The expected structure is as follows: + // fallback_folder/ + // <package.name>/<version>/ + // <package.name>.<version>.nupkg + // <package.name>.<version>.nupkg.sha512 + // <package.name>.nuspec + // ... extracted nupkg files (check code for excluded files) ... + // + // - <package.name> and <version> must be in lower case. + // - The sha512 of the nupkg is base64 encoded. + // - We can get the nuspec from the nupkg which is a Zip file. + + string packageIdLower = packageId.ToLower(); + string packageVersionLower = packageVersion.ToLower(); + + string destDir = Path.Combine(fallbackFolder, packageIdLower, packageVersionLower); + string nupkgDestPath = Path.Combine(destDir, $"{packageIdLower}.{packageVersionLower}.nupkg"); + string nupkgSha512DestPath = Path.Combine(destDir, $"{packageIdLower}.{packageVersionLower}.nupkg.sha512"); + + if (File.Exists(nupkgDestPath) && File.Exists(nupkgSha512DestPath)) + return; // Already added (for speed we don't check if every file is properly extracted) + + Directory.CreateDirectory(destDir); + + // Generate .nupkg.sha512 file + + using (var alg = SHA512.Create()) + { + alg.ComputeHash(File.ReadAllBytes(nupkgPath)); + string base64Hash = Convert.ToBase64String(alg.Hash); + File.WriteAllText(nupkgSha512DestPath, base64Hash); + } + + // Extract nupkg + ExtractNupkg(destDir, nupkgPath, packageId, packageVersion); + + // Copy .nupkg + File.Copy(nupkgPath, nupkgDestPath); + } + + private static readonly string[] NupkgExcludePaths = + { + "_rels/", + "package/", + "[Content_Types].xml" + }; + + private static void ExtractNupkg(string destDir, string nupkgPath, string packageId, string packageVersion) + { + // NOTE: Must use SimplifyGodotPath to make sure we don't extract files outside the destination directory. + + using (var archive = ZipFile.OpenRead(nupkgPath)) + { + // Extract .nuspec manually as it needs to be in lower case + + var nuspecEntry = archive.GetEntry(packageId + ".nuspec"); + + if (nuspecEntry == null) + throw new InvalidOperationException($"Failed to extract package {packageId}.{packageVersion}. Could not find the nuspec file."); + + nuspecEntry.ExtractToFile(Path.Combine(destDir, nuspecEntry.Name.ToLower().SimplifyGodotPath())); + + // Extract the other package files + + foreach (var entry in archive.Entries) + { + // NOTE: SimplifyGodotPath() removes trailing slash and backslash, + // so we can't use the result to check if the entry is a directory. + + string entryFullName = entry.FullName.Replace('\\', '/'); + + // Check if the file must be ignored + if ( // Excluded files. + NupkgExcludePaths.Any(e => entryFullName.StartsWith(e, StringComparison.OrdinalIgnoreCase)) || + // Nupkg hash files and nupkg metadata files on all directory. + 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")) + { + continue; + } + + string entryFullNameSimplified = entryFullName.SimplifyGodotPath(); + string destFilePath = Path.Combine(destDir, entryFullNameSimplified); + bool isDir = entryFullName.EndsWith("/"); + + if (isDir) + { + Directory.CreateDirectory(destFilePath); + } + else + { + Directory.CreateDirectory(Path.GetDirectoryName(destFilePath)); + entry.ExtractToFile(destFilePath, overwrite: true); + } + } + } + } + + /// <summary> + /// Copies and extracts all the Godot bundled packages to the Godot NuGet fallback folder. + /// Does nothing if the packages were already copied. + /// </summary> + public static void AddBundledPackagesToFallbackFolder(string fallbackFolder) + { + GD.Print("Copying Godot Offline Packages..."); + + string nupkgsLocation = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "nupkgs"); + + void AddPackage(string packageId, string packageVersion) + { + string nupkgPath = Path.Combine(nupkgsLocation, $"{packageId}.{packageVersion}.nupkg"); + AddPackageToFallbackFolder(fallbackFolder, nupkgPath, packageId, packageVersion); + } + + foreach (var (packageId, packageVersion) in PackagesToAdd) + AddPackage(packageId, packageVersion); + } + + private static readonly (string packageId, string packageVersion)[] PackagesToAdd = + { + ("Godot.NET.Sdk", GeneratedGodotNupkgsVersions.GodotNETSdk) + }; + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index f5c99e87ef..58561c5097 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -147,6 +147,21 @@ namespace GodotTools case MenuOptions.AboutCSharp: _ShowAboutDialog(); break; + case MenuOptions.SetupGodotNugetFallbackFolder: + { + try + { + string fallbackFolder = NuGetUtils.GodotFallbackFolderPath; + NuGetUtils.AddFallbackFolderToUserNuGetConfigs(NuGetUtils.GodotFallbackFolderName, fallbackFolder); + NuGetUtils.AddBundledPackagesToFallbackFolder(fallbackFolder); + } + catch (Exception e) + { + ShowErrorDialog("Failed to setup Godot NuGet Offline Packages: " + e.Message); + } + + break; + } default: throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid menu option"); } @@ -183,6 +198,7 @@ namespace GodotTools { CreateSln, AboutCSharp, + SetupGodotNugetFallbackFolder, } public void ShowErrorDialog(string message, string title = "Error") @@ -426,6 +442,7 @@ namespace GodotTools // TODO: Remove or edit this info dialog once Mono support is no longer in alpha { menuPopup.AddItem("About C# support".TTR(), (int)MenuOptions.AboutCSharp); + menuPopup.AddItem("Setup Godot NuGet Offline Packages".TTR(), (int)MenuOptions.SetupGodotNugetFallbackFolder); aboutDialog = new AcceptDialog(); editorBaseControl.AddChild(aboutDialog); aboutDialog.Title = "Important: C# support is not feature-complete"; @@ -535,6 +552,16 @@ namespace GodotTools exportPlugin.RegisterExportSettings(); exportPluginWeak = WeakRef(exportPlugin); + try + { + // At startup we make sure NuGet.Config files have our Godot NuGet fallback folder included + NuGetUtils.AddFallbackFolderToUserNuGetConfigs(NuGetUtils.GodotFallbackFolderName, NuGetUtils.GodotFallbackFolderPath); + } + catch (Exception e) + { + GD.PushError("Failed to add Godot NuGet Offline Packages to NuGet.Config: " + e.Message); + } + BuildManager.Initialize(); RiderPathManager.Initialize(); diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index a17c371117..4c137a2ba4 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -97,7 +97,7 @@ #define C_METHOD_MANAGED_TO_SIGNAL C_NS_MONOMARSHAL "::signal_info_to_callable" #define C_METHOD_MANAGED_FROM_SIGNAL C_NS_MONOMARSHAL "::callable_to_signal_info" -#define BINDINGS_GENERATOR_VERSION UINT32_C(11) +#define BINDINGS_GENERATOR_VERSION UINT32_C(12) const char *BindingsGenerator::TypeInterface::DEFAULT_VARARG_C_IN("\t%0 %1_in = %1;\n"); @@ -1368,7 +1368,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str output.append(")\n" OPEN_BLOCK_L2 "if (" BINDINGS_PTR_FIELD " == IntPtr.Zero)\n" INDENT4 BINDINGS_PTR_FIELD " = "); output.append(itype.api_type == ClassDB::API_EDITOR ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS); output.append("." + ctor_method); - output.append("(this);\n" CLOSE_BLOCK_L2); + output.append("(this);\n" INDENT3 "_InitializeGodotScriptInstanceInternals();\n" CLOSE_BLOCK_L2); } else { // Hide the constructor output.append(MEMBER_BEGIN "internal "); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs index 42610c5ef7..48582d5ad8 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs @@ -15,14 +15,13 @@ namespace Godot public Object() : this(false) { if (ptr == IntPtr.Zero) - { ptr = godot_icall_Object_Ctor(this); - } - else - { - // This is called inside godot_icall_Object_Ctor, so we must call it as well in this case. - godot_icall_Object_ConnectEventSignals(ptr); - } + _InitializeGodotScriptInstanceInternals(); + } + + internal void _InitializeGodotScriptInstanceInternals() + { + godot_icall_Object_ConnectEventSignals(ptr); } internal Object(bool memoryOwn) diff --git a/modules/mono/mono_gd/gd_mono_internals.cpp b/modules/mono/mono_gd/gd_mono_internals.cpp index fe1c2d28dd..0ed9e441ef 100644 --- a/modules/mono/mono_gd/gd_mono_internals.cpp +++ b/modules/mono/mono_gd/gd_mono_internals.cpp @@ -109,8 +109,6 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) { CSharpInstance *csharp_instance = CSharpInstance::create_for_managed_type(unmanaged, script.ptr(), gchandle); unmanaged->set_script_and_instance(script, csharp_instance); - - csharp_instance->connect_event_signals(); } void unhandled_exception(MonoException *p_exc) { |