Merge pull request #64089 from neikeq/dotnet6

This commit is contained in:
Rémi Verschelde 2022-08-22 10:07:21 +02:00 committed by GitHub
commit 8a1e598011
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
242 changed files with 20784 additions and 18554 deletions

View File

@ -6,6 +6,8 @@ env:
# Only used for the cache key. Increment version to force clean build.
GODOT_BASE_BRANCH: master-v2
SCONSFLAGS: verbose=yes warnings=extra werror=yes module_text_server_fb_enabled=yes
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: false
concurrency:
group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-linux
@ -99,6 +101,12 @@ jobs:
- name: Setup python and scons
uses: ./.github/actions/godot-deps
- name: Set up .NET Sdk
uses: actions/setup-dotnet@v1
if: ${{ matrix.build-mono }}
with:
dotnet-version: '6.0.x'
- name: Compilation
uses: ./.github/actions/godot-build
with:
@ -108,10 +116,15 @@ jobs:
tools: ${{ matrix.tools }}
tests: ${{ matrix.tests }}
- name: Generate Mono glue
- name: Generate C# glue
if: ${{ matrix.build-mono }}
run: |
${{ matrix.bin }} --headless --generate-mono-glue modules/mono/glue || true
${{ matrix.bin }} --headless --generate-mono-glue ./modules/mono/glue || true
- name: Build .NET solutions
if: ${{ matrix.build-mono }}
run: |
./modules/mono/build_scripts/build_assemblies.py --godot-output-dir=./bin --godot-platform=linuxbsd
# Rebuild with mono
- name: Compilation (mono_glue=yes)

View File

@ -1433,20 +1433,6 @@
<member name="memory/limits/multithreaded_server/rid_pool_prealloc" type="int" setter="" getter="" default="60">
This is used by servers when used in multi-threading mode (servers and visual). RIDs are preallocated to avoid stalling the server requesting them on threads. If servers get stalled too often when loading resources in a thread, increase this number.
</member>
<member name="mono/debugger_agent/port" type="int" setter="" getter="" default="23685">
</member>
<member name="mono/debugger_agent/wait_for_debugger" type="bool" setter="" getter="" default="false">
</member>
<member name="mono/debugger_agent/wait_timeout" type="int" setter="" getter="" default="3000">
</member>
<member name="mono/profiler/args" type="String" setter="" getter="" default="&quot;log:calls,alloc,sample,output=output.mlpd&quot;">
</member>
<member name="mono/profiler/enabled" type="bool" setter="" getter="" default="false">
</member>
<member name="mono/runtime/unhandled_exception_policy" type="int" setter="" getter="" default="0">
The policy to use for unhandled Mono (C#) exceptions. The default "Terminate Application" exits the project as soon as an unhandled exception is thrown. "Log Error" logs an error message to the console instead, and will not interrupt the project execution when an unhandled exception is thrown.
[b]Note:[/b] The unhandled exception policy is always set to "Log Error" in the editor, which also includes C# [code]tool[/code] scripts running within the editor as well as editor plugin code.
</member>
<member name="navigation/2d/default_cell_size" type="int" setter="" getter="" default="1">
Default cell size for 2D navigation maps. See [method NavigationServer2D.map_set_cell_size].
</member>

View File

@ -2255,18 +2255,6 @@ bool Main::start() {
ERR_FAIL_COND_V_MSG(da.is_null(), false, "Argument supplied to --doctool must be a valid directory path.");
}
#ifndef MODULE_MONO_ENABLED
// Hack to define Mono-specific project settings even on non-Mono builds,
// so that we don't lose their descriptions and default values in DocData.
// Default values should be synced with mono_gd/gd_mono.cpp.
GLOBAL_DEF("mono/debugger_agent/port", 23685);
GLOBAL_DEF("mono/debugger_agent/wait_for_debugger", false);
GLOBAL_DEF("mono/debugger_agent/wait_timeout", 3000);
GLOBAL_DEF("mono/profiler/args", "log:calls,alloc,sample,output=output.mlpd");
GLOBAL_DEF("mono/profiler/enabled", false);
GLOBAL_DEF("mono/runtime/unhandled_exception_policy", 0);
#endif
Error err;
DocTools doc;
doc.generate(doc_base);

View File

@ -818,18 +818,21 @@ def generate_vs_project(env, num_jobs):
module_configs = ModuleConfigs()
if env.get("module_mono_enabled"):
import modules.mono.build_scripts.mono_reg_utils as mono_reg
import modules.mono.build_scripts.mono_configure as mono_configure
mono_root = env.get("mono_prefix") or mono_reg.find_mono_root_dir(env["bits"])
if mono_root:
app_host_dir = mono_configure.find_dotnet_app_host_dir(env)
if app_host_dir and os.path.isdir(app_host_dir):
mono_defines = [("NETHOST_USE_AS_STATIC",)]
if env["tools"]:
mono_defines += [("GD_MONO_HOT_RELOAD",)]
module_configs.add_mode(
"mono",
includes=os.path.join(mono_root, "include", "mono-2.0"),
cli_args="module_mono_enabled=yes mono_glue=yes",
defines=[("MONO_GLUE_ENABLED",)],
includes=app_host_dir,
cli_args="module_mono_enabled=yes",
defines=mono_defines,
)
else:
print("Mono installation directory not found. Generated project will not have build variants for Mono.")
print(".NET App Host directory not found. Generated project will not have build variants for .NET.")
env["MSVSBUILDCOM"] = module_configs.build_commandline("scons")
env["MSVSREBUILDCOM"] = module_configs.build_commandline("scons vsproj=yes")

View File

@ -12,3 +12,32 @@ insert_final_newline = true
trim_trailing_whitespace = true
max_line_length = 120
csharp_indent_case_contents_when_block = false
[*.cs]
# CA1707: Identifiers should not contain underscores
# TODO:
# Maybe we could disable this selectively only
# where it's not desired and for generated code.
dotnet_diagnostic.CA1707.severity = none
# CA1711: Identifiers should not have incorrect suffix
# Disable warning for suffixes like EventHandler, Flags, Enum, etc.
dotnet_diagnostic.CA1711.severity = none
# CA1716: Identifiers should not match keywords
# TODO: We should look into this.
dotnet_diagnostic.CA1716.severity = warning
# CA1720: Identifiers should not contain type names
dotnet_diagnostic.CA1720.severity = none
# CA1805: Do not initialize unnecessarily
# Don't tell me what to do.
dotnet_diagnostic.CA1805.severity = none
# CA1304: Specify CultureInfo
# TODO: We should look into this.
dotnet_diagnostic.CA1304.severity = warning
# CA1305: Specify IFormatProvider
# TODO: We should look into this. Disabled for now because it's annoying.
dotnet_diagnostic.CA1305.severity = none
# CA1310: Specify StringComparison for correctness
# TODO: We should look into this. Disabled for now because it's annoying.
dotnet_diagnostic.CA1310.severity = none
# Diagnostics to prevent defensive copies of `in` struct parameters
resharper_possibly_impure_method_call_on_readonly_variable_highlighting = error

View File

@ -1,3 +1,6 @@
<Project>
<Import Project="$(MSBuildThisFileDirectory)\SdkPackageVersions.props" />
<PropertyGroup>
<GodotSdkPackageVersionsFilePath>$(MSBuildThisFileDirectory)\SdkPackageVersions.props</GodotSdkPackageVersionsFilePath>
</PropertyGroup>
<Import Project="$(GodotSdkPackageVersionsFilePath)" />
</Project>

View File

@ -0,0 +1,22 @@
<Project>
<PropertyGroup>
<_HasNuGetPackage Condition=" '$(_HasNuGetPackage)' == '' And '$(PackageId)' != '' And '$(GeneratePackageOnBuild.ToLower())' == 'true' ">true</_HasNuGetPackage>
<_HasNuGetPackage Condition=" '$(_HasNuGetPackage)' == '' ">false</_HasNuGetPackage>
</PropertyGroup>
<Target Name="CopyNupkgToSConsOutputDir" AfterTargets="Pack"
Condition=" '$(_HasNuGetPackage)' == 'true' ">
<PropertyGroup>
<GodotSourceRootPath>$(MSBuildThisFileDirectory)\..\..\</GodotSourceRootPath>
<GodotOutputDataDir>$(GodotSourceRootPath)\bin\GodotSharp\</GodotOutputDataDir>
</PropertyGroup>
<Copy SourceFiles="$(PackageOutputPath)$(PackageId).$(PackageVersion).nupkg" DestinationFolder="$(GodotOutputDataDir)Tools\nupkgs\" />
</Target>
<Target Name="PushNuGetPackagesToLocalSource" BeforeTargets="Pack"
Condition=" '$(_HasNuGetPackage)' == 'true' And '$(PushNuGetToLocalSource)' != '' ">
<Copy SourceFiles="$(PackageOutputPath)$(PackageId).$(PackageVersion).nupkg" DestinationFolder="$(PushNuGetToLocalSource)\" />
</Target>
<Target Name="ClearNuGetLocalPackageCache" BeforeTargets="Pack"
Condition=" '$(_HasNuGetPackage)' == 'true' And '$(ClearNuGetLocalCache.ToLower())' == 'true' ">
<RemoveDir Directories="$(NugetPackageRoot)/$(PackageId.ToLower())/$(PackageVersion)"/>
</Target>
</Project>

45
modules/mono/README.md Normal file
View File

@ -0,0 +1,45 @@
# How to build and run
1. Build Godot with the module enabled: `module_mono_enabled=yes`.
2. After building Godot, use it to generate the C# glue code:
```sh
<godot_binary> --generate-mono-glue ./modules/mono/glue
```
3. Build the C# solutions:
```sh
./modules/mono/build_scripts/build_assemblies.py --godot-output-dir ./bin
```
The paths specified in these examples assume the command is being run from
the Godot source root.
# How to deal with NuGet packages
We distribute the API assemblies, our source generators, and our custom
MSBuild project SDK as NuGet packages. This is all transparent to the user,
but it can make things complicated during development.
In order to use Godot with a development of those packages, we must create
a local NuGet source where MSBuild can find them. This can be done with
the .NET CLI:
```sh
dotnet nuget add source ~/MyLocalNugetSource --name MyLocalNugetSource
```
The Godot NuGet packages must be added to that local source. Additionally,
we must make sure there are no other versions of the package in the NuGet
cache, as MSBuild may pick one of those instead.
In order to simplify this process, the `build_assemblies.py` script provides
the following `--push-nupkgs-local` option:
```sh
./modules/mono/build_scripts/build_assemblies.py --godot-output-dir ./bin \
--push-nupkgs-local ~/MyLocalNugetSource
```
This option ensures the packages will be added to the specified local NuGet
source and that conflicting versions of the package are removed from the
NuGet cache. It's recommended to always use this option when building the
C# solutions during development to avoid mistakes.

View File

@ -7,49 +7,14 @@ Import("env_modules")
env_mono = env_modules.Clone()
if env_mono["tools"]:
# NOTE: It is safe to generate this file here, since this is still executed serially
import build_scripts.gen_cs_glue_version as gen_cs_glue_version
gen_cs_glue_version.generate_header("glue/GodotSharp", "glue/cs_glue_version.gen.h")
# Glue sources
if env_mono["mono_glue"]:
env_mono.Append(CPPDEFINES=["MONO_GLUE_ENABLED"])
import os.path
if not os.path.isfile("glue/mono_glue.gen.cpp"):
raise RuntimeError("Mono glue sources not found. Did you forget to run '--generate-mono-glue'?")
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)
if env_mono["tools"] and env_mono["mono_glue"] and env_mono["build_cil"]:
# Build Godot API solution
import build_scripts.api_solution_build as api_solution_build
api_sln_cmd = api_solution_build.build(env_mono)
# Build GodotTools
import build_scripts.godot_tools_build as godot_tools_build
godot_tools_build.build(env_mono, api_sln_cmd)
# Build Godot.NET.Sdk
import build_scripts.godot_net_sdk_build as godot_net_sdk_build
godot_net_sdk_build.build(env_mono)
# Add sources
env_mono.add_source_files(env.modules_sources, "*.cpp")
env_mono.add_source_files(env.modules_sources, "glue/*.cpp")
env_mono.add_source_files(env.modules_sources, "glue/mono_glue.gen.cpp")
env_mono.add_source_files(env.modules_sources, "mono_gd/*.cpp")
env_mono.add_source_files(env.modules_sources, "utils/*.cpp")

View File

@ -1,7 +1,8 @@
<Project>
<PropertyGroup>
<PackageFloatingVersion_Godot>4.0.*-*</PackageFloatingVersion_Godot>
<PackageVersion_Godot_NET_Sdk>4.0.0-dev6</PackageVersion_Godot_NET_Sdk>
<PackageVersion_Godot_SourceGenerators>4.0.0-dev3</PackageVersion_Godot_SourceGenerators>
<PackageVersion_GodotSharp>4.0.0-dev</PackageVersion_GodotSharp>
<PackageVersion_Godot_NET_Sdk>4.0.0-dev8</PackageVersion_Godot_NET_Sdk>
<PackageVersion_Godot_SourceGenerators>4.0.0-dev8</PackageVersion_Godot_SourceGenerators>
</PropertyGroup>
</Project>

View File

@ -1,80 +0,0 @@
# Build the Godot API solution
import os
from SCons.Script import Dir
def build_api_solution(source, target, env):
# source and target elements are of type SCons.Node.FS.File, hence why we convert them to str
module_dir = env["module_dir"]
solution_path = os.path.join(module_dir, "glue/GodotSharp/GodotSharp.sln")
build_config = env["solution_build_config"]
extra_msbuild_args = ["/p:NoWarn=1591"] # Ignore missing documentation warnings
from .solution_builder import build_solution
build_solution(env, solution_path, build_config, extra_msbuild_args=extra_msbuild_args)
# Copy targets
core_src_dir = os.path.abspath(os.path.join(solution_path, os.pardir, "GodotSharp", "bin", build_config))
editor_src_dir = os.path.abspath(os.path.join(solution_path, os.pardir, "GodotSharpEditor", "bin", build_config))
dst_dir = os.path.abspath(os.path.join(str(target[0]), os.pardir))
if not os.path.isdir(dst_dir):
assert not os.path.isfile(dst_dir)
os.makedirs(dst_dir)
def copy_target(target_path):
from shutil import copy
filename = os.path.basename(target_path)
src_path = os.path.join(core_src_dir, filename)
if not os.path.isfile(src_path):
src_path = os.path.join(editor_src_dir, filename)
copy(src_path, target_path)
for scons_target in target:
copy_target(str(scons_target))
def build(env_mono):
assert env_mono["tools"]
target_filenames = [
"GodotSharp.dll",
"GodotSharp.pdb",
"GodotSharp.xml",
"GodotSharpEditor.dll",
"GodotSharpEditor.pdb",
"GodotSharpEditor.xml",
]
depend_cmd = []
for build_config in ["Debug", "Release"]:
output_dir = Dir("#bin").abspath
editor_api_dir = os.path.join(output_dir, "GodotSharp", "Api", build_config)
targets = [os.path.join(editor_api_dir, filename) for filename in target_filenames]
cmd = env_mono.CommandNoCache(
targets, depend_cmd, build_api_solution, module_dir=os.getcwd(), solution_build_config=build_config
)
env_mono.AlwaysBuild(cmd)
# Make the Release build of the API solution depend on the Debug build.
# We do this in order to prevent SCons from building them in parallel,
# which can freak out MSBuild. In many cases, one of the builds would
# hang indefinitely requiring a key to be pressed for it to continue.
depend_cmd = cmd
return depend_cmd

View File

@ -0,0 +1,329 @@
#!/usr/bin/python3
import os
import os.path
import shlex
import subprocess
from dataclasses import dataclass
def find_dotnet_cli():
if os.name == "nt":
for hint_dir in os.environ["PATH"].split(os.pathsep):
hint_dir = hint_dir.strip('"')
hint_path = os.path.join(hint_dir, "dotnet")
if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
return hint_path
if os.path.isfile(hint_path + ".exe") and os.access(hint_path + ".exe", os.X_OK):
return hint_path + ".exe"
else:
for hint_dir in os.environ["PATH"].split(os.pathsep):
hint_dir = hint_dir.strip('"')
hint_path = os.path.join(hint_dir, "dotnet")
if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
return hint_path
def find_msbuild_standalone_windows():
msbuild_tools_path = find_msbuild_tools_path_reg()
if msbuild_tools_path:
return os.path.join(msbuild_tools_path, "MSBuild.exe")
return None
def find_msbuild_mono_windows(mono_prefix):
assert mono_prefix is not None
mono_bin_dir = os.path.join(mono_prefix, "bin")
msbuild_mono = os.path.join(mono_bin_dir, "msbuild.bat")
if os.path.isfile(msbuild_mono):
return msbuild_mono
return None
def find_msbuild_mono_unix():
import sys
hint_dirs = []
if sys.platform == "darwin":
hint_dirs[:0] = [
"/Library/Frameworks/Mono.framework/Versions/Current/bin",
"/usr/local/var/homebrew/linked/mono/bin",
]
for hint_dir in hint_dirs:
hint_path = os.path.join(hint_dir, "msbuild")
if os.path.isfile(hint_path):
return hint_path
elif os.path.isfile(hint_path + ".exe"):
return hint_path + ".exe"
for hint_dir in os.environ["PATH"].split(os.pathsep):
hint_dir = hint_dir.strip('"')
hint_path = os.path.join(hint_dir, "msbuild")
if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
return hint_path
if os.path.isfile(hint_path + ".exe") and os.access(hint_path + ".exe", os.X_OK):
return hint_path + ".exe"
return None
def find_msbuild_tools_path_reg():
import subprocess
program_files = os.getenv("PROGRAMFILES(X86)")
if not program_files:
program_files = os.getenv("PROGRAMFILES")
vswhere = os.path.join(program_files, "Microsoft Visual Studio", "Installer", "vswhere.exe")
vswhere_args = ["-latest", "-products", "*", "-requires", "Microsoft.Component.MSBuild"]
try:
lines = subprocess.check_output([vswhere] + vswhere_args).splitlines()
for line in lines:
parts = line.decode("utf-8").split(":", 1)
if len(parts) < 2 or parts[0] != "installationPath":
continue
val = parts[1].strip()
if not val:
raise ValueError("Value of `installationPath` entry is empty")
# Since VS2019, the directory is simply named "Current"
msbuild_dir = os.path.join(val, "MSBuild", "Current", "Bin")
if os.path.isdir(msbuild_dir):
return msbuild_dir
# Directory name "15.0" is used in VS 2017
return os.path.join(val, "MSBuild", "15.0", "Bin")
raise ValueError("Cannot find `installationPath` entry")
except ValueError as e:
print("Error reading output from vswhere: " + str(e))
except OSError:
pass # Fine, vswhere not found
except (subprocess.CalledProcessError, OSError):
pass
@dataclass
class ToolsLocation:
dotnet_cli: str = ""
msbuild_standalone: str = ""
msbuild_mono: str = ""
mono_bin_dir: str = ""
def find_any_msbuild_tool(mono_prefix):
# Preference order: dotnet CLI > Standalone MSBuild > Mono's MSBuild
# Find dotnet CLI
dotnet_cli = find_dotnet_cli()
if dotnet_cli:
return ToolsLocation(dotnet_cli=dotnet_cli)
# Find standalone MSBuild
if os.name == "nt":
msbuild_standalone = find_msbuild_standalone_windows()
if msbuild_standalone:
return ToolsLocation(msbuild_standalone=msbuild_standalone)
if mono_prefix:
# Find Mono's MSBuild
if os.name == "nt":
msbuild_mono = find_msbuild_mono_windows(mono_prefix)
if msbuild_mono:
return ToolsLocation(msbuild_mono=msbuild_mono)
else:
msbuild_mono = find_msbuild_mono_unix()
if msbuild_mono:
return ToolsLocation(msbuild_mono=msbuild_mono)
return None
def run_msbuild(tools: ToolsLocation, sln: str, msbuild_args: [str] = None):
if msbuild_args is None:
msbuild_args = []
using_msbuild_mono = False
# Preference order: dotnet CLI > Standalone MSBuild > Mono's MSBuild
if tools.dotnet_cli:
args = [tools.dotnet_cli, "msbuild"]
elif tools.msbuild_standalone:
args = [tools.msbuild_standalone]
elif tools.msbuild_mono:
args = [tools.msbuild_mono]
using_msbuild_mono = True
else:
raise RuntimeError("Path to MSBuild or dotnet CLI not provided.")
args += [sln]
if len(msbuild_args) > 0:
args += msbuild_args
print("Running MSBuild: ", " ".join(shlex.quote(arg) for arg in args), flush=True)
msbuild_env = os.environ.copy()
# Needed when running from Developer Command Prompt for VS
if "PLATFORM" in msbuild_env:
del msbuild_env["PLATFORM"]
if using_msbuild_mono:
# The (Csc/Vbc/Fsc)ToolExe environment variables are required when
# building with Mono's MSBuild. They must point to the batch files
# in Mono's bin directory to make sure they are executed with Mono.
msbuild_env.update(
{
"CscToolExe": os.path.join(tools.mono_bin_dir, "csc.bat"),
"VbcToolExe": os.path.join(tools.mono_bin_dir, "vbc.bat"),
"FscToolExe": os.path.join(tools.mono_bin_dir, "fsharpc.bat"),
}
)
return subprocess.call(args, env=msbuild_env)
def build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local):
target_filenames = [
"GodotSharp.dll",
"GodotSharp.pdb",
"GodotSharp.xml",
"GodotSharpEditor.dll",
"GodotSharpEditor.pdb",
"GodotSharpEditor.xml",
"GodotPlugins.dll",
"GodotPlugins.pdb",
"GodotPlugins.runtimeconfig.json",
]
for build_config in ["Debug", "Release"]:
editor_api_dir = os.path.join(output_dir, "GodotSharp", "Api", build_config)
targets = [os.path.join(editor_api_dir, filename) for filename in target_filenames]
args = ["/restore", "/t:Build", "/p:Configuration=" + build_config, "/p:NoWarn=1591"]
if push_nupkgs_local:
args += ["/p:ClearNuGetLocalCache=true", "/p:PushNuGetToLocalSource=" + push_nupkgs_local]
sln = os.path.join(module_dir, "glue/GodotSharp/GodotSharp.sln")
exit_code = run_msbuild(
msbuild_tool,
sln=sln,
msbuild_args=args,
)
if exit_code != 0:
return exit_code
# Copy targets
core_src_dir = os.path.abspath(os.path.join(sln, os.pardir, "GodotSharp", "bin", build_config))
editor_src_dir = os.path.abspath(os.path.join(sln, os.pardir, "GodotSharpEditor", "bin", build_config))
plugins_src_dir = os.path.abspath(os.path.join(sln, os.pardir, "GodotPlugins", "bin", build_config, "net6.0"))
if not os.path.isdir(editor_api_dir):
assert not os.path.isfile(editor_api_dir)
os.makedirs(editor_api_dir)
def copy_target(target_path):
from shutil import copy
filename = os.path.basename(target_path)
src_path = os.path.join(core_src_dir, filename)
if not os.path.isfile(src_path):
src_path = os.path.join(editor_src_dir, filename)
if not os.path.isfile(src_path):
src_path = os.path.join(plugins_src_dir, filename)
print(f"Copying assembly to {target_path}...")
copy(src_path, target_path)
for scons_target in targets:
copy_target(scons_target)
return 0
def build_all(msbuild_tool, module_dir, output_dir, godot_platform, dev_debug, push_nupkgs_local):
# Godot API
exit_code = build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local)
if exit_code != 0:
return exit_code
# GodotTools
sln = os.path.join(module_dir, "editor/GodotTools/GodotTools.sln")
args = ["/restore", "/t:Build", "/p:Configuration=" + ("Debug" if dev_debug else "Release")] + (
["/p:GodotPlatform=" + godot_platform] if godot_platform else []
)
if push_nupkgs_local:
args += ["/p:ClearNuGetLocalCache=true", "/p:PushNuGetToLocalSource=" + push_nupkgs_local]
exit_code = run_msbuild(msbuild_tool, sln=sln, msbuild_args=args)
if exit_code != 0:
return exit_code
# Godot.NET.Sdk
args = ["/restore", "/t:Build", "/p:Configuration=Release"]
if push_nupkgs_local:
args += ["/p:ClearNuGetLocalCache=true", "/p:PushNuGetToLocalSource=" + push_nupkgs_local]
sln = os.path.join(module_dir, "editor/Godot.NET.Sdk/Godot.NET.Sdk.sln")
exit_code = run_msbuild(msbuild_tool, sln=sln, msbuild_args=args)
if exit_code != 0:
return exit_code
return 0
def main():
import argparse
import sys
parser = argparse.ArgumentParser(description="Builds all Godot .NET solutions")
parser.add_argument("--godot-output-dir", type=str, required=True)
parser.add_argument(
"--dev-debug",
action="store_true",
default=False,
help="Build GodotTools and Godot.NET.Sdk with 'Configuration=Debug'",
)
parser.add_argument("--godot-platform", type=str, default="")
parser.add_argument("--mono-prefix", type=str, default="")
parser.add_argument("--push-nupkgs-local", type=str, default="")
args = parser.parse_args()
this_script_dir = os.path.dirname(os.path.realpath(__file__))
module_dir = os.path.abspath(os.path.join(this_script_dir, os.pardir))
output_dir = os.path.abspath(args.godot_output_dir)
msbuild_tool = find_any_msbuild_tool(args.mono_prefix)
if msbuild_tool is None:
print("Unable to find MSBuild")
sys.exit(1)
exit_code = build_all(
msbuild_tool,
module_dir,
output_dir,
args.godot_platform,
args.dev_debug,
args.push_nupkgs_local,
)
sys.exit(exit_code)
if __name__ == "__main__":
main()

View File

@ -1,20 +0,0 @@
def generate_header(solution_dir, version_header_dst):
import os
latest_mtime = 0
for root, dirs, files in os.walk(solution_dir, topdown=True):
dirs[:] = [d for d in dirs if d not in ["Generated"]] # Ignored generated files
files = [f for f in files if f.endswith(".cs")]
for file in files:
filepath = os.path.join(root, file)
mtime = os.path.getmtime(filepath)
latest_mtime = mtime if mtime > latest_mtime else latest_mtime
glue_version = int(latest_mtime) # The latest modified time will do for now
with open(version_header_dst, "w") as version_header:
version_header.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n")
version_header.write("#ifndef CS_GLUE_VERSION_H\n")
version_header.write("#define CS_GLUE_VERSION_H\n\n")
version_header.write("#define CS_GLUE_VERSION UINT32_C(" + str(glue_version) + ")\n")
version_header.write("\n#endif // CS_GLUE_VERSION_H\n")

View File

@ -1,55 +0,0 @@
# Build Godot.NET.Sdk solution
import os
from SCons.Script import Dir
def build_godot_net_sdk(source, target, env):
# source and target elements are of type SCons.Node.FS.File, hence why we convert them to str
module_dir = env["module_dir"]
solution_path = os.path.join(module_dir, "editor/Godot.NET.Sdk/Godot.NET.Sdk.sln")
build_config = "Release"
from .solution_builder import build_solution
extra_msbuild_args = ["/p:GodotPlatform=" + env["platform"]]
build_solution(env, solution_path, build_config, extra_msbuild_args)
# No need to copy targets. The Godot.NET.Sdk csproj takes care of copying them.
def get_nupkgs_versions(props_file):
import xml.etree.ElementTree as ET
tree = ET.parse(props_file)
root = tree.getroot()
return {
"Godot.NET.Sdk": root.find("./PropertyGroup/PackageVersion_Godot_NET_Sdk").text.strip(),
"Godot.SourceGenerators": root.find("./PropertyGroup/PackageVersion_Godot_SourceGenerators").text.strip(),
}
def build(env_mono):
assert env_mono["tools"]
output_dir = Dir("#bin").abspath
editor_tools_dir = os.path.join(output_dir, "GodotSharp", "Tools")
nupkgs_dir = os.path.join(editor_tools_dir, "nupkgs")
module_dir = os.getcwd()
nupkgs_versions = get_nupkgs_versions(os.path.join(module_dir, "SdkPackageVersions.props"))
target_filenames = [
"Godot.NET.Sdk.%s.nupkg" % nupkgs_versions["Godot.NET.Sdk"],
"Godot.SourceGenerators.%s.nupkg" % nupkgs_versions["Godot.SourceGenerators"],
]
targets = [os.path.join(nupkgs_dir, filename) for filename in target_filenames]
cmd = env_mono.CommandNoCache(targets, [], build_godot_net_sdk, module_dir=module_dir)
env_mono.AlwaysBuild(cmd)

View File

@ -1,38 +0,0 @@
# Build GodotTools solution
import os
from SCons.Script import Dir
def build_godot_tools(source, target, env):
# source and target elements are of type SCons.Node.FS.File, hence why we convert them to str
module_dir = env["module_dir"]
solution_path = os.path.join(module_dir, "editor/GodotTools/GodotTools.sln")
build_config = "Debug" if env["target"] == "debug" else "Release"
from .solution_builder import build_solution
extra_msbuild_args = ["/p:GodotPlatform=" + env["platform"]]
build_solution(env, solution_path, build_config, extra_msbuild_args)
# No need to copy targets. The GodotTools csproj takes care of copying them.
def build(env_mono, api_sln_cmd):
assert env_mono["tools"]
output_dir = Dir("#bin").abspath
editor_tools_dir = os.path.join(output_dir, "GodotSharp", "Tools")
target_filenames = ["GodotTools.dll"]
if env_mono["target"] == "debug":
target_filenames += ["GodotTools.pdb"]
targets = [os.path.join(editor_tools_dir, filename) for filename in target_filenames]
cmd = env_mono.CommandNoCache(targets, api_sln_cmd, build_godot_tools, module_dir=os.getcwd())
env_mono.AlwaysBuild(cmd)

View File

@ -1,55 +0,0 @@
def generate_compressed_config(config_src, output_dir):
import os.path
# Source file
with open(os.path.join(output_dir, "android_mono_config.gen.cpp"), "w") as cpp:
with open(config_src, "rb") as f:
buf = f.read()
decompr_size = len(buf)
import zlib
# Use maximum zlib compression level to further reduce file size
# (at the cost of initial build times).
buf = zlib.compress(buf, zlib.Z_BEST_COMPRESSION)
compr_size = len(buf)
bytes_seq_str = ""
for i, buf_idx in enumerate(range(compr_size)):
if i > 0:
bytes_seq_str += ", "
bytes_seq_str += str(buf[buf_idx])
cpp.write(
"""/* THIS FILE IS GENERATED DO NOT EDIT */
#include "android_mono_config.h"
#ifdef ANDROID_ENABLED
#include "core/io/compression.h"
namespace {
// config
static const int config_compressed_size = %d;
static const int config_uncompressed_size = %d;
static const unsigned char config_compressed_data[] = { %s };
} // namespace
String get_godot_android_mono_config() {
Vector<uint8_t> data;
data.resize(config_uncompressed_size);
uint8_t* w = data.ptrw();
Compression::decompress(w, config_uncompressed_size, config_compressed_data,
config_compressed_size, Compression::MODE_DEFLATE);
String s;
if (s.parse_utf8((const char *)w, data.size()) != OK) {
ERR_FAIL_V(String());
}
return s;
}
#endif // ANDROID_ENABLED
"""
% (compr_size, decompr_size, bytes_seq_str)
)

View File

@ -1,28 +0,0 @@
<configuration>
<dllmap wordsize="32" dll="i:cygwin1.dll" target="/system/lib/libc.so" />
<dllmap wordsize="64" dll="i:cygwin1.dll" target="/system/lib64/libc.so" />
<dllmap wordsize="32" dll="libc" target="/system/lib/libc.so" />
<dllmap wordsize="64" dll="libc" target="/system/lib64/libc.so" />
<dllmap wordsize="32" dll="intl" target="/system/lib/libc.so" />
<dllmap wordsize="64" dll="intl" target="/system/lib64/libc.so" />
<dllmap wordsize="32" dll="libintl" target="/system/lib/libc.so" />
<dllmap wordsize="64" dll="libintl" target="/system/lib64/libc.so" />
<dllmap dll="MonoPosixHelper" target="libMonoPosixHelper.so" />
<dllmap dll="System.Native" target="libmono-native.so" />
<dllmap wordsize="32" dll="i:msvcrt" target="/system/lib/libc.so" />
<dllmap wordsize="64" dll="i:msvcrt" target="/system/lib64/libc.so" />
<dllmap wordsize="32" dll="i:msvcrt.dll" target="/system/lib/libc.so" />
<dllmap wordsize="64" dll="i:msvcrt.dll" target="/system/lib64/libc.so" />
<dllmap wordsize="32" dll="sqlite" target="/system/lib/libsqlite.so" />
<dllmap wordsize="64" dll="sqlite" target="/system/lib64/libsqlite.so" />
<dllmap wordsize="32" dll="sqlite3" target="/system/lib/libsqlite.so" />
<dllmap wordsize="64" dll="sqlite3" target="/system/lib64/libsqlite.so" />
<dllmap wordsize="32" dll="liblog" target="/system/lib/liblog.so" />
<dllmap wordsize="64" dll="liblog" target="/system/lib64/liblog.so" />
<dllmap dll="i:kernel32.dll">
<dllentry dll="__Internal" name="CopyMemory" target="mono_win32_compat_CopyMemory"/>
<dllentry dll="__Internal" name="FillMemory" target="mono_win32_compat_FillMemory"/>
<dllentry dll="__Internal" name="MoveMemory" target="mono_win32_compat_MoveMemory"/>
<dllentry dll="__Internal" name="ZeroMemory" target="mono_win32_compat_ZeroMemory"/>
</dllmap>
</configuration>

View File

@ -1,65 +1,5 @@
import os
import os.path
import subprocess
from SCons.Script import Dir, Environment
if os.name == "nt":
from . import mono_reg_utils as monoreg
android_arch_dirs = {
"armv7": "armeabi-v7a",
"arm64v8": "arm64-v8a",
"x86": "x86",
"x86_64": "x86_64",
}
def get_android_out_dir(env):
return os.path.join(
Dir("#platform/android/java/lib/libs").abspath,
"release" if env["target"] == "release" else "debug",
android_arch_dirs[env["android_arch"]],
)
def find_name_in_dir_files(directory, names, prefixes=[""], extensions=[""]):
for extension in extensions:
if extension and not extension.startswith("."):
extension = "." + extension
for prefix in prefixes:
for curname in names:
if os.path.isfile(os.path.join(directory, prefix + curname + extension)):
return curname
return ""
def find_file_in_dir(directory, names, prefixes=[""], extensions=[""]):
for extension in extensions:
if extension and not extension.startswith("."):
extension = "." + extension
for prefix in prefixes:
for curname in names:
filename = prefix + curname + extension
if os.path.isfile(os.path.join(directory, filename)):
return filename
return ""
def copy_file(src_dir, dst_dir, src_name, dst_name=""):
from shutil import copy
src_path = os.path.join(Dir(src_dir).abspath, src_name)
dst_dir = Dir(dst_dir).abspath
if not os.path.isdir(dst_dir):
os.makedirs(dst_dir)
if dst_name:
copy(src_path, os.path.join(dst_dir, dst_name))
else:
copy(src_path, dst_dir)
def is_desktop(platform):
@ -71,504 +11,261 @@ def is_unix_like(platform):
def module_supports_tools_on(platform):
return platform not in ["android", "javascript", "ios"]
def find_wasm_src_dir(mono_root):
hint_dirs = [
os.path.join(mono_root, "src"),
os.path.join(mono_root, "../src"),
]
for hint_dir in hint_dirs:
if os.path.isfile(os.path.join(hint_dir, "driver.c")):
return hint_dir
return ""
return is_desktop(platform)
def configure(env, env_mono):
bits = env["bits"]
is_android = env["platform"] == "android"
is_javascript = env["platform"] == "javascript"
is_ios = env["platform"] == "ios"
is_ios_sim = is_ios and env["arch"] in ["x86", "x86_64"]
# is_android = env["platform"] == "android"
# is_javascript = env["platform"] == "javascript"
# is_ios = env["platform"] == "ios"
# is_ios_sim = is_ios and env["arch"] in ["x86", "x86_64"]
tools_enabled = env["tools"]
mono_static = env["mono_static"]
copy_mono_root = env["copy_mono_root"]
mono_prefix = env["mono_prefix"]
mono_bcl = env["mono_bcl"]
mono_lib_names = ["mono-2.0-sgen", "monosgen-2.0"]
if is_android and not env["android_arch"] in android_arch_dirs:
raise RuntimeError("This module does not support the specified 'android_arch': " + env["android_arch"])
if tools_enabled and not module_supports_tools_on(env["platform"]):
# TODO:
# Android: We have to add the data directory to the apk, concretely the Api and Tools folders.
raise RuntimeError("This module does not currently support building for this platform with tools enabled")
if is_android and mono_static:
# FIXME: When static linking and doing something that requires libmono-native, we get a dlopen error as 'libmono-native'
# seems to depend on 'libmonosgen-2.0'. Could be fixed by re-directing to '__Internal' with a dllmap or in the dlopen hook.
raise RuntimeError("Statically linking Mono is not currently supported for this platform")
if not mono_static and (is_javascript or is_ios):
raise RuntimeError("Dynamically linking Mono is not currently supported for this platform")
if not mono_prefix and (os.getenv("MONO32_PREFIX") or os.getenv("MONO64_PREFIX")):
print(
"WARNING: The environment variables 'MONO32_PREFIX' and 'MONO64_PREFIX' are deprecated; use the"
" 'mono_prefix' SCons parameter instead"
)
# Although we don't support building with tools for any platform where we currently use static AOT,
# if these are supported in the future, we won't be using static AOT for them as that would be
# too restrictive for the editor. These builds would probably be made to only use the interpreter.
mono_aot_static = (is_ios and not is_ios_sim) and not env["tools"]
# Static AOT is only supported on the root domain
mono_single_appdomain = mono_aot_static
if mono_single_appdomain:
env_mono.Append(CPPDEFINES=["GD_MONO_SINGLE_APPDOMAIN"])
if (env["tools"] or env["target"] != "release") and not mono_single_appdomain:
if env["tools"]:
env_mono.Append(CPPDEFINES=["GD_MONO_HOT_RELOAD"])
if env["platform"] == "windows":
mono_root = mono_prefix
app_host_dir = find_dotnet_app_host_dir(env)
if not mono_root and os.name == "nt":
mono_root = monoreg.find_mono_root_dir(bits)
def check_app_host_file_exists(file):
file_path = os.path.join(app_host_dir, file)
if not os.path.isfile(file_path):
raise RuntimeError("File not found: " + file_path)
if not mono_root:
raise RuntimeError(
"Mono installation directory not found; specify one manually with the 'mono_prefix' SCons parameter"
)
# TODO:
# All libnethost does for us is provide a function to find hostfxr.
# If we could handle that logic ourselves we could void linking it.
print("Found Mono root directory: " + mono_root)
# nethost file names:
# static: libnethost.a/lib
# shared: libnethost.a/dylib and nethost.dll
check_app_host_file_exists("libnethost.lib" if os.name == "nt" else "libnethost.a")
check_app_host_file_exists("nethost.h")
check_app_host_file_exists("hostfxr.h")
check_app_host_file_exists("coreclr_delegates.h")
mono_lib_path = os.path.join(mono_root, "lib")
env_mono.Prepend(CPPPATH=app_host_dir)
env.Append(LIBPATH=mono_lib_path)
env_mono.Prepend(CPPPATH=os.path.join(mono_root, "include", "mono-2.0"))
env.Append(LIBPATH=[app_host_dir])
lib_suffixes = [".lib"]
# 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 not env.msvc:
# MingW supports both '.a' and '.lib'
lib_suffixes.insert(0, ".a")
if mono_static:
if env.msvc:
mono_static_lib_name = "libmono-static-sgen"
else:
mono_static_lib_name = "libmonosgen-2.0"
mono_static_lib_file = find_file_in_dir(mono_lib_path, [mono_static_lib_name], extensions=lib_suffixes)
if not mono_static_lib_file:
raise RuntimeError("Could not find static mono library in: " + mono_lib_path)
if env["platform"] == "windows":
env_mono.Append(CPPDEFINES=["NETHOST_USE_AS_STATIC"])
if env.msvc:
env.Append(LINKFLAGS=mono_static_lib_file)
env.Append(LINKFLAGS="Mincore.lib")
env.Append(LINKFLAGS="msvcrt.lib")
env.Append(LINKFLAGS="LIBCMT.lib")
env.Append(LINKFLAGS="Psapi.lib")
env.Append(LINKFLAGS="libnethost.lib")
else:
mono_static_lib_file_path = os.path.join(mono_lib_path, mono_static_lib_file)
env.Append(LINKFLAGS=["-Wl,-whole-archive", mono_static_lib_file_path, "-Wl,-no-whole-archive"])
env.Append(LIBS=["psapi"])
env.Append(LIBS=["version"])
env.Append(LINKFLAGS=["-Wl,-whole-archive", libnethost_path, "-Wl,-no-whole-archive"])
else:
mono_lib_file = find_file_in_dir(mono_lib_path, mono_lib_names, extensions=lib_suffixes)
is_apple = env["platform"] in ["macos", "ios"]
# is_macos = is_apple and not is_ios
if not mono_lib_file:
raise RuntimeError("Could not find mono library in: " + mono_lib_path)
# if is_ios and not is_ios_sim:
# env_mono.Append(CPPDEFINES=["IOS_DEVICE"])
if env.msvc:
env.Append(LINKFLAGS=mono_lib_file)
if is_apple:
env.Append(LINKFLAGS=["-Wl,-force_load," + libnethost_path])
else:
mono_lib_file_path = os.path.join(mono_lib_path, mono_lib_file)
env.Append(LINKFLAGS=mono_lib_file_path)
env.Append(LINKFLAGS=["-Wl,-whole-archive", libnethost_path, "-Wl,-no-whole-archive"])
mono_bin_path = os.path.join(mono_root, "bin")
mono_dll_file = find_file_in_dir(mono_bin_path, mono_lib_names, prefixes=["", "lib"], extensions=[".dll"])
def find_dotnet_app_host_dir(env):
dotnet_version = "6.0"
if not mono_dll_file:
raise RuntimeError("Could not find mono shared library in: " + mono_bin_path)
dotnet_root = env["dotnet_root"]
copy_file(mono_bin_path, "#bin", mono_dll_file)
else:
is_apple = env["platform"] in ["macos", "ios"]
is_macos = is_apple and not is_ios
if not dotnet_root:
dotnet_cmd = find_executable("dotnet")
if dotnet_cmd:
sdk_path = find_dotnet_sdk(dotnet_cmd, dotnet_version)
if sdk_path:
dotnet_root = os.path.abspath(os.path.join(sdk_path, os.pardir))
sharedlib_ext = ".dylib" if is_apple else ".so"
if not dotnet_root:
raise RuntimeError("Cannot find .NET Core Sdk")
mono_root = mono_prefix
mono_lib_path = ""
mono_so_file = ""
print("Found .NET Core Sdk root directory: " + dotnet_root)
if not mono_root and (is_android or is_javascript or is_ios):
raise RuntimeError(
"Mono installation directory not found; specify one manually with the 'mono_prefix' SCons parameter"
)
dotnet_cmd = os.path.join(dotnet_root, "dotnet.exe" if os.name == "nt" else "dotnet")
if not mono_root and is_macos:
# Try with some known directories under macOS
hint_dirs = ["/Library/Frameworks/Mono.framework/Versions/Current", "/usr/local/var/homebrew/linked/mono"]
for hint_dir in hint_dirs:
if os.path.isdir(hint_dir):
mono_root = hint_dir
break
runtime_identifier = determine_runtime_identifier(env)
# We can't use pkg-config to link mono statically,
# but we can still use it to find the mono root directory
if not mono_root and mono_static:
mono_root = pkgconfig_try_find_mono_root(mono_lib_names, sharedlib_ext)
if not mono_root:
raise RuntimeError(
"Building with mono_static=yes, but failed to find the mono prefix with pkg-config; "
+ "specify one manually with the 'mono_prefix' SCons parameter"
)
# TODO: In the future, if it can't be found this way, we want to obtain it
# from the runtime.{runtime_identifier}.Microsoft.NETCore.DotNetAppHost NuGet package.
app_host_version = find_app_host_version(dotnet_cmd, dotnet_version)
if not app_host_version:
raise RuntimeError("Cannot find .NET app host for version: " + dotnet_version)
if is_ios and not is_ios_sim:
env_mono.Append(CPPDEFINES=["IOS_DEVICE"])
def get_runtime_path():
return os.path.join(
dotnet_root,
"packs",
"Microsoft.NETCore.App.Host." + runtime_identifier,
app_host_version,
"runtimes",
runtime_identifier,
"native",
)
if mono_root:
print("Found Mono root directory: " + mono_root)
app_host_dir = get_runtime_path()
mono_lib_path = os.path.join(mono_root, "lib")
# Some Linux distros use their distro name as the RID in these paths.
# If the initial generic path doesn't exist, try to get the RID from `dotnet --info`.
# The generic RID should still be the first choice. Some platforms like Windows 10
# define the RID as `win10-x64` but still use the generic `win-x64` for directory names.
if not app_host_dir or not os.path.isdir(app_host_dir):
runtime_identifier = find_dotnet_cli_rid(dotnet_cmd)
app_host_dir = get_runtime_path()
env.Append(LIBPATH=[mono_lib_path])
env_mono.Prepend(CPPPATH=os.path.join(mono_root, "include", "mono-2.0"))
return app_host_dir
mono_lib = find_name_in_dir_files(mono_lib_path, mono_lib_names, prefixes=["lib"], extensions=[".a"])
if not mono_lib:
raise RuntimeError("Could not find mono library in: " + mono_lib_path)
def determine_runtime_identifier(env):
names_map = {
"windows": "win",
"macos": "osx",
"linuxbsd": "linux",
"server": "linux", # FIXME: Is server linux only, or also macos?
}
env_mono.Append(CPPDEFINES=["_REENTRANT"])
if mono_static:
if not is_javascript:
env.Append(LINKFLAGS=["-rdynamic"])
mono_lib_file = os.path.join(mono_lib_path, "lib" + mono_lib + ".a")
if is_apple:
if is_macos:
env.Append(LINKFLAGS=["-Wl,-force_load," + mono_lib_file])
else:
arch = env["arch"]
def copy_mono_lib(libname_wo_ext):
copy_file(
mono_lib_path, "#bin", libname_wo_ext + ".a", "%s.ios.%s.a" % (libname_wo_ext, arch)
)
# Copy Mono libraries to the output folder. These are meant to be bundled with
# the export templates and added to the Xcode project when exporting a game.
copy_mono_lib("lib" + mono_lib)
copy_mono_lib("libmono-native")
copy_mono_lib("libmono-profiler-log")
if not is_ios_sim:
copy_mono_lib("libmono-ee-interp")
copy_mono_lib("libmono-icall-table")
copy_mono_lib("libmono-ilgen")
else:
assert is_desktop(env["platform"]) or is_android or is_javascript
env.Append(LINKFLAGS=["-Wl,-whole-archive", mono_lib_file, "-Wl,-no-whole-archive"])
if is_javascript:
env.Append(LIBS=["mono-icall-table", "mono-native", "mono-ilgen", "mono-ee-interp"])
wasm_src_dir = os.path.join(mono_root, "src")
if not os.path.isdir(wasm_src_dir):
raise RuntimeError("Could not find mono wasm src directory")
# Ideally this should be defined only for 'driver.c', but I can't fight scons for another 2 hours
env_mono.Append(CPPDEFINES=["CORE_BINDINGS"])
env_mono.add_source_files(
env.modules_sources,
[
os.path.join(wasm_src_dir, "driver.c"),
os.path.join(wasm_src_dir, "zlib-helper.c"),
os.path.join(wasm_src_dir, "corebindings.c"),
],
)
env.Append(
LINKFLAGS=[
"--js-library",
os.path.join(wasm_src_dir, "library_mono.js"),
"--js-library",
os.path.join(wasm_src_dir, "binding_support.js"),
"--js-library",
os.path.join(wasm_src_dir, "dotnet_support.js"),
]
)
else:
env.Append(LIBS=[mono_lib])
if is_macos:
env.Append(LIBS=["iconv", "pthread"])
elif is_android:
pass # Nothing
elif is_ios:
pass # Nothing, linking is delegated to the exported Xcode project
elif is_javascript:
env.Append(LIBS=["m", "rt", "dl", "pthread"])
else:
env.Append(LIBS=["m", "rt", "dl", "pthread"])
if not mono_static:
mono_so_file = find_file_in_dir(
mono_lib_path, mono_lib_names, prefixes=["lib"], extensions=[sharedlib_ext]
)
if not mono_so_file:
raise RuntimeError("Could not find mono shared library in: " + mono_lib_path)
else:
assert not mono_static
# TODO: Add option to force using pkg-config
print("Mono root directory not found. Using pkg-config instead")
env.ParseConfig("pkg-config monosgen-2 --libs")
env_mono.ParseConfig("pkg-config monosgen-2 --cflags")
tmpenv = Environment()
tmpenv.AppendENVPath("PKG_CONFIG_PATH", os.getenv("PKG_CONFIG_PATH"))
tmpenv.ParseConfig("pkg-config monosgen-2 --libs-only-L")
for hint_dir in tmpenv["LIBPATH"]:
file_found = find_file_in_dir(hint_dir, mono_lib_names, prefixes=["lib"], extensions=[sharedlib_ext])
if file_found:
mono_lib_path = hint_dir
mono_so_file = file_found
break
if not mono_so_file:
raise RuntimeError("Could not find mono shared library in: " + str(tmpenv["LIBPATH"]))
if not mono_static:
libs_output_dir = get_android_out_dir(env) if is_android else "#bin"
copy_file(mono_lib_path, libs_output_dir, mono_so_file)
if not tools_enabled:
if is_desktop(env["platform"]):
if not mono_root:
mono_root = (
subprocess.check_output(["pkg-config", "mono-2", "--variable=prefix"]).decode("utf8").strip()
)
make_template_dir(env, mono_root)
elif is_android:
# Compress Android Mono Config
from . import make_android_mono_config
module_dir = os.getcwd()
config_file_path = os.path.join(module_dir, "build_scripts", "mono_android_config.xml")
make_android_mono_config.generate_compressed_config(config_file_path, "mono_gd/")
# Copy the required shared libraries
copy_mono_shared_libs(env, mono_root, None)
elif is_javascript:
pass # No data directory for this platform
elif is_ios:
pass # No data directory for this platform
if copy_mono_root:
if not mono_root:
mono_root = subprocess.check_output(["pkg-config", "mono-2", "--variable=prefix"]).decode("utf8").strip()
if tools_enabled:
# Only supported for editor builds.
copy_mono_root_files(env, mono_root, mono_bcl)
def make_template_dir(env, mono_root):
from shutil import rmtree
platform = env["platform"]
target = env["target"]
template_dir_name = ""
assert is_desktop(platform)
template_dir_name = "data.mono.%s.%s.%s" % (platform, env["bits"], target)
output_dir = Dir("#bin").abspath
template_dir = os.path.join(output_dir, template_dir_name)
template_mono_root_dir = os.path.join(template_dir, "Mono")
if os.path.isdir(template_mono_root_dir):
rmtree(template_mono_root_dir) # Clean first
# Copy etc/mono/
template_mono_config_dir = os.path.join(template_mono_root_dir, "etc", "mono")
copy_mono_etc_dir(mono_root, template_mono_config_dir, platform)
# Copy the required shared libraries
copy_mono_shared_libs(env, mono_root, template_mono_root_dir)
def copy_mono_root_files(env, mono_root, mono_bcl):
from glob import glob
from shutil import copy
from shutil import rmtree
if not mono_root:
raise RuntimeError("Mono installation directory not found")
output_dir = Dir("#bin").abspath
editor_mono_root_dir = os.path.join(output_dir, "GodotSharp", "Mono")
if os.path.isdir(editor_mono_root_dir):
rmtree(editor_mono_root_dir) # Clean first
# Copy etc/mono/
editor_mono_config_dir = os.path.join(editor_mono_root_dir, "etc", "mono")
copy_mono_etc_dir(mono_root, editor_mono_config_dir, env["platform"])
# Copy the required shared libraries
copy_mono_shared_libs(env, mono_root, editor_mono_root_dir)
# Copy framework assemblies
mono_framework_dir = mono_bcl or os.path.join(mono_root, "lib", "mono", "4.5")
mono_framework_facades_dir = os.path.join(mono_framework_dir, "Facades")
editor_mono_framework_dir = os.path.join(editor_mono_root_dir, "lib", "mono", "4.5")
editor_mono_framework_facades_dir = os.path.join(editor_mono_framework_dir, "Facades")
if not os.path.isdir(editor_mono_framework_dir):
os.makedirs(editor_mono_framework_dir)
if not os.path.isdir(editor_mono_framework_facades_dir):
os.makedirs(editor_mono_framework_facades_dir)
for assembly in glob(os.path.join(mono_framework_dir, "*.dll")):
copy(assembly, editor_mono_framework_dir)
for assembly in glob(os.path.join(mono_framework_facades_dir, "*.dll")):
copy(assembly, editor_mono_framework_facades_dir)
def copy_mono_etc_dir(mono_root, target_mono_config_dir, platform):
from distutils.dir_util import copy_tree
from glob import glob
from shutil import copy
if not os.path.isdir(target_mono_config_dir):
os.makedirs(target_mono_config_dir)
mono_etc_dir = os.path.join(mono_root, "etc", "mono")
if not os.path.isdir(mono_etc_dir):
mono_etc_dir = ""
etc_hint_dirs = []
if platform != "windows":
etc_hint_dirs += ["/etc/mono", "/usr/local/etc/mono"]
if "MONO_CFG_DIR" in os.environ:
etc_hint_dirs += [os.path.join(os.environ["MONO_CFG_DIR"], "mono")]
for etc_hint_dir in etc_hint_dirs:
if os.path.isdir(etc_hint_dir):
mono_etc_dir = etc_hint_dir
break
if not mono_etc_dir:
raise RuntimeError("Mono installation etc directory not found")
copy_tree(os.path.join(mono_etc_dir, "2.0"), os.path.join(target_mono_config_dir, "2.0"))
copy_tree(os.path.join(mono_etc_dir, "4.0"), os.path.join(target_mono_config_dir, "4.0"))
copy_tree(os.path.join(mono_etc_dir, "4.5"), os.path.join(target_mono_config_dir, "4.5"))
if os.path.isdir(os.path.join(mono_etc_dir, "mconfig")):
copy_tree(os.path.join(mono_etc_dir, "mconfig"), os.path.join(target_mono_config_dir, "mconfig"))
for file in glob(os.path.join(mono_etc_dir, "*")):
if os.path.isfile(file):
copy(file, target_mono_config_dir)
def copy_mono_shared_libs(env, mono_root, target_mono_root_dir):
from shutil import copy
def copy_if_exists(src, dst):
if os.path.isfile(src):
copy(src, dst)
# .NET RID architectures: x86, x64, arm, or arm64
platform = env["platform"]
if platform == "windows":
src_mono_bin_dir = os.path.join(mono_root, "bin")
target_mono_bin_dir = os.path.join(target_mono_root_dir, "bin")
if not os.path.isdir(target_mono_bin_dir):
os.makedirs(target_mono_bin_dir)
mono_posix_helper_file = find_file_in_dir(
src_mono_bin_dir, ["MonoPosixHelper"], prefixes=["", "lib"], extensions=[".dll"]
)
copy(
os.path.join(src_mono_bin_dir, mono_posix_helper_file),
os.path.join(target_mono_bin_dir, "MonoPosixHelper.dll"),
)
# For newer versions
btls_dll_path = os.path.join(src_mono_bin_dir, "libmono-btls-shared.dll")
if os.path.isfile(btls_dll_path):
copy(btls_dll_path, target_mono_bin_dir)
if is_desktop(platform):
if env["arch"] in ["arm", "arm32"]:
rid = "arm"
elif env["arch"] == "arm64":
rid = "arm64"
else:
bits = env["bits"]
bit_arch_map = {"64": "x64", "32": "x86"}
rid = bit_arch_map[bits]
return "%s-%s" % (names_map[platform], rid)
else:
target_mono_lib_dir = (
get_android_out_dir(env) if platform == "android" else os.path.join(target_mono_root_dir, "lib")
)
if not os.path.isdir(target_mono_lib_dir):
os.makedirs(target_mono_lib_dir)
lib_file_names = []
if platform == "macos":
lib_file_names = [
lib_name + ".dylib"
for lib_name in ["libmono-btls-shared", "libmono-native-compat", "libMonoPosixHelper"]
]
elif is_unix_like(platform):
lib_file_names = [
lib_name + ".so"
for lib_name in [
"libmono-btls-shared",
"libmono-ee-interp",
"libmono-native",
"libMonoPosixHelper",
"libmono-profiler-aot",
"libmono-profiler-coverage",
"libmono-profiler-log",
"libMonoSupportW",
]
]
for lib_file_name in lib_file_names:
copy_if_exists(os.path.join(mono_root, "lib", lib_file_name), target_mono_lib_dir)
raise NotImplementedError()
def pkgconfig_try_find_mono_root(mono_lib_names, sharedlib_ext):
tmpenv = Environment()
tmpenv.AppendENVPath("PKG_CONFIG_PATH", os.getenv("PKG_CONFIG_PATH"))
tmpenv.ParseConfig("pkg-config monosgen-2 --libs-only-L")
for hint_dir in tmpenv["LIBPATH"]:
name_found = find_name_in_dir_files(hint_dir, mono_lib_names, prefixes=["lib"], extensions=[sharedlib_ext])
if name_found and os.path.isdir(os.path.join(hint_dir, "..", "include", "mono-2.0")):
return os.path.join(hint_dir, "..")
def find_app_host_version(dotnet_cmd, search_version_str):
import subprocess
from distutils.version import LooseVersion
search_version = LooseVersion(search_version_str)
try:
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")
if not line.startswith("Microsoft.NETCore.App "):
continue
parts = line.split(" ", 2)
if len(parts) < 3:
continue
version_str = parts[1]
version = LooseVersion(version_str)
if version >= search_version:
return version_str
except (subprocess.CalledProcessError, OSError) as e:
import sys
print(e, file=sys.stderr)
return ""
def find_dotnet_sdk(dotnet_cmd, search_version_str):
import subprocess
from distutils.version import LooseVersion
search_version = LooseVersion(search_version_str)
try:
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")
parts = line.split(" ", 1)
if len(parts) < 2:
continue
version_str = parts[0]
version = LooseVersion(version_str)
if version < search_version:
continue
path_part = parts[1]
return path_part[1 : path_part.find("]")]
except (subprocess.CalledProcessError, OSError) as e:
import sys
print(e, file=sys.stderr)
return ""
def find_dotnet_cli_rid(dotnet_cmd):
import subprocess
try:
env = dict(os.environ, DOTNET_CLI_UI_LANGUAGE="en-US")
lines = subprocess.check_output([dotnet_cmd, "--info"], env=env).splitlines()
for line_bytes in lines:
line = line_bytes.decode("utf-8")
if not line.startswith(" RID:"):
continue
parts = line.split()
if len(parts) < 2:
continue
return parts[1]
except (subprocess.CalledProcessError, OSError) as e:
import sys
print(e, file=sys.stderr)
return ""
ENV_PATH_SEP = ";" if os.name == "nt" else ":"
def find_executable(name):
is_windows = os.name == "nt"
windows_exts = os.environ["PATHEXT"].split(ENV_PATH_SEP) if is_windows else None
path_dirs = os.environ["PATH"].split(ENV_PATH_SEP)
search_dirs = path_dirs + [os.getcwd()] # cwd is last in the list
for dir in search_dirs:
path = os.path.join(dir, name)
if is_windows:
for extension in windows_exts:
path_with_ext = path + extension
if os.path.isfile(path_with_ext) and os.access(path_with_ext, os.X_OK):
return path_with_ext
else:
if os.path.isfile(path) and os.access(path, os.X_OK):
return path
return ""

View File

@ -1,112 +0,0 @@
import os
import platform
if os.name == "nt":
import winreg
def _reg_open_key(key, subkey):
try:
return winreg.OpenKey(key, subkey)
except OSError:
if platform.architecture()[0] == "32bit":
bitness_sam = winreg.KEY_WOW64_64KEY
else:
bitness_sam = winreg.KEY_WOW64_32KEY
return winreg.OpenKey(key, subkey, 0, winreg.KEY_READ | bitness_sam)
def _reg_open_key_bits(key, subkey, bits):
sam = winreg.KEY_READ
if platform.architecture()[0] == "32bit":
if bits == "64":
# Force 32bit process to search in 64bit registry
sam |= winreg.KEY_WOW64_64KEY
else:
if bits == "32":
# Force 64bit process to search in 32bit registry
sam |= winreg.KEY_WOW64_32KEY
return winreg.OpenKey(key, subkey, 0, sam)
def _find_mono_in_reg(subkey, bits):
try:
with _reg_open_key_bits(winreg.HKEY_LOCAL_MACHINE, subkey, bits) as hKey:
value = winreg.QueryValueEx(hKey, "SdkInstallRoot")[0]
return value
except OSError:
return None
def _find_mono_in_reg_old(subkey, bits):
try:
with _reg_open_key_bits(winreg.HKEY_LOCAL_MACHINE, subkey, bits) as hKey:
default_clr = winreg.QueryValueEx(hKey, "DefaultCLR")[0]
if default_clr:
return _find_mono_in_reg(subkey + "\\" + default_clr, bits)
return None
except OSError:
return None
def find_mono_root_dir(bits):
root_dir = _find_mono_in_reg(r"SOFTWARE\Mono", bits)
if root_dir is not None:
return str(root_dir)
root_dir = _find_mono_in_reg_old(r"SOFTWARE\Novell\Mono", bits)
if root_dir is not None:
return str(root_dir)
return ""
def find_msbuild_tools_path_reg():
import subprocess
vswhere = os.getenv("PROGRAMFILES(X86)")
if not vswhere:
vswhere = os.getenv("PROGRAMFILES")
vswhere += r"\Microsoft Visual Studio\Installer\vswhere.exe"
vswhere_args = ["-latest", "-products", "*", "-requires", "Microsoft.Component.MSBuild"]
try:
lines = subprocess.check_output([vswhere] + vswhere_args).splitlines()
for line in lines:
parts = line.decode("utf-8").split(":", 1)
if len(parts) < 2 or parts[0] != "installationPath":
continue
val = parts[1].strip()
if not val:
raise ValueError("Value of `installationPath` entry is empty")
# Since VS2019, the directory is simply named "Current"
msbuild_dir = os.path.join(val, "MSBuild\\Current\\Bin")
if os.path.isdir(msbuild_dir):
return msbuild_dir
# Directory name "15.0" is used in VS 2017
return os.path.join(val, "MSBuild\\15.0\\Bin")
raise ValueError("Cannot find `installationPath` entry")
except ValueError as e:
print("Error reading output from vswhere: " + e.message)
except subprocess.CalledProcessError as e:
print(e.output)
except OSError as e:
print(e)
# Try to find 14.0 in the Registry
try:
subkey = r"SOFTWARE\Microsoft\MSBuild\ToolsVersions\14.0"
with _reg_open_key(winreg.HKEY_LOCAL_MACHINE, subkey) as hKey:
value = winreg.QueryValueEx(hKey, "MSBuildToolsPath")[0]
return value
except OSError:
return ""

View File

@ -1,145 +0,0 @@
import os
verbose = False
def find_dotnet_cli():
import os.path
if os.name == "nt":
for hint_dir in os.environ["PATH"].split(os.pathsep):
hint_dir = hint_dir.strip('"')
hint_path = os.path.join(hint_dir, "dotnet")
if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
return hint_path
if os.path.isfile(hint_path + ".exe") and os.access(hint_path + ".exe", os.X_OK):
return hint_path + ".exe"
else:
for hint_dir in os.environ["PATH"].split(os.pathsep):
hint_dir = hint_dir.strip('"')
hint_path = os.path.join(hint_dir, "dotnet")
if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
return hint_path
def find_msbuild_unix():
import os.path
import sys
hint_dirs = []
if sys.platform == "darwin":
hint_dirs[:0] = [
"/Library/Frameworks/Mono.framework/Versions/Current/bin",
"/usr/local/var/homebrew/linked/mono/bin",
]
for hint_dir in hint_dirs:
hint_path = os.path.join(hint_dir, "msbuild")
if os.path.isfile(hint_path):
return hint_path
elif os.path.isfile(hint_path + ".exe"):
return hint_path + ".exe"
for hint_dir in os.environ["PATH"].split(os.pathsep):
hint_dir = hint_dir.strip('"')
hint_path = os.path.join(hint_dir, "msbuild")
if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
return hint_path
if os.path.isfile(hint_path + ".exe") and os.access(hint_path + ".exe", os.X_OK):
return hint_path + ".exe"
return None
def find_msbuild_windows(env):
from .mono_reg_utils import find_mono_root_dir, find_msbuild_tools_path_reg
mono_root = env["mono_prefix"] or find_mono_root_dir(env["bits"])
if not mono_root:
raise RuntimeError("Cannot find mono root directory")
mono_bin_dir = os.path.join(mono_root, "bin")
msbuild_mono = os.path.join(mono_bin_dir, "msbuild.bat")
msbuild_tools_path = find_msbuild_tools_path_reg()
if msbuild_tools_path:
return (os.path.join(msbuild_tools_path, "MSBuild.exe"), {})
if os.path.isfile(msbuild_mono):
# The (Csc/Vbc/Fsc)ToolExe environment variables are required when
# building with Mono's MSBuild. They must point to the batch files
# in Mono's bin directory to make sure they are executed with Mono.
mono_msbuild_env = {
"CscToolExe": os.path.join(mono_bin_dir, "csc.bat"),
"VbcToolExe": os.path.join(mono_bin_dir, "vbc.bat"),
"FscToolExe": os.path.join(mono_bin_dir, "fsharpc.bat"),
}
return (msbuild_mono, mono_msbuild_env)
return None
def run_command(command, args, env_override=None, name=None):
def cmd_args_to_str(cmd_args):
return " ".join([arg if not " " in arg else '"%s"' % arg for arg in cmd_args])
args = [command] + args
if name is None:
name = os.path.basename(command)
if verbose:
print("Running '%s': %s" % (name, cmd_args_to_str(args)))
import subprocess
try:
if env_override is None:
subprocess.check_call(args)
else:
subprocess.check_call(args, env=env_override)
except subprocess.CalledProcessError as e:
raise RuntimeError("'%s' exited with error code: %s" % (name, e.returncode))
def build_solution(env, solution_path, build_config, extra_msbuild_args=[]):
global verbose
verbose = env["verbose"]
msbuild_env = os.environ.copy()
# Needed when running from Developer Command Prompt for VS
if "PLATFORM" in msbuild_env:
del msbuild_env["PLATFORM"]
msbuild_args = []
dotnet_cli = find_dotnet_cli()
if dotnet_cli:
msbuild_path = dotnet_cli
msbuild_args += ["msbuild"] # `dotnet msbuild` command
else:
# Find MSBuild
if os.name == "nt":
msbuild_info = find_msbuild_windows(env)
if msbuild_info is None:
raise RuntimeError("Cannot find MSBuild executable")
msbuild_path = msbuild_info[0]
msbuild_env.update(msbuild_info[1])
else:
msbuild_path = find_msbuild_unix()
if msbuild_path is None:
raise RuntimeError("Cannot find MSBuild executable")
print("MSBuild path: " + msbuild_path)
# Build solution
msbuild_args += [solution_path, "/restore", "/t:Build", "/p:Configuration=" + build_config]
msbuild_args += extra_msbuild_args
run_command(msbuild_path, msbuild_args, env_override=msbuild_env, name="msbuild")

View File

@ -1,4 +1,6 @@
supported_platforms = ["windows", "macos", "linuxbsd", "server", "android", "haiku", "javascript", "ios"]
# Prior to .NET Core, we supported these: ["windows", "macos", "linuxbsd", "server", "android", "haiku", "javascript", "ios"]
# Eventually support for each them should be added back (except Haiku if not supported by .NET Core)
supported_platforms = ["windows", "macos", "linuxbsd", "server"]
def can_build(env, platform):
@ -13,26 +15,11 @@ def get_opts(platform):
return [
PathVariable(
"mono_prefix",
"Path to the Mono installation directory for the target platform and architecture",
"dotnet_root",
"Path to the .NET Sdk installation directory for the target platform and architecture",
"",
PathVariable.PathAccept,
),
PathVariable(
"mono_bcl",
"Path to a custom Mono BCL (Base Class Library) directory for the target platform",
"",
PathVariable.PathAccept,
),
BoolVariable("mono_static", "Statically link Mono", default_mono_static),
BoolVariable("mono_glue", "Build with the Mono glue sources", True),
BoolVariable("build_cil", "Build C# solutions", True),
BoolVariable(
"copy_mono_root", "Make a copy of the Mono installation directory to bundle with the editor", True
),
BoolVariable(
"mono_bundles_zlib", "Specify if the Mono runtime was built with bundled zlib", default_mono_bundles_zlib
),
]
@ -44,13 +31,6 @@ def configure(env):
env.add_module_version_string("mono")
if env["mono_bundles_zlib"]:
# Mono may come with zlib bundled for WASM or on newer version when built with MinGW.
print("This Mono runtime comes with zlib bundled. Disabling 'builtin_zlib'...")
env["builtin_zlib"] = False
thirdparty_zlib_dir = "#thirdparty/zlib/"
env.Prepend(CPPPATH=[thirdparty_zlib_dir])
def get_doc_classes():
return [

File diff suppressed because it is too large Load Diff

View File

@ -39,8 +39,6 @@
#include "mono_gc_handle.h"
#include "mono_gd/gd_mono.h"
#include "mono_gd/gd_mono_header.h"
#include "mono_gd/gd_mono_internals.h"
#ifdef TOOLS_ENABLED
#include "editor/editor_plugin.h"
@ -67,48 +65,17 @@ TScriptInstance *cast_script_instance(ScriptInstance *p_inst) {
#define CAST_CSHARP_INSTANCE(m_inst) (cast_script_instance<CSharpInstance, CSharpLanguage>(m_inst))
struct DotNetScriptLookupInfo {
String class_namespace;
String class_name;
GDMonoClass *script_class = nullptr;
DotNetScriptLookupInfo() {} // Required by HashMap...
DotNetScriptLookupInfo(const String &p_class_namespace, const String &p_class_name, GDMonoClass *p_script_class) :
class_namespace(p_class_namespace), class_name(p_class_name), script_class(p_script_class) {
}
};
class CSharpScript : public Script {
GDCLASS(CSharpScript, Script);
public:
struct SignalParameter {
String name;
Variant::Type type;
bool nil_is_variant = false;
};
struct EventSignal {
GDMonoField *field = nullptr;
GDMonoMethod *invoke_method = nullptr;
Vector<SignalParameter> parameters;
};
private:
friend class CSharpInstance;
friend class CSharpLanguage;
friend struct CSharpScriptDepSort;
bool tool = false;
bool valid = false;
bool reload_invalidated = false;
GDMonoClass *base = nullptr;
GDMonoClass *native = nullptr;
GDMonoClass *script_class = nullptr;
Ref<CSharpScript> base_cache; // TODO what's this for?
Ref<CSharpScript> base_script;
HashSet<Object *> instances;
@ -118,26 +85,32 @@ private:
// Replace with buffer containing the serialized state of managed scripts.
// Keep variant state backup to use only with script instance placeholders.
List<Pair<StringName, Variant>> properties;
List<Pair<StringName, Array>> event_signals;
Dictionary event_signals;
};
HashSet<ObjectID> pending_reload_instances;
RBMap<ObjectID, StateBackup> pending_reload_state;
StringName tied_class_name_for_reload;
StringName tied_class_namespace_for_reload;
#endif
String source;
StringName name;
SelfList<CSharpScript> script_list = this;
HashMap<StringName, Vector<SignalParameter>> _signals;
HashMap<StringName, EventSignal> event_signals;
bool signals_invalidated = true;
Dictionary rpc_config;
struct EventSignalInfo {
StringName name; // MethodInfo stores a string...
MethodInfo method_info;
};
struct CSharpMethodInfo {
StringName name; // MethodInfo stores a string...
MethodInfo method_info;
};
Vector<EventSignalInfo> event_signals;
Vector<CSharpMethodInfo> methods;
#ifdef TOOLS_ENABLED
List<PropertyInfo> exported_members_cache; // members_cache
HashMap<StringName, Variant> exported_members_defval_cache; // member_default_values_cache
@ -146,7 +119,6 @@ private:
bool placeholder_fallback_enabled = false;
bool exports_invalidated = true;
void _update_exports_values(HashMap<StringName, Variant> &values, List<PropertyInfo> &propnames);
void _update_member_info_no_exports();
void _placeholder_erased(PlaceHolderScriptInstance *p_placeholder) override;
#endif
@ -158,39 +130,24 @@ private:
void _clear();
void _update_name();
void load_script_signals(GDMonoClass *p_class, GDMonoClass *p_native_class);
bool _get_signal(GDMonoClass *p_class, GDMonoMethod *p_delegate_invoke, Vector<SignalParameter> &params);
bool _update_exports(PlaceHolderScriptInstance *p_instance_to_update = nullptr);
bool _get_member_export(IMonoClassMember *p_member, bool p_inspect_export, PropertyInfo &r_prop_info, bool &r_exported);
#ifdef TOOLS_ENABLED
static int _try_get_member_export_hint(IMonoClassMember *p_member, ManagedType p_type, Variant::Type p_variant_type, bool p_allow_generics, PropertyHint &r_hint, String &r_hint_string);
#endif
CSharpInstance *_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error);
Variant _new(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
// Do not use unless you know what you are doing
friend void GDMonoInternals::tie_managed_to_unmanaged(MonoObject *, Object *);
static Ref<CSharpScript> create_for_managed_type(GDMonoClass *p_class, GDMonoClass *p_native);
static void update_script_class_info(Ref<CSharpScript> p_script);
static void initialize_for_managed_type(Ref<CSharpScript> p_script, GDMonoClass *p_class, GDMonoClass *p_native);
Variant _member_get_rpc_config(IMonoClassMember *p_member) const;
protected:
static void _bind_methods();
Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override;
void _resource_path_changed() override;
bool _get(const StringName &p_name, Variant &r_ret) const;
bool _set(const StringName &p_name, const Variant &p_value);
void _get_property_list(List<PropertyInfo> *p_properties) const;
public:
static void reload_registered_script(Ref<CSharpScript> p_script);
bool can_instantiate() const override;
StringName get_instance_base_type() const override;
ScriptInstance *instance_create(Object *p_this) override;
@ -214,14 +171,20 @@ public:
bool has_script_signal(const StringName &p_signal) const override;
void get_script_signal_list(List<MethodInfo> *r_signals) const override;
Vector<EventSignalInfo> get_script_event_signals() const;
bool get_property_default_value(const StringName &p_property, Variant &r_value) const override;
void get_script_property_list(List<PropertyInfo> *r_list) const override;
void update_exports() override;
void get_members(HashSet<StringName> *p_members) override;
bool is_tool() const override { return tool; }
bool is_valid() const override { return valid; }
bool is_tool() const override {
return tool;
}
bool is_valid() const override {
return valid;
}
bool inherits_script(const Ref<Script> &p_script) const override;
@ -237,7 +200,9 @@ public:
const Variant get_rpc_config() const override;
#ifdef TOOLS_ENABLED
bool is_placeholder_fallback_enabled() const override { return placeholder_fallback_enabled; }
bool is_placeholder_fallback_enabled() const override {
return placeholder_fallback_enabled;
}
#endif
Error load_source_code(const String &p_path);
@ -270,22 +235,18 @@ class CSharpInstance : public ScriptInstance {
bool _unreference_owner_unsafe();
/*
* If nullptr is returned, the caller must destroy the script instance by removing it from its owner.
* If false is returned, the caller must destroy the script instance by removing it from its owner.
*/
MonoObject *_internal_new_managed();
bool _internal_new_managed();
// Do not use unless you know what you are doing
friend void GDMonoInternals::tie_managed_to_unmanaged(MonoObject *, Object *);
static CSharpInstance *create_for_managed_type(Object *p_owner, CSharpScript *p_script, const MonoGCHandleData &p_gchandle);
void get_properties_state_for_reloading(List<Pair<StringName, Variant>> &r_state);
void get_event_signals_state_for_reloading(List<Pair<StringName, Array>> &r_state);
public:
MonoObject *get_mono_object() const;
_FORCE_INLINE_ bool is_destructing_script_instance() { return destructing_script_instance; }
_FORCE_INLINE_ GCHandleIntPtr get_gchandle_intptr() { return gchandle.get_intptr(); }
Object *get_owner() override;
bool set(const StringName &p_name, const Variant &p_value) override;
@ -300,13 +261,13 @@ public:
bool has_method(const StringName &p_method) const override;
Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override;
void mono_object_disposed(MonoObject *p_obj);
void mono_object_disposed(GCHandleIntPtr p_gchandle_to_free);
/*
* If 'r_delete_owner' is set to true, the caller must memdelete the script instance's owner. Otherwise, if
* 'r_remove_script_instance' is set to true, the caller must destroy the script instance by removing it from its owner.
*/
void mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_finalizer, bool &r_delete_owner, bool &r_remove_script_instance);
void mono_object_disposed_baseref(GCHandleIntPtr p_gchandle_to_free, bool p_is_finalizer, bool &r_delete_owner, bool &r_remove_script_instance);
void connect_event_signals();
void disconnect_event_signals();
@ -332,7 +293,6 @@ public:
struct CSharpScriptBinding {
bool inited = false;
StringName type_name;
GDMonoClass *wrapper_class = nullptr;
MonoGCHandleData gchandle;
Object *owner = nullptr;
@ -370,35 +330,22 @@ class CSharpLanguage : public ScriptLanguage {
ManagedCallableMiddleman *managed_callable_middleman = memnew(ManagedCallableMiddleman);
struct StringNameCache {
StringName _signal_callback;
StringName _set;
StringName _get;
StringName _get_property_list;
StringName _property_can_revert;
StringName _property_get_revert;
StringName _notification;
StringName _script_source;
StringName dotctor; // .ctor
StringName on_before_serialize; // OnBeforeSerialize
StringName on_after_deserialize; // OnAfterDeserialize
StringName delegate_invoke_method_name;
StringNameCache();
};
int lang_idx = -1;
HashMap<String, DotNetScriptLookupInfo> dotnet_script_lookup_map;
void lookup_script_for_class(GDMonoClass *p_class);
// For debug_break and debug_break_parse
int _debug_parse_err_line = -1;
String _debug_parse_err_file;
String _debug_error;
friend class GDMono;
void _on_scripts_domain_unloaded();
void _on_scripts_domain_about_to_unload();
#ifdef TOOLS_ENABLED
EditorPlugin *godotsharp_editor = nullptr;
@ -420,21 +367,35 @@ public:
StringNameCache string_names;
const Mutex &get_language_bind_mutex() { return language_bind_mutex; }
const Mutex &get_language_bind_mutex() {
return language_bind_mutex;
}
const Mutex &get_script_instances_mutex() {
return script_instances_mutex;
}
_FORCE_INLINE_ int get_language_index() { return lang_idx; }
_FORCE_INLINE_ int get_language_index() {
return lang_idx;
}
void set_language_index(int p_idx);
_FORCE_INLINE_ const StringNameCache &get_string_names() { return string_names; }
_FORCE_INLINE_ const StringNameCache &get_string_names() {
return string_names;
}
_FORCE_INLINE_ static CSharpLanguage *get_singleton() { return singleton; }
_FORCE_INLINE_ static CSharpLanguage *get_singleton() {
return singleton;
}
#ifdef TOOLS_ENABLED
_FORCE_INLINE_ EditorPlugin *get_godotsharp_editor() const { return godotsharp_editor; }
_FORCE_INLINE_ EditorPlugin *get_godotsharp_editor() const {
return godotsharp_editor;
}
#endif
static void release_script_gchandle(MonoGCHandleData &p_gchandle);
static void release_script_gchandle(MonoObject *p_expected_obj, MonoGCHandleData &p_gchandle);
static void release_script_gchandle_thread_safe(GCHandleIntPtr p_gchandle_to_free, MonoGCHandleData &r_gchandle);
static void release_binding_gchandle_thread_safe(GCHandleIntPtr p_gchandle_to_free, CSharpScriptBinding &r_script_binding);
bool debug_break(const String &p_error, bool p_allow_continue = true);
bool debug_break_parse(const String &p_file, int p_line, const String &p_error);
@ -444,12 +405,8 @@ public:
void reload_assemblies(bool p_soft_reload);
#endif
_FORCE_INLINE_ ManagedCallableMiddleman *get_managed_callable_middleman() const { return managed_callable_middleman; }
void lookup_scripts_in_assembly(GDMonoAssembly *p_assembly);
const DotNetScriptLookupInfo *lookup_dotnet_script(const String &p_script_path) const {
return dotnet_script_lookup_map.getptr(p_script_path);
_FORCE_INLINE_ ManagedCallableMiddleman *get_managed_callable_middleman() const {
return managed_callable_middleman;
}
String get_name() const override;
@ -479,7 +436,9 @@ public:
Script *create_script() const override;
bool has_named_classes() const override;
bool supports_builtin_mode() const override;
/* TODO? */ int find_function(const String &p_function, const String &p_code) const override { return -1; }
/* TODO? */ int find_function(const String &p_function, const String &p_code) const override {
return -1;
}
String make_function(const String &p_class, const String &p_name, const PackedStringArray &p_args) const override;
virtual String _get_indentation() const;
/* TODO? */ void auto_indent_code(String &p_code, int p_from_line, int p_to_line) const override {}
@ -494,14 +453,20 @@ public:
/* TODO */ void debug_get_stack_level_locals(int p_level, List<String> *p_locals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) override {}
/* TODO */ void debug_get_stack_level_members(int p_level, List<String> *p_members, List<Variant> *p_values, int p_max_subitems, int p_max_depth) override {}
/* TODO */ void debug_get_globals(List<String> *p_locals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) override {}
/* TODO */ String debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems, int p_max_depth) override { return ""; }
/* TODO */ String debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems, int p_max_depth) override {
return "";
}
Vector<StackInfo> debug_get_current_stack_info() override;
/* PROFILING FUNCTIONS */
/* TODO */ void profiling_start() override {}
/* TODO */ void profiling_stop() override {}
/* TODO */ int profiling_get_accumulated_data(ProfilingInfo *p_info_arr, int p_info_max) override { return 0; }
/* TODO */ int profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_info_max) override { return 0; }
/* TODO */ int profiling_get_accumulated_data(ProfilingInfo *p_info_arr, int p_info_max) override {
return 0;
}
/* TODO */ int profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_info_max) override {
return 0;
}
void frame() override;
@ -520,16 +485,12 @@ public:
bool overrides_external_editor() override;
#endif
/* THREAD ATTACHING */
void thread_enter() override;
void thread_exit() override;
RBMap<Object *, CSharpScriptBinding>::Element *insert_script_binding(Object *p_object, const CSharpScriptBinding &p_script_binding);
bool setup_csharp_script_binding(CSharpScriptBinding &r_script_binding, Object *p_object);
#ifdef DEBUG_ENABLED
Vector<StackInfo> stack_trace_get_info(MonoObject *p_stack_trace);
#endif
static void tie_native_managed_to_unmanaged(GCHandleIntPtr p_gchandle_intptr, Object *p_unmanaged, const StringName *p_native_name, bool p_ref_counted);
static void tie_user_managed_to_unmanaged(GCHandleIntPtr p_gchandle_intptr, Object *p_unmanaged, Ref<CSharpScript> *p_script, bool p_ref_counted);
static void tie_managed_to_unmanaged_with_pre_setup(GCHandleIntPtr p_gchandle_intptr, Object *p_unmanaged);
void post_unsafe_reference(Object *p_obj);
void pre_unsafe_unreference(Object *p_obj);

View File

@ -10,55 +10,10 @@
<tutorials>
</tutorials>
<methods>
<method name="attach_thread">
<return type="void" />
<description>
Attaches the current thread to the Mono runtime.
</description>
</method>
<method name="detach_thread">
<return type="void" />
<description>
Detaches the current thread from the Mono runtime.
</description>
</method>
<method name="get_domain_id">
<return type="int" />
<description>
Returns the current MonoDomain ID.
[b]Note:[/b] The Mono runtime must be initialized for this method to work (use [method is_runtime_initialized] to check). If the Mono runtime isn't initialized at the time this method is called, the engine will crash.
</description>
</method>
<method name="get_scripts_domain_id">
<return type="int" />
<description>
Returns the scripts MonoDomain's ID. This will be the same MonoDomain ID as [method get_domain_id], unless the scripts domain isn't loaded.
[b]Note:[/b] The Mono runtime must be initialized for this method to work (use [method is_runtime_initialized] to check). If the Mono runtime isn't initialized at the time this method is called, the engine will crash.
</description>
</method>
<method name="is_domain_finalizing_for_unload">
<return type="bool" />
<param index="0" name="domain_id" type="int" />
<description>
Returns [code]true[/code] if the domain is being finalized, [code]false[/code] otherwise.
</description>
</method>
<method name="is_runtime_initialized">
<return type="bool" />
<description>
Returns [code]true[/code] if the Mono runtime is initialized, [code]false[/code] otherwise.
</description>
</method>
<method name="is_runtime_shutting_down">
<return type="bool" />
<description>
Returns [code]true[/code] if the Mono runtime is shutting down, [code]false[/code] otherwise.
</description>
</method>
<method name="is_scripts_domain_loaded">
<return type="bool" />
<description>
Returns [code]true[/code] if the scripts domain is loaded, [code]false[/code] otherwise.
Returns [code]true[/code] if the .NET runtime is initialized, [code]false[/code] otherwise.
</description>
</method>
</methods>

View File

@ -1,4 +1,4 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.NET.Sdk", "Godot.NET.Sdk\Godot.NET.Sdk.csproj", "{31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}"
EndProject

View File

@ -26,16 +26,8 @@
<None Include="Sdk\Sdk.props" Pack="true" PackagePath="Sdk" />
<None Include="Sdk\Sdk.targets" Pack="true" PackagePath="Sdk" />
<!-- SdkPackageVersions.props -->
<None Include="..\..\..\SdkPackageVersions.props" Pack="true" PackagePath="Sdk">
<None Include="$(GodotSdkPackageVersionsFilePath)" Pack="true" PackagePath="Sdk">
<Link>Sdk\SdkPackageVersions.props</Link>
</None>
</ItemGroup>
<Target Name="CopyNupkgToSConsOutputDir" AfterTargets="Pack">
<PropertyGroup>
<GodotSourceRootPath>$(SolutionDir)\..\..\..\..\</GodotSourceRootPath>
<GodotOutputDataDir>$(GodotSourceRootPath)\bin\GodotSharp\</GodotOutputDataDir>
</PropertyGroup>
<Copy SourceFiles="$(PackageOutputPath)$(PackageId).$(PackageVersion).nupkg" DestinationFolder="$(GodotOutputDataDir)Tools\nupkgs\" />
</Target>
</Project>

View File

@ -95,21 +95,4 @@
<DefineConstants>$(GodotDefineConstants);$(DefineConstants)</DefineConstants>
</PropertyGroup>
<!-- Godot API references -->
<ItemGroup>
<!--
TODO:
We should consider a nuget package for reference assemblies. This is difficult because the
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>
</Project>

View File

@ -19,4 +19,10 @@
<ItemGroup Condition=" '$(DisableImplicitGodotGeneratorReferences)' != 'true' ">
<PackageReference Include="Godot.SourceGenerators" Version="$(PackageFloatingVersion_Godot)" />
</ItemGroup>
<!-- Godot API references -->
<ItemGroup Condition=" '$(DisableImplicitGodotSharpReferences)' != 'true' ">
<PackageReference Include="GodotSharp" Version="$(PackageVersion_GodotSharp)" />
<PackageReference Include="GodotSharpEditor" Version="$(PackageVersion_GodotSharp)" Condition=" '$(Configuration)' == 'Debug' " />
</ItemGroup>
</Project>

View File

@ -0,0 +1,7 @@
namespace Godot.SourceGenerators.Sample;
public partial class EventSignals : Godot.Object
{
[Signal]
public delegate void MySignalEventHandler(string str, int num);
}

View File

@ -0,0 +1,109 @@
using System;
using System.Diagnostics.CodeAnalysis;
#pragma warning disable CS0169
#pragma warning disable CS0414
namespace Godot.SourceGenerators.Sample
{
[SuppressMessage("ReSharper", "BuiltInTypeReferenceStyle")]
[SuppressMessage("ReSharper", "RedundantNameQualifier")]
[SuppressMessage("ReSharper", "ArrangeObjectCreationWhenTypeEvident")]
[SuppressMessage("ReSharper", "InconsistentNaming")]
public partial class ExportedFields : Godot.Object
{
[Export] private Boolean field_Boolean = true;
[Export] private Char field_Char = 'f';
[Export] private SByte field_SByte = 10;
[Export] private Int16 field_Int16 = 10;
[Export] private Int32 field_Int32 = 10;
[Export] private Int64 field_Int64 = 10;
[Export] private Byte field_Byte = 10;
[Export] private UInt16 field_UInt16 = 10;
[Export] private UInt32 field_UInt32 = 10;
[Export] private UInt64 field_UInt64 = 10;
[Export] private Single field_Single = 10;
[Export] private Double field_Double = 10;
[Export] private String field_String = "foo";
// Godot structs
[Export] private Vector2 field_Vector2 = new(10f, 10f);
[Export] private Vector2i field_Vector2i = Vector2i.Up;
[Export] private Rect2 field_Rect2 = new(new Vector2(10f, 10f), new Vector2(10f, 10f));
[Export] private Rect2i field_Rect2i = new(new Vector2i(10, 10), new Vector2i(10, 10));
[Export] private Transform2D field_Transform2D = Transform2D.Identity;
[Export] private Vector3 field_Vector3 = new(10f, 10f, 10f);
[Export] private Vector3i field_Vector3i = Vector3i.Back;
[Export] private Basis field_Basis = new Basis(Quaternion.Identity);
[Export] private Quaternion field_Quaternion = new Quaternion(Basis.Identity);
[Export] private Transform3D field_Transform3D = Transform3D.Identity;
[Export] private Vector4 field_Vector4 = new(10f, 10f, 10f, 10f);
[Export] private Vector4i field_Vector4i = Vector4i.One;
[Export] private Projection field_Projection = Projection.Identity;
[Export] private AABB field_AABB = new AABB(10f, 10f, 10f, new Vector3(1f, 1f, 1f));
[Export] private Color field_Color = Colors.Aquamarine;
[Export] private Plane field_Plane = Plane.PlaneXZ;
[Export] private Callable field_Callable = new Callable(Engine.GetMainLoop(), "_process");
[Export] private SignalInfo field_SignalInfo = new SignalInfo(Engine.GetMainLoop(), "property_list_changed");
// Enums
[SuppressMessage("ReSharper", "UnusedMember.Local")]
enum MyEnum
{
A,
B,
C
}
[Export] private MyEnum field_Enum = MyEnum.C;
[Flags]
[SuppressMessage("ReSharper", "UnusedMember.Local")]
enum MyFlagsEnum
{
A,
B,
C
}
[Export] private MyFlagsEnum field_FlagsEnum = MyFlagsEnum.C;
// Arrays
[Export] private Byte[] field_ByteArray = { 0, 1, 2, 3, 4, 5, 6 };
[Export] private Int32[] field_Int32Array = { 0, 1, 2, 3, 4, 5, 6 };
[Export] private Int64[] field_Int64Array = { 0, 1, 2, 3, 4, 5, 6 };
[Export] private Single[] field_SingleArray = { 0f, 1f, 2f, 3f, 4f, 5f, 6f };
[Export] private Double[] field_DoubleArray = { 0d, 1d, 2d, 3d, 4d, 5d, 6d };
[Export] private String[] field_StringArray = { "foo", "bar" };
[Export(PropertyHint.Enum, "A,B,C")] private String[] field_StringArrayEnum = { "foo", "bar" };
[Export] private Vector2[] field_Vector2Array = { Vector2.Up, Vector2.Down, Vector2.Left, Vector2.Right };
[Export] private Vector3[] field_Vector3Array = { Vector3.Up, Vector3.Down, Vector3.Left, Vector3.Right };
[Export] private Color[] field_ColorArray = { Colors.Aqua, Colors.Aquamarine, Colors.Azure, Colors.Beige };
[Export] private Godot.Object[] field_GodotObjectOrDerivedArray = { null };
[Export] private StringName[] field_StringNameArray = { "foo", "bar" };
[Export] private NodePath[] field_NodePathArray = { "foo", "bar" };
[Export] private RID[] field_RIDArray = { default, default, default };
// Variant
[Export] private Variant field_Variant = "foo";
// Classes
[Export] private Godot.Object field_GodotObjectOrDerived;
[Export] private Godot.Texture field_GodotResourceTexture;
[Export] private StringName field_StringName = new StringName("foo");
[Export] private NodePath field_NodePath = new NodePath("foo");
[Export] private RID field_RID;
[Export] private Godot.Collections.Dictionary field_GodotDictionary =
new() { { "foo", 10 }, { Vector2.Up, Colors.Chocolate } };
[Export] private Godot.Collections.Array field_GodotArray =
new() { "foo", 10, Vector2.Up, Colors.Chocolate };
[Export] private Godot.Collections.Dictionary<string, bool> field_GodotGenericDictionary =
new() { { "foo", true }, { "bar", false } };
[Export] private Godot.Collections.Array<int> field_GodotGenericArray =
new() { 0, 1, 2, 3, 4, 5, 6 };
}
}

View File

@ -0,0 +1,109 @@
using System;
using System.Diagnostics.CodeAnalysis;
#pragma warning disable CS0169
#pragma warning disable CS0414
namespace Godot.SourceGenerators.Sample
{
[SuppressMessage("ReSharper", "BuiltInTypeReferenceStyle")]
[SuppressMessage("ReSharper", "RedundantNameQualifier")]
[SuppressMessage("ReSharper", "ArrangeObjectCreationWhenTypeEvident")]
[SuppressMessage("ReSharper", "InconsistentNaming")]
public partial class ExportedProperties : Godot.Object
{
[Export] private Boolean property_Boolean { get; set; } = true;
[Export] private Char property_Char { get; set; } = 'f';
[Export] private SByte property_SByte { get; set; } = 10;
[Export] private Int16 property_Int16 { get; set; } = 10;
[Export] private Int32 property_Int32 { get; set; } = 10;
[Export] private Int64 property_Int64 { get; set; } = 10;
[Export] private Byte property_Byte { get; set; } = 10;
[Export] private UInt16 property_UInt16 { get; set; } = 10;
[Export] private UInt32 property_UInt32 { get; set; } = 10;
[Export] private UInt64 property_UInt64 { get; set; } = 10;
[Export] private Single property_Single { get; set; } = 10;
[Export] private Double property_Double { get; set; } = 10;
[Export] private String property_String { get; set; } = "foo";
// Godot structs
[Export] private Vector2 property_Vector2 { get; set; } = new(10f, 10f);
[Export] private Vector2i property_Vector2i { get; set; } = Vector2i.Up;
[Export] private Rect2 property_Rect2 { get; set; } = new(new Vector2(10f, 10f), new Vector2(10f, 10f));
[Export] private Rect2i property_Rect2i { get; set; } = new(new Vector2i(10, 10), new Vector2i(10, 10));
[Export] private Transform2D property_Transform2D { get; set; } = Transform2D.Identity;
[Export] private Vector3 property_Vector3 { get; set; } = new(10f, 10f, 10f);
[Export] private Vector3i property_Vector3i { get; set; } = Vector3i.Back;
[Export] private Basis property_Basis { get; set; } = new Basis(Quaternion.Identity);
[Export] private Quaternion property_Quaternion { get; set; } = new Quaternion(Basis.Identity);
[Export] private Transform3D property_Transform3D { get; set; } = Transform3D.Identity;
[Export] private Vector4 property_Vector4 { get; set; } = new(10f, 10f, 10f, 10f);
[Export] private Vector4i property_Vector4i { get; set; } = Vector4i.One;
[Export] private Projection property_Projection { get; set; } = Projection.Identity;
[Export] private AABB property_AABB { get; set; } = new AABB(10f, 10f, 10f, new Vector3(1f, 1f, 1f));
[Export] private Color property_Color { get; set; } = Colors.Aquamarine;
[Export] private Plane property_Plane { get; set; } = Plane.PlaneXZ;
[Export] private Callable property_Callable { get; set; } = new Callable(Engine.GetMainLoop(), "_process");
[Export] private SignalInfo property_SignalInfo { get; set; } = new SignalInfo(Engine.GetMainLoop(), "property_list_changed");
// Enums
[SuppressMessage("ReSharper", "UnusedMember.Local")]
enum MyEnum
{
A,
B,
C
}
[Export] private MyEnum property_Enum { get; set; } = MyEnum.C;
[Flags]
[SuppressMessage("ReSharper", "UnusedMember.Local")]
enum MyFlagsEnum
{
A,
B,
C
}
[Export] private MyFlagsEnum property_FlagsEnum { get; set; } = MyFlagsEnum.C;
// Arrays
[Export] private Byte[] property_ByteArray { get; set; } = { 0, 1, 2, 3, 4, 5, 6 };
[Export] private Int32[] property_Int32Array { get; set; } = { 0, 1, 2, 3, 4, 5, 6 };
[Export] private Int64[] property_Int64Array { get; set; } = { 0, 1, 2, 3, 4, 5, 6 };
[Export] private Single[] property_SingleArray { get; set; } = { 0f, 1f, 2f, 3f, 4f, 5f, 6f };
[Export] private Double[] property_DoubleArray { get; set; } = { 0d, 1d, 2d, 3d, 4d, 5d, 6d };
[Export] private String[] property_StringArray { get; set; } = { "foo", "bar" };
[Export(PropertyHint.Enum, "A,B,C")] private String[] property_StringArrayEnum { get; set; } = { "foo", "bar" };
[Export] private Vector2[] property_Vector2Array { get; set; } = { Vector2.Up, Vector2.Down, Vector2.Left, Vector2.Right };
[Export] private Vector3[] property_Vector3Array { get; set; } = { Vector3.Up, Vector3.Down, Vector3.Left, Vector3.Right };
[Export] private Color[] property_ColorArray { get; set; } = { Colors.Aqua, Colors.Aquamarine, Colors.Azure, Colors.Beige };
[Export] private Godot.Object[] property_GodotObjectOrDerivedArray { get; set; } = { null };
[Export] private StringName[] field_StringNameArray { get; set; } = { "foo", "bar" };
[Export] private NodePath[] field_NodePathArray { get; set; } = { "foo", "bar" };
[Export] private RID[] field_RIDArray { get; set; } = { default, default, default };
// Variant
[Export] private Variant property_Variant { get; set; } = "foo";
// Classes
[Export] private Godot.Object property_GodotObjectOrDerived { get; set; }
[Export] private Godot.Texture property_GodotResourceTexture { get; set; }
[Export] private StringName property_StringName { get; set; } = new StringName("foo");
[Export] private NodePath property_NodePath { get; set; } = new NodePath("foo");
[Export] private RID property_RID { get; set; }
[Export] private Godot.Collections.Dictionary property_GodotDictionary { get; set; } =
new() { { "foo", 10 }, { Vector2.Up, Colors.Chocolate } };
[Export] private Godot.Collections.Array property_GodotArray { get; set; } =
new() { "foo", 10, Vector2.Up, Colors.Chocolate };
[Export] private Godot.Collections.Dictionary<string, bool> property_GodotGenericDictionary { get; set; } =
new() { { "foo", true }, { "bar", false } };
[Export] private Godot.Collections.Array<int> property_GodotGenericArray { get; set; } =
new() { 0, 1, 2, 3, 4, 5, 6 };
}
}

View File

@ -1,16 +1,21 @@
#pragma warning disable CS0169
namespace Godot.SourceGenerators.Sample
{
partial class Generic<T> : Godot.Object
{
private int _field;
}
// Generic again but different generic parameters
partial class Generic<T, R> : Godot.Object
{
private int _field;
}
// Generic again but without generic parameters
partial class Generic : Godot.Object
{
private int _field;
}
}

View File

@ -1,12 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<!-- $(GodotProjectDir) would normally be defined by the Godot.NET.Sdk -->
<GodotProjectDir>$(MSBuildProjectDirectory)</GodotProjectDir>
<!-- For compiling GetGodotPropertyDefaultValues. -->
<DefineConstants>$(DefineConstants);TOOLS</DefineConstants>
</PropertyGroup>
<PropertyGroup>

View File

@ -0,0 +1,31 @@
using System.Diagnostics.CodeAnalysis;
namespace Godot.SourceGenerators.Sample;
[SuppressMessage("ReSharper", "RedundantNameQualifier")]
public partial class Methods : Godot.Object
{
private void MethodWithOverload()
{
}
private void MethodWithOverload(int a)
{
}
private void MethodWithOverload(int a, int b)
{
}
// Should be ignored. The previous one is picked.
// ReSharper disable once UnusedMember.Local
private void MethodWithOverload(float a, float b)
{
}
// Generic methods should be ignored.
// ReSharper disable once UnusedMember.Local
private void GenericMethod<T>(T t)
{
}
}

View File

@ -0,0 +1,36 @@
#pragma warning disable CS0169
namespace Godot.SourceGenerators.Sample
{
public partial class ScriptBoilerplate : Node
{
private NodePath _nodePath;
private int _velocity;
public override void _Process(float delta)
{
_ = delta;
base._Process(delta);
}
public int Bazz(StringName name)
{
_ = name;
return 1;
}
public void IgnoreThisMethodWithByRefParams(ref int a)
{
_ = a;
}
}
partial struct OuterClass
{
public partial class NesterClass : RefCounted
{
public override Variant _Get(StringName property) => default;
}
}
}

View File

@ -1,3 +1,4 @@
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
@ -14,9 +15,8 @@ namespace Godot.SourceGenerators
"Missing partial modifier on declaration of type '" +
$"{symbol.FullQualifiedName()}' which is a subclass of '{GodotClasses.Object}'";
string description = $"{message}. Subclasses of '{GodotClasses.Object}' must be " +
"declared with the partial modifier or annotated with the " +
$"attribute '{GodotClasses.DisableGodotGeneratorsAttr}'.";
string description = $"{message}. Subclasses of '{GodotClasses.Object}' " +
"must be declared with the partial modifier.";
context.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor(id: "GODOT-G0001",
@ -29,5 +29,191 @@ 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));
}
public static void ReportExportedMemberIsStatic(
GeneratorExecutionContext context,
ISymbol exportedMemberSymbol
)
{
var locations = exportedMemberSymbol.Locations;
var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
bool isField = exportedMemberSymbol is IFieldSymbol;
string message = $"Attempted to export static {(isField ? "field" : "property")}: " +
$"'{exportedMemberSymbol.ToDisplayString()}'";
string description = $"{message}. Only instance fields and properties can be exported." +
" Remove the 'static' modifier or the '[Export]' attribute.";
context.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor(id: "GODOT-G0101",
title: message,
messageFormat: message,
category: "Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description),
location,
location?.SourceTree?.FilePath));
}
public static void ReportExportedMemberTypeNotSupported(
GeneratorExecutionContext context,
ISymbol exportedMemberSymbol
)
{
var locations = exportedMemberSymbol.Locations;
var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
bool isField = exportedMemberSymbol is IFieldSymbol;
string message = $"The type of the exported {(isField ? "field" : "property")} " +
$"is not supported: '{exportedMemberSymbol.ToDisplayString()}'";
string description = $"{message}. Use a supported type or remove the '[Export]' attribute.";
context.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor(id: "GODOT-G0102",
title: message,
messageFormat: message,
category: "Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description),
location,
location?.SourceTree?.FilePath));
}
public static void ReportExportedMemberIsReadOnly(
GeneratorExecutionContext context,
ISymbol exportedMemberSymbol
)
{
var locations = exportedMemberSymbol.Locations;
var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
bool isField = exportedMemberSymbol is IFieldSymbol;
string message = $"The exported {(isField ? "field" : "property")} " +
$"is read-only: '{exportedMemberSymbol.ToDisplayString()}'";
string description = isField ?
$"{message}. Exported fields cannot be read-only." :
$"{message}. Exported properties must be writable.";
context.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor(id: "GODOT-G0103",
title: message,
messageFormat: message,
category: "Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description),
location,
location?.SourceTree?.FilePath));
}
public static void ReportExportedMemberIsWriteOnly(
GeneratorExecutionContext context,
ISymbol exportedMemberSymbol
)
{
var locations = exportedMemberSymbol.Locations;
var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
string message = $"The exported property is write-only: '{exportedMemberSymbol.ToDisplayString()}'";
string description = $"{message}. Exported properties must be readable.";
context.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor(id: "GODOT-G0104",
title: message,
messageFormat: message,
category: "Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description),
location,
location?.SourceTree?.FilePath));
}
public static void ReportSignalDelegateMissingSuffix(
GeneratorExecutionContext context,
INamedTypeSymbol delegateSymbol)
{
var locations = delegateSymbol.Locations;
var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
string message = "The name of the delegate must end with 'EventHandler': " +
delegateSymbol.ToDisplayString() +
$". Did you mean '{delegateSymbol.Name}EventHandler'?";
string description = $"{message}. Rename the delegate accordingly or remove the '[Signal]' attribute.";
context.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor(id: "GODOT-G0201",
title: message,
messageFormat: message,
category: "Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description),
location,
location?.SourceTree?.FilePath));
}
public static void ReportSignalDelegateSignatureNotSupported(
GeneratorExecutionContext context,
INamedTypeSymbol delegateSymbol)
{
var locations = delegateSymbol.Locations;
var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
string message = "The delegate signature of the signal " +
$"is not supported: '{delegateSymbol.ToDisplayString()}'";
string description = $"{message}. Use supported types only or remove the '[Signal]' attribute.";
context.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor(id: "GODOT-G0202",
title: message,
messageFormat: message,
category: "Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description),
location,
location?.SourceTree?.FilePath));
}
}
}

View File

@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
@ -13,30 +15,65 @@ namespace Godot.SourceGenerators
) => context.AnalyzerConfigOptions.GlobalOptions
.TryGetValue("build_property." + property, out value);
private static bool InheritsFrom(this INamedTypeSymbol? symbol, string baseName)
{
if (symbol == null)
return false;
public static bool AreGodotSourceGeneratorsDisabled(this GeneratorExecutionContext context)
=> context.TryGetGlobalAnalyzerProperty("GodotSourceGenerators", out string? toggle) &&
toggle != null &&
toggle.Equals("disabled", StringComparison.OrdinalIgnoreCase);
while (true)
public static bool IsGodotToolsProject(this GeneratorExecutionContext context)
=> context.TryGetGlobalAnalyzerProperty("IsGodotToolsProject", out string? toggle) &&
toggle != null &&
toggle.Equals("true", StringComparison.OrdinalIgnoreCase);
public static bool InheritsFrom(this INamedTypeSymbol? symbol, string assemblyName, string typeFullName)
{
while (symbol != null)
{
if (symbol.ToString() == baseName)
if (symbol.ContainingAssembly.Name == assemblyName &&
symbol.ToString() == typeFullName)
{
return true;
}
if (symbol.BaseType != null)
{
symbol = symbol.BaseType;
continue;
}
break;
symbol = symbol.BaseType;
}
return false;
}
public static INamedTypeSymbol? GetGodotScriptNativeClass(this INamedTypeSymbol classTypeSymbol)
{
var symbol = classTypeSymbol;
while (symbol != null)
{
if (symbol.ContainingAssembly.Name == "GodotSharp")
return symbol;
symbol = symbol.BaseType;
}
return null;
}
public static string? GetGodotScriptNativeClassName(this INamedTypeSymbol classTypeSymbol)
{
var nativeType = classTypeSymbol.GetGodotScriptNativeClass();
if (nativeType == null)
return null;
var godotClassNameAttr = nativeType.GetAttributes()
.FirstOrDefault(a => a.AttributeClass?.IsGodotClassNameAttribute() ?? false);
string? godotClassName = null;
if (godotClassNameAttr is { ConstructorArguments: { Length: > 0 } })
godotClassName = godotClassNameAttr.ConstructorArguments[0].Value?.ToString();
return godotClassName ?? nativeType.Name;
}
private static bool IsGodotScriptClass(
this ClassDeclarationSyntax cds, Compilation compilation,
out INamedTypeSymbol? symbol
@ -47,7 +84,7 @@ namespace Godot.SourceGenerators
var classTypeSymbol = sm.GetDeclaredSymbol(cds);
if (classTypeSymbol?.BaseType == null
|| !classTypeSymbol.BaseType.InheritsFrom(GodotClasses.Object))
|| !classTypeSymbol.BaseType.InheritsFrom("GodotSharp", GodotClasses.Object))
{
symbol = null;
return false;
@ -69,21 +106,171 @@ 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 HasDisableGeneratorsAttribute(this INamedTypeSymbol symbol)
=> symbol.GetAttributes().Any(attr =>
attr.AttributeClass?.ToString() == GodotClasses.DisableGodotGeneratorsAttr);
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);
public static string FullQualifiedName(this INamedTypeSymbol symbol)
public static string FullQualifiedName(this ITypeSymbol symbol)
=> symbol.ToDisplayString(NullableFlowState.NotNull, FullyQualifiedFormatOmitGlobal);
public static string NameWithTypeParameters(this INamedTypeSymbol symbol)
{
return symbol.IsGenericType ?
string.Concat(symbol.Name, "<", string.Join(", ", symbol.TypeParameters), ">") :
symbol.Name;
}
public static string FullQualifiedName(this INamespaceSymbol namespaceSymbol)
=> namespaceSymbol.ToDisplayString(FullyQualifiedFormatOmitGlobal);
public static string SanitizeQualifiedNameForUniqueHint(this string qualifiedName)
=> qualifiedName
// AddSource() doesn't support angle brackets
.Replace("<", "(Of ")
.Replace(">", ")");
public static bool IsGodotExportAttribute(this INamedTypeSymbol symbol)
=> symbol.ToString() == GodotClasses.ExportAttr;
public static bool IsGodotSignalAttribute(this INamedTypeSymbol symbol)
=> symbol.ToString() == GodotClasses.SignalAttr;
public static bool IsGodotClassNameAttribute(this INamedTypeSymbol symbol)
=> symbol.ToString() == GodotClasses.GodotClassNameAttr;
public static bool IsSystemFlagsAttribute(this INamedTypeSymbol symbol)
=> symbol.ToString() == GodotClasses.SystemFlagsAttr;
public static GodotMethodData? HasGodotCompatibleSignature(
this IMethodSymbol method,
MarshalUtils.TypeCache typeCache
)
{
if (method.IsGenericMethod)
return null;
var retSymbol = method.ReturnType;
var retType = method.ReturnsVoid ?
null :
MarshalUtils.ConvertManagedTypeToMarshalType(method.ReturnType, typeCache);
if (retType == null && !method.ReturnsVoid)
return null;
var parameters = method.Parameters;
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.ConvertManagedTypeToMarshalType(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)
return null; // Ignore incompatible method
return new GodotMethodData(method, paramTypes, parameters
.Select(p => p.Type).ToImmutableArray(), retType, retSymbol);
}
public static IEnumerable<GodotMethodData> WhereHasGodotCompatibleSignature(
this IEnumerable<IMethodSymbol> methods,
MarshalUtils.TypeCache typeCache
)
{
foreach (var method in methods)
{
var methodData = HasGodotCompatibleSignature(method, typeCache);
if (methodData != null)
yield return methodData.Value;
}
}
public static IEnumerable<GodotPropertyData> WhereIsGodotCompatibleType(
this IEnumerable<IPropertySymbol> properties,
MarshalUtils.TypeCache typeCache
)
{
foreach (var property in properties)
{
// TODO: We should still restore read-only properties after reloading assembly. Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload.
// Ignore properties without a getter or without a setter. Godot properties must be both readable and writable.
if (property.IsWriteOnly || property.IsReadOnly)
continue;
var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(property.Type, typeCache);
if (marshalType == null)
continue;
yield return new GodotPropertyData(property, marshalType.Value);
}
}
public static IEnumerable<GodotFieldData> WhereIsGodotCompatibleType(
this IEnumerable<IFieldSymbol> fields,
MarshalUtils.TypeCache typeCache
)
{
foreach (var field in fields)
{
// TODO: We should still restore read-only fields after reloading assembly. Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload.
// Ignore properties without a getter or without a setter. Godot properties must be both readable and writable.
if (field.IsReadOnly)
continue;
var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(field.Type, typeCache);
if (marshalType == null)
continue;
yield return new GodotFieldData(field, marshalType.Value);
}
}
}
}

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>8.0</LangVersion>
<LangVersion>9.0</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
@ -21,21 +21,13 @@
</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">
<PropertyGroup>
<GodotSourceRootPath>$(SolutionDir)\..\..\..\..\</GodotSourceRootPath>
<GodotOutputDataDir>$(GodotSourceRootPath)\bin\GodotSharp\</GodotOutputDataDir>
</PropertyGroup>
<Copy SourceFiles="$(PackageOutputPath)$(PackageId).$(PackageVersion).nupkg" DestinationFolder="$(GodotOutputDataDir)Tools\nupkgs\" />
</Target>
</Project>

View File

@ -2,6 +2,7 @@
<ItemGroup>
<!-- $(GodotProjectDir) is defined by Godot.NET.Sdk -->
<CompilerVisibleProperty Include="GodotProjectDir" />
<CompilerVisibleProperty Include="GodotScriptPathAttributeGenerator" />
<CompilerVisibleProperty Include="GodotSourceGenerators" />
<CompilerVisibleProperty Include="IsGodotToolsProject" />
</ItemGroup>
</Project>

View File

@ -3,7 +3,10 @@ namespace Godot.SourceGenerators
public static class GodotClasses
{
public const string Object = "Godot.Object";
public const string DisableGodotGeneratorsAttr = "Godot.DisableGodotGeneratorsAttribute";
public const string AssemblyHasScriptsAttr = "Godot.AssemblyHasScriptsAttribute";
public const string ExportAttr = "Godot.ExportAttribute";
public const string SignalAttr = "Godot.SignalAttribute";
public const string GodotClassNameAttr = "Godot.GodotClassName";
public const string SystemFlagsAttr = "System.FlagsAttribute";
}
}

View File

@ -0,0 +1,148 @@
using System;
namespace Godot.SourceGenerators
{
// TODO: May need to think about compatibility here. Could Godot change these values between minor versions?
internal enum VariantType
{
Nil = 0,
Bool = 1,
Int = 2,
Float = 3,
String = 4,
Vector2 = 5,
Vector2i = 6,
Rect2 = 7,
Rect2i = 8,
Vector3 = 9,
Vector3i = 10,
Transform2d = 11,
Vector4 = 12,
Vector4i = 13,
Plane = 14,
Quaternion = 15,
Aabb = 16,
Basis = 17,
Transform3d = 18,
Projection = 19,
Color = 20,
StringName = 21,
NodePath = 22,
Rid = 23,
Object = 24,
Callable = 25,
Signal = 26,
Dictionary = 27,
Array = 28,
PackedByteArray = 29,
PackedInt32Array = 30,
PackedInt64Array = 31,
PackedFloat32Array = 32,
PackedFloat64Array = 33,
PackedStringArray = 34,
PackedVector2Array = 35,
PackedVector3Array = 36,
PackedColorArray = 37,
Max = 38
}
internal enum PropertyHint
{
None = 0,
Range = 1,
Enum = 2,
EnumSuggestion = 3,
ExpEasing = 4,
Link = 5,
Flags = 6,
Layers2dRender = 7,
Layers2dPhysics = 8,
Layers2dNavigation = 9,
Layers3dRender = 10,
Layers3dPhysics = 11,
Layers3dNavigation = 12,
File = 13,
Dir = 14,
GlobalFile = 15,
GlobalDir = 16,
ResourceType = 17,
MultilineText = 18,
Expression = 19,
PlaceholderText = 20,
ColorNoAlpha = 21,
ImageCompressLossy = 22,
ImageCompressLossless = 23,
ObjectId = 24,
TypeString = 25,
NodePathToEditedNode = 26,
MethodOfVariantType = 27,
MethodOfBaseType = 28,
MethodOfInstance = 29,
MethodOfScript = 30,
PropertyOfVariantType = 31,
PropertyOfBaseType = 32,
PropertyOfInstance = 33,
PropertyOfScript = 34,
ObjectTooBig = 35,
NodePathValidTypes = 36,
SaveFile = 37,
GlobalSaveFile = 38,
IntIsObjectid = 39,
IntIsPointer = 41,
ArrayType = 40,
LocaleId = 42,
LocalizableString = 43,
NodeType = 44,
Max = 45
}
[Flags]
internal enum PropertyUsageFlags
{
None = 0,
Storage = 2,
Editor = 4,
Checkable = 8,
Checked = 16,
Internationalized = 32,
Group = 64,
Category = 128,
Subgroup = 256,
ClassIsBitfield = 512,
NoInstanceState = 1024,
RestartIfChanged = 2048,
ScriptVariable = 4096,
StoreIfNull = 8192,
AnimateAsTrigger = 16384,
UpdateAllIfModified = 32768,
ScriptDefaultValue = 65536,
ClassIsEnum = 131072,
NilIsVariant = 262144,
Internal = 524288,
DoNotShareOnDuplicate = 1048576,
HighEndGfx = 2097152,
NodePathFromSceneRoot = 4194304,
ResourceNotPersistent = 8388608,
KeyingIncrements = 16777216,
DeferredSetResource = 33554432,
EditorInstantiateObject = 67108864,
EditorBasicSetting = 134217728,
Array = 536870912,
Default = 6,
DefaultIntl = 38,
NoEditor = 2
}
public enum MethodFlags
{
Normal = 1,
Editor = 2,
Const = 4,
Virtual = 8,
Vararg = 16,
Static = 32,
ObjectCore = 64,
Default = 1
}
}

View File

@ -0,0 +1,62 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
namespace Godot.SourceGenerators
{
public struct GodotMethodData
{
public GodotMethodData(IMethodSymbol method, ImmutableArray<MarshalType> paramTypes,
ImmutableArray<ITypeSymbol> paramTypeSymbols, MarshalType? retType, ITypeSymbol? retSymbol)
{
Method = method;
ParamTypes = paramTypes;
ParamTypeSymbols = paramTypeSymbols;
RetType = retType;
RetSymbol = retSymbol;
}
public IMethodSymbol Method { get; }
public ImmutableArray<MarshalType> ParamTypes { get; }
public ImmutableArray<ITypeSymbol> ParamTypeSymbols { get; }
public MarshalType? RetType { get; }
public ITypeSymbol? RetSymbol { get; }
}
public struct GodotSignalDelegateData
{
public GodotSignalDelegateData(string name, INamedTypeSymbol delegateSymbol, GodotMethodData invokeMethodData)
{
Name = name;
DelegateSymbol = delegateSymbol;
InvokeMethodData = invokeMethodData;
}
public string Name { get; }
public INamedTypeSymbol DelegateSymbol { get; }
public GodotMethodData InvokeMethodData { get; }
}
public struct GodotPropertyData
{
public GodotPropertyData(IPropertySymbol propertySymbol, MarshalType type)
{
PropertySymbol = propertySymbol;
Type = type;
}
public IPropertySymbol PropertySymbol { get; }
public MarshalType Type { get; }
}
public struct GodotFieldData
{
public GodotFieldData(IFieldSymbol fieldSymbol, MarshalType type)
{
FieldSymbol = fieldSymbol;
Type = type;
}
public IFieldSymbol FieldSymbol { get; }
public MarshalType Type { get; }
}
}

View File

@ -0,0 +1,60 @@
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(EntryPoint = ""godotsharp_game_main_init"")]
private static godot_bool InitializeFromGameProject(IntPtr godotDllHandle, IntPtr outManagedCallbacks)
{
try
{
DllImportResolver dllImportResolver = new GodotDllImportResolver(godotDllHandle).OnResolveDllImport;
var coreApiAssembly = typeof(Godot.Object).Assembly;
NativeLibrary.SetDllImportResolver(coreApiAssembly, dllImportResolver);
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));
}
}
}

View File

@ -0,0 +1,73 @@
using System.Diagnostics.CodeAnalysis;
namespace Godot.SourceGenerators
{
[SuppressMessage("ReSharper", "InconsistentNaming")]
public enum MarshalType
{
Boolean,
Char,
SByte,
Int16,
Int32,
Int64,
Byte,
UInt16,
UInt32,
UInt64,
Single,
Double,
String,
// Godot structs
Vector2,
Vector2i,
Rect2,
Rect2i,
Transform2D,
Vector3,
Vector3i,
Basis,
Quaternion,
Transform3D,
Vector4,
Vector4i,
Projection,
AABB,
Color,
Plane,
Callable,
SignalInfo,
// Enums
Enum,
// Arrays
ByteArray,
Int32Array,
Int64Array,
Float32Array,
Float64Array,
StringArray,
Vector2Array,
Vector3Array,
ColorArray,
GodotObjectOrDerivedArray,
SystemArrayOfStringName,
SystemArrayOfNodePath,
SystemArrayOfRID,
// Variant
Variant,
// Classes
GodotObjectOrDerived,
StringName,
NodePath,
RID,
GodotDictionary,
GodotArray,
GodotGenericDictionary,
GodotGenericArray,
}
}

View File

@ -0,0 +1,678 @@
using System;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
namespace Godot.SourceGenerators
{
internal static class MarshalUtils
{
public class TypeCache
{
public INamedTypeSymbol GodotObjectType { get; }
public TypeCache(GeneratorExecutionContext context)
{
INamedTypeSymbol GetTypeByMetadataNameOrThrow(string fullyQualifiedMetadataName)
{
return context.Compilation.GetTypeByMetadataName(fullyQualifiedMetadataName) ??
throw new InvalidOperationException("Type not found: " + fullyQualifiedMetadataName);
}
GodotObjectType = GetTypeByMetadataNameOrThrow("Godot.Object");
}
}
public static VariantType? ConvertMarshalTypeToVariantType(MarshalType marshalType)
=> marshalType switch
{
MarshalType.Boolean => VariantType.Bool,
MarshalType.Char => VariantType.Int,
MarshalType.SByte => VariantType.Int,
MarshalType.Int16 => VariantType.Int,
MarshalType.Int32 => VariantType.Int,
MarshalType.Int64 => VariantType.Int,
MarshalType.Byte => VariantType.Int,
MarshalType.UInt16 => VariantType.Int,
MarshalType.UInt32 => VariantType.Int,
MarshalType.UInt64 => VariantType.Int,
MarshalType.Single => VariantType.Float,
MarshalType.Double => VariantType.Float,
MarshalType.String => VariantType.String,
MarshalType.Vector2 => VariantType.Vector2,
MarshalType.Vector2i => VariantType.Vector2i,
MarshalType.Rect2 => VariantType.Rect2,
MarshalType.Rect2i => VariantType.Rect2i,
MarshalType.Transform2D => VariantType.Transform2d,
MarshalType.Vector3 => VariantType.Vector3,
MarshalType.Vector3i => VariantType.Vector3i,
MarshalType.Basis => VariantType.Basis,
MarshalType.Quaternion => VariantType.Quaternion,
MarshalType.Transform3D => VariantType.Transform3d,
MarshalType.Vector4 => VariantType.Vector4,
MarshalType.Vector4i => VariantType.Vector4i,
MarshalType.Projection => VariantType.Projection,
MarshalType.AABB => VariantType.Aabb,
MarshalType.Color => VariantType.Color,
MarshalType.Plane => VariantType.Plane,
MarshalType.Callable => VariantType.Callable,
MarshalType.SignalInfo => VariantType.Signal,
MarshalType.Enum => VariantType.Int,
MarshalType.ByteArray => VariantType.PackedByteArray,
MarshalType.Int32Array => VariantType.PackedInt32Array,
MarshalType.Int64Array => VariantType.PackedInt64Array,
MarshalType.Float32Array => VariantType.PackedFloat32Array,
MarshalType.Float64Array => VariantType.PackedFloat64Array,
MarshalType.StringArray => VariantType.PackedStringArray,
MarshalType.Vector2Array => VariantType.PackedVector2Array,
MarshalType.Vector3Array => VariantType.PackedVector3Array,
MarshalType.ColorArray => VariantType.PackedColorArray,
MarshalType.GodotObjectOrDerivedArray => VariantType.Array,
MarshalType.SystemArrayOfStringName => VariantType.Array,
MarshalType.SystemArrayOfNodePath => VariantType.Array,
MarshalType.SystemArrayOfRID => VariantType.Array,
MarshalType.Variant => VariantType.Nil,
MarshalType.GodotObjectOrDerived => VariantType.Object,
MarshalType.StringName => VariantType.StringName,
MarshalType.NodePath => VariantType.NodePath,
MarshalType.RID => VariantType.Rid,
MarshalType.GodotDictionary => VariantType.Dictionary,
MarshalType.GodotArray => VariantType.Array,
MarshalType.GodotGenericDictionary => VariantType.Dictionary,
MarshalType.GodotGenericArray => VariantType.Array,
_ => null
};
public static MarshalType? ConvertManagedTypeToMarshalType(ITypeSymbol type, TypeCache typeCache)
{
var specialType = type.SpecialType;
switch (specialType)
{
case SpecialType.System_Boolean:
return MarshalType.Boolean;
case SpecialType.System_Char:
return MarshalType.Char;
case SpecialType.System_SByte:
return MarshalType.SByte;
case SpecialType.System_Int16:
return MarshalType.Int16;
case SpecialType.System_Int32:
return MarshalType.Int32;
case SpecialType.System_Int64:
return MarshalType.Int64;
case SpecialType.System_Byte:
return MarshalType.Byte;
case SpecialType.System_UInt16:
return MarshalType.UInt16;
case SpecialType.System_UInt32:
return MarshalType.UInt32;
case SpecialType.System_UInt64:
return MarshalType.UInt64;
case SpecialType.System_Single:
return MarshalType.Single;
case SpecialType.System_Double:
return MarshalType.Double;
case SpecialType.System_String:
return MarshalType.String;
default:
{
var typeKind = type.TypeKind;
if (typeKind == TypeKind.Enum)
return MarshalType.Enum;
if (typeKind == TypeKind.Struct)
{
if (type.ContainingAssembly.Name == "GodotSharp" &&
type.ContainingNamespace.Name == "Godot")
{
return type switch
{
{ Name: "Vector2" } => MarshalType.Vector2,
{ Name: "Vector2i" } => MarshalType.Vector2i,
{ Name: "Rect2" } => MarshalType.Rect2,
{ Name: "Rect2i" } => MarshalType.Rect2i,
{ Name: "Transform2D" } => MarshalType.Transform2D,
{ Name: "Vector3" } => MarshalType.Vector3,
{ Name: "Vector3i" } => MarshalType.Vector3i,
{ Name: "Basis" } => MarshalType.Basis,
{ Name: "Quaternion" } => MarshalType.Quaternion,
{ Name: "Transform3D" } => MarshalType.Transform3D,
{ Name: "Vector4" } => MarshalType.Vector4,
{ Name: "Vector4i" } => MarshalType.Vector4i,
{ Name: "Projection" } => MarshalType.Projection,
{ Name: "AABB" } => MarshalType.AABB,
{ Name: "Color" } => MarshalType.Color,
{ Name: "Plane" } => MarshalType.Plane,
{ Name: "RID" } => MarshalType.RID,
{ Name: "Callable" } => MarshalType.Callable,
{ Name: "SignalInfo" } => MarshalType.SignalInfo,
{ Name: "Variant" } => MarshalType.Variant,
_ => null
};
}
}
else if (typeKind == TypeKind.Array)
{
var arrayType = (IArrayTypeSymbol)type;
var elementType = arrayType.ElementType;
switch (elementType.SpecialType)
{
case SpecialType.System_Byte:
return MarshalType.ByteArray;
case SpecialType.System_Int32:
return MarshalType.Int32Array;
case SpecialType.System_Int64:
return MarshalType.Int64Array;
case SpecialType.System_Single:
return MarshalType.Float32Array;
case SpecialType.System_Double:
return MarshalType.Float64Array;
case SpecialType.System_String:
return MarshalType.StringArray;
}
if (elementType.SimpleDerivesFrom(typeCache.GodotObjectType))
return MarshalType.GodotObjectOrDerivedArray;
if (elementType.ContainingAssembly.Name == "GodotSharp" &&
elementType.ContainingNamespace.Name == "Godot")
{
switch (elementType)
{
case { Name: "Vector2" }:
return MarshalType.Vector2Array;
case { Name: "Vector3" }:
return MarshalType.Vector3Array;
case { Name: "Color" }:
return MarshalType.ColorArray;
case { Name: "StringName" }:
return MarshalType.SystemArrayOfStringName;
case { Name: "NodePath" }:
return MarshalType.SystemArrayOfNodePath;
case { Name: "RID" }:
return MarshalType.SystemArrayOfRID;
}
}
return null;
}
else
{
if (type.SimpleDerivesFrom(typeCache.GodotObjectType))
return MarshalType.GodotObjectOrDerived;
if (type.ContainingAssembly.Name == "GodotSharp")
{
switch (type.ContainingNamespace.Name)
{
case "Godot":
return type switch
{
{ Name: "StringName" } => MarshalType.StringName,
{ Name: "NodePath" } => MarshalType.NodePath,
_ => null
};
case "Collections"
when type.ContainingNamespace.FullQualifiedName() == "Godot.Collections":
return type switch
{
{ Name: "Dictionary" } =>
type is INamedTypeSymbol { IsGenericType: false } ?
MarshalType.GodotDictionary :
MarshalType.GodotGenericDictionary,
{ Name: "Array" } =>
type is INamedTypeSymbol { IsGenericType: false } ?
MarshalType.GodotArray :
MarshalType.GodotGenericArray,
_ => null
};
}
}
}
break;
}
}
return null;
}
private static bool SimpleDerivesFrom(this ITypeSymbol? type, ITypeSymbol candidateBaseType)
{
while (type != null)
{
if (SymbolEqualityComparer.Default.Equals(type, candidateBaseType))
return true;
type = type.BaseType;
}
return false;
}
public static ITypeSymbol? GetArrayElementType(ITypeSymbol typeSymbol)
{
if (typeSymbol.TypeKind == TypeKind.Array)
{
var arrayType = (IArrayTypeSymbol)typeSymbol;
return arrayType.ElementType;
}
if (typeSymbol is INamedTypeSymbol { IsGenericType: true } genericType)
return genericType.TypeArguments.FirstOrDefault();
return null;
}
private static StringBuilder Append(this StringBuilder source, string a, string b)
=> source.Append(a).Append(b);
private static StringBuilder Append(this StringBuilder source, string a, string b, string c)
=> source.Append(a).Append(b).Append(c);
private static StringBuilder Append(this StringBuilder source, string a, string b,
string c, string d)
=> source.Append(a).Append(b).Append(c).Append(d);
private static StringBuilder Append(this StringBuilder source, string a, string b,
string c, string d, string e)
=> source.Append(a).Append(b).Append(c).Append(d).Append(e);
private static StringBuilder Append(this StringBuilder source, string a, string b,
string c, string d, string e, string f)
=> source.Append(a).Append(b).Append(c).Append(d).Append(e).Append(f);
private static StringBuilder Append(this StringBuilder source, string a, string b,
string c, string d, string e, string f, string g)
=> source.Append(a).Append(b).Append(c).Append(d).Append(e).Append(f).Append(g);
private static StringBuilder Append(this StringBuilder source, string a, string b,
string c, string d, string e, string f, string g, string h)
=> source.Append(a).Append(b).Append(c).Append(d).Append(e).Append(f).Append(g).Append(h);
private const string VariantUtils = "global::Godot.NativeInterop.VariantUtils";
public static StringBuilder AppendNativeVariantToManagedExpr(this StringBuilder source,
string inputExpr, ITypeSymbol typeSymbol, MarshalType marshalType)
{
return marshalType switch
{
MarshalType.Boolean =>
source.Append(VariantUtils, ".ConvertToBool(", inputExpr, ")"),
MarshalType.Char =>
source.Append("(char)", VariantUtils, ".ConvertToUInt16(", inputExpr, ")"),
MarshalType.SByte =>
source.Append(VariantUtils, ".ConvertToInt8(", inputExpr, ")"),
MarshalType.Int16 =>
source.Append(VariantUtils, ".ConvertToInt16(", inputExpr, ")"),
MarshalType.Int32 =>
source.Append(VariantUtils, ".ConvertToInt32(", inputExpr, ")"),
MarshalType.Int64 =>
source.Append(VariantUtils, ".ConvertToInt64(", inputExpr, ")"),
MarshalType.Byte =>
source.Append(VariantUtils, ".ConvertToUInt8(", inputExpr, ")"),
MarshalType.UInt16 =>
source.Append(VariantUtils, ".ConvertToUInt16(", inputExpr, ")"),
MarshalType.UInt32 =>
source.Append(VariantUtils, ".ConvertToUInt32(", inputExpr, ")"),
MarshalType.UInt64 =>
source.Append(VariantUtils, ".ConvertToUInt64(", inputExpr, ")"),
MarshalType.Single =>
source.Append(VariantUtils, ".ConvertToFloat32(", inputExpr, ")"),
MarshalType.Double =>
source.Append(VariantUtils, ".ConvertToFloat64(", inputExpr, ")"),
MarshalType.String =>
source.Append(VariantUtils, ".ConvertToStringObject(", inputExpr, ")"),
MarshalType.Vector2 =>
source.Append(VariantUtils, ".ConvertToVector2(", inputExpr, ")"),
MarshalType.Vector2i =>
source.Append(VariantUtils, ".ConvertToVector2i(", inputExpr, ")"),
MarshalType.Rect2 =>
source.Append(VariantUtils, ".ConvertToRect2(", inputExpr, ")"),
MarshalType.Rect2i =>
source.Append(VariantUtils, ".ConvertToRect2i(", inputExpr, ")"),
MarshalType.Transform2D =>
source.Append(VariantUtils, ".ConvertToTransform2D(", inputExpr, ")"),
MarshalType.Vector3 =>
source.Append(VariantUtils, ".ConvertToVector3(", inputExpr, ")"),
MarshalType.Vector3i =>
source.Append(VariantUtils, ".ConvertToVector3i(", inputExpr, ")"),
MarshalType.Basis =>
source.Append(VariantUtils, ".ConvertToBasis(", inputExpr, ")"),
MarshalType.Quaternion =>
source.Append(VariantUtils, ".ConvertToQuaternion(", inputExpr, ")"),
MarshalType.Transform3D =>
source.Append(VariantUtils, ".ConvertToTransform3D(", inputExpr, ")"),
MarshalType.Vector4 =>
source.Append(VariantUtils, ".ConvertToVector4(", inputExpr, ")"),
MarshalType.Vector4i =>
source.Append(VariantUtils, ".ConvertToVector4i(", inputExpr, ")"),
MarshalType.Projection =>
source.Append(VariantUtils, ".ConvertToProjection(", inputExpr, ")"),
MarshalType.AABB =>
source.Append(VariantUtils, ".ConvertToAABB(", inputExpr, ")"),
MarshalType.Color =>
source.Append(VariantUtils, ".ConvertToColor(", inputExpr, ")"),
MarshalType.Plane =>
source.Append(VariantUtils, ".ConvertToPlane(", inputExpr, ")"),
MarshalType.Callable =>
source.Append(VariantUtils, ".ConvertToCallableManaged(", inputExpr, ")"),
MarshalType.SignalInfo =>
source.Append(VariantUtils, ".ConvertToSignalInfo(", inputExpr, ")"),
MarshalType.Enum =>
source.Append("(", typeSymbol.FullQualifiedName(),
")", VariantUtils, ".ConvertToInt32(", inputExpr, ")"),
MarshalType.ByteArray =>
source.Append(VariantUtils, ".ConvertAsPackedByteArrayToSystemArray(", inputExpr, ")"),
MarshalType.Int32Array =>
source.Append(VariantUtils, ".ConvertAsPackedInt32ArrayToSystemArray(", inputExpr, ")"),
MarshalType.Int64Array =>
source.Append(VariantUtils, ".ConvertAsPackedInt64ArrayToSystemArray(", inputExpr, ")"),
MarshalType.Float32Array =>
source.Append(VariantUtils, ".ConvertAsPackedFloat32ArrayToSystemArray(", inputExpr, ")"),
MarshalType.Float64Array =>
source.Append(VariantUtils, ".ConvertAsPackedFloat64ArrayToSystemArray(", inputExpr, ")"),
MarshalType.StringArray =>
source.Append(VariantUtils, ".ConvertAsPackedStringArrayToSystemArray(", inputExpr, ")"),
MarshalType.Vector2Array =>
source.Append(VariantUtils, ".ConvertAsPackedVector2ArrayToSystemArray(", inputExpr, ")"),
MarshalType.Vector3Array =>
source.Append(VariantUtils, ".ConvertAsPackedVector3ArrayToSystemArray(", inputExpr, ")"),
MarshalType.ColorArray =>
source.Append(VariantUtils, ".ConvertAsPackedColorArrayToSystemArray(", inputExpr, ")"),
MarshalType.GodotObjectOrDerivedArray =>
source.Append(VariantUtils, ".ConvertToSystemArrayOfGodotObject<",
((IArrayTypeSymbol)typeSymbol).ElementType.FullQualifiedName(), ">(", inputExpr, ")"),
MarshalType.SystemArrayOfStringName =>
source.Append(VariantUtils, ".ConvertToSystemArrayOfStringName(", inputExpr, ")"),
MarshalType.SystemArrayOfNodePath =>
source.Append(VariantUtils, ".ConvertToSystemArrayOfNodePath(", inputExpr, ")"),
MarshalType.SystemArrayOfRID =>
source.Append(VariantUtils, ".ConvertToSystemArrayOfRID(", inputExpr, ")"),
MarshalType.Variant =>
source.Append("global::Godot.Variant.CreateCopyingBorrowed(", inputExpr, ")"),
MarshalType.GodotObjectOrDerived =>
source.Append("(", typeSymbol.FullQualifiedName(),
")", VariantUtils, ".ConvertToGodotObject(", inputExpr, ")"),
MarshalType.StringName =>
source.Append(VariantUtils, ".ConvertToStringNameObject(", inputExpr, ")"),
MarshalType.NodePath =>
source.Append(VariantUtils, ".ConvertToNodePathObject(", inputExpr, ")"),
MarshalType.RID =>
source.Append(VariantUtils, ".ConvertToRID(", inputExpr, ")"),
MarshalType.GodotDictionary =>
source.Append(VariantUtils, ".ConvertToDictionaryObject(", inputExpr, ")"),
MarshalType.GodotArray =>
source.Append(VariantUtils, ".ConvertToArrayObject(", inputExpr, ")"),
MarshalType.GodotGenericDictionary =>
source.Append(VariantUtils, ".ConvertToDictionaryObject<",
((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedName(), ", ",
((INamedTypeSymbol)typeSymbol).TypeArguments[1].FullQualifiedName(), ">(", inputExpr, ")"),
MarshalType.GodotGenericArray =>
source.Append(VariantUtils, ".ConvertToArrayObject<",
((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedName(), ">(", inputExpr, ")"),
_ => throw new ArgumentOutOfRangeException(nameof(marshalType), marshalType,
"Received unexpected marshal type")
};
}
public static StringBuilder AppendManagedToNativeVariantExpr(
this StringBuilder source, string inputExpr, MarshalType marshalType)
{
return marshalType switch
{
MarshalType.Boolean =>
source.Append(VariantUtils, ".CreateFromBool(", inputExpr, ")"),
MarshalType.Char =>
source.Append(VariantUtils, ".CreateFromInt((ushort)", inputExpr, ")"),
MarshalType.SByte =>
source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"),
MarshalType.Int16 =>
source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"),
MarshalType.Int32 =>
source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"),
MarshalType.Int64 =>
source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"),
MarshalType.Byte =>
source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"),
MarshalType.UInt16 =>
source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"),
MarshalType.UInt32 =>
source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"),
MarshalType.UInt64 =>
source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"),
MarshalType.Single =>
source.Append(VariantUtils, ".CreateFromFloat(", inputExpr, ")"),
MarshalType.Double =>
source.Append(VariantUtils, ".CreateFromFloat(", inputExpr, ")"),
MarshalType.String =>
source.Append(VariantUtils, ".CreateFromString(", inputExpr, ")"),
MarshalType.Vector2 =>
source.Append(VariantUtils, ".CreateFromVector2(", inputExpr, ")"),
MarshalType.Vector2i =>
source.Append(VariantUtils, ".CreateFromVector2i(", inputExpr, ")"),
MarshalType.Rect2 =>
source.Append(VariantUtils, ".CreateFromRect2(", inputExpr, ")"),
MarshalType.Rect2i =>
source.Append(VariantUtils, ".CreateFromRect2i(", inputExpr, ")"),
MarshalType.Transform2D =>
source.Append(VariantUtils, ".CreateFromTransform2D(", inputExpr, ")"),
MarshalType.Vector3 =>
source.Append(VariantUtils, ".CreateFromVector3(", inputExpr, ")"),
MarshalType.Vector3i =>
source.Append(VariantUtils, ".CreateFromVector3i(", inputExpr, ")"),
MarshalType.Basis =>
source.Append(VariantUtils, ".CreateFromBasis(", inputExpr, ")"),
MarshalType.Quaternion =>
source.Append(VariantUtils, ".CreateFromQuaternion(", inputExpr, ")"),
MarshalType.Transform3D =>
source.Append(VariantUtils, ".CreateFromTransform3D(", inputExpr, ")"),
MarshalType.Vector4 =>
source.Append(VariantUtils, ".CreateFromVector4(", inputExpr, ")"),
MarshalType.Vector4i =>
source.Append(VariantUtils, ".CreateFromVector4i(", inputExpr, ")"),
MarshalType.Projection =>
source.Append(VariantUtils, ".CreateFromProjection(", inputExpr, ")"),
MarshalType.AABB =>
source.Append(VariantUtils, ".CreateFromAABB(", inputExpr, ")"),
MarshalType.Color =>
source.Append(VariantUtils, ".CreateFromColor(", inputExpr, ")"),
MarshalType.Plane =>
source.Append(VariantUtils, ".CreateFromPlane(", inputExpr, ")"),
MarshalType.Callable =>
source.Append(VariantUtils, ".CreateFromCallable(", inputExpr, ")"),
MarshalType.SignalInfo =>
source.Append(VariantUtils, ".CreateFromSignalInfo(", inputExpr, ")"),
MarshalType.Enum =>
source.Append(VariantUtils, ".CreateFromInt((int)", inputExpr, ")"),
MarshalType.ByteArray =>
source.Append(VariantUtils, ".CreateFromPackedByteArray(", inputExpr, ")"),
MarshalType.Int32Array =>
source.Append(VariantUtils, ".CreateFromPackedInt32Array(", inputExpr, ")"),
MarshalType.Int64Array =>
source.Append(VariantUtils, ".CreateFromPackedInt64Array(", inputExpr, ")"),
MarshalType.Float32Array =>
source.Append(VariantUtils, ".CreateFromPackedFloat32Array(", inputExpr, ")"),
MarshalType.Float64Array =>
source.Append(VariantUtils, ".CreateFromPackedFloat64Array(", inputExpr, ")"),
MarshalType.StringArray =>
source.Append(VariantUtils, ".CreateFromPackedStringArray(", inputExpr, ")"),
MarshalType.Vector2Array =>
source.Append(VariantUtils, ".CreateFromPackedVector2Array(", inputExpr, ")"),
MarshalType.Vector3Array =>
source.Append(VariantUtils, ".CreateFromPackedVector3Array(", inputExpr, ")"),
MarshalType.ColorArray =>
source.Append(VariantUtils, ".CreateFromPackedColorArray(", inputExpr, ")"),
MarshalType.GodotObjectOrDerivedArray =>
source.Append(VariantUtils, ".CreateFromSystemArrayOfGodotObject(", inputExpr, ")"),
MarshalType.SystemArrayOfStringName =>
source.Append(VariantUtils, ".CreateFromSystemArrayOfStringName(", inputExpr, ")"),
MarshalType.SystemArrayOfNodePath =>
source.Append(VariantUtils, ".CreateFromSystemArrayOfNodePath(", inputExpr, ")"),
MarshalType.SystemArrayOfRID =>
source.Append(VariantUtils, ".CreateFromSystemArrayOfRID(", inputExpr, ")"),
MarshalType.Variant =>
source.Append(inputExpr, ".CopyNativeVariant()"),
MarshalType.GodotObjectOrDerived =>
source.Append(VariantUtils, ".CreateFromGodotObject(", inputExpr, ")"),
MarshalType.StringName =>
source.Append(VariantUtils, ".CreateFromStringName(", inputExpr, ")"),
MarshalType.NodePath =>
source.Append(VariantUtils, ".CreateFromNodePath(", inputExpr, ")"),
MarshalType.RID =>
source.Append(VariantUtils, ".CreateFromRID(", inputExpr, ")"),
MarshalType.GodotDictionary =>
source.Append(VariantUtils, ".CreateFromDictionary(", inputExpr, ")"),
MarshalType.GodotArray =>
source.Append(VariantUtils, ".CreateFromArray(", inputExpr, ")"),
MarshalType.GodotGenericDictionary =>
source.Append(VariantUtils, ".CreateFromDictionary(", inputExpr, ")"),
MarshalType.GodotGenericArray =>
source.Append(VariantUtils, ".CreateFromArray(", inputExpr, ")"),
_ => throw new ArgumentOutOfRangeException(nameof(marshalType), marshalType,
"Received unexpected marshal type")
};
}
public static StringBuilder AppendVariantToManagedExpr(this StringBuilder source,
string inputExpr, ITypeSymbol typeSymbol, MarshalType marshalType)
{
return marshalType switch
{
MarshalType.Boolean => source.Append(inputExpr, ".AsBool()"),
MarshalType.Char => source.Append(inputExpr, ".AsChar()"),
MarshalType.SByte => source.Append(inputExpr, ".AsSByte()"),
MarshalType.Int16 => source.Append(inputExpr, ".AsInt16()"),
MarshalType.Int32 => source.Append(inputExpr, ".AsInt32()"),
MarshalType.Int64 => source.Append(inputExpr, ".AsInt64()"),
MarshalType.Byte => source.Append(inputExpr, ".AsByte()"),
MarshalType.UInt16 => source.Append(inputExpr, ".AsUInt16()"),
MarshalType.UInt32 => source.Append(inputExpr, ".AsUInt32()"),
MarshalType.UInt64 => source.Append(inputExpr, ".AsUInt64()"),
MarshalType.Single => source.Append(inputExpr, ".AsSingle()"),
MarshalType.Double => source.Append(inputExpr, ".AsDouble()"),
MarshalType.String => source.Append(inputExpr, ".AsString()"),
MarshalType.Vector2 => source.Append(inputExpr, ".AsVector2()"),
MarshalType.Vector2i => source.Append(inputExpr, ".AsVector2i()"),
MarshalType.Rect2 => source.Append(inputExpr, ".AsRect2()"),
MarshalType.Rect2i => source.Append(inputExpr, ".AsRect2i()"),
MarshalType.Transform2D => source.Append(inputExpr, ".AsTransform2D()"),
MarshalType.Vector3 => source.Append(inputExpr, ".AsVector3()"),
MarshalType.Vector3i => source.Append(inputExpr, ".AsVector3i()"),
MarshalType.Basis => source.Append(inputExpr, ".AsBasis()"),
MarshalType.Quaternion => source.Append(inputExpr, ".AsQuaternion()"),
MarshalType.Transform3D => source.Append(inputExpr, ".AsTransform3D()"),
MarshalType.Vector4 => source.Append(inputExpr, ".AsVector4()"),
MarshalType.Vector4i => source.Append(inputExpr, ".AsVector4i()"),
MarshalType.Projection => source.Append(inputExpr, ".AsProjection()"),
MarshalType.AABB => source.Append(inputExpr, ".AsAABB()"),
MarshalType.Color => source.Append(inputExpr, ".AsColor()"),
MarshalType.Plane => source.Append(inputExpr, ".AsPlane()"),
MarshalType.Callable => source.Append(inputExpr, ".AsCallable()"),
MarshalType.SignalInfo => source.Append(inputExpr, ".AsSignalInfo()"),
MarshalType.Enum =>
source.Append("(", typeSymbol.FullQualifiedName(), ")", inputExpr, ".AsInt64()"),
MarshalType.ByteArray => source.Append(inputExpr, ".AsByteArray()"),
MarshalType.Int32Array => source.Append(inputExpr, ".AsInt32Array()"),
MarshalType.Int64Array => source.Append(inputExpr, ".AsInt64Array()"),
MarshalType.Float32Array => source.Append(inputExpr, ".AsFloat32Array()"),
MarshalType.Float64Array => source.Append(inputExpr, ".AsFloat64Array()"),
MarshalType.StringArray => source.Append(inputExpr, ".AsStringArray()"),
MarshalType.Vector2Array => source.Append(inputExpr, ".AsVector2Array()"),
MarshalType.Vector3Array => source.Append(inputExpr, ".AsVector3Array()"),
MarshalType.ColorArray => source.Append(inputExpr, ".AsColorArray()"),
MarshalType.GodotObjectOrDerivedArray => source.Append(inputExpr, ".AsGodotObjectArray<",
((IArrayTypeSymbol)typeSymbol).ElementType.FullQualifiedName(), ">()"),
MarshalType.SystemArrayOfStringName => source.Append(inputExpr, ".AsSystemArrayOfStringName()"),
MarshalType.SystemArrayOfNodePath => source.Append(inputExpr, ".AsSystemArrayOfNodePath()"),
MarshalType.SystemArrayOfRID => source.Append(inputExpr, ".AsSystemArrayOfRID()"),
MarshalType.Variant => source.Append(inputExpr),
MarshalType.GodotObjectOrDerived => source.Append("(",
typeSymbol.FullQualifiedName(), ")", inputExpr, ".AsGodotObject()"),
MarshalType.StringName => source.Append(inputExpr, ".AsStringName()"),
MarshalType.NodePath => source.Append(inputExpr, ".AsNodePath()"),
MarshalType.RID => source.Append(inputExpr, ".AsRID()"),
MarshalType.GodotDictionary => source.Append(inputExpr, ".AsGodotDictionary()"),
MarshalType.GodotArray => source.Append(inputExpr, ".AsGodotArray()"),
MarshalType.GodotGenericDictionary => source.Append(inputExpr, ".AsGodotDictionary<",
((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedName(), ", ",
((INamedTypeSymbol)typeSymbol).TypeArguments[1].FullQualifiedName(), ">()"),
MarshalType.GodotGenericArray => source.Append(inputExpr, ".AsGodotArray<",
((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedName(), ">()"),
_ => throw new ArgumentOutOfRangeException(nameof(marshalType), marshalType,
"Received unexpected marshal type")
};
}
public static StringBuilder AppendManagedToVariantExpr(this StringBuilder source,
string inputExpr, MarshalType marshalType)
{
switch (marshalType)
{
case MarshalType.Boolean:
case MarshalType.Char:
case MarshalType.SByte:
case MarshalType.Int16:
case MarshalType.Int32:
case MarshalType.Int64:
case MarshalType.Byte:
case MarshalType.UInt16:
case MarshalType.UInt32:
case MarshalType.UInt64:
case MarshalType.Single:
case MarshalType.Double:
case MarshalType.String:
case MarshalType.Vector2:
case MarshalType.Vector2i:
case MarshalType.Rect2:
case MarshalType.Rect2i:
case MarshalType.Transform2D:
case MarshalType.Vector3:
case MarshalType.Vector3i:
case MarshalType.Basis:
case MarshalType.Quaternion:
case MarshalType.Transform3D:
case MarshalType.Vector4:
case MarshalType.Vector4i:
case MarshalType.Projection:
case MarshalType.AABB:
case MarshalType.Color:
case MarshalType.Plane:
case MarshalType.Callable:
case MarshalType.SignalInfo:
case MarshalType.ByteArray:
case MarshalType.Int32Array:
case MarshalType.Int64Array:
case MarshalType.Float32Array:
case MarshalType.Float64Array:
case MarshalType.StringArray:
case MarshalType.Vector2Array:
case MarshalType.Vector3Array:
case MarshalType.ColorArray:
case MarshalType.GodotObjectOrDerivedArray:
case MarshalType.SystemArrayOfStringName:
case MarshalType.SystemArrayOfNodePath:
case MarshalType.SystemArrayOfRID:
case MarshalType.GodotObjectOrDerived:
case MarshalType.StringName:
case MarshalType.NodePath:
case MarshalType.RID:
case MarshalType.GodotDictionary:
case MarshalType.GodotArray:
case MarshalType.GodotGenericDictionary:
case MarshalType.GodotGenericArray:
return source.Append("Variant.CreateFrom(", inputExpr, ")");
case MarshalType.Enum:
return source.Append("Variant.CreateFrom((long)", inputExpr, ")");
case MarshalType.Variant:
return source.Append(inputExpr);
default:
throw new ArgumentOutOfRangeException(nameof(marshalType), marshalType,
"Received unexpected marshal type");
}
}
}
}

View File

@ -0,0 +1,24 @@
using System.Collections.Generic;
namespace Godot.SourceGenerators
{
internal struct MethodInfo
{
public MethodInfo(string name, PropertyInfo returnVal, MethodFlags flags,
List<PropertyInfo>? arguments,
List<string?>? defaultArguments)
{
Name = name;
ReturnVal = returnVal;
Flags = flags;
Arguments = arguments;
DefaultArguments = defaultArguments;
}
public string Name { get; }
public PropertyInfo ReturnVal { get; }
public MethodFlags Flags { get; }
public List<PropertyInfo>? Arguments { get; }
public List<string?>? DefaultArguments { get; }
}
}

View File

@ -0,0 +1,23 @@
namespace Godot.SourceGenerators
{
internal struct PropertyInfo
{
public PropertyInfo(VariantType type, string name, PropertyHint hint,
string? hintString, PropertyUsageFlags usage, bool exported)
{
Type = type;
Name = name;
Hint = hint;
HintString = hintString;
Usage = usage;
Exported = exported;
}
public VariantType Type { get; }
public string Name { get; }
public PropertyHint Hint { get; }
public string? HintString { get; }
public PropertyUsageFlags Usage { get; }
public bool Exported { get; }
}
}

View File

@ -0,0 +1,408 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
namespace Godot.SourceGenerators
{
[Generator]
public class ScriptMethodsGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context)
{
}
public void Execute(GeneratorExecutionContext context)
{
if (context.AreGodotSourceGeneratorsDisabled())
return;
INamedTypeSymbol[] godotClasses = context
.Compilation.SyntaxTrees
.SelectMany(tree =>
tree.GetRoot().DescendantNodes()
.OfType<ClassDeclarationSyntax>()
.SelectGodotScriptClasses(context.Compilation)
// Report and skip non-partial classes
.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;
})
.Select(x => x.symbol)
)
.Distinct<INamedTypeSymbol>(SymbolEqualityComparer.Default)
.ToArray();
if (godotClasses.Length > 0)
{
var typeCache = new MarshalUtils.TypeCache(context);
foreach (var godotClass in godotClasses)
{
VisitGodotScriptClass(context, typeCache, godotClass);
}
}
}
private class MethodOverloadEqualityComparer : IEqualityComparer<GodotMethodData>
{
public bool Equals(GodotMethodData x, GodotMethodData y)
=> x.ParamTypes.Length == y.ParamTypes.Length && x.Method.Name == y.Method.Name;
public int GetHashCode(GodotMethodData obj)
{
unchecked
{
return (obj.ParamTypes.Length.GetHashCode() * 397) ^ obj.Method.Name.GetHashCode();
}
}
}
private static void VisitGodotScriptClass(
GeneratorExecutionContext context,
MarshalUtils.TypeCache typeCache,
INamedTypeSymbol symbol
)
{
INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace;
string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ?
namespaceSymbol.FullQualifiedName() :
string.Empty;
bool hasNamespace = classNs.Length != 0;
bool isInnerClass = symbol.ContainingType != null;
string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()
+ "_ScriptMethods_Generated";
var source = new StringBuilder();
source.Append("using Godot;\n");
source.Append("using Godot.NativeInterop;\n");
source.Append("\n");
if (hasNamespace)
{
source.Append("namespace ");
source.Append(classNs);
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.NameWithTypeParameters());
source.Append("\n{\n");
containingType = containingType.ContainingType;
}
}
source.Append("partial class ");
source.Append(symbol.NameWithTypeParameters());
source.Append("\n{\n");
var members = symbol.GetMembers();
var methodSymbols = members
.Where(s => !s.IsStatic && s.Kind == SymbolKind.Method && !s.IsImplicitlyDeclared)
.Cast<IMethodSymbol>()
.Where(m => m.MethodKind == MethodKind.Ordinary);
var godotClassMethods = methodSymbols.WhereHasGodotCompatibleSignature(typeCache)
.Distinct(new MethodOverloadEqualityComparer())
.ToArray();
source.Append(" private partial class GodotInternal {\n");
// Generate cached StringNames for methods and properties, for fast lookup
var distinctMethodNames = godotClassMethods
.Select(m => m.Method.Name)
.Distinct()
.ToArray();
foreach (string methodName in distinctMethodNames)
{
source.Append(" public static readonly StringName MethodName_");
source.Append(methodName);
source.Append(" = \"");
source.Append(methodName);
source.Append("\";\n");
}
source.Append(" }\n"); // class GodotInternal
// Generate GetGodotMethodList
if (godotClassMethods.Length > 0)
{
source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n");
const string listType = "System.Collections.Generic.List<global::Godot.Bridge.MethodInfo>";
source.Append(" internal new static ")
.Append(listType)
.Append(" GetGodotMethodList()\n {\n");
source.Append(" var methods = new ")
.Append(listType)
.Append("(")
.Append(godotClassMethods.Length)
.Append(");\n");
foreach (var method in godotClassMethods)
{
var methodInfo = DetermineMethodInfo(method);
AppendMethodInfo(source, methodInfo);
}
source.Append(" return methods;\n");
source.Append(" }\n");
source.Append("#pragma warning restore CS0109\n");
}
// Generate InvokeGodotClassMethod
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 godotClassMethods)
{
GenerateMethodInvoker(method, source);
}
source.Append(" return base.InvokeGodotClassMethod(method, args, argCount, out ret);\n");
source.Append(" }\n");
}
// Generate HasGodotClassMethod
if (distinctMethodNames.Length > 0)
{
source.Append(" protected override bool HasGodotClassMethod(in godot_string_name method)\n {\n");
bool isFirstEntry = true;
foreach (string methodName in distinctMethodNames)
{
GenerateHasMethodEntry(methodName, 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)
{
source.Append("\n}\n");
}
context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8));
}
private static void AppendMethodInfo(StringBuilder source, MethodInfo methodInfo)
{
source.Append(" methods.Add(new(name: GodotInternal.MethodName_")
.Append(methodInfo.Name)
.Append(", returnVal: ");
AppendPropertyInfo(source, methodInfo.ReturnVal);
source.Append(", flags: (Godot.MethodFlags)")
.Append((int)methodInfo.Flags)
.Append(", arguments: ");
if (methodInfo.Arguments is { Count: > 0 })
{
source.Append("new() { ");
foreach (var param in methodInfo.Arguments)
{
AppendPropertyInfo(source, param);
// C# allows colon after the last element
source.Append(", ");
}
source.Append(" }");
}
else
{
source.Append("null");
}
source.Append(", defaultArguments: null));\n");
}
private static void AppendPropertyInfo(StringBuilder source, PropertyInfo propertyInfo)
{
source.Append("new(type: (Godot.Variant.Type)")
.Append((int)propertyInfo.Type)
.Append(", name: \"")
.Append(propertyInfo.Name)
.Append("\", hint: (Godot.PropertyHint)")
.Append((int)propertyInfo.Hint)
.Append(", hintString: \"")
.Append(propertyInfo.HintString)
.Append("\", usage: (Godot.PropertyUsageFlags)")
.Append((int)propertyInfo.Usage)
.Append(", exported: ")
.Append(propertyInfo.Exported ? "true" : "false")
.Append(")");
}
private static MethodInfo DetermineMethodInfo(GodotMethodData method)
{
PropertyInfo returnVal;
if (method.RetType != null)
{
returnVal = DeterminePropertyInfo(method.RetType.Value, name: string.Empty);
}
else
{
returnVal = new PropertyInfo(VariantType.Nil, string.Empty, PropertyHint.None,
hintString: null, PropertyUsageFlags.Default, exported: false);
}
int paramCount = method.ParamTypes.Length;
List<PropertyInfo>? arguments;
if (paramCount > 0)
{
arguments = new(capacity: paramCount);
for (int i = 0; i < paramCount; i++)
{
arguments.Add(DeterminePropertyInfo(method.ParamTypes[i],
name: method.Method.Parameters[i].Name));
}
}
else
{
arguments = null;
}
return new MethodInfo(method.Method.Name, returnVal, MethodFlags.Default, arguments,
defaultArguments: null);
}
private static PropertyInfo DeterminePropertyInfo(MarshalType marshalType, string name)
{
var memberVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(marshalType)!.Value;
var propUsage = PropertyUsageFlags.Default;
if (memberVariantType == VariantType.Nil)
propUsage |= PropertyUsageFlags.NilIsVariant;
return new PropertyInfo(memberVariantType, name,
PropertyHint.None, string.Empty, propUsage, exported: false);
}
private static void GenerateHasMethodEntry(
string methodName,
StringBuilder source,
bool isFirstEntry
)
{
source.Append(" ");
if (!isFirstEntry)
source.Append("else ");
source.Append("if (method == GodotInternal.MethodName_");
source.Append(methodName);
source.Append(") {\n return true;\n }\n");
}
private static void GenerateMethodInvoker(
GodotMethodData method,
StringBuilder source
)
{
string methodName = method.Method.Name;
source.Append(" if (method == GodotInternal.MethodName_");
source.Append(methodName);
source.Append(" && argCount == ");
source.Append(method.ParamTypes.Length);
source.Append(") {\n");
if (method.RetType != null)
source.Append(" var callRet = ");
else
source.Append(" ");
source.Append(methodName);
source.Append("(");
for (int i = 0; i < method.ParamTypes.Length; i++)
{
if (i != 0)
source.Append(", ");
source.AppendNativeVariantToManagedExpr(string.Concat("args[", i.ToString(), "]"),
method.ParamTypeSymbols[i], method.ParamTypes[i]);
}
source.Append(");\n");
if (method.RetType != null)
{
source.Append(" ret = ");
source.AppendManagedToNativeVariantExpr("callRet", method.RetType.Value);
source.Append(";\n");
source.Append(" return true;\n");
}
else
{
source.Append(" ret = default;\n");
source.Append(" return true;\n");
}
source.Append(" }\n");
}
}
}

View File

@ -14,13 +14,13 @@ namespace Godot.SourceGenerators
{
public void Execute(GeneratorExecutionContext context)
{
if (context.TryGetGlobalAnalyzerProperty("GodotScriptPathAttributeGenerator", out string? toggle)
&& toggle == "disabled")
{
if (context.AreGodotSourceGeneratorsDisabled())
return;
}
// NOTE: IsNullOrEmpty doesn't work well with nullable checks
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)
|| godotProjectDir!.Length == 0)
@ -28,17 +28,18 @@ namespace Godot.SourceGenerators
throw new InvalidOperationException("Property 'GodotProjectDir' is null or empty.");
}
var godotClasses = context.Compilation.SyntaxTrees
Dictionary<INamedTypeSymbol, IEnumerable<ClassDeclarationSyntax>> godotClasses = context
.Compilation.SyntaxTrees
.SelectMany(tree =>
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 =>
{
if (x.cds.IsPartial() || x.symbol.HasDisableGeneratorsAttribute())
if (x.cds.IsPartial())
return true;
Common.ReportNonPartialGodotScriptClass(context, x.cds, x.symbol);
return false;
@ -89,21 +90,14 @@ namespace Godot.SourceGenerators
attributes.Append(@""")]");
}
string className = symbol.Name;
INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace;
string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ?
namespaceSymbol.FullQualifiedName() :
string.Empty;
bool hasNamespace = classNs.Length != 0;
var uniqueName = new StringBuilder();
if (hasNamespace)
uniqueName.Append($"{classNs}.");
uniqueName.Append(className);
if (symbol.IsGenericType)
uniqueName.Append($"Of{string.Join(string.Empty, symbol.TypeParameters)}");
uniqueName.Append("_ScriptPath_Generated");
var uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()
+ "_ScriptPath_Generated";
var source = new StringBuilder();
@ -123,10 +117,8 @@ namespace Godot.SourceGenerators
}
source.Append(attributes);
source.Append("\n partial class ");
source.Append(className);
if (symbol.IsGenericType)
source.Append($"<{string.Join(", ", symbol.TypeParameters)}>");
source.Append("\npartial class ");
source.Append(symbol.NameWithTypeParameters());
source.Append("\n{\n}\n");
if (hasNamespace)
@ -134,7 +126,7 @@ namespace Godot.SourceGenerators
source.Append("\n}\n");
}
context.AddSource(uniqueName.ToString(), SourceText.From(source.ToString(), Encoding.UTF8));
context.AddSource(uniqueHint.ToString(), SourceText.From(source.ToString(), Encoding.UTF8));
}
private static void AddScriptTypesAssemblyAttr(GeneratorExecutionContext context,

View File

@ -0,0 +1,615 @@
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
namespace Godot.SourceGenerators
{
[Generator]
public class ScriptPropertiesGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context)
{
}
public void Execute(GeneratorExecutionContext context)
{
if (context.AreGodotSourceGeneratorsDisabled())
return;
INamedTypeSymbol[] godotClasses = context
.Compilation.SyntaxTrees
.SelectMany(tree =>
tree.GetRoot().DescendantNodes()
.OfType<ClassDeclarationSyntax>()
.SelectGodotScriptClasses(context.Compilation)
// Report and skip non-partial classes
.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;
})
.Select(x => x.symbol)
)
.Distinct<INamedTypeSymbol>(SymbolEqualityComparer.Default)
.ToArray();
if (godotClasses.Length > 0)
{
var typeCache = new MarshalUtils.TypeCache(context);
foreach (var godotClass in godotClasses)
{
VisitGodotScriptClass(context, typeCache, godotClass);
}
}
}
private static void VisitGodotScriptClass(
GeneratorExecutionContext context,
MarshalUtils.TypeCache typeCache,
INamedTypeSymbol symbol
)
{
INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace;
string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ?
namespaceSymbol.FullQualifiedName() :
string.Empty;
bool hasNamespace = classNs.Length != 0;
bool isInnerClass = symbol.ContainingType != null;
string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()
+ "_ScriptProperties_Generated";
var source = new StringBuilder();
source.Append("using Godot;\n");
source.Append("using Godot.NativeInterop;\n");
source.Append("\n");
if (hasNamespace)
{
source.Append("namespace ");
source.Append(classNs);
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.NameWithTypeParameters());
source.Append("\n{\n");
containingType = containingType.ContainingType;
}
}
source.Append("partial class ");
source.Append(symbol.NameWithTypeParameters());
source.Append("\n{\n");
var members = symbol.GetMembers();
var propertySymbols = members
.Where(s => !s.IsStatic && s.Kind == SymbolKind.Property)
.Cast<IPropertySymbol>();
var fieldSymbols = members
.Where(s => !s.IsStatic && s.Kind == SymbolKind.Field && !s.IsImplicitlyDeclared)
.Cast<IFieldSymbol>();
var godotClassProperties = propertySymbols.WhereIsGodotCompatibleType(typeCache).ToArray();
var godotClassFields = fieldSymbols.WhereIsGodotCompatibleType(typeCache).ToArray();
source.Append(" private partial class GodotInternal {\n");
// Generate cached StringNames for methods and properties, for fast lookup
foreach (var property in godotClassProperties)
{
string propertyName = property.PropertySymbol.Name;
source.Append(" public static readonly StringName PropName_");
source.Append(propertyName);
source.Append(" = \"");
source.Append(propertyName);
source.Append("\";\n");
}
foreach (var field in godotClassFields)
{
string fieldName = field.FieldSymbol.Name;
source.Append(" public static readonly StringName PropName_");
source.Append(fieldName);
source.Append(" = \"");
source.Append(fieldName);
source.Append("\";\n");
}
source.Append(" }\n"); // class GodotInternal
if (godotClassProperties.Length > 0 || godotClassFields.Length > 0)
{
bool isFirstEntry;
// Generate SetGodotClassPropertyValue
bool allPropertiesAreReadOnly = godotClassFields.All(fi => fi.FieldSymbol.IsReadOnly) &&
godotClassProperties.All(pi => pi.PropertySymbol.IsReadOnly);
if (!allPropertiesAreReadOnly)
{
source.Append(" protected override bool SetGodotClassPropertyValue(in godot_string_name name, ");
source.Append("in godot_variant value)\n {\n");
isFirstEntry = true;
foreach (var property in godotClassProperties)
{
if (property.PropertySymbol.IsReadOnly)
continue;
GeneratePropertySetter(property.PropertySymbol.Name,
property.PropertySymbol.Type, property.Type, source, isFirstEntry);
isFirstEntry = false;
}
foreach (var field in godotClassFields)
{
if (field.FieldSymbol.IsReadOnly)
continue;
GeneratePropertySetter(field.FieldSymbol.Name,
field.FieldSymbol.Type, field.Type, source, isFirstEntry);
isFirstEntry = false;
}
source.Append(" return base.SetGodotClassPropertyValue(name, value);\n");
source.Append(" }\n");
}
// Generate GetGodotClassPropertyValue
source.Append(" protected override bool GetGodotClassPropertyValue(in godot_string_name name, ");
source.Append("out godot_variant value)\n {\n");
isFirstEntry = true;
foreach (var property in godotClassProperties)
{
GeneratePropertyGetter(property.PropertySymbol.Name,
property.Type, source, isFirstEntry);
isFirstEntry = false;
}
foreach (var field in godotClassFields)
{
GeneratePropertyGetter(field.FieldSymbol.Name,
field.Type, source, isFirstEntry);
isFirstEntry = false;
}
source.Append(" return base.GetGodotClassPropertyValue(name, out value);\n");
source.Append(" }\n");
// Generate GetGodotPropertyList
source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n");
string dictionaryType = "System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>";
source.Append(" internal new static ")
.Append(dictionaryType)
.Append(" GetGodotPropertyList()\n {\n");
source.Append(" var properties = new ")
.Append(dictionaryType)
.Append("();\n");
foreach (var property in godotClassProperties)
{
var propertyInfo = DeterminePropertyInfo(context, typeCache,
property.PropertySymbol, property.Type);
if (propertyInfo == null)
continue;
AppendPropertyInfo(source, propertyInfo.Value);
}
foreach (var field in godotClassFields)
{
var propertyInfo = DeterminePropertyInfo(context, typeCache,
field.FieldSymbol, field.Type);
if (propertyInfo == null)
continue;
AppendPropertyInfo(source, propertyInfo.Value);
}
source.Append(" return properties;\n");
source.Append(" }\n");
source.Append("#pragma warning restore CS0109\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)
{
source.Append("\n}\n");
}
context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8));
}
private static void GeneratePropertySetter(
string propertyMemberName,
ITypeSymbol propertyTypeSymbol,
MarshalType propertyMarshalType,
StringBuilder source,
bool isFirstEntry
)
{
source.Append(" ");
if (!isFirstEntry)
source.Append("else ");
source.Append("if (name == GodotInternal.PropName_")
.Append(propertyMemberName)
.Append(") {\n")
.Append(" ")
.Append(propertyMemberName)
.Append(" = ")
.AppendNativeVariantToManagedExpr("value", propertyTypeSymbol, propertyMarshalType)
.Append(";\n")
.Append(" return true;\n")
.Append(" }\n");
}
private static void GeneratePropertyGetter(
string propertyMemberName,
MarshalType propertyMarshalType,
StringBuilder source,
bool isFirstEntry
)
{
source.Append(" ");
if (!isFirstEntry)
source.Append("else ");
source.Append("if (name == GodotInternal.PropName_")
.Append(propertyMemberName)
.Append(") {\n")
.Append(" value = ")
.AppendManagedToNativeVariantExpr(propertyMemberName, propertyMarshalType)
.Append(";\n")
.Append(" return true;\n")
.Append(" }\n");
}
private static void AppendPropertyInfo(StringBuilder source, PropertyInfo propertyInfo)
{
source.Append(" properties.Add(new(type: (Godot.Variant.Type)")
.Append((int)propertyInfo.Type)
.Append(", name: GodotInternal.PropName_")
.Append(propertyInfo.Name)
.Append(", hint: (Godot.PropertyHint)")
.Append((int)propertyInfo.Hint)
.Append(", hintString: \"")
.Append(propertyInfo.HintString)
.Append("\", usage: (Godot.PropertyUsageFlags)")
.Append((int)propertyInfo.Usage)
.Append(", exported: ")
.Append(propertyInfo.Exported ? "true" : "false")
.Append("));\n");
}
private static PropertyInfo? DeterminePropertyInfo(
GeneratorExecutionContext context,
MarshalUtils.TypeCache typeCache,
ISymbol memberSymbol,
MarshalType marshalType
)
{
var exportAttr = memberSymbol.GetAttributes()
.FirstOrDefault(a => a.AttributeClass?.IsGodotExportAttribute() ?? false);
var propertySymbol = memberSymbol as IPropertySymbol;
var fieldSymbol = memberSymbol as IFieldSymbol;
if (exportAttr != null && propertySymbol != null)
{
if (propertySymbol.GetMethod == null)
{
// This should never happen, as we filtered WriteOnly properties, but just in case.
Common.ReportExportedMemberIsWriteOnly(context, propertySymbol);
return null;
}
if (propertySymbol.SetMethod == null)
{
// This should never happen, as we filtered ReadOnly properties, but just in case.
Common.ReportExportedMemberIsReadOnly(context, propertySymbol);
return null;
}
}
var memberType = propertySymbol?.Type ?? fieldSymbol!.Type;
var memberVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(marshalType)!.Value;
string memberName = memberSymbol.Name;
if (exportAttr == null)
{
return new PropertyInfo(memberVariantType, memberName, PropertyHint.None,
hintString: null, PropertyUsageFlags.ScriptVariable, exported: false);
}
if (!TryGetMemberExportHint(typeCache, memberType, exportAttr, memberVariantType,
isTypeArgument: false, out var hint, out var hintString))
{
var constructorArguments = exportAttr.ConstructorArguments;
if (constructorArguments.Length > 0)
{
var hintValue = exportAttr.ConstructorArguments[0].Value;
hint = hintValue switch
{
null => PropertyHint.None,
int intValue => (PropertyHint)intValue,
_ => (PropertyHint)(long)hintValue
};
hintString = constructorArguments.Length > 1 ?
exportAttr.ConstructorArguments[1].Value?.ToString() :
null;
}
else
{
hint = PropertyHint.None;
}
}
var propUsage = PropertyUsageFlags.Default | PropertyUsageFlags.ScriptVariable;
if (memberVariantType == VariantType.Nil)
propUsage |= PropertyUsageFlags.NilIsVariant;
return new PropertyInfo(memberVariantType, memberName,
hint, hintString, propUsage, exported: true);
}
private static bool TryGetMemberExportHint(
MarshalUtils.TypeCache typeCache,
ITypeSymbol type, AttributeData exportAttr,
VariantType variantType, bool isTypeArgument,
out PropertyHint hint, out string? hintString
)
{
hint = PropertyHint.None;
hintString = null;
if (variantType == VariantType.Nil)
return true; // Variant, no export hint
if (variantType == VariantType.Int &&
type.IsValueType && type.TypeKind == TypeKind.Enum)
{
bool hasFlagsAttr = type.GetAttributes()
.Any(a => a.AttributeClass?.IsSystemFlagsAttribute() ?? false);
hint = hasFlagsAttr ? PropertyHint.Flags : PropertyHint.Enum;
var members = type.GetMembers();
var enumFields = members
.Where(s => s.Kind == SymbolKind.Field && s.IsStatic &&
s.DeclaredAccessibility == Accessibility.Public &&
!s.IsImplicitlyDeclared)
.Cast<IFieldSymbol>().ToArray();
var hintStringBuilder = new StringBuilder();
var nameOnlyHintStringBuilder = new StringBuilder();
// True: enum Foo { Bar, Baz, Qux }
// True: enum Foo { Bar = 0, Baz = 1, Qux = 2 }
// False: enum Foo { Bar = 0, Baz = 7, Qux = 5 }
bool usesDefaultValues = true;
for (int i = 0; i < enumFields.Length; i++)
{
var enumField = enumFields[i];
if (i > 0)
{
hintStringBuilder.Append(",");
nameOnlyHintStringBuilder.Append(",");
}
string enumFieldName = enumField.Name;
hintStringBuilder.Append(enumFieldName);
nameOnlyHintStringBuilder.Append(enumFieldName);
long val = enumField.ConstantValue switch
{
sbyte v => v,
short v => v,
int v => v,
long v => v,
byte v => v,
ushort v => v,
uint v => v,
ulong v => (long)v,
_ => 0
};
uint expectedVal = (uint)(hint == PropertyHint.Flags ? 1 << i : i);
if (val != expectedVal)
usesDefaultValues = false;
hintStringBuilder.Append(":");
hintStringBuilder.Append(val);
}
hintString = !usesDefaultValues ?
hintStringBuilder.ToString() :
// If we use the format NAME:VAL, that's what the editor displays.
// That's annoying if the user is not using custom values for the enum constants.
// This may not be needed in the future if the editor is changed to not display values.
nameOnlyHintStringBuilder.ToString();
return true;
}
if (variantType == VariantType.Object && type is INamedTypeSymbol memberNamedType)
{
if (memberNamedType.InheritsFrom("GodotSharp", "Godot.Resource"))
{
string nativeTypeName = memberNamedType.GetGodotScriptNativeClassName()!;
hint = PropertyHint.ResourceType;
hintString = nativeTypeName;
return true;
}
if (memberNamedType.InheritsFrom("GodotSharp", "Godot.Node"))
{
string nativeTypeName = memberNamedType.GetGodotScriptNativeClassName()!;
hint = PropertyHint.NodeType;
hintString = nativeTypeName;
return true;
}
}
static bool GetStringArrayEnumHint(VariantType elementVariantType,
AttributeData exportAttr, out string? hintString)
{
var constructorArguments = exportAttr.ConstructorArguments;
if (constructorArguments.Length > 0)
{
var presetHintValue = exportAttr.ConstructorArguments[0].Value;
PropertyHint presetHint = presetHintValue switch
{
null => PropertyHint.None,
int intValue => (PropertyHint)intValue,
_ => (PropertyHint)(long)presetHintValue
};
if (presetHint == PropertyHint.Enum)
{
string? presetHintString = constructorArguments.Length > 1 ?
exportAttr.ConstructorArguments[1].Value?.ToString() :
null;
hintString = (int)elementVariantType + "/" + (int)PropertyHint.Enum + ":";
if (presetHintString != null)
hintString += presetHintString;
return true;
}
}
hintString = null;
return false;
}
if (!isTypeArgument && variantType == VariantType.Array)
{
var elementType = MarshalUtils.GetArrayElementType(type);
if (elementType == null)
return false; // Non-generic Array, so there's no hint to add
var elementMarshalType = MarshalUtils.ConvertManagedTypeToMarshalType(elementType, typeCache)!.Value;
var elementVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(elementMarshalType)!.Value;
bool isPresetHint = false;
if (elementVariantType == VariantType.String)
isPresetHint = GetStringArrayEnumHint(elementVariantType, exportAttr, out hintString);
if (!isPresetHint)
{
bool hintRes = TryGetMemberExportHint(typeCache, elementType,
exportAttr, elementVariantType, isTypeArgument: true,
out var elementHint, out var elementHintString);
// Format: type/hint:hint_string
if (hintRes)
{
hintString = (int)elementVariantType + "/" + (int)elementHint + ":";
if (elementHintString != null)
hintString += elementHintString;
}
else
{
hintString = (int)elementVariantType + "/" + (int)PropertyHint.None + ":";
}
}
hint = PropertyHint.TypeString;
return hintString != null;
}
if (!isTypeArgument && variantType == VariantType.PackedStringArray)
{
if (GetStringArrayEnumHint(VariantType.String, exportAttr, out hintString))
{
hint = PropertyHint.TypeString;
return true;
}
}
if (!isTypeArgument && variantType == VariantType.Dictionary)
{
// TODO: Dictionaries are not supported in the inspector
return false;
}
return false;
}
}
}

View File

@ -0,0 +1,293 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
namespace Godot.SourceGenerators
{
[Generator]
public class ScriptPropertyDefValGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context)
{
}
public void Execute(GeneratorExecutionContext context)
{
if (context.AreGodotSourceGeneratorsDisabled())
return;
INamedTypeSymbol[] godotClasses = context
.Compilation.SyntaxTrees
.SelectMany(tree =>
tree.GetRoot().DescendantNodes()
.OfType<ClassDeclarationSyntax>()
.SelectGodotScriptClasses(context.Compilation)
// Report and skip non-partial classes
.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;
})
.Select(x => x.symbol)
)
.Distinct<INamedTypeSymbol>(SymbolEqualityComparer.Default)
.ToArray();
if (godotClasses.Length > 0)
{
var typeCache = new MarshalUtils.TypeCache(context);
foreach (var godotClass in godotClasses)
{
VisitGodotScriptClass(context, typeCache, godotClass);
}
}
}
private static void VisitGodotScriptClass(
GeneratorExecutionContext context,
MarshalUtils.TypeCache typeCache,
INamedTypeSymbol symbol
)
{
INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace;
string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ?
namespaceSymbol.FullQualifiedName() :
string.Empty;
bool hasNamespace = classNs.Length != 0;
bool isInnerClass = symbol.ContainingType != null;
string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()
+ "_ScriptPropertyDefVal_Generated";
var source = new StringBuilder();
source.Append("using Godot;\n");
source.Append("using Godot.NativeInterop;\n");
source.Append("\n");
if (hasNamespace)
{
source.Append("namespace ");
source.Append(classNs);
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.NameWithTypeParameters());
source.Append("\n{\n");
containingType = containingType.ContainingType;
}
}
source.Append("partial class ");
source.Append(symbol.NameWithTypeParameters());
source.Append("\n{\n");
var exportedMembers = new List<ExportedPropertyMetadata>();
var members = symbol.GetMembers();
var exportedProperties = members
.Where(s => !s.IsStatic && s.Kind == SymbolKind.Property)
.Cast<IPropertySymbol>()
.Where(s => s.GetAttributes()
.Any(a => a.AttributeClass?.IsGodotExportAttribute() ?? false))
.ToArray();
var exportedFields = members
.Where(s => !s.IsStatic && s.Kind == SymbolKind.Field && !s.IsImplicitlyDeclared)
.Cast<IFieldSymbol>()
.Where(s => s.GetAttributes()
.Any(a => a.AttributeClass?.IsGodotExportAttribute() ?? false))
.ToArray();
foreach (var property in exportedProperties)
{
if (property.IsStatic)
{
Common.ReportExportedMemberIsStatic(context, property);
continue;
}
// TODO: We should still restore read-only properties after reloading assembly. Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload.
// Ignore properties without a getter or without a setter. Godot properties must be both readable and writable.
if (property.IsWriteOnly)
{
Common.ReportExportedMemberIsWriteOnly(context, property);
continue;
}
if (property.IsReadOnly)
{
Common.ReportExportedMemberIsReadOnly(context, property);
continue;
}
var propertyType = property.Type;
var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(propertyType, typeCache);
if (marshalType == null)
{
Common.ReportExportedMemberTypeNotSupported(context, property);
continue;
}
// TODO: Detect default value from simple property getters (currently we only detect from initializers)
EqualsValueClauseSyntax? initializer = property.DeclaringSyntaxReferences
.Select(r => r.GetSyntax() as PropertyDeclarationSyntax)
.Select(s => s?.Initializer ?? null)
.FirstOrDefault();
string? value = initializer?.Value.ToString();
exportedMembers.Add(new ExportedPropertyMetadata(
property.Name, marshalType.Value, propertyType, value));
}
foreach (var field in exportedFields)
{
if (field.IsStatic)
{
Common.ReportExportedMemberIsStatic(context, field);
continue;
}
// TODO: We should still restore read-only fields after reloading assembly. Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload.
// Ignore properties without a getter or without a setter. Godot properties must be both readable and writable.
if (field.IsReadOnly)
{
Common.ReportExportedMemberIsReadOnly(context, field);
continue;
}
var fieldType = field.Type;
var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(fieldType, typeCache);
if (marshalType == null)
{
Common.ReportExportedMemberTypeNotSupported(context, field);
continue;
}
EqualsValueClauseSyntax? initializer = field.DeclaringSyntaxReferences
.Select(r => r.GetSyntax())
.OfType<VariableDeclaratorSyntax>()
.Select(s => s.Initializer)
.FirstOrDefault(i => i != null);
string? value = initializer?.Value.ToString();
exportedMembers.Add(new ExportedPropertyMetadata(
field.Name, marshalType.Value, fieldType, value));
}
// Generate GetGodotExportedProperties
if (exportedMembers.Count > 0)
{
source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n");
string dictionaryType = "System.Collections.Generic.Dictionary<StringName, object>";
source.Append("#if TOOLS\n");
source.Append(" internal new static ");
source.Append(dictionaryType);
source.Append(" GetGodotPropertyDefaultValues()\n {\n");
source.Append(" var values = new ");
source.Append(dictionaryType);
source.Append("(");
source.Append(exportedMembers.Count);
source.Append(");\n");
foreach (var exportedMember in exportedMembers)
{
string defaultValueLocalName = string.Concat("__", exportedMember.Name, "_default_value");
source.Append(" ");
source.Append(exportedMember.TypeSymbol.FullQualifiedName());
source.Append(" ");
source.Append(defaultValueLocalName);
source.Append(" = ");
source.Append(exportedMember.Value ?? "default");
source.Append(";\n");
source.Append(" values.Add(GodotInternal.PropName_");
source.Append(exportedMember.Name);
source.Append(", ");
source.Append(defaultValueLocalName);
source.Append(");\n");
}
source.Append(" return values;\n");
source.Append(" }\n");
source.Append("#endif\n");
source.Append("#pragma warning restore CS0109\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)
{
source.Append("\n}\n");
}
context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8));
}
private struct ExportedPropertyMetadata
{
public ExportedPropertyMetadata(string name, MarshalType type, ITypeSymbol typeSymbol, string? value)
{
Name = name;
Type = type;
TypeSymbol = typeSymbol;
Value = value;
}
public string Name { get; }
public MarshalType Type { get; }
public ITypeSymbol TypeSymbol { get; }
public string? Value { get; }
}
}
}

View File

@ -0,0 +1,19 @@
using Microsoft.CodeAnalysis;
namespace Godot.SourceGenerators
{
// Placeholder. Once we switch to native extensions this will act as the registrar for all
// user Godot classes in the assembly. Think of it as something similar to `register_types`.
public class ScriptRegistrarGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context)
{
throw new System.NotImplementedException();
}
public void Execute(GeneratorExecutionContext context)
{
throw new System.NotImplementedException();
}
}
}

View File

@ -0,0 +1,283 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
namespace Godot.SourceGenerators
{
[Generator]
public class ScriptSerializationGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context)
{
}
public void Execute(GeneratorExecutionContext context)
{
if (context.AreGodotSourceGeneratorsDisabled())
return;
INamedTypeSymbol[] godotClasses = context
.Compilation.SyntaxTrees
.SelectMany(tree =>
tree.GetRoot().DescendantNodes()
.OfType<ClassDeclarationSyntax>()
.SelectGodotScriptClasses(context.Compilation)
// Report and skip non-partial classes
.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;
})
.Select(x => x.symbol)
)
.Distinct<INamedTypeSymbol>(SymbolEqualityComparer.Default)
.ToArray();
if (godotClasses.Length > 0)
{
var typeCache = new MarshalUtils.TypeCache(context);
foreach (var godotClass in godotClasses)
{
VisitGodotScriptClass(context, typeCache, godotClass);
}
}
}
private static void VisitGodotScriptClass(
GeneratorExecutionContext context,
MarshalUtils.TypeCache typeCache,
INamedTypeSymbol symbol
)
{
INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace;
string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ?
namespaceSymbol.FullQualifiedName() :
string.Empty;
bool hasNamespace = classNs.Length != 0;
bool isInnerClass = symbol.ContainingType != null;
string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()
+ "_ScriptSerialization_Generated";
var source = new StringBuilder();
source.Append("using Godot;\n");
source.Append("using Godot.NativeInterop;\n");
source.Append("\n");
if (hasNamespace)
{
source.Append("namespace ");
source.Append(classNs);
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.NameWithTypeParameters());
source.Append("\n{\n");
containingType = containingType.ContainingType;
}
}
source.Append("partial class ");
source.Append(symbol.NameWithTypeParameters());
source.Append("\n{\n");
var members = symbol.GetMembers();
var propertySymbols = members
.Where(s => !s.IsStatic && s.Kind == SymbolKind.Property)
.Cast<IPropertySymbol>();
var fieldSymbols = members
.Where(s => !s.IsStatic && s.Kind == SymbolKind.Field && !s.IsImplicitlyDeclared)
.Cast<IFieldSymbol>();
var godotClassProperties = propertySymbols.WhereIsGodotCompatibleType(typeCache).ToArray();
var godotClassFields = fieldSymbols.WhereIsGodotCompatibleType(typeCache).ToArray();
var signalDelegateSymbols = members
.Where(s => s.Kind == SymbolKind.NamedType)
.Cast<INamedTypeSymbol>()
.Where(namedTypeSymbol => namedTypeSymbol.TypeKind == TypeKind.Delegate)
.Where(s => s.GetAttributes()
.Any(a => a.AttributeClass?.IsGodotSignalAttribute() ?? false));
List<GodotSignalDelegateData> godotSignalDelegates = new();
foreach (var signalDelegateSymbol in signalDelegateSymbols)
{
if (!signalDelegateSymbol.Name.EndsWith(ScriptSignalsGenerator.SignalDelegateSuffix))
continue;
string signalName = signalDelegateSymbol.Name;
signalName = signalName.Substring(0,
signalName.Length - ScriptSignalsGenerator.SignalDelegateSuffix.Length);
var invokeMethodData = signalDelegateSymbol
.DelegateInvokeMethod?.HasGodotCompatibleSignature(typeCache);
if (invokeMethodData == null)
continue;
godotSignalDelegates.Add(new(signalName, signalDelegateSymbol, invokeMethodData.Value));
}
source.Append(
" protected override void SaveGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info)\n {\n");
source.Append(" base.SaveGodotObjectData(info);\n");
// Save properties
foreach (var property in godotClassProperties)
{
string propertyName = property.PropertySymbol.Name;
source.Append(" info.AddProperty(GodotInternal.PropName_")
.Append(propertyName)
.Append(", ")
.AppendManagedToVariantExpr(string.Concat("this.", propertyName), property.Type)
.Append(");\n");
}
// Save fields
foreach (var field in godotClassFields)
{
string fieldName = field.FieldSymbol.Name;
source.Append(" info.AddProperty(GodotInternal.PropName_")
.Append(fieldName)
.Append(", ")
.AppendManagedToVariantExpr(string.Concat("this.", fieldName), field.Type)
.Append(");\n");
}
// Save signal events
foreach (var signalDelegate in godotSignalDelegates)
{
string signalName = signalDelegate.Name;
source.Append(" info.AddSignalEventDelegate(GodotInternal.SignalName_")
.Append(signalName)
.Append(", this.backing_")
.Append(signalName)
.Append(");\n");
}
source.Append(" }\n");
source.Append(
" protected override void RestoreGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info)\n {\n");
source.Append(" base.RestoreGodotObjectData(info);\n");
// Restore properties
foreach (var property in godotClassProperties)
{
string propertyName = property.PropertySymbol.Name;
source.Append(" if (info.TryGetProperty(GodotInternal.PropName_")
.Append(propertyName)
.Append(", out var _value_")
.Append(propertyName)
.Append("))\n")
.Append(" this.")
.Append(propertyName)
.Append(" = ")
.AppendVariantToManagedExpr(string.Concat("_value_", propertyName),
property.PropertySymbol.Type, property.Type)
.Append(";\n");
}
// Restore fields
foreach (var field in godotClassFields)
{
string fieldName = field.FieldSymbol.Name;
source.Append(" if (info.TryGetProperty(GodotInternal.PropName_")
.Append(fieldName)
.Append(", out var _value_")
.Append(fieldName)
.Append("))\n")
.Append(" this.")
.Append(fieldName)
.Append(" = ")
.AppendVariantToManagedExpr(string.Concat("_value_", fieldName),
field.FieldSymbol.Type, field.Type)
.Append(";\n");
}
// Restore signal events
foreach (var signalDelegate in godotSignalDelegates)
{
string signalName = signalDelegate.Name;
string signalDelegateQualifiedName = signalDelegate.DelegateSymbol.FullQualifiedName();
source.Append(" if (info.TryGetSignalEventDelegate<")
.Append(signalDelegateQualifiedName)
.Append(">(GodotInternal.SignalName_")
.Append(signalName)
.Append(", out var _value_")
.Append(signalName)
.Append("))\n")
.Append(" this.backing_")
.Append(signalName)
.Append(" = _value_")
.Append(signalName)
.Append(";\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)
{
source.Append("\n}\n");
}
context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8));
}
}
}

View File

@ -0,0 +1,411 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
// TODO:
// Determine a proper way to emit the signal.
// 'Emit(nameof(TheEvent))' creates a StringName everytime and has the overhead of string marshaling.
// I haven't decided on the best option yet. Some possibilities:
// - Expose the generated StringName fields to the user, for use with 'Emit(...)'.
// - Generate a 'EmitSignalName' method for each event signal.
namespace Godot.SourceGenerators
{
[Generator]
public class ScriptSignalsGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context)
{
}
public void Execute(GeneratorExecutionContext context)
{
if (context.AreGodotSourceGeneratorsDisabled())
return;
INamedTypeSymbol[] godotClasses = context
.Compilation.SyntaxTrees
.SelectMany(tree =>
tree.GetRoot().DescendantNodes()
.OfType<ClassDeclarationSyntax>()
.SelectGodotScriptClasses(context.Compilation)
// Report and skip non-partial classes
.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;
})
.Select(x => x.symbol)
)
.Distinct<INamedTypeSymbol>(SymbolEqualityComparer.Default)
.ToArray();
if (godotClasses.Length > 0)
{
var typeCache = new MarshalUtils.TypeCache(context);
foreach (var godotClass in godotClasses)
{
VisitGodotScriptClass(context, typeCache, godotClass);
}
}
}
internal static string SignalDelegateSuffix = "EventHandler";
private static void VisitGodotScriptClass(
GeneratorExecutionContext context,
MarshalUtils.TypeCache typeCache,
INamedTypeSymbol symbol
)
{
INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace;
string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ?
namespaceSymbol.FullQualifiedName() :
string.Empty;
bool hasNamespace = classNs.Length != 0;
bool isInnerClass = symbol.ContainingType != null;
string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()
+ "_ScriptSignals_Generated";
var source = new StringBuilder();
source.Append("using Godot;\n");
source.Append("using Godot.NativeInterop;\n");
source.Append("\n");
if (hasNamespace)
{
source.Append("namespace ");
source.Append(classNs);
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.NameWithTypeParameters());
source.Append("\n{\n");
containingType = containingType.ContainingType;
}
}
source.Append("partial class ");
source.Append(symbol.NameWithTypeParameters());
source.Append("\n{\n");
// TODO:
// The delegate name already needs to end with 'Signal' to avoid collision with the event name.
// Requiring SignalAttribute is redundant. Should we remove it to make declaration shorter?
var members = symbol.GetMembers();
var signalDelegateSymbols = members
.Where(s => s.Kind == SymbolKind.NamedType)
.Cast<INamedTypeSymbol>()
.Where(namedTypeSymbol => namedTypeSymbol.TypeKind == TypeKind.Delegate)
.Where(s => s.GetAttributes()
.Any(a => a.AttributeClass?.IsGodotSignalAttribute() ?? false));
List<GodotSignalDelegateData> godotSignalDelegates = new();
foreach (var signalDelegateSymbol in signalDelegateSymbols)
{
if (!signalDelegateSymbol.Name.EndsWith(SignalDelegateSuffix))
{
Common.ReportSignalDelegateMissingSuffix(context, signalDelegateSymbol);
continue;
}
string signalName = signalDelegateSymbol.Name;
signalName = signalName.Substring(0, signalName.Length - SignalDelegateSuffix.Length);
var invokeMethodData = signalDelegateSymbol
.DelegateInvokeMethod?.HasGodotCompatibleSignature(typeCache);
if (invokeMethodData == null)
{
// TODO: Better error for incompatible signature. We should indicate incompatible argument types, as we do with exported properties.
Common.ReportSignalDelegateSignatureNotSupported(context, signalDelegateSymbol);
continue;
}
godotSignalDelegates.Add(new(signalName, signalDelegateSymbol, invokeMethodData.Value));
}
source.Append(" private partial class GodotInternal {\n");
// Generate cached StringNames for methods and properties, for fast lookup
foreach (var signalDelegate in godotSignalDelegates)
{
string signalName = signalDelegate.Name;
source.Append(" public static readonly StringName SignalName_");
source.Append(signalName);
source.Append(" = \"");
source.Append(signalName);
source.Append("\";\n");
}
source.Append(" }\n"); // class GodotInternal
// Generate GetGodotSignalList
if (godotSignalDelegates.Count > 0)
{
source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n");
const string listType = "System.Collections.Generic.List<global::Godot.Bridge.MethodInfo>";
source.Append(" internal new static ")
.Append(listType)
.Append(" GetGodotSignalList()\n {\n");
source.Append(" var signals = new ")
.Append(listType)
.Append("(")
.Append(godotSignalDelegates.Count)
.Append(");\n");
foreach (var signalDelegateData in godotSignalDelegates)
{
var methodInfo = DetermineMethodInfo(signalDelegateData);
AppendMethodInfo(source, methodInfo);
}
source.Append(" return signals;\n");
source.Append(" }\n");
source.Append("#pragma warning restore CS0109\n");
}
// Generate signal event
foreach (var signalDelegate in godotSignalDelegates)
{
string signalName = signalDelegate.Name;
// TODO: Hide backing event from code-completion and debugger
// The reason we have a backing field is to hide the invoke method from the event,
// as it doesn't emit the signal, only the event delegates. This can confuse users.
// Maybe we should directly connect the delegates, as we do with native signals?
source.Append(" private ")
.Append(signalDelegate.DelegateSymbol.FullQualifiedName())
.Append(" backing_")
.Append(signalName)
.Append(";\n");
source.Append(" public event ")
.Append(signalDelegate.DelegateSymbol.FullQualifiedName())
.Append(" ")
.Append(signalName)
.Append(" {\n")
.Append(" add => backing_")
.Append(signalName)
.Append(" += value;\n")
.Append(" remove => backing_")
.Append(signalName)
.Append(" -= value;\n")
.Append("}\n");
}
// Generate RaiseGodotClassSignalCallbacks
if (godotSignalDelegates.Count > 0)
{
source.Append(
" protected override void RaiseGodotClassSignalCallbacks(in godot_string_name signal, ");
source.Append("NativeVariantPtrArgs args, int argCount)\n {\n");
foreach (var signal in godotSignalDelegates)
{
GenerateSignalEventInvoker(signal, source);
}
source.Append(" base.RaiseGodotClassSignalCallbacks(signal, args, argCount);\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)
{
source.Append("\n}\n");
}
context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8));
}
private static void AppendMethodInfo(StringBuilder source, MethodInfo methodInfo)
{
source.Append(" signals.Add(new(name: GodotInternal.SignalName_")
.Append(methodInfo.Name)
.Append(", returnVal: ");
AppendPropertyInfo(source, methodInfo.ReturnVal);
source.Append(", flags: (Godot.MethodFlags)")
.Append((int)methodInfo.Flags)
.Append(", arguments: ");
if (methodInfo.Arguments is { Count: > 0 })
{
source.Append("new() { ");
foreach (var param in methodInfo.Arguments)
{
AppendPropertyInfo(source, param);
// C# allows colon after the last element
source.Append(", ");
}
source.Append(" }");
}
else
{
source.Append("null");
}
source.Append(", defaultArguments: null));\n");
}
private static void AppendPropertyInfo(StringBuilder source, PropertyInfo propertyInfo)
{
source.Append("new(type: (Godot.Variant.Type)")
.Append((int)propertyInfo.Type)
.Append(", name: \"")
.Append(propertyInfo.Name)
.Append("\", hint: (Godot.PropertyHint)")
.Append((int)propertyInfo.Hint)
.Append(", hintString: \"")
.Append(propertyInfo.HintString)
.Append("\", usage: (Godot.PropertyUsageFlags)")
.Append((int)propertyInfo.Usage)
.Append(", exported: ")
.Append(propertyInfo.Exported ? "true" : "false")
.Append(")");
}
private static MethodInfo DetermineMethodInfo(GodotSignalDelegateData signalDelegateData)
{
var invokeMethodData = signalDelegateData.InvokeMethodData;
PropertyInfo returnVal;
if (invokeMethodData.RetType != null)
{
returnVal = DeterminePropertyInfo(invokeMethodData.RetType.Value, name: string.Empty);
}
else
{
returnVal = new PropertyInfo(VariantType.Nil, string.Empty, PropertyHint.None,
hintString: null, PropertyUsageFlags.Default, exported: false);
}
int paramCount = invokeMethodData.ParamTypes.Length;
List<PropertyInfo>? arguments;
if (paramCount > 0)
{
arguments = new(capacity: paramCount);
for (int i = 0; i < paramCount; i++)
{
arguments.Add(DeterminePropertyInfo(invokeMethodData.ParamTypes[i],
name: invokeMethodData.Method.Parameters[i].Name));
}
}
else
{
arguments = null;
}
return new MethodInfo(signalDelegateData.Name, returnVal, MethodFlags.Default, arguments,
defaultArguments: null);
}
private static PropertyInfo DeterminePropertyInfo(MarshalType marshalType, string name)
{
var memberVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(marshalType)!.Value;
var propUsage = PropertyUsageFlags.Default;
if (memberVariantType == VariantType.Nil)
propUsage |= PropertyUsageFlags.NilIsVariant;
return new PropertyInfo(memberVariantType, name,
PropertyHint.None, string.Empty, propUsage, exported: false);
}
private static void GenerateSignalEventInvoker(
GodotSignalDelegateData signal,
StringBuilder source
)
{
string signalName = signal.Name;
var invokeMethodData = signal.InvokeMethodData;
source.Append(" if (signal == GodotInternal.SignalName_");
source.Append(signalName);
source.Append(" && argCount == ");
source.Append(invokeMethodData.ParamTypes.Length);
source.Append(") {\n");
source.Append(" backing_");
source.Append(signalName);
source.Append("?.Invoke(");
for (int i = 0; i < invokeMethodData.ParamTypes.Length; i++)
{
if (i != 0)
source.Append(", ");
source.AppendNativeVariantToManagedExpr(string.Concat("args[", i.ToString(), "]"),
invokeMethodData.ParamTypeSymbols[i], invokeMethodData.ParamTypes[i]);
}
source.Append(");\n");
source.Append(" return;\n");
source.Append(" }\n");
}
}
}

View File

@ -7,8 +7,6 @@ namespace GodotTools.BuildLogger
{
public class GodotBuildLogger : ILogger
{
public static readonly string AssemblyPath = Path.GetFullPath(typeof(GodotBuildLogger).Assembly.Location);
public string Parameters { get; set; }
public LoggerVerbosity Verbosity { get; set; }

View File

@ -5,6 +5,6 @@
<LangVersion>7.2</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build.Framework" Version="16.5.0" />
<PackageReference Include="Microsoft.Build.Framework" Version="15.1.548" ExcludeAssets="runtime" />
</ItemGroup>
</Project>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ProjectGuid>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</ProjectGuid>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>7.2</LangVersion>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>10</LangVersion>
</PropertyGroup>
</Project>

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ProjectGuid>{B06C2951-C8E3-4F28-80B2-717CF327EB19}</ProjectGuid>
<OutputType>Exe</OutputType>

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ProjectGuid>{EAFFF236-FA96-4A4D-BD23-0E51EF988277}</ProjectGuid>
<OutputType>Exe</OutputType>

View File

@ -1,32 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ProjectGuid>{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}</ProjectGuid>
<TargetFramework>net472</TargetFramework>
<LangVersion>7.2</LangVersion>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>10</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build" Version="16.5.0" />
<PackageReference Include="Microsoft.Build" Version="15.1.548" ExcludeAssets="runtime" />
<PackageReference Include="Microsoft.Build.Locator" Version="1.2.6" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj" />
<ProjectReference Include="..\GodotTools.Shared\GodotTools.Shared.csproj" />
</ItemGroup>
<!--
The Microsoft.Build.Runtime package is too problematic so we create a MSBuild.exe stub. The workaround described
here doesn't work with Microsoft.NETFramework.ReferenceAssemblies: https://github.com/microsoft/msbuild/issues/3486
We need a MSBuild.exe file as there's an issue in Microsoft.Build where it executes platform dependent code when
searching for MSBuild.exe before the fallback to not using it. A stub is fine as it should never be executed.
-->
<ItemGroup>
<None Include="MSBuild.exe" CopyToOutputDirectory="Always" />
</ItemGroup>
<Target Name="CopyMSBuildStubWindows" AfterTargets="Build" Condition=" '$(GodotPlatform)' == 'windows' Or ( '$(GodotPlatform)' == '' And '$(OS)' == 'Windows_NT' ) ">
<PropertyGroup>
<GodotSourceRootPath>$(SolutionDir)/../../../../</GodotSourceRootPath>
<GodotOutputDataDir>$(GodotSourceRootPath)/bin/GodotSharp</GodotOutputDataDir>
</PropertyGroup>
<!-- Need to copy it here as well on Windows -->
<Copy SourceFiles="MSBuild.exe" DestinationFiles="$(GodotOutputDataDir)\Mono\lib\mono\v4.0\MSBuild.exe" />
</Target>
</Project>

View File

@ -1,4 +1,5 @@
using System;
using System.Globalization;
using System.IO;
using System.Text;
using Microsoft.Build.Construction;
@ -21,7 +22,8 @@ namespace GodotTools.ProjectEditor
root.Sdk = GodotSdkAttrValue;
var mainGroup = root.AddPropertyGroup();
mainGroup.AddProperty("TargetFramework", "netstandard2.1");
mainGroup.AddProperty("TargetFramework", "net6.0");
mainGroup.AddProperty("EnableDynamicLoading", "true");
string sanitizedName = IdentifierUtils.SanitizeQualifiedIdentifier(name, allowEmptyIdentifiers: true);
@ -44,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();
}
}
}

View File

@ -19,6 +19,16 @@ namespace GodotTools.ProjectEditor
public static class ProjectUtils
{
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)
{
var root = ProjectRootElement.Open(path);
@ -42,7 +52,8 @@ namespace GodotTools.ProjectEditor
var root = project.Root;
string godotSdkAttrValue = ProjectGenerator.GodotSdkAttrValue;
if (!string.IsNullOrEmpty(root.Sdk) && root.Sdk.Trim().Equals(godotSdkAttrValue, StringComparison.OrdinalIgnoreCase))
if (!string.IsNullOrEmpty(root.Sdk) &&
root.Sdk.Trim().Equals(godotSdkAttrValue, StringComparison.OrdinalIgnoreCase))
return;
root.Sdk = godotSdkAttrValue;

View File

@ -8,8 +8,8 @@
</Target>
<Target Name="GenerateGodotNupkgsVersionsFile"
DependsOnTargets="PrepareForBuild;_GenerateGodotNupkgsVersionsFile"
BeforeTargets="BeforeCompile;CoreCompile">
DependsOnTargets="_GenerateGodotNupkgsVersionsFile"
BeforeTargets="PrepareForBuild;CompileDesignTime;BeforeCompile;CoreCompile">
<ItemGroup>
<Compile Include="$(GeneratedGodotNupkgsVersionsFile)" />
<FileWrites Include="$(GeneratedGodotNupkgsVersionsFile)" />

View File

@ -1,6 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<!-- Specify compile items manually to avoid including dangling generated items. -->
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
</PropertyGroup>
<Import Project="GenerateGodotNupkgsVersions.targets" />
</Project>

View File

@ -1,4 +1,4 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.ProjectEditor", "GodotTools.ProjectEditor\GodotTools.ProjectEditor.csproj", "{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}"
@ -15,6 +15,10 @@ 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
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators.Internal", "..\..\glue\GodotSharp\Godot.SourceGenerators.Internal\Godot.SourceGenerators.Internal.csproj", "{55666071-BEC1-4A52-8A98-9A4A7A947DBF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -49,5 +53,13 @@ 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
{55666071-BEC1-4A52-8A98-9A4A7A947DBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{55666071-BEC1-4A52-8A98-9A4A7A947DBF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{55666071-BEC1-4A52-8A98-9A4A7A947DBF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{55666071-BEC1-4A52-8A98-9A4A7A947DBF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@ -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 bool Restore { get; }
public string Solution { get; private set; }
public string Configuration { get; private set; }
public string? RuntimeIdentifier { get; private set; }
public string? PublishOutputDir { get; private set; }
public bool Restore { get; private set; }
public bool Rebuild { get; private set; }
public bool OnlyClean { get; private set; }
// TODO Use List once we have proper serialization
public Array<string> CustomProperties { get; } = new Array<string>();
public Godot.Collections.Array CustomProperties { get; private set; } = new();
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,44 @@ 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;
}
}
// Needed for instantiation from Godot, after reloading assemblies
private BuildInfo()
{
Solution = string.Empty;
Configuration = string.Empty;
}
public BuildInfo(string solution, string[] targets, string configuration, bool restore)
public BuildInfo(string solution, string configuration, bool restore, bool rebuild, bool onlyClean)
{
Solution = solution;
Targets = targets;
Configuration = configuration;
Restore = restore;
Rebuild = rebuild;
OnlyClean = onlyClean;
}
public BuildInfo(string solution, string configuration, string runtimeIdentifier,
string publishOutputDir, bool restore, bool rebuild, bool onlyClean)
{
Solution = solution;
Configuration = configuration;
RuntimeIdentifier = runtimeIdentifier;
PublishOutputDir = publishOutputDir;
Restore = restore;
Rebuild = rebuild;
OnlyClean = onlyClean;
}
}
}

View File

@ -1,12 +1,10 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading.Tasks;
using GodotTools.Ides.Rider;
using Godot;
using GodotTools.Internals;
using JetBrains.Annotations;
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, [CanBeNull] string[] targets = null, [CanBeNull] 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);
return BuildProjectBlocking(buildInfo);
// Required in order to update the build tasks list
Internal.GodotMainIteration();
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,31 +209,109 @@ namespace GodotTools.Build
if (!File.Exists(buildInfo.Solution))
return true; // No solution to build
// Make sure the API assemblies are up to date before building the project.
// We may not have had the chance to update the release API assemblies, and the debug ones
// may have been deleted by the user at some point after they were loaded by the Godot editor.
string apiAssembliesUpdateError = Internal.UpdateApiAssembliesFromPrebuilt(buildInfo.Configuration == "ExportRelease" ? "Release" : "Debug");
using var pr = new EditorProgress("dotnet_build_project", "Building .NET project...", 1);
if (!string.IsNullOrEmpty(apiAssembliesUpdateError))
pr.Step("Building project solution", 0);
if (!Build(buildInfo))
{
ShowBuildErrorDialog("Failed to update the Godot API assemblies");
ShowBuildErrorDialog("Failed to build project solution");
return false;
}
using (var pr = new EditorProgress("mono_project_debug_build", "Building project solution...", 1))
{
pr.Step("Building project solution", 0);
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))
@ -215,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)
@ -226,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
});
}
}
}

View File

@ -1,17 +1,16 @@
using Godot;
using System;
using Godot.Collections;
using System.Diagnostics.CodeAnalysis;
using GodotTools.Internals;
using JetBrains.Annotations;
using File = GodotTools.Utils.File;
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; }
@ -22,7 +21,8 @@ namespace GodotTools.Build
public string ProjectFile { get; set; }
}
[Signal] public event Action BuildStateChanged;
[Signal]
public delegate void BuildStateChangedEventHandler();
public bool HasBuildExited { get; private set; } = false;
@ -58,7 +58,7 @@ namespace GodotTools.Build
}
// TODO Use List once we have proper serialization.
private readonly Array<BuildIssue> _issues = new Array<BuildIssue>();
private Godot.Collections.Array<BuildIssue> _issues = new();
private ItemList _issuesList;
private PopupMenu _issuesListContextMenu;
private TextEdit _buildLog;
@ -123,7 +123,7 @@ namespace GodotTools.Build
throw new IndexOutOfRangeException("Item list index out of range");
// Get correct issue idx from issue list
int issueIndex = (int)(long)_issuesList.GetItemMetadata(idx);
int issueIndex = (int)_issuesList.GetItemMetadata(idx);
if (issueIndex < 0 || issueIndex >= _issues.Count)
throw new IndexOutOfRangeException("Issue index out of range");
@ -133,7 +133,9 @@ namespace GodotTools.Build
if (string.IsNullOrEmpty(issue.ProjectFile) && string.IsNullOrEmpty(issue.File))
return;
string projectDir = issue.ProjectFile.Length > 0 ? issue.ProjectFile.GetBaseDir() : _buildInfo.Solution.GetBaseDir();
string projectDir = !string.IsNullOrEmpty(issue.ProjectFile) ?
issue.ProjectFile.GetBaseDir() :
_buildInfo.Solution.GetBaseDir();
string file = Path.Combine(projectDir.SimplifyGodotPath(), issue.File.SimplifyGodotPath());
@ -412,6 +414,16 @@ namespace GodotTools.Build
{
// In case it didn't update yet. We don't want to have to serialize any pending output.
UpdateBuildLogText();
// NOTE:
// Currently, GodotTools is loaded in its own load context. This load context is not reloaded, but the script still are.
// Until that changes, we need workarounds like this one because events keep strong references to disposed objects.
BuildManager.BuildLaunchFailed -= BuildLaunchFailed;
BuildManager.BuildStarted -= BuildStarted;
BuildManager.BuildFinished -= BuildFinished;
// StdOutput/Error can be received from different threads, so we need to use CallDeferred
BuildManager.StdOutputReceived -= StdOutputReceived;
BuildManager.StdErrorReceived -= StdErrorReceived;
}
public void OnAfterDeserialize()

View File

@ -1,61 +1,32 @@
using GodotTools.Core;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Linq;
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);
private static Godot.EditorSettings EditorSettings =>
GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
BuildArguments(buildInfo, startInfo.ArgumentList);
private static bool UsingMonoMsBuildOnWindows
{
get
{
if (OS.IsWindows)
{
return (BuildTool)EditorSettings.GetSetting("mono/builds/build_tool")
== BuildTool.MsBuildMono;
}
return false;
}
}
private static Process LaunchBuild(BuildInfo buildInfo, Action<string> stdOutHandler, Action<string> stdErrHandler)
{
(string msbuildPath, BuildTool buildTool) = MsBuildFinder.FindMsBuild();
if (msbuildPath == null)
throw new FileNotFoundException("Cannot find the MSBuild executable.");
string compilerArgs = BuildArguments(buildTool, buildInfo);
var startInfo = new ProcessStartInfo(msbuildPath, compilerArgs);
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);
@ -65,25 +36,15 @@ namespace GodotTools.Build
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);
process.OutputDataReceived += (_, e) => stdOutHandler.Invoke(e.Data);
if (stdErrHandler != null)
process.ErrorDataReceived += (s, e) => stdErrHandler.Invoke(e.Data);
process.ErrorDataReceived += (_, e) => stdErrHandler.Invoke(e.Data);
process.Start();
@ -103,7 +64,8 @@ namespace GodotTools.Build
}
}
public static async Task<int> BuildAsync(BuildInfo buildInfo, Action<string> stdOutHandler, Action<string> stdErrHandler)
public static async Task<int> BuildAsync(BuildInfo buildInfo, Action<string> stdOutHandler,
Action<string> stdErrHandler)
{
using (var process = LaunchBuild(buildInfo, stdOutHandler, stdErrHandler))
{
@ -113,28 +75,150 @@ namespace GodotTools.Build
}
}
private static string BuildArguments(BuildTool buildTool, BuildInfo buildInfo)
private static Process LaunchPublish(BuildInfo buildInfo, Action<string> stdOutHandler,
Action<string> stdErrHandler)
{
string arguments = string.Empty;
string dotnetPath = DotNetFinder.FindDotNetExe();
if (buildTool == BuildTool.DotnetCli)
arguments += "msbuild"; // `dotnet msbuild` command
if (dotnetPath == null)
throw new FileNotFoundException("Cannot find the dotnet executable.");
arguments += $@" ""{buildInfo.Solution}""";
var startInfo = new ProcessStartInfo(dotnetPath);
if (buildInfo.Restore)
arguments += " /restore";
BuildPublishArguments(buildInfo, startInfo.ArgumentList);
arguments += $@" /t:{string.Join(",", buildInfo.Targets)} " +
$@"""/p:{"Configuration=" + buildInfo.Configuration}"" /v:normal " +
$@"""/l:{typeof(GodotBuildLogger).FullName},{GodotBuildLogger.AssemblyPath};{buildInfo.LogsDirPath}""";
string launchMessage = startInfo.GetCommandLineDisplay(new StringBuilder("Running: ")).ToString();
stdOutHandler?.Invoke(launchMessage);
if (Godot.OS.IsStdoutVerbose())
Console.WriteLine(launchMessage);
foreach (string customProperty in buildInfo.CustomProperties)
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardError = true;
startInfo.UseShellExecute = false;
// Needed when running from Developer Command Prompt for VS
RemovePlatformVariable(startInfo.EnvironmentVariables);
var process = new Process { StartInfo = startInfo };
if (stdOutHandler != null)
process.OutputDataReceived += (_, e) => stdOutHandler.Invoke(e.Data);
if (stdErrHandler != null)
process.ErrorDataReceived += (_, e) => stdErrHandler.Invoke(e.Data);
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
return process;
}
public static int Publish(BuildInfo buildInfo, Action<string> stdOutHandler, Action<string> stdErrHandler)
{
using (var process = LaunchPublish(buildInfo, stdOutHandler, stdErrHandler))
{
arguments += " /p:" + customProperty;
process.WaitForExit();
return process.ExitCode;
}
}
private static void BuildArguments(BuildInfo buildInfo, Collection<string> arguments)
{
// `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)
{
// 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 arguments;
// Configuration
arguments.Add("-c");
arguments.Add(buildInfo.Configuration);
// Verbosity
arguments.Add("-v");
arguments.Add("normal");
// Logger
AddLoggerArgument(buildInfo, arguments);
// Custom properties
foreach (var customProperty in buildInfo.CustomProperties)
{
arguments.Add("-p:" + (string)customProperty);
}
}
private static void BuildPublishArguments(BuildInfo buildInfo, Collection<string> arguments)
{
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");
// Configuration
arguments.Add("-c");
arguments.Add(buildInfo.Configuration);
// Runtime Identifier
arguments.Add("-r");
arguments.Add(buildInfo.RuntimeIdentifier!);
// Self-published
arguments.Add("--self-contained");
arguments.Add("true");
// Verbosity
arguments.Add("-v");
arguments.Add("normal");
// Logger
AddLoggerArgument(buildInfo, arguments);
// Custom properties
foreach (var customProperty in buildInfo.CustomProperties)
{
arguments.Add("-p:" + (string)customProperty);
}
// Publish output directory
if (buildInfo.PublishOutputDir != null)
{
arguments.Add("-o");
arguments.Add(buildInfo.PublishOutputDir);
}
}
private static void AddLoggerArgument(BuildInfo buildInfo, Collection<string> arguments)
{
string buildLoggerPath = Path.Combine(Internals.GodotSharpDirs.DataEditorToolsDir,
"GodotTools.BuildLogger.dll");
arguments.Add(
$"-l:{typeof(GodotBuildLogger).FullName},{buildLoggerPath};{buildInfo.LogsDirPath}");
}
private static void RemovePlatformVariable(StringDictionary environmentVariables)
@ -145,7 +229,7 @@ namespace GodotTools.Build
foreach (string env in environmentVariables.Keys)
{
if (env.ToUpper() == "PLATFORM")
if (env.ToUpperInvariant() == "PLATFORM")
platformEnvironmentVariables.Add(env);
}

View File

@ -1,10 +0,0 @@
namespace GodotTools.Build
{
public enum BuildTool : long
{
MsBuildMono,
MsBuildVs,
JetBrainsMsBuild,
DotnetCli
}
}

View File

@ -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;
}
}
}

View File

@ -1,13 +1,12 @@
using System;
using Godot;
using GodotTools.Internals;
using JetBrains.Annotations;
using static GodotTools.Internals.Globals;
using File = GodotTools.Utils.File;
namespace GodotTools.Build
{
public class MSBuildPanel : VBoxContainer
public partial class MSBuildPanel : VBoxContainer
{
public BuildOutputView BuildOutputView { get; private set; }
@ -28,7 +27,6 @@ namespace GodotTools.Build
BuildOutputView.UpdateIssuesList();
}
[UsedImplicitly]
public void BuildSolution()
{
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
@ -57,7 +55,6 @@ namespace GodotTools.Build
Internal.ReloadAssemblies(softReload: false);
}
[UsedImplicitly]
private void RebuildSolution()
{
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
@ -73,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
@ -86,13 +83,12 @@ namespace GodotTools.Build
Internal.ReloadAssemblies(softReload: false);
}
[UsedImplicitly]
private void CleanSolution()
{
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;

View File

@ -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;
}
}
}

View File

@ -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

View File

@ -289,7 +289,7 @@ MONO_AOT_MODE_LAST = 1000,
// Archive the AOT object files into a static library
var arFilePathsForAllArchs = new List<string>();
string projectAssemblyName = GodotSharpEditor.ProjectAssemblyName;
string projectAssemblyName = GodotSharpDirs.ProjectAssemblyName;
foreach (var archPathsPair in objFilePathsForiOSArch)
{

View File

@ -4,11 +4,9 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using GodotTools.Build;
using GodotTools.Core;
using GodotTools.Internals;
using JetBrains.Annotations;
using static GodotTools.Internals.Globals;
using Directory = GodotTools.Utils.Directory;
using File = GodotTools.Utils.File;
@ -17,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);
@ -85,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)
@ -100,7 +46,9 @@ namespace GodotTools.Export
return;
if (Path.GetExtension(path) != Internal.CSharpLanguageExtension)
throw new ArgumentException($"Resource of type {Internal.CSharpLanguageType} has an invalid file extension: {path}", nameof(path));
throw new ArgumentException(
$"Resource of type {Internal.CSharpLanguageType} has an invalid file extension: {path}",
nameof(path));
// TODO What if the source file is not part of the game's C# project
@ -152,161 +100,95 @@ 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))
// 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";
string ridOS = DetermineRuntimeIdentifierOS(platform);
string ridArch = DetermineRuntimeIdentifierArch(arch);
string runtimeIdentifier = $"{ridOS}-{ridArch}";
// Create temporary publish output directory
string publishOutputTempDir = Path.Combine(Path.GetTempPath(), "godot-publish-dotnet",
$"{Process.GetCurrentProcess().Id}-{buildConfig}-{runtimeIdentifier}");
if (!Directory.Exists(publishOutputTempDir))
Directory.CreateDirectory(publishOutputTempDir);
// Execute dotnet publish
if (!BuildManager.PublishProjectBlocking(buildConfig, platform,
runtimeIdentifier, publishOutputTempDir))
{
throw new Exception("Failed to build project");
// Add dependency assemblies
var assemblies = new Godot.Collections.Dictionary<string, string>();
string projectDllName = GodotSharpEditor.ProjectAssemblyName;
string projectDllSrcDir = Path.Combine(GodotSharpDirs.ResTempAssembliesBaseDir, buildConfig);
string projectDllSrcPath = Path.Combine(projectDllSrcDir, $"{projectDllName}.dll");
assemblies[projectDllName] = projectDllSrcPath;
string bclDir = DeterminePlatformBclDir(platform);
if (platform == OS.Platforms.Android)
{
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;
}
else if (platform == OS.Platforms.HTML5)
{
// 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;
}
}
}
var initialAssemblies = assemblies.Duplicate();
internal_GetExportedAssemblyDependencies(initialAssemblies, buildConfig, bclDir, assemblies);
AddI18NAssemblies(assemblies, bclDir);
string outputDataDir = null;
if (PlatformHasTemplateDir(platform))
outputDataDir = ExportDataDirectory(features, platform, isDebug, outputDir);
string apiConfig = isDebug ? "Debug" : "Release";
string resAssembliesDir = Path.Combine(GodotSharpDirs.ResAssembliesBaseDir, apiConfig);
bool assembliesInsidePck = (bool)ProjectSettings.GetSetting("mono/export/export_assemblies_inside_pck") || outputDataDir == null;
if (!assembliesInsidePck)
string soExt = ridOS switch
{
string outputDataGameAssembliesDir = Path.Combine(outputDataDir, "Assemblies");
if (!Directory.Exists(outputDataGameAssembliesDir))
Directory.CreateDirectory(outputDataGameAssembliesDir);
OS.DotNetOS.Win or OS.DotNetOS.Win10 => "dll",
OS.DotNetOS.OSX or OS.DotNetOS.iOS => "dylib",
_ => "so"
};
if (!File.Exists(Path.Combine(publishOutputTempDir, $"{GodotSharpDirs.ProjectAssemblyName}.dll"))
// NativeAOT shared library output
&& !File.Exists(Path.Combine(publishOutputTempDir, $"{GodotSharpDirs.ProjectAssemblyName}.{soExt}")))
{
throw new NotSupportedException(
"Publish succeeded but project assembly not found in the output directory");
}
foreach (var assembly in assemblies)
// Copy all files from the dotnet publish output directory to
// a data directory next to the Godot output executable.
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(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);
Directory.CreateDirectory(Path.Combine(outputDataDir, dir.Substring(publishOutputTempDir.Length + 1)));
}
// AOT compilation
bool aotEnabled = platform == OS.Platforms.iOS || (bool)ProjectSettings.GetSetting("mono/export/aot/enabled");
if (aotEnabled)
foreach (string file in Directory.GetFiles(publishOutputTempDir, "*", SearchOption.AllDirectories))
{
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);
File.Copy(file, Path.Combine(outputDataDir, file.Substring(publishOutputTempDir.Length + 1)));
}
}
private string DetermineRuntimeIdentifierOS(string platform)
=> OS.DotNetOSPlatformMap[platform];
private string DetermineRuntimeIdentifierArch(string arch)
{
return arch switch
{
"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()
{
base._ExportEnd();
@ -316,8 +198,10 @@ namespace GodotTools.Export
if (Directory.Exists(aotTempDir))
Directory.Delete(aotTempDir, recursive: true);
// TODO: Just a workaround until the export plugins can be made to abort with errors
if (!string.IsNullOrEmpty(_maybeLastExportError)) // Check empty as well, because it's set to empty after hot-reloading
// TODO: The following is just a workaround until the export plugins can be made to abort with errors
// We check for empty as well, because it's set to empty after hot-reloading
if (!string.IsNullOrEmpty(_maybeLastExportError))
{
string lastExportError = _maybeLastExportError;
_maybeLastExportError = null;
@ -326,69 +210,11 @@ namespace GodotTools.Export
}
}
[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;
}
@ -396,87 +222,11 @@ 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");
string appNameSafe = appName.ToSafeDirName();
return $"data_{appNameSafe}";
}
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_GetExportedAssemblyDependencies(Godot.Collections.Dictionary<string, string> initialAssemblies,
string buildConfig, string customBclDir, Godot.Collections.Dictionary<string, string> dependencyAssemblies);
}
}

View File

@ -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;
@ -39,28 +40,27 @@ namespace GodotTools
public bool SkipBuildBeforePlaying { get; set; } = false;
public static string ProjectAssemblyName
[UsedImplicitly]
private bool CreateProjectSolutionIfNeeded()
{
get
if (!File.Exists(GodotSharpDirs.ProjectSlnPath) || !File.Exists(GodotSharpDirs.ProjectCsProjPath))
{
string projectAssemblyName = (string)ProjectSettings.GetSetting("application/config/name");
projectAssemblyName = projectAssemblyName.ToSafeDirName();
if (string.IsNullOrEmpty(projectAssemblyName))
projectAssemblyName = "UnnamedProject";
return projectAssemblyName;
return CreateProjectSolution();
}
return true;
}
private bool CreateProjectSolution()
{
using (var pr = new EditorProgress("create_csharp_solution", "Generating solution...".TTR(), 3))
using (var pr = new EditorProgress("create_csharp_solution", "Generating solution...".TTR(), 2))
{
pr.Step("Generating C# project...".TTR());
string resourceDir = ProjectSettings.GlobalizePath("res://");
string path = resourceDir;
string name = ProjectAssemblyName;
string name = GodotSharpDirs.ProjectAssemblyName;
string guid = CsProjOperations.GenerateGameProject(path, name);
@ -75,7 +75,7 @@ namespace GodotTools
{
Guid = guid,
PathRelativeToSolution = name + ".csproj",
Configs = new List<string> {"Debug", "ExportDebug", "ExportRelease"}
Configs = new List<string> { "Debug", "ExportDebug", "ExportRelease" }
};
solution.AddNewProject(name, projectInfo);
@ -90,24 +90,6 @@ namespace GodotTools
return false;
}
pr.Step("Updating Godot API assemblies...".TTR());
string debugApiAssembliesError = Internal.UpdateApiAssembliesFromPrebuilt("Debug");
if (!string.IsNullOrEmpty(debugApiAssembliesError))
{
ShowErrorDialog("Failed to update the Godot API assemblies: " + debugApiAssembliesError);
return false;
}
string releaseApiAssembliesError = Internal.UpdateApiAssembliesFromPrebuilt("Release");
if (!string.IsNullOrEmpty(releaseApiAssembliesError))
{
ShowErrorDialog("Failed to update the Godot API assemblies: " + releaseApiAssembliesError);
return false;
}
pr.Step("Done".TTR());
// Here, after all calls to progress_task_step
@ -141,7 +123,8 @@ namespace GodotTools
try
{
string fallbackFolder = NuGetUtils.GodotFallbackFolderPath;
NuGetUtils.AddFallbackFolderToUserNuGetConfigs(NuGetUtils.GodotFallbackFolderName, fallbackFolder);
NuGetUtils.AddFallbackFolderToUserNuGetConfigs(NuGetUtils.GodotFallbackFolderName,
fallbackFolder);
NuGetUtils.AddBundledPackagesToFallbackFolder(fallbackFolder);
}
catch (Exception e)
@ -167,13 +150,6 @@ namespace GodotTools
Instance.MSBuildPanel.BuildSolution();
}
public override void _Ready()
{
base._Ready();
MSBuildPanel.BuildOutputView.BuildStateChanged += BuildStateChanged;
}
private enum MenuOptions
{
CreateSln,
@ -197,7 +173,7 @@ namespace GodotTools
[UsedImplicitly]
public Error OpenInExternalEditor(Script script, int line, int col)
{
var editorId = (ExternalEditorId)_editorSettings.GetSetting("mono/editor/external_editor");
var editorId = (ExternalEditorId)(int)_editorSettings.GetSetting("mono/editor/external_editor");
switch (editorId)
{
@ -219,13 +195,15 @@ namespace GodotTools
try
{
if (Godot.OS.IsStdoutVerbose())
Console.WriteLine($"Running: \"{command}\" {string.Join(" ", args.Select(a => $"\"{a}\""))}");
Console.WriteLine(
$"Running: \"{command}\" {string.Join(" ", args.Select(a => $"\"{a}\""))}");
OS.RunProcess(command, args);
}
catch (Exception e)
{
GD.PushError($"Error when trying to run code editor: VisualStudio. Exception message: '{e.Message}'");
GD.PushError(
$"Error when trying to run code editor: VisualStudio. Exception message: '{e.Message}'");
}
break;
@ -347,7 +325,8 @@ namespace GodotTools
[UsedImplicitly]
public bool OverridesExternalEditor()
{
return (ExternalEditorId)_editorSettings.GetSetting("mono/editor/external_editor") != ExternalEditorId.None;
return (ExternalEditorId)(int)_editorSettings.GetSetting("mono/editor/external_editor") !=
ExternalEditorId.None;
}
public override bool _Build()
@ -368,7 +347,7 @@ namespace GodotTools
// NOTE: The order in which changes are made to the project is important
// Migrate to MSBuild project Sdks style if using the old style
ProjectUtils.MigrateToProjectSdksStyle(msbuildProject, ProjectAssemblyName);
ProjectUtils.MigrateToProjectSdksStyle(msbuildProject, GodotSharpDirs.ProjectAssemblyName);
ProjectUtils.EnsureGodotSdkIsUpToDate(msbuildProject);
@ -400,18 +379,49 @@ namespace GodotTools
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();
_editorSettings = editorInterface.GetEditorSettings();
GodotSharpDirs.RegisterProjectSettings();
_errorDialog = new AcceptDialog();
editorBaseControl.AddChild(_errorDialog);
MSBuildPanel = new MSBuildPanel();
MSBuildPanel.Ready += () =>
MSBuildPanel.BuildOutputView.BuildStateChanged += BuildStateChanged;
_bottomPanelBtn = AddControlToBottomPanel(MSBuildPanel, "MSBuild".TTR());
AddChild(new HotReloadAssemblyWatcher {Name = "HotReloadAssemblyWatcher"});
AddChild(new HotReloadAssemblyWatcher { Name = "HotReloadAssemblyWatcher" });
_menuPopup = new PopupMenu();
_menuPopup.Hide();
@ -472,9 +482,9 @@ namespace GodotTools
_editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary
{
["type"] = Variant.Type.Int,
["type"] = (int)Variant.Type.Int,
["name"] = "mono/editor/external_editor",
["hint"] = PropertyHint.Enum,
["hint"] = (int)PropertyHint.Enum,
["hint_string"] = settingsHintStr
});
@ -487,7 +497,8 @@ namespace GodotTools
try
{
// At startup we make sure NuGet.Config files have our Godot NuGet fallback folder included
NuGetUtils.AddFallbackFolderToUserNuGetConfigs(NuGetUtils.GodotFallbackFolderName, NuGetUtils.GodotFallbackFolderPath);
NuGetUtils.AddFallbackFolderToUserNuGetConfigs(NuGetUtils.GodotFallbackFolderName,
NuGetUtils.GodotFallbackFolderPath);
}
catch (Exception e)
{
@ -503,20 +514,23 @@ namespace GodotTools
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (_exportPluginWeak != null)
if (disposing)
{
// We need to dispose our export plugin before the editor destroys EditorSettings.
// Otherwise, if the GC disposes it at a later time, EditorExportPlatformAndroid
// will be freed after EditorSettings already was, and its device polling thread
// will try to access the EditorSettings singleton, resulting in null dereferencing.
(_exportPluginWeak.GetRef() as ExportPlugin)?.Dispose();
if (IsInstanceValid(_exportPluginWeak))
{
// We need to dispose our export plugin before the editor destroys EditorSettings.
// Otherwise, if the GC disposes it at a later time, EditorExportPlatformAndroid
// will be freed after EditorSettings already was, and its device polling thread
// will try to access the EditorSettings singleton, resulting in null dereferencing.
(_exportPluginWeak.GetRef().AsGodotObject() as ExportPlugin)?.Dispose();
_exportPluginWeak.Dispose();
_exportPluginWeak.Dispose();
}
GodotIdeManager?.Dispose();
}
GodotIdeManager?.Dispose();
base.Dispose(disposing);
}
public void OnBeforeSerialize()
@ -533,8 +547,10 @@ namespace GodotTools
public static GodotSharpEditor Instance { get; private set; }
[UsedImplicitly]
private GodotSharpEditor()
private static IntPtr InternalCreateInstance(IntPtr unmanagedCallbacks, int unmanagedCallbacksSize)
{
Internal.Initialize(unmanagedCallbacks, unmanagedCallbacksSize);
return new GodotSharpEditor().NativeInstance;
}
}
}

View File

@ -1,14 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ProjectGuid>{27B00618-A6F2-4828-B922-05CAEB08C286}</ProjectGuid>
<TargetFramework>net472</TargetFramework>
<LangVersion>7.2</LangVersion>
<TargetFramework>net6.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
<LangVersion>10</LangVersion>
<!-- The Godot editor uses the Debug Godot API assemblies -->
<GodotApiConfiguration>Debug</GodotApiConfiguration>
<GodotSourceRootPath>$(SolutionDir)/../../../../</GodotSourceRootPath>
<GodotOutputDataDir>$(GodotSourceRootPath)/bin/GodotSharp</GodotOutputDataDir>
<GodotApiAssembliesDir>$(GodotOutputDataDir)/Api/$(GodotApiConfiguration)</GodotApiAssembliesDir>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
<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' -->
@ -20,6 +30,8 @@
<PackageReference Include="JetBrains.Annotations" Version="2019.1.3.0" ExcludeAssets="runtime" PrivateAssets="all" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<!-- For RiderPathLocator -->
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<Reference Include="GodotSharp">
<HintPath>$(GodotApiAssembliesDir)/GodotSharp.dll</HintPath>
<Private>False</Private>
@ -29,6 +41,10 @@
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Godot.NET.Sdk\Godot.SourceGenerators\Godot.SourceGenerators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\..\..\glue\GodotSharp\Godot.SourceGenerators.Internal\Godot.SourceGenerators.Internal.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GodotTools.BuildLogger\GodotTools.BuildLogger.csproj" />
<ProjectReference Include="..\GodotTools.IdeMessaging\GodotTools.IdeMessaging.csproj" />

View File

@ -1,10 +1,11 @@
using Godot;
using GodotTools.Internals;
using JetBrains.Annotations;
using static GodotTools.Internals.Globals;
namespace GodotTools
{
public class HotReloadAssemblyWatcher : Node
public partial class HotReloadAssemblyWatcher : Node
{
private Timer _watchTimer;
@ -25,6 +26,7 @@ namespace GodotTools
Internal.ReloadAssemblies(softReload: false);
}
[UsedImplicitly]
public void RestartTimer()
{
_watchTimer.Stop();
@ -38,7 +40,7 @@ namespace GodotTools
_watchTimer = new Timer
{
OneShot = false,
WaitTime = (float)EditorDef("mono/assembly_watch_interval_sec", 0.5)
WaitTime = 0.5f
};
_watchTimer.Timeout += TimerTimeout;
AddChild(_watchTimer);

View File

@ -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;
@ -76,7 +76,7 @@ namespace GodotTools.Ides
public async Task<EditorPick?> LaunchIdeAsync(int millisecondsTimeout = 10000)
{
var editorId = (ExternalEditorId)GodotSharpEditor.Instance.GetEditorInterface()
var editorId = (ExternalEditorId)(int)GodotSharpEditor.Instance.GetEditorInterface()
.GetEditorSettings().GetSetting("mono/editor/external_editor");
string editorIdentity = GetExternalEditorIdentity(editorId);

View File

@ -1,9 +1,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Runtime.Versioning;
using Godot;
using JetBrains.Annotations;
using Microsoft.Win32;
using Newtonsoft.Json;
using Directory = System.IO.Directory;
@ -113,6 +114,7 @@ namespace GodotTools.Ides.Rider
return installInfos.ToArray();
}
[SupportedOSPlatform("windows")]
private static RiderInfo[] CollectRiderInfosWindows()
{
var installInfos = new List<RiderInfo>();
@ -217,6 +219,7 @@ namespace GodotTools.Ides.Rider
throw new Exception("Unknown OS.");
}
[SupportedOSPlatform("windows")]
private static void CollectPathsFromRegistry(string registryKey, List<string> installPaths)
{
using (var key = Registry.CurrentUser.OpenSubKey(registryKey))
@ -229,6 +232,7 @@ namespace GodotTools.Ides.Rider
}
}
[SupportedOSPlatform("windows")]
private static void CollectPathsFromRegistry(List<string> installPaths, RegistryKey key)
{
if (key == null) return;
@ -324,7 +328,7 @@ namespace GodotTools.Ides.Rider
{
public string install_location;
[CanBeNull]
[return: MaybeNull]
public static string GetInstallLocationFromJson(string json)
{
try
@ -378,7 +382,7 @@ namespace GodotTools.Ides.Rider
public string version;
public string versionSuffix;
[CanBeNull]
[return: MaybeNull]
internal static ProductInfo GetProductInfo(string json)
{
try
@ -402,7 +406,7 @@ namespace GodotTools.Ides.Rider
// ReSharper disable once InconsistentNaming
public ActiveApplication active_application;
[CanBeNull]
[return: MaybeNull]
public static string GetLatestBuildFromJson(string json)
{
try

View File

@ -22,7 +22,7 @@ namespace GodotTools.Ides.Rider
public static void Initialize()
{
var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
var editor = (ExternalEditorId)editorSettings.GetSetting("mono/editor/external_editor");
var editor = (ExternalEditorId)(int)editorSettings.GetSetting("mono/editor/external_editor");
if (editor == ExternalEditorId.Rider)
{
if (!editorSettings.HasSetting(EditorPathSettingName))
@ -30,9 +30,9 @@ namespace GodotTools.Ides.Rider
Globals.EditorDef(EditorPathSettingName, "Optional");
editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary
{
["type"] = Variant.Type.String,
["type"] = (int)Variant.Type.String,
["name"] = EditorPathSettingName,
["hint"] = PropertyHint.File,
["hint"] = (int)PropertyHint.File,
["hint_string"] = ""
});
}

View File

@ -1,6 +1,7 @@
using System;
using System.Runtime.CompilerServices;
using Godot;
using Godot.NativeInterop;
namespace GodotTools.Internals
{
@ -8,19 +9,12 @@ namespace GodotTools.Internals
{
public string Task { get; }
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_Create(string task, string label, int amount, bool canCancel);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_Dispose(string task);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool internal_Step(string task, string state, int step, bool forceRefresh);
public EditorProgress(string task, string label, int amount, bool canCancel = false)
{
Task = task;
internal_Create(task, label, amount, canCancel);
using godot_string taskIn = Marshaling.ConvertStringToNative(task);
using godot_string labelIn = Marshaling.ConvertStringToNative(label);
Internal.godot_icall_EditorProgress_Create(taskIn, labelIn, amount, canCancel);
}
~EditorProgress()
@ -33,18 +27,23 @@ namespace GodotTools.Internals
public void Dispose()
{
internal_Dispose(Task);
using godot_string taskIn = Marshaling.ConvertStringToNative(Task);
Internal.godot_icall_EditorProgress_Dispose(taskIn);
GC.SuppressFinalize(this);
}
public void Step(string state, int step = -1, bool forceRefresh = true)
{
internal_Step(Task, state, step, forceRefresh);
using godot_string taskIn = Marshaling.ConvertStringToNative(Task);
using godot_string stateIn = Marshaling.ConvertStringToNative(state);
Internal.godot_icall_EditorProgress_Step(taskIn, stateIn, step, forceRefresh);
}
public bool TryStep(string state, int step = -1, bool forceRefresh = true)
{
return internal_Step(Task, state, step, forceRefresh);
using godot_string taskIn = Marshaling.ConvertStringToNative(Task);
using godot_string stateIn = Marshaling.ConvertStringToNative(state);
return Internal.godot_icall_EditorProgress_Step(taskIn, stateIn, step, forceRefresh);
}
}
}

View File

@ -1,3 +1,4 @@
using Godot.NativeInterop;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
@ -5,35 +6,41 @@ namespace GodotTools.Internals
{
public static class Globals
{
public static float EditorScale => internal_EditorScale();
public static float EditorScale => Internal.godot_icall_Globals_EditorScale();
public static object GlobalDef(string setting, object defaultValue, bool restartIfChanged = false) =>
internal_GlobalDef(setting, defaultValue, restartIfChanged);
public static unsafe object GlobalDef(string setting, object defaultValue, bool restartIfChanged = false)
{
using godot_string settingIn = Marshaling.ConvertStringToNative(setting);
using godot_variant defaultValueIn = Marshaling.ConvertManagedObjectToVariant(defaultValue);
Internal.godot_icall_Globals_GlobalDef(settingIn, defaultValueIn, restartIfChanged, out godot_variant result);
using (result)
return Marshaling.ConvertVariantToManagedObject(result);
}
public static object EditorDef(string setting, object defaultValue, bool restartIfChanged = false) =>
internal_EditorDef(setting, defaultValue, restartIfChanged);
public static unsafe object EditorDef(string setting, object defaultValue, bool restartIfChanged = false)
{
using godot_string settingIn = Marshaling.ConvertStringToNative(setting);
using godot_variant defaultValueIn = Marshaling.ConvertManagedObjectToVariant(defaultValue);
Internal.godot_icall_Globals_EditorDef(settingIn, defaultValueIn, restartIfChanged, out godot_variant result);
using (result)
return Marshaling.ConvertVariantToManagedObject(result);
}
public static object EditorShortcut(string setting) =>
internal_EditorShortcut(setting);
public static object EditorShortcut(string setting)
{
using godot_string settingIn = Marshaling.ConvertStringToNative(setting);
Internal.godot_icall_Globals_EditorShortcut(settingIn, out godot_variant result);
using (result)
return Marshaling.ConvertVariantToManagedObject(result);
}
[SuppressMessage("ReSharper", "InconsistentNaming")]
public static string TTR(this string text) => internal_TTR(text);
// Internal Calls
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern float internal_EditorScale();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern object internal_GlobalDef(string setting, object defaultValue, bool restartIfChanged);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern object internal_EditorDef(string setting, object defaultValue, bool restartIfChanged);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern object internal_EditorShortcut(string setting);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_TTR(string text);
public static string TTR(this string text)
{
using godot_string textIn = Marshaling.ConvertStringToNative(text);
Internal.godot_icall_Globals_TTR(textIn, out godot_string dest);
using (dest)
return Marshaling.ConvertStringToManaged(dest);
}
}
}

View File

@ -1,103 +1,125 @@
using System.Runtime.CompilerServices;
using System.IO;
using Godot;
using Godot.NativeInterop;
using GodotTools.Core;
using static GodotTools.Internals.Globals;
namespace GodotTools.Internals
{
public static class GodotSharpDirs
{
public static string ResDataDir => internal_ResDataDir();
public static string ResMetadataDir => internal_ResMetadataDir();
public static string ResAssembliesBaseDir => internal_ResAssembliesBaseDir();
public static string ResAssembliesDir => internal_ResAssembliesDir();
public static string ResConfigDir => internal_ResConfigDir();
public static string ResTempDir => internal_ResTempDir();
public static string ResTempAssembliesBaseDir => internal_ResTempAssembliesBaseDir();
public static string ResTempAssembliesDir => internal_ResTempAssembliesDir();
public static string ResMetadataDir
{
get
{
Internal.godot_icall_GodotSharpDirs_ResMetadataDir(out godot_string dest);
using (dest)
return Marshaling.ConvertStringToManaged(dest);
}
}
public static string MonoUserDir => internal_MonoUserDir();
public static string MonoLogsDir => internal_MonoLogsDir();
public static string MonoUserDir
{
get
{
Internal.godot_icall_GodotSharpDirs_MonoUserDir(out godot_string dest);
using (dest)
return Marshaling.ConvertStringToManaged(dest);
}
}
#region Tools-only
public static string MonoSolutionsDir => internal_MonoSolutionsDir();
public static string BuildLogsDirs => internal_BuildLogsDirs();
public static string BuildLogsDirs
{
get
{
Internal.godot_icall_GodotSharpDirs_BuildLogsDirs(out godot_string dest);
using (dest)
return Marshaling.ConvertStringToManaged(dest);
}
}
public static string ProjectSlnPath => internal_ProjectSlnPath();
public static string ProjectCsProjPath => internal_ProjectCsProjPath();
public static string DataEditorToolsDir
{
get
{
Internal.godot_icall_GodotSharpDirs_DataEditorToolsDir(out godot_string dest);
using (dest)
return Marshaling.ConvertStringToManaged(dest);
}
}
public static string DataEditorToolsDir => internal_DataEditorToolsDir();
public static string DataEditorPrebuiltApiDir => internal_DataEditorPrebuiltApiDir();
#endregion
public static void RegisterProjectSettings()
{
GlobalDef("dotnet/project/assembly_name", "");
GlobalDef("dotnet/project/solution_directory", "");
GlobalDef("dotnet/project/c#_project_directory", "");
}
public static string DataMonoEtcDir => internal_DataMonoEtcDir();
public static string DataMonoLibDir => internal_DataMonoLibDir();
private static void DetermineProjectLocation()
{
static string DetermineProjectName()
{
string projectAssemblyName = (string)ProjectSettings.GetSetting("application/config/name");
projectAssemblyName = projectAssemblyName.ToSafeDirName();
if (string.IsNullOrEmpty(projectAssemblyName))
projectAssemblyName = "UnnamedProject";
return projectAssemblyName;
}
#region Windows-only
public static string DataMonoBinDir => internal_DataMonoBinDir();
#endregion
_projectAssemblyName = (string)ProjectSettings.GetSetting("dotnet/project/assembly_name");
if (string.IsNullOrEmpty(_projectAssemblyName))
{
_projectAssemblyName = DetermineProjectName();
ProjectSettings.SetSetting("dotnet/project/assembly_name", _projectAssemblyName);
}
string slnParentDir = (string)ProjectSettings.GetSetting("dotnet/project/solution_directory");
if (string.IsNullOrEmpty(slnParentDir))
slnParentDir = "res://";
#region Internal
string csprojParentDir = (string)ProjectSettings.GetSetting("dotnet/project/c#_project_directory");
if (string.IsNullOrEmpty(csprojParentDir))
csprojParentDir = "res://";
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_ResDataDir();
_projectSlnPath = Path.Combine(ProjectSettings.GlobalizePath(slnParentDir),
string.Concat(_projectAssemblyName, ".sln"));
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_ResMetadataDir();
_projectCsProjPath = Path.Combine(ProjectSettings.GlobalizePath(csprojParentDir),
string.Concat(_projectAssemblyName, ".csproj"));
}
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_ResAssembliesBaseDir();
private static string _projectAssemblyName;
private static string _projectSlnPath;
private static string _projectCsProjPath;
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_ResAssembliesDir();
public static string ProjectAssemblyName
{
get
{
if (_projectAssemblyName == null)
DetermineProjectLocation();
return _projectAssemblyName;
}
}
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_ResConfigDir();
public static string ProjectSlnPath
{
get
{
if (_projectSlnPath == null)
DetermineProjectLocation();
return _projectSlnPath;
}
}
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_ResTempDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_ResTempAssembliesBaseDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_ResTempAssembliesDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_MonoUserDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_MonoLogsDir();
#region Tools-only
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_MonoSolutionsDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_BuildLogsDirs();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_ProjectSlnPath();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_ProjectCsProjPath();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_DataEditorToolsDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_DataEditorPrebuiltApiDir();
#endregion
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_DataMonoEtcDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_DataMonoLibDir();
#region Windows-only
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_DataMonoBinDir();
#endregion
#endregion
public static string ProjectCsProjPath
{
get
{
if (_projectCsProjPath == null)
DetermineProjectLocation();
return _projectCsProjPath;
}
}
}
}

View File

@ -1,114 +1,161 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Godot;
using Godot.NativeInterop;
using Godot.SourceGenerators.Internal;
using GodotTools.IdeMessaging.Requests;
namespace GodotTools.Internals
{
public static class Internal
[SuppressMessage("ReSharper", "InconsistentNaming")]
[GenerateUnmanagedCallbacks(typeof(InternalUnmanagedCallbacks))]
internal static partial class Internal
{
public const string CSharpLanguageType = "CSharpScript";
public const string CSharpLanguageExtension = ".cs";
public static string UpdateApiAssembliesFromPrebuilt(string config) =>
internal_UpdateApiAssembliesFromPrebuilt(config);
public static string FullExportTemplatesDir
{
get
{
godot_icall_Internal_FullExportTemplatesDir(out godot_string dest);
using (dest)
return Marshaling.ConvertStringToManaged(dest);
}
}
public static string FullExportTemplatesDir =>
internal_FullExportTemplatesDir();
public static string SimplifyGodotPath(this string path) => Godot.StringExtensions.SimplifyPath(path);
public static string SimplifyGodotPath(this string path) => internal_SimplifyGodotPath(path);
public static bool IsMacOSAppBundleInstalled(string bundleId)
{
using godot_string bundleIdIn = Marshaling.ConvertStringToNative(bundleId);
return godot_icall_Internal_IsMacOSAppBundleInstalled(bundleIdIn);
}
public static bool IsMacOSAppBundleInstalled(string bundleId) => internal_IsMacOSAppBundleInstalled(bundleId);
public static bool GodotIs32Bits() => godot_icall_Internal_GodotIs32Bits();
public static bool GodotIs32Bits() => internal_GodotIs32Bits();
public static bool GodotIsRealTDouble() => godot_icall_Internal_GodotIsRealTDouble();
public static bool GodotIsRealTDouble() => internal_GodotIsRealTDouble();
public static void GodotMainIteration() => godot_icall_Internal_GodotMainIteration();
public static void GodotMainIteration() => internal_GodotMainIteration();
public static bool IsAssembliesReloadingNeeded() => godot_icall_Internal_IsAssembliesReloadingNeeded();
public static ulong GetCoreApiHash() => internal_GetCoreApiHash();
public static void ReloadAssemblies(bool softReload) => godot_icall_Internal_ReloadAssemblies(softReload);
public static ulong GetEditorApiHash() => internal_GetEditorApiHash();
public static bool IsAssembliesReloadingNeeded() => internal_IsAssembliesReloadingNeeded();
public static void ReloadAssemblies(bool softReload) => internal_ReloadAssemblies(softReload);
public static void EditorDebuggerNodeReloadScripts() => internal_EditorDebuggerNodeReloadScripts();
public static void EditorDebuggerNodeReloadScripts() => godot_icall_Internal_EditorDebuggerNodeReloadScripts();
public static bool ScriptEditorEdit(Resource resource, int line, int col, bool grabFocus = true) =>
internal_ScriptEditorEdit(resource, line, col, grabFocus);
godot_icall_Internal_ScriptEditorEdit(resource.NativeInstance, line, col, grabFocus);
public static void EditorNodeShowScriptScreen() => internal_EditorNodeShowScriptScreen();
public static void EditorNodeShowScriptScreen() => godot_icall_Internal_EditorNodeShowScriptScreen();
public static string MonoWindowsInstallRoot => internal_MonoWindowsInstallRoot();
public static void EditorRunPlay() => godot_icall_Internal_EditorRunPlay();
public static void EditorRunPlay() => internal_EditorRunPlay();
public static void EditorRunStop() => godot_icall_Internal_EditorRunStop();
public static void EditorRunStop() => internal_EditorRunStop();
public static void ScriptEditorDebugger_ReloadScripts() =>
godot_icall_Internal_ScriptEditorDebugger_ReloadScripts();
public static void ScriptEditorDebugger_ReloadScripts() => internal_ScriptEditorDebugger_ReloadScripts();
public static string[] CodeCompletionRequest(CodeCompletionRequest.CompletionKind kind, string scriptFile) =>
internal_CodeCompletionRequest((int)kind, scriptFile);
public static string[] CodeCompletionRequest(CodeCompletionRequest.CompletionKind kind,
string scriptFile)
{
using godot_string scriptFileIn = Marshaling.ConvertStringToNative(scriptFile);
godot_icall_Internal_CodeCompletionRequest((int)kind, scriptFileIn, out godot_packed_string_array res);
using (res)
return Marshaling.ConvertNativePackedStringArrayToSystemArray(res);
}
#region Internal
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_UpdateApiAssembliesFromPrebuilt(string config);
private static bool initialized = false;
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_FullExportTemplatesDir();
// ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Global
internal static unsafe void Initialize(IntPtr unmanagedCallbacks, int unmanagedCallbacksSize)
{
if (initialized)
throw new InvalidOperationException("Already initialized");
initialized = true;
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_SimplifyGodotPath(this string path);
if (unmanagedCallbacksSize != sizeof(InternalUnmanagedCallbacks))
throw new ArgumentException("Unmanaged callbacks size mismatch");
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool internal_IsMacOSAppBundleInstalled(string bundleId);
_unmanagedCallbacks = Unsafe.AsRef<InternalUnmanagedCallbacks>((void*)unmanagedCallbacks);
}
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool internal_GodotIs32Bits();
private partial struct InternalUnmanagedCallbacks
{
}
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool internal_GodotIsRealTDouble();
/*
* IMPORTANT:
* The order of the methods defined in NativeFuncs must match the order
* in the array defined at the bottom of 'editor/editor_internal_calls.cpp'.
*/
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_GodotMainIteration();
public static partial void godot_icall_GodotSharpDirs_ResMetadataDir(out godot_string r_dest);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern ulong internal_GetCoreApiHash();
public static partial void godot_icall_GodotSharpDirs_MonoUserDir(out godot_string r_dest);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern ulong internal_GetEditorApiHash();
public static partial void godot_icall_GodotSharpDirs_BuildLogsDirs(out godot_string r_dest);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool internal_IsAssembliesReloadingNeeded();
public static partial void godot_icall_GodotSharpDirs_DataEditorToolsDir(out godot_string r_dest);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_ReloadAssemblies(bool softReload);
public static partial void godot_icall_EditorProgress_Create(in godot_string task, in godot_string label,
int amount, bool canCancel);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_EditorDebuggerNodeReloadScripts();
public static partial void godot_icall_EditorProgress_Dispose(in godot_string task);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool internal_ScriptEditorEdit(Resource resource, int line, int col, bool grabFocus);
public static partial bool godot_icall_EditorProgress_Step(in godot_string task, in godot_string state,
int step,
bool forceRefresh);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_EditorNodeShowScriptScreen();
private static partial void godot_icall_Internal_FullExportTemplatesDir(out godot_string dest);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_MonoWindowsInstallRoot();
private static partial bool godot_icall_Internal_IsMacOSAppBundleInstalled(in godot_string bundleId);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_EditorRunPlay();
private static partial bool godot_icall_Internal_GodotIs32Bits();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_EditorRunStop();
private static partial bool godot_icall_Internal_GodotIsRealTDouble();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_ScriptEditorDebugger_ReloadScripts();
private static partial void godot_icall_Internal_GodotMainIteration();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string[] internal_CodeCompletionRequest(int kind, string scriptFile);
private static partial bool godot_icall_Internal_IsAssembliesReloadingNeeded();
private static partial void godot_icall_Internal_ReloadAssemblies(bool softReload);
private static partial void godot_icall_Internal_EditorDebuggerNodeReloadScripts();
private static partial bool godot_icall_Internal_ScriptEditorEdit(IntPtr resource, int line, int col,
bool grabFocus);
private static partial void godot_icall_Internal_EditorNodeShowScriptScreen();
private static partial void godot_icall_Internal_EditorRunPlay();
private static partial void godot_icall_Internal_EditorRunStop();
private static partial void godot_icall_Internal_ScriptEditorDebugger_ReloadScripts();
private static partial void godot_icall_Internal_CodeCompletionRequest(int kind, in godot_string scriptFile,
out godot_packed_string_array res);
public static partial float godot_icall_Globals_EditorScale();
public static partial void godot_icall_Globals_GlobalDef(in godot_string setting, in godot_variant defaultValue,
bool restartIfChanged, out godot_variant result);
public static partial void godot_icall_Globals_EditorDef(in godot_string setting, in godot_variant defaultValue,
bool restartIfChanged, out godot_variant result);
public static partial void
godot_icall_Globals_EditorShortcut(in godot_string setting, out godot_variant result);
public static partial void godot_icall_Globals_TTR(in godot_string text, out godot_string dest);
public static partial void godot_icall_Utils_OS_GetPlatformName(out godot_string dest);
public static partial bool godot_icall_Utils_OS_UnixFileHasExecutableAccess(in godot_string filePath);
#endregion
}

View File

@ -1,14 +1,14 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using Godot;
using GodotTools.Core;
using JetBrains.Annotations;
namespace GodotTools.Utils
{
public static class FsPathUtils
{
private static readonly string _resourcePath = ProjectSettings.GlobalizePath("res://");
private static readonly string ResourcePath = ProjectSettings.GlobalizePath("res://");
private static bool PathStartsWithAlreadyNorm(this string childPath, string parentPath)
{
@ -30,11 +30,11 @@ namespace GodotTools.Utils
return childPathNorm.PathStartsWithAlreadyNorm(parentPathNorm);
}
[CanBeNull]
[return: MaybeNull]
public static string LocalizePathWithCaseChecked(string path)
{
string pathNorm = path.NormalizePath() + Path.DirectorySeparatorChar;
string resourcePathNorm = _resourcePath.NormalizePath() + Path.DirectorySeparatorChar;
string resourcePathNorm = ResourcePath.NormalizePath() + Path.DirectorySeparatorChar;
if (!pathNorm.PathStartsWithAlreadyNorm(resourcePathNorm))
return null;

View File

@ -1,24 +1,21 @@
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 JetBrains.Annotations;
using System.Runtime.Versioning;
using System.Text;
using GodotTools.Internals;
namespace GodotTools.Utils
{
[SuppressMessage("ReSharper", "InconsistentNaming")]
public static class OS
{
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string GetPlatformName();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool UnixFileHasExecutableAccess(string filePath);
public static class Names
private static class Names
{
public const string Windows = "Windows";
public const string MacOS = "macOS";
@ -47,6 +44,35 @@ namespace GodotTools.Utils
public const string HTML5 = "javascript";
}
public 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,
@ -63,14 +89,40 @@ namespace GodotTools.Utils
[Names.HTML5] = Platforms.HTML5
};
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)
{
return name.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
Internal.godot_icall_Utils_OS_GetPlatformName(out godot_string dest);
using (dest)
{
string platformName = Marshaling.ConvertStringToManaged(dest);
return name.Equals(platformName, StringComparison.OrdinalIgnoreCase);
}
}
private static bool IsAnyOS(IEnumerable<string> names)
{
return names.Any(p => p.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase));
Internal.godot_icall_Utils_OS_GetPlatformName(out godot_string dest);
using (dest)
{
string platformName = Marshaling.ConvertStringToManaged(dest);
return names.Any(p => p.Equals(platformName, StringComparison.OrdinalIgnoreCase));
}
}
private static readonly IEnumerable<string> LinuxBSDPlatforms =
@ -80,30 +132,39 @@ namespace GodotTools.Utils
new[] { Names.MacOS, Names.Server, Names.Haiku, Names.Android, Names.iOS }
.Concat(LinuxBSDPlatforms).ToArray();
private static readonly Lazy<bool> _isWindows = new Lazy<bool>(() => IsOS(Names.Windows));
private static readonly Lazy<bool> _isMacOS = new Lazy<bool>(() => IsOS(Names.MacOS));
private static readonly Lazy<bool> _isLinuxBSD = new Lazy<bool>(() => IsAnyOS(LinuxBSDPlatforms));
private static readonly Lazy<bool> _isServer = new Lazy<bool>(() => IsOS(Names.Server));
private static readonly Lazy<bool> _isUWP = new Lazy<bool>(() => IsOS(Names.UWP));
private static readonly Lazy<bool> _isHaiku = new Lazy<bool>(() => IsOS(Names.Haiku));
private static readonly Lazy<bool> _isAndroid = new Lazy<bool>(() => IsOS(Names.Android));
private static readonly Lazy<bool> _isiOS = new Lazy<bool>(() => IsOS(Names.iOS));
private static readonly Lazy<bool> _isHTML5 = new Lazy<bool>(() => IsOS(Names.HTML5));
private static readonly Lazy<bool> _isUnixLike = new Lazy<bool>(() => IsAnyOS(UnixLikePlatforms));
private static readonly Lazy<bool> _isWindows = new(() => IsOS(Names.Windows));
private static readonly Lazy<bool> _isMacOS = new(() => IsOS(Names.MacOS));
private static readonly Lazy<bool> _isLinuxBSD = new(() => IsAnyOS(LinuxBSDPlatforms));
private static readonly Lazy<bool> _isServer = new(() => IsOS(Names.Server));
private static readonly Lazy<bool> _isUWP = new(() => IsOS(Names.UWP));
private static readonly Lazy<bool> _isHaiku = new(() => IsOS(Names.Haiku));
private static readonly Lazy<bool> _isAndroid = new(() => IsOS(Names.Android));
private static readonly Lazy<bool> _isiOS = new(() => IsOS(Names.iOS));
private static readonly Lazy<bool> _isHTML5 = new(() => IsOS(Names.HTML5));
private static readonly Lazy<bool> _isUnixLike = new(() => IsAnyOS(UnixLikePlatforms));
[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 IsWindows => _isWindows.Value || IsUWP;
public static bool IsMacOS => _isMacOS.Value;
public static bool IsLinuxBSD => _isLinuxBSD.Value;
public static bool IsServer => _isServer.Value;
public static bool IsUWP => _isUWP.Value;
public static bool IsHaiku => _isHaiku.Value;
public static bool IsAndroid => _isAndroid.Value;
public static bool IsiOS => _isiOS.Value;
public static bool IsHTML5 => _isHTML5.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)
@ -112,9 +173,11 @@ namespace GodotTools.Utils
return PathWhichUnix(name);
}
[return: MaybeNull]
private static string PathWhichWindows([NotNull] string name)
{
string[] windowsExts = Environment.GetEnvironmentVariable("PATHEXT")?.Split(PathSep) ?? Array.Empty<string>();
string[] windowsExts =
Environment.GetEnvironmentVariable("PATHEXT")?.Split(PathSep) ?? Array.Empty<string>();
string[] pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep);
char[] invalidPathChars = Path.GetInvalidPathChars();
@ -133,7 +196,7 @@ namespace GodotTools.Utils
string nameExt = Path.GetExtension(name);
bool hasPathExt = !string.IsNullOrEmpty(nameExt) &&
windowsExts.Contains(nameExt, StringComparer.OrdinalIgnoreCase);
windowsExts.Contains(nameExt, StringComparer.OrdinalIgnoreCase);
searchDirs.Add(System.IO.Directory.GetCurrentDirectory()); // last in the list
@ -141,12 +204,13 @@ namespace GodotTools.Utils
return searchDirs.Select(dir => Path.Combine(dir, name)).FirstOrDefault(File.Exists);
return (from dir in searchDirs
select Path.Combine(dir, name)
select Path.Combine(dir, name)
into path
from ext in windowsExts
select path + ext).FirstOrDefault(File.Exists);
from ext in windowsExts
select path + ext).FirstOrDefault(File.Exists);
}
[return: MaybeNull]
private static string PathWhichUnix([NotNull] string name)
{
string[] pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep);
@ -168,19 +232,16 @@ namespace GodotTools.Utils
searchDirs.Add(System.IO.Directory.GetCurrentDirectory()); // last in the list
return searchDirs.Select(dir => Path.Combine(dir, name))
.FirstOrDefault(path => File.Exists(path) && UnixFileHasExecutableAccess(path));
.FirstOrDefault(path =>
{
using godot_string pathIn = Marshaling.ConvertStringToNative(path);
return File.Exists(path) && Internal.godot_icall_Utils_OS_UnixFileHasExecutableAccess(pathIn);
});
}
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,
@ -188,44 +249,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());
using var process = new Process { StartInfo = startInfo };
process.Start();
process.WaitForExit();
return process.ExitCode;
}
private static void AppendProcessFileNameForDisplay(this StringBuilder builder, string fileName)
{
if (builder.Length > 0)
builder.Append(' ');
if (fileName.Contains(' '))
{
builder.Append('"');
builder.Append(fileName);
builder.Append('"');
}
else
{
builder.Append(fileName);
}
}
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.
foreach (string argument in argumentList)
{
if (builder.Length > 0)
builder.Append(' ');
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);
}
var startInfo = new ProcessStartInfo(command, CmdLineArgsToString(arguments));
Console.WriteLine($"Executing: \"{startInfo.FileName}\" {startInfo.Arguments}");
// Print the output
startInfo.RedirectStandardOutput = false;
startInfo.RedirectStandardError = false;
startInfo.UseShellExecute = false;
using (var process = new Process { StartInfo = startInfo })
{
process.Start();
process.WaitForExit();
return process.ExitCode;
}
return builder;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -228,6 +228,23 @@ class BindingsGenerator {
bool is_singleton = false;
bool is_ref_counted = false;
/**
* Determines whether the native return value of this type must be disposed
* by the generated internal call (think of `godot_string`, whose destructor
* must be called). Some structs that are disposable may still disable this
* flag if the ownership is transferred.
*/
bool c_type_is_disposable_struct = false;
/**
* Determines whether the native return value of this type must be zero initialized
* before its address is passed to ptrcall. This is required for types whose destructor
* is called before being assigned the return value by `PtrToArg::encode`, e.g.:
* Array, Dictionary, String, StringName, Variant.
* It's not necessary to set this to `true` if [c_type_is_disposable_struct] is already `true`.
*/
bool c_ret_needs_default_initialization = false;
/**
* Used only by Object-derived types.
* Determines if this type is not abstract (incomplete).
@ -242,32 +259,35 @@ class BindingsGenerator {
*/
bool memory_own = false;
/**
* This must be set to true for any struct bigger than 32-bits. Those cannot be passed/returned by value
* with internal calls, so we must use pointers instead. Returns must be replace with out parameters.
* In this case, [c_out] and [cs_out] must have a different format, explained below.
* The Mono IL interpreter icall trampolines don't support passing structs bigger than 32-bits by value (at least not on WASM).
*/
bool ret_as_byref_arg = false;
// !! The comments of the following fields make reference to other fields via square brackets, e.g.: [field_name]
// !! When renaming those fields, make sure to rename their references in the comments
// --- C INTERFACE ---
static const char *DEFAULT_VARARG_C_IN;
/**
* One or more statements that manipulate the parameter before being passed as argument of a ptrcall.
* One or more statements that transform the parameter before being passed as argument of a ptrcall.
* If the statement adds a local that must be passed as the argument instead of the parameter,
* the name of that local must be specified with [c_arg_in].
* For variadic methods, this field is required and, if empty, [DEFAULT_VARARG_C_IN] is used instead.
* the expression with the name of that local must be specified with [c_arg_in].
* Formatting elements:
* %0: [c_type] of the parameter
* %1: name of the parameter
* %2-4: reserved
* %5: indentation text
*/
String c_in;
/**
* One or more statements that transform the parameter before being passed as argument of a vararg call.
* If the statement adds a local that must be passed as the argument instead of the parameter,
* the name of that local must be specified with [c_arg_in].
* Formatting elements:
* %0: [c_type] of the parameter
* %1: name of the parameter
* %2-4: reserved
* %5: indentation text
*/
String c_in_vararg;
/**
* Determines the expression that will be passed as argument to ptrcall.
* By default the value equals the name of the parameter,
@ -291,7 +311,8 @@ class BindingsGenerator {
* %0: [c_type_out] of the return type
* %1: name of the variable to be returned
* %2: [name] of the return type
* %3: name of the parameter that must be assigned the return value
* %3-4: reserved
* %5: indentation text
*/
String c_out;
@ -327,7 +348,21 @@ class BindingsGenerator {
* An expression that overrides the way the parameter is passed to the internal call.
* If empty, the parameter is passed as is.
* Formatting elements:
* %0 or %s: name of the parameter
* %0: name of the parameter
* %1: [c_type] of the parameter
*/
String cs_in_expr;
bool cs_in_expr_is_unsafe = false;
/**
* One or more statements that transform the parameter before being passed to the internal call.
* If the statement adds a local that must be passed as the argument instead of the parameter,
* the expression with the name of that local must be specified with [cs_in_expr].
* Formatting elements:
* %0: [c_type] of the parameter
* %1: name of the parameter
* %2-4: reserved
* %5: indentation text
*/
String cs_in;
@ -338,7 +373,9 @@ class BindingsGenerator {
* %0: internal method name
* %1: internal method call arguments without surrounding parenthesis
* %2: [cs_type] of the return type
* %3: [im_type_out] of the return type
* %3: [c_type_out] of the return type
* %4: reserved
* %5: indentation text
*/
String cs_out;
@ -349,14 +386,20 @@ class BindingsGenerator {
String cs_type;
/**
* Type used for parameters of internal call methods.
* Formatting elements:
* %0: input expression of type `in godot_variant`
* %1: [cs_type] of this type
* %2: [name] of this type
*/
String im_type_in;
String cs_variant_to_managed;
/**
* Type used for the return type of internal call methods.
* Formatting elements:
* %0: input expression
* %1: [cs_type] of this type
* %2: [name] of this type
*/
String im_type_out;
String cs_managed_to_variant;
const DocData::ClassDoc *class_doc = nullptr;
@ -366,6 +409,8 @@ class BindingsGenerator {
List<MethodInterface> methods;
List<SignalInterface> signals_;
bool has_virtual_methods = false;
const MethodInterface *find_method_by_name(const StringName &p_cname) const {
for (const MethodInterface &E : methods) {
if (E.cname == p_cname) {
@ -432,8 +477,8 @@ class BindingsGenerator {
itype.c_type = itype.name;
itype.cs_type = itype.proxy_name;
itype.im_type_in = "ref " + itype.proxy_name;
itype.im_type_out = itype.proxy_name;
itype.c_type_in = itype.proxy_name + "*";
itype.c_type_out = itype.proxy_name;
itype.class_doc = &EditorHelp::get_doc_data()->class_list[itype.proxy_name];
}
@ -467,65 +512,27 @@ class BindingsGenerator {
return itype;
}
static void create_placeholder_type(TypeInterface &r_itype, const StringName &p_cname) {
r_itype.name = p_cname;
r_itype.cname = p_cname;
r_itype.proxy_name = r_itype.name;
r_itype.c_type = r_itype.name;
r_itype.c_type_in = "MonoObject*";
r_itype.c_type_out = "MonoObject*";
r_itype.cs_type = r_itype.proxy_name;
r_itype.im_type_in = r_itype.proxy_name;
r_itype.im_type_out = r_itype.proxy_name;
}
static void postsetup_enum_type(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.
r_enum_itype.c_arg_in = "&%s_in";
{
// The expected types for parameters and return value in ptrcall are 'int64_t' or 'uint64_t'.
r_enum_itype.c_in = "\t%0 %1_in = (%0)%1;\n";
r_enum_itype.c_out = "\treturn (%0)%1;\n";
r_enum_itype.c_type = "int64_t";
}
r_enum_itype.c_type_in = "int32_t";
r_enum_itype.c_type_out = r_enum_itype.c_type_in;
r_enum_itype.cs_type = r_enum_itype.proxy_name;
r_enum_itype.cs_in = "(int)%s";
r_enum_itype.cs_out = "return (%2)%0(%1);";
r_enum_itype.im_type_in = "int";
r_enum_itype.im_type_out = "int";
r_enum_itype.class_doc = &EditorHelp::get_doc_data()->class_list[r_enum_itype.proxy_name];
}
static void postsetup_enum_type(TypeInterface &r_enum_itype);
TypeInterface() {}
};
struct InternalCall {
String name;
String im_type_out; // Return type for the C# method declaration. Also used as companion of [unique_siq]
String im_sig; // Signature for the C# method declaration
String unique_sig; // Unique signature to avoid duplicates in containers
bool editor_only = false;
bool is_vararg = false;
bool is_static = false;
TypeReference return_type;
List<TypeReference> argument_types;
_FORCE_INLINE_ int get_arguments_count() const { return argument_types.size(); }
InternalCall() {}
InternalCall(const String &p_name, const String &p_im_type_out, const String &p_im_sig = String(), const String &p_unique_sig = String()) {
InternalCall(ClassDB::APIType api_type, const String &p_name, const String &p_unique_sig = String()) {
name = p_name;
im_type_out = p_im_type_out;
im_sig = p_im_sig;
unique_sig = p_unique_sig;
editor_only = false;
}
InternalCall(ClassDB::APIType api_type, const String &p_name, const String &p_im_type_out, const String &p_im_sig = String(), const String &p_unique_sig = String()) {
name = p_name;
im_type_out = p_im_type_out;
im_sig = p_im_sig;
unique_sig = p_unique_sig;
editor_only = api_type == ClassDB::API_EDITOR;
}
@ -540,7 +547,6 @@ class BindingsGenerator {
HashMap<StringName, TypeInterface> obj_types;
HashMap<StringName, TypeInterface> placeholder_types;
HashMap<StringName, TypeInterface> builtin_types;
HashMap<StringName, TypeInterface> enum_types;
@ -548,13 +554,9 @@ class BindingsGenerator {
List<ConstantInterface> global_constants;
List<InternalCall> method_icalls;
/// Stores the unique internal calls from [method_icalls] that are assigned to each method.
HashMap<const MethodInterface *, const InternalCall *> method_icalls_map;
List<const InternalCall *> generated_icall_funcs;
List<InternalCall> core_custom_icalls;
List<InternalCall> editor_custom_icalls;
HashMap<StringName, List<StringName>> blacklisted_methods;
void _initialize_blacklisted_methods();
@ -571,6 +573,8 @@ class BindingsGenerator {
StringName type_String = StaticCString::create("String");
StringName type_StringName = StaticCString::create("StringName");
StringName type_NodePath = StaticCString::create("NodePath");
StringName type_Array_generic = StaticCString::create("Array_@generic");
StringName type_Dictionary_generic = StaticCString::create("Dictionary_@generic");
StringName type_at_GlobalScope = StaticCString::create("@GlobalScope");
StringName enum_Error = StaticCString::create("Error");
@ -595,12 +599,14 @@ class BindingsGenerator {
StringName type_Vector4i = StaticCString::create("Vector4i");
// Object not included as it must be checked for all derived classes
static constexpr int nullable_types_count = 17;
static constexpr int nullable_types_count = 18;
StringName nullable_types[nullable_types_count] = {
type_String,
type_StringName,
type_NodePath,
type_Array_generic,
type_Dictionary_generic,
StaticCString::create(_STR(Array)),
StaticCString::create(_STR(Dictionary)),
StaticCString::create(_STR(Callable)),
@ -636,17 +642,6 @@ class BindingsGenerator {
NameCache name_cache;
const List<InternalCall>::Element *find_icall_by_name(const String &p_name, const List<InternalCall> &p_list) {
const List<InternalCall>::Element *it = p_list.front();
while (it) {
if (it->get().name == p_name) {
return it;
}
it = it->next();
}
return nullptr;
}
const ConstantInterface *find_constant_by_name(const String &p_name, const List<ConstantInterface> &p_constants) const {
for (const ConstantInterface &E : p_constants) {
if (E.name == p_name) {
@ -657,18 +652,38 @@ class BindingsGenerator {
return nullptr;
}
inline String get_unique_sig(const TypeInterface &p_type) {
if (p_type.is_ref_counted) {
return "Ref";
} else if (p_type.is_object_type) {
inline String get_arg_unique_sig(const TypeInterface &p_type) {
// For parameters, we treat reference and non-reference derived types the same.
if (p_type.is_object_type) {
return "Obj";
} else if (p_type.is_enum) {
return "int";
} else if (p_type.cname == name_cache.type_Array_generic) {
return "Array";
} else if (p_type.cname == name_cache.type_Dictionary_generic) {
return "Dictionary";
}
return p_type.name;
}
inline String get_ret_unique_sig(const TypeInterface *p_type) {
// Reference derived return types are treated differently.
if (p_type->is_ref_counted) {
return "Ref";
} else if (p_type->is_object_type) {
return "Obj";
} else if (p_type->is_enum) {
return "int";
} else if (p_type->cname == name_cache.type_Array_generic) {
return "Array";
} else if (p_type->cname == name_cache.type_Dictionary_generic) {
return "Dictionary";
}
return p_type->name;
}
String bbcode_to_xml(const String &p_bbcode, const TypeInterface *p_itype);
void _append_xml_method(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts);
@ -682,10 +697,9 @@ class BindingsGenerator {
int _determine_enum_prefix(const EnumInterface &p_ienum);
void _apply_prefix_to_enum_constants(EnumInterface &p_ienum, int p_prefix_length);
void _generate_method_icalls(const TypeInterface &p_itype);
Error _populate_method_icalls_table(const TypeInterface &p_itype);
const TypeInterface *_get_type_or_null(const TypeReference &p_typeref);
const TypeInterface *_get_type_or_placeholder(const TypeReference &p_typeref);
const String _get_generic_type_parameters(const TypeInterface &p_itype, const List<TypeReference> &p_generic_type_parameters);
@ -706,11 +720,11 @@ class BindingsGenerator {
Error _generate_cs_method(const TypeInterface &p_itype, const MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output);
Error _generate_cs_signal(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::SignalInterface &p_isignal, StringBuilder &p_output);
Error _generate_cs_native_calls(const InternalCall &p_icall, StringBuilder &r_output);
void _generate_array_extensions(StringBuilder &p_output);
void _generate_global_constants(StringBuilder &p_output);
Error _generate_glue_method(const TypeInterface &p_itype, const MethodInterface &p_imethod, StringBuilder &p_output);
Error _save_file(const String &p_path, const StringBuilder &p_content);
void _log(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3;
@ -721,15 +735,12 @@ public:
Error generate_cs_core_project(const String &p_proj_dir);
Error generate_cs_editor_project(const String &p_proj_dir);
Error generate_cs_api(const String &p_output_dir);
Error generate_glue(const String &p_output_dir);
_FORCE_INLINE_ bool is_log_print_enabled() { return log_print_enabled; }
_FORCE_INLINE_ void set_log_print_enabled(bool p_enabled) { log_print_enabled = p_enabled; }
_FORCE_INLINE_ bool is_initialized() { return initialized; }
static uint32_t get_version();
static void handle_cmdline_args(const List<String> &p_cmdline_args);
BindingsGenerator() {

View File

@ -46,179 +46,81 @@
#include "main/main.h"
#include "../csharp_script.h"
#include "../glue/cs_glue_version.gen.h"
#include "../godotsharp_dirs.h"
#include "../mono_gd/gd_mono_marshal.h"
#include "../utils/macos_utils.h"
#include "code_completion.h"
#include "godotsharp_export.h"
MonoString *godot_icall_GodotSharpDirs_ResDataDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_data_dir());
#include "../interop_types.h"
#ifdef __cplusplus
extern "C" {
#endif
void godot_icall_GodotSharpDirs_ResMetadataDir(godot_string *r_dest) {
memnew_placement(r_dest, String(GodotSharpDirs::get_res_metadata_dir()));
}
MonoString *godot_icall_GodotSharpDirs_ResMetadataDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_metadata_dir());
void godot_icall_GodotSharpDirs_MonoUserDir(godot_string *r_dest) {
memnew_placement(r_dest, String(GodotSharpDirs::get_mono_user_dir()));
}
MonoString *godot_icall_GodotSharpDirs_ResAssembliesBaseDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_assemblies_base_dir());
}
MonoString *godot_icall_GodotSharpDirs_ResAssembliesDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_assemblies_dir());
}
MonoString *godot_icall_GodotSharpDirs_ResConfigDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_config_dir());
}
MonoString *godot_icall_GodotSharpDirs_ResTempDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_temp_dir());
}
MonoString *godot_icall_GodotSharpDirs_ResTempAssembliesBaseDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_temp_assemblies_base_dir());
}
MonoString *godot_icall_GodotSharpDirs_ResTempAssembliesDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_temp_assemblies_dir());
}
MonoString *godot_icall_GodotSharpDirs_MonoUserDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_mono_user_dir());
}
MonoString *godot_icall_GodotSharpDirs_MonoLogsDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_mono_logs_dir());
}
MonoString *godot_icall_GodotSharpDirs_MonoSolutionsDir() {
void godot_icall_GodotSharpDirs_BuildLogsDirs(godot_string *r_dest) {
#ifdef TOOLS_ENABLED
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_mono_solutions_dir());
memnew_placement(r_dest, String(GodotSharpDirs::get_build_logs_dir()));
#else
return nullptr;
#endif
}
MonoString *godot_icall_GodotSharpDirs_BuildLogsDirs() {
void godot_icall_GodotSharpDirs_DataEditorToolsDir(godot_string *r_dest) {
#ifdef TOOLS_ENABLED
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_build_logs_dir());
memnew_placement(r_dest, String(GodotSharpDirs::get_data_editor_tools_dir()));
#else
return nullptr;
#endif
}
MonoString *godot_icall_GodotSharpDirs_ProjectSlnPath() {
#ifdef TOOLS_ENABLED
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_project_sln_path());
#else
return nullptr;
#endif
}
MonoString *godot_icall_GodotSharpDirs_ProjectCsProjPath() {
#ifdef TOOLS_ENABLED
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_project_csproj_path());
#else
return nullptr;
#endif
}
MonoString *godot_icall_GodotSharpDirs_DataEditorToolsDir() {
#ifdef TOOLS_ENABLED
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_editor_tools_dir());
#else
return nullptr;
#endif
}
MonoString *godot_icall_GodotSharpDirs_DataEditorPrebuiltApiDir() {
#ifdef TOOLS_ENABLED
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_editor_prebuilt_api_dir());
#else
return nullptr;
#endif
}
MonoString *godot_icall_GodotSharpDirs_DataMonoEtcDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_mono_etc_dir());
}
MonoString *godot_icall_GodotSharpDirs_DataMonoLibDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_mono_lib_dir());
}
MonoString *godot_icall_GodotSharpDirs_DataMonoBinDir() {
#ifdef WINDOWS_ENABLED
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_mono_bin_dir());
#else
return nullptr;
#endif
}
void godot_icall_EditorProgress_Create(MonoString *p_task, MonoString *p_label, int32_t p_amount, MonoBoolean p_can_cancel) {
String task = GDMonoMarshal::mono_string_to_godot(p_task);
String label = GDMonoMarshal::mono_string_to_godot(p_label);
void godot_icall_EditorProgress_Create(const godot_string *p_task, const godot_string *p_label, int32_t p_amount, bool p_can_cancel) {
String task = *reinterpret_cast<const String *>(p_task);
String label = *reinterpret_cast<const String *>(p_label);
EditorNode::progress_add_task(task, label, p_amount, (bool)p_can_cancel);
}
void godot_icall_EditorProgress_Dispose(MonoString *p_task) {
String task = GDMonoMarshal::mono_string_to_godot(p_task);
void godot_icall_EditorProgress_Dispose(const godot_string *p_task) {
String task = *reinterpret_cast<const String *>(p_task);
EditorNode::progress_end_task(task);
}
MonoBoolean godot_icall_EditorProgress_Step(MonoString *p_task, MonoString *p_state, int32_t p_step, MonoBoolean p_force_refresh) {
String task = GDMonoMarshal::mono_string_to_godot(p_task);
String state = GDMonoMarshal::mono_string_to_godot(p_state);
bool godot_icall_EditorProgress_Step(const godot_string *p_task, const godot_string *p_state, int32_t p_step, bool p_force_refresh) {
String task = *reinterpret_cast<const String *>(p_task);
String state = *reinterpret_cast<const String *>(p_state);
return EditorNode::progress_task_step(task, state, p_step, (bool)p_force_refresh);
}
uint32_t godot_icall_ExportPlugin_GetExportedAssemblyDependencies(MonoObject *p_initial_assemblies,
MonoString *p_build_config, MonoString *p_custom_bcl_dir, MonoObject *r_assembly_dependencies) {
Dictionary initial_dependencies = GDMonoMarshal::mono_object_to_variant(p_initial_assemblies);
String build_config = GDMonoMarshal::mono_string_to_godot(p_build_config);
String custom_bcl_dir = GDMonoMarshal::mono_string_to_godot(p_custom_bcl_dir);
Dictionary assembly_dependencies = GDMonoMarshal::mono_object_to_variant(r_assembly_dependencies);
return GodotSharpExport::get_exported_assembly_dependencies(initial_dependencies, build_config, custom_bcl_dir, assembly_dependencies);
}
MonoString *godot_icall_Internal_UpdateApiAssembliesFromPrebuilt(MonoString *p_config) {
String config = GDMonoMarshal::mono_string_to_godot(p_config);
String error_str = GDMono::get_singleton()->update_api_assemblies_from_prebuilt(config);
return GDMonoMarshal::mono_string_from_godot(error_str);
}
MonoString *godot_icall_Internal_FullExportTemplatesDir() {
void godot_icall_Internal_FullExportTemplatesDir(godot_string *r_dest) {
String full_templates_dir = EditorPaths::get_singleton()->get_export_templates_dir().plus_file(VERSION_FULL_CONFIG);
return GDMonoMarshal::mono_string_from_godot(full_templates_dir);
memnew_placement(r_dest, String(full_templates_dir));
}
MonoString *godot_icall_Internal_SimplifyGodotPath(MonoString *p_path) {
String path = GDMonoMarshal::mono_string_to_godot(p_path);
return GDMonoMarshal::mono_string_from_godot(path.simplify_path());
}
MonoBoolean godot_icall_Internal_IsMacOSAppBundleInstalled(MonoString *p_bundle_id) {
bool godot_icall_Internal_IsMacOSAppBundleInstalled(const godot_string *p_bundle_id) {
#ifdef MACOS_ENABLED
String bundle_id = GDMonoMarshal::mono_string_to_godot(p_bundle_id);
return (MonoBoolean)macos_is_app_bundle_installed(bundle_id);
String bundle_id = *reinterpret_cast<const String *>(p_bundle_id);
return (bool)macos_is_app_bundle_installed(bundle_id);
#else
(void)p_bundle_id; // UNUSED
return (MonoBoolean) false;
return (bool)false;
#endif
}
MonoBoolean godot_icall_Internal_GodotIs32Bits() {
bool godot_icall_Internal_GodotIs32Bits() {
return sizeof(void *) == 4;
}
MonoBoolean godot_icall_Internal_GodotIsRealTDouble() {
bool godot_icall_Internal_GodotIsRealTDouble() {
#ifdef REAL_T_IS_DOUBLE
return (MonoBoolean) true;
return (bool)true;
#else
return (MonoBoolean) false;
return (bool)false;
#endif
}
@ -226,23 +128,15 @@ void godot_icall_Internal_GodotMainIteration() {
Main::iteration();
}
uint64_t godot_icall_Internal_GetCoreApiHash() {
return ClassDB::get_api_hash(ClassDB::API_CORE);
}
uint64_t godot_icall_Internal_GetEditorApiHash() {
return ClassDB::get_api_hash(ClassDB::API_EDITOR);
}
MonoBoolean godot_icall_Internal_IsAssembliesReloadingNeeded() {
bool godot_icall_Internal_IsAssembliesReloadingNeeded() {
#ifdef GD_MONO_HOT_RELOAD
return (MonoBoolean)CSharpLanguage::get_singleton()->is_assembly_reloading_needed();
return (bool)CSharpLanguage::get_singleton()->is_assembly_reloading_needed();
#else
return (MonoBoolean) false;
return (bool)false;
#endif
}
void godot_icall_Internal_ReloadAssemblies(MonoBoolean p_soft_reload) {
void godot_icall_Internal_ReloadAssemblies(bool p_soft_reload) {
#ifdef GD_MONO_HOT_RELOAD
mono_bind::GodotSharp::get_singleton()->call_deferred(SNAME("_reload_assemblies"), (bool)p_soft_reload);
#endif
@ -252,24 +146,15 @@ void godot_icall_Internal_EditorDebuggerNodeReloadScripts() {
EditorDebuggerNode::get_singleton()->reload_scripts();
}
MonoBoolean godot_icall_Internal_ScriptEditorEdit(MonoObject *p_resource, int32_t p_line, int32_t p_col, MonoBoolean p_grab_focus) {
Ref<Resource> resource = GDMonoMarshal::mono_object_to_variant(p_resource);
return (MonoBoolean)ScriptEditor::get_singleton()->edit(resource, p_line, p_col, (bool)p_grab_focus);
bool godot_icall_Internal_ScriptEditorEdit(Resource *p_resource, int32_t p_line, int32_t p_col, bool p_grab_focus) {
Ref<Resource> resource = p_resource;
return (bool)ScriptEditor::get_singleton()->edit(resource, p_line, p_col, (bool)p_grab_focus);
}
void godot_icall_Internal_EditorNodeShowScriptScreen() {
EditorNode::get_singleton()->call("_editor_select", EditorNode::EDITOR_SCRIPT);
}
MonoString *godot_icall_Internal_MonoWindowsInstallRoot() {
#ifdef WINDOWS_ENABLED
String install_root_dir = GDMono::get_singleton()->get_mono_reg_info().install_root_dir;
return GDMonoMarshal::mono_string_from_godot(install_root_dir);
#else
return nullptr;
#endif
}
void godot_icall_Internal_EditorRunPlay() {
EditorNode::get_singleton()->run_play();
}
@ -285,114 +170,93 @@ void godot_icall_Internal_ScriptEditorDebugger_ReloadScripts() {
}
}
MonoArray *godot_icall_Internal_CodeCompletionRequest(int32_t p_kind, MonoString *p_script_file) {
String script_file = GDMonoMarshal::mono_string_to_godot(p_script_file);
void godot_icall_Internal_CodeCompletionRequest(int32_t p_kind, const godot_string *p_script_file, godot_packed_array *r_ret) {
String script_file = *reinterpret_cast<const String *>(p_script_file);
PackedStringArray suggestions = gdmono::get_code_completion((gdmono::CompletionKind)p_kind, script_file);
return GDMonoMarshal::PackedStringArray_to_mono_array(suggestions);
memnew_placement(r_ret, PackedStringArray(suggestions));
}
float godot_icall_Globals_EditorScale() {
return EDSCALE;
}
MonoObject *godot_icall_Globals_GlobalDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) {
String setting = GDMonoMarshal::mono_string_to_godot(p_setting);
Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value);
void godot_icall_Globals_GlobalDef(const godot_string *p_setting, const godot_variant *p_default_value, bool p_restart_if_changed, godot_variant *r_result) {
String setting = *reinterpret_cast<const String *>(p_setting);
Variant default_value = *reinterpret_cast<const Variant *>(p_default_value);
Variant result = _GLOBAL_DEF(setting, default_value, (bool)p_restart_if_changed);
return GDMonoMarshal::variant_to_mono_object(result);
memnew_placement(r_result, Variant(result));
}
MonoObject *godot_icall_Globals_EditorDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) {
String setting = GDMonoMarshal::mono_string_to_godot(p_setting);
Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value);
void godot_icall_Globals_EditorDef(const godot_string *p_setting, const godot_variant *p_default_value, bool p_restart_if_changed, godot_variant *r_result) {
String setting = *reinterpret_cast<const String *>(p_setting);
Variant default_value = *reinterpret_cast<const Variant *>(p_default_value);
Variant result = _EDITOR_DEF(setting, default_value, (bool)p_restart_if_changed);
return GDMonoMarshal::variant_to_mono_object(result);
memnew_placement(r_result, Variant(result));
}
MonoObject *godot_icall_Globals_EditorShortcut(MonoString *p_setting) {
String setting = GDMonoMarshal::mono_string_to_godot(p_setting);
void godot_icall_Globals_EditorShortcut(const godot_string *p_setting, godot_variant *r_result) {
String setting = *reinterpret_cast<const String *>(p_setting);
Ref<Shortcut> result = ED_GET_SHORTCUT(setting);
return GDMonoMarshal::variant_to_mono_object(result);
memnew_placement(r_result, Variant(result));
}
MonoString *godot_icall_Globals_TTR(MonoString *p_text) {
String text = GDMonoMarshal::mono_string_to_godot(p_text);
return GDMonoMarshal::mono_string_from_godot(TTR(text));
void godot_icall_Globals_TTR(const godot_string *p_text, godot_string *r_dest) {
String text = *reinterpret_cast<const String *>(p_text);
memnew_placement(r_dest, String(TTR(text)));
}
MonoString *godot_icall_Utils_OS_GetPlatformName() {
void godot_icall_Utils_OS_GetPlatformName(godot_string *r_dest) {
String os_name = OS::get_singleton()->get_name();
return GDMonoMarshal::mono_string_from_godot(os_name);
memnew_placement(r_dest, String(os_name));
}
MonoBoolean godot_icall_Utils_OS_UnixFileHasExecutableAccess(MonoString *p_file_path) {
bool godot_icall_Utils_OS_UnixFileHasExecutableAccess(const godot_string *p_file_path) {
#ifdef UNIX_ENABLED
String file_path = GDMonoMarshal::mono_string_to_godot(p_file_path);
String file_path = *reinterpret_cast<const String *>(p_file_path);
return access(file_path.utf8().get_data(), X_OK) == 0;
#else
ERR_FAIL_V(false);
#endif
}
void register_editor_internal_calls() {
// GodotSharpDirs
GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResDataDir", godot_icall_GodotSharpDirs_ResDataDir);
GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResMetadataDir", godot_icall_GodotSharpDirs_ResMetadataDir);
GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResAssembliesBaseDir", godot_icall_GodotSharpDirs_ResAssembliesBaseDir);
GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResAssembliesDir", godot_icall_GodotSharpDirs_ResAssembliesDir);
GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResConfigDir", godot_icall_GodotSharpDirs_ResConfigDir);
GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempDir", godot_icall_GodotSharpDirs_ResTempDir);
GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempAssembliesBaseDir", godot_icall_GodotSharpDirs_ResTempAssembliesBaseDir);
GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempAssembliesDir", godot_icall_GodotSharpDirs_ResTempAssembliesDir);
GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoUserDir", godot_icall_GodotSharpDirs_MonoUserDir);
GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoLogsDir", godot_icall_GodotSharpDirs_MonoLogsDir);
GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoSolutionsDir", godot_icall_GodotSharpDirs_MonoSolutionsDir);
GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_BuildLogsDirs", godot_icall_GodotSharpDirs_BuildLogsDirs);
GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ProjectSlnPath", godot_icall_GodotSharpDirs_ProjectSlnPath);
GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ProjectCsProjPath", godot_icall_GodotSharpDirs_ProjectCsProjPath);
GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataEditorToolsDir", godot_icall_GodotSharpDirs_DataEditorToolsDir);
GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataEditorPrebuiltApiDir", godot_icall_GodotSharpDirs_DataEditorPrebuiltApiDir);
GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoEtcDir", godot_icall_GodotSharpDirs_DataMonoEtcDir);
GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoLibDir", godot_icall_GodotSharpDirs_DataMonoLibDir);
GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoBinDir", godot_icall_GodotSharpDirs_DataMonoBinDir);
// EditorProgress
GDMonoUtils::add_internal_call("GodotTools.Internals.EditorProgress::internal_Create", godot_icall_EditorProgress_Create);
GDMonoUtils::add_internal_call("GodotTools.Internals.EditorProgress::internal_Dispose", godot_icall_EditorProgress_Dispose);
GDMonoUtils::add_internal_call("GodotTools.Internals.EditorProgress::internal_Step", godot_icall_EditorProgress_Step);
// ExportPlugin
GDMonoUtils::add_internal_call("GodotTools.Export.ExportPlugin::internal_GetExportedAssemblyDependencies", godot_icall_ExportPlugin_GetExportedAssemblyDependencies);
// Internals
GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_UpdateApiAssembliesFromPrebuilt", godot_icall_Internal_UpdateApiAssembliesFromPrebuilt);
GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_FullExportTemplatesDir", godot_icall_Internal_FullExportTemplatesDir);
GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_SimplifyGodotPath", godot_icall_Internal_SimplifyGodotPath);
GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_IsMacOSAppBundleInstalled", godot_icall_Internal_IsMacOSAppBundleInstalled);
GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GodotIs32Bits", godot_icall_Internal_GodotIs32Bits);
GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GodotIsRealTDouble", godot_icall_Internal_GodotIsRealTDouble);
GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GodotMainIteration", godot_icall_Internal_GodotMainIteration);
GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GetCoreApiHash", godot_icall_Internal_GetCoreApiHash);
GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GetEditorApiHash", godot_icall_Internal_GetEditorApiHash);
GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_IsAssembliesReloadingNeeded", godot_icall_Internal_IsAssembliesReloadingNeeded);
GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_ReloadAssemblies", godot_icall_Internal_ReloadAssemblies);
GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorDebuggerNodeReloadScripts", godot_icall_Internal_EditorDebuggerNodeReloadScripts);
GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorEdit", godot_icall_Internal_ScriptEditorEdit);
GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorNodeShowScriptScreen", godot_icall_Internal_EditorNodeShowScriptScreen);
GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_MonoWindowsInstallRoot", godot_icall_Internal_MonoWindowsInstallRoot);
GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorRunPlay", godot_icall_Internal_EditorRunPlay);
GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorRunStop", godot_icall_Internal_EditorRunStop);
GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorDebugger_ReloadScripts", godot_icall_Internal_ScriptEditorDebugger_ReloadScripts);
GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_CodeCompletionRequest", godot_icall_Internal_CodeCompletionRequest);
// Globals
GDMonoUtils::add_internal_call("GodotTools.Internals.Globals::internal_EditorScale", godot_icall_Globals_EditorScale);
GDMonoUtils::add_internal_call("GodotTools.Internals.Globals::internal_GlobalDef", godot_icall_Globals_GlobalDef);
GDMonoUtils::add_internal_call("GodotTools.Internals.Globals::internal_EditorDef", godot_icall_Globals_EditorDef);
GDMonoUtils::add_internal_call("GodotTools.Internals.Globals::internal_EditorShortcut", godot_icall_Globals_EditorShortcut);
GDMonoUtils::add_internal_call("GodotTools.Internals.Globals::internal_TTR", godot_icall_Globals_TTR);
// Utils.OS
GDMonoUtils::add_internal_call("GodotTools.Utils.OS::GetPlatformName", godot_icall_Utils_OS_GetPlatformName);
GDMonoUtils::add_internal_call("GodotTools.Utils.OS::UnixFileHasExecutableAccess", godot_icall_Utils_OS_UnixFileHasExecutableAccess);
#ifdef __cplusplus
}
#endif
// The order in this array must match the declaration order of
// the methods in 'GodotTools/Internals/Internal.cs'.
static const void *unmanaged_callbacks[]{
(void *)godot_icall_GodotSharpDirs_ResMetadataDir,
(void *)godot_icall_GodotSharpDirs_MonoUserDir,
(void *)godot_icall_GodotSharpDirs_BuildLogsDirs,
(void *)godot_icall_GodotSharpDirs_DataEditorToolsDir,
(void *)godot_icall_EditorProgress_Create,
(void *)godot_icall_EditorProgress_Dispose,
(void *)godot_icall_EditorProgress_Step,
(void *)godot_icall_Internal_FullExportTemplatesDir,
(void *)godot_icall_Internal_IsMacOSAppBundleInstalled,
(void *)godot_icall_Internal_GodotIs32Bits,
(void *)godot_icall_Internal_GodotIsRealTDouble,
(void *)godot_icall_Internal_GodotMainIteration,
(void *)godot_icall_Internal_IsAssembliesReloadingNeeded,
(void *)godot_icall_Internal_ReloadAssemblies,
(void *)godot_icall_Internal_EditorDebuggerNodeReloadScripts,
(void *)godot_icall_Internal_ScriptEditorEdit,
(void *)godot_icall_Internal_EditorNodeShowScriptScreen,
(void *)godot_icall_Internal_EditorRunPlay,
(void *)godot_icall_Internal_EditorRunStop,
(void *)godot_icall_Internal_ScriptEditorDebugger_ReloadScripts,
(void *)godot_icall_Internal_CodeCompletionRequest,
(void *)godot_icall_Globals_EditorScale,
(void *)godot_icall_Globals_GlobalDef,
(void *)godot_icall_Globals_EditorDef,
(void *)godot_icall_Globals_EditorShortcut,
(void *)godot_icall_Globals_TTR,
(void *)godot_icall_Utils_OS_GetPlatformName,
(void *)godot_icall_Utils_OS_UnixFileHasExecutableAccess,
};
const void **godotsharp::get_editor_interop_funcs(int32_t &r_size) {
r_size = sizeof(unmanaged_callbacks);
return unmanaged_callbacks;
}

View File

@ -31,6 +31,10 @@
#ifndef EDITOR_INTERNAL_CALLS_H
#define EDITOR_INTERNAL_CALLS_H
void register_editor_internal_calls();
#include "core/typedefs.h"
namespace godotsharp {
const void **get_editor_interop_funcs(int32_t &r_size);
}
#endif // EDITOR_INTERNAL_CALLS_H

View File

@ -1,144 +0,0 @@
/*************************************************************************/
/* godotsharp_export.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "godotsharp_export.h"
#include <mono/metadata/image.h>
#include "core/config/project_settings.h"
#include "core/io/file_access_pack.h"
#include "core/os/os.h"
#include "../mono_gd/gd_mono.h"
#include "../mono_gd/gd_mono_assembly.h"
#include "../mono_gd/gd_mono_cache.h"
#include "../utils/macros.h"
namespace GodotSharpExport {
MonoAssemblyName *new_mono_assembly_name() {
// Mono has no public API to create an empty MonoAssemblyName and the struct is private.
// As such the only way to create it is with a stub name and then clear it.
MonoAssemblyName *aname = mono_assembly_name_new("stub");
CRASH_COND(aname == nullptr);
mono_assembly_name_free(aname); // Frees the string fields, not the struct
return aname;
}
struct AssemblyRefInfo {
String name;
uint16_t major = 0;
uint16_t minor = 0;
uint16_t build = 0;
uint16_t revision = 0;
};
AssemblyRefInfo get_assemblyref_name(MonoImage *p_image, int index) {
const MonoTableInfo *table_info = mono_image_get_table_info(p_image, MONO_TABLE_ASSEMBLYREF);
uint32_t cols[MONO_ASSEMBLYREF_SIZE];
mono_metadata_decode_row(table_info, index, cols, MONO_ASSEMBLYREF_SIZE);
return {
String::utf8(mono_metadata_string_heap(p_image, cols[MONO_ASSEMBLYREF_NAME])),
(uint16_t)cols[MONO_ASSEMBLYREF_MAJOR_VERSION],
(uint16_t)cols[MONO_ASSEMBLYREF_MINOR_VERSION],
(uint16_t)cols[MONO_ASSEMBLYREF_BUILD_NUMBER],
(uint16_t)cols[MONO_ASSEMBLYREF_REV_NUMBER]
};
}
Error get_assembly_dependencies(GDMonoAssembly *p_assembly, MonoAssemblyName *reusable_aname, const Vector<String> &p_search_dirs, Dictionary &r_assembly_dependencies) {
MonoImage *image = p_assembly->get_image();
for (int i = 0; i < mono_image_get_table_rows(image, MONO_TABLE_ASSEMBLYREF); i++) {
AssemblyRefInfo ref_info = get_assemblyref_name(image, i);
const String &ref_name = ref_info.name;
if (r_assembly_dependencies.has(ref_name)) {
continue;
}
mono_assembly_get_assemblyref(image, i, reusable_aname);
GDMonoAssembly *ref_assembly = nullptr;
if (!GDMono::get_singleton()->load_assembly(ref_name, reusable_aname, &ref_assembly, /* refonly: */ true, p_search_dirs)) {
ERR_FAIL_V_MSG(ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + ref_name + "'.");
}
r_assembly_dependencies[ref_name] = ref_assembly->get_path();
Error err = get_assembly_dependencies(ref_assembly, reusable_aname, p_search_dirs, r_assembly_dependencies);
ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot load one of the dependencies for the assembly: '" + ref_name + "'.");
}
return OK;
}
Error get_exported_assembly_dependencies(const Dictionary &p_initial_assemblies,
const String &p_build_config, const String &p_custom_bcl_dir, Dictionary &r_assembly_dependencies) {
MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.Domain.ProjectExport");
ERR_FAIL_NULL_V(export_domain, FAILED);
_GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(export_domain);
_GDMONO_SCOPE_DOMAIN_(export_domain);
Vector<String> search_dirs;
GDMonoAssembly::fill_search_dirs(search_dirs, p_build_config, p_custom_bcl_dir);
if (p_custom_bcl_dir.length()) {
// Only one mscorlib can be loaded. We need this workaround to make sure we get it from the right BCL directory.
r_assembly_dependencies["mscorlib"] = p_custom_bcl_dir.plus_file("mscorlib.dll").simplify_path();
}
for (const Variant *key = p_initial_assemblies.next(); key; key = p_initial_assemblies.next(key)) {
String assembly_name = *key;
String assembly_path = p_initial_assemblies[*key];
GDMonoAssembly *assembly = nullptr;
bool load_success = GDMono::get_singleton()->load_assembly_from(assembly_name, assembly_path, &assembly, /* refonly: */ true);
ERR_FAIL_COND_V_MSG(!load_success, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + assembly_name + "'.");
MonoAssemblyName *reusable_aname = new_mono_assembly_name();
SCOPE_EXIT { mono_free(reusable_aname); };
Error err = get_assembly_dependencies(assembly, reusable_aname, search_dirs, r_assembly_dependencies);
if (err != OK) {
return err;
}
}
return OK;
}
} // namespace GodotSharpExport

View File

@ -1,48 +0,0 @@
/*************************************************************************/
/* godotsharp_export.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef GODOTSHARP_EXPORT_H
#define GODOTSHARP_EXPORT_H
#include "core/error/error_list.h"
#include "core/string/ustring.h"
#include "core/variant/dictionary.h"
#include "../mono_gd/gd_mono_header.h"
namespace GodotSharpExport {
Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies);
Error get_exported_assembly_dependencies(const Dictionary &p_initial_assemblies,
const String &p_build_config, const String &p_custom_lib_dir, Dictionary &r_assembly_dependencies);
} // namespace GodotSharpExport
#endif // GODOTSHARP_EXPORT_H

View File

@ -9,7 +9,7 @@ public partial class _CLASS_ : _BASE_
public const float JumpVelocity = -400.0f;
// Get the gravity from the project settings to be synced with RigidDynamicBody nodes.
public float gravity = (float)ProjectSettings.GetSetting("physics/2d/default_gravity");
public float gravity = ProjectSettings.GetSetting("physics/2d/default_gravity").AsSingle();
public override void _PhysicsProcess(float delta)
{

View File

@ -9,7 +9,7 @@ public partial class _CLASS_ : _BASE_
public const float JumpVelocity = 4.5f;
// Get the gravity from the project settings to be synced with RigidDynamicBody nodes.
public float gravity = (float)ProjectSettings.GetSetting("physics/3d/default_gravity");
public float gravity = ProjectSettings.GetSetting("physics/3d/default_gravity").AsSingle();
public override void _PhysicsProcess(float delta)
{

View File

@ -55,7 +55,7 @@ public partial class VisualShaderNode_CLASS_ : _BASE_
return 0;
}
public override string _GetCode(Godot.Collections.Array inputVars, Godot.Collections.Array outputVars, Shader.Mode mode, VisualShader.Type type)
public override string _GetCode(Godot.Collections.Array<string> inputVars, Godot.Collections.Array<string> outputVars, Shader.Mode mode, VisualShader.Type type)
{
return "";
}

View File

@ -0,0 +1,8 @@
[**/Generated/**.cs]
# Validate parameter is non-null before using it
# Useful for generated code, as it disables nullable
dotnet_diagnostic.CA1062.severity = error
# CA1069: Enums should not have duplicate values
dotnet_diagnostic.CA1069.severity = none
# CA1708: Identifiers should differ by more than case
dotnet_diagnostic.CA1708.severity = none

View File

@ -0,0 +1,5 @@
<assembly name="System.Runtime.InteropServices">
<member name="T:System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute">
<attribute ctor="M:JetBrains.Annotations.MeansImplicitUseAttribute.#ctor" />
</member>
</assembly>

Some files were not shown because too many files have changed in this diff Show More