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.
This commit is contained in:
Ignacio Etcheverry 2020-04-22 16:44:03 +02:00
parent bb9d89d78c
commit 71fc87e101
5 changed files with 55 additions and 45 deletions

View file

@ -168,13 +168,13 @@ namespace GodotTools.Export
// Add dependency assemblies
var dependencies = new Godot.Collections.Dictionary<string, string>();
var assemblies = new Godot.Collections.Dictionary<string, string>();
string projectDllName = GodotSharpEditor.ProjectAssemblyName;
string projectDllSrcDir = Path.Combine(GodotSharpDirs.ResTempAssembliesBaseDir, buildConfig);
string projectDllSrcPath = Path.Combine(projectDllSrcDir, $"{projectDllName}.dll");
dependencies[projectDllName] = projectDllSrcPath;
assemblies[projectDllName] = projectDllSrcPath;
if (platform == OS.Platforms.Android)
{
@ -184,15 +184,15 @@ namespace GodotTools.Export
if (!File.Exists(monoAndroidAssemblyPath))
throw new FileNotFoundException("Assembly not found: 'Mono.Android'", monoAndroidAssemblyPath);
dependencies["Mono.Android"] = monoAndroidAssemblyPath;
assemblies["Mono.Android"] = monoAndroidAssemblyPath;
}
string bclDir = DeterminePlatformBclDir(platform);
var initialDependencies = dependencies.Duplicate();
internal_GetExportedAssemblyDependencies(initialDependencies, buildConfig, bclDir, dependencies);
var initialAssemblies = assemblies.Duplicate();
internal_GetExportedAssemblyDependencies(initialAssemblies, buildConfig, bclDir, assemblies);
AddI18NAssemblies(dependencies, bclDir);
AddI18NAssemblies(assemblies, bclDir);
string outputDataDir = null;
@ -211,20 +211,32 @@ namespace GodotTools.Export
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 dependDstPath = Path.Combine(resAssembliesDir, dependSrcPath.GetFile());
AddFile(dependSrcPath, dependDstPath);
}
else
{
string dependDstPath = Path.Combine(outputDataDir, "Assemblies", dependSrcPath.GetFile());
File.Copy(dependSrcPath, dependDstPath);
}
string assemblySrcPath = assembly.Value;
string assemblyPathWithoutExtension = Path.ChangeExtension(assemblySrcPath, null);
string pdbSrcPath = assemblyPathWithoutExtension + ".pdb";
AddToAssembliesDir(assemblySrcPath);
if (File.Exists(pdbSrcPath))
AddToAssembliesDir(pdbSrcPath);
}
// AOT compilation
@ -254,7 +266,7 @@ namespace GodotTools.Export
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))
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)]
private static extern void internal_GetExportedAssemblyDependencies(Godot.Collections.Dictionary<string, string> initialDependencies,
string buildConfig, string customBclDir, Godot.Collections.Dictionary<string, string> dependencies);
private static extern void internal_GetExportedAssemblyDependencies(Godot.Collections.Dictionary<string, string> initialAssemblies,
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;
}
uint32_t godot_icall_ExportPlugin_GetExportedAssemblyDependencies(MonoObject *p_initial_dependencies,
MonoString *p_build_config, MonoString *p_custom_bcl_dir, MonoObject *r_dependencies) {
Dictionary initial_dependencies = GDMonoMarshal::mono_object_to_variant(p_initial_dependencies);
uint32_t godot_icall_ExportPlugin_GetExportedAssemblyDependencies(MonoObject *p_initial_assemblies,
MonoString *p_build_config, MonoString *p_custom_bcl_dir, MonoObject *r_assembly_dependencies) {
Dictionary initial_dependencies = GDMonoMarshal::mono_object_to_variant(p_initial_assemblies);
String build_config = GDMonoMarshal::mono_string_to_godot(p_build_config);
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) {

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]));
}
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();
for (int i = 0; i < mono_image_get_table_rows(image, MONO_TABLE_ASSEMBLYREF); i++) {
String ref_name = get_assemblyref_name(image, i);
if (r_dependencies.has(ref_name))
if (r_assembly_dependencies.has(ref_name))
continue;
GDMonoAssembly *ref_assembly = nullptr;
@ -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 + "'.");
// 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 + "'.");
}
return OK;
}
Error get_exported_assembly_dependencies(const Dictionary &p_initial_dependencies,
const String &p_build_config, const String &p_custom_bcl_dir, Dictionary &r_dependencies) {
Error get_exported_assembly_dependencies(const Dictionary &p_initial_assemblies,
const String &p_build_config, const String &p_custom_bcl_dir, Dictionary &r_assembly_dependencies) {
MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.Domain.ProjectExport");
ERR_FAIL_NULL_V(export_domain, FAILED);
_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;
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_path = p_initial_dependencies[*key];
String assembly_path = p_initial_assemblies[*key];
GDMonoAssembly *assembly = nullptr;
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 + "'.");
Error err = get_assembly_dependencies(assembly, search_dirs, r_dependencies);
Error err = get_assembly_dependencies(assembly, search_dirs, r_assembly_dependencies);
if (err != OK)
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_exported_assembly_dependencies(const Dictionary &p_initial_dependencies,
const String &p_build_config, const String &p_custom_lib_dir, Dictionary &r_dependencies);
Error get_exported_assembly_dependencies(const Dictionary &p_initial_assemblies,
const String &p_build_config, const String &p_custom_lib_dir, Dictionary &r_assembly_dependencies);
} // namespace GodotSharpExport

View file

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