Mono/C#: Allow debugging exported games

- Include PDB files in exported games.
- Release export templates also allow debugging now.

Right now the only way to enable debugging in exported games is with the  environment variables, which may be cumbersome or not even possible on some platforms.

(cherry picked from commit 71fc87e101)
This commit is contained in:
Ignacio Etcheverry 2020-04-22 16:44:03 +02:00 committed by Rémi Verschelde
parent 08f41f474b
commit 317d8decad
5 changed files with 55 additions and 45 deletions

View File

@ -168,13 +168,13 @@ namespace GodotTools.Export
// Add dependency assemblies // Add dependency assemblies
var dependencies = new Godot.Collections.Dictionary<string, string>(); var assemblies = new Godot.Collections.Dictionary<string, string>();
string projectDllName = GodotSharpEditor.ProjectAssemblyName; string projectDllName = GodotSharpEditor.ProjectAssemblyName;
string projectDllSrcDir = Path.Combine(GodotSharpDirs.ResTempAssembliesBaseDir, buildConfig); string projectDllSrcDir = Path.Combine(GodotSharpDirs.ResTempAssembliesBaseDir, buildConfig);
string projectDllSrcPath = Path.Combine(projectDllSrcDir, $"{projectDllName}.dll"); string projectDllSrcPath = Path.Combine(projectDllSrcDir, $"{projectDllName}.dll");
dependencies[projectDllName] = projectDllSrcPath; assemblies[projectDllName] = projectDllSrcPath;
if (platform == OS.Platforms.Android) if (platform == OS.Platforms.Android)
{ {
@ -184,15 +184,15 @@ namespace GodotTools.Export
if (!File.Exists(monoAndroidAssemblyPath)) if (!File.Exists(monoAndroidAssemblyPath))
throw new FileNotFoundException("Assembly not found: 'Mono.Android'", monoAndroidAssemblyPath); throw new FileNotFoundException("Assembly not found: 'Mono.Android'", monoAndroidAssemblyPath);
dependencies["Mono.Android"] = monoAndroidAssemblyPath; assemblies["Mono.Android"] = monoAndroidAssemblyPath;
} }
string bclDir = DeterminePlatformBclDir(platform); string bclDir = DeterminePlatformBclDir(platform);
var initialDependencies = dependencies.Duplicate(); var initialAssemblies = assemblies.Duplicate();
internal_GetExportedAssemblyDependencies(initialDependencies, buildConfig, bclDir, dependencies); internal_GetExportedAssemblyDependencies(initialAssemblies, buildConfig, bclDir, assemblies);
AddI18NAssemblies(dependencies, bclDir); AddI18NAssemblies(assemblies, bclDir);
string outputDataDir = null; string outputDataDir = null;
@ -211,20 +211,32 @@ namespace GodotTools.Export
Directory.CreateDirectory(outputDataGameAssembliesDir); Directory.CreateDirectory(outputDataGameAssembliesDir);
} }
foreach (var dependency in dependencies) foreach (var assembly in assemblies)
{ {
string dependSrcPath = dependency.Value; void AddToAssembliesDir(string fileSrcPath)
{
if (assembliesInsidePck)
{
string fileDstPath = Path.Combine(resAssembliesDir, fileSrcPath.GetFile());
AddFile(fileSrcPath, fileDstPath);
}
else
{
Debug.Assert(outputDataDir != null);
string fileDstPath = Path.Combine(outputDataDir, "Assemblies", fileSrcPath.GetFile());
File.Copy(fileSrcPath, fileDstPath);
}
}
if (assembliesInsidePck) string assemblySrcPath = assembly.Value;
{
string dependDstPath = Path.Combine(resAssembliesDir, dependSrcPath.GetFile()); string assemblyPathWithoutExtension = Path.ChangeExtension(assemblySrcPath, null);
AddFile(dependSrcPath, dependDstPath); string pdbSrcPath = assemblyPathWithoutExtension + ".pdb";
}
else AddToAssembliesDir(assemblySrcPath);
{
string dependDstPath = Path.Combine(outputDataDir, "Assemblies", dependSrcPath.GetFile()); if (File.Exists(pdbSrcPath))
File.Copy(dependSrcPath, dependDstPath); AddToAssembliesDir(pdbSrcPath);
}
} }
// AOT compilation // AOT compilation
@ -254,7 +266,7 @@ namespace GodotTools.Export
ToolchainPath = aotToolchainPath ToolchainPath = aotToolchainPath
}; };
AotBuilder.CompileAssemblies(this, aotOpts, features, platform, isDebug, bclDir, outputDir, outputDataDir, dependencies); AotBuilder.CompileAssemblies(this, aotOpts, features, platform, isDebug, bclDir, outputDir, outputDataDir, assemblies);
} }
} }
@ -366,7 +378,7 @@ namespace GodotTools.Export
if (PlatformRequiresCustomBcl(platform)) if (PlatformRequiresCustomBcl(platform))
throw new FileNotFoundException($"Missing BCL (Base Class Library) for platform: {platform}"); throw new FileNotFoundException($"Missing BCL (Base Class Library) for platform: {platform}");
platformBclDir = typeof(object).Assembly.Location; // Use the one we're running on platformBclDir = typeof(object).Assembly.Location.GetBaseDir(); // Use the one we're running on
} }
} }
@ -425,7 +437,7 @@ namespace GodotTools.Export
} }
[MethodImpl(MethodImplOptions.InternalCall)] [MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_GetExportedAssemblyDependencies(Godot.Collections.Dictionary<string, string> initialDependencies, private static extern void internal_GetExportedAssemblyDependencies(Godot.Collections.Dictionary<string, string> initialAssemblies,
string buildConfig, string customBclDir, Godot.Collections.Dictionary<string, string> dependencies); string buildConfig, string customBclDir, Godot.Collections.Dictionary<string, string> dependencyAssemblies);
} }
} }

View File

@ -231,14 +231,14 @@ int32_t godot_icall_ScriptClassParser_ParseFile(MonoString *p_filepath, MonoObje
return err; return err;
} }
uint32_t godot_icall_ExportPlugin_GetExportedAssemblyDependencies(MonoObject *p_initial_dependencies, uint32_t godot_icall_ExportPlugin_GetExportedAssemblyDependencies(MonoObject *p_initial_assemblies,
MonoString *p_build_config, MonoString *p_custom_bcl_dir, MonoObject *r_dependencies) { MonoString *p_build_config, MonoString *p_custom_bcl_dir, MonoObject *r_assembly_dependencies) {
Dictionary initial_dependencies = GDMonoMarshal::mono_object_to_variant(p_initial_dependencies); Dictionary initial_dependencies = GDMonoMarshal::mono_object_to_variant(p_initial_assemblies);
String build_config = GDMonoMarshal::mono_string_to_godot(p_build_config); String build_config = GDMonoMarshal::mono_string_to_godot(p_build_config);
String custom_bcl_dir = GDMonoMarshal::mono_string_to_godot(p_custom_bcl_dir); String custom_bcl_dir = GDMonoMarshal::mono_string_to_godot(p_custom_bcl_dir);
Dictionary dependencies = GDMonoMarshal::mono_object_to_variant(r_dependencies); Dictionary assembly_dependencies = GDMonoMarshal::mono_object_to_variant(r_assembly_dependencies);
return GodotSharpExport::get_exported_assembly_dependencies(initial_dependencies, build_config, custom_bcl_dir, dependencies); return GodotSharpExport::get_exported_assembly_dependencies(initial_dependencies, build_config, custom_bcl_dir, assembly_dependencies);
} }
MonoString *godot_icall_Internal_UpdateApiAssembliesFromPrebuilt(MonoString *p_config) { MonoString *godot_icall_Internal_UpdateApiAssembliesFromPrebuilt(MonoString *p_config) {

View File

@ -50,13 +50,13 @@ String get_assemblyref_name(MonoImage *p_image, int index) {
return String::utf8(mono_metadata_string_heap(p_image, cols[MONO_ASSEMBLYREF_NAME])); return String::utf8(mono_metadata_string_heap(p_image, cols[MONO_ASSEMBLYREF_NAME]));
} }
Error 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_assembly_dependencies) {
MonoImage *image = p_assembly->get_image(); MonoImage *image = p_assembly->get_image();
for (int i = 0; i < mono_image_get_table_rows(image, MONO_TABLE_ASSEMBLYREF); i++) { for (int i = 0; i < mono_image_get_table_rows(image, MONO_TABLE_ASSEMBLYREF); i++) {
String ref_name = get_assemblyref_name(image, i); String ref_name = get_assemblyref_name(image, i);
if (r_dependencies.has(ref_name)) if (r_assembly_dependencies.has(ref_name))
continue; continue;
GDMonoAssembly *ref_assembly = NULL; GDMonoAssembly *ref_assembly = NULL;
@ -93,17 +93,17 @@ Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String>
ERR_FAIL_COND_V_MSG(!ref_assembly, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + ref_name + "'."); ERR_FAIL_COND_V_MSG(!ref_assembly, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + ref_name + "'.");
// Use the path we got from the search. Don't try to get the path from the loaded assembly as we can't trust it will be from the selected BCL dir. // Use the path we got from the search. Don't try to get the path from the loaded assembly as we can't trust it will be from the selected BCL dir.
r_dependencies[ref_name] = path; r_assembly_dependencies[ref_name] = path;
Error err = get_assembly_dependencies(ref_assembly, p_search_dirs, r_dependencies); Error err = get_assembly_dependencies(ref_assembly, p_search_dirs, r_assembly_dependencies);
ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot load one of the dependencies for the assembly: '" + ref_name + "'."); ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot load one of the dependencies for the assembly: '" + ref_name + "'.");
} }
return OK; return OK;
} }
Error get_exported_assembly_dependencies(const Dictionary &p_initial_dependencies, Error get_exported_assembly_dependencies(const Dictionary &p_initial_assemblies,
const String &p_build_config, const String &p_custom_bcl_dir, Dictionary &r_dependencies) { const String &p_build_config, const String &p_custom_bcl_dir, Dictionary &r_assembly_dependencies) {
MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.Domain.ProjectExport"); MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.Domain.ProjectExport");
ERR_FAIL_NULL_V(export_domain, FAILED); ERR_FAIL_NULL_V(export_domain, FAILED);
_GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(export_domain); _GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(export_domain);
@ -113,16 +113,16 @@ Error get_exported_assembly_dependencies(const Dictionary &p_initial_dependencie
Vector<String> search_dirs; Vector<String> search_dirs;
GDMonoAssembly::fill_search_dirs(search_dirs, p_build_config, p_custom_bcl_dir); GDMonoAssembly::fill_search_dirs(search_dirs, p_build_config, p_custom_bcl_dir);
for (const Variant *key = p_initial_dependencies.next(); key; key = p_initial_dependencies.next(key)) { for (const Variant *key = p_initial_assemblies.next(); key; key = p_initial_assemblies.next(key)) {
String assembly_name = *key; String assembly_name = *key;
String assembly_path = p_initial_dependencies[*key]; String assembly_path = p_initial_assemblies[*key];
GDMonoAssembly *assembly = NULL; GDMonoAssembly *assembly = NULL;
bool load_success = GDMono::get_singleton()->load_assembly_from(assembly_name, assembly_path, &assembly, /* refonly: */ true); bool load_success = GDMono::get_singleton()->load_assembly_from(assembly_name, assembly_path, &assembly, /* refonly: */ true);
ERR_FAIL_COND_V_MSG(!load_success, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + assembly_name + "'."); ERR_FAIL_COND_V_MSG(!load_success, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + assembly_name + "'.");
Error err = get_assembly_dependencies(assembly, search_dirs, r_dependencies); Error err = get_assembly_dependencies(assembly, search_dirs, r_assembly_dependencies);
if (err != OK) if (err != OK)
return err; return err;
} }

View File

@ -41,8 +41,8 @@ namespace GodotSharpExport {
Error 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);
Error get_exported_assembly_dependencies(const Dictionary &p_initial_dependencies, Error get_exported_assembly_dependencies(const Dictionary &p_initial_assemblies,
const String &p_build_config, const String &p_custom_lib_dir, Dictionary &r_dependencies); const String &p_build_config, const String &p_custom_lib_dir, Dictionary &r_assembly_dependencies);
} // namespace GodotSharpExport } // namespace GodotSharpExport

View File

@ -128,12 +128,8 @@ void gd_mono_profiler_init() {
} }
} }
#if defined(DEBUG_ENABLED)
void gd_mono_debug_init() { void gd_mono_debug_init() {
mono_debug_init(MONO_DEBUG_FORMAT_MONO);
CharString da_args = OS::get_singleton()->get_environment("GODOT_MONO_DEBUGGER_AGENT").utf8(); CharString da_args = OS::get_singleton()->get_environment("GODOT_MONO_DEBUGGER_AGENT").utf8();
#ifdef TOOLS_ENABLED #ifdef TOOLS_ENABLED
@ -158,6 +154,10 @@ void gd_mono_debug_init() {
return; // Exported games don't use the project settings to setup the debugger agent return; // Exported games don't use the project settings to setup the debugger agent
#endif #endif
// Debugging enabled
mono_debug_init(MONO_DEBUG_FORMAT_MONO);
// --debugger-agent=help // --debugger-agent=help
const char *options[] = { const char *options[] = {
"--soft-breakpoints", "--soft-breakpoints",
@ -166,7 +166,6 @@ void gd_mono_debug_init() {
mono_jit_parse_options(2, (char **)options); mono_jit_parse_options(2, (char **)options);
} }
#endif // defined(DEBUG_ENABLED)
#endif // !defined(JAVASCRIPT_ENABLED) #endif // !defined(JAVASCRIPT_ENABLED)
#if defined(JAVASCRIPT_ENABLED) #if defined(JAVASCRIPT_ENABLED)
@ -174,6 +173,7 @@ MonoDomain *gd_initialize_mono_runtime() {
const char *vfs_prefix = "managed"; const char *vfs_prefix = "managed";
int enable_debugging = 0; int enable_debugging = 0;
// TODO: Provide a way to enable debugging on WASM release builds.
#ifdef DEBUG_ENABLED #ifdef DEBUG_ENABLED
enable_debugging = 1; enable_debugging = 1;
#endif #endif
@ -184,9 +184,7 @@ MonoDomain *gd_initialize_mono_runtime() {
} }
#else #else
MonoDomain *gd_initialize_mono_runtime() { MonoDomain *gd_initialize_mono_runtime() {
#ifdef DEBUG_ENABLED
gd_mono_debug_init(); gd_mono_debug_init();
#endif
#if defined(IPHONE_ENABLED) || defined(ANDROID_ENABLED) #if defined(IPHONE_ENABLED) || defined(ANDROID_ENABLED)
// I don't know whether this actually matters or not // I don't know whether this actually matters or not