C#: Fix detection of outdated release Godot API assemblies

This commit is contained in:
Ignacio Etcheverry 2019-10-11 01:23:35 +02:00
parent f4afaecdd1
commit 8c438a2197
12 changed files with 315 additions and 193 deletions

View file

@ -160,9 +160,16 @@ namespace GodotTools
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
return true; // No solution to build
// Make sure to update the API assemblies if they happen to be missing. Just in
// case the user decided to delete them at some point after they were loaded.
Internal.UpdateApiAssembliesFromPrebuilt();
// Make sure the API assemblies are up to date before building the project.
// We may not have had the chance to update the release API assemblies, and the debug ones
// may have been deleted by the user at some point after they were loaded by the Godot editor.
string apiAssembliesUpdateError = Internal.UpdateApiAssembliesFromPrebuilt(config == "Release" ? "Release" : "Debug");
if (!string.IsNullOrEmpty(apiAssembliesUpdateError))
{
ShowBuildErrorDialog("Failed to update the Godot API assemblies");
return false;
}
var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
var buildTool = (BuildTool) editorSettings.GetSetting("mono/builds/build_tool");

View file

@ -34,7 +34,7 @@ namespace GodotTools
private bool CreateProjectSolution()
{
using (var pr = new EditorProgress("create_csharp_solution", "Generating solution...".TTR(), 2))
using (var pr = new EditorProgress("create_csharp_solution", "Generating solution...".TTR(), 3))
{
pr.Step("Generating C# project...".TTR());
@ -73,9 +73,23 @@ namespace GodotTools
return false;
}
// Make sure to update the API assemblies if they happen to be missing. Just in
// case the user decided to delete them at some point after they were loaded.
Internal.UpdateApiAssembliesFromPrebuilt();
pr.Step("Updating Godot API assemblies...".TTR());
string debugApiAssembliesError = Internal.UpdateApiAssembliesFromPrebuilt("Debug");
if (!string.IsNullOrEmpty(debugApiAssembliesError))
{
ShowErrorDialog("Failed to update the Godot API assemblies: " + debugApiAssembliesError);
return false;
}
string releaseApiAssembliesError = Internal.UpdateApiAssembliesFromPrebuilt("Release");
if (!string.IsNullOrEmpty(releaseApiAssembliesError))
{
ShowErrorDialog("Failed to update the Godot API assemblies: " + releaseApiAssembliesError);
return false;
}
pr.Step("Done".TTR());

View file

@ -40,8 +40,7 @@ namespace GodotTools.Ides
protected ILogger Logger
{
get => logger ?? (logger = new ConsoleLogger());
set => logger = value;
get => logger ?? (logger = new GodotLogger());
}
private void StartServer()

View file

@ -10,8 +10,8 @@ namespace GodotTools.Internals
public const string CSharpLanguageType = "CSharpScript";
public const string CSharpLanguageExtension = "cs";
public static string UpdateApiAssembliesFromPrebuilt() =>
internal_UpdateApiAssembliesFromPrebuilt();
public static string UpdateApiAssembliesFromPrebuilt(string config) =>
internal_UpdateApiAssembliesFromPrebuilt(config);
public static string FullTemplatesDir =>
internal_FullTemplatesDir();
@ -55,7 +55,7 @@ namespace GodotTools.Internals
// Internal Calls
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_UpdateApiAssembliesFromPrebuilt();
private static extern string internal_UpdateApiAssembliesFromPrebuilt(string config);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_FullTemplatesDir();

View file

@ -75,7 +75,7 @@ bool generate_api_solution(const String &p_solution_dir, const String &p_core_pr
p_editor_proj_dir, p_editor_compile_items,
GDMono::get_singleton()->get_tools_project_editor_assembly());
} else {
MonoDomain *temp_domain = GDMonoUtils::create_domain("GodotEngine.ApiSolutionGenerationDomain");
MonoDomain *temp_domain = GDMonoUtils::create_domain("GodotEngine.Domain.ApiSolutionGeneration");
CRASH_COND(temp_domain == NULL);
_GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(temp_domain);

View file

@ -230,31 +230,9 @@ uint32_t godot_icall_GodotSharpExport_GetExportedAssemblyDependencies(MonoString
return GodotSharpExport::get_exported_assembly_dependencies(project_dll_name, project_dll_src_path, build_config, custom_lib_dir, dependencies);
}
float godot_icall_Globals_EditorScale() {
return EDSCALE;
}
MonoObject *godot_icall_Globals_GlobalDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) {
String setting = GDMonoMarshal::mono_string_to_godot(p_setting);
Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value);
Variant result = _GLOBAL_DEF(setting, default_value, (bool)p_restart_if_changed);
return GDMonoMarshal::variant_to_mono_object(result);
}
MonoObject *godot_icall_Globals_EditorDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) {
String setting = GDMonoMarshal::mono_string_to_godot(p_setting);
Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value);
Variant result = _EDITOR_DEF(setting, default_value, (bool)p_restart_if_changed);
return GDMonoMarshal::variant_to_mono_object(result);
}
MonoString *godot_icall_Globals_TTR(MonoString *p_text) {
String text = GDMonoMarshal::mono_string_to_godot(p_text);
return GDMonoMarshal::mono_string_from_godot(TTR(text));
}
MonoString *godot_icall_Internal_UpdateApiAssembliesFromPrebuilt() {
String error_str = GDMono::get_singleton()->update_api_assemblies_from_prebuilt();
MonoString *godot_icall_Internal_UpdateApiAssembliesFromPrebuilt(MonoString *p_config) {
String config = GDMonoMarshal::mono_string_to_godot(p_config);
String error_str = GDMono::get_singleton()->update_api_assemblies_from_prebuilt(config);
return GDMonoMarshal::mono_string_from_godot(error_str);
}
@ -365,6 +343,29 @@ void godot_icall_Internal_ScriptEditorDebugger_ReloadScripts() {
}
}
float godot_icall_Globals_EditorScale() {
return EDSCALE;
}
MonoObject *godot_icall_Globals_GlobalDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) {
String setting = GDMonoMarshal::mono_string_to_godot(p_setting);
Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value);
Variant result = _GLOBAL_DEF(setting, default_value, (bool)p_restart_if_changed);
return GDMonoMarshal::variant_to_mono_object(result);
}
MonoObject *godot_icall_Globals_EditorDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) {
String setting = GDMonoMarshal::mono_string_to_godot(p_setting);
Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value);
Variant result = _EDITOR_DEF(setting, default_value, (bool)p_restart_if_changed);
return GDMonoMarshal::variant_to_mono_object(result);
}
MonoString *godot_icall_Globals_TTR(MonoString *p_text) {
String text = GDMonoMarshal::mono_string_to_godot(p_text);
return GDMonoMarshal::mono_string_from_godot(TTR(text));
}
MonoString *godot_icall_Utils_OS_GetPlatformName() {
String os_name = OS::get_singleton()->get_name();
return GDMonoMarshal::mono_string_from_godot(os_name);

View file

@ -32,9 +32,13 @@
#include <mono/metadata/image.h>
#include "core/os/os.h"
#include "../mono_gd/gd_mono.h"
#include "../mono_gd/gd_mono_assembly.h"
namespace GodotSharpExport {
String get_assemblyref_name(MonoImage *p_image, int index) {
const MonoTableInfo *table_info = mono_image_get_table_info(p_image, MONO_TABLE_ASSEMBLYREF);
@ -45,7 +49,7 @@ String get_assemblyref_name(MonoImage *p_image, int index) {
return String::utf8(mono_metadata_string_heap(p_image, cols[MONO_ASSEMBLYREF_NAME]));
}
Error GodotSharpExport::get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies) {
Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies) {
MonoImage *image = p_assembly->get_image();
for (int i = 0; i < mono_image_get_table_rows(image, MONO_TABLE_ASSEMBLYREF); i++) {
@ -96,8 +100,8 @@ Error GodotSharpExport::get_assembly_dependencies(GDMonoAssembly *p_assembly, co
return OK;
}
Error GodotSharpExport::get_exported_assembly_dependencies(const String &p_project_dll_name, const String &p_project_dll_src_path, const String &p_build_config, const String &p_custom_lib_dir, Dictionary &r_dependencies) {
MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.ProjectExportDomain");
Error get_exported_assembly_dependencies(const String &p_project_dll_name, const String &p_project_dll_src_path, const String &p_build_config, const String &p_custom_bcl_dir, Dictionary &r_dependencies) {
MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.Domain.ProjectExport");
ERR_FAIL_NULL_V(export_domain, FAILED);
_GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(export_domain);
@ -110,7 +114,9 @@ Error GodotSharpExport::get_exported_assembly_dependencies(const String &p_proje
ERR_FAIL_COND_V_MSG(!load_success, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + p_project_dll_name + "'.");
Vector<String> search_dirs;
GDMonoAssembly::fill_search_dirs(search_dirs, p_build_config, p_custom_lib_dir);
GDMonoAssembly::fill_search_dirs(search_dirs, p_build_config, p_custom_bcl_dir);
return get_assembly_dependencies(scripts_assembly, search_dirs, r_dependencies);
}
} // namespace GodotSharpExport

View file

@ -39,10 +39,11 @@
namespace GodotSharpExport {
Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies);
Error get_exported_assembly_dependencies(const String &p_project_dll_name,
const String &p_project_dll_src_path, const String &p_build_config,
const String &p_custom_lib_dir, Dictionary &r_dependencies);
Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies);
} // namespace GodotSharpExport

View file

@ -43,6 +43,8 @@
#include "utils/android_utils.h"
#endif
#include "mono_gd/gd_mono.h"
namespace GodotSharpDirs {
String _get_expected_build_config() {
@ -59,20 +61,6 @@ String _get_expected_build_config() {
#endif
}
String _get_expected_api_build_config() {
#ifdef TOOLS_ENABLED
return "Debug";
#else
#ifdef DEBUG_ENABLED
return "Debug";
#else
return "Release";
#endif
#endif
}
String _get_mono_user_dir() {
#ifdef TOOLS_ENABLED
if (EditorSettings::get_singleton()) {
@ -134,7 +122,7 @@ private:
res_data_dir = "res://.mono";
res_metadata_dir = res_data_dir.plus_file("metadata");
res_assemblies_base_dir = res_data_dir.plus_file("assemblies");
res_assemblies_dir = res_assemblies_base_dir.plus_file(_get_expected_api_build_config());
res_assemblies_dir = res_assemblies_base_dir.plus_file(GDMono::get_expected_api_build_config());
res_config_dir = res_data_dir.plus_file("etc").plus_file("mono");
// TODO use paths from csproj

View file

@ -381,10 +381,10 @@ void GDMono::initialize_load_assemblies() {
}
bool GDMono::_are_api_assemblies_out_of_sync() {
bool out_of_sync = core_api_assembly && (core_api_assembly_out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated);
bool out_of_sync = core_api_assembly.assembly && (core_api_assembly.out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated);
#ifdef TOOLS_ENABLED
if (!out_of_sync)
out_of_sync = editor_api_assembly && editor_api_assembly_out_of_sync;
out_of_sync = editor_api_assembly.assembly && editor_api_assembly.out_of_sync;
#endif
return out_of_sync;
}
@ -523,10 +523,10 @@ bool GDMono::load_assembly_from(const String &p_name, const String &p_path, 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;
ApiAssemblyInfo::Version ApiAssemblyInfo::Version::get_from_loaded_assembly(GDMonoAssembly *p_api_assembly, ApiAssemblyInfo::Type p_api_type) {
ApiAssemblyInfo::Version api_assembly_version;
const char *nativecalls_name = p_api_type == APIAssembly::API_CORE ?
const char *nativecalls_name = p_api_type == ApiAssemblyInfo::API_CORE ?
BINDINGS_CLASS_NATIVECALLS :
BINDINGS_CLASS_NATIVECALLS_EDITOR;
@ -549,8 +549,8 @@ APIAssembly::Version APIAssembly::Version::get_from_loaded_assembly(GDMonoAssemb
return api_assembly_version;
}
String APIAssembly::to_string(APIAssembly::Type p_type) {
return p_type == APIAssembly::API_CORE ? "API_CORE" : "API_EDITOR";
String ApiAssemblyInfo::to_string(ApiAssemblyInfo::Type p_type) {
return p_type == ApiAssemblyInfo::API_CORE ? "API_CORE" : "API_EDITOR";
}
bool GDMono::_load_corlib_assembly() {
@ -567,16 +567,12 @@ bool GDMono::_load_corlib_assembly() {
}
#ifdef TOOLS_ENABLED
bool GDMono::copy_prebuilt_api_assembly(APIAssembly::Type p_api_type, const String &p_config) {
bool &api_assembly_out_of_sync = (p_api_type == APIAssembly::API_CORE) ?
GDMono::get_singleton()->core_api_assembly_out_of_sync :
GDMono::get_singleton()->editor_api_assembly_out_of_sync;
bool GDMono::copy_prebuilt_api_assembly(ApiAssemblyInfo::Type p_api_type, const String &p_config) {
String src_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config);
String dst_dir = GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config);
String assembly_name = p_api_type == APIAssembly::API_CORE ? CORE_API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME;
String assembly_name = p_api_type == ApiAssemblyInfo::API_CORE ? CORE_API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME;
// Create destination directory if needed
if (!DirAccess::exists(dst_dir)) {
@ -590,35 +586,102 @@ bool GDMono::copy_prebuilt_api_assembly(APIAssembly::Type p_api_type, const Stri
}
}
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
String xml_file = assembly_name + ".xml";
if (da->copy(src_dir.plus_file(xml_file), dst_dir.plus_file(xml_file)) != OK)
WARN_PRINTS("Failed to copy '" + xml_file + "'.");
String pdb_file = assembly_name + ".pdb";
if (da->copy(src_dir.plus_file(pdb_file), dst_dir.plus_file(pdb_file)) != OK)
WARN_PRINTS("Failed to copy '" + pdb_file + "'.");
String assembly_file = assembly_name + ".dll";
String assembly_src = src_dir.plus_file(assembly_file);
String assembly_dst = dst_dir.plus_file(assembly_file);
if (!FileAccess::exists(assembly_dst) || api_assembly_out_of_sync) {
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
String xml_file = assembly_name + ".xml";
if (da->copy(src_dir.plus_file(xml_file), dst_dir.plus_file(xml_file)) != OK)
WARN_PRINTS("Failed to copy '" + xml_file + "'.");
String pdb_file = assembly_name + ".pdb";
if (da->copy(src_dir.plus_file(pdb_file), dst_dir.plus_file(pdb_file)) != OK)
WARN_PRINTS("Failed to copy '" + pdb_file + "'.");
Error err = da->copy(assembly_src, assembly_dst);
if (err != OK) {
ERR_PRINTS("Failed to copy '" + assembly_file + "'.");
return false;
}
api_assembly_out_of_sync = false;
if (da->copy(src_dir.plus_file(assembly_file), dst_dir.plus_file(assembly_file)) != OK) {
ERR_PRINTS("Failed to copy '" + assembly_file + "'.");
return false;
}
return true;
}
String GDMono::update_api_assemblies_from_prebuilt() {
static bool try_get_cached_api_hash_for(const String &p_api_assemblies_dir, bool &r_out_of_sync) {
String core_api_assembly_path = p_api_assemblies_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
String editor_api_assembly_path = p_api_assemblies_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
if (!FileAccess::exists(core_api_assembly_path) || !FileAccess::exists(editor_api_assembly_path))
return false;
String cached_api_hash_path = p_api_assemblies_dir.plus_file("api_hash_cache.cfg");
if (!FileAccess::exists(cached_api_hash_path))
return false;
Ref<ConfigFile> cfg;
cfg.instance();
Error cfg_err = cfg->load(cached_api_hash_path);
ERR_FAIL_COND_V(cfg_err != OK, false);
// Checking the modified time is good enough
if (FileAccess::get_modified_time(core_api_assembly_path) != (uint64_t)cfg->get_value("core", "modified_time") ||
FileAccess::get_modified_time(editor_api_assembly_path) != (uint64_t)cfg->get_value("editor", "modified_time")) {
return false;
}
r_out_of_sync = GodotSharpBindings::get_bindings_version() != (uint32_t)cfg->get_value("core", "bindings_version") ||
GodotSharpBindings::get_cs_glue_version() != (uint32_t)cfg->get_value("core", "cs_glue_version") ||
GodotSharpBindings::get_bindings_version() != (uint32_t)cfg->get_value("editor", "bindings_version") ||
GodotSharpBindings::get_cs_glue_version() != (uint32_t)cfg->get_value("editor", "cs_glue_version") ||
GodotSharpBindings::get_core_api_hash() != (uint64_t)cfg->get_value("core", "api_hash") ||
GodotSharpBindings::get_editor_api_hash() != (uint64_t)cfg->get_value("editor", "api_hash");
return true;
}
static void create_cached_api_hash_for(const String &p_api_assemblies_dir) {
String core_api_assembly_path = p_api_assemblies_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
String editor_api_assembly_path = p_api_assemblies_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
String cached_api_hash_path = p_api_assemblies_dir.plus_file("api_hash_cache.cfg");
Ref<ConfigFile> cfg;
cfg.instance();
cfg->set_value("core", "modified_time", FileAccess::get_modified_time(core_api_assembly_path));
cfg->set_value("editor", "modified_time", FileAccess::get_modified_time(editor_api_assembly_path));
cfg->set_value("core", "bindings_version", GodotSharpBindings::get_bindings_version());
cfg->set_value("core", "cs_glue_version", GodotSharpBindings::get_cs_glue_version());
cfg->set_value("editor", "bindings_version", GodotSharpBindings::get_bindings_version());
cfg->set_value("editor", "cs_glue_version", GodotSharpBindings::get_cs_glue_version());
// This assumes the prebuilt api assemblies we copied to the project are not out of sync
cfg->set_value("core", "api_hash", GodotSharpBindings::get_core_api_hash());
cfg->set_value("editor", "api_hash", GodotSharpBindings::get_editor_api_hash());
Error err = cfg->save(cached_api_hash_path);
ERR_FAIL_COND(err != OK);
}
bool GDMono::_temp_domain_load_are_assemblies_out_of_sync(const String &p_config) {
MonoDomain *temp_domain = GDMonoUtils::create_domain("GodotEngine.Domain.CheckApiAssemblies");
ERR_FAIL_NULL_V(temp_domain, "Failed to create temporary domain to check API assemblies");
_GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(temp_domain);
_GDMONO_SCOPE_DOMAIN_(temp_domain);
GDMono::LoadedApiAssembly temp_core_api_assembly;
GDMono::LoadedApiAssembly temp_editor_api_assembly;
if (!_try_load_api_assemblies(temp_core_api_assembly, temp_editor_api_assembly,
p_config, /* refonly: */ true, /* loaded_callback: */ NULL)) {
return temp_core_api_assembly.out_of_sync || temp_editor_api_assembly.out_of_sync;
}
return true; // Failed to load, assume they're outdated assemblies
}
String GDMono::update_api_assemblies_from_prebuilt(const String &p_config, const bool *p_core_api_out_of_sync, const bool *p_editor_api_out_of_sync) {
#define FAIL_REASON(m_out_of_sync, m_prebuilt_exists) \
( \
@ -629,46 +692,55 @@ String GDMono::update_api_assemblies_from_prebuilt() {
String("and the prebuilt assemblies are missing.") : \
String("and we failed to copy the prebuilt assemblies.")))
bool api_assembly_out_of_sync = core_api_assembly_out_of_sync || editor_api_assembly_out_of_sync;
String dst_assemblies_dir = GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config);
String core_assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(CORE_API_ASSEMBLY_NAME ".dll");
String editor_assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
String core_assembly_path = dst_assemblies_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
String editor_assembly_path = dst_assemblies_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
if (!api_assembly_out_of_sync && FileAccess::exists(core_assembly_path) && FileAccess::exists(editor_assembly_path))
return String(); // No update needed
bool api_assemblies_out_of_sync = false;
const int CONFIGS_LEN = 2;
String configs[CONFIGS_LEN] = { String("Debug"), String("Release") };
for (int i = 0; i < CONFIGS_LEN; i++) {
String config = configs[i];
print_verbose("Updating '" + config + "' API assemblies");
String prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(config);
String prebuilt_core_dll_path = prebuilt_api_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
String prebuilt_editor_dll_path = prebuilt_api_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
if (!FileAccess::exists(prebuilt_core_dll_path) || !FileAccess::exists(prebuilt_editor_dll_path)) {
return FAIL_REASON(api_assembly_out_of_sync, /* prebuilt_exists: */ false);
}
// Copy the prebuilt Api
if (!copy_prebuilt_api_assembly(APIAssembly::API_CORE, config) ||
!copy_prebuilt_api_assembly(APIAssembly::API_EDITOR, config)) {
return FAIL_REASON(api_assembly_out_of_sync, /* prebuilt_exists: */ true);
if (p_core_api_out_of_sync && p_editor_api_out_of_sync) {
api_assemblies_out_of_sync = p_core_api_out_of_sync || p_editor_api_out_of_sync;
} else if (FileAccess::exists(core_assembly_path) && FileAccess::exists(editor_assembly_path)) {
// Determine if they're out of sync
if (!try_get_cached_api_hash_for(dst_assemblies_dir, api_assemblies_out_of_sync)) {
api_assemblies_out_of_sync = _temp_domain_load_are_assemblies_out_of_sync(p_config);
}
}
// Note: Even if only one of the assemblies if missing or out of sync, we update both
if (!api_assemblies_out_of_sync && FileAccess::exists(core_assembly_path) && FileAccess::exists(editor_assembly_path))
return String(); // No update needed
print_verbose("Updating '" + p_config + "' API assemblies");
String prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config);
String prebuilt_core_dll_path = prebuilt_api_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
String prebuilt_editor_dll_path = prebuilt_api_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
if (!FileAccess::exists(prebuilt_core_dll_path) || !FileAccess::exists(prebuilt_editor_dll_path)) {
return FAIL_REASON(api_assemblies_out_of_sync, /* prebuilt_exists: */ false);
}
// Copy the prebuilt Api
if (!copy_prebuilt_api_assembly(ApiAssemblyInfo::API_CORE, p_config) ||
!copy_prebuilt_api_assembly(ApiAssemblyInfo::API_EDITOR, p_config)) {
return FAIL_REASON(api_assemblies_out_of_sync, /* prebuilt_exists: */ true);
}
// Cache the api hash of the assemblies we just copied
create_cached_api_hash_for(dst_assemblies_dir);
return String(); // Updated successfully
#undef FAIL_REASON
}
#endif
bool GDMono::_load_core_api_assembly() {
bool GDMono::_load_core_api_assembly(LoadedApiAssembly &r_loaded_api_assembly, const String &p_config, bool p_refonly) {
if (core_api_assembly)
if (r_loaded_api_assembly.assembly)
return true;
#ifdef TOOLS_ENABLED
@ -676,101 +748,115 @@ bool GDMono::_load_core_api_assembly() {
// If running the project manager, load it from the prebuilt API directory
String assembly_dir = !Main::is_project_manager() ?
GodotSharpDirs::get_res_assemblies_dir() :
GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug");
GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config) :
GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config);
String assembly_path = assembly_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
bool success = FileAccess::exists(assembly_path) &&
load_assembly_from(CORE_API_ASSEMBLY_NAME, assembly_path, &core_api_assembly);
load_assembly_from(CORE_API_ASSEMBLY_NAME, assembly_path, &r_loaded_api_assembly.assembly, p_refonly);
#else
bool success = load_assembly(CORE_API_ASSEMBLY_NAME, &core_api_assembly);
bool success = load_assembly(CORE_API_ASSEMBLY_NAME, &core_api_assembly, p_refonly);
#endif
if (success) {
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;
if (!core_api_assembly_out_of_sync) {
GDMonoUtils::update_godot_api_cache();
_install_trace_listener();
}
ApiAssemblyInfo::Version api_assembly_ver = ApiAssemblyInfo::Version::get_from_loaded_assembly(r_loaded_api_assembly.assembly, ApiAssemblyInfo::API_CORE);
r_loaded_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;
} else {
core_api_assembly_out_of_sync = false;
r_loaded_api_assembly.out_of_sync = false;
}
return success;
}
#ifdef TOOLS_ENABLED
bool GDMono::_load_editor_api_assembly() {
bool GDMono::_load_editor_api_assembly(LoadedApiAssembly &r_loaded_api_assembly, const String &p_config, bool p_refonly) {
if (editor_api_assembly)
if (r_loaded_api_assembly.assembly)
return true;
// For the editor and the editor player we want to load it from a specific path to make sure we can keep it up to date
// If running the project manager, load it from the prebuilt API directory
String assembly_dir = !Main::is_project_manager() ?
GodotSharpDirs::get_res_assemblies_dir() :
GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug");
GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config) :
GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config);
String assembly_path = assembly_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
bool success = FileAccess::exists(assembly_path) &&
load_assembly_from(EDITOR_API_ASSEMBLY_NAME, assembly_path, &editor_api_assembly);
load_assembly_from(EDITOR_API_ASSEMBLY_NAME, assembly_path, &r_loaded_api_assembly.assembly, p_refonly);
if (success) {
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;
ApiAssemblyInfo::Version api_assembly_ver = ApiAssemblyInfo::Version::get_from_loaded_assembly(r_loaded_api_assembly.assembly, ApiAssemblyInfo::API_EDITOR);
r_loaded_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;
} else {
editor_api_assembly_out_of_sync = false;
r_loaded_api_assembly.out_of_sync = false;
}
return success;
}
#endif
bool GDMono::_try_load_api_assemblies() {
if (!_load_core_api_assembly()) {
bool GDMono::_try_load_api_assemblies(LoadedApiAssembly &r_core_api_assembly, LoadedApiAssembly &r_editor_api_assembly,
const String &p_config, bool p_refonly, CoreApiAssemblyLoadedCallback p_callback) {
if (!_load_core_api_assembly(r_core_api_assembly, p_config, p_refonly)) {
if (OS::get_singleton()->is_stdout_verbose())
print_error("Mono: Failed to load Core API assembly");
return false;
}
#ifdef TOOLS_ENABLED
if (!_load_editor_api_assembly()) {
if (!_load_editor_api_assembly(r_editor_api_assembly, p_config, p_refonly)) {
if (OS::get_singleton()->is_stdout_verbose())
print_error("Mono: Failed to load Editor API assembly");
return false;
}
if (editor_api_assembly_out_of_sync)
if (r_editor_api_assembly.out_of_sync)
return false;
#endif
// Check if the core API assembly is out of sync only after trying to load the
// editor API assembly. Otherwise, if both assemblies are out of sync, we would
// only update the former as we won't know the latter also needs to be updated.
if (core_api_assembly_out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated)
if (r_core_api_assembly.out_of_sync)
return false;
if (p_callback)
return p_callback();
return true;
}
bool GDMono::_on_core_api_assembly_loaded() {
GDMonoUtils::update_godot_api_cache();
if (!GDMonoUtils::mono_cache.godot_api_cache_updated)
return false;
get_singleton()->_install_trace_listener();
return true;
}
bool GDMono::_try_load_api_assemblies_preset() {
return _try_load_api_assemblies(core_api_assembly, editor_api_assembly,
get_expected_api_build_config(), /* refonly: */ false, _on_core_api_assembly_loaded);
}
void GDMono::_load_api_assemblies() {
bool api_assemblies_loaded = _try_load_api_assemblies();
bool api_assemblies_loaded = _try_load_api_assemblies_preset();
if (!api_assemblies_loaded) {
#ifdef TOOLS_ENABLED
// The API assemblies are out of sync. Fine, try one more time, but this time
// update them from the prebuilt assemblies directory before trying to load them.
// The API assemblies are out of sync or some other error happened. Fine, try one more time, but
// this time update them from the prebuilt assemblies directory before trying to load them again.
// Shouldn't happen. The project manager loads the prebuilt API assemblies
CRASH_COND_MSG(Main::is_project_manager(), "Failed to load one of the prebuilt API assemblies.");
@ -780,7 +866,7 @@ void GDMono::_load_api_assemblies() {
CRASH_COND_MSG(domain_unload_err != OK, "Mono: Failed to unload scripts domain.");
// 2. Update the API assemblies
String update_error = update_api_assemblies_from_prebuilt();
String update_error = update_api_assemblies_from_prebuilt("Debug", &core_api_assembly.out_of_sync, &editor_api_assembly.out_of_sync);
CRASH_COND_MSG(!update_error.empty(), update_error);
// 3. Load the scripts domain again
@ -788,7 +874,7 @@ void GDMono::_load_api_assemblies() {
CRASH_COND_MSG(domain_load_err != OK, "Mono: Failed to load scripts domain.");
// 4. Try loading the updated assemblies
api_assemblies_loaded = _try_load_api_assemblies();
api_assemblies_loaded = _try_load_api_assemblies_preset();
#endif
}
@ -796,14 +882,14 @@ void GDMono::_load_api_assemblies() {
// welp... too bad
if (_are_api_assemblies_out_of_sync()) {
if (core_api_assembly_out_of_sync) {
if (core_api_assembly.out_of_sync) {
ERR_PRINT("The assembly '" CORE_API_ASSEMBLY_NAME "' is out of sync.");
} else if (!GDMonoUtils::mono_cache.godot_api_cache_updated) {
ERR_PRINT("The loaded assembly '" CORE_API_ASSEMBLY_NAME "' is in sync, but the cache update failed.");
}
#ifdef TOOLS_ENABLED
if (editor_api_assembly_out_of_sync) {
if (editor_api_assembly.out_of_sync) {
ERR_PRINT("The assembly '" EDITOR_API_ASSEMBLY_NAME "' is out of sync.");
}
#endif
@ -852,15 +938,14 @@ void GDMono::_install_trace_listener() {
#ifdef DEBUG_ENABLED
// Install the trace listener now before the project assembly is loaded
typedef void (*DebuggingUtils_InstallTraceListener)(MonoObject **);
GDMonoClass *debug_utils = get_core_api_assembly()->get_class(BINDINGS_NAMESPACE, "DebuggingUtils");
GDMonoMethod *install_func = debug_utils->get_method("InstallTraceListener");
MonoException *exc = NULL;
GDMonoClass *debug_utils = core_api_assembly->get_class(BINDINGS_NAMESPACE, "DebuggingUtils");
DebuggingUtils_InstallTraceListener install_func =
(DebuggingUtils_InstallTraceListener)debug_utils->get_method_thunk("InstallTraceListener");
install_func((MonoObject **)&exc);
install_func->invoke_raw(NULL, NULL, &exc);
if (exc) {
ERR_PRINT("Failed to install 'System.Diagnostics.Trace' listener.");
GDMonoUtils::debug_print_unhandled_exception(exc);
ERR_PRINT("Failed to install 'System.Diagnostics.Trace' listener.");
}
#endif
}
@ -871,7 +956,7 @@ Error GDMono::_load_scripts_domain() {
print_verbose("Mono: Loading scripts domain...");
scripts_domain = GDMonoUtils::create_domain("GodotEngine.ScriptsDomain");
scripts_domain = GDMonoUtils::create_domain("GodotEngine.Domain.Scripts");
ERR_FAIL_NULL_V_MSG(scripts_domain, ERR_CANT_CREATE, "Mono: Could not create scripts app domain.");
@ -903,10 +988,8 @@ Error GDMono::_unload_scripts_domain() {
_domain_assemblies_cleanup(mono_domain_get_id(scripts_domain));
core_api_assembly = NULL;
project_assembly = NULL;
#ifdef TOOLS_ENABLED
editor_api_assembly = NULL;
tools_assembly = NULL;
tools_project_editor_assembly = NULL;
#endif
@ -1076,16 +1159,9 @@ GDMono::GDMono() {
root_domain = NULL;
scripts_domain = NULL;
core_api_assembly_out_of_sync = false;
#ifdef TOOLS_ENABLED
editor_api_assembly_out_of_sync = false;
#endif
corlib_assembly = NULL;
core_api_assembly = NULL;
project_assembly = NULL;
#ifdef TOOLS_ENABLED
editor_api_assembly = NULL;
tools_assembly = NULL;
tools_project_editor_assembly = NULL;
#endif

View file

@ -41,7 +41,7 @@
#include "../utils/mono_reg_utils.h"
#endif
namespace APIAssembly {
namespace ApiAssemblyInfo {
enum Type {
API_CORE,
API_EDITOR
@ -76,7 +76,7 @@ struct Version {
};
String to_string(Type p_type);
} // namespace APIAssembly
} // namespace ApiAssemblyInfo
class GDMono {
@ -86,44 +86,58 @@ public:
POLICY_LOG_ERROR
};
struct LoadedApiAssembly {
GDMonoAssembly *assembly;
bool out_of_sync;
LoadedApiAssembly() :
assembly(NULL),
out_of_sync(false) {
}
};
private:
bool runtime_initialized;
bool finalizing_scripts_domain;
UnhandledExceptionPolicy unhandled_exception_policy;
MonoDomain *root_domain;
MonoDomain *scripts_domain;
bool core_api_assembly_out_of_sync;
#ifdef TOOLS_ENABLED
bool editor_api_assembly_out_of_sync;
#endif
HashMap<uint32_t, HashMap<String, GDMonoAssembly *> > assemblies;
GDMonoAssembly *corlib_assembly;
GDMonoAssembly *core_api_assembly;
GDMonoAssembly *project_assembly;
#ifdef TOOLS_ENABLED
GDMonoAssembly *editor_api_assembly;
GDMonoAssembly *tools_assembly;
GDMonoAssembly *tools_project_editor_assembly;
#endif
HashMap<uint32_t, HashMap<String, GDMonoAssembly *> > assemblies;
LoadedApiAssembly core_api_assembly;
LoadedApiAssembly editor_api_assembly;
UnhandledExceptionPolicy unhandled_exception_policy;
void _domain_assemblies_cleanup(uint32_t p_domain_id);
typedef bool (*CoreApiAssemblyLoadedCallback)();
bool _are_api_assemblies_out_of_sync();
bool _temp_domain_load_are_assemblies_out_of_sync(const String &p_config);
bool _load_core_api_assembly(LoadedApiAssembly &r_loaded_api_assembly, const String &p_config, bool p_refonly);
#ifdef TOOLS_ENABLED
bool _load_editor_api_assembly(LoadedApiAssembly &r_loaded_api_assembly, const String &p_config, bool p_refonly);
#endif
static bool _on_core_api_assembly_loaded();
bool _load_corlib_assembly();
bool _load_core_api_assembly();
#ifdef TOOLS_ENABLED
bool _load_editor_api_assembly();
bool _load_tools_assemblies();
#endif
bool _load_project_assembly();
bool _try_load_api_assemblies();
bool _try_load_api_assemblies(LoadedApiAssembly &r_core_api_assembly, LoadedApiAssembly &r_editor_api_assembly,
const String &p_config, bool p_refonly, CoreApiAssemblyLoadedCallback p_callback);
bool _try_load_api_assemblies_preset();
void _load_api_assemblies();
void _install_trace_listener();
@ -133,6 +147,8 @@ private:
Error _load_scripts_domain();
Error _unload_scripts_domain();
void _domain_assemblies_cleanup(uint32_t p_domain_id);
uint64_t api_core_hash;
#ifdef TOOLS_ENABLED
uint64_t api_editor_hash;
@ -166,9 +182,21 @@ public:
#endif // TOOLS_ENABLED
#endif // DEBUG_METHODS_ENABLED
_FORCE_INLINE_ static String get_expected_api_build_config() {
#ifdef TOOLS_ENABLED
bool copy_prebuilt_api_assembly(APIAssembly::Type p_api_type, const String &p_config);
String update_api_assemblies_from_prebuilt();
return "Debug";
#else
#ifdef DEBUG_ENABLED
return "Debug";
#else
return "Release";
#endif
#endif
}
#ifdef TOOLS_ENABLED
bool copy_prebuilt_api_assembly(ApiAssemblyInfo::Type p_api_type, const String &p_config);
String update_api_assemblies_from_prebuilt(const String &p_config, const bool *p_core_api_out_of_sync = NULL, const bool *p_editor_api_out_of_sync = NULL);
#endif
static GDMono *get_singleton() { return singleton; }
@ -188,10 +216,10 @@ public:
_FORCE_INLINE_ MonoDomain *get_scripts_domain() { return scripts_domain; }
_FORCE_INLINE_ GDMonoAssembly *get_corlib_assembly() const { return corlib_assembly; }
_FORCE_INLINE_ GDMonoAssembly *get_core_api_assembly() const { return core_api_assembly; }
_FORCE_INLINE_ GDMonoAssembly *get_core_api_assembly() const { return core_api_assembly.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; }
_FORCE_INLINE_ GDMonoAssembly *get_editor_api_assembly() const { return editor_api_assembly.assembly; }
_FORCE_INLINE_ GDMonoAssembly *get_tools_assembly() const { return tools_assembly; }
_FORCE_INLINE_ GDMonoAssembly *get_tools_project_editor_assembly() const { return tools_project_editor_assembly; }
#endif

View file

@ -550,6 +550,8 @@ MonoObject *create_managed_from(const Dictionary &p_from, GDMonoClass *p_class)
}
MonoDomain *create_domain(const String &p_friendly_name) {
print_verbose("Mono: Creating domain '" + p_friendly_name + "'...");
MonoDomain *domain = mono_domain_create_appdomain((char *)p_friendly_name.utf8().get_data(), NULL);
if (domain) {