Jelajahi Sumber

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

Mono: Better versioning and gracefully unloading of Godot API assemblies
Rémi Verschelde 7 tahun lalu
induk
melakukan
7568a45539

+ 30 - 0
core/ustring.cpp

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

+ 1 - 0
core/ustring.h

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

+ 15 - 8
modules/mono/SCsub

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

+ 1 - 1
modules/mono/csharp_script.cpp

@@ -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());

+ 55 - 23
modules/mono/editor/bindings_generator.cpp

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

+ 3 - 0
modules/mono/editor/bindings_generator.h

@@ -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() {

+ 30 - 16
modules/mono/editor/godotsharp_builds.cpp

@@ -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) {
+
+	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());
+}
 
-	String api_name = p_api_type == API_CORE ? API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME;
+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_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 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 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);

+ 5 - 7
modules/mono/editor/godotsharp_builds.h

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

+ 2 - 2
modules/mono/editor/godotsharp_editor.cpp

@@ -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"));

+ 3 - 0
modules/mono/editor/godotsharp_export.cpp

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

+ 1 - 0
modules/mono/glue/cs_files/VERSION.txt

@@ -0,0 +1 @@
+1

+ 3 - 0
modules/mono/godotsharp_defs.h

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

+ 221 - 23
modules/mono/mono_gd/gd_mono.cpp

@@ -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
+
+	bool success = load_assembly(API_ASSEMBLY_NAME, &core_api_assembly);
 
-	if (success)
+	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,20 +503,72 @@ 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");
+	Error save_err = metadata->save(path);
+	ERR_FAIL_COND(save_err != OK);
+}
 
-	return true;
-#endif
+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() {
 
@@ -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;

+ 54 - 3
modules/mono/mono_gd/gd_mono.h

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

+ 4 - 4
modules/mono/mono_gd/gd_mono_utils.cpp

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