Merge pull request #17020 from neikeq/cs-api-asm-checks

Mono: Better versioning and gracefully unloading of Godot API assemblies
This commit is contained in:
Rémi Verschelde 2018-02-25 21:51:11 +01:00 committed by GitHub
commit 7568a45539
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 429 additions and 88 deletions

View file

@ -1135,6 +1135,36 @@ String String::num_int64(int64_t p_num, int base, bool capitalize_hex) {
return s;
}
String String::num_uint64(uint64_t p_num, int base, bool capitalize_hex) {
uint64_t n = p_num;
int chars = 0;
do {
n /= base;
chars++;
} while (n);
String s;
s.resize(chars + 1);
CharType *c = s.ptrw();
c[chars] = 0;
n = p_num;
do {
int mod = ABS(n % base);
if (mod >= 10) {
char a = (capitalize_hex ? 'A' : 'a');
c[--chars] = a + (mod - 10);
} else {
c[--chars] = '0' + mod;
}
n /= base;
} while (n);
return s;
}
String String::num_real(double p_num) {
String s;

View file

@ -146,6 +146,7 @@ public:
static String num_scientific(double p_num);
static String num_real(double p_num);
static String num_int64(int64_t p_num, int base = 10, bool capitalize_hex = false);
static String num_uint64(uint64_t p_num, int base = 10, bool capitalize_hex = false);
static String chr(CharType p_char);
static String md5(const uint8_t *p_md5);
static String hex_encode_buffer(const uint8_t *p_buffer, int p_len);

View file

@ -31,11 +31,18 @@ def make_cs_files_header(src, dst):
if i > 0:
header.write(', ')
header.write(byte_to_str(buf[buf_idx]))
inserted_files += '\tr_files.insert(\"' + file + '\", ' \
inserted_files += '\tr_files.insert("' + file + '", ' \
'CompressedFile(_cs_' + name + '_compressed_size, ' \
'_cs_' + name + '_uncompressed_size, ' \
'_cs_' + name + '_compressed));\n'
header.write(' };\n')
version_file = os.path.join(src, 'VERSION.txt')
with open(version_file, 'r') as content_file:
try:
glue_version = int(content_file.read()) # make sure the format is valid
header.write('\n#define CS_GLUE_VERSION UINT32_C(' + str(glue_version) + ')\n')
except ValueError:
raise ValueError('Invalid C# glue version in: ' + version_file)
header.write('\nstruct CompressedFile\n' '{\n'
'\tint compressed_size;\n' '\tint uncompressed_size;\n' '\tconst unsigned char* data;\n'
'\n\tCompressedFile(int p_comp_size, int p_uncomp_size, const unsigned char* p_data)\n'
@ -80,23 +87,23 @@ def find_msbuild_unix(filename):
import sys
hint_dirs = ['/opt/novell/mono/bin']
if sys.platform == "darwin":
if sys.platform == 'darwin':
hint_dirs = ['/Library/Frameworks/Mono.framework/Versions/Current/bin'] + hint_dirs
for hint_dir in hint_dirs:
hint_path = os.path.join(hint_dir, filename)
if os.path.isfile(hint_path):
return hint_path
elif os.path.isfile(hint_path + ".exe"):
return hint_path + ".exe"
elif os.path.isfile(hint_path + '.exe'):
return hint_path + '.exe'
for hint_dir in os.environ["PATH"].split(os.pathsep):
for hint_dir in os.environ['PATH'].split(os.pathsep):
hint_dir = hint_dir.strip('"')
hint_path = os.path.join(hint_dir, filename)
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"
if os.path.isfile(hint_path + '.exe') and os.access(hint_path + '.exe', os.X_OK):
return hint_path + '.exe'
return None
@ -152,7 +159,7 @@ def mono_build_solution(source, target, env):
xbuild_fallback = env['xbuild_fallback']
if xbuild_fallback and os.name == 'nt':
print("Option 'xbuild_fallback' not supported on Windows")
print('Option \'xbuild_fallback\' not supported on Windows')
xbuild_fallback = False
if xbuild_fallback:

View file

@ -456,7 +456,7 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::debug_get_current_stack_info()
#ifdef DEBUG_ENABLED
// Printing an error here will result in endless recursion, so we must be careful
if (!gdmono->is_runtime_initialized() || !GDMono::get_singleton()->get_api_assembly() || !GDMonoUtils::mono_cache.corlib_cache_updated)
if (!gdmono->is_runtime_initialized() || !GDMono::get_singleton()->get_core_api_assembly() || !GDMonoUtils::mono_cache.corlib_cache_updated)
return Vector<StackInfo>();
MonoObject *stack_trace = mono_object_new(mono_domain_get(), CACHED_CLASS(System_Diagnostics_StackTrace)->get_mono_ptr());

View file

@ -70,8 +70,6 @@
#define LOCAL_RET "ret"
#define CS_CLASS_NATIVECALLS "NativeCalls"
#define CS_CLASS_NATIVECALLS_EDITOR "EditorNativeCalls"
#define CS_FIELD_MEMORYOWN "memoryOwn"
#define CS_PARAM_METHODBIND "method"
#define CS_PARAM_INSTANCE "ptr"
@ -105,6 +103,8 @@
#define C_METHOD_MANAGED_TO_DICT C_NS_MONOMARSHAL "::mono_object_to_Dictionary"
#define C_METHOD_MANAGED_FROM_DICT C_NS_MONOMARSHAL "::Dictionary_to_mono_object"
#define BINDINGS_GENERATOR_VERSION UINT32_C(1)
const char *BindingsGenerator::TypeInterface::DEFAULT_VARARG_C_IN = "\t%0 %1_in = %1;\n";
bool BindingsGenerator::verbose_output = false;
@ -529,7 +529,15 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_output_dir, bo
"using System.Collections.Generic;\n"
"\n");
cs_icalls_content.push_back("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK);
cs_icalls_content.push_back(INDENT1 "internal static class " CS_CLASS_NATIVECALLS "\n" INDENT1 OPEN_BLOCK);
cs_icalls_content.push_back(INDENT1 "internal static class " BINDINGS_CLASS_NATIVECALLS "\n" INDENT1 OPEN_BLOCK);
cs_icalls_content.push_back(INDENT2 "internal static ulong godot_api_hash = ");
cs_icalls_content.push_back(String::num_uint64(GDMono::get_singleton()->get_api_core_hash()) + ";\n");
cs_icalls_content.push_back(INDENT2 "internal static uint bindings_version = ");
cs_icalls_content.push_back(String::num_uint64(BINDINGS_GENERATOR_VERSION) + ";\n");
cs_icalls_content.push_back(INDENT2 "internal static uint cs_glue_version = ");
cs_icalls_content.push_back(String::num_uint64(CS_GLUE_VERSION) + ";\n");
cs_icalls_content.push_back("\n");
#define ADD_INTERNAL_CALL(m_icall) \
if (!m_icall.editor_only) { \
@ -551,7 +559,7 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_output_dir, bo
cs_icalls_content.push_back(INDENT1 CLOSE_BLOCK CLOSE_BLOCK);
String internal_methods_file = path_join(core_dir, CS_CLASS_NATIVECALLS ".cs");
String internal_methods_file = path_join(core_dir, BINDINGS_CLASS_NATIVECALLS ".cs");
Error err = _save_file(internal_methods_file, cs_icalls_content);
if (err != OK)
@ -626,7 +634,15 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_output_dir,
"using System.Collections.Generic;\n"
"\n");
cs_icalls_content.push_back("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK);
cs_icalls_content.push_back(INDENT1 "internal static class " CS_CLASS_NATIVECALLS_EDITOR "\n" INDENT1 OPEN_BLOCK);
cs_icalls_content.push_back(INDENT1 "internal static class " BINDINGS_CLASS_NATIVECALLS_EDITOR "\n" INDENT1 OPEN_BLOCK);
cs_icalls_content.push_back(INDENT2 "internal static ulong godot_api_hash = ");
cs_icalls_content.push_back(String::num_uint64(GDMono::get_singleton()->get_api_editor_hash()) + ";\n");
cs_icalls_content.push_back(INDENT2 "internal static uint bindings_version = ");
cs_icalls_content.push_back(String::num_uint64(BINDINGS_GENERATOR_VERSION) + ";\n");
cs_icalls_content.push_back(INDENT2 "internal static uint cs_glue_version = ");
cs_icalls_content.push_back(String::num_uint64(CS_GLUE_VERSION) + ";\n");
cs_icalls_content.push_back("\n");
#define ADD_INTERNAL_CALL(m_icall) \
if (m_icall.editor_only) { \
@ -648,7 +664,7 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_output_dir,
cs_icalls_content.push_back(INDENT1 CLOSE_BLOCK CLOSE_BLOCK);
String internal_methods_file = path_join(core_dir, CS_CLASS_NATIVECALLS_EDITOR ".cs");
String internal_methods_file = path_join(core_dir, BINDINGS_CLASS_NATIVECALLS_EDITOR ".cs");
Error err = _save_file(internal_methods_file, cs_icalls_content);
if (err != OK)
@ -882,7 +898,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
output.push_back("\";\n");
output.push_back(INDENT2 "internal static IntPtr " BINDINGS_PTR_FIELD " = ");
output.push_back(itype.api_type == ClassDB::API_EDITOR ? CS_CLASS_NATIVECALLS_EDITOR : CS_CLASS_NATIVECALLS);
output.push_back(itype.api_type == ClassDB::API_EDITOR ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS);
output.push_back("." ICALL_PREFIX);
output.push_back(itype.name);
output.push_back(SINGLETON_ICALL_SUFFIX "();\n");
@ -912,7 +928,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
// The engine will initialize the pointer field of the managed side before calling the constructor
// This is why we only allocate a new native object from the constructor if the pointer field is not set
output.push_back(")\n" OPEN_BLOCK_L2 "if (" BINDINGS_PTR_FIELD " == IntPtr.Zero)\n" INDENT4 BINDINGS_PTR_FIELD " = ");
output.push_back(itype.api_type == ClassDB::API_EDITOR ? CS_CLASS_NATIVECALLS_EDITOR : CS_CLASS_NATIVECALLS);
output.push_back(itype.api_type == ClassDB::API_EDITOR ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS);
output.push_back("." + ctor_method);
output.push_back("(this);\n" CLOSE_BLOCK_L2);
} else {
@ -956,7 +972,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
"if (disposed) return;\n" INDENT3
"if (" BINDINGS_PTR_FIELD " != IntPtr.Zero)\n" OPEN_BLOCK_L3
"if (" CS_FIELD_MEMORYOWN ")\n" OPEN_BLOCK_L4 CS_FIELD_MEMORYOWN
" = false;\n" INDENT5 CS_CLASS_NATIVECALLS "." ICALL_OBJECT_DTOR
" = false;\n" INDENT5 BINDINGS_CLASS_NATIVECALLS "." ICALL_OBJECT_DTOR
"(this, " BINDINGS_PTR_FIELD ");\n" CLOSE_BLOCK_L4 CLOSE_BLOCK_L3 INDENT3
"this." BINDINGS_PTR_FIELD " = IntPtr.Zero;\n" INDENT3
"GC.SuppressFinalize(this);\n" INDENT3 "disposed = true;\n" CLOSE_BLOCK_L2);
@ -1229,7 +1245,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
{
if (p_itype.is_object_type && !p_imethod.is_virtual && !p_imethod.requires_object_call) {
p_output.push_back(MEMBER_BEGIN "private static IntPtr ");
p_output.push_back(method_bind_field + " = " CS_CLASS_NATIVECALLS "." ICALL_GET_METHODBIND "(" BINDINGS_NATIVE_NAME_FIELD ", \"");
p_output.push_back(method_bind_field + " = " BINDINGS_CLASS_NATIVECALLS "." ICALL_GET_METHODBIND "(" BINDINGS_NATIVE_NAME_FIELD ", \"");
p_output.push_back(p_imethod.name);
p_output.push_back("\");\n");
}
@ -1310,7 +1326,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
const InternalCall *im_icall = match->value();
String im_call = im_icall->editor_only ? CS_CLASS_NATIVECALLS_EDITOR : CS_CLASS_NATIVECALLS;
String im_call = im_icall->editor_only ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS;
im_call += "." + im_icall->name + "(" + icall_params + ");\n";
if (p_imethod.arguments.size())
@ -1400,25 +1416,33 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) {
}
output.push_back("namespace GodotSharpBindings\n" OPEN_BLOCK);
output.push_back("uint64_t get_core_api_hash() { return ");
output.push_back(itos(GDMono::get_singleton()->get_api_core_hash()) + "; }\n");
output.push_back(String::num_uint64(GDMono::get_singleton()->get_api_core_hash()) + "; }\n");
output.push_back("#ifdef TOOLS_ENABLED\n"
"uint64_t get_editor_api_hash() { return ");
output.push_back(itos(GDMono::get_singleton()->get_api_editor_hash()) +
output.push_back(String::num_uint64(GDMono::get_singleton()->get_api_editor_hash()) +
"; }\n#endif // TOOLS_ENABLED\n");
output.push_back("uint32_t get_bindings_version() { return ");
output.push_back(String::num_uint64(BINDINGS_GENERATOR_VERSION) + "; }\n");
output.push_back("uint32_t get_cs_glue_version() { return ");
output.push_back(String::num_uint64(CS_GLUE_VERSION) + "; }\n");
output.push_back("void register_generated_icalls() " OPEN_BLOCK);
output.push_back("\tgodot_register_header_icalls();");
#define ADD_INTERNAL_CALL_REGISTRATION(m_icall) \
{ \
output.push_back("\tmono_add_internal_call("); \
output.push_back("\"" BINDINGS_NAMESPACE "."); \
output.push_back(m_icall.editor_only ? CS_CLASS_NATIVECALLS_EDITOR : CS_CLASS_NATIVECALLS); \
output.push_back("::"); \
output.push_back(m_icall.name); \
output.push_back("\", (void*)"); \
output.push_back(m_icall.name); \
output.push_back(");\n"); \
#define ADD_INTERNAL_CALL_REGISTRATION(m_icall) \
{ \
output.push_back("\tmono_add_internal_call("); \
output.push_back("\"" BINDINGS_NAMESPACE "."); \
output.push_back(m_icall.editor_only ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS); \
output.push_back("::"); \
output.push_back(m_icall.name); \
output.push_back("\", (void*)"); \
output.push_back(m_icall.name); \
output.push_back(");\n"); \
}
bool tools_sequence = false;
@ -1486,6 +1510,14 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) {
return OK;
}
uint32_t BindingsGenerator::get_version() {
return BINDINGS_GENERATOR_VERSION;
}
uint32_t BindingsGenerator::get_cs_glue_version() {
return CS_GLUE_VERSION;
}
Error BindingsGenerator::_save_file(const String &p_path, const List<String> &p_content) {
FileAccessRef file = FileAccess::open(p_path, FileAccess::WRITE);

View file

@ -536,6 +536,9 @@ public:
Error generate_cs_editor_project(const String &p_output_dir, const String &p_core_dll_path, bool p_verbose_output = true);
Error generate_glue(const String &p_output_dir);
static uint32_t get_version();
static uint32_t get_cs_glue_version();
void initialize();
_FORCE_INLINE_ static BindingsGenerator *get_singleton() {

View file

@ -33,7 +33,6 @@
#include "main/main.h"
#include "../godotsharp_dirs.h"
#include "../mono_gd/gd_mono.h"
#include "../mono_gd/gd_mono_class.h"
#include "../mono_gd/gd_mono_marshal.h"
#include "../utils/path_utils.h"
@ -178,13 +177,15 @@ bool GodotSharpBuilds::build_api_sln(const String &p_name, const String &p_api_s
return true;
}
bool GodotSharpBuilds::copy_api_assembly(const String &p_src_dir, const String &p_dst_dir, const String &p_assembly_name) {
bool GodotSharpBuilds::copy_api_assembly(const String &p_src_dir, const String &p_dst_dir, const String &p_assembly_name, APIAssembly::Type p_api_type) {
String assembly_file = p_assembly_name + ".dll";
String assembly_src = p_src_dir.plus_file(assembly_file);
String assembly_dst = p_dst_dir.plus_file(assembly_file);
if (!FileAccess::exists(assembly_dst) || FileAccess::get_modified_time(assembly_src) > FileAccess::get_modified_time(assembly_dst)) {
if (!FileAccess::exists(assembly_dst) ||
FileAccess::get_modified_time(assembly_src) > FileAccess::get_modified_time(assembly_dst) ||
GDMono::get_singleton()->metadata_is_api_assembly_invalidated(p_api_type)) {
DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
String xml_file = p_assembly_name + ".xml";
@ -203,33 +204,46 @@ bool GodotSharpBuilds::copy_api_assembly(const String &p_src_dir, const String &
show_build_error_dialog("Failed to copy " + assembly_file);
return false;
}
GDMono::get_singleton()->metadata_set_api_assembly_invalidated(p_api_type, false);
}
return true;
}
bool GodotSharpBuilds::make_api_sln(GodotSharpBuilds::APIType p_api_type) {
String GodotSharpBuilds::_api_folder_name(APIAssembly::Type p_api_type) {
String api_name = p_api_type == API_CORE ? API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME;
uint64_t api_hash = p_api_type == APIAssembly::API_CORE ?
GDMono::get_singleton()->get_api_core_hash() :
GDMono::get_singleton()->get_api_editor_hash();
return String::num_uint64(api_hash) +
"_" + String::num_uint64(BindingsGenerator::get_version()) +
"_" + String::num_uint64(BindingsGenerator::get_cs_glue_version());
}
bool GodotSharpBuilds::make_api_sln(APIAssembly::Type p_api_type) {
String api_name = p_api_type == APIAssembly::API_CORE ? API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME;
String api_build_config = "Release";
EditorProgress pr("mono_build_release_" + api_name, "Building " + api_name + " solution...", 4);
pr.step("Generating " + api_name + " solution");
uint64_t core_hash = GDMono::get_singleton()->get_api_core_hash();
uint64_t editor_hash = GDMono::get_singleton()->get_api_editor_hash();
String core_api_sln_dir = GodotSharpDirs::get_mono_solutions_dir()
.plus_file(_api_folder_name(APIAssembly::API_CORE))
.plus_file(API_ASSEMBLY_NAME);
String editor_api_sln_dir = GodotSharpDirs::get_mono_solutions_dir()
.plus_file(_api_folder_name(APIAssembly::API_EDITOR))
.plus_file(EDITOR_API_ASSEMBLY_NAME);
String core_api_sln_dir = GodotSharpDirs::get_mono_solutions_dir().plus_file(API_ASSEMBLY_NAME "_" + itos(core_hash));
String editor_api_sln_dir = GodotSharpDirs::get_mono_solutions_dir().plus_file(EDITOR_API_ASSEMBLY_NAME "_" + itos(editor_hash));
String api_sln_dir = p_api_type == API_CORE ? core_api_sln_dir : editor_api_sln_dir;
String api_sln_dir = p_api_type == APIAssembly::API_CORE ? core_api_sln_dir : editor_api_sln_dir;
String api_sln_file = api_sln_dir.plus_file(api_name + ".sln");
if (!DirAccess::exists(api_sln_dir) || !FileAccess::exists(api_sln_file)) {
String core_api_assembly;
if (p_api_type == API_EDITOR) {
if (p_api_type == APIAssembly::API_EDITOR) {
core_api_assembly = core_api_sln_dir.plus_file("bin")
.plus_file(api_build_config)
.plus_file(API_ASSEMBLY_NAME ".dll");
@ -242,7 +256,7 @@ bool GodotSharpBuilds::make_api_sln(GodotSharpBuilds::APIType p_api_type) {
BindingsGenerator *gen = BindingsGenerator::get_singleton();
bool gen_verbose = OS::get_singleton()->is_stdout_verbose();
Error err = p_api_type == API_CORE ?
Error err = p_api_type == APIAssembly::API_CORE ?
gen->generate_cs_core_project(api_sln_dir, gen_verbose) :
gen->generate_cs_editor_project(api_sln_dir, core_api_assembly, gen_verbose);
@ -275,7 +289,7 @@ bool GodotSharpBuilds::make_api_sln(GodotSharpBuilds::APIType p_api_type) {
// Copy the built assembly to the assemblies directory
String api_assembly_dir = api_sln_dir.plus_file("bin").plus_file(api_build_config);
if (!GodotSharpBuilds::copy_api_assembly(api_assembly_dir, res_assemblies_dir, api_name))
if (!GodotSharpBuilds::copy_api_assembly(api_assembly_dir, res_assemblies_dir, api_name, p_api_type))
return false;
pr.step("Done");
@ -288,10 +302,10 @@ bool GodotSharpBuilds::build_project_blocking(const String &p_config) {
if (!FileAccess::exists(GodotSharpDirs::get_project_sln_path()))
return true; // No solution to build
if (!GodotSharpBuilds::make_api_sln(GodotSharpBuilds::API_CORE))
if (!GodotSharpBuilds::make_api_sln(APIAssembly::API_CORE))
return false;
if (!GodotSharpBuilds::make_api_sln(GodotSharpBuilds::API_EDITOR))
if (!GodotSharpBuilds::make_api_sln(APIAssembly::API_EDITOR))
return false;
EditorProgress pr("mono_project_debug_build", "Building project solution...", 2);

View file

@ -31,6 +31,7 @@
#ifndef GODOTSHARP_BUILDS_H
#define GODOTSHARP_BUILDS_H
#include "../mono_gd/gd_mono.h"
#include "mono_bottom_panel.h"
#include "mono_build_info.h"
@ -56,17 +57,14 @@ private:
HashMap<MonoBuildInfo, BuildProcess, MonoBuildInfo::Hasher> builds;
static String _api_folder_name(APIAssembly::Type p_api_type);
static GodotSharpBuilds *singleton;
friend class GDMono;
static void _register_internal_calls();
public:
enum APIType {
API_CORE,
API_EDITOR
};
enum BuildTool {
MSBUILD_MONO,
#ifdef WINDOWS_ENABLED
@ -89,9 +87,9 @@ public:
bool build_async(const MonoBuildInfo &p_build_info, GodotSharpBuild_ExitCallback p_callback = NULL);
static bool build_api_sln(const String &p_name, const String &p_api_sln_dir, const String &p_config);
static bool copy_api_assembly(const String &p_src_dir, const String &p_dst_dir, const String &p_assembly_name);
static bool copy_api_assembly(const String &p_src_dir, const String &p_dst_dir, const String &p_assembly_name, APIAssembly::Type p_api_type);
static bool make_api_sln(APIType p_api_type);
static bool make_api_sln(APIAssembly::Type p_api_type);
static bool build_project_blocking(const String &p_config);

View file

@ -85,10 +85,10 @@ bool GodotSharpEditor::_create_project_solution() {
return false;
}
if (!GodotSharpBuilds::make_api_sln(GodotSharpBuilds::API_CORE))
if (!GodotSharpBuilds::make_api_sln(APIAssembly::API_CORE))
return false;
if (!GodotSharpBuilds::make_api_sln(GodotSharpBuilds::API_EDITOR))
if (!GodotSharpBuilds::make_api_sln(APIAssembly::API_EDITOR))
return false;
pr.step(TTR("Done"));

View file

@ -55,6 +55,9 @@ void GodotSharpExport::_export_begin(const Set<String> &p_features, bool p_debug
// TODO right now there is no way to stop the export process with an error
ERR_FAIL_COND(!GDMono::get_singleton()->is_runtime_initialized());
ERR_FAIL_NULL(GDMono::get_singleton()->get_tools_domain());
String build_config = p_debug ? "Debug" : "Release";
ERR_FAIL_COND(!GodotSharpBuilds::build_project_blocking(build_config));

View file

@ -0,0 +1 @@
1

View file

@ -39,4 +39,7 @@
#define EDITOR_API_ASSEMBLY_NAME "GodotSharpEditor"
#define EDITOR_TOOLS_ASSEMBLY_NAME "GodotSharpTools"
#define BINDINGS_CLASS_NATIVECALLS "NativeCalls"
#define BINDINGS_CLASS_NATIVECALLS_EDITOR "EditorNativeCalls"
#endif // GODOTSHARP_DEFS_H

View file

@ -42,7 +42,10 @@
#include "project_settings.h"
#include "../csharp_script.h"
#include "../godotsharp_dirs.h"
#include "../utils/path_utils.h"
#include "gd_mono_class.h"
#include "gd_mono_marshal.h"
#include "gd_mono_utils.h"
#ifdef TOOLS_ENABLED
@ -208,7 +211,46 @@ void GDMono::initialize() {
_register_internal_calls();
// The following assemblies are not required at initialization
_load_all_script_assemblies();
#ifndef MONO_GLUE_DISABLED
if (_load_api_assemblies()) {
if (!core_api_assembly_out_of_sync && !editor_api_assembly_out_of_sync && GDMonoUtils::mono_cache.godot_api_cache_updated) {
// Everything is fine with the api assemblies, load the project assembly
_load_project_assembly();
} else {
#ifdef TOOLS_ENABLED
// The assembly was successfuly loaded, but the full api could not be cached.
// This is most likely an outdated assembly loaded because of an invalid version in the metadata,
// so we invalidate the version in the metadata and unload the script domain.
if (core_api_assembly_out_of_sync) {
ERR_PRINT("The loaded Core API assembly is out of sync");
metadata_set_api_assembly_invalidated(APIAssembly::API_CORE, true);
} else if (!GDMonoUtils::mono_cache.godot_api_cache_updated) {
ERR_PRINT("The loaded Core API assembly is in sync, but the cache update failed");
metadata_set_api_assembly_invalidated(APIAssembly::API_CORE, true);
}
if (editor_api_assembly_out_of_sync) {
ERR_PRINT("The loaded Editor API assembly is out of sync");
metadata_set_api_assembly_invalidated(APIAssembly::API_EDITOR, true);
}
OS::get_singleton()->print("Mono: Proceeding to unload scripts domain because of invalid API assemblies\n");
Error err = _unload_scripts_domain();
if (err != OK) {
WARN_PRINT("Mono: Failed to unload scripts domain");
}
#else
ERR_PRINT("The loaded API assembly is invalid");
CRASH_NOW();
#endif
}
}
#else
if (OS::get_singleton()->is_stdout_verbose())
OS::get_singleton()->print("Mono: Glue disabled, ignoring script assemblies\n");
#endif
mono_install_unhandled_exception_hook(gdmono_unhandled_exception_hook, NULL);
@ -219,7 +261,11 @@ void GDMono::initialize() {
namespace GodotSharpBindings {
uint64_t get_core_api_hash();
#ifdef TOOLS_ENABLED
uint64_t get_editor_api_hash();
#endif // TOOLS_ENABLED
uint32_t get_bindings_version();
uint32_t get_cs_glue_version();
void register_generated_icalls();
} // namespace GodotSharpBindings
@ -313,6 +359,36 @@ bool GDMono::load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMo
return true;
}
APIAssembly::Version APIAssembly::Version::get_from_loaded_assembly(GDMonoAssembly *p_api_assembly, APIAssembly::Type p_api_type) {
APIAssembly::Version api_assembly_version;
const char *nativecalls_name = p_api_type == APIAssembly::API_CORE ?
BINDINGS_CLASS_NATIVECALLS :
BINDINGS_CLASS_NATIVECALLS_EDITOR;
GDMonoClass *nativecalls_klass = p_api_assembly->get_class(BINDINGS_NAMESPACE, nativecalls_name);
if (nativecalls_klass) {
GDMonoField *api_hash_field = nativecalls_klass->get_field("godot_api_hash");
if (api_hash_field)
api_assembly_version.godot_api_hash = GDMonoMarshal::unbox<uint64_t>(api_hash_field->get_value(NULL));
GDMonoField *binds_ver_field = nativecalls_klass->get_field("bindings_version");
if (binds_ver_field)
api_assembly_version.bindings_version = GDMonoMarshal::unbox<uint32_t>(binds_ver_field->get_value(NULL));
GDMonoField *cs_glue_ver_field = nativecalls_klass->get_field("cs_glue_version");
if (cs_glue_ver_field)
api_assembly_version.cs_glue_version = GDMonoMarshal::unbox<uint32_t>(cs_glue_ver_field->get_value(NULL));
}
return api_assembly_version;
}
String APIAssembly::to_string(APIAssembly::Type p_type) {
return p_type == APIAssembly::API_CORE ? "API_CORE" : "API_EDITOR";
}
bool GDMono::_load_corlib_assembly() {
if (corlib_assembly)
@ -328,13 +404,25 @@ bool GDMono::_load_corlib_assembly() {
bool GDMono::_load_core_api_assembly() {
if (api_assembly)
if (core_api_assembly)
return true;
bool success = load_assembly(API_ASSEMBLY_NAME, &api_assembly);
#ifdef TOOLS_ENABLED
if (metadata_is_api_assembly_invalidated(APIAssembly::API_CORE))
return false;
#endif
if (success)
bool success = load_assembly(API_ASSEMBLY_NAME, &core_api_assembly);
if (success) {
#ifndef MONO_GLUE_DISABLED
APIAssembly::Version api_assembly_ver = APIAssembly::Version::get_from_loaded_assembly(core_api_assembly, APIAssembly::API_CORE);
core_api_assembly_out_of_sync = GodotSharpBindings::get_core_api_hash() != api_assembly_ver.godot_api_hash ||
GodotSharpBindings::get_bindings_version() != api_assembly_ver.bindings_version ||
GodotSharpBindings::get_cs_glue_version() != api_assembly_ver.cs_glue_version;
#endif
GDMonoUtils::update_godot_api_cache();
}
return success;
}
@ -345,7 +433,23 @@ bool GDMono::_load_editor_api_assembly() {
if (editor_api_assembly)
return true;
return load_assembly(EDITOR_API_ASSEMBLY_NAME, &editor_api_assembly);
#ifdef TOOLS_ENABLED
if (metadata_is_api_assembly_invalidated(APIAssembly::API_EDITOR))
return false;
#endif
bool success = load_assembly(EDITOR_API_ASSEMBLY_NAME, &editor_api_assembly);
if (success) {
#ifndef MONO_GLUE_DISABLED
APIAssembly::Version api_assembly_ver = APIAssembly::Version::get_from_loaded_assembly(editor_api_assembly, APIAssembly::API_EDITOR);
editor_api_assembly_out_of_sync = GodotSharpBindings::get_editor_api_hash() != api_assembly_ver.godot_api_hash ||
GodotSharpBindings::get_bindings_version() != api_assembly_ver.bindings_version ||
GodotSharpBindings::get_cs_glue_version() != api_assembly_ver.cs_glue_version;
#endif
}
return success;
}
#endif
@ -373,15 +477,18 @@ bool GDMono::_load_project_assembly() {
bool success = load_assembly(name, &project_assembly);
if (success)
if (success) {
mono_assembly_set_main(project_assembly->get_assembly());
} else {
if (OS::get_singleton()->is_stdout_verbose())
OS::get_singleton()->printerr("Mono: Failed to load project assembly\n");
}
return success;
}
bool GDMono::_load_all_script_assemblies() {
bool GDMono::_load_api_assemblies() {
#ifndef MONO_GLUE_DISABLED
if (!_load_core_api_assembly()) {
if (OS::get_singleton()->is_stdout_verbose())
OS::get_singleton()->printerr("Mono: Failed to load Core API assembly\n");
@ -396,21 +503,73 @@ bool GDMono::_load_all_script_assemblies() {
#endif
}
if (!_load_project_assembly()) {
if (OS::get_singleton()->is_stdout_verbose())
OS::get_singleton()->printerr("Mono: Failed to load project assembly\n");
return false;
return true;
}
#ifdef TOOLS_ENABLED
String GDMono::_get_api_assembly_metadata_path() {
return GodotSharpDirs::get_res_metadata_dir().plus_file("api_assemblies.cfg");
}
void GDMono::metadata_set_api_assembly_invalidated(APIAssembly::Type p_api_type, bool p_invalidated) {
String section = APIAssembly::to_string(p_api_type);
String path = _get_api_assembly_metadata_path();
Ref<ConfigFile> metadata;
metadata.instance();
metadata->load(path);
metadata->set_value(section, "invalidated", p_invalidated);
String assembly_path = GodotSharpDirs::get_res_assemblies_dir()
.plus_file(p_api_type == APIAssembly::API_CORE ?
API_ASSEMBLY_NAME ".dll" :
EDITOR_API_ASSEMBLY_NAME ".dll");
ERR_FAIL_COND(!FileAccess::exists(assembly_path));
uint64_t modified_time = FileAccess::get_modified_time(assembly_path);
metadata->set_value(section, "invalidated_asm_modified_time", String::num_uint64(modified_time));
String dir = path.get_base_dir();
if (!DirAccess::exists(dir)) {
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
ERR_FAIL_COND(!da);
Error err = da->make_dir_recursive(ProjectSettings::get_singleton()->globalize_path(dir));
ERR_FAIL_COND(err != OK);
}
return true;
#else
if (OS::get_singleton()->is_stdout_verbose())
OS::get_singleton()->print("Mono: Glue disbled, ignoring script assemblies\n");
return true;
#endif
Error save_err = metadata->save(path);
ERR_FAIL_COND(save_err != OK);
}
bool GDMono::metadata_is_api_assembly_invalidated(APIAssembly::Type p_api_type) {
String section = APIAssembly::to_string(p_api_type);
Ref<ConfigFile> metadata;
metadata.instance();
metadata->load(_get_api_assembly_metadata_path());
String assembly_path = GodotSharpDirs::get_res_assemblies_dir()
.plus_file(p_api_type == APIAssembly::API_CORE ?
API_ASSEMBLY_NAME ".dll" :
EDITOR_API_ASSEMBLY_NAME ".dll");
if (!FileAccess::exists(assembly_path))
return false;
uint64_t modified_time = FileAccess::get_modified_time(assembly_path);
uint64_t stored_modified_time = metadata->get_value(section, "invalidated_asm_modified_time", 0);
return metadata->get_value(section, "invalidated", false) && modified_time <= stored_modified_time;
}
#endif
Error GDMono::_load_scripts_domain() {
ERR_FAIL_COND_V(scripts_domain != NULL, ERR_BUG);
@ -452,12 +611,15 @@ Error GDMono::_unload_scripts_domain() {
_domain_assemblies_cleanup(mono_domain_get_id(scripts_domain));
api_assembly = NULL;
core_api_assembly = NULL;
project_assembly = NULL;
#ifdef TOOLS_ENABLED
editor_api_assembly = NULL;
#endif
core_api_assembly_out_of_sync = false;
editor_api_assembly_out_of_sync = false;
MonoDomain *domain = scripts_domain;
scripts_domain = NULL;
@ -512,12 +674,45 @@ Error GDMono::reload_scripts_domain() {
return err;
}
if (!_load_all_script_assemblies()) {
if (OS::get_singleton()->is_stdout_verbose())
OS::get_singleton()->printerr("Mono: Failed to load script assemblies\n");
#ifndef MONO_GLUE_DISABLED
if (!_load_api_assemblies()) {
return ERR_CANT_OPEN;
}
if (!core_api_assembly_out_of_sync && !editor_api_assembly_out_of_sync && GDMonoUtils::mono_cache.godot_api_cache_updated) {
// Everything is fine with the api assemblies, load the project assembly
_load_project_assembly();
} else {
// The assembly was successfuly loaded, but the full api could not be cached.
// This is most likely an outdated assembly loaded because of an invalid version in the metadata,
// so we invalidate the version in the metadata and unload the script domain.
if (core_api_assembly_out_of_sync) {
metadata_set_api_assembly_invalidated(APIAssembly::API_CORE, true);
} else if (!GDMonoUtils::mono_cache.godot_api_cache_updated) {
ERR_PRINT("Core API assembly is in sync, but the cache update failed");
metadata_set_api_assembly_invalidated(APIAssembly::API_CORE, true);
}
if (editor_api_assembly_out_of_sync) {
metadata_set_api_assembly_invalidated(APIAssembly::API_EDITOR, true);
}
Error err = _unload_scripts_domain();
if (err != OK) {
WARN_PRINT("Mono: Failed to unload scripts domain");
}
return ERR_CANT_RESOLVE;
}
if (!_load_project_assembly())
return ERR_CANT_OPEN;
#else
if (OS::get_singleton()->is_stdout_verbose())
OS::get_singleton()->print("Mono: Glue disabled, ignoring script assemblies\n");
#endif
return OK;
}
#endif
@ -604,8 +799,11 @@ GDMono::GDMono() {
tools_domain = NULL;
#endif
core_api_assembly_out_of_sync = false;
editor_api_assembly_out_of_sync = false;
corlib_assembly = NULL;
api_assembly = NULL;
core_api_assembly = NULL;
project_assembly = NULL;
#ifdef TOOLS_ENABLED
editor_api_assembly = NULL;

View file

@ -31,6 +31,8 @@
#ifndef GD_MONO_H
#define GD_MONO_H
#include "core/io/config_file.h"
#include "../godotsharp_defs.h"
#include "gd_mono_assembly.h"
#include "gd_mono_log.h"
@ -39,6 +41,43 @@
#include "../utils/mono_reg_utils.h"
#endif
namespace APIAssembly {
enum Type {
API_CORE,
API_EDITOR
};
struct Version {
uint64_t godot_api_hash;
uint32_t bindings_version;
uint32_t cs_glue_version;
bool operator==(const Version &p_other) const {
return godot_api_hash == p_other.godot_api_hash &&
bindings_version == p_other.bindings_version &&
cs_glue_version == p_other.cs_glue_version;
}
Version() :
godot_api_hash(0),
bindings_version(0),
cs_glue_version(0) {
}
Version(uint64_t p_godot_api_hash,
uint32_t p_bindings_version,
uint32_t p_cs_glue_version) :
godot_api_hash(p_godot_api_hash),
bindings_version(p_bindings_version),
cs_glue_version(p_cs_glue_version) {
}
static Version get_from_loaded_assembly(GDMonoAssembly *p_api_assembly, Type p_api_type);
};
String to_string(Type p_type);
} // namespace APIAssembly
#define SCRIPTS_DOMAIN GDMono::get_singleton()->get_scripts_domain()
#ifdef TOOLS_ENABLED
#define TOOLS_DOMAIN GDMono::get_singleton()->get_tools_domain()
@ -55,8 +94,11 @@ class GDMono {
MonoDomain *tools_domain;
#endif
bool core_api_assembly_out_of_sync;
bool editor_api_assembly_out_of_sync;
GDMonoAssembly *corlib_assembly;
GDMonoAssembly *api_assembly;
GDMonoAssembly *core_api_assembly;
GDMonoAssembly *project_assembly;
#ifdef TOOLS_ENABLED
GDMonoAssembly *editor_api_assembly;
@ -75,7 +117,11 @@ class GDMono {
#endif
bool _load_project_assembly();
bool _load_all_script_assemblies();
bool _load_api_assemblies();
#ifdef TOOLS_ENABLED
String _get_api_assembly_metadata_path();
#endif
void _register_internal_calls();
@ -111,6 +157,11 @@ public:
#endif
#endif
#ifdef TOOLS_ENABLED
void metadata_set_api_assembly_invalidated(APIAssembly::Type p_api_type, bool p_invalidated);
bool metadata_is_api_assembly_invalidated(APIAssembly::Type p_api_type);
#endif
static GDMono *get_singleton() { return singleton; }
// Do not use these, unless you know what you're doing
@ -126,7 +177,7 @@ public:
#endif
_FORCE_INLINE_ GDMonoAssembly *get_corlib_assembly() const { return corlib_assembly; }
_FORCE_INLINE_ GDMonoAssembly *get_api_assembly() const { return api_assembly; }
_FORCE_INLINE_ GDMonoAssembly *get_core_api_assembly() const { return core_api_assembly; }
_FORCE_INLINE_ GDMonoAssembly *get_project_assembly() const { return project_assembly; }
#ifdef TOOLS_ENABLED
_FORCE_INLINE_ GDMonoAssembly *get_editor_api_assembly() const { return editor_api_assembly; }

View file

@ -143,7 +143,7 @@ void MonoCache::cleanup() {
godot_api_cache_updated = false;
}
#define GODOT_API_CLASS(m_class) (GDMono::get_singleton()->get_api_assembly()->get_class(BINDINGS_NAMESPACE, #m_class))
#define GODOT_API_CLASS(m_class) (GDMono::get_singleton()->get_core_api_assembly()->get_class(BINDINGS_NAMESPACE, #m_class))
void update_corlib_cache() {
@ -245,7 +245,7 @@ void update_godot_api_cache() {
mono_runtime_object_init(task_scheduler);
mono_cache.task_scheduler_handle = MonoGCHandle::create_strong(task_scheduler);
mono_cache.corlib_cache_updated = true;
mono_cache.godot_api_cache_updated = true;
}
void clear_cache() {
@ -305,7 +305,7 @@ GDMonoClass *type_get_proxy_class(const StringName &p_type) {
if (class_name[0] == '_')
class_name = class_name.substr(1, class_name.length());
GDMonoClass *klass = GDMono::get_singleton()->get_api_assembly()->get_class(BINDINGS_NAMESPACE, class_name);
GDMonoClass *klass = GDMono::get_singleton()->get_core_api_assembly()->get_class(BINDINGS_NAMESPACE, class_name);
#ifdef TOOLS_ENABLED
if (!klass) {
@ -321,7 +321,7 @@ GDMonoClass *get_class_native_base(GDMonoClass *p_class) {
do {
const GDMonoAssembly *assembly = klass->get_assembly();
if (assembly == GDMono::get_singleton()->get_api_assembly())
if (assembly == GDMono::get_singleton()->get_core_api_assembly())
return klass;
#ifdef TOOLS_ENABLED
if (assembly == GDMono::get_singleton()->get_editor_api_assembly())