commit 5c461069b39150244d4d8f309bffbe6de6c60308 Author: Relintai Date: Tue May 23 17:54:30 2023 +0200 Added godot's mono module. diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c9dcd77 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +[*.sln] +indent_style = tab + +[*.{csproj,props,targets,nuspec,resx}] +indent_style = space +indent_size = 2 + +[*.cs] +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true +max_line_length = 120 +csharp_indent_case_contents_when_block = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fa6d00c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Do not ignore solution files inside the mono module. Overrides Godot's global gitignore. +!*.sln diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..c73b002 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +Copyright (c) 2023-present Péter Magyar. +Copyright (c) 2014-2023 Godot Engine contributors (cf. AUTHORS.md). +Copyright (c) 2007-2023 Juan Linietsky, Ariel Manzur. + +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. diff --git a/SCsub b/SCsub new file mode 100644 index 0000000..93f0f98 --- /dev/null +++ b/SCsub @@ -0,0 +1,64 @@ +#!/usr/bin/env python + +import build_scripts.tls_configure as tls_configure +import build_scripts.mono_configure as mono_configure + +Import("env") +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'?") + +# Configure Thread Local Storage + +conf = Configure(env_mono) +tls_configure.configure(conf) +env_mono = conf.Finish() + +# 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) + +# 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") + +env_mono.add_source_files(env.modules_sources, "mono_gd/support/*.cpp") + +if env["platform"] in ["osx", "iphone"]: + env_mono.add_source_files(env.modules_sources, "mono_gd/support/*.mm") + env_mono.add_source_files(env.modules_sources, "mono_gd/support/*.m") +elif env["platform"] == "android": + env_mono.add_source_files(env.modules_sources, "mono_gd/android_mono_config.gen.cpp") + +if env["tools"]: + env_mono.add_source_files(env.modules_sources, "editor/*.cpp") diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/build_scripts/__init__.py b/build_scripts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/build_scripts/api_solution_build.py b/build_scripts/api_solution_build.py new file mode 100644 index 0000000..9abac22 --- /dev/null +++ b/build_scripts/api_solution_build.py @@ -0,0 +1,80 @@ +# 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 diff --git a/build_scripts/gen_cs_glue_version.py b/build_scripts/gen_cs_glue_version.py new file mode 100644 index 0000000..98bbb4d --- /dev/null +++ b/build_scripts/gen_cs_glue_version.py @@ -0,0 +1,20 @@ +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") diff --git a/build_scripts/godot_tools_build.py b/build_scripts/godot_tools_build.py new file mode 100644 index 0000000..3bbbf29 --- /dev/null +++ b/build_scripts/godot_tools_build.py @@ -0,0 +1,38 @@ +# 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) diff --git a/build_scripts/make_android_mono_config.py b/build_scripts/make_android_mono_config.py new file mode 100644 index 0000000..ef41f92 --- /dev/null +++ b/build_scripts/make_android_mono_config.py @@ -0,0 +1,55 @@ +def generate_compressed_config(config_src, output_dir): + import os.path + from compat import byte_to_str + + # 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 + + buf = zlib.compress(buf) + 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 += byte_to_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" +#include "core/pool_vector.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() { + PoolVector data; + data.resize(config_uncompressed_size); + PoolVector::Write w = data.write(); + Compression::decompress(w.ptr(), config_uncompressed_size, config_compressed_data, + config_compressed_size, Compression::MODE_DEFLATE); + String s; + if (s.parse_utf8((const char *)w.ptr(), data.size())) { + ERR_FAIL_V(String()); + } + return s; +} + +#endif // ANDROID_ENABLED +""" + % (compr_size, decompr_size, bytes_seq_str) + ) diff --git a/build_scripts/mono_android_config.xml b/build_scripts/mono_android_config.xml new file mode 100644 index 0000000..e79670a --- /dev/null +++ b/build_scripts/mono_android_config.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build_scripts/mono_configure.py b/build_scripts/mono_configure.py new file mode 100644 index 0000000..de29e75 --- /dev/null +++ b/build_scripts/mono_configure.py @@ -0,0 +1,585 @@ +import os +import os.path +import sys +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): + return platform in ["windows", "osx", "x11", "server", "uwp", "haiku"] + + +def is_unix_like(platform): + return platform in ["osx", "x11", "server", "android", "haiku", "iphone"] + + +def module_supports_tools_on(platform): + return platform not in ["android", "javascript", "iphone"] + + +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 "" + + +def configure(env, env_mono): + bits = env["bits"] + is_android = env["platform"] == "android" + is_javascript = env["platform"] == "javascript" + is_ios = env["platform"] == "iphone" + is_ios_sim = is_ios and env["ios_simulator"] + + 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: + env_mono.Append(CPPDEFINES=["GD_MONO_HOT_RELOAD"]) + + if env["platform"] == "windows": + mono_root = mono_prefix + + if not mono_root and os.name == "nt": + mono_root = monoreg.find_mono_root_dir(bits) + + if not mono_root: + raise RuntimeError( + "Mono installation directory not found; specify one manually with the 'mono_prefix' SCons parameter" + ) + + print("Found Mono root directory: " + mono_root) + + mono_lib_path = os.path.join(mono_root, "lib") + + env.Append(LIBPATH=mono_lib_path) + env_mono.Prepend(CPPPATH=os.path.join(mono_root, "include", "mono-2.0")) + + lib_suffixes = [".lib"] + + 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.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") + 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"]) + else: + mono_lib_name = find_name_in_dir_files( + mono_lib_path, mono_lib_names, prefixes=["", "lib"], extensions=lib_suffixes + ) + + if not mono_lib_name: + raise RuntimeError("Could not find mono library in: " + mono_lib_path) + + if env.msvc: + env.Append(LINKFLAGS=mono_lib_name + ".lib") + else: + env.Append(LIBS=[mono_lib_name]) + + 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"]) + + if not mono_dll_file: + raise RuntimeError("Could not find mono shared library in: " + mono_bin_path) + + copy_file(mono_bin_path, "#bin", mono_dll_file) + else: + is_apple = env["platform"] in ["osx", "iphone"] + is_macos = is_apple and not is_ios + + sharedlib_ext = ".dylib" if is_apple else ".so" + + mono_root = mono_prefix + mono_lib_path = "" + mono_so_file = "" + + 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" + ) + + if not mono_root and is_macos: + # Try with some known directories under OSX + 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 + + # 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" + ) + + if is_ios and not is_ios_sim: + env_mono.Append(CPPDEFINES=["IOS_DEVICE"]) + + if mono_root: + print("Found Mono root directory: " + mono_root) + + mono_lib_path = os.path.join(mono_root, "lib") + + env.Append(LIBPATH=[mono_lib_path]) + env_mono.Prepend(CPPPATH=os.path.join(mono_root, "include", "mono-2.0")) + + 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) + + 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): + if is_ios_sim: + copy_file( + mono_lib_path, + "#bin", + libname_wo_ext + ".a", + "%s.iphone.%s.simulator.a" % (libname_wo_ext, arch), + ) + else: + copy_file( + mono_lib_path, + "#bin", + libname_wo_ext + ".a", + "%s.iphone.%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) + + 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) + 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) + + src_mono_lib_dir = os.path.join(mono_root, "lib") + + lib_file_names = [] + if platform == "osx": + lib_file_names = [lib_name + ".dylib" for lib_name in ["libmono-btls-shared", "libMonoPosixHelper"]] + + if os.path.isfile(os.path.join(src_mono_lib_dir, "libmono-native-compat.dylib")): + lib_file_names += ["libmono-native-compat.dylib"] + else: + lib_file_names += ["libmono-native.dylib"] + 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(src_mono_lib_dir, lib_file_name), target_mono_lib_dir) + + +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, "..") + return "" diff --git a/build_scripts/mono_reg_utils.py b/build_scripts/mono_reg_utils.py new file mode 100644 index 0000000..596129c --- /dev/null +++ b/build_scripts/mono_reg_utils.py @@ -0,0 +1,119 @@ +import os +import platform + +from compat import decode_utf8 + +if os.name == "nt": + import sys + + if sys.version_info < (3,): + import _winreg as winreg + else: + import winreg + + +def _reg_open_key(key, subkey): + try: + return winreg.OpenKey(key, subkey) + except (WindowsError, 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 (WindowsError, 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 (WindowsError, EnvironmentError): + 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 = decode_utf8(line).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 (WindowsError, OSError): + return "" diff --git a/build_scripts/solution_builder.py b/build_scripts/solution_builder.py new file mode 100644 index 0000000..03f4e57 --- /dev/null +++ b/build_scripts/solution_builder.py @@ -0,0 +1,148 @@ +import os + + +verbose = False + + +def find_dotnet_cli(): + import os.path + + if os.name == "nt": + windows_exts = os.environ["PATHEXT"] + windows_exts = windows_exts.split(os.pathsep) if windows_exts 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 + 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") diff --git a/build_scripts/tls_configure.py b/build_scripts/tls_configure.py new file mode 100644 index 0000000..522be4b --- /dev/null +++ b/build_scripts/tls_configure.py @@ -0,0 +1,37 @@ +from __future__ import print_function + + +def supported(result): + return "supported" if result else "not supported" + + +def check_cxx11_thread_local(conf): + print("Checking for `thread_local` support...", end=" ") + result = conf.TryCompile("thread_local int foo = 0; int main() { return foo; }", ".cpp") + print(supported(result)) + return bool(result) + + +def check_declspec_thread(conf): + print("Checking for `__declspec(thread)` support...", end=" ") + result = conf.TryCompile("__declspec(thread) int foo = 0; int main() { return foo; }", ".cpp") + print(supported(result)) + return bool(result) + + +def check_gcc___thread(conf): + print("Checking for `__thread` support...", end=" ") + result = conf.TryCompile("__thread int foo = 0; int main() { return foo; }", ".cpp") + print(supported(result)) + return bool(result) + + +def configure(conf): + if check_cxx11_thread_local(conf): + conf.env.Append(CPPDEFINES=["HAVE_CXX11_THREAD_LOCAL"]) + else: + if conf.env.msvc: + if check_declspec_thread(conf): + conf.env.Append(CPPDEFINES=["HAVE_DECLSPEC_THREAD"]) + elif check_gcc___thread(conf): + conf.env.Append(CPPDEFINES=["HAVE_GCC___THREAD"]) diff --git a/class_db_api_json.cpp b/class_db_api_json.cpp new file mode 100644 index 0000000..727d79f --- /dev/null +++ b/class_db_api_json.cpp @@ -0,0 +1,247 @@ +/**************************************************************************/ +/* class_db_api_json.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 "class_db_api_json.h" + +#ifdef DEBUG_METHODS_ENABLED + +#include "core/io/json.h" +#include "core/os/file_access.h" +#include "core/project_settings.h" +#include "core/version.h" + +void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) { + Dictionary classes_dict; + + List names; + + const StringName *k = NULL; + + while ((k = ClassDB::classes.next(k))) { + names.push_back(*k); + } + //must be alphabetically sorted for hash to compute + names.sort_custom(); + + for (List::Element *E = names.front(); E; E = E->next()) { + ClassDB::ClassInfo *t = ClassDB::classes.getptr(E->get()); + ERR_FAIL_COND(!t); + if (t->api != p_api || !t->exposed) + continue; + + Dictionary class_dict; + classes_dict[t->name] = class_dict; + + class_dict["inherits"] = t->inherits; + + { //methods + + List snames; + + k = NULL; + + while ((k = t->method_map.next(k))) { + String name = k->operator String(); + + ERR_CONTINUE(name.empty()); + + if (name[0] == '_') + continue; // Ignore non-virtual methods that start with an underscore + + snames.push_back(*k); + } + + snames.sort_custom(); + + Array methods; + + for (List::Element *F = snames.front(); F; F = F->next()) { + Dictionary method_dict; + methods.push_back(method_dict); + + MethodBind *mb = t->method_map[F->get()]; + method_dict["name"] = mb->get_name(); + method_dict["argument_count"] = mb->get_argument_count(); + method_dict["return_type"] = mb->get_argument_type(-1); + + Array arguments; + method_dict["arguments"] = arguments; + + for (int i = 0; i < mb->get_argument_count(); i++) { + Dictionary argument_dict; + arguments.push_back(argument_dict); + const PropertyInfo info = mb->get_argument_info(i); + argument_dict["type"] = info.type; + argument_dict["name"] = info.name; + argument_dict["hint"] = info.hint; + argument_dict["hint_string"] = info.hint_string; + } + + method_dict["default_argument_count"] = mb->get_default_argument_count(); + + Array default_arguments; + method_dict["default_arguments"] = default_arguments; + + for (int i = 0; i < mb->get_default_argument_count(); i++) { + Dictionary default_argument_dict; + default_arguments.push_back(default_argument_dict); + //hash should not change, i hope for tis + Variant da = mb->get_default_argument(i); + default_argument_dict["value"] = da; + } + + method_dict["hint_flags"] = mb->get_hint_flags(); + } + + if (!methods.empty()) { + class_dict["methods"] = methods; + } + } + + { //constants + + List snames; + + k = NULL; + + while ((k = t->constant_map.next(k))) { + snames.push_back(*k); + } + + snames.sort_custom(); + + Array constants; + + for (List::Element *F = snames.front(); F; F = F->next()) { + Dictionary constant_dict; + constants.push_back(constant_dict); + + constant_dict["name"] = F->get(); + constant_dict["value"] = t->constant_map[F->get()]; + } + + if (!constants.empty()) { + class_dict["constants"] = constants; + } + } + + { //signals + + List snames; + + k = NULL; + + while ((k = t->signal_map.next(k))) { + snames.push_back(*k); + } + + snames.sort_custom(); + + Array signals; + + for (List::Element *F = snames.front(); F; F = F->next()) { + Dictionary signal_dict; + signals.push_back(signal_dict); + + MethodInfo &mi = t->signal_map[F->get()]; + signal_dict["name"] = F->get(); + + Array arguments; + signal_dict["arguments"] = arguments; + for (int i = 0; i < mi.arguments.size(); i++) { + Dictionary argument_dict; + arguments.push_back(argument_dict); + argument_dict["type"] = mi.arguments[i].type; + } + } + + if (!signals.empty()) { + class_dict["signals"] = signals; + } + } + + { //properties + + List snames; + + k = NULL; + + while ((k = t->property_setget.next(k))) { + snames.push_back(*k); + } + + snames.sort_custom(); + + Array properties; + + for (List::Element *F = snames.front(); F; F = F->next()) { + Dictionary property_dict; + properties.push_back(property_dict); + + ClassDB::PropertySetGet *psg = t->property_setget.getptr(F->get()); + + property_dict["name"] = F->get(); + property_dict["setter"] = psg->setter; + property_dict["getter"] = psg->getter; + } + + if (!properties.empty()) { + class_dict["property_setget"] = properties; + } + } + + Array property_list; + + //property list + for (List::Element *F = t->property_list.front(); F; F = F->next()) { + Dictionary property_dict; + property_list.push_back(property_dict); + + property_dict["name"] = F->get().name; + property_dict["type"] = F->get().type; + property_dict["hint"] = F->get().hint; + property_dict["hint_string"] = F->get().hint_string; + property_dict["usage"] = F->get().usage; + } + + if (!property_list.empty()) { + class_dict["property_list"] = property_list; + } + } + + FileAccessRef f = FileAccess::open(p_output_file, FileAccess::WRITE); + ERR_FAIL_COND_MSG(!f, "Cannot open file '" + p_output_file + "'."); + f->store_string(JSON::print(classes_dict, /*indent: */ "\t")); + f->close(); + + print_line(String() + "ClassDB API JSON written to: " + ProjectSettings::get_singleton()->globalize_path(p_output_file)); +} + +#endif // DEBUG_METHODS_ENABLED diff --git a/class_db_api_json.h b/class_db_api_json.h new file mode 100644 index 0000000..9ae6aba --- /dev/null +++ b/class_db_api_json.h @@ -0,0 +1,46 @@ +/**************************************************************************/ +/* class_db_api_json.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 CLASS_DB_API_JSON_H +#define CLASS_DB_API_JSON_H + +// 'core/method_bind.h' defines DEBUG_METHODS_ENABLED, but it looks like we +// cannot include it here. That's why we include it through 'core/class_db.h'. +#include "core/class_db.h" + +#ifdef DEBUG_METHODS_ENABLED + +#include "core/ustring.h" + +void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api); + +#endif // DEBUG_METHODS_ENABLED + +#endif // CLASS_DB_API_JSON_H diff --git a/config.py b/config.py new file mode 100644 index 0000000..5fb8359 --- /dev/null +++ b/config.py @@ -0,0 +1,77 @@ +supported_platforms = ["windows", "osx", "x11", "server", "android", "haiku", "javascript", "iphone"] + + +def can_build(env, platform): + return not env["arch"].startswith("rv") + + +def configure(env): + platform = env["platform"] + + if platform not in supported_platforms: + raise RuntimeError("This module does not currently support building for this platform") + + env.use_ptrcall = True + env.add_module_version_string("mono") + + from SCons.Script import BoolVariable, PathVariable, Variables, Help + + default_mono_static = platform in ["iphone", "javascript"] + default_mono_bundles_zlib = platform in ["javascript"] + + envvars = Variables() + envvars.Add( + PathVariable( + "mono_prefix", + "Path to the Mono installation directory for the target platform and architecture", + "", + PathVariable.PathAccept, + ) + ) + envvars.Add( + PathVariable( + "mono_bcl", + "Path to a custom Mono BCL (Base Class Library) directory for the target platform", + "", + PathVariable.PathAccept, + ) + ) + envvars.Add(BoolVariable("mono_static", "Statically link Mono", default_mono_static)) + envvars.Add(BoolVariable("mono_glue", "Build with the Mono glue sources", True)) + envvars.Add(BoolVariable("build_cil", "Build C# solutions", True)) + envvars.Add( + BoolVariable("copy_mono_root", "Make a copy of the Mono installation directory to bundle with the editor", True) + ) + + # TODO: It would be great if this could be detected automatically instead + envvars.Add( + BoolVariable( + "mono_bundles_zlib", "Specify if the Mono runtime was built with bundled zlib", default_mono_bundles_zlib + ) + ) + + envvars.Update(env) + Help(envvars.GenerateHelpText(env)) + + 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 [ + "CSharpScript", + "GodotSharp", + ] + + +def get_doc_path(): + return "doc_classes" + + +def is_enabled(): + # The module is disabled by default. Use module_mono_enabled=yes to enable it. + return False diff --git a/csharp_script.cpp b/csharp_script.cpp new file mode 100644 index 0000000..9da68c4 --- /dev/null +++ b/csharp_script.cpp @@ -0,0 +1,3482 @@ +/**************************************************************************/ +/* csharp_script.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 "csharp_script.h" + +#include + +#include "core/io/json.h" +#include "core/os/file_access.h" +#include "core/os/os.h" +#include "core/os/thread.h" +#include "core/project_settings.h" + +#ifdef TOOLS_ENABLED +#include "core/os/keyboard.h" +#include "editor/bindings_generator.h" +#include "editor/csharp_project.h" +#include "editor/editor_node.h" +#include "editor/editor_settings.h" +#include "editor/node_dock.h" +#endif + +#ifdef DEBUG_METHODS_ENABLED +#include "class_db_api_json.h" +#endif + +#include "editor/editor_internal_calls.h" +#include "godotsharp_dirs.h" +#include "mono_gd/gd_mono_cache.h" +#include "mono_gd/gd_mono_class.h" +#include "mono_gd/gd_mono_marshal.h" +#include "mono_gd/gd_mono_utils.h" +#include "signal_awaiter_utils.h" +#include "utils/macros.h" +#include "utils/string_utils.h" +#include "utils/thread_local.h" + +#define CACHED_STRING_NAME(m_var) (CSharpLanguage::get_singleton()->get_string_names().m_var) + +#ifdef TOOLS_ENABLED +static bool _create_project_solution_if_needed() { + String sln_path = GodotSharpDirs::get_project_sln_path(); + String csproj_path = GodotSharpDirs::get_project_csproj_path(); + + if (!FileAccess::exists(sln_path) || !FileAccess::exists(csproj_path)) { + // A solution does not yet exist, create a new one + + CRASH_COND(CSharpLanguage::get_singleton()->get_godotsharp_editor() == NULL); + return CSharpLanguage::get_singleton()->get_godotsharp_editor()->call("CreateProjectSolution"); + } + + return true; +} +#endif + +CSharpLanguage *CSharpLanguage::singleton = NULL; + +String CSharpLanguage::get_name() const { + return "C#"; +} + +String CSharpLanguage::get_type() const { + return "CSharpScript"; +} + +String CSharpLanguage::get_extension() const { + return "cs"; +} + +Error CSharpLanguage::execute_file(const String &p_path) { + // ?? + return OK; +} + +void CSharpLanguage::init() { +#ifdef DEBUG_METHODS_ENABLED + if (OS::get_singleton()->get_cmdline_args().find("--class-db-json")) { + class_db_api_to_json("user://class_db_api.json", ClassDB::API_CORE); +#ifdef TOOLS_ENABLED + class_db_api_to_json("user://class_db_api_editor.json", ClassDB::API_EDITOR); +#endif + } +#endif + + gdmono = memnew(GDMono); + gdmono->initialize(); + +#if defined(TOOLS_ENABLED) && defined(DEBUG_METHODS_ENABLED) + // Generate bindings here, before loading assemblies. 'initialize_load_assemblies' aborts + // the applications if the api assemblies or the main tools assembly is missing, but this + // is not a problem for BindingsGenerator as it only needs the tools project editor assembly. + List cmdline_args = OS::get_singleton()->get_cmdline_args(); + BindingsGenerator::handle_cmdline_args(cmdline_args); +#endif + +#ifndef MONO_GLUE_ENABLED + print_line("Run this binary with '--generate-mono-glue path/to/modules/mono/glue'"); +#endif + + if (gdmono->is_runtime_initialized()) + gdmono->initialize_load_assemblies(); + +#ifdef TOOLS_ENABLED + EditorNode::add_init_callback(&_editor_init_callback); +#endif +} + +void CSharpLanguage::finish() { + finalizing = true; + + // Make sure all script binding gchandles are released before finalizing GDMono + for (Map::Element *E = script_bindings.front(); E; E = E->next()) { + CSharpScriptBinding &script_binding = E->value(); + + if (script_binding.gchandle.is_valid()) { + script_binding.gchandle->release(); + script_binding.inited = false; + } + } + + if (gdmono) { + memdelete(gdmono); + gdmono = NULL; + } + + // Clear here, after finalizing all domains to make sure there is nothing else referencing the elements. + script_bindings.clear(); + +#ifdef DEBUG_ENABLED + for (Map::Element *E = unsafe_object_references.front(); E; E = E->next()) { + const ObjectID &id = E->key(); + Object *obj = ObjectDB::get_instance(id); + + if (obj) { + ERR_PRINT("Leaked unsafe reference to object: " + obj->to_string()); + } else { + ERR_PRINT("Leaked unsafe reference to deleted object: " + itos(id)); + } + } +#endif + + finalizing = false; +} + +void CSharpLanguage::get_reserved_words(List *p_words) const { + static const char *_reserved_words[] = { + // Reserved keywords + "abstract", + "as", + "base", + "bool", + "break", + "byte", + "case", + "catch", + "char", + "checked", + "class", + "const", + "continue", + "decimal", + "default", + "delegate", + "do", + "double", + "else", + "enum", + "event", + "explicit", + "extern", + "false", + "finally", + "fixed", + "float", + "for", + "foreach", + "goto", + "if", + "implicit", + "in", + "int", + "interface", + "internal", + "is", + "lock", + "long", + "namespace", + "new", + "null", + "object", + "operator", + "out", + "override", + "params", + "private", + "protected", + "public", + "readonly", + "ref", + "return", + "sbyte", + "sealed", + "short", + "sizeof", + "stackalloc", + "static", + "string", + "struct", + "switch", + "this", + "throw", + "true", + "try", + "typeof", + "uint", + "ulong", + "unchecked", + "unsafe", + "ushort", + "using", + "virtual", + "void", + "volatile", + "while", + + // Contextual keywords. Not reserved words, but I guess we should include + // them because this seems to be used only for syntax highlighting. + "add", + "alias", + "ascending", + "async", + "await", + "by", + "descending", + "dynamic", + "equals", + "from", + "get", + "global", + "group", + "into", + "join", + "let", + "nameof", + "on", + "orderby", + "partial", + "remove", + "select", + "set", + "value", + "var", + "when", + "where", + "yield", + 0 + }; + + const char **w = _reserved_words; + + while (*w) { + p_words->push_back(*w); + w++; + } +} + +bool CSharpLanguage::is_control_flow_keyword(String p_keyword) const { + return p_keyword == "break" || + p_keyword == "case" || + p_keyword == "catch" || + p_keyword == "continue" || + p_keyword == "default" || + p_keyword == "do" || + p_keyword == "else" || + p_keyword == "finally" || + p_keyword == "for" || + p_keyword == "foreach" || + p_keyword == "goto" || + p_keyword == "if" || + p_keyword == "return" || + p_keyword == "switch" || + p_keyword == "throw" || + p_keyword == "try" || + p_keyword == "while"; +} + +void CSharpLanguage::get_comment_delimiters(List *p_delimiters) const { + p_delimiters->push_back("//"); // single-line comment + p_delimiters->push_back("/* */"); // delimited comment +} + +void CSharpLanguage::get_string_delimiters(List *p_delimiters) const { + p_delimiters->push_back("' '"); // character literal + p_delimiters->push_back("\" \""); // regular string literal + // Verbatim string literals (`@" "`) don't render correctly, so don't highlight them. + // Generic string highlighting suffices as a workaround for now. +} + +static String get_base_class_name(const String &p_base_class_name, const String p_class_name) { + String base_class = p_base_class_name; + if (p_class_name == base_class) { + base_class = "Godot." + base_class; + } + return base_class; +} + +Ref