summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--modules/mono/SCsub3
-rw-r--r--modules/mono/build_scripts/mono_configure.py44
-rw-r--r--modules/mono/csharp_script.cpp39
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ScriptBoilerplate.cs17
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs32
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs46
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj4
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props3
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs58
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptBoilerplateGenerator.cs187
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs5
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs3
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs11
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.sln6
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs44
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs221
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs4
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs204
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildTool.cs10
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/DotNetFinder.cs99
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs6
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs233
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs14
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs388
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs41
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj10
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs2
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs2
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs11
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs19
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs182
-rw-r--r--modules/mono/editor/bindings_generator.cpp81
-rw-r--r--modules/mono/editor/editor_internal_calls.cpp18
-rw-r--r--modules/mono/glue/GodotSharp/GodotPlugins/Main.cs93
-rw-r--r--modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/GodotMethodAttribute.cs24
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs27
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs9
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs66
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/GodotDllImportResolver.cs64
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs34
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Properties/AssemblyInfo.cs1
-rw-r--r--modules/mono/godotsharp_dirs.cpp4
-rw-r--r--modules/mono/mono_gd/gd_mono.cpp268
-rw-r--r--modules/mono/mono_gd/gd_mono.h44
-rw-r--r--modules/mono/mono_gd/gd_mono_cache.cpp2
-rw-r--r--modules/mono/mono_gd/gd_mono_cache.h6
-rw-r--r--modules/mono/utils/path_utils.cpp31
-rw-r--r--modules/mono/utils/path_utils.h2
51 files changed, 1536 insertions, 1192 deletions
diff --git a/modules/mono/SCsub b/modules/mono/SCsub
index 5757917f56..ab7a2bf761 100644
--- a/modules/mono/SCsub
+++ b/modules/mono/SCsub
@@ -7,9 +7,6 @@ Import("env_modules")
env_mono = env_modules.Clone()
-if env_mono["tools"] or env_mono["target"] != "release":
- env_mono.Append(CPPDEFINES=["GD_MONO_HOT_RELOAD"])
-
# Configure Mono
mono_configure.configure(env, env_mono)
diff --git a/modules/mono/build_scripts/mono_configure.py b/modules/mono/build_scripts/mono_configure.py
index 5d2030f756..39c9d58eee 100644
--- a/modules/mono/build_scripts/mono_configure.py
+++ b/modules/mono/build_scripts/mono_configure.py
@@ -25,7 +25,7 @@ def configure(env, env_mono):
if tools_enabled and not module_supports_tools_on(env["platform"]):
raise RuntimeError("This module does not currently support building for this platform with tools enabled")
- if env["tools"] or env["target"] != "release":
+ if env["tools"]:
env_mono.Append(CPPDEFINES=["GD_MONO_HOT_RELOAD"])
app_host_dir = find_dotnet_app_host_dir(env)
@@ -47,29 +47,33 @@ def configure(env, env_mono):
check_app_host_file_exists("hostfxr.h")
check_app_host_file_exists("coreclr_delegates.h")
- env.Append(LIBPATH=[app_host_dir])
env_mono.Prepend(CPPPATH=app_host_dir)
- libnethost_path = os.path.join(app_host_dir, "libnethost.lib" if os.name == "nt" else "libnethost.a")
+ env.Append(LIBPATH=[app_host_dir])
+
+ # Only the editor build links nethost, which is needed to find hostfxr.
+ # Exported games don't need this logic as hostfxr is bundled with them.
+ if tools_enabled:
+ libnethost_path = os.path.join(app_host_dir, "libnethost.lib" if os.name == "nt" else "libnethost.a")
- if env["platform"] == "windows":
- env_mono.Append(CPPDEFINES=["NETHOST_USE_AS_STATIC"])
+ if env["platform"] == "windows":
+ env_mono.Append(CPPDEFINES=["NETHOST_USE_AS_STATIC"])
- if env.msvc:
- env.Append(LINKFLAGS="libnethost.lib")
+ if env.msvc:
+ env.Append(LINKFLAGS="libnethost.lib")
+ else:
+ env.Append(LINKFLAGS=["-Wl,-whole-archive", libnethost_path, "-Wl,-no-whole-archive"])
else:
- env.Append(LINKFLAGS=["-Wl,-whole-archive", libnethost_path, "-Wl,-no-whole-archive"])
- else:
- is_apple = env["platform"] in ["macos", "ios"]
- # is_macos = is_apple and not is_ios
+ is_apple = env["platform"] in ["macos", "ios"]
+ # is_macos = is_apple and not is_ios
- # if is_ios and not is_ios_sim:
- # env_mono.Append(CPPDEFINES=["IOS_DEVICE"])
+ # if is_ios and not is_ios_sim:
+ # env_mono.Append(CPPDEFINES=["IOS_DEVICE"])
- if is_apple:
- env.Append(LINKFLAGS=["-Wl,-force_load," + libnethost_path])
- else:
- env.Append(LINKFLAGS=["-Wl,-whole-archive", libnethost_path, "-Wl,-no-whole-archive"])
+ if is_apple:
+ env.Append(LINKFLAGS=["-Wl,-force_load," + libnethost_path])
+ else:
+ env.Append(LINKFLAGS=["-Wl,-whole-archive", libnethost_path, "-Wl,-no-whole-archive"])
def find_dotnet_app_host_dir(env):
@@ -156,7 +160,8 @@ def find_app_host_version(dotnet_cmd, search_version_str):
search_version = LooseVersion(search_version_str)
try:
- lines = subprocess.check_output([dotnet_cmd, "--list-runtimes"]).splitlines()
+ env = dict(os.environ, DOTNET_CLI_UI_LANGUAGE="en-US")
+ lines = subprocess.check_output([dotnet_cmd, "--list-runtimes"], env=env).splitlines()
for line_bytes in lines:
line = line_bytes.decode("utf-8")
@@ -188,7 +193,8 @@ def find_dotnet_sdk(dotnet_cmd, search_version_str):
search_version = LooseVersion(search_version_str)
try:
- lines = subprocess.check_output([dotnet_cmd, "--list-sdks"]).splitlines()
+ env = dict(os.environ, DOTNET_CLI_UI_LANGUAGE="en-US")
+ lines = subprocess.check_output([dotnet_cmd, "--list-sdks"], env=env).splitlines()
for line_bytes in lines:
line = line_bytes.decode("utf-8")
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index 52b0e82c6e..ade436d3e0 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -107,7 +107,7 @@ Error CSharpLanguage::execute_file(const String &p_path) {
extern void *godotsharp_pinvoke_funcs[178];
[[maybe_unused]] volatile void **do_not_strip_godotsharp_pinvoke_funcs;
#ifdef TOOLS_ENABLED
-extern void *godotsharp_editor_pinvoke_funcs[32];
+extern void *godotsharp_editor_pinvoke_funcs[30];
[[maybe_unused]] volatile void **do_not_strip_godotsharp_editor_pinvoke_funcs;
#endif
@@ -137,11 +137,11 @@ void CSharpLanguage::init() {
gdmono = memnew(GDMono);
gdmono->initialize();
+#ifdef TOOLS_ENABLED
if (gdmono->is_runtime_initialized()) {
gdmono->initialize_load_assemblies();
}
-#ifdef TOOLS_ENABLED
EditorNode::add_init_callback(&_editor_init_callback);
#endif
}
@@ -788,6 +788,11 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
}
#warning TODO ALCs after switching to .NET 6
+
+ // Try to load the project assembly if it was not yet loaded
+ // (while hot-reload is not yet implemented)
+ gdmono->initialize_load_assemblies();
+
#if 0
// There is no soft reloading with Mono. It's always hard reloading.
@@ -1230,7 +1235,7 @@ void CSharpLanguage::_on_scripts_domain_about_to_unload() {
void CSharpLanguage::_editor_init_callback() {
// Load GodotTools and initialize GodotSharpEditor
- Object *editor_plugin_obj = GDMono::get_singleton()->plugin_callbacks.LoadToolsAssemblyCallback(
+ Object *editor_plugin_obj = GDMono::get_singleton()->get_plugin_callbacks().LoadToolsAssemblyCallback(
GodotSharpDirs::get_data_editor_tools_dir().plus_file("GodotTools.dll").utf16());
CRASH_COND(editor_plugin_obj == nullptr);
@@ -1836,11 +1841,8 @@ bool CSharpInstance::has_method(const StringName &p_method) const {
return false;
}
- String method = p_method;
- bool deep = true;
-
- return GDMonoCache::managed_callbacks.ScriptManagerBridge_HasMethodUnknownParams(
- script.ptr(), &method, deep);
+ return GDMonoCache::managed_callbacks.CSharpInstanceBridge_HasMethodUnknownParams(
+ gchandle.get_intptr(), &p_method);
}
Variant CSharpInstance::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
@@ -2954,21 +2956,9 @@ void CSharpScript::get_script_method_list(List<MethodInfo> *p_list) const {
}
bool CSharpScript::has_method(const StringName &p_method) const {
- if (!valid) {
- return false;
- }
-
- if (!GDMonoCache::godot_api_cache_updated) {
- return false;
- }
-
- String method = p_method;
- bool deep = false;
-
- bool found = GDMonoCache::managed_callbacks.ScriptManagerBridge_HasMethodUnknownParams(
- this, &method, deep);
-
- return found;
+ // The equivalent of this will be implemented once we switch to the GDExtension system
+ ERR_PRINT_ONCE("CSharpScript::has_method is not implemented");
+ return false;
}
MethodInfo CSharpScript::get_method_info(const StringName &p_method) const {
@@ -3004,6 +2994,9 @@ Error CSharpScript::reload(bool p_keep_state) {
String script_path = get_path();
+ // In case it was already added by a previous reload
+ GDMonoCache::managed_callbacks.ScriptManagerBridge_RemoveScriptBridge(this);
+
valid = GDMonoCache::managed_callbacks.ScriptManagerBridge_AddScriptBridge(this, &script_path);
if (valid) {
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 5a499742e9..9bc134818a 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
@@ -104,11 +104,9 @@
Godot scripting API is continuaslly breaking backwards compatibility even in patch releases.
-->
<Reference Include="GodotSharp">
- <Private>false</Private>
<HintPath>$(GodotProjectDir).godot\mono\assemblies\$(GodotApiConfiguration)\GodotSharp.dll</HintPath>
</Reference>
<Reference Include="GodotSharpEditor" Condition=" '$(Configuration)' == 'Debug' ">
- <Private>false</Private>
<HintPath>$(GodotProjectDir).godot\mono\assemblies\$(GodotApiConfiguration)\GodotSharpEditor.dll</HintPath>
</Reference>
</ItemGroup>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ScriptBoilerplate.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ScriptBoilerplate.cs
index 05723c8940..0c7328284e 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ScriptBoilerplate.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ScriptBoilerplate.cs
@@ -1,6 +1,8 @@
+using System;
+
namespace Godot.SourceGenerators.Sample
{
- public partial class ScriptBoilerplate : Godot.Node
+ public partial class ScriptBoilerplate : Node
{
private NodePath _nodePath;
private int _velocity;
@@ -17,5 +19,18 @@ namespace Godot.SourceGenerators.Sample
_ = name;
return 1;
}
+
+ public void IgnoreThisMethodWithByRefParams(ref int a)
+ {
+ _ = a;
+ }
+ }
+
+ partial struct OuterClass
+ {
+ public partial class NesterClass : RefCounted
+ {
+ public override object _Get(StringName property) => null;
+ }
}
}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs
index 4dd67252ed..fa41c85322 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs
@@ -28,5 +28,37 @@ namespace Godot.SourceGenerators
cds.GetLocation(),
cds.SyntaxTree.FilePath));
}
+
+ public static void ReportNonPartialGodotScriptOuterClass(
+ GeneratorExecutionContext context,
+ TypeDeclarationSyntax outerTypeDeclSyntax
+ )
+ {
+ var outerSymbol = context.Compilation
+ .GetSemanticModel(outerTypeDeclSyntax.SyntaxTree)
+ .GetDeclaredSymbol(outerTypeDeclSyntax);
+
+ string fullQualifiedName = outerSymbol is INamedTypeSymbol namedTypeSymbol ?
+ namedTypeSymbol.FullQualifiedName() :
+ "type not found";
+
+ string message =
+ $"Missing partial modifier on declaration of type '{fullQualifiedName}', " +
+ $"which contains one or more subclasses of '{GodotClasses.Object}'";
+
+ string description = $"{message}. Subclasses of '{GodotClasses.Object}' and their " +
+ "containing types must be declared with the partial modifier.";
+
+ context.ReportDiagnostic(Diagnostic.Create(
+ new DiagnosticDescriptor(id: "GODOT-G0002",
+ title: message,
+ messageFormat: message,
+ category: "Usage",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description),
+ outerTypeDeclSyntax.GetLocation(),
+ outerTypeDeclSyntax.SyntaxTree.FilePath));
+ }
}
}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs
index f8c50e66c8..9586e71d02 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs
@@ -19,6 +19,11 @@ namespace Godot.SourceGenerators
toggle != null &&
toggle.Equals("disabled", StringComparison.OrdinalIgnoreCase);
+ public static bool IsGodotToolsProject(this GeneratorExecutionContext context)
+ => context.TryGetGlobalAnalyzerProperty("IsGodotToolsProject", out string? toggle) &&
+ toggle != null &&
+ toggle.Equals("true", StringComparison.OrdinalIgnoreCase);
+
private static bool InheritsFrom(this INamedTypeSymbol? symbol, string baseName)
{
if (symbol == null)
@@ -75,9 +80,48 @@ namespace Godot.SourceGenerators
}
}
- public static bool IsPartial(this ClassDeclarationSyntax cds)
+ public static bool IsNested(this TypeDeclarationSyntax cds)
+ => cds.Parent is TypeDeclarationSyntax;
+
+ public static bool IsPartial(this TypeDeclarationSyntax cds)
=> cds.Modifiers.Any(SyntaxKind.PartialKeyword);
+ public static bool AreAllOuterTypesPartial(
+ this TypeDeclarationSyntax cds,
+ out TypeDeclarationSyntax? typeMissingPartial
+ )
+ {
+ SyntaxNode? outerSyntaxNode = cds.Parent;
+
+ while (outerSyntaxNode is TypeDeclarationSyntax outerTypeDeclSyntax)
+ {
+ if (!outerTypeDeclSyntax.IsPartial())
+ {
+ typeMissingPartial = outerTypeDeclSyntax;
+ return false;
+ }
+
+ outerSyntaxNode = outerSyntaxNode.Parent;
+ }
+
+ typeMissingPartial = null;
+ return true;
+ }
+
+ public static string GetDeclarationKeyword(this INamedTypeSymbol namedTypeSymbol)
+ {
+ string? keyword = namedTypeSymbol.DeclaringSyntaxReferences
+ .OfType<TypeDeclarationSyntax>().FirstOrDefault()?
+ .Keyword.Text;
+
+ return keyword ?? namedTypeSymbol.TypeKind switch
+ {
+ TypeKind.Interface => "interface",
+ TypeKind.Struct => "struct",
+ _ => "class"
+ };
+ }
+
private static SymbolDisplayFormat FullyQualifiedFormatOmitGlobal { get; } =
SymbolDisplayFormat.FullyQualifiedFormat
.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted);
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj
index 11d8e0f72b..791ad85572 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj
@@ -21,14 +21,14 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" />
- <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.1" PrivateAssets="all" />
+ <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<!-- Package the generator in the analyzer directory of the nuget package -->
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
<!-- Package the props file -->
- <None Include="Godot.SourceGenerators.props" Pack="true" PackagePath="build" Visible="false" />
+ <None Include="Godot.SourceGenerators.props" Pack="true" PackagePath="build" Visible="true" />
</ItemGroup>
<Target Name="CopyNupkgToSConsOutputDir" AfterTargets="Pack">
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props
index 5025215d34..7881ed0a8c 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props
@@ -2,6 +2,7 @@
<ItemGroup>
<!-- $(GodotProjectDir) is defined by Godot.NET.Sdk -->
<CompilerVisibleProperty Include="GodotProjectDir" />
- <CompilerVisibleProperty Include="EnableGodotGenerators" />
+ <CompilerVisibleProperty Include="GodotSourceGenerators" />
+ <CompilerVisibleProperty Include="IsGodotToolsProject" />
</ItemGroup>
</Project>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs
new file mode 100644
index 0000000000..497a1b908c
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs
@@ -0,0 +1,58 @@
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Godot.SourceGenerators
+{
+ [Generator]
+ public class GodotPluginsInitializerGenerator : ISourceGenerator
+ {
+ public void Initialize(GeneratorInitializationContext context)
+ {
+ }
+
+ public void Execute(GeneratorExecutionContext context)
+ {
+ if (context.IsGodotToolsProject())
+ return;
+
+ string source =
+ @"using System;
+using System.Runtime.InteropServices;
+using Godot.Bridge;
+using Godot.NativeInterop;
+
+namespace GodotPlugins.Game
+{
+ internal static partial class Main
+ {
+ [UnmanagedCallersOnly]
+ private static godot_bool InitializeFromGameProject(IntPtr outManagedCallbacks)
+ {
+ try
+ {
+ var coreApiAssembly = typeof(Godot.Object).Assembly;
+
+ NativeLibrary.SetDllImportResolver(coreApiAssembly, GodotDllImportResolver.OnResolveDllImport);
+
+ ManagedCallbacks.Create(outManagedCallbacks);
+
+ ScriptManagerBridge.LookupScriptsInAssembly(typeof(GodotPlugins.Game.Main).Assembly);
+
+ return godot_bool.True;
+ }
+ catch (Exception e)
+ {
+ Console.Error.WriteLine(e);
+ return false.ToGodotBool();
+ }
+ }
+ }
+}
+";
+
+ context.AddSource("GodotPlugins.Game_Generated",
+ SourceText.From(source, Encoding.UTF8));
+ }
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptBoilerplateGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptBoilerplateGenerator.cs
index 5ace809d95..51e9406c15 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptBoilerplateGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptBoilerplateGenerator.cs
@@ -16,8 +16,6 @@ namespace Godot.SourceGenerators
if (context.AreGodotSourceGeneratorsDisabled())
return;
- // False positive for RS1024. We're already using `SymbolEqualityComparer.Default`...
-#pragma warning disable RS1024
INamedTypeSymbol[] godotClasses = context
.Compilation.SyntaxTrees
.SelectMany(tree =>
@@ -28,7 +26,16 @@ namespace Godot.SourceGenerators
.Where(x =>
{
if (x.cds.IsPartial())
+ {
+ if (x.cds.IsNested() && !x.cds.AreAllOuterTypesPartial(out var typeMissingPartial))
+ {
+ Common.ReportNonPartialGodotScriptOuterClass(context, typeMissingPartial!);
+ return false;
+ }
+
return true;
+ }
+
Common.ReportNonPartialGodotScriptClass(context, x.cds, x.symbol);
return false;
})
@@ -36,7 +43,6 @@ namespace Godot.SourceGenerators
)
.Distinct<INamedTypeSymbol>(SymbolEqualityComparer.Default)
.ToArray();
-#pragma warning restore RS1024
if (godotClasses.Length > 0)
{
@@ -63,6 +69,8 @@ namespace Godot.SourceGenerators
string.Empty;
bool hasNamespace = classNs.Length != 0;
+ bool isInnerClass = symbol.ContainingType != null;
+
string uniqueName = hasNamespace ?
classNs + "." + className + "_ScriptBoilerplate_Generated" :
className + "_ScriptBoilerplate_Generated";
@@ -80,6 +88,22 @@ namespace Godot.SourceGenerators
source.Append(" {\n\n");
}
+ if (isInnerClass)
+ {
+ var containingType = symbol.ContainingType;
+
+ while (containingType != null)
+ {
+ source.Append("partial ");
+ source.Append(containingType.GetDeclarationKeyword());
+ source.Append(" ");
+ source.Append(containingType.Name);
+ source.Append("\n{\n");
+
+ containingType = containingType.ContainingType;
+ }
+ }
+
source.Append("partial class ");
source.Append(className);
source.Append("\n{\n");
@@ -102,15 +126,15 @@ namespace Godot.SourceGenerators
.Cast<IFieldSymbol>()
.Where(p => !p.IsImplicitlyDeclared);
- var methods = WhereHasCompatibleGodotType(methodSymbols, typeCache).ToArray();
- var properties = WhereIsCompatibleGodotType(propertySymbols, typeCache).ToArray();
- var fields = WhereIsCompatibleGodotType(fieldSymbols, typeCache).ToArray();
+ var godotClassMethods = WhereHasCompatibleGodotType(methodSymbols, typeCache).ToArray();
+ var godotClassProperties = WhereIsCompatibleGodotType(propertySymbols, typeCache).ToArray();
+ var godotClassFields = WhereIsCompatibleGodotType(fieldSymbols, typeCache).ToArray();
source.Append(" private class GodotInternal {\n");
// Generate cached StringNames for methods and properties, for fast lookup
- foreach (var method in methods)
+ foreach (var method in godotClassMethods)
{
string methodName = method.Method.Name;
source.Append(" public static readonly StringName MethodName_");
@@ -120,7 +144,7 @@ namespace Godot.SourceGenerators
source.Append("\";\n");
}
- foreach (var property in properties)
+ foreach (var property in godotClassProperties)
{
string propertyName = property.Property.Name;
source.Append(" public static readonly StringName PropName_");
@@ -130,7 +154,7 @@ namespace Godot.SourceGenerators
source.Append("\";\n");
}
- foreach (var field in fields)
+ foreach (var field in godotClassFields)
{
string fieldName = field.Field.Name;
source.Append(" public static readonly StringName PropName_");
@@ -140,14 +164,16 @@ namespace Godot.SourceGenerators
source.Append("\";\n");
}
- source.Append(" }\n");
+ source.Append(" }\n"); // class GodotInternal
+
+ // Generate InvokeGodotClassMethod
- if (methods.Length > 0)
+ if (godotClassMethods.Length > 0)
{
source.Append(" protected override bool InvokeGodotClassMethod(in godot_string_name method, ");
source.Append("NativeVariantPtrArgs args, int argCount, out godot_variant ret)\n {\n");
- foreach (var method in methods)
+ foreach (var method in godotClassMethods)
{
GenerateMethodInvoker(method, source);
}
@@ -157,42 +183,64 @@ namespace Godot.SourceGenerators
source.Append(" }\n");
}
- if (properties.Length > 0 || fields.Length > 0)
+ // Generate Set/GetGodotClassPropertyValue
+
+ if (godotClassProperties.Length > 0 || godotClassFields.Length > 0)
{
+ bool isFirstEntry;
+
// Setters
- source.Append(" protected override bool SetGodotClassPropertyValue(in godot_string_name name, ");
- source.Append("in godot_variant value)\n {\n");
+ bool allPropertiesAreReadOnly = godotClassFields.All(fi => fi.Field.IsReadOnly) &&
+ godotClassProperties.All(pi => pi.Property.IsReadOnly);
- foreach (var property in properties)
+ if (!allPropertiesAreReadOnly)
{
- GeneratePropertySetter(property.Property.Name,
- property.Property.Type.FullQualifiedName(), source);
- }
+ source.Append(" protected override bool SetGodotClassPropertyValue(in godot_string_name name, ");
+ source.Append("in godot_variant value)\n {\n");
- foreach (var field in fields)
- {
- GeneratePropertySetter(field.Field.Name,
- field.Field.Type.FullQualifiedName(), source);
- }
+ isFirstEntry = true;
+ foreach (var property in godotClassProperties)
+ {
+ if (property.Property.IsReadOnly)
+ continue;
- source.Append(" return base.SetGodotClassPropertyValue(name, value);\n");
+ GeneratePropertySetter(property.Property.Name,
+ property.Property.Type.FullQualifiedName(), source, isFirstEntry);
+ isFirstEntry = false;
+ }
- source.Append(" }\n");
+ foreach (var field in godotClassFields)
+ {
+ if (field.Field.IsReadOnly)
+ continue;
+
+ GeneratePropertySetter(field.Field.Name,
+ field.Field.Type.FullQualifiedName(), source, isFirstEntry);
+ isFirstEntry = false;
+ }
+
+ source.Append(" return base.SetGodotClassPropertyValue(name, value);\n");
+
+ source.Append(" }\n");
+ }
// Getters
source.Append(" protected override bool GetGodotClassPropertyValue(in godot_string_name name, ");
source.Append("out godot_variant value)\n {\n");
- foreach (var property in properties)
+ isFirstEntry = true;
+ foreach (var property in godotClassProperties)
{
- GeneratePropertyGetter(property.Property.Name, source);
+ GeneratePropertyGetter(property.Property.Name, source, isFirstEntry);
+ isFirstEntry = false;
}
- foreach (var field in fields)
+ foreach (var field in godotClassFields)
{
- GeneratePropertyGetter(field.Field.Name, source);
+ GeneratePropertyGetter(field.Field.Name, source, isFirstEntry);
+ isFirstEntry = false;
}
source.Append(" return base.GetGodotClassPropertyValue(name, out value);\n");
@@ -200,7 +248,37 @@ namespace Godot.SourceGenerators
source.Append(" }\n");
}
- source.Append("}\n");
+ // Generate HasGodotClassMethod
+
+ if (godotClassMethods.Length > 0)
+ {
+ source.Append(" protected override bool HasGodotClassMethod(in godot_string_name method)\n {\n");
+
+ bool isFirstEntry = true;
+ foreach (var method in godotClassMethods)
+ {
+ GenerateHasMethodEntry(method, source, isFirstEntry);
+ isFirstEntry = false;
+ }
+
+ source.Append(" return base.HasGodotClassMethod(method);\n");
+
+ source.Append(" }\n");
+ }
+
+ source.Append("}\n"); // partial class
+
+ if (isInnerClass)
+ {
+ var containingType = symbol.ContainingType;
+
+ while (containingType != null)
+ {
+ source.Append("}\n"); // outer class
+
+ containingType = containingType.ContainingType;
+ }
+ }
if (hasNamespace)
{
@@ -269,10 +347,14 @@ namespace Godot.SourceGenerators
private static void GeneratePropertySetter(
string propertyMemberName,
string propertyTypeQualifiedName,
- StringBuilder source
+ StringBuilder source,
+ bool isFirstEntry
)
{
- source.Append(" if (name == GodotInternal.PropName_");
+ source.Append(" ");
+ if (!isFirstEntry)
+ source.Append("else ");
+ source.Append("if (name == GodotInternal.PropName_");
source.Append(propertyMemberName);
source.Append(") {\n");
@@ -295,10 +377,14 @@ namespace Godot.SourceGenerators
private static void GeneratePropertyGetter(
string propertyMemberName,
- StringBuilder source
+ StringBuilder source,
+ bool isFirstEntry
)
{
- source.Append(" if (name == GodotInternal.PropName_");
+ source.Append(" ");
+ if (!isFirstEntry)
+ source.Append("else ");
+ source.Append("if (name == GodotInternal.PropName_");
source.Append(propertyMemberName);
source.Append(") {\n");
@@ -312,6 +398,22 @@ namespace Godot.SourceGenerators
source.Append(" }\n");
}
+ private static void GenerateHasMethodEntry(
+ GodotMethodInfo method,
+ StringBuilder source,
+ bool isFirstEntry
+ )
+ {
+ string methodName = method.Method.Name;
+
+ source.Append(" ");
+ if (!isFirstEntry)
+ source.Append("else ");
+ source.Append("if (method == GodotInternal.MethodName_");
+ source.Append(methodName);
+ source.Append(") {\n return true;\n }\n");
+ }
+
public void Initialize(GeneratorInitializationContext context)
{
}
@@ -376,12 +478,17 @@ namespace Godot.SourceGenerators
var parameters = method.Parameters;
- var paramTypes = parameters.Select(p =>
- MarshalUtils.ConvertManagedTypeToVariantType(p.Type, typeCache))
+ var paramTypes = parameters
+ // Currently we don't support `ref`, `out`, `in`, `ref readonly` parameters (and we never may)
+ .Where(p => p.RefKind == RefKind.None)
+ // Attempt to determine the variant type
+ .Select(p => MarshalUtils.ConvertManagedTypeToVariantType(p.Type, typeCache))
+ // Discard parameter types that couldn't be determined (null entries)
.Where(t => t != null).Cast<MarshalType>().ToImmutableArray();
+ // If any parameter type was incompatible, it was discarded so the length won't match
if (parameters.Length > paramTypes.Length)
- continue; // Some param types weren't compatible
+ continue; // Ignore incompatible method
yield return new GodotMethodInfo(method, paramTypes, parameters
.Select(p => p.Type).ToImmutableArray(), retType);
@@ -395,6 +502,10 @@ namespace Godot.SourceGenerators
{
foreach (var property in properties)
{
+ // Ignore properties without a getter. Godot properties must be readable.
+ if (property.IsWriteOnly)
+ continue;
+
var marshalType = MarshalUtils.ConvertManagedTypeToVariantType(property.Type, typeCache);
if (marshalType == null)
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs
index f0b2758342..b625287087 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs
@@ -17,6 +17,9 @@ namespace Godot.SourceGenerators
if (context.AreGodotSourceGeneratorsDisabled())
return;
+ if (context.IsGodotToolsProject())
+ return;
+
// NOTE: NotNullWhen diagnostics don't work on projects targeting .NET Standard 2.0
// ReSharper disable once ReplaceWithStringIsNullOrEmpty
if (!context.TryGetGlobalAnalyzerProperty("GodotProjectDir", out string? godotProjectDir)
@@ -31,7 +34,7 @@ namespace Godot.SourceGenerators
tree.GetRoot().DescendantNodes()
.OfType<ClassDeclarationSyntax>()
// Ignore inner classes
- .Where(cds => !(cds.Parent is ClassDeclarationSyntax))
+ .Where(cds => !cds.IsNested())
.SelectGodotScriptClasses(context.Compilation)
// Report and skip non-partial classes
.Where(x =>
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
index c549cf5f12..d921e2f6a2 100644
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
@@ -1,4 +1,5 @@
using System;
+using System.Globalization;
using System.IO;
using System.Text;
using Microsoft.Build.Construction;
@@ -45,7 +46,7 @@ namespace GodotTools.ProjectEditor
// Save (without BOM)
root.Save(path, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
- return Guid.NewGuid().ToString().ToUpper();
+ return Guid.NewGuid().ToString().ToUpperInvariant();
}
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs
index 9b921c517c..7b1d5c228a 100644
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs
@@ -19,8 +19,15 @@ namespace GodotTools.ProjectEditor
public static class ProjectUtils
{
- public static void MSBuildLocatorRegisterDefaults()
- => Microsoft.Build.Locator.MSBuildLocator.RegisterDefaults();
+ public static void MSBuildLocatorRegisterDefaults(out Version version, out string path)
+ {
+ var instance = Microsoft.Build.Locator.MSBuildLocator.RegisterDefaults();
+ version = instance.Version;
+ path = instance.MSBuildPath;
+ }
+
+ public static void MSBuildLocatorRegisterMSBuildPath(string msbuildPath)
+ => Microsoft.Build.Locator.MSBuildLocator.RegisterMSBuildPath(msbuildPath);
public static MSBuildProject Open(string path)
{
diff --git a/modules/mono/editor/GodotTools/GodotTools.sln b/modules/mono/editor/GodotTools/GodotTools.sln
index 415e49b426..f3d2f90f39 100644
--- a/modules/mono/editor/GodotTools/GodotTools.sln
+++ b/modules/mono/editor/GodotTools/GodotTools.sln
@@ -15,6 +15,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.OpenVisualStudio
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.Shared", "GodotTools.Shared\GodotTools.Shared.csproj", "{2758FFAF-8237-4CF2-B569-66BF8B3587BB}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators", "..\Godot.NET.Sdk\Godot.SourceGenerators\Godot.SourceGenerators.csproj", "{D8C421B2-8911-41EB-B983-F675C7141EB7}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -49,5 +51,9 @@ Global
{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
+ {D8C421B2-8911-41EB-B983-F675C7141EB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D8C421B2-8911-41EB-B983-F675C7141EB7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D8C421B2-8911-41EB-B983-F675C7141EB7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D8C421B2-8911-41EB-B983-F675C7141EB7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs
index 28bf57dc21..18073d6492 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs
@@ -4,26 +4,36 @@ using Godot.Collections;
using GodotTools.Internals;
using Path = System.IO.Path;
+#nullable enable
+
namespace GodotTools.Build
{
[Serializable]
- public sealed class BuildInfo : RefCounted // TODO Remove RefCounted once we have proper serialization
+ public sealed partial class BuildInfo : RefCounted // TODO Remove RefCounted once we have proper serialization
{
public string Solution { get; }
- public string[] Targets { get; }
public string Configuration { get; }
+ public string? RuntimeIdentifier { get; }
+ public string? PublishOutputDir { get; }
public bool Restore { get; }
+ public bool Rebuild { get; }
+ public bool OnlyClean { get; }
+
// TODO Use List once we have proper serialization
public Array<string> CustomProperties { get; } = new Array<string>();
- public string LogsDirPath => Path.Combine(GodotSharpDirs.BuildLogsDirs, $"{Solution.MD5Text()}_{Configuration}");
+ public string LogsDirPath =>
+ Path.Combine(GodotSharpDirs.BuildLogsDirs, $"{Solution.MD5Text()}_{Configuration}");
- public override bool Equals(object obj)
+ public override bool Equals(object? obj)
{
if (obj is BuildInfo other)
- return other.Solution == Solution && other.Targets == Targets &&
- other.Configuration == Configuration && other.Restore == Restore &&
- other.CustomProperties == CustomProperties && other.LogsDirPath == LogsDirPath;
+ return other.Solution == Solution &&
+ other.Configuration == Configuration && other.RuntimeIdentifier == RuntimeIdentifier &&
+ other.PublishOutputDir == PublishOutputDir && other.Restore == Restore &&
+ other.Rebuild == Rebuild && other.OnlyClean == OnlyClean &&
+ other.CustomProperties == CustomProperties &&
+ other.LogsDirPath == LogsDirPath;
return false;
}
@@ -34,25 +44,37 @@ namespace GodotTools.Build
{
int hash = 17;
hash = (hash * 29) + Solution.GetHashCode();
- hash = (hash * 29) + Targets.GetHashCode();
hash = (hash * 29) + Configuration.GetHashCode();
+ hash = (hash * 29) + (RuntimeIdentifier?.GetHashCode() ?? 0);
+ hash = (hash * 29) + (PublishOutputDir?.GetHashCode() ?? 0);
hash = (hash * 29) + Restore.GetHashCode();
+ hash = (hash * 29) + Rebuild.GetHashCode();
+ hash = (hash * 29) + OnlyClean.GetHashCode();
hash = (hash * 29) + CustomProperties.GetHashCode();
hash = (hash * 29) + LogsDirPath.GetHashCode();
return hash;
}
}
- private BuildInfo()
+ public BuildInfo(string solution, string configuration, bool restore, bool rebuild, bool onlyClean)
{
+ Solution = solution;
+ Configuration = configuration;
+ Restore = restore;
+ Rebuild = rebuild;
+ OnlyClean = onlyClean;
}
- public BuildInfo(string solution, string[] targets, string configuration, bool restore)
+ public BuildInfo(string solution, string configuration, string runtimeIdentifier,
+ string publishOutputDir, bool restore, bool rebuild, bool onlyClean)
{
Solution = solution;
- Targets = targets;
Configuration = configuration;
+ RuntimeIdentifier = runtimeIdentifier;
+ PublishOutputDir = publishOutputDir;
Restore = restore;
+ Rebuild = rebuild;
+ OnlyClean = onlyClean;
}
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
index 58677625c6..43256953f5 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
@@ -2,11 +2,9 @@ using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading.Tasks;
-using GodotTools.Ides.Rider;
+using Godot;
using GodotTools.Internals;
-using static GodotTools.Internals.Globals;
using File = GodotTools.Utils.File;
-using OS = GodotTools.Utils.OS;
namespace GodotTools.Build
{
@@ -14,13 +12,8 @@ namespace GodotTools.Build
{
private static BuildInfo _buildInProgress;
- public const string PropNameMSBuildMono = "MSBuild (Mono)";
- public const string PropNameMSBuildVs = "MSBuild (VS Build Tools)";
- public const string PropNameMSBuildJetBrains = "MSBuild (JetBrains Rider)";
- public const string PropNameDotnetCli = "dotnet CLI";
-
public const string MsBuildIssuesFileName = "msbuild_issues.csv";
- public const string MsBuildLogFileName = "msbuild_log.txt";
+ private const string MsBuildLogFileName = "msbuild_log.txt";
public delegate void BuildLaunchFailedEventHandler(BuildInfo buildInfo, string reason);
@@ -62,11 +55,11 @@ namespace GodotTools.Build
private static void PrintVerbose(string text)
{
- if (Godot.OS.IsStdoutVerbose())
- Godot.GD.Print(text);
+ if (OS.IsStdoutVerbose())
+ GD.Print(text);
}
- public static bool Build(BuildInfo buildInfo)
+ private static bool Build(BuildInfo buildInfo)
{
if (_buildInProgress != null)
throw new InvalidOperationException("A build is already in progress");
@@ -103,7 +96,8 @@ namespace GodotTools.Build
}
catch (Exception e)
{
- BuildLaunchFailed?.Invoke(buildInfo, $"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}");
+ BuildLaunchFailed?.Invoke(buildInfo,
+ $"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}");
Console.Error.WriteLine(e);
return false;
}
@@ -148,7 +142,8 @@ namespace GodotTools.Build
}
catch (Exception e)
{
- BuildLaunchFailed?.Invoke(buildInfo, $"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}");
+ BuildLaunchFailed?.Invoke(buildInfo,
+ $"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}");
Console.Error.WriteLine(e);
return false;
}
@@ -159,18 +154,54 @@ namespace GodotTools.Build
}
}
- public static bool BuildProjectBlocking(string config, [MaybeNull] string[] targets = null, [MaybeNull] string platform = null)
+ private static bool Publish(BuildInfo buildInfo)
{
- var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, targets ?? new[] {"Build"}, config, restore: true);
+ if (_buildInProgress != null)
+ throw new InvalidOperationException("A build is already in progress");
- // If a platform was not specified, try determining the current one. If that fails, let MSBuild auto-detect it.
- if (platform != null || OS.PlatformNameMap.TryGetValue(Godot.OS.GetName(), out platform))
- buildInfo.CustomProperties.Add($"GodotTargetPlatform={platform}");
+ _buildInProgress = buildInfo;
- if (Internal.GodotIsRealTDouble())
- buildInfo.CustomProperties.Add("GodotRealTIsDouble=true");
+ try
+ {
+ BuildStarted?.Invoke(buildInfo);
+
+ // Required in order to update the build tasks list
+ Internal.GodotMainIteration();
- return BuildProjectBlocking(buildInfo);
+ try
+ {
+ RemoveOldIssuesFile(buildInfo);
+ }
+ catch (IOException e)
+ {
+ BuildLaunchFailed?.Invoke(buildInfo, $"Cannot remove issues file: {GetIssuesFilePath(buildInfo)}");
+ Console.Error.WriteLine(e);
+ }
+
+ try
+ {
+ int exitCode = BuildSystem.Publish(buildInfo, StdOutputReceived, StdErrorReceived);
+
+ if (exitCode != 0)
+ PrintVerbose(
+ $"dotnet publish exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}");
+
+ BuildFinished?.Invoke(exitCode == 0 ? BuildResult.Success : BuildResult.Error);
+
+ return exitCode == 0;
+ }
+ catch (Exception e)
+ {
+ BuildLaunchFailed?.Invoke(buildInfo,
+ $"The publish method threw an exception.\n{e.GetType().FullName}: {e.Message}");
+ Console.Error.WriteLine(e);
+ return false;
+ }
+ }
+ finally
+ {
+ _buildInProgress = null;
+ }
}
private static bool BuildProjectBlocking(BuildInfo buildInfo)
@@ -178,20 +209,109 @@ namespace GodotTools.Build
if (!File.Exists(buildInfo.Solution))
return true; // No solution to build
- using (var pr = new EditorProgress("mono_project_debug_build", "Building project solution...", 1))
+ using var pr = new EditorProgress("dotnet_build_project", "Building .NET project...", 1);
+
+ pr.Step("Building project solution", 0);
+
+ if (!Build(buildInfo))
{
- pr.Step("Building project solution", 0);
+ ShowBuildErrorDialog("Failed to build project solution");
+ return false;
+ }
- if (!Build(buildInfo))
- {
- ShowBuildErrorDialog("Failed to build project solution");
- return false;
- }
+ return true;
+ }
+
+ private static bool CleanProjectBlocking(BuildInfo buildInfo)
+ {
+ if (!File.Exists(buildInfo.Solution))
+ return true; // No solution to clean
+
+ using var pr = new EditorProgress("dotnet_clean_project", "Cleaning .NET project...", 1);
+
+ pr.Step("Cleaning project solution", 0);
+
+ if (!Build(buildInfo))
+ {
+ ShowBuildErrorDialog("Failed to clean project solution");
+ return false;
}
return true;
}
+ private static bool PublishProjectBlocking(BuildInfo buildInfo)
+ {
+ using var pr = new EditorProgress("dotnet_publish_project", "Publishing .NET project...", 1);
+
+ pr.Step("Running dotnet publish", 0);
+
+ if (!Publish(buildInfo))
+ {
+ ShowBuildErrorDialog("Failed to publish .NET project");
+ return false;
+ }
+
+ return true;
+ }
+
+ private static BuildInfo CreateBuildInfo(
+ [DisallowNull] string configuration,
+ [AllowNull] string platform = null,
+ bool rebuild = false,
+ bool onlyClean = false
+ )
+ {
+ var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, configuration,
+ restore: true, rebuild, onlyClean);
+
+ // If a platform was not specified, try determining the current one. If that fails, let MSBuild auto-detect it.
+ if (platform != null || Utils.OS.PlatformNameMap.TryGetValue(OS.GetName(), out platform))
+ buildInfo.CustomProperties.Add($"GodotTargetPlatform={platform}");
+
+ if (Internal.GodotIsRealTDouble())
+ buildInfo.CustomProperties.Add("GodotRealTIsDouble=true");
+
+ return buildInfo;
+ }
+
+ private static BuildInfo CreatePublishBuildInfo(
+ [DisallowNull] string configuration,
+ [DisallowNull] string platform,
+ [DisallowNull] string runtimeIdentifier,
+ [DisallowNull] string publishOutputDir
+ )
+ {
+ var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, configuration,
+ runtimeIdentifier, publishOutputDir, restore: true, rebuild: false, onlyClean: false);
+
+ buildInfo.CustomProperties.Add($"GodotTargetPlatform={platform}");
+
+ if (Internal.GodotIsRealTDouble())
+ buildInfo.CustomProperties.Add("GodotRealTIsDouble=true");
+
+ return buildInfo;
+ }
+
+ public static bool BuildProjectBlocking(
+ [DisallowNull] string configuration,
+ [AllowNull] string platform = null,
+ bool rebuild = false
+ ) => BuildProjectBlocking(CreateBuildInfo(configuration, platform, rebuild));
+
+ public static bool CleanProjectBlocking(
+ [DisallowNull] string configuration,
+ [AllowNull] string platform = null
+ ) => CleanProjectBlocking(CreateBuildInfo(configuration, platform, rebuild: false));
+
+ public static bool PublishProjectBlocking(
+ [DisallowNull] string configuration,
+ [DisallowNull] string platform,
+ [DisallowNull] string runtimeIdentifier,
+ string publishOutputDir
+ ) => PublishProjectBlocking(CreatePublishBuildInfo(configuration,
+ platform, runtimeIdentifier, publishOutputDir));
+
public static bool EditorBuildCallback()
{
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
@@ -204,7 +324,7 @@ namespace GodotTools.Build
}
catch (Exception e)
{
- Godot.GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message);
+ GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message);
}
if (GodotSharpEditor.Instance.SkipBuildBeforePlaying)
@@ -215,47 +335,6 @@ namespace GodotTools.Build
public static void Initialize()
{
- // Build tool settings
- var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
-
- BuildTool msbuildDefault;
-
- if (OS.IsWindows)
- {
- if (RiderPathManager.IsExternalEditorSetToRider(editorSettings))
- msbuildDefault = BuildTool.JetBrainsMsBuild;
- else
- msbuildDefault = !string.IsNullOrEmpty(OS.PathWhich("dotnet")) ? BuildTool.DotnetCli : BuildTool.MsBuildVs;
- }
- else
- {
- msbuildDefault = !string.IsNullOrEmpty(OS.PathWhich("dotnet")) ? BuildTool.DotnetCli : BuildTool.MsBuildMono;
- }
-
- EditorDef("mono/builds/build_tool", msbuildDefault);
-
- string hintString;
-
- if (OS.IsWindows)
- {
- hintString = $"{PropNameMSBuildMono}:{(int)BuildTool.MsBuildMono}," +
- $"{PropNameMSBuildVs}:{(int)BuildTool.MsBuildVs}," +
- $"{PropNameMSBuildJetBrains}:{(int)BuildTool.JetBrainsMsBuild}," +
- $"{PropNameDotnetCli}:{(int)BuildTool.DotnetCli}";
- }
- else
- {
- hintString = $"{PropNameMSBuildMono}:{(int)BuildTool.MsBuildMono}," +
- $"{PropNameDotnetCli}:{(int)BuildTool.DotnetCli}";
- }
-
- editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary
- {
- ["type"] = Godot.Variant.Type.Int,
- ["name"] = "mono/builds/build_tool",
- ["hint"] = Godot.PropertyHint.Enum,
- ["hint_string"] = hintString
- });
}
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs
index ed5ee10585..f7b8c6bffd 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs
@@ -8,10 +8,10 @@ using Path = System.IO.Path;
namespace GodotTools.Build
{
- public class BuildOutputView : VBoxContainer, ISerializationListener
+ public partial class BuildOutputView : VBoxContainer, ISerializationListener
{
[Serializable]
- private class BuildIssue : RefCounted // TODO Remove RefCounted once we have proper serialization
+ private partial class BuildIssue : RefCounted // TODO Remove RefCounted once we have proper serialization
{
public bool Warning { get; set; }
public string File { get; set; }
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
index 0e793a44ba..d2e3ecff57 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
@@ -1,61 +1,93 @@
-using GodotTools.Core;
using System;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Diagnostics;
+using System.Globalization;
using System.IO;
+using System.Text;
using System.Threading.Tasks;
using GodotTools.BuildLogger;
-using GodotTools.Internals;
using GodotTools.Utils;
-using Directory = System.IO.Directory;
namespace GodotTools.Build
{
public static class BuildSystem
{
- private static string MonoWindowsBinDir
+ private static Process LaunchBuild(BuildInfo buildInfo, Action<string> stdOutHandler,
+ Action<string> stdErrHandler)
{
- get
- {
- string monoWinBinDir = Path.Combine(Internal.MonoWindowsInstallRoot, "bin");
+ string dotnetPath = DotNetFinder.FindDotNetExe();
- if (!Directory.Exists(monoWinBinDir))
- throw new FileNotFoundException("Cannot find the Windows Mono install bin directory.");
+ if (dotnetPath == null)
+ throw new FileNotFoundException("Cannot find the dotnet executable.");
- return monoWinBinDir;
- }
+ var startInfo = new ProcessStartInfo(dotnetPath);
+
+ BuildArguments(buildInfo, startInfo.ArgumentList);
+
+ string launchMessage = startInfo.GetCommandLineDisplay(new StringBuilder("Running: ")).ToString();
+ stdOutHandler?.Invoke(launchMessage);
+ if (Godot.OS.IsStdoutVerbose())
+ Console.WriteLine(launchMessage);
+
+ startInfo.RedirectStandardOutput = true;
+ startInfo.RedirectStandardError = true;
+ startInfo.UseShellExecute = false;
+ startInfo.CreateNoWindow = true;
+
+ // Needed when running from Developer Command Prompt for VS
+ RemovePlatformVariable(startInfo.EnvironmentVariables);
+
+ var process = new Process { StartInfo = startInfo };
+
+ if (stdOutHandler != null)
+ process.OutputDataReceived += (s, e) => stdOutHandler.Invoke(e.Data);
+ if (stdErrHandler != null)
+ process.ErrorDataReceived += (s, e) => stdErrHandler.Invoke(e.Data);
+
+ process.Start();
+
+ process.BeginOutputReadLine();
+ process.BeginErrorReadLine();
+
+ return process;
}
- private static Godot.EditorSettings EditorSettings =>
- GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
+ public static int Build(BuildInfo buildInfo, Action<string> stdOutHandler, Action<string> stdErrHandler)
+ {
+ using (var process = LaunchBuild(buildInfo, stdOutHandler, stdErrHandler))
+ {
+ process.WaitForExit();
+
+ return process.ExitCode;
+ }
+ }
- private static bool UsingMonoMsBuildOnWindows
+ public static async Task<int> BuildAsync(BuildInfo buildInfo, Action<string> stdOutHandler,
+ Action<string> stdErrHandler)
{
- get
+ using (var process = LaunchBuild(buildInfo, stdOutHandler, stdErrHandler))
{
- if (OS.IsWindows)
- {
- return (BuildTool)EditorSettings.GetSetting("mono/builds/build_tool")
- == BuildTool.MsBuildMono;
- }
+ await process.WaitForExitAsync();
- return false;
+ return process.ExitCode;
}
}
- private static Process LaunchBuild(BuildInfo buildInfo, Action<string> stdOutHandler, Action<string> stdErrHandler)
+ private static Process LaunchPublish(BuildInfo buildInfo, Action<string> stdOutHandler,
+ Action<string> stdErrHandler)
{
- (string msbuildPath, BuildTool buildTool) = MsBuildFinder.FindMsBuild();
+ string dotnetPath = DotNetFinder.FindDotNetExe();
- if (msbuildPath == null)
- throw new FileNotFoundException("Cannot find the MSBuild executable.");
+ if (dotnetPath == null)
+ throw new FileNotFoundException("Cannot find the dotnet executable.");
- string compilerArgs = BuildArguments(buildTool, buildInfo);
+ var startInfo = new ProcessStartInfo(dotnetPath);
- var startInfo = new ProcessStartInfo(msbuildPath, compilerArgs);
+ BuildPublishArguments(buildInfo, startInfo.ArgumentList);
- string launchMessage = $"Running: \"{startInfo.FileName}\" {startInfo.Arguments}";
+ string launchMessage = startInfo.GetCommandLineDisplay(new StringBuilder("Running: ")).ToString();
stdOutHandler?.Invoke(launchMessage);
if (Godot.OS.IsStdoutVerbose())
Console.WriteLine(launchMessage);
@@ -63,22 +95,11 @@ namespace GodotTools.Build
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardError = true;
startInfo.UseShellExecute = false;
- startInfo.CreateNoWindow = true;
-
- if (UsingMonoMsBuildOnWindows)
- {
- // These environment variables are required for Mono's MSBuild to find the compilers.
- // We use the batch files in Mono's bin directory to make sure the compilers are executed with mono.
- string monoWinBinDir = MonoWindowsBinDir;
- startInfo.EnvironmentVariables.Add("CscToolExe", Path.Combine(monoWinBinDir, "csc.bat"));
- startInfo.EnvironmentVariables.Add("VbcToolExe", Path.Combine(monoWinBinDir, "vbc.bat"));
- startInfo.EnvironmentVariables.Add("FscToolExe", Path.Combine(monoWinBinDir, "fsharpc.bat"));
- }
// Needed when running from Developer Command Prompt for VS
RemovePlatformVariable(startInfo.EnvironmentVariables);
- var process = new Process {StartInfo = startInfo};
+ var process = new Process { StartInfo = startInfo };
if (stdOutHandler != null)
process.OutputDataReceived += (s, e) => stdOutHandler.Invoke(e.Data);
@@ -93,9 +114,9 @@ namespace GodotTools.Build
return process;
}
- public static int Build(BuildInfo buildInfo, Action<string> stdOutHandler, Action<string> stdErrHandler)
+ public static int Publish(BuildInfo buildInfo, Action<string> stdOutHandler, Action<string> stdErrHandler)
{
- using (var process = LaunchBuild(buildInfo, stdOutHandler, stdErrHandler))
+ using (var process = LaunchPublish(buildInfo, stdOutHandler, stdErrHandler))
{
process.WaitForExit();
@@ -103,46 +124,105 @@ namespace GodotTools.Build
}
}
- public static async Task<int> BuildAsync(BuildInfo buildInfo, Action<string> stdOutHandler, Action<string> stdErrHandler)
+ private static void BuildArguments(BuildInfo buildInfo, Collection<string> arguments)
{
- using (var process = LaunchBuild(buildInfo, stdOutHandler, stdErrHandler))
+ // `dotnet clean` / `dotnet build` commands
+ arguments.Add(buildInfo.OnlyClean ? "clean" : "build");
+
+ // Solution
+ arguments.Add(buildInfo.Solution);
+
+ // `dotnet clean` doesn't recognize these options
+ if (!buildInfo.OnlyClean)
{
- await process.WaitForExitAsync();
+ // Restore
+ // `dotnet build` restores by default, unless requested not to
+ if (!buildInfo.Restore)
+ arguments.Add("--no-restore");
+
+ // Incremental or rebuild
+ if (buildInfo.Rebuild)
+ arguments.Add("--no-incremental");
+ }
- return process.ExitCode;
+ // Configuration
+ arguments.Add("-c");
+ arguments.Add(buildInfo.Configuration);
+
+ // Verbosity
+ arguments.Add("-v");
+ arguments.Add("normal");
+
+ // Logger
+ AddLoggerArgument(buildInfo, arguments);
+
+ // Custom properties
+ foreach (string customProperty in buildInfo.CustomProperties)
+ {
+ arguments.Add("-p:" + customProperty);
}
}
- private static string BuildArguments(BuildTool buildTool, BuildInfo buildInfo)
+ private static void BuildPublishArguments(BuildInfo buildInfo, Collection<string> arguments)
{
- string arguments = string.Empty;
+ arguments.Add("publish"); // `dotnet publish` command
+
+ // Solution
+ arguments.Add(buildInfo.Solution);
+
+ // Restore
+ // `dotnet publish` restores by default, unless requested not to
+ if (!buildInfo.Restore)
+ arguments.Add("--no-restore");
+
+ // Incremental or rebuild
+ if (buildInfo.Rebuild)
+ arguments.Add("--no-incremental");
- if (buildTool == BuildTool.DotnetCli)
- arguments += "msbuild"; // `dotnet msbuild` command
+ // Configuration
+ arguments.Add("-c");
+ arguments.Add(buildInfo.Configuration);
- arguments += $@" ""{buildInfo.Solution}""";
+ // Runtime Identifier
+ arguments.Add("-r");
+ arguments.Add(buildInfo.RuntimeIdentifier!);
- if (buildInfo.Restore)
- arguments += " /restore";
+ // Self-published
+ arguments.Add("--self-contained");
+ arguments.Add("true");
- arguments += $@" /t:{string.Join(",", buildInfo.Targets)} " +
- $@"""/p:{"Configuration=" + buildInfo.Configuration}"" /v:normal " +
- $@"""{AddLoggerArgument(buildInfo)}""";
+ // Verbosity
+ arguments.Add("-v");
+ arguments.Add("normal");
+ // Logger
+ AddLoggerArgument(buildInfo, arguments);
+
+ // Trimming is not supported for dynamically loaded assemblies, as is our case with self hosting:
+ // https://github.com/dotnet/runtime/blob/main/docs/design/features/native-hosting.md#incompatible-with-trimming
+ arguments.Add("-p:PublishTrimmed=false");
+
+ // Custom properties
foreach (string customProperty in buildInfo.CustomProperties)
{
- arguments += " /p:" + customProperty;
+ arguments.Add("-p:" + customProperty);
}
- return arguments;
+ // Publish output directory
+ if (buildInfo.PublishOutputDir != null)
+ {
+ arguments.Add("-o");
+ arguments.Add(buildInfo.PublishOutputDir);
+ }
}
- private static string AddLoggerArgument(BuildInfo buildInfo)
+ private static void AddLoggerArgument(BuildInfo buildInfo, Collection<string> arguments)
{
- string buildLoggerPath = Path.Combine(GodotSharpDirs.DataEditorToolsDir,
+ string buildLoggerPath = Path.Combine(Internals.GodotSharpDirs.DataEditorToolsDir,
"GodotTools.BuildLogger.dll");
- return $"/l:{typeof(GodotBuildLogger).FullName},{buildLoggerPath};{buildInfo.LogsDirPath}";
+ arguments.Add(
+ $"-l:{typeof(GodotBuildLogger).FullName},{buildLoggerPath};{buildInfo.LogsDirPath}");
}
private static void RemovePlatformVariable(StringDictionary environmentVariables)
@@ -153,7 +233,7 @@ namespace GodotTools.Build
foreach (string env in environmentVariables.Keys)
{
- if (env.ToUpper() == "PLATFORM")
+ if (env.ToUpperInvariant() == "PLATFORM")
platformEnvironmentVariables.Add(env);
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildTool.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildTool.cs
deleted file mode 100644
index 837c8adddb..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildTool.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace GodotTools.Build
-{
- public enum BuildTool : long
- {
- MsBuildMono,
- MsBuildVs,
- JetBrainsMsBuild,
- DotnetCli
- }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/DotNetFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/DotNetFinder.cs
new file mode 100644
index 0000000000..7bce53308c
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/DotNetFinder.cs
@@ -0,0 +1,99 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using JetBrains.Annotations;
+using OS = GodotTools.Utils.OS;
+
+namespace GodotTools.Build
+{
+ public static class DotNetFinder
+ {
+ [CanBeNull]
+ public static string FindDotNetExe()
+ {
+ // 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.
+
+ return OS.PathWhich("dotnet");
+ }
+
+ public static bool TryFindDotNetSdk(
+ Version expectedVersion,
+ [NotNullWhen(true)] out Version version,
+ [NotNullWhen(true)] out string path
+ )
+ {
+ version = null;
+ path = null;
+
+ string dotNetExe = FindDotNetExe();
+
+ if (string.IsNullOrEmpty(dotNetExe))
+ return false;
+
+ using Process process = new Process();
+ process.StartInfo = new ProcessStartInfo(dotNetExe, "--list-sdks")
+ {
+ UseShellExecute = false,
+ RedirectStandardOutput = true
+ };
+
+ process.StartInfo.EnvironmentVariables["DOTNET_CLI_UI_LANGUAGE"] = "en-US";
+
+ var lines = new List<string>();
+
+ process.OutputDataReceived += (_, e) =>
+ {
+ if (!string.IsNullOrWhiteSpace(e.Data))
+ lines.Add(e.Data);
+ };
+
+ try
+ {
+ process.Start();
+ }
+ catch
+ {
+ return false;
+ }
+
+ process.BeginOutputReadLine();
+ process.WaitForExit();
+
+ Version latestVersionMatch = null;
+ string matchPath = null;
+
+ foreach (var line in lines)
+ {
+ string[] sdkLineParts = line.Trim()
+ .Split(' ', 2, StringSplitOptions.TrimEntries);
+
+ if (sdkLineParts.Length < 2)
+ continue;
+
+ if (!Version.TryParse(sdkLineParts[0], out var lineVersion))
+ continue;
+
+ // We're looking for the exact same major version
+ if (lineVersion.Major != expectedVersion.Major)
+ continue;
+
+ if (latestVersionMatch != null && lineVersion < latestVersionMatch)
+ continue;
+
+ latestVersionMatch = lineVersion;
+ matchPath = sdkLineParts[1].TrimStart('[').TrimEnd(']');
+ }
+
+ if (latestVersionMatch == null)
+ return false;
+
+ version = latestVersionMatch;
+ path = Path.Combine(matchPath!, version.ToString());
+
+ return true;
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs
index 13b3ab7da2..6dae0a3a0e 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs
@@ -6,7 +6,7 @@ using File = GodotTools.Utils.File;
namespace GodotTools.Build
{
- public class MSBuildPanel : VBoxContainer
+ public partial class MSBuildPanel : VBoxContainer
{
public BuildOutputView BuildOutputView { get; private set; }
@@ -70,7 +70,7 @@ namespace GodotTools.Build
GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message);
}
- if (!BuildManager.BuildProjectBlocking("Debug", targets: new[] { "Rebuild" }))
+ if (!BuildManager.BuildProjectBlocking("Debug", rebuild: true))
return; // Build failed
// Notify running game for hot-reload
@@ -88,7 +88,7 @@ namespace GodotTools.Build
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
return; // No solution to build
- BuildManager.BuildProjectBlocking("Debug", targets: new[] { "Clean" });
+ _ = BuildManager.CleanProjectBlocking("Debug");
}
private void ViewLogToggled(bool pressed) => BuildOutputView.LogVisible = pressed;
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs
deleted file mode 100644
index a859c6f717..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs
+++ /dev/null
@@ -1,233 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using Godot;
-using GodotTools.Ides.Rider;
-using GodotTools.Internals;
-using Directory = System.IO.Directory;
-using Environment = System.Environment;
-using File = System.IO.File;
-using Path = System.IO.Path;
-using OS = GodotTools.Utils.OS;
-
-namespace GodotTools.Build
-{
- public static class MsBuildFinder
- {
- private static string _msbuildToolsPath = string.Empty;
- private static string _msbuildUnixPath = string.Empty;
-
- public static (string, BuildTool) FindMsBuild()
- {
- var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
- var buildTool = (BuildTool)editorSettings.GetSetting("mono/builds/build_tool");
-
- if (OS.IsWindows)
- {
- switch (buildTool)
- {
- case BuildTool.DotnetCli:
- {
- string dotnetCliPath = OS.PathWhich("dotnet");
- if (!string.IsNullOrEmpty(dotnetCliPath))
- return (dotnetCliPath, BuildTool.DotnetCli);
- GD.PushError($"Cannot find executable for '{BuildManager.PropNameDotnetCli}'. Fallback to MSBuild from Visual Studio.");
- goto case BuildTool.MsBuildVs;
- }
- case BuildTool.MsBuildVs:
- {
- if (string.IsNullOrEmpty(_msbuildToolsPath) || !File.Exists(_msbuildToolsPath))
- {
- // Try to search it again if it wasn't found last time or if it was removed from its location
- _msbuildToolsPath = FindMsBuildToolsPathOnWindows();
-
- if (string.IsNullOrEmpty(_msbuildToolsPath))
- throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMSBuildVs}'.");
- }
-
- if (!_msbuildToolsPath.EndsWith("\\"))
- _msbuildToolsPath += "\\";
-
- return (Path.Combine(_msbuildToolsPath, "MSBuild.exe"), BuildTool.MsBuildVs);
- }
- case BuildTool.MsBuildMono:
- {
- string msbuildPath = Path.Combine(Internal.MonoWindowsInstallRoot, "bin", "msbuild.bat");
-
- if (!File.Exists(msbuildPath))
- throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMSBuildMono}'. Tried with path: {msbuildPath}");
-
- return (msbuildPath, BuildTool.MsBuildMono);
- }
- case BuildTool.JetBrainsMsBuild:
- {
- string editorPath = (string)editorSettings.GetSetting(RiderPathManager.EditorPathSettingName);
-
- if (!File.Exists(editorPath))
- throw new FileNotFoundException($"Cannot find Rider executable. Tried with path: {editorPath}");
-
- var riderDir = new FileInfo(editorPath).Directory?.Parent;
-
- string msbuildPath = Path.Combine(riderDir.FullName, @"tools\MSBuild\Current\Bin\MSBuild.exe");
-
- if (!File.Exists(msbuildPath))
- throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMSBuildJetBrains}'. Tried with path: {msbuildPath}");
-
- return (msbuildPath, BuildTool.JetBrainsMsBuild);
- }
- default:
- throw new IndexOutOfRangeException("Invalid build tool in editor settings");
- }
- }
-
- if (OS.IsUnixLike)
- {
- switch (buildTool)
- {
- case BuildTool.DotnetCli:
- {
- string dotnetCliPath = FindBuildEngineOnUnix("dotnet");
- if (!string.IsNullOrEmpty(dotnetCliPath))
- return (dotnetCliPath, BuildTool.DotnetCli);
- GD.PushError($"Cannot find executable for '{BuildManager.PropNameDotnetCli}'. Fallback to MSBuild from Mono.");
- goto case BuildTool.MsBuildMono;
- }
- case BuildTool.MsBuildMono:
- {
- if (string.IsNullOrEmpty(_msbuildUnixPath) || !File.Exists(_msbuildUnixPath))
- {
- // Try to search it again if it wasn't found last time or if it was removed from its location
- _msbuildUnixPath = FindBuildEngineOnUnix("msbuild");
- }
-
- if (string.IsNullOrEmpty(_msbuildUnixPath))
- throw new FileNotFoundException($"Cannot find binary for '{BuildManager.PropNameMSBuildMono}'");
-
- return (_msbuildUnixPath, BuildTool.MsBuildMono);
- }
- default:
- throw new IndexOutOfRangeException("Invalid build tool in editor settings");
- }
- }
-
- throw new PlatformNotSupportedException();
- }
-
- private static IEnumerable<string> MsBuildHintDirs
- {
- get
- {
- var result = new List<string>();
-
- if (OS.IsMacOS)
- {
- result.Add("/Library/Frameworks/Mono.framework/Versions/Current/bin/");
- result.Add("/opt/local/bin/");
- result.Add("/usr/local/var/homebrew/linked/mono/bin/");
- result.Add("/usr/local/bin/");
- result.Add("/usr/local/bin/dotnet/");
- result.Add("/usr/local/share/dotnet/");
- }
-
- result.Add("/opt/novell/mono/bin/");
-
- return result;
- }
- }
-
- private static string FindBuildEngineOnUnix(string name)
- {
- string ret = OS.PathWhich(name);
-
- if (!string.IsNullOrEmpty(ret))
- return ret;
-
- string retFallback = OS.PathWhich($"{name}.exe");
-
- if (!string.IsNullOrEmpty(retFallback))
- return retFallback;
-
- foreach (string hintDir in MsBuildHintDirs)
- {
- string hintPath = Path.Combine(hintDir, name);
-
- if (File.Exists(hintPath))
- return hintPath;
- }
-
- return string.Empty;
- }
-
- private static string FindMsBuildToolsPathOnWindows()
- {
- if (!OS.IsWindows)
- throw new PlatformNotSupportedException();
-
- // Try to find 15.0 with vswhere
-
- string[] envNames = Internal.GodotIs32Bits() ?
- envNames = new[] { "ProgramFiles", "ProgramW6432" } :
- envNames = new[] { "ProgramFiles(x86)", "ProgramFiles" };
-
- string vsWherePath = null;
- foreach (var envName in envNames)
- {
- vsWherePath = Environment.GetEnvironmentVariable(envName);
- if (!string.IsNullOrEmpty(vsWherePath))
- {
- vsWherePath += "\\Microsoft Visual Studio\\Installer\\vswhere.exe";
- if (File.Exists(vsWherePath))
- break;
- }
-
- vsWherePath = null;
- }
-
- var vsWhereArgs = new[] {"-latest", "-products", "*", "-requires", "Microsoft.Component.MSBuild"};
-
- var outputArray = new Godot.Collections.Array<string>();
- int exitCode = Godot.OS.Execute(vsWherePath, vsWhereArgs,
- output: (Godot.Collections.Array)outputArray);
-
- if (exitCode != 0)
- return string.Empty;
-
- if (outputArray.Count == 0)
- return string.Empty;
-
- var lines = outputArray[0].Split('\n');
-
- foreach (string line in lines)
- {
- int sepIdx = line.IndexOf(':');
-
- if (sepIdx <= 0)
- continue;
-
- string key = line.Substring(0, sepIdx); // No need to trim
-
- if (key != "installationPath")
- continue;
-
- string value = line.Substring(sepIdx + 1).StripEdges();
-
- if (string.IsNullOrEmpty(value))
- throw new FormatException("installationPath value is empty");
-
- if (!value.EndsWith("\\"))
- value += "\\";
-
- // Since VS2019, the directory is simply named "Current"
- string msbuildDir = Path.Combine(value, "MSBuild\\Current\\Bin");
-
- if (Directory.Exists(msbuildDir))
- return msbuildDir;
-
- // Directory name "15.0" is used in VS 2017
- return Path.Combine(value, "MSBuild\\15.0\\Bin");
- }
-
- return string.Empty;
- }
- }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs b/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs
index 63b97e981e..fdb86c8f34 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs
@@ -1,4 +1,5 @@
using System;
+using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
@@ -39,7 +40,8 @@ namespace GodotTools.Build
// 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("value")).Value =
+ "https://api.nuget.org/v3/index.json";
nugetOrgSourceEntry.Attributes.Append(xmlDoc.CreateAttribute("protocolVersion")).Value = "3";
rootNode.AppendChild(xmlDoc.CreateElement("packageSources")).AppendChild(nugetOrgSourceEntry);
}
@@ -181,8 +183,8 @@ namespace GodotTools.Build
// - 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 packageIdLower = packageId.ToLowerInvariant();
+ string packageVersionLower = packageVersion.ToLowerInvariant();
string destDir = Path.Combine(fallbackFolder, packageIdLower, packageVersionLower);
string nupkgDestPath = Path.Combine(destDir, $"{packageIdLower}.{packageVersionLower}.nupkg");
@@ -227,9 +229,11 @@ namespace GodotTools.Build
var nuspecEntry = archive.GetEntry(packageId + ".nuspec");
if (nuspecEntry == null)
- throw new InvalidOperationException($"Failed to extract package {packageId}.{packageVersion}. Could not find the nuspec file.");
+ throw new InvalidOperationException(
+ $"Failed to extract package {packageId}.{packageVersion}. Could not find the nuspec file.");
- nuspecEntry.ExtractToFile(Path.Combine(destDir, nuspecEntry.Name.ToLower().SimplifyGodotPath()));
+ nuspecEntry.ExtractToFile(Path.Combine(destDir, nuspecEntry.Name
+ .ToLowerInvariant().SimplifyGodotPath()));
// Extract the other package files
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
index d8ebe762e1..edf3eeb7fa 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
@@ -1,12 +1,9 @@
using Godot;
-using Godot.NativeInterop;
using System;
using System.Collections.Generic;
using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
-using System.Runtime.CompilerServices;
using GodotTools.Build;
using GodotTools.Core;
using GodotTools.Internals;
@@ -18,61 +15,13 @@ using Path = System.IO.Path;
namespace GodotTools.Export
{
- public class ExportPlugin : EditorExportPlugin
+ public partial class ExportPlugin : EditorExportPlugin
{
- [Flags]
- private enum I18NCodesets : long
- {
- None = 0,
- CJK = 1,
- MidEast = 2,
- Other = 4,
- Rare = 8,
- West = 16,
- All = CJK | MidEast | Other | Rare | West
- }
-
- private string _maybeLastExportError;
-
- private void AddI18NAssemblies(Godot.Collections.Dictionary<string, string> assemblies, string bclDir)
- {
- var codesets = (I18NCodesets)ProjectSettings.GetSetting("mono/export/i18n_codesets");
-
- if (codesets == I18NCodesets.None)
- return;
-
- void AddI18NAssembly(string name) => assemblies.Add(name, Path.Combine(bclDir, $"{name}.dll"));
-
- AddI18NAssembly("I18N");
-
- if ((codesets & I18NCodesets.CJK) != 0)
- AddI18NAssembly("I18N.CJK");
- if ((codesets & I18NCodesets.MidEast) != 0)
- AddI18NAssembly("I18N.MidEast");
- if ((codesets & I18NCodesets.Other) != 0)
- AddI18NAssembly("I18N.Other");
- if ((codesets & I18NCodesets.Rare) != 0)
- AddI18NAssembly("I18N.Rare");
- if ((codesets & I18NCodesets.West) != 0)
- AddI18NAssembly("I18N.West");
- }
-
public void RegisterExportSettings()
{
// TODO: These would be better as export preset options, but that doesn't seem to be supported yet
GlobalDef("mono/export/include_scripts_content", false);
- GlobalDef("mono/export/export_assemblies_inside_pck", true);
-
- GlobalDef("mono/export/i18n_codesets", I18NCodesets.All);
-
- ProjectSettings.AddPropertyInfo(new Godot.Collections.Dictionary
- {
- ["type"] = Variant.Type.Int,
- ["name"] = "mono/export/i18n_codesets",
- ["hint"] = PropertyHint.Flags,
- ["hint_string"] = "CJK,MidEast,Other,Rare,West"
- });
GlobalDef("mono/export/aot/enabled", false);
GlobalDef("mono/export/aot/full_aot", false);
@@ -86,11 +35,7 @@ namespace GodotTools.Export
GlobalDef("mono/export/aot/android_toolchain_path", "");
}
- private void AddFile(string srcPath, string dstPath, bool remap = false)
- {
- // Add file to the PCK
- AddFile(dstPath.Replace("\\", "/"), File.ReadAllBytes(srcPath), remap);
- }
+ private string _maybeLastExportError;
// With this method we can override how a file is exported in the PCK
public override void _ExportFile(string path, string type, string[] features)
@@ -155,178 +100,84 @@ namespace GodotTools.Export
if (!DeterminePlatformFromFeatures(features, out string platform))
throw new NotSupportedException("Target platform not supported");
+ if (!new[] { OS.Platforms.Windows, OS.Platforms.LinuxBSD, OS.Platforms.MacOS, OS.Platforms.Server }
+ .Contains(platform))
+ {
+ throw new NotImplementedException("Target platform not yet implemented");
+ }
+
string outputDir = new FileInfo(path).Directory?.FullName ??
- throw new FileNotFoundException("Base directory not found");
+ throw new FileNotFoundException("Output base directory not found");
string buildConfig = isDebug ? "ExportDebug" : "ExportRelease";
- if (!BuildManager.BuildProjectBlocking(buildConfig, platform: platform))
- throw new Exception("Failed to build project");
+ // TODO: This works for now, as we only implemented support for x86 family desktop so far, but it needs to be fixed
+ string arch = features.Contains("64") ? "x86_64" : "x86";
- // Add dependency assemblies
+ string ridOS = DetermineRuntimeIdentifierOS(platform);
+ string ridArch = DetermineRuntimeIdentifierArch(arch);
+ string runtimeIdentifier = $"{ridOS}-{ridArch}";
- var assemblies = new Godot.Collections.Dictionary<string, string>();
+ // Create temporary publish output directory
- string projectDllName = GodotSharpEditor.ProjectAssemblyName;
- string projectDllSrcDir = Path.Combine(GodotSharpDirs.ResTempAssembliesBaseDir, buildConfig);
- string projectDllSrcPath = Path.Combine(projectDllSrcDir, $"{projectDllName}.dll");
+ string publishOutputTempDir = Path.Combine(Path.GetTempPath(), "godot-publish-dotnet",
+ $"{Process.GetCurrentProcess().Id}-{buildConfig}-{runtimeIdentifier}");
- assemblies[projectDllName] = projectDllSrcPath;
+ if (!Directory.Exists(publishOutputTempDir))
+ Directory.CreateDirectory(publishOutputTempDir);
- string bclDir = DeterminePlatformBclDir(platform);
+ // Execute dotnet publish
- if (platform == OS.Platforms.Android)
+ if (!BuildManager.PublishProjectBlocking(buildConfig, platform,
+ runtimeIdentifier, publishOutputTempDir))
{
- string godotAndroidExtProfileDir = GetBclProfileDir("godot_android_ext");
- string monoAndroidAssemblyPath = Path.Combine(godotAndroidExtProfileDir, "Mono.Android.dll");
-
- if (!File.Exists(monoAndroidAssemblyPath))
- throw new FileNotFoundException("Assembly not found: 'Mono.Android'", monoAndroidAssemblyPath);
-
- assemblies["Mono.Android"] = monoAndroidAssemblyPath;
+ throw new Exception("Failed to build project");
}
- else if (platform == OS.Platforms.HTML5)
+
+ if (!File.Exists(Path.Combine(publishOutputTempDir, $"{GodotSharpEditor.ProjectAssemblyName}.dll")))
{
- // Ideally these would be added automatically since they're referenced by the wasm BCL assemblies.
- // However, at least in the case of 'WebAssembly.Net.Http' for some reason the BCL assemblies
- // reference a different version even though the assembly is the same, for some weird reason.
-
- var wasmFrameworkAssemblies = new[] { "WebAssembly.Bindings", "WebAssembly.Net.WebSockets" };
-
- foreach (string thisWasmFrameworkAssemblyName in wasmFrameworkAssemblies)
- {
- string thisWasmFrameworkAssemblyPath = Path.Combine(bclDir, thisWasmFrameworkAssemblyName + ".dll");
- if (!File.Exists(thisWasmFrameworkAssemblyPath))
- throw new FileNotFoundException($"Assembly not found: '{thisWasmFrameworkAssemblyName}'",
- thisWasmFrameworkAssemblyPath);
- assemblies[thisWasmFrameworkAssemblyName] = thisWasmFrameworkAssemblyPath;
- }
-
- // Assemblies that can have a different name in a newer version. Newer version must come first and it has priority.
- (string newName, string oldName)[] wasmFrameworkAssembliesOneOf = new[]
- {
- ("System.Net.Http.WebAssemblyHttpHandler", "WebAssembly.Net.Http")
- };
-
- foreach (var thisWasmFrameworkAssemblyName in wasmFrameworkAssembliesOneOf)
- {
- string thisWasmFrameworkAssemblyPath =
- Path.Combine(bclDir, thisWasmFrameworkAssemblyName.newName + ".dll");
- if (File.Exists(thisWasmFrameworkAssemblyPath))
- {
- assemblies[thisWasmFrameworkAssemblyName.newName] = thisWasmFrameworkAssemblyPath;
- }
- else
- {
- thisWasmFrameworkAssemblyPath =
- Path.Combine(bclDir, thisWasmFrameworkAssemblyName.oldName + ".dll");
- if (!File.Exists(thisWasmFrameworkAssemblyPath))
- {
- throw new FileNotFoundException(
- "Expected one of the following assemblies but none were found: " +
- $"'{thisWasmFrameworkAssemblyName.newName}' / '{thisWasmFrameworkAssemblyName.oldName}'",
- thisWasmFrameworkAssemblyPath);
- }
-
- assemblies[thisWasmFrameworkAssemblyName.oldName] = thisWasmFrameworkAssemblyPath;
- }
- }
+ throw new NotSupportedException(
+ "Publish succeeded but project assembly not found in the output directory");
}
- // var initialAssemblies = assemblies.Duplicate();
- // godot_dictionary initialAssembliesAux = ((Godot.Collections.Dictionary)initialAssemblies).NativeValue;
- // using godot_string buildConfigAux = Marshaling.ConvertStringToNative(buildConfig);
- // using godot_string bclDirAux = Marshaling.ConvertStringToNative(bclDir);
- // godot_dictionary assembliesAux = ((Godot.Collections.Dictionary)assemblies).NativeValue;
- // TODO
- throw new NotImplementedException();
- //internal_GetExportedAssemblyDependencies(initialAssembliesAux, buildConfigAux, bclDirAux, ref assembliesAux);
-
- AddI18NAssemblies(assemblies, bclDir);
-
- string outputDataDir = null;
+ // Copy all files from the dotnet publish output directory to
+ // a data directory next to the Godot output executable.
- if (PlatformHasTemplateDir(platform))
- outputDataDir = ExportDataDirectory(features, platform, isDebug, outputDir);
+ string outputDataDir = Path.Combine(outputDir, DetermineDataDirNameForProject());
- string apiConfig = isDebug ? "Debug" : "Release";
- // TODO
- throw new NotImplementedException();
- string resAssembliesDir = null; // Path.Combine(GodotSharpDirs.ResAssembliesBaseDir, apiConfig);
+ if (Directory.Exists(outputDataDir))
+ Directory.Delete(outputDataDir, recursive: true); // Clean first
- bool assembliesInsidePck = (bool)ProjectSettings.GetSetting("mono/export/export_assemblies_inside_pck") ||
- outputDataDir == null;
+ Directory.CreateDirectory(outputDataDir);
- if (!assembliesInsidePck)
+ foreach (string dir in Directory.GetDirectories(publishOutputTempDir, "*", SearchOption.AllDirectories))
{
- string outputDataGameAssembliesDir = Path.Combine(outputDataDir, "Assemblies");
- if (!Directory.Exists(outputDataGameAssembliesDir))
- Directory.CreateDirectory(outputDataGameAssembliesDir);
+ Directory.CreateDirectory(Path.Combine(outputDataDir, dir.Substring(publishOutputTempDir.Length + 1)));
}
- foreach (var assembly in assemblies)
+ foreach (string file in Directory.GetFiles(publishOutputTempDir, "*", SearchOption.AllDirectories))
{
- void AddToAssembliesDir(string fileSrcPath)
- {
- if (assembliesInsidePck)
- {
- string fileDstPath = Path.Combine(resAssembliesDir, fileSrcPath.GetFile());
- AddFile(fileSrcPath, fileDstPath);
- }
- else
- {
- Debug.Assert(outputDataDir != null);
- string fileDstPath = Path.Combine(outputDataDir, "Assemblies", fileSrcPath.GetFile());
- File.Copy(fileSrcPath, fileDstPath);
- }
- }
-
- string assemblySrcPath = assembly.Value;
-
- string assemblyPathWithoutExtension = Path.ChangeExtension(assemblySrcPath, null);
- string pdbSrcPath = assemblyPathWithoutExtension + ".pdb";
-
- AddToAssembliesDir(assemblySrcPath);
-
- if (File.Exists(pdbSrcPath))
- AddToAssembliesDir(pdbSrcPath);
+ File.Copy(file, Path.Combine(outputDataDir, file.Substring(publishOutputTempDir.Length + 1)));
}
+ }
- // AOT compilation
- bool aotEnabled = platform == OS.Platforms.iOS ||
- (bool)ProjectSettings.GetSetting("mono/export/aot/enabled");
+ private string DetermineRuntimeIdentifierOS(string platform)
+ => OS.DotNetOSPlatformMap[platform];
- if (aotEnabled)
+ private string DetermineRuntimeIdentifierArch(string arch)
+ {
+ return arch switch
{
- string aotToolchainPath = null;
-
- if (platform == OS.Platforms.Android)
- aotToolchainPath = (string)ProjectSettings.GetSetting("mono/export/aot/android_toolchain_path");
-
- if (aotToolchainPath == string.Empty)
- aotToolchainPath = null; // Don't risk it being used as current working dir
-
- // TODO: LLVM settings are hard-coded and disabled for now
- var aotOpts = new AotOptions
- {
- EnableLLVM = false,
- LLVMOnly = false,
- LLVMPath = "",
- LLVMOutputPath = "",
- FullAot = platform == OS.Platforms.iOS ||
- (bool)(ProjectSettings.GetSetting("mono/export/aot/full_aot") ?? false),
- UseInterpreter = (bool)ProjectSettings.GetSetting("mono/export/aot/use_interpreter"),
- ExtraAotOptions = (string[])ProjectSettings.GetSetting("mono/export/aot/extra_aot_options") ??
- Array.Empty<string>(),
- ExtraOptimizerOptions =
- (string[])ProjectSettings.GetSetting("mono/export/aot/extra_optimizer_options") ??
- Array.Empty<string>(),
- ToolchainPath = aotToolchainPath
- };
-
- AotBuilder.CompileAssemblies(this, aotOpts, features, platform, isDebug, bclDir, outputDir,
- outputDataDir, assemblies);
- }
+ "x86" => "x86",
+ "x86_32" => "x86",
+ "x64" => "x64",
+ "x86_64" => "x64",
+ "armeabi-v7a" => "arm",
+ "arm64-v8a" => "arm64",
+ "armv7" => "arm",
+ "arm64" => "arm64",
+ _ => throw new ArgumentOutOfRangeException(nameof(arch), arch, "Unexpected architecture")
+ };
}
public override void _ExportEnd()
@@ -350,70 +201,11 @@ namespace GodotTools.Export
}
}
- [return: NotNull]
- private static string ExportDataDirectory(string[] features, string platform, bool isDebug, string outputDir)
- {
- string target = isDebug ? "release_debug" : "release";
-
- // NOTE: Bits is ok for now as all platforms with a data directory only have one or two architectures.
- // However, this may change in the future if we add arm linux or windows desktop templates.
- string bits = features.Contains("64") ? "64" : "32";
-
- string TemplateDirName() => $"data.mono.{platform}.{bits}.{target}";
-
- string templateDirPath = Path.Combine(Internal.FullExportTemplatesDir, TemplateDirName());
- bool validTemplatePathFound = true;
-
- if (!Directory.Exists(templateDirPath))
- {
- validTemplatePathFound = false;
-
- if (isDebug)
- {
- target = "debug"; // Support both 'release_debug' and 'debug' for the template data directory name
- templateDirPath = Path.Combine(Internal.FullExportTemplatesDir, TemplateDirName());
- validTemplatePathFound = true;
-
- if (!Directory.Exists(templateDirPath))
- validTemplatePathFound = false;
- }
- }
-
- if (!validTemplatePathFound)
- throw new FileNotFoundException("Data template directory not found", templateDirPath);
-
- string outputDataDir = Path.Combine(outputDir, DetermineDataDirNameForProject());
-
- if (Directory.Exists(outputDataDir))
- Directory.Delete(outputDataDir, recursive: true); // Clean first
-
- Directory.CreateDirectory(outputDataDir);
-
- foreach (string dir in Directory.GetDirectories(templateDirPath, "*", SearchOption.AllDirectories))
- {
- Directory.CreateDirectory(Path.Combine(outputDataDir, dir.Substring(templateDirPath.Length + 1)));
- }
-
- foreach (string file in Directory.GetFiles(templateDirPath, "*", SearchOption.AllDirectories))
- {
- File.Copy(file, Path.Combine(outputDataDir, file.Substring(templateDirPath.Length + 1)));
- }
-
- return outputDataDir;
- }
-
- private static bool PlatformHasTemplateDir(string platform)
- {
- // macOS export templates are contained in a zip, so we place our custom template inside it and let Godot do the rest.
- return !new[] { OS.Platforms.MacOS, OS.Platforms.Android, OS.Platforms.iOS, OS.Platforms.HTML5 }
- .Contains(platform);
- }
-
private static bool DeterminePlatformFromFeatures(IEnumerable<string> features, out string platform)
{
foreach (var feature in features)
{
- if (OS.PlatformNameMap.TryGetValue(feature, out platform))
+ if (OS.PlatformFeatureMap.TryGetValue(feature, out platform))
return true;
}
@@ -421,78 +213,6 @@ namespace GodotTools.Export
return false;
}
- private static string GetBclProfileDir(string profile)
- {
- string templatesDir = Internal.FullExportTemplatesDir;
- return Path.Combine(templatesDir, "bcl", profile);
- }
-
- private static string DeterminePlatformBclDir(string platform)
- {
- string templatesDir = Internal.FullExportTemplatesDir;
- string platformBclDir = Path.Combine(templatesDir, "bcl", platform);
-
- if (!File.Exists(Path.Combine(platformBclDir, "mscorlib.dll")))
- {
- string profile = DeterminePlatformBclProfile(platform);
- platformBclDir = Path.Combine(templatesDir, "bcl", profile);
-
- if (!File.Exists(Path.Combine(platformBclDir, "mscorlib.dll")))
- {
- if (PlatformRequiresCustomBcl(platform))
- throw new FileNotFoundException($"Missing BCL (Base Class Library) for platform: {platform}");
-
- platformBclDir = typeof(object).Assembly.Location.GetBaseDir(); // Use the one we're running on
- }
- }
-
- return platformBclDir;
- }
-
- /// <summary>
- /// Determines whether the BCL bundled with the Godot editor can be used for the target platform,
- /// or if it requires a custom BCL that must be distributed with the export templates.
- /// </summary>
- private static bool PlatformRequiresCustomBcl(string platform)
- {
- if (new[] { OS.Platforms.Android, OS.Platforms.iOS, OS.Platforms.HTML5 }.Contains(platform))
- return true;
-
- // The 'net_4_x' BCL is not compatible between Windows and the other platforms.
- // We use the names 'net_4_x_win' and 'net_4_x' to differentiate between the two.
-
- bool isWinOrUwp = new[]
- {
- OS.Platforms.Windows,
- OS.Platforms.UWP
- }.Contains(platform);
-
- return OS.IsWindows ? !isWinOrUwp : isWinOrUwp;
- }
-
- private static string DeterminePlatformBclProfile(string platform)
- {
- switch (platform)
- {
- case OS.Platforms.Windows:
- case OS.Platforms.UWP:
- return "net_4_x_win";
- case OS.Platforms.MacOS:
- case OS.Platforms.LinuxBSD:
- case OS.Platforms.Server:
- case OS.Platforms.Haiku:
- return "net_4_x";
- case OS.Platforms.Android:
- return "monodroid";
- case OS.Platforms.iOS:
- return "monotouch";
- case OS.Platforms.HTML5:
- return "wasm";
- default:
- throw new NotSupportedException($"Platform not supported: {platform}");
- }
- }
-
private static string DetermineDataDirNameForProject()
{
string appName = (string)ProjectSettings.GetSetting("application/config/name");
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
index 2d85513766..4e27f4ed14 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
@@ -13,13 +13,14 @@ using GodotTools.Internals;
using GodotTools.ProjectEditor;
using JetBrains.Annotations;
using static GodotTools.Internals.Globals;
+using Environment = System.Environment;
using File = GodotTools.Utils.File;
using OS = GodotTools.Utils.OS;
using Path = System.IO.Path;
namespace GodotTools
{
- public class GodotSharpEditor : EditorPlugin, ISerializationListener
+ public partial class GodotSharpEditor : EditorPlugin, ISerializationListener
{
private EditorSettings _editorSettings;
@@ -151,13 +152,6 @@ namespace GodotTools
Instance.MSBuildPanel.BuildSolution();
}
- public override void _Ready()
- {
- base._Ready();
-
- MSBuildPanel.BuildOutputView.BuildStateChanged += BuildStateChanged;
- }
-
private enum MenuOptions
{
CreateSln,
@@ -382,12 +376,37 @@ namespace GodotTools
{
base._EnablePlugin();
- ProjectUtils.MSBuildLocatorRegisterDefaults();
-
if (Instance != null)
throw new InvalidOperationException();
Instance = this;
+ var dotNetSdkSearchVersion = Environment.Version;
+
+ // First we try to find the .NET Sdk ourselves to make sure we get the
+ // correct version first (`RegisterDefaults` always picks the latest).
+ if (DotNetFinder.TryFindDotNetSdk(dotNetSdkSearchVersion, out var sdkVersion, out string sdkPath))
+ {
+ if (Godot.OS.IsStdoutVerbose())
+ Console.WriteLine($"Found .NET Sdk version '{sdkVersion}': {sdkPath}");
+
+ ProjectUtils.MSBuildLocatorRegisterMSBuildPath(sdkPath);
+ }
+ else
+ {
+ try
+ {
+ ProjectUtils.MSBuildLocatorRegisterDefaults(out sdkVersion, out sdkPath);
+ if (Godot.OS.IsStdoutVerbose())
+ Console.WriteLine($"Found .NET Sdk version '{sdkVersion}': {sdkPath}");
+ }
+ catch (InvalidOperationException e)
+ {
+ if (Godot.OS.IsStdoutVerbose())
+ GD.PrintErr(e.ToString());
+ GD.PushError($".NET Sdk not found. The required version is '{dotNetSdkSearchVersion}'.");
+ }
+ }
+
var editorInterface = GetEditorInterface();
var editorBaseControl = editorInterface.GetBaseControl();
@@ -397,6 +416,8 @@ namespace GodotTools
editorBaseControl.AddChild(_errorDialog);
MSBuildPanel = new MSBuildPanel();
+ MSBuildPanel.Ready += () =>
+ MSBuildPanel.BuildOutputView.BuildStateChanged += BuildStateChanged;
_bottomPanelBtn = AddControlToBottomPanel(MSBuildPanel, "MSBuild".TTR());
AddChild(new HotReloadAssemblyWatcher { Name = "HotReloadAssemblyWatcher" });
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
index d0fae02d5d..92a40b9b8b 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
@@ -11,6 +11,13 @@
<GodotApiAssembliesDir>$(GodotOutputDataDir)/Api/$(GodotApiConfiguration)</GodotApiAssembliesDir>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
+ <!-- Needed for our source generators to work despite this not being a Godot game project -->
+ <PropertyGroup>
+ <IsGodotToolsProject>true</IsGodotToolsProject>
+ </PropertyGroup>
+ <ItemGroup>
+ <CompilerVisibleProperty Include="IsGodotToolsProject" />
+ </ItemGroup>
<PropertyGroup Condition=" Exists('$(GodotApiAssembliesDir)/GodotSharp.dll') ">
<!-- The project is part of the Godot source tree -->
<!-- Use the Godot source tree output folder instead of '$(ProjectDir)/bin' -->
@@ -34,6 +41,9 @@
</Reference>
</ItemGroup>
<ItemGroup>
+ <ProjectReference Include="..\..\Godot.NET.Sdk\Godot.SourceGenerators\Godot.SourceGenerators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
+ </ItemGroup>
+ <ItemGroup>
<ProjectReference Include="..\GodotTools.BuildLogger\GodotTools.BuildLogger.csproj" />
<ProjectReference Include="..\GodotTools.IdeMessaging\GodotTools.IdeMessaging.csproj" />
<ProjectReference Include="..\GodotTools.ProjectEditor\GodotTools.ProjectEditor.csproj" />
diff --git a/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs
index 414729b18e..260d13a714 100644
--- a/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs
@@ -5,7 +5,7 @@ using static GodotTools.Internals.Globals;
namespace GodotTools
{
- public class HotReloadAssemblyWatcher : Node
+ public partial class HotReloadAssemblyWatcher : Node
{
private Timer _watchTimer;
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
index 23339fe50b..9acdeb4183 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
@@ -8,7 +8,7 @@ using GodotTools.Internals;
namespace GodotTools.Ides
{
- public sealed class GodotIdeManager : Node, ISerializationListener
+ public sealed partial class GodotIdeManager : Node, ISerializationListener
{
private MessagingServer _messagingServer;
diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs
index eca7da07c8..63c15e7d28 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs
@@ -1,4 +1,3 @@
-using System.Runtime.CompilerServices;
using Godot.NativeInterop;
namespace GodotTools.Internals
@@ -15,16 +14,6 @@ namespace GodotTools.Internals
}
}
- public static string ResTempAssembliesBaseDir
- {
- get
- {
- Internal.godot_icall_GodotSharpDirs_ResTempAssembliesBaseDir(out godot_string dest);
- using (dest)
- return Marshaling.ConvertStringToManaged(dest);
- }
- }
-
public static string MonoUserDir
{
get
diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
index 7b39f8ecdb..be53b8f1cf 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
@@ -1,5 +1,4 @@
using System;
-using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Godot;
using Godot.NativeInterop;
@@ -47,16 +46,6 @@ namespace GodotTools.Internals
public static void EditorNodeShowScriptScreen() => godot_icall_Internal_EditorNodeShowScriptScreen();
- public static string MonoWindowsInstallRoot
- {
- get
- {
- godot_icall_Internal_MonoWindowsInstallRoot(out godot_string dest);
- using (dest)
- return Marshaling.ConvertStringToManaged(dest);
- }
- }
-
public static void EditorRunPlay() => godot_icall_Internal_EditorRunPlay();
public static void EditorRunStop() => godot_icall_Internal_EditorRunStop();
@@ -64,7 +53,7 @@ namespace GodotTools.Internals
public static void ScriptEditorDebugger_ReloadScripts() =>
godot_icall_Internal_ScriptEditorDebugger_ReloadScripts();
- public static unsafe string[] CodeCompletionRequest(CodeCompletionRequest.CompletionKind kind,
+ public static string[] CodeCompletionRequest(CodeCompletionRequest.CompletionKind kind,
string scriptFile)
{
using godot_string scriptFileIn = Marshaling.ConvertStringToNative(scriptFile);
@@ -81,9 +70,6 @@ namespace GodotTools.Internals
public static extern void godot_icall_GodotSharpDirs_ResMetadataDir(out godot_string r_dest);
[DllImport(GodotDllName)]
- public static extern void godot_icall_GodotSharpDirs_ResTempAssembliesBaseDir(out godot_string r_dest);
-
- [DllImport(GodotDllName)]
public static extern void godot_icall_GodotSharpDirs_MonoUserDir(out godot_string r_dest);
[DllImport(GodotDllName)]
@@ -144,9 +130,6 @@ namespace GodotTools.Internals
private static extern void godot_icall_Internal_EditorNodeShowScriptScreen();
[DllImport(GodotDllName)]
- private static extern void godot_icall_Internal_MonoWindowsInstallRoot(out godot_string dest);
-
- [DllImport(GodotDllName)]
private static extern void godot_icall_Internal_EditorRunPlay();
[DllImport(GodotDllName)]
diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs
index db77a71c2f..73e3df4d2c 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs
@@ -1,11 +1,12 @@
using Godot.NativeInterop;
using System;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
-using System.Runtime.CompilerServices;
+using System.Text;
using GodotTools.Internals;
namespace GodotTools.Utils
@@ -13,7 +14,7 @@ namespace GodotTools.Utils
[SuppressMessage("ReSharper", "InconsistentNaming")]
public static class OS
{
- public static class Names
+ private static class Names
{
public const string Windows = "Windows";
public const string MacOS = "macOS";
@@ -42,6 +43,35 @@ namespace GodotTools.Utils
public const string HTML5 = "javascript";
}
+ private static class DotNetOS
+ {
+ public const string Win = "win";
+ public const string OSX = "osx";
+ public const string Linux = "linux";
+ public const string Win10 = "win10";
+ public const string Android = "android";
+ public const string iOS = "ios";
+ public const string Browser = "browser";
+ }
+
+ public static readonly Dictionary<string, string> PlatformFeatureMap = new Dictionary<string, string>(
+ // Export `features` may be in lower case
+ StringComparer.InvariantCultureIgnoreCase
+ )
+ {
+ ["Windows"] = Platforms.Windows,
+ ["macOS"] = Platforms.MacOS,
+ ["LinuxBSD"] = Platforms.LinuxBSD,
+ // "X11" for compatibility, temporarily, while we are on an outdated branch
+ ["X11"] = Platforms.LinuxBSD,
+ ["Server"] = Platforms.Server,
+ ["UWP"] = Platforms.UWP,
+ ["Haiku"] = Platforms.Haiku,
+ ["Android"] = Platforms.Android,
+ ["iOS"] = Platforms.iOS,
+ ["HTML5"] = Platforms.HTML5
+ };
+
public static readonly Dictionary<string, string> PlatformNameMap = new Dictionary<string, string>
{
[Names.Windows] = Platforms.Windows,
@@ -58,7 +88,23 @@ namespace GodotTools.Utils
[Names.HTML5] = Platforms.HTML5
};
- private static unsafe bool IsOS(string name)
+ public static readonly Dictionary<string, string> DotNetOSPlatformMap = new Dictionary<string, string>
+ {
+ [Platforms.Windows] = DotNetOS.Win,
+ [Platforms.MacOS] = DotNetOS.OSX,
+ // TODO:
+ // Does .NET 6 support BSD variants? If it does, it may need the name `unix`
+ // instead of `linux` in the runtime identifier. This would be a problem as
+ // Godot has a single export profile for both, named LinuxBSD.
+ [Platforms.LinuxBSD] = DotNetOS.Linux,
+ [Platforms.Server] = DotNetOS.Linux,
+ [Platforms.UWP] = DotNetOS.Win10,
+ [Platforms.Android] = DotNetOS.Android,
+ [Platforms.iOS] = DotNetOS.iOS,
+ [Platforms.HTML5] = DotNetOS.Browser
+ };
+
+ private static bool IsOS(string name)
{
Internal.godot_icall_Utils_OS_GetPlatformName(out godot_string dest);
using (dest)
@@ -68,7 +114,7 @@ namespace GodotTools.Utils
}
}
- private static unsafe bool IsAnyOS(IEnumerable<string> names)
+ private static bool IsAnyOS(IEnumerable<string> names)
{
Internal.godot_icall_Utils_OS_GetPlatformName(out godot_string dest);
using (dest)
@@ -99,25 +145,34 @@ namespace GodotTools.Utils
// TODO SupportedOSPlatformGuard once we target .NET 6
// [SupportedOSPlatformGuard("windows")]
public static bool IsWindows => _isWindows.Value || IsUWP;
+
// [SupportedOSPlatformGuard("osx")]
public static bool IsMacOS => _isMacOS.Value;
+
// [SupportedOSPlatformGuard("linux")]
public static bool IsLinuxBSD => _isLinuxBSD.Value;
+
// [SupportedOSPlatformGuard("linux")]
public static bool IsServer => _isServer.Value;
+
// [SupportedOSPlatformGuard("windows")]
public static bool IsUWP => _isUWP.Value;
+
public static bool IsHaiku => _isHaiku.Value;
+
// [SupportedOSPlatformGuard("android")]
public static bool IsAndroid => _isAndroid.Value;
+
// [SupportedOSPlatformGuard("ios")]
public static bool IsiOS => _isiOS.Value;
+
// [SupportedOSPlatformGuard("browser")]
public static bool IsHTML5 => _isHTML5.Value;
public static bool IsUnixLike => _isUnixLike.Value;
public static char PathSep => IsWindows ? ';' : ':';
+ [return: MaybeNull]
public static string PathWhich([NotNull] string name)
{
if (IsWindows)
@@ -126,6 +181,7 @@ namespace GodotTools.Utils
return PathWhichUnix(name);
}
+ [return: MaybeNull]
private static string PathWhichWindows([NotNull] string name)
{
string[] windowsExts =
@@ -162,6 +218,7 @@ namespace GodotTools.Utils
select path + ext).FirstOrDefault(File.Exists);
}
+ [return: MaybeNull]
private static string PathWhichUnix([NotNull] string name)
{
string[] pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep);
@@ -192,14 +249,7 @@ namespace GodotTools.Utils
public static void RunProcess(string command, IEnumerable<string> arguments)
{
- // TODO: Once we move to .NET Standard 2.1 we can use ProcessStartInfo.ArgumentList instead
- string CmdLineArgsToString(IEnumerable<string> args)
- {
- // Not perfect, but as long as we are careful...
- return string.Join(" ", args.Select(arg => arg.Contains(" ") ? $@"""{arg}""" : arg));
- }
-
- var startInfo = new ProcessStartInfo(command, CmdLineArgsToString(arguments))
+ var startInfo = new ProcessStartInfo(command)
{
RedirectStandardOutput = true,
RedirectStandardError = true,
@@ -207,44 +257,104 @@ namespace GodotTools.Utils
CreateNoWindow = true
};
- using (Process process = Process.Start(startInfo))
- {
- if (process == null)
- throw new Exception("No process was started");
+ foreach (string arg in arguments)
+ startInfo.ArgumentList.Add(arg);
- process.BeginOutputReadLine();
- process.BeginErrorReadLine();
- if (IsWindows && process.Id > 0)
- User32Dll.AllowSetForegroundWindow(process.Id); // allows application to focus itself
- }
+ using Process process = Process.Start(startInfo);
+
+ if (process == null)
+ throw new Exception("No process was started");
+
+ process.BeginOutputReadLine();
+ process.BeginErrorReadLine();
+
+ if (IsWindows && process.Id > 0)
+ User32Dll.AllowSetForegroundWindow(process.Id); // Allows application to focus itself
}
public static int ExecuteCommand(string command, IEnumerable<string> arguments)
{
- // TODO: Once we move to .NET Standard 2.1 we can use ProcessStartInfo.ArgumentList instead
- string CmdLineArgsToString(IEnumerable<string> args)
+ var startInfo = new ProcessStartInfo(command)
{
- // Not perfect, but as long as we are careful...
- return string.Join(" ", args.Select(arg => arg.Contains(" ") ? $@"""{arg}""" : arg));
- }
+ // Print the output
+ RedirectStandardOutput = false,
+ RedirectStandardError = false,
+ UseShellExecute = false
+ };
+
+ foreach (string arg in arguments)
+ startInfo.ArgumentList.Add(arg);
+
+ Console.WriteLine(startInfo.GetCommandLineDisplay(new StringBuilder("Executing: ")).ToString());
- var startInfo = new ProcessStartInfo(command, CmdLineArgsToString(arguments));
+ using var process = new Process { StartInfo = startInfo };
+ process.Start();
+ process.WaitForExit();
- Console.WriteLine($"Executing: \"{startInfo.FileName}\" {startInfo.Arguments}");
+ return process.ExitCode;
+ }
+
+ private static void AppendProcessFileNameForDisplay(this StringBuilder builder, string fileName)
+ {
+ if (builder.Length > 0)
+ builder.Append(' ');
- // Print the output
- startInfo.RedirectStandardOutput = false;
- startInfo.RedirectStandardError = false;
+ if (fileName.Contains(' '))
+ {
+ builder.Append('"');
+ builder.Append(fileName);
+ builder.Append('"');
+ }
+ else
+ {
+ builder.Append(fileName);
+ }
+ }
- startInfo.UseShellExecute = false;
+ private static void AppendProcessArgumentsForDisplay(this StringBuilder builder,
+ Collection<string> argumentList)
+ {
+ // This is intended just for reading. It doesn't need to be a valid command line.
+ // E.g.: We don't handle escaping of quotes.
- using (var process = new Process { StartInfo = startInfo })
+ foreach (string argument in argumentList)
{
- process.Start();
- process.WaitForExit();
+ if (builder.Length > 0)
+ builder.Append(' ');
- return process.ExitCode;
+ if (argument.Contains(' '))
+ {
+ builder.Append('"');
+ builder.Append(argument);
+ builder.Append('"');
+ }
+ else
+ {
+ builder.Append(argument);
+ }
}
}
+
+ public static StringBuilder GetCommandLineDisplay(
+ this ProcessStartInfo startInfo,
+ StringBuilder optionalBuilder = null
+ )
+ {
+ var builder = optionalBuilder ?? new StringBuilder();
+
+ builder.AppendProcessFileNameForDisplay(startInfo.FileName);
+
+ if (startInfo.ArgumentList.Count == 0)
+ {
+ builder.Append(' ');
+ builder.Append(startInfo.Arguments);
+ }
+ else
+ {
+ builder.AppendProcessArgumentsForDisplay(startInfo.ArgumentList);
+ }
+
+ return builder;
+ }
}
}
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index fdc50e22f3..746cb8a142 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -76,7 +76,6 @@ StringBuilder &operator<<(StringBuilder &r_sb, const char *p_cstring) {
#define CLOSE_BLOCK_L4 INDENT4 CLOSE_BLOCK
#define BINDINGS_GLOBAL_SCOPE_CLASS "GD"
-#define BINDINGS_PTR_FIELD "NativePtr"
#define BINDINGS_NATIVE_NAME_FIELD "NativeName"
#define CS_PARAM_MEMORYOWN "memoryOwn"
@@ -86,6 +85,7 @@ StringBuilder &operator<<(StringBuilder &r_sb, const char *p_cstring) {
#define CS_METHOD_CALL "Call"
#define CS_PROPERTY_SINGLETON "Singleton"
#define CS_METHOD_INVOKE_GODOT_CLASS_METHOD "InvokeGodotClassMethod"
+#define CS_METHOD_HAS_GODOT_CLASS_METHOD "HasGodotClassMethod"
#define CS_STATIC_FIELD_NATIVE_CTOR "NativeCtor"
#define CS_STATIC_FIELD_METHOD_BIND_PREFIX "MethodBind"
@@ -103,7 +103,6 @@ StringBuilder &operator<<(StringBuilder &r_sb, const char *p_cstring) {
#define C_CLASS_NATIVE_FUNCS "NativeFuncs"
#define C_NS_MONOUTILS "InteropUtils"
-#define C_METHOD_TIE_MANAGED_TO_UNMANAGED C_NS_MONOUTILS ".TieManagedToUnmanaged"
#define C_METHOD_UNMANAGED_GET_MANAGED C_NS_MONOUTILS ".UnmanagedGetManaged"
#define C_METHOD_ENGINE_GET_SINGLETON C_NS_MONOUTILS ".EngineGetSingleton"
@@ -122,8 +121,6 @@ StringBuilder &operator<<(StringBuilder &r_sb, const char *p_cstring) {
// Types that will be ignored by the generator and won't be available in C#.
const Vector<String> ignored_types = { "PhysicsServer3DExtension" };
-typedef String string;
-
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
// any of the changes done here to the 'uint32_t' type interface as well.
@@ -1541,7 +1538,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
// Add native name static field
if (is_derived_type) {
- output << MEMBER_BEGIN "private static readonly System.Type _cachedType = typeof(" << itype.proxy_name << ");\n";
+ output << MEMBER_BEGIN "private static readonly System.Type CachedType = typeof(" << itype.proxy_name << ");\n";
}
output.append(MEMBER_BEGIN "private static readonly StringName " BINDINGS_NATIVE_NAME_FIELD " = \"");
@@ -1561,22 +1558,12 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
// Add default constructor
if (itype.is_instantiable) {
output << MEMBER_BEGIN "public " << itype.proxy_name << "() : this("
- << (itype.memory_own ? "true" : "false") << ")\n" OPEN_BLOCK_L2;
-
- // The default constructor may also be called by the engine when instancing existing native objects
- // The engine will initialize the pointer field of the managed side before calling the constructor
- // This is why we only allocate a new native object from the constructor if the pointer field is not set
- output << INDENT3 "if (" BINDINGS_PTR_FIELD " == IntPtr.Zero)\n" OPEN_BLOCK_L3
- << INDENT4 "unsafe\n" INDENT4 OPEN_BLOCK
- << INDENT5 BINDINGS_PTR_FIELD " = " CS_STATIC_FIELD_NATIVE_CTOR "();\n"
- << CLOSE_BLOCK_L4
- << INDENT4 C_METHOD_TIE_MANAGED_TO_UNMANAGED "(this, " BINDINGS_PTR_FIELD ", "
- << BINDINGS_NATIVE_NAME_FIELD << ", refCounted: " << (itype.is_ref_counted ? "true" : "false")
- << ", ((object)this).GetType(), _cachedType);\n" CLOSE_BLOCK_L3
- << INDENT3 "else\n" INDENT3 OPEN_BLOCK
- << INDENT4 "InteropUtils.TieManagedToUnmanagedWithPreSetup(this, "
- << BINDINGS_PTR_FIELD ", ((object)this).GetType(), _cachedType);\n" CLOSE_BLOCK_L3
- << INDENT3 "_InitializeGodotScriptInstanceInternals();\n" CLOSE_BLOCK_L2;
+ << (itype.memory_own ? "true" : "false") << ")\n" OPEN_BLOCK_L2
+ << INDENT3 "unsafe\n" INDENT3 OPEN_BLOCK
+ << INDENT4 "_ConstructAndInitialize(" CS_STATIC_FIELD_NATIVE_CTOR ", "
+ << BINDINGS_NATIVE_NAME_FIELD ", CachedType, refCounted: "
+ << (itype.is_ref_counted ? "true" : "false") << ");\n"
+ << CLOSE_BLOCK_L3 CLOSE_BLOCK_L2;
} else {
// Hide the constructor
output.append(MEMBER_BEGIN "internal ");
@@ -1608,9 +1595,11 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
"Failed to generate signal '" + isignal.name + "' for class '" + itype.name + "'.");
}
- // Script calls
+ // Script members look-up
if (!itype.is_singleton && (is_derived_type || itype.has_virtual_methods)) {
+ // Generate method names cache fields
+
for (const MethodInterface &imethod : itype.methods) {
if (!imethod.is_virtual) {
continue;
@@ -1629,6 +1618,10 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
<< " = \"" << imethod.proxy_name << "\";\n";
}
+ // TODO: Only generate HasGodotClassMethod and InvokeGodotClassMethod if there's any method
+
+ // Generate InvokeGodotClassMethod
+
output << MEMBER_BEGIN "protected internal " << (is_derived_type ? "override" : "virtual")
<< " bool " CS_METHOD_INVOKE_GODOT_CLASS_METHOD "(in godot_string_name method, "
<< "NativeVariantPtrArgs args, int argCount, out godot_variant ret)\n"
@@ -1639,9 +1632,17 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
continue;
}
+ // We also call HasGodotClassMethod to ensure the method is overridden and avoid calling
+ // the stub implementation. This solution adds some extra overhead to calls, but it's
+ // much simpler than other solutions. This won't be a problem once we move to function
+ // pointers of generated wrappers for each method, as lookup will only happen once.
+
+ // We check both native names (snake_case) and proxy names (PascalCase)
output << INDENT3 "if ((method == " << CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX << imethod.name
<< " || method == " << CS_STATIC_FIELD_METHOD_NAME_PREFIX << imethod.name
- << ") && argCount == " << itos(imethod.arguments.size()) << ")\n"
+ << ") && argCount == " << itos(imethod.arguments.size())
+ << " && " << CS_METHOD_HAS_GODOT_CLASS_METHOD << "((godot_string_name)"
+ << CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX << imethod.name << ".NativeValue))\n"
<< INDENT3 "{\n";
if (imethod.return_type.cname != name_cache.type_void) {
@@ -1696,6 +1697,38 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
}
output << INDENT2 "}\n";
+
+ // Generate HasGodotClassMethod
+
+ output << MEMBER_BEGIN "protected internal " << (is_derived_type ? "override" : "virtual")
+ << " bool " CS_METHOD_HAS_GODOT_CLASS_METHOD "(in godot_string_name method)\n"
+ << INDENT2 "{\n";
+
+ for (const MethodInterface &imethod : itype.methods) {
+ if (!imethod.is_virtual) {
+ continue;
+ }
+
+ // We check for native names (snake_case). If we detect one, we call HasGodotClassMethod
+ // again, but this time with the respective proxy name (PascalCase). It's the job of
+ // user derived classes to override the method and check for those. Our C# source
+ // generators take care of generating those override methods.
+ output << INDENT3 "if (method == " << CS_STATIC_FIELD_METHOD_NAME_PREFIX << imethod.name
+ << ")\n" INDENT3 "{\n"
+ << INDENT4 "if (" CS_METHOD_HAS_GODOT_CLASS_METHOD "("
+ << CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX << imethod.name
+ << ".NativeValue.DangerousSelfRef))\n" INDENT4 "{\n"
+ << INDENT5 "return true;\n"
+ << INDENT4 "}\n" INDENT3 "}\n";
+ }
+
+ if (is_derived_type) {
+ output << INDENT3 "return base." CS_METHOD_HAS_GODOT_CLASS_METHOD "(method);\n";
+ } else {
+ output << INDENT3 "return false;\n";
+ }
+
+ output << INDENT2 "}\n";
}
output.append(INDENT1 CLOSE_BLOCK /* class */
@@ -2278,7 +2311,7 @@ Error BindingsGenerator::_generate_cs_native_calls(const InternalCall &p_icall,
if (p_icall.is_vararg) {
if (i < p_icall.get_arguments_count() - 1) {
- string c_in_vararg = arg_type->c_in_vararg;
+ String c_in_vararg = arg_type->c_in_vararg;
if (arg_type->is_object_type) {
c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromGodotObject(%1);\n";
diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp
index 5b1bc8ccbf..1544e61315 100644
--- a/modules/mono/editor/editor_internal_calls.cpp
+++ b/modules/mono/editor/editor_internal_calls.cpp
@@ -74,10 +74,6 @@ GD_PINVOKE_EXPORT void godot_icall_GodotSharpDirs_ResMetadataDir(godot_string *r
memnew_placement(r_dest, String(GodotSharpDirs::get_res_metadata_dir()));
}
-GD_PINVOKE_EXPORT void godot_icall_GodotSharpDirs_ResTempAssembliesBaseDir(godot_string *r_dest) {
- memnew_placement(r_dest, String(GodotSharpDirs::get_res_temp_assemblies_base_dir()));
-}
-
GD_PINVOKE_EXPORT void godot_icall_GodotSharpDirs_MonoUserDir(godot_string *r_dest) {
memnew_placement(r_dest, String(GodotSharpDirs::get_mono_user_dir()));
}
@@ -189,16 +185,6 @@ GD_PINVOKE_EXPORT void godot_icall_Internal_EditorNodeShowScriptScreen() {
EditorNode::get_singleton()->call("_editor_select", EditorNode::EDITOR_SCRIPT);
}
-GD_PINVOKE_EXPORT void godot_icall_Internal_MonoWindowsInstallRoot(godot_string *r_dest) {
-#ifdef WINDOWS_ENABLED
- String install_root_dir = GDMono::get_singleton()->get_mono_reg_info().install_root_dir;
- memnew_placement(r_dest, String(install_root_dir));
-#else
- memnew_placement(r_dest, String);
- return;
-#endif
-}
-
GD_PINVOKE_EXPORT void godot_icall_Internal_EditorRunPlay() {
EditorNode::get_singleton()->run_play();
}
@@ -267,9 +253,8 @@ GD_PINVOKE_EXPORT bool godot_icall_Utils_OS_UnixFileHasExecutableAccess(const go
}
#endif
-void *godotsharp_editor_pinvoke_funcs[32] = {
+void *godotsharp_editor_pinvoke_funcs[30] = {
(void *)godot_icall_GodotSharpDirs_ResMetadataDir,
- (void *)godot_icall_GodotSharpDirs_ResTempAssembliesBaseDir,
(void *)godot_icall_GodotSharpDirs_MonoUserDir,
(void *)godot_icall_GodotSharpDirs_BuildLogsDirs,
(void *)godot_icall_GodotSharpDirs_ProjectSlnPath,
@@ -288,7 +273,6 @@ void *godotsharp_editor_pinvoke_funcs[32] = {
(void *)godot_icall_Internal_EditorDebuggerNodeReloadScripts,
(void *)godot_icall_Internal_ScriptEditorEdit,
(void *)godot_icall_Internal_EditorNodeShowScriptScreen,
- (void *)godot_icall_Internal_MonoWindowsInstallRoot,
(void *)godot_icall_Internal_EditorRunPlay,
(void *)godot_icall_Internal_EditorRunStop,
(void *)godot_icall_Internal_ScriptEditorDebugger_ReloadScripts,
diff --git a/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs b/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs
index 9f938373c4..0cb9e57530 100644
--- a/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs
+++ b/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs
@@ -4,6 +4,7 @@ using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
+using Godot.Bridge;
using Godot.NativeInterop;
namespace GodotPlugins
@@ -13,6 +14,7 @@ namespace GodotPlugins
private static readonly List<AssemblyName> SharedAssemblies = new();
private static readonly Assembly CoreApiAssembly = typeof(Godot.Object).Assembly;
private static Assembly? _editorApiAssembly;
+ private static Assembly? _projectAssembly;
private static readonly AssemblyLoadContext MainLoadContext =
AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()) ??
@@ -20,67 +22,59 @@ namespace GodotPlugins
// Right now we do it this way for simplicity as hot-reload is disabled. It will need to be changed later.
[UnmanagedCallersOnly]
- internal static unsafe godot_bool Initialize(godot_bool editorHint,
- PluginsCallbacks* pluginsCallbacks, Godot.Bridge.ManagedCallbacks* managedCallbacks)
+ // ReSharper disable once UnusedMember.Local
+ private static unsafe godot_bool InitializeFromEngine(godot_bool editorHint,
+ PluginsCallbacks* pluginsCallbacks, ManagedCallbacks* managedCallbacks)
{
try
{
SharedAssemblies.Add(CoreApiAssembly.GetName());
+ NativeLibrary.SetDllImportResolver(CoreApiAssembly, GodotDllImportResolver.OnResolveDllImport);
if (editorHint.ToBool())
{
_editorApiAssembly = Assembly.Load("GodotSharpEditor");
SharedAssemblies.Add(_editorApiAssembly.GetName());
+ NativeLibrary.SetDllImportResolver(_editorApiAssembly, GodotDllImportResolver.OnResolveDllImport);
}
- NativeLibrary.SetDllImportResolver(CoreApiAssembly, OnResolveDllImport);
-
*pluginsCallbacks = new()
{
LoadProjectAssemblyCallback = &LoadProjectAssembly,
LoadToolsAssemblyCallback = &LoadToolsAssembly,
};
- *managedCallbacks = Godot.Bridge.ManagedCallbacks.Create();
+ *managedCallbacks = ManagedCallbacks.Create();
return godot_bool.True;
}
catch (Exception e)
{
Console.Error.WriteLine(e);
- *pluginsCallbacks = default;
- *managedCallbacks = default;
return false.ToGodotBool();
}
}
[StructLayout(LayoutKind.Sequential)]
- internal struct PluginsCallbacks
+ private struct PluginsCallbacks
{
public unsafe delegate* unmanaged<char*, godot_bool> LoadProjectAssemblyCallback;
public unsafe delegate* unmanaged<char*, IntPtr> LoadToolsAssemblyCallback;
}
[UnmanagedCallersOnly]
- internal static unsafe godot_bool LoadProjectAssembly(char* nAssemblyPath)
+ private static unsafe godot_bool LoadProjectAssembly(char* nAssemblyPath)
{
try
{
- string assemblyPath = new(nAssemblyPath);
+ if (_projectAssembly != null)
+ return godot_bool.True; // Already loaded
- var assembly = LoadPlugin(assemblyPath);
-
- var method = CoreApiAssembly.GetType("Godot.Bridge.ScriptManagerBridge")?
- .GetMethod("LookupScriptsInAssembly",
- BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
+ string assemblyPath = new(nAssemblyPath);
- if (method == null)
- {
- throw new MissingMethodException("Godot.Bridge.ScriptManagerBridge",
- "LookupScriptsInAssembly");
- }
+ _projectAssembly = LoadPlugin(assemblyPath);
- method.Invoke(null, new object[] { assembly });
+ ScriptManagerBridge.LookupScriptsInAssembly(_projectAssembly);
return godot_bool.True;
}
@@ -92,7 +86,7 @@ namespace GodotPlugins
}
[UnmanagedCallersOnly]
- internal static unsafe IntPtr LoadToolsAssembly(char* nAssemblyPath)
+ private static unsafe IntPtr LoadToolsAssembly(char* nAssemblyPath)
{
try
{
@@ -103,7 +97,7 @@ namespace GodotPlugins
var assembly = LoadPlugin(assemblyPath);
- NativeLibrary.SetDllImportResolver(assembly, OnResolveDllImport);
+ NativeLibrary.SetDllImportResolver(assembly, GodotDllImportResolver.OnResolveDllImport);
var method = assembly.GetType("GodotTools.GodotSharpEditor")?
.GetMethod("InternalCreateInstance",
@@ -140,58 +134,5 @@ namespace GodotPlugins
var loadContext = new PluginLoadContext(assemblyPath, sharedAssemblies, MainLoadContext);
return loadContext.LoadFromAssemblyName(new AssemblyName(assemblyName));
}
-
- public static IntPtr OnResolveDllImport(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
- {
- if (libraryName == "__Internal")
- {
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- return Win32.GetModuleHandle(IntPtr.Zero);
- }
- else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
- {
- return Linux.dlopen(IntPtr.Zero, Linux.RTLD_LAZY);
- }
- else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
- {
- return MacOS.dlopen(IntPtr.Zero, MacOS.RTLD_LAZY);
- }
- }
-
- return IntPtr.Zero;
- }
-
- // ReSharper disable InconsistentNaming
- private static class MacOS
- {
- private const string SystemLibrary = "/usr/lib/libSystem.dylib";
-
- public const int RTLD_LAZY = 1;
-
- [DllImport(SystemLibrary)]
- public static extern IntPtr dlopen(IntPtr path, int mode);
- }
-
- private static class Linux
- {
- // libdl.so was resulting in DllNotFoundException, for some reason...
- // libcoreclr.so should work with both CoreCLR and the .NET Core version of Mono.
- private const string SystemLibrary = "libcoreclr.so";
-
- public const int RTLD_LAZY = 1;
-
- [DllImport(SystemLibrary)]
- public static extern IntPtr dlopen(IntPtr path, int mode);
- }
-
- private static class Win32
- {
- private const string SystemLibrary = "Kernel32.dll";
-
- [DllImport(SystemLibrary)]
- public static extern IntPtr GetModuleHandle(IntPtr lpModuleName);
- }
- // ReSharper restore InconsistentNaming
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs b/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs
index 1b969716aa..982549fff7 100644
--- a/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs
+++ b/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs
@@ -15,8 +15,6 @@ namespace GodotPlugins
public PluginLoadContext(string pluginPath, ICollection<string> sharedAssemblies,
AssemblyLoadContext mainLoadContext)
{
- Console.WriteLine(pluginPath);
- Console.Out.Flush();
_resolver = new AssemblyDependencyResolver(pluginPath);
_sharedAssemblies = sharedAssemblies;
_mainLoadContext = mainLoadContext;
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/GodotMethodAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/GodotMethodAttribute.cs
deleted file mode 100644
index 8d4ff0fdb7..0000000000
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/GodotMethodAttribute.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System;
-
-namespace Godot
-{
- /// <summary>
- /// An attribute for a method.
- /// </summary>
- [AttributeUsage(AttributeTargets.Method)]
- internal class GodotMethodAttribute : Attribute
- {
- private string methodName;
-
- public string MethodName { get { return methodName; } }
-
- /// <summary>
- /// Constructs a new GodotMethodAttribute instance.
- /// </summary>
- /// <param name="methodName">The name of the method.</param>
- public GodotMethodAttribute(string methodName)
- {
- this.methodName = methodName;
- }
- }
-}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs
index f28d7b1c51..266df07d63 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs
@@ -21,12 +21,8 @@ namespace Godot.Bridge
return false.ToGodotBool();
}
- NativeFuncs.godotsharp_string_name_as_string(out godot_string dest, CustomUnsafe.AsRef(method));
- string methodStr;
- using (dest)
- methodStr = Marshaling.ConvertStringToManaged(dest);
-
- bool methodInvoked = godotObject.InvokeGodotClassMethod(CustomUnsafe.AsRef(method), new NativeVariantPtrArgs(args),
+ bool methodInvoked = godotObject.InvokeGodotClassMethod(CustomUnsafe.AsRef(method),
+ new NativeVariantPtrArgs(args),
argCount, out godot_variant retValue);
if (!methodInvoked)
@@ -168,5 +164,24 @@ namespace Godot.Bridge
*outValid = false.ToGodotBool();
}
}
+
+ [UnmanagedCallersOnly]
+ internal static unsafe godot_bool HasMethodUnknownParams(IntPtr godotObjectGCHandle, godot_string_name* method)
+ {
+ try
+ {
+ var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target;
+
+ if (godotObject == null)
+ return false.ToGodotBool();
+
+ return godotObject.HasGodotClassMethod(CustomUnsafe.AsRef(method)).ToGodotBool();
+ }
+ catch (Exception e)
+ {
+ ExceptionUtils.DebugPrintUnhandledException(e);
+ return false.ToGodotBool();
+ }
+ }
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
index bd939ef27d..fb1efa0ac8 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
@@ -5,7 +5,7 @@ using Godot.NativeInterop;
namespace Godot.Bridge
{
[StructLayout(LayoutKind.Sequential)]
- internal unsafe struct ManagedCallbacks
+ public unsafe struct ManagedCallbacks
{
// @formatter:off
public delegate* unmanaged<IntPtr, godot_variant**, int, godot_bool*, void> SignalAwaiter_SignalCallback;
@@ -19,7 +19,6 @@ namespace Godot.Bridge
public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant**, int, godot_bool*, void> ScriptManagerBridge_RaiseEventSignal;
public delegate* unmanaged<IntPtr, godot_dictionary*, void> ScriptManagerBridge_GetScriptSignalList;
public delegate* unmanaged<IntPtr, godot_string*, godot_bool> ScriptManagerBridge_HasScriptSignal;
- public delegate* unmanaged<IntPtr, godot_string*, godot_bool, godot_bool> ScriptManagerBridge_HasMethodUnknownParams;
public delegate* unmanaged<IntPtr, IntPtr, godot_bool> ScriptManagerBridge_ScriptIsOrInherits;
public delegate* unmanaged<IntPtr, godot_string*, godot_bool> ScriptManagerBridge_AddScriptBridge;
public delegate* unmanaged<IntPtr, void> ScriptManagerBridge_RemoveScriptBridge;
@@ -30,6 +29,7 @@ namespace Godot.Bridge
public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant*, godot_bool> CSharpInstanceBridge_Get;
public delegate* unmanaged<IntPtr, godot_bool, void> CSharpInstanceBridge_CallDispose;
public delegate* unmanaged<IntPtr, godot_string*, godot_bool*, void> CSharpInstanceBridge_CallToString;
+ public delegate* unmanaged<IntPtr, godot_string_name*, godot_bool> CSharpInstanceBridge_HasMethodUnknownParams;
public delegate* unmanaged<IntPtr, void> GCHandleBridge_FreeGCHandle;
public delegate* unmanaged<void> DebuggingUtils_InstallTraceListener;
public delegate* unmanaged<void> Dispatcher_InitializeDefaultGodotTaskScheduler;
@@ -52,7 +52,6 @@ namespace Godot.Bridge
ScriptManagerBridge_RaiseEventSignal = &ScriptManagerBridge.RaiseEventSignal,
ScriptManagerBridge_GetScriptSignalList = &ScriptManagerBridge.GetScriptSignalList,
ScriptManagerBridge_HasScriptSignal = &ScriptManagerBridge.HasScriptSignal,
- ScriptManagerBridge_HasMethodUnknownParams = &ScriptManagerBridge.HasMethodUnknownParams,
ScriptManagerBridge_ScriptIsOrInherits = &ScriptManagerBridge.ScriptIsOrInherits,
ScriptManagerBridge_AddScriptBridge = &ScriptManagerBridge.AddScriptBridge,
ScriptManagerBridge_RemoveScriptBridge = &ScriptManagerBridge.RemoveScriptBridge,
@@ -63,6 +62,7 @@ namespace Godot.Bridge
CSharpInstanceBridge_Get = &CSharpInstanceBridge.Get,
CSharpInstanceBridge_CallDispose = &CSharpInstanceBridge.CallDispose,
CSharpInstanceBridge_CallToString = &CSharpInstanceBridge.CallToString,
+ CSharpInstanceBridge_HasMethodUnknownParams = &CSharpInstanceBridge.HasMethodUnknownParams,
GCHandleBridge_FreeGCHandle = &GCHandleBridge.FreeGCHandle,
DebuggingUtils_InstallTraceListener = &DebuggingUtils.InstallTraceListener,
Dispatcher_InitializeDefaultGodotTaskScheduler = &Dispatcher.InitializeDefaultGodotTaskScheduler,
@@ -70,5 +70,8 @@ namespace Godot.Bridge
// @formatter:on
};
}
+
+ public static void Create(IntPtr outManagedCallbacks)
+ => *(ManagedCallbacks*)outManagedCallbacks = Create();
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
index e87b7f4d4b..b86ced55cb 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
@@ -8,7 +8,7 @@ using Godot.NativeInterop;
namespace Godot.Bridge
{
- internal static class ScriptManagerBridge
+ public static class ScriptManagerBridge
{
private static System.Collections.Generic.Dictionary<string, ScriptLookupInfo> _scriptLookupMap = new();
private static System.Collections.Generic.Dictionary<IntPtr, Type> _scriptBridgeMap = new();
@@ -212,7 +212,7 @@ namespace Godot.Bridge
// Called from GodotPlugins
// ReSharper disable once UnusedMember.Local
- private static void LookupScriptsInAssembly(Assembly assembly)
+ public static void LookupScriptsInAssembly(Assembly assembly)
{
static void LookupScriptForClass(Type type)
{
@@ -440,68 +440,6 @@ namespace Godot.Bridge
}
[UnmanagedCallersOnly]
- internal static unsafe godot_bool HasMethodUnknownParams(IntPtr scriptPtr, godot_string* method,
- godot_bool deep)
- {
- try
- {
- // Performance is not critical here as this will be replaced with source generators.
- if (!_scriptBridgeMap.TryGetValue(scriptPtr, out var scriptType))
- return false.ToGodotBool();
-
- string methodStr = Marshaling.ConvertStringToManaged(*method);
-
- if (deep.ToBool())
- {
- Type top = scriptType;
- Type native = Object.InternalGetClassNativeBase(scriptType);
-
- while (top != null && top != native)
- {
- var methodInfo = top.GetMethod(methodStr,
- BindingFlags.DeclaredOnly | BindingFlags.Instance |
- BindingFlags.NonPublic | BindingFlags.Public);
-
- if (methodInfo != null)
- return true.ToGodotBool();
-
- top = top.BaseType;
- }
-
- top = native;
- Type typeOfSystemObject = typeof(System.Object);
- while (top != null && top != typeOfSystemObject)
- {
- bool found = top.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance |
- BindingFlags.NonPublic | BindingFlags.Public)
- .Where(m => m.GetCustomAttributes(false).OfType<GodotMethodAttribute>()
- .Where(a => a.MethodName == methodStr)
- .Any())
- .Any();
-
- if (found)
- return true.ToGodotBool();
-
- top = top.BaseType;
- }
-
- return false.ToGodotBool();
- }
- else
- {
- var methodInfo = scriptType.GetMethod(methodStr, BindingFlags.DeclaredOnly | BindingFlags.Instance |
- BindingFlags.NonPublic | BindingFlags.Public);
- return (methodInfo != null).ToGodotBool();
- }
- }
- catch (Exception e)
- {
- ExceptionUtils.DebugUnhandledException(e);
- return false.ToGodotBool();
- }
- }
-
- [UnmanagedCallersOnly]
internal static godot_bool ScriptIsOrInherits(IntPtr scriptPtr, IntPtr scriptPtrMaybeBase)
{
try
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/GodotDllImportResolver.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/GodotDllImportResolver.cs
new file mode 100644
index 0000000000..c4a90625c3
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/GodotDllImportResolver.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+#nullable enable
+
+namespace Godot.NativeInterop
+{
+ public static class GodotDllImportResolver
+ {
+ public static IntPtr OnResolveDllImport(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
+ {
+ if (libraryName == "__Internal")
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ return Win32.GetModuleHandle(IntPtr.Zero);
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ return Linux.dlopen(IntPtr.Zero, Linux.RTLD_LAZY);
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ return MacOS.dlopen(IntPtr.Zero, MacOS.RTLD_LAZY);
+ }
+ }
+
+ return IntPtr.Zero;
+ }
+
+ // ReSharper disable InconsistentNaming
+ private static class MacOS
+ {
+ private const string SystemLibrary = "/usr/lib/libSystem.dylib";
+
+ public const int RTLD_LAZY = 1;
+
+ [DllImport(SystemLibrary)]
+ public static extern IntPtr dlopen(IntPtr path, int mode);
+ }
+
+ private static class Linux
+ {
+ // libdl.so was resulting in DllNotFoundException, for some reason...
+ // libcoreclr.so should work with both CoreCLR and the .NET Core version of Mono.
+ private const string SystemLibrary = "libcoreclr.so";
+
+ public const int RTLD_LAZY = 1;
+
+ [DllImport(SystemLibrary)]
+ public static extern IntPtr dlopen(IntPtr path, int mode);
+ }
+
+ private static class Win32
+ {
+ private const string SystemLibrary = "Kernel32.dll";
+
+ [DllImport(SystemLibrary)]
+ public static extern IntPtr GetModuleHandle(IntPtr lpModuleName);
+ }
+ // ReSharper restore InconsistentNaming
+ }
+}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs
index a3a4c2599e..6255ffbbc7 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs
@@ -8,7 +8,7 @@ namespace Godot
public partial class Object : IDisposable
{
private bool _disposed = false;
- private Type _cachedType = typeof(Object);
+ private static readonly Type CachedType = typeof(Object);
internal IntPtr NativePtr;
private bool _memoryOwn;
@@ -20,20 +20,30 @@ namespace Godot
/// </summary>
public Object() : this(false)
{
+ unsafe
+ {
+ _ConstructAndInitialize(NativeCtor, NativeName, CachedType, refCounted: false);
+ }
+ }
+
+ internal unsafe void _ConstructAndInitialize(
+ delegate* unmanaged<IntPtr> nativeCtor,
+ StringName nativeName,
+ Type cachedType,
+ bool refCounted
+ )
+ {
if (NativePtr == IntPtr.Zero)
{
- unsafe
- {
- NativePtr = NativeCtor();
- }
+ NativePtr = nativeCtor();
InteropUtils.TieManagedToUnmanaged(this, NativePtr,
- NativeName, refCounted: false, GetType(), _cachedType);
+ nativeName, refCounted, GetType(), cachedType);
}
else
{
InteropUtils.TieManagedToUnmanagedWithPreSetup(this, NativePtr,
- GetType(), _cachedType);
+ GetType(), cachedType);
}
_weakReferenceToSelf = DisposablesTracker.RegisterGodotObject(this);
@@ -191,8 +201,14 @@ namespace Godot
internal static bool InternalIsClassNativeBase(Type t)
{
- var assemblyName = t.Assembly.GetName();
- return assemblyName.Name == "GodotSharp" || assemblyName.Name == "GodotSharpEditor";
+ // Check whether the type is declared in the GodotSharp or GodotSharpEditor assemblies
+ var typeAssembly = t.Assembly;
+
+ if (typeAssembly == CachedType.Assembly)
+ return true;
+
+ var typeAssemblyName = t.Assembly.GetName();
+ return typeAssemblyName.Name == "GodotSharpEditor";
}
// ReSharper disable once VirtualMemberNeverOverridden.Global
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
index f1a397f8fa..b7fbb81f9c 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
+++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
@@ -33,7 +33,6 @@
<Compile Include="Core\Array.cs" />
<Compile Include="Core\Attributes\AssemblyHasScriptsAttribute.cs" />
<Compile Include="Core\Attributes\ExportAttribute.cs" />
- <Compile Include="Core\Attributes\GodotMethodAttribute.cs" />
<Compile Include="Core\Attributes\RPCAttribute.cs" />
<Compile Include="Core\Attributes\ScriptPathAttribute.cs" />
<Compile Include="Core\Attributes\SignalAttribute.cs" />
@@ -68,6 +67,7 @@
<Compile Include="Core\Mathf.cs" />
<Compile Include="Core\MathfEx.cs" />
<Compile Include="Core\NativeInterop\ExceptionUtils.cs" />
+ <Compile Include="Core\NativeInterop\GodotDllImportResolver.cs" />
<Compile Include="Core\NativeInterop\InteropUtils.cs" />
<Compile Include="Core\NativeInterop\NativeFuncs.extended.cs" />
<Compile Include="Core\NativeInterop\NativeVariantPtrArgs.cs" />
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Properties/AssemblyInfo.cs b/modules/mono/glue/GodotSharp/GodotSharp/Properties/AssemblyInfo.cs
index dbd98d0afb..da6f293871 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Properties/AssemblyInfo.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Properties/AssemblyInfo.cs
@@ -1,4 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("GodotSharpEditor")]
-[assembly: InternalsVisibleTo("GodotPlugins")]
diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp
index 26ae7246ad..f79a245d7d 100644
--- a/modules/mono/godotsharp_dirs.cpp
+++ b/modules/mono/godotsharp_dirs.cpp
@@ -235,7 +235,11 @@ private:
#endif
+#ifdef TOOLS_ENABLED
api_assemblies_dir = api_assemblies_base_dir.plus_file(GDMono::get_expected_api_build_config());
+#else
+ api_assemblies_dir = data_dir_root;
+#endif
}
public:
diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp
index 7a3fd1af10..0792a88f52 100644
--- a/modules/mono/mono_gd/gd_mono.cpp
+++ b/modules/mono/mono_gd/gd_mono.cpp
@@ -42,7 +42,9 @@
#include "../utils/path_utils.h"
#include "gd_mono_cache.h"
+#ifdef TOOLS_ENABLED
#include <nethost.h>
+#endif
#include <coreclr_delegates.h>
#include <hostfxr.h>
@@ -129,6 +131,7 @@ void gd_mono_debug_init() {
} // namespace
namespace {
+hostfxr_initialize_for_dotnet_command_line_fn hostfxr_initialize_for_dotnet_command_line = nullptr;
hostfxr_initialize_for_runtime_config_fn hostfxr_initialize_for_runtime_config = nullptr;
hostfxr_get_runtime_delegate_fn hostfxr_get_runtime_delegate = nullptr;
hostfxr_close_fn hostfxr_close = nullptr;
@@ -151,6 +154,7 @@ HostFxrCharString str_to_hostfxr(const String &p_str) {
#endif
}
+#ifdef TOOLS_ENABLED
String str_from_hostfxr(const char_t *p_buffer) {
#ifdef _WIN32
return String::utf16((const char16_t *)p_buffer);
@@ -158,36 +162,98 @@ String str_from_hostfxr(const char_t *p_buffer) {
return String::utf8((const char *)p_buffer);
#endif
}
+#endif
const char_t *get_data(const HostFxrCharString &p_char_str) {
return (const char_t *)p_char_str.get_data();
}
+#ifdef TOOLS_ENABLED
+String find_hostfxr(size_t p_known_buffet_size, get_hostfxr_parameters *p_get_hostfxr_params) {
+ // Pre-allocate a large buffer for the path to hostfxr
+ Vector<char_t> buffer;
+ buffer.resize(p_known_buffet_size);
+
+ int rc = get_hostfxr_path(buffer.ptrw(), &p_known_buffet_size, p_get_hostfxr_params);
+
+ ERR_FAIL_COND_V_MSG(rc != 0, String(), "get_hostfxr_path failed with code: " + itos(rc));
+
+ return str_from_hostfxr(buffer.ptr());
+}
+#endif
+
String find_hostfxr() {
+#ifdef TOOLS_ENABLED
+ const int CoreHostLibMissingFailure = 0x80008083;
const int HostApiBufferTooSmall = 0x80008098;
size_t buffer_size = 0;
int rc = get_hostfxr_path(nullptr, &buffer_size, nullptr);
if (rc == HostApiBufferTooSmall) {
- // Pre-allocate a large buffer for the path to hostfxr
- Vector<char_t> buffer;
- buffer.resize(buffer_size);
-
- rc = get_hostfxr_path(buffer.ptrw(), &buffer_size, nullptr);
+ return find_hostfxr(buffer_size, nullptr);
+ }
- if (rc != 0) {
- return String();
+ if (rc == CoreHostLibMissingFailure) {
+ // Apparently `get_hostfxr_path` doesn't look for dotnet in `PATH`? (I suppose it needs the
+ // `DOTNET_ROOT` environment variable). If it fails, we try to find the dotnet executable
+ // in `PATH` ourselves and pass its location as `dotnet_root` to `get_hostfxr_path`.
+ String dotnet_exe = path::find_executable("dotnet");
+
+ if (!dotnet_exe.is_empty()) {
+ // The file found in PATH may be a symlink
+ dotnet_exe = path::abspath(path::realpath(dotnet_exe));
+
+ // TODO:
+ // Sometimes, the symlink may not point to the dotnet executable in the dotnet root.
+ // That's the case with snaps. The snap install should have been found with the
+ // previous `get_hostfxr_path`, but it would still be better to do this properly
+ // and use something like `dotnet --list-sdks/runtimes` to find the actual location.
+ // This way we could also check if the proper sdk or runtime is installed. This would
+ // allow us to fail gracefully and show some helpful information in the editor.
+
+ HostFxrCharString dotnet_root = str_to_hostfxr(dotnet_exe.get_base_dir());
+
+ get_hostfxr_parameters get_hostfxr_parameters = {
+ sizeof(get_hostfxr_parameters),
+ nullptr,
+ get_data(dotnet_root)
+ };
+
+ buffer_size = 0;
+ rc = get_hostfxr_path(nullptr, &buffer_size, &get_hostfxr_parameters);
+ if (rc == HostApiBufferTooSmall) {
+ return find_hostfxr(buffer_size, &get_hostfxr_parameters);
+ }
}
+ }
- return str_from_hostfxr(buffer.ptr());
+ if (rc == CoreHostLibMissingFailure) {
+ ERR_PRINT(String() + ".NET: One of the dependent libraries is missing. " +
+ "Typically when the `hostfxr`, `hostpolicy` or `coreclr` dynamic " +
+ "libraries are not present in the expected locations.");
}
return String();
+#else
+
+#if defined(WINDOWS_ENABLED)
+ return GodotSharpDirs::get_api_assemblies_dir()
+ .plus_file("hostfxr.dll");
+#elif defined(MACOS_ENABLED)
+ return GodotSharpDirs::get_api_assemblies_dir()
+ .plus_file("libhostfxr.dylib");
+#elif defined(UNIX_ENABLED)
+ return GodotSharpDirs::get_api_assemblies_dir()
+ .plus_file("libhostfxr.so");
+#else
+#error "Platform not supported (yet?)"
+#endif
+
+#endif
}
-// Forward declarations
-bool load_hostfxr() {
+bool load_hostfxr(void *&r_hostfxr_dll_handle) {
String hostfxr_path = find_hostfxr();
if (hostfxr_path.is_empty()) {
@@ -196,16 +262,20 @@ bool load_hostfxr() {
print_verbose("Found hostfxr: " + hostfxr_path);
- void *lib = nullptr;
- Error err = OS::get_singleton()->open_dynamic_library(hostfxr_path, lib);
- // TODO: Clean up lib handle when shutting down
+ Error err = OS::get_singleton()->open_dynamic_library(hostfxr_path, r_hostfxr_dll_handle);
if (err != OK) {
return false;
}
+ void *lib = r_hostfxr_dll_handle;
+
void *symbol = nullptr;
+ err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "hostfxr_initialize_for_dotnet_command_line", symbol);
+ ERR_FAIL_COND_V(err != OK, false);
+ hostfxr_initialize_for_dotnet_command_line = (hostfxr_initialize_for_dotnet_command_line_fn)symbol;
+
err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "hostfxr_initialize_for_runtime_config", symbol);
ERR_FAIL_COND_V(err != OK, false);
hostfxr_initialize_for_runtime_config = (hostfxr_initialize_for_runtime_config_fn)symbol;
@@ -223,12 +293,13 @@ bool load_hostfxr() {
hostfxr_close);
}
-load_assembly_and_get_function_pointer_fn initialize_hostfxr(const char_t *p_config_path) {
+#ifdef TOOLS_ENABLED
+load_assembly_and_get_function_pointer_fn initialize_hostfxr_for_config(const char_t *p_config_path) {
hostfxr_handle cxt = nullptr;
int rc = hostfxr_initialize_for_runtime_config(p_config_path, nullptr, &cxt);
if (rc != 0 || cxt == nullptr) {
hostfxr_close(cxt);
- ERR_FAIL_V_MSG(nullptr, "hostfxr_initialize_for_runtime_config failed");
+ ERR_FAIL_V_MSG(nullptr, "hostfxr_initialize_for_runtime_config failed with code: " + itos(rc));
}
void *load_assembly_and_get_function_pointer = nullptr;
@@ -236,13 +307,122 @@ load_assembly_and_get_function_pointer_fn initialize_hostfxr(const char_t *p_con
rc = hostfxr_get_runtime_delegate(cxt,
hdt_load_assembly_and_get_function_pointer, &load_assembly_and_get_function_pointer);
if (rc != 0 || load_assembly_and_get_function_pointer == nullptr) {
- ERR_FAIL_V_MSG(nullptr, "hostfxr_get_runtime_delegate failed");
+ ERR_FAIL_V_MSG(nullptr, "hostfxr_get_runtime_delegate failed with code: " + itos(rc));
}
hostfxr_close(cxt);
return (load_assembly_and_get_function_pointer_fn)load_assembly_and_get_function_pointer;
}
+#else
+load_assembly_and_get_function_pointer_fn initialize_hostfxr_self_contained(
+ const char_t *p_main_assembly_path) {
+ hostfxr_handle cxt = nullptr;
+
+ List<String> cmdline_args = OS::get_singleton()->get_cmdline_args();
+
+ List<HostFxrCharString> argv_store;
+ Vector<const char_t *> argv;
+ argv.resize(cmdline_args.size() + 1);
+
+ argv.write[0] = p_main_assembly_path;
+
+ int i = 1;
+ for (const String &E : cmdline_args) {
+ HostFxrCharString &stored = argv_store.push_back(str_to_hostfxr(E))->get();
+ argv.write[i] = stored.ptr();
+ i++;
+ }
+
+ int rc = hostfxr_initialize_for_dotnet_command_line(argv.size(), argv.ptrw(), nullptr, &cxt);
+ if (rc != 0 || cxt == nullptr) {
+ hostfxr_close(cxt);
+ ERR_FAIL_V_MSG(nullptr, "hostfxr_initialize_for_dotnet_command_line failed with code: " + itos(rc));
+ }
+
+ void *load_assembly_and_get_function_pointer = nullptr;
+
+ rc = hostfxr_get_runtime_delegate(cxt,
+ hdt_load_assembly_and_get_function_pointer, &load_assembly_and_get_function_pointer);
+ if (rc != 0 || load_assembly_and_get_function_pointer == nullptr) {
+ ERR_FAIL_V_MSG(nullptr, "hostfxr_get_runtime_delegate failed with code: " + itos(rc));
+ }
+
+ hostfxr_close(cxt);
+
+ return (load_assembly_and_get_function_pointer_fn)load_assembly_and_get_function_pointer;
+}
+#endif
+
+#ifdef TOOLS_ENABLED
+using godot_plugins_initialize_fn = bool (*)(bool, gdmono::PluginCallbacks *, GDMonoCache::ManagedCallbacks *);
+#else
+using godot_plugins_initialize_fn = bool (*)(GDMonoCache::ManagedCallbacks *);
+#endif
+
+#ifdef TOOLS_ENABLED
+godot_plugins_initialize_fn initialize_hostfxr_and_godot_plugins(bool &r_runtime_initialized) {
+ godot_plugins_initialize_fn godot_plugins_initialize = nullptr;
+
+ HostFxrCharString godot_plugins_path = str_to_hostfxr(
+ GodotSharpDirs::get_api_assemblies_dir().plus_file("GodotPlugins.dll"));
+
+ HostFxrCharString config_path = str_to_hostfxr(
+ GodotSharpDirs::get_api_assemblies_dir().plus_file("GodotPlugins.runtimeconfig.json"));
+
+ load_assembly_and_get_function_pointer_fn load_assembly_and_get_function_pointer =
+ initialize_hostfxr_for_config(get_data(config_path));
+ ERR_FAIL_NULL_V(load_assembly_and_get_function_pointer, nullptr);
+
+ r_runtime_initialized = true;
+
+ print_verbose(".NET: hostfxr initialized");
+
+ int rc = load_assembly_and_get_function_pointer(get_data(godot_plugins_path),
+ HOSTFXR_STR("GodotPlugins.Main, GodotPlugins"),
+ HOSTFXR_STR("InitializeFromEngine"),
+ UNMANAGEDCALLERSONLY_METHOD,
+ nullptr,
+ (void **)&godot_plugins_initialize);
+ ERR_FAIL_COND_V_MSG(rc != 0, nullptr, ".NET: Failed to get GodotPlugins initialization function pointer");
+
+ return godot_plugins_initialize;
+}
+#else
+godot_plugins_initialize_fn initialize_hostfxr_and_godot_plugins(bool &r_runtime_initialized) {
+ String appname = ProjectSettings::get_singleton()->get("application/config/name");
+ String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
+ if (appname_safe.is_empty()) {
+ appname_safe = "UnnamedProject";
+ }
+
+ godot_plugins_initialize_fn godot_plugins_initialize = nullptr;
+
+ String assembly_name = appname_safe;
+
+ HostFxrCharString assembly_path = str_to_hostfxr(GodotSharpDirs::get_api_assemblies_dir()
+ .plus_file(assembly_name + ".dll"));
+
+ load_assembly_and_get_function_pointer_fn load_assembly_and_get_function_pointer =
+ initialize_hostfxr_self_contained(get_data(assembly_path));
+ ERR_FAIL_NULL_V(load_assembly_and_get_function_pointer, nullptr);
+
+ r_runtime_initialized = true;
+
+ print_verbose(".NET: hostfxr initialized");
+
+ int rc = load_assembly_and_get_function_pointer(get_data(assembly_path),
+ str_to_hostfxr("GodotPlugins.Game.Main, " + assembly_name),
+ HOSTFXR_STR("InitializeFromGameProject"),
+ UNMANAGEDCALLERSONLY_METHOD,
+ nullptr,
+ (void **)&godot_plugins_initialize);
+ ERR_FAIL_COND_V_MSG(rc != 0, nullptr, ".NET: Failed to get GodotPlugins initialization function pointer");
+
+ return godot_plugins_initialize;
+}
+#endif
+
} // namespace
static bool _on_core_api_assembly_loaded() {
@@ -261,61 +441,44 @@ static bool _on_core_api_assembly_loaded() {
}
void GDMono::initialize() {
- ERR_FAIL_NULL(Engine::get_singleton());
-
print_verbose(".NET: Initializing module...");
_init_godot_api_hashes();
- if (!load_hostfxr()) {
+ if (!load_hostfxr(hostfxr_dll_handle)) {
ERR_FAIL_MSG(".NET: Failed to load hostfxr");
}
- auto config_path = str_to_hostfxr(GodotSharpDirs::get_api_assemblies_dir()
- .plus_file("GodotPlugins.runtimeconfig.json"));
-
- load_assembly_and_get_function_pointer_fn load_assembly_and_get_function_pointer =
- initialize_hostfxr(get_data(config_path));
- ERR_FAIL_NULL(load_assembly_and_get_function_pointer);
-
- runtime_initialized = true;
-
- print_verbose(".NET: hostfxr initialized");
-
- auto godot_plugins_path = str_to_hostfxr(GodotSharpDirs::get_api_assemblies_dir()
- .plus_file("GodotPlugins.dll"));
-
- using godot_plugins_initialize_fn = bool (*)(bool, PluginCallbacks *, GDMonoCache::ManagedCallbacks *);
- godot_plugins_initialize_fn godot_plugins_initialize = nullptr;
-
- int rc = load_assembly_and_get_function_pointer(get_data(godot_plugins_path),
- HOSTFXR_STR("GodotPlugins.Main, GodotPlugins"),
- HOSTFXR_STR("Initialize"),
- UNMANAGEDCALLERSONLY_METHOD,
- nullptr,
- (void **)&godot_plugins_initialize);
- ERR_FAIL_COND_MSG(rc != 0, ".NET: Failed to get Godot.Plugins Initialize function pointer");
+ godot_plugins_initialize_fn godot_plugins_initialize =
+ initialize_hostfxr_and_godot_plugins(runtime_initialized);
+ ERR_FAIL_NULL(godot_plugins_initialize);
- PluginCallbacks aux_plugin_callbacks;
GDMonoCache::ManagedCallbacks managed_callbacks;
+
+#ifdef TOOLS_ENABLED
+ gdmono::PluginCallbacks plugin_callbacks_res;
bool init_ok = godot_plugins_initialize(Engine::get_singleton()->is_editor_hint(),
- &aux_plugin_callbacks, &managed_callbacks);
- ERR_FAIL_COND_MSG(!init_ok, ".NET: Call to Godot.Plugins Initialize failed");
+ &plugin_callbacks_res, &managed_callbacks);
+ ERR_FAIL_COND_MSG(!init_ok, ".NET: GodotPlugins initialization failed");
+
+ plugin_callbacks = plugin_callbacks_res;
+#else
+ bool init_ok = godot_plugins_initialize(&managed_callbacks);
+ ERR_FAIL_COND_MSG(!init_ok, ".NET: GodotPlugins initialization failed");
+#endif
GDMonoCache::update_godot_api_cache(managed_callbacks);
- plugin_callbacks = aux_plugin_callbacks;
print_verbose(".NET: GodotPlugins initialized");
_on_core_api_assembly_loaded();
}
+#ifdef TOOLS_ENABLED
void GDMono::initialize_load_assemblies() {
-#if defined(TOOLS_ENABLED)
if (Engine::get_singleton()->is_project_manager_hint()) {
return;
}
-#endif
// Load the project's main assembly. This doesn't necessarily need to succeed.
// The game may not be using .NET at all, or if the project does use .NET and
@@ -326,6 +489,7 @@ void GDMono::initialize_load_assemblies() {
}
}
}
+#endif
void GDMono::_init_godot_api_hashes() {
#ifdef DEBUG_METHODS_ENABLED
@@ -337,6 +501,7 @@ void GDMono::_init_godot_api_hashes() {
#endif // DEBUG_METHODS_ENABLED
}
+#ifdef TOOLS_ENABLED
bool GDMono::_load_project_assembly() {
String appname = ProjectSettings::get_singleton()->get("application/config/name");
String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
@@ -350,6 +515,7 @@ bool GDMono::_load_project_assembly() {
return plugin_callbacks.LoadProjectAssemblyCallback(assembly_path.utf16());
}
+#endif
#warning TODO hot-reload
#if 0
@@ -479,6 +645,10 @@ GDMono::~GDMono() {
}
}
+ if (hostfxr_dll_handle) {
+ OS::get_singleton()->close_dynamic_library(hostfxr_dll_handle);
+ }
+
finalizing_scripts_domain = false;
runtime_initialized = false;
diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h
index fee1cab9c7..66ed331b67 100644
--- a/modules/mono/mono_gd/gd_mono.h
+++ b/modules/mono/mono_gd/gd_mono.h
@@ -35,11 +35,36 @@
#include "../godotsharp_defs.h"
+#ifdef WIN32
+#define GD_CLR_STDCALL __stdcall
+#else
+#define GD_CLR_STDCALL
+#endif
+
+namespace gdmono {
+
+#ifdef TOOLS_ENABLED
+struct PluginCallbacks {
+ using FuncLoadProjectAssemblyCallback = bool(GD_CLR_STDCALL *)(const char16_t *);
+ using FuncLoadToolsAssemblyCallback = Object *(GD_CLR_STDCALL *)(const char16_t *);
+ FuncLoadProjectAssemblyCallback LoadProjectAssemblyCallback = nullptr;
+ FuncLoadToolsAssemblyCallback LoadToolsAssemblyCallback = nullptr;
+};
+#endif
+
+} // namespace gdmono
+
+#undef GD_CLR_STDCALL
+
class GDMono {
bool runtime_initialized;
bool finalizing_scripts_domain;
+ void *hostfxr_dll_handle = nullptr;
+
+#ifdef TOOLS_ENABLED
bool _load_project_assembly();
+#endif
bool _try_load_api_assemblies();
@@ -51,18 +76,9 @@ class GDMono {
#endif
void _init_godot_api_hashes();
- friend class CSharpLanguage;
-#ifdef WIN32
-#define GD_CLR_STDCALL __stdcall
-#else
-#define GD_CLR_STDCALL
+#ifdef TOOLS_ENABLED
+ gdmono::PluginCallbacks plugin_callbacks;
#endif
- struct PluginCallbacks {
- using FuncLoadProjectAssemblyCallback = bool(GD_CLR_STDCALL *)(const char16_t *);
- using FuncLoadToolsAssemblyCallback = Object *(GD_CLR_STDCALL *)(const char16_t *);
- FuncLoadProjectAssemblyCallback LoadProjectAssemblyCallback = nullptr;
- FuncLoadToolsAssemblyCallback LoadToolsAssemblyCallback = nullptr;
- } plugin_callbacks;
protected:
static GDMono *singleton;
@@ -102,12 +118,18 @@ public:
_FORCE_INLINE_ bool is_runtime_initialized() const { return runtime_initialized; }
_FORCE_INLINE_ bool is_finalizing_scripts_domain() { return finalizing_scripts_domain; }
+#ifdef TOOLS_ENABLED
+ const gdmono::PluginCallbacks &get_plugin_callbacks() { return plugin_callbacks; }
+#endif
+
#ifdef GD_MONO_HOT_RELOAD
Error reload_scripts_domain();
#endif
void initialize();
+#ifdef TOOLS_ENABLED
void initialize_load_assemblies();
+#endif
GDMono();
~GDMono();
diff --git a/modules/mono/mono_gd/gd_mono_cache.cpp b/modules/mono/mono_gd/gd_mono_cache.cpp
index e8b25cb119..7d33f0a896 100644
--- a/modules/mono/mono_gd/gd_mono_cache.cpp
+++ b/modules/mono/mono_gd/gd_mono_cache.cpp
@@ -54,7 +54,6 @@ void update_godot_api_cache(const ManagedCallbacks &p_managed_callbacks) {
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, RaiseEventSignal);
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetScriptSignalList);
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, HasScriptSignal);
- CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, HasMethodUnknownParams);
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, ScriptIsOrInherits);
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, AddScriptBridge);
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, RemoveScriptBridge);
@@ -65,6 +64,7 @@ void update_godot_api_cache(const ManagedCallbacks &p_managed_callbacks) {
CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, Get);
CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, CallDispose);
CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, CallToString);
+ CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, HasMethodUnknownParams);
CHECK_CALLBACK_NOT_NULL(GCHandleBridge, FreeGCHandle);
CHECK_CALLBACK_NOT_NULL(DebuggingUtils, InstallTraceListener);
CHECK_CALLBACK_NOT_NULL(Dispatcher, InitializeDefaultGodotTaskScheduler);
diff --git a/modules/mono/mono_gd/gd_mono_cache.h b/modules/mono/mono_gd/gd_mono_cache.h
index 17c8c9fa51..b993facff9 100644
--- a/modules/mono/mono_gd/gd_mono_cache.h
+++ b/modules/mono/mono_gd/gd_mono_cache.h
@@ -64,7 +64,6 @@ struct ManagedCallbacks {
using FuncScriptManagerBridge_RaiseEventSignal = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *, const Variant **, int, bool *);
using FuncScriptManagerBridge_GetScriptSignalList = void(GD_CLR_STDCALL *)(const CSharpScript *, Dictionary *);
using FuncScriptManagerBridge_HasScriptSignal = bool(GD_CLR_STDCALL *)(const CSharpScript *, const String *);
- using FuncScriptManagerBridge_HasMethodUnknownParams = bool(GD_CLR_STDCALL *)(const CSharpScript *, const String *, bool);
using FuncScriptManagerBridge_ScriptIsOrInherits = bool(GD_CLR_STDCALL *)(const CSharpScript *, const CSharpScript *);
using FuncScriptManagerBridge_AddScriptBridge = bool(GD_CLR_STDCALL *)(const CSharpScript *, const String *);
using FuncScriptManagerBridge_RemoveScriptBridge = void(GD_CLR_STDCALL *)(const CSharpScript *);
@@ -75,6 +74,7 @@ struct ManagedCallbacks {
using FuncCSharpInstanceBridge_Get = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *, Variant *);
using FuncCSharpInstanceBridge_CallDispose = void(GD_CLR_STDCALL *)(GCHandleIntPtr, bool);
using FuncCSharpInstanceBridge_CallToString = void(GD_CLR_STDCALL *)(GCHandleIntPtr, String *, bool *);
+ using FuncCSharpInstanceBridge_HasMethodUnknownParams = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *);
using FuncGCHandleBridge_FreeGCHandle = void(GD_CLR_STDCALL *)(GCHandleIntPtr);
using FuncDebuggingUtils_InstallTraceListener = void(GD_CLR_STDCALL *)();
using FuncDispatcher_InitializeDefaultGodotTaskScheduler = void(GD_CLR_STDCALL *)();
@@ -91,7 +91,6 @@ struct ManagedCallbacks {
FuncScriptManagerBridge_RaiseEventSignal ScriptManagerBridge_RaiseEventSignal;
FuncScriptManagerBridge_GetScriptSignalList ScriptManagerBridge_GetScriptSignalList;
FuncScriptManagerBridge_HasScriptSignal ScriptManagerBridge_HasScriptSignal;
- FuncScriptManagerBridge_HasMethodUnknownParams ScriptManagerBridge_HasMethodUnknownParams;
FuncScriptManagerBridge_ScriptIsOrInherits ScriptManagerBridge_ScriptIsOrInherits;
FuncScriptManagerBridge_AddScriptBridge ScriptManagerBridge_AddScriptBridge;
FuncScriptManagerBridge_RemoveScriptBridge ScriptManagerBridge_RemoveScriptBridge;
@@ -102,6 +101,7 @@ struct ManagedCallbacks {
FuncCSharpInstanceBridge_Get CSharpInstanceBridge_Get;
FuncCSharpInstanceBridge_CallDispose CSharpInstanceBridge_CallDispose;
FuncCSharpInstanceBridge_CallToString CSharpInstanceBridge_CallToString;
+ FuncCSharpInstanceBridge_HasMethodUnknownParams CSharpInstanceBridge_HasMethodUnknownParams;
FuncGCHandleBridge_FreeGCHandle GCHandleBridge_FreeGCHandle;
FuncDebuggingUtils_InstallTraceListener DebuggingUtils_InstallTraceListener;
FuncDispatcher_InitializeDefaultGodotTaskScheduler Dispatcher_InitializeDefaultGodotTaskScheduler;
@@ -118,4 +118,6 @@ inline void clear_godot_api_cache() {
}
} // namespace GDMonoCache
+#undef GD_CLR_STDCALL
+
#endif // GD_MONO_CACHE_H
diff --git a/modules/mono/utils/path_utils.cpp b/modules/mono/utils/path_utils.cpp
index a1905dfcfe..19ad59a1bc 100644
--- a/modules/mono/utils/path_utils.cpp
+++ b/modules/mono/utils/path_utils.cpp
@@ -51,6 +51,37 @@
namespace path {
+String find_executable(const String &p_name) {
+#ifdef WINDOWS_ENABLED
+ Vector<String> exts = OS::get_singleton()->get_environment("PATHEXT").split(ENV_PATH_SEP, false);
+#endif
+ Vector<String> env_path = OS::get_singleton()->get_environment("PATH").split(ENV_PATH_SEP, false);
+
+ if (env_path.is_empty()) {
+ return String();
+ }
+
+ for (int i = 0; i < env_path.size(); i++) {
+ String p = path::join(env_path[i], p_name);
+
+#ifdef WINDOWS_ENABLED
+ for (int j = 0; j < exts.size(); j++) {
+ String p2 = p + exts[j].to_lower(); // lowercase to reduce risk of case mismatch warning
+
+ if (FileAccess::exists(p2)) {
+ return p2;
+ }
+ }
+#else
+ if (FileAccess::exists(p)) {
+ return p;
+ }
+#endif
+ }
+
+ return String();
+}
+
String cwd() {
#ifdef WINDOWS_ENABLED
const DWORD expected_size = ::GetCurrentDirectoryW(0, nullptr);
diff --git a/modules/mono/utils/path_utils.h b/modules/mono/utils/path_utils.h
index 9a2c757361..d1c3d3ccfd 100644
--- a/modules/mono/utils/path_utils.h
+++ b/modules/mono/utils/path_utils.h
@@ -36,6 +36,8 @@
namespace path {
+String find_executable(const String &p_name);
+
String join(const String &p_a, const String &p_b);
String join(const String &p_a, const String &p_b, const String &p_c);
String join(const String &p_a, const String &p_b, const String &p_c, const String &p_d);