Mono/C#: Initial exporter support for AOT compilation
This commit is contained in:
parent
de7c2ad21b
commit
2b67924a0b
16 changed files with 616 additions and 173 deletions
|
@ -132,8 +132,6 @@ void CSharpLanguage::init() {
|
||||||
|
|
||||||
#ifdef TOOLS_ENABLED
|
#ifdef TOOLS_ENABLED
|
||||||
EditorNode::add_init_callback(&_editor_init_callback);
|
EditorNode::add_init_callback(&_editor_init_callback);
|
||||||
|
|
||||||
GLOBAL_DEF("mono/export/include_scripts_content", false);
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ namespace GodotTools.Build
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (OS.IsWindows())
|
if (OS.IsWindows)
|
||||||
{
|
{
|
||||||
return (BuildManager.BuildTool) EditorSettings.GetSetting("mono/builds/build_tool")
|
return (BuildManager.BuildTool) EditorSettings.GetSetting("mono/builds/build_tool")
|
||||||
== BuildManager.BuildTool.MsBuildMono;
|
== BuildManager.BuildTool.MsBuildMono;
|
||||||
|
|
|
@ -21,7 +21,7 @@ namespace GodotTools.Build
|
||||||
var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
|
var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
|
||||||
var buildTool = (BuildManager.BuildTool) editorSettings.GetSetting("mono/builds/build_tool");
|
var buildTool = (BuildManager.BuildTool) editorSettings.GetSetting("mono/builds/build_tool");
|
||||||
|
|
||||||
if (OS.IsWindows())
|
if (OS.IsWindows)
|
||||||
{
|
{
|
||||||
switch (buildTool)
|
switch (buildTool)
|
||||||
{
|
{
|
||||||
|
@ -59,7 +59,7 @@ namespace GodotTools.Build
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (OS.IsUnix())
|
if (OS.IsUnixLike())
|
||||||
{
|
{
|
||||||
if (buildTool == BuildManager.BuildTool.MsBuildMono)
|
if (buildTool == BuildManager.BuildTool.MsBuildMono)
|
||||||
{
|
{
|
||||||
|
@ -128,7 +128,7 @@ namespace GodotTools.Build
|
||||||
|
|
||||||
private static string FindMsBuildToolsPathOnWindows()
|
private static string FindMsBuildToolsPathOnWindows()
|
||||||
{
|
{
|
||||||
if (!OS.IsWindows())
|
if (!OS.IsWindows)
|
||||||
throw new PlatformNotSupportedException();
|
throw new PlatformNotSupportedException();
|
||||||
|
|
||||||
// Try to find 15.0 with vswhere
|
// Try to find 15.0 with vswhere
|
||||||
|
|
|
@ -246,7 +246,7 @@ namespace GodotTools
|
||||||
{
|
{
|
||||||
// Build tool settings
|
// Build tool settings
|
||||||
|
|
||||||
EditorDef("mono/builds/build_tool", OS.IsWindows() ? BuildTool.MsBuildVs : BuildTool.MsBuildMono);
|
EditorDef("mono/builds/build_tool", OS.IsWindows ? BuildTool.MsBuildVs : BuildTool.MsBuildMono);
|
||||||
|
|
||||||
var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
|
var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
|
||||||
|
|
||||||
|
@ -255,7 +255,7 @@ namespace GodotTools
|
||||||
["type"] = Godot.Variant.Type.Int,
|
["type"] = Godot.Variant.Type.Int,
|
||||||
["name"] = "mono/builds/build_tool",
|
["name"] = "mono/builds/build_tool",
|
||||||
["hint"] = Godot.PropertyHint.Enum,
|
["hint"] = Godot.PropertyHint.Enum,
|
||||||
["hint_string"] = OS.IsWindows() ?
|
["hint_string"] = OS.IsWindows ?
|
||||||
$"{PropNameMsbuildMono},{PropNameMsbuildVs}" :
|
$"{PropNameMsbuildMono},{PropNameMsbuildVs}" :
|
||||||
$"{PropNameMsbuildMono}"
|
$"{PropNameMsbuildMono}"
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using GodotTools.Core;
|
using GodotTools.Core;
|
||||||
using GodotTools.Internals;
|
using GodotTools.Internals;
|
||||||
|
using static GodotTools.Internals.Globals;
|
||||||
using Directory = GodotTools.Utils.Directory;
|
using Directory = GodotTools.Utils.Directory;
|
||||||
using File = GodotTools.Utils.File;
|
using File = GodotTools.Utils.File;
|
||||||
using OS = GodotTools.Utils.OS;
|
using OS = GodotTools.Utils.OS;
|
||||||
|
@ -15,6 +17,25 @@ namespace GodotTools.Export
|
||||||
{
|
{
|
||||||
public class ExportPlugin : EditorExportPlugin
|
public class ExportPlugin : EditorExportPlugin
|
||||||
{
|
{
|
||||||
|
public void RegisterExportSettings()
|
||||||
|
{
|
||||||
|
// TODO: These would be better as export preset options, but that doesn't seem to be supported yet
|
||||||
|
|
||||||
|
GlobalDef("mono/export/include_scripts_content", false);
|
||||||
|
|
||||||
|
GlobalDef("mono/export/aot/enabled", false);
|
||||||
|
GlobalDef("mono/export/aot/full_aot", false);
|
||||||
|
|
||||||
|
// --aot or --aot=opt1,opt2 (use 'mono --aot=help AuxAssembly.dll' to list AOT options)
|
||||||
|
GlobalDef("mono/export/aot/extra_aot_options", new string[] { });
|
||||||
|
// --optimize/-O=opt1,opt2 (use 'mono --list-opt'' to list optimize options)
|
||||||
|
GlobalDef("mono/export/aot/extra_optimizer_options", new string[] { });
|
||||||
|
|
||||||
|
GlobalDef("mono/export/aot/android_toolchain_path", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private string maybeLastExportError;
|
||||||
|
|
||||||
private void AddFile(string srcPath, string dstPath, bool remap = false)
|
private void AddFile(string srcPath, string dstPath, bool remap = false)
|
||||||
{
|
{
|
||||||
AddFile(dstPath, File.ReadAllBytes(srcPath), remap);
|
AddFile(dstPath, File.ReadAllBytes(srcPath), remap);
|
||||||
|
@ -36,8 +57,11 @@ namespace GodotTools.Export
|
||||||
|
|
||||||
if (!includeScriptsContent)
|
if (!includeScriptsContent)
|
||||||
{
|
{
|
||||||
// We don't want to include the source code on exported games
|
// We don't want to include the source code on exported games.
|
||||||
AddFile(path, new byte[] { }, remap: false);
|
|
||||||
|
// Sadly, Godot prints errors when adding an empty file (nothing goes wrong, it's just noise).
|
||||||
|
// Because of this, we add a file which contains a line break.
|
||||||
|
AddFile(path, System.Text.Encoding.UTF8.GetBytes("\n"), remap: false);
|
||||||
Skip();
|
Skip();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,27 +73,28 @@ namespace GodotTools.Export
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_ExportBeginImpl(features, isDebug, path, flags);
|
_ExportBeginImpl(features, isDebug, path, flags);
|
||||||
// TODO: Handle _ExportBeginImpl return value. Do something on error once _ExportBegin supports failing.
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
maybeLastExportError = e.Message;
|
||||||
GD.PushError($"Failed to export project: {e.Message}");
|
GD.PushError($"Failed to export project: {e.Message}");
|
||||||
Console.Error.WriteLine(e);
|
Console.Error.WriteLine(e);
|
||||||
// TODO: Do something on error once _ExportBegin supports failing.
|
// TODO: Do something on error once _ExportBegin supports failing.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool _ExportBeginImpl(string[] features, bool isDebug, string path, int flags)
|
private void _ExportBeginImpl(string[] features, bool isDebug, string path, int flags)
|
||||||
{
|
{
|
||||||
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
|
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
|
||||||
return true;
|
return;
|
||||||
|
|
||||||
string platform = DeterminePlatformFromFeatures(features);
|
string platform = DeterminePlatformFromFeatures(features);
|
||||||
|
|
||||||
if (platform == null)
|
if (platform == null)
|
||||||
throw new NotSupportedException("Target platform not supported");
|
throw new NotSupportedException("Target platform not supported");
|
||||||
|
|
||||||
// TODO Right now there is no way to stop the export process with an error
|
string outputDir = new FileInfo(path).Directory?.FullName ??
|
||||||
|
throw new FileNotFoundException("Base directory not found");
|
||||||
|
|
||||||
string buildConfig = isDebug ? "Debug" : "Release";
|
string buildConfig = isDebug ? "Debug" : "Release";
|
||||||
|
|
||||||
|
@ -82,10 +107,7 @@ namespace GodotTools.Export
|
||||||
var godotDefines = features;
|
var godotDefines = features;
|
||||||
|
|
||||||
if (!BuildManager.BuildProjectBlocking(buildConfig, godotDefines))
|
if (!BuildManager.BuildProjectBlocking(buildConfig, godotDefines))
|
||||||
{
|
throw new Exception("Failed to build project");
|
||||||
GD.PushError("Failed to build project");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add dependency assemblies
|
// Add dependency assemblies
|
||||||
|
|
||||||
|
@ -103,12 +125,9 @@ namespace GodotTools.Export
|
||||||
dependencies[projectDllName] = projectDllSrcPath;
|
dependencies[projectDllName] = projectDllSrcPath;
|
||||||
|
|
||||||
{
|
{
|
||||||
string templatesDir = Internal.FullTemplatesDir;
|
string platformBclDir = DeterminePlatformBclDir(platform);
|
||||||
string platformBclDir = Path.Combine(templatesDir, $"{platform}-bcl", platform);
|
|
||||||
|
|
||||||
string customBclDir = Directory.Exists(platformBclDir) ? platformBclDir : string.Empty;
|
internal_GetExportedAssemblyDependencies(projectDllName, projectDllSrcPath, buildConfig, platformBclDir, dependencies);
|
||||||
|
|
||||||
internal_GetExportedAssemblyDependencies(projectDllName, projectDllSrcPath, buildConfig, customBclDir, dependencies);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
string apiConfig = isDebug ? "Debug" : "Release";
|
string apiConfig = isDebug ? "Debug" : "Release";
|
||||||
|
@ -122,18 +141,44 @@ namespace GodotTools.Export
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mono specific export template extras (data dir)
|
// Mono specific export template extras (data dir)
|
||||||
ExportDataDirectory(features, platform, isDebug, path);
|
string outputDataDir = null;
|
||||||
|
|
||||||
return true;
|
if (PlatformHasTemplateDir(platform))
|
||||||
|
outputDataDir = ExportDataDirectory(features, platform, isDebug, outputDir);
|
||||||
|
|
||||||
|
// AOT
|
||||||
|
|
||||||
|
if ((bool) ProjectSettings.GetSetting("mono/export/aot/enabled"))
|
||||||
|
{
|
||||||
|
AotCompileDependencies(features, platform, isDebug, outputDir, outputDataDir, dependencies);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ExportDataDirectory(ICollection<string> features, string platform, bool debug, string path)
|
public override void _ExportEnd()
|
||||||
{
|
{
|
||||||
if (!PlatformHasTemplateDir(platform))
|
base._ExportEnd();
|
||||||
return;
|
|
||||||
|
|
||||||
|
string aotTempDir = Path.Combine(Path.GetTempPath(), $"godot-aot-{Process.GetCurrentProcess().Id}");
|
||||||
|
|
||||||
|
if (Directory.Exists(aotTempDir))
|
||||||
|
Directory.Delete(aotTempDir, recursive: true);
|
||||||
|
|
||||||
|
// TODO: Just a workaround until the export plugins can be made to abort with errors
|
||||||
|
if (string.IsNullOrEmpty(maybeLastExportError)) // Check empty as well, because it's set to empty after hot-reloading
|
||||||
|
{
|
||||||
|
string lastExportError = maybeLastExportError;
|
||||||
|
maybeLastExportError = null;
|
||||||
|
|
||||||
|
GodotSharpEditor.Instance.ShowErrorDialog(lastExportError, "C# export failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ExportDataDirectory(string[] features, string platform, bool isDebug, string outputDir)
|
||||||
|
{
|
||||||
|
string target = isDebug ? "release_debug" : "release";
|
||||||
|
|
||||||
|
// NOTE: Bits is ok for now as all platforms with a data directory have it, but that may change in the future.
|
||||||
string bits = features.Contains("64") ? "64" : "32";
|
string bits = features.Contains("64") ? "64" : "32";
|
||||||
string target = debug ? "release_debug" : "release";
|
|
||||||
|
|
||||||
string TemplateDirName() => $"data.mono.{platform}.{bits}.{target}";
|
string TemplateDirName() => $"data.mono.{platform}.{bits}.{target}";
|
||||||
|
|
||||||
|
@ -142,8 +187,8 @@ namespace GodotTools.Export
|
||||||
if (!Directory.Exists(templateDirPath))
|
if (!Directory.Exists(templateDirPath))
|
||||||
{
|
{
|
||||||
templateDirPath = null;
|
templateDirPath = null;
|
||||||
|
|
||||||
if (debug)
|
if (isDebug)
|
||||||
{
|
{
|
||||||
target = "debug"; // Support both 'release_debug' and 'debug' for the template data directory name
|
target = "debug"; // Support both 'release_debug' and 'debug' for the template data directory name
|
||||||
templateDirPath = Path.Combine(Internal.FullTemplatesDir, TemplateDirName());
|
templateDirPath = Path.Combine(Internal.FullTemplatesDir, TemplateDirName());
|
||||||
|
@ -156,9 +201,6 @@ namespace GodotTools.Export
|
||||||
if (templateDirPath == null)
|
if (templateDirPath == null)
|
||||||
throw new FileNotFoundException("Data template directory not found");
|
throw new FileNotFoundException("Data template directory not found");
|
||||||
|
|
||||||
string outputDir = new FileInfo(path).Directory?.FullName ??
|
|
||||||
throw new FileNotFoundException("Base directory not found");
|
|
||||||
|
|
||||||
string outputDataDir = Path.Combine(outputDir, DataDirName);
|
string outputDataDir = Path.Combine(outputDir, DataDirName);
|
||||||
|
|
||||||
if (Directory.Exists(outputDataDir))
|
if (Directory.Exists(outputDataDir))
|
||||||
|
@ -175,6 +217,331 @@ namespace GodotTools.Export
|
||||||
{
|
{
|
||||||
File.Copy(file, Path.Combine(outputDataDir, file.Substring(templateDirPath.Length + 1)));
|
File.Copy(file, Path.Combine(outputDataDir, file.Substring(templateDirPath.Length + 1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return outputDataDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AotCompileDependencies(string[] features, string platform, bool isDebug, string outputDir, string outputDataDir, IDictionary<string, string> dependencies)
|
||||||
|
{
|
||||||
|
// TODO: WASM
|
||||||
|
|
||||||
|
string bclDir = DeterminePlatformBclDir(platform) ?? typeof(object).Assembly.Location.GetBaseDir();
|
||||||
|
|
||||||
|
string aotTempDir = Path.Combine(Path.GetTempPath(), $"godot-aot-{Process.GetCurrentProcess().Id}");
|
||||||
|
|
||||||
|
if (!Directory.Exists(aotTempDir))
|
||||||
|
Directory.CreateDirectory(aotTempDir);
|
||||||
|
|
||||||
|
var assemblies = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
foreach (var dependency in dependencies)
|
||||||
|
{
|
||||||
|
string assemblyName = dependency.Key;
|
||||||
|
string assemblyPath = dependency.Value;
|
||||||
|
|
||||||
|
string assemblyPathInBcl = Path.Combine(bclDir, assemblyName + ".dll");
|
||||||
|
|
||||||
|
if (File.Exists(assemblyPathInBcl))
|
||||||
|
{
|
||||||
|
// Don't create teporaries for assemblies from the BCL
|
||||||
|
assemblies.Add(assemblyName, assemblyPathInBcl);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string tempAssemblyPath = Path.Combine(aotTempDir, assemblyName + ".dll");
|
||||||
|
File.Copy(assemblyPath, tempAssemblyPath);
|
||||||
|
assemblies.Add(assemblyName, tempAssemblyPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var assembly in assemblies)
|
||||||
|
{
|
||||||
|
string assemblyName = assembly.Key;
|
||||||
|
string assemblyPath = assembly.Value;
|
||||||
|
|
||||||
|
string sharedLibExtension = platform == OS.Platforms.Windows ? ".dll" :
|
||||||
|
platform == OS.Platforms.OSX ? ".dylib" :
|
||||||
|
platform == OS.Platforms.HTML5 ? ".wasm" :
|
||||||
|
".so";
|
||||||
|
|
||||||
|
string outputFileName = assemblyName + ".dll" + sharedLibExtension;
|
||||||
|
|
||||||
|
if (platform == OS.Platforms.Android)
|
||||||
|
{
|
||||||
|
// Not sure if the 'lib' prefix is an Android thing or just Godot being picky,
|
||||||
|
// but we use '-aot-' as well just in case to avoid conflicts with other libs.
|
||||||
|
outputFileName = "lib-aot-" + outputFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
string outputFilePath = null;
|
||||||
|
string tempOutputFilePath;
|
||||||
|
|
||||||
|
switch (platform)
|
||||||
|
{
|
||||||
|
case OS.Platforms.OSX:
|
||||||
|
tempOutputFilePath = Path.Combine(aotTempDir, outputFileName);
|
||||||
|
break;
|
||||||
|
case OS.Platforms.Android:
|
||||||
|
tempOutputFilePath = Path.Combine(aotTempDir, "%%ANDROID_ABI%%", outputFileName);
|
||||||
|
break;
|
||||||
|
case OS.Platforms.HTML5:
|
||||||
|
tempOutputFilePath = Path.Combine(aotTempDir, outputFileName);
|
||||||
|
outputFilePath = Path.Combine(outputDir, outputFileName);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
tempOutputFilePath = Path.Combine(aotTempDir, outputFileName);
|
||||||
|
outputFilePath = Path.Combine(outputDataDir, "Mono", platform == OS.Platforms.Windows ? "bin" : "lib", outputFileName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = new Dictionary<string, string>();
|
||||||
|
var enabledAndroidAbis = platform == OS.Platforms.Android ? GetEnabledAndroidAbis(features).ToArray() : null;
|
||||||
|
|
||||||
|
if (platform == OS.Platforms.Android)
|
||||||
|
{
|
||||||
|
Debug.Assert(enabledAndroidAbis != null);
|
||||||
|
|
||||||
|
foreach (var abi in enabledAndroidAbis)
|
||||||
|
{
|
||||||
|
data["abi"] = abi;
|
||||||
|
var outputFilePathForThisAbi = tempOutputFilePath.Replace("%%ANDROID_ABI%%", abi);
|
||||||
|
|
||||||
|
AotCompileAssembly(platform, isDebug, data, assemblyPath, outputFilePathForThisAbi);
|
||||||
|
|
||||||
|
AddSharedObject(outputFilePathForThisAbi, tags: new[] {abi});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string bits = features.Contains("64") ? "64" : features.Contains("64") ? "32" : null;
|
||||||
|
|
||||||
|
if (bits != null)
|
||||||
|
data["bits"] = bits;
|
||||||
|
|
||||||
|
AotCompileAssembly(platform, isDebug, data, assemblyPath, tempOutputFilePath);
|
||||||
|
|
||||||
|
if (platform == OS.Platforms.OSX)
|
||||||
|
{
|
||||||
|
AddSharedObject(tempOutputFilePath, tags: null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.Assert(outputFilePath != null);
|
||||||
|
File.Copy(tempOutputFilePath, outputFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AotCompileAssembly(string platform, bool isDebug, Dictionary<string, string> data, string assemblyPath, string outputFilePath)
|
||||||
|
{
|
||||||
|
// Make sure the output directory exists
|
||||||
|
Directory.CreateDirectory(outputFilePath.GetBaseDir());
|
||||||
|
|
||||||
|
string exeExt = OS.IsWindows ? ".exe" : string.Empty;
|
||||||
|
|
||||||
|
string monoCrossDirName = DetermineMonoCrossDirName(platform, data);
|
||||||
|
string monoCrossRoot = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "aot-compilers", monoCrossDirName);
|
||||||
|
string monoCrossBin = Path.Combine(monoCrossRoot, "bin");
|
||||||
|
|
||||||
|
string toolPrefix = DetermineToolPrefix(monoCrossBin);
|
||||||
|
string monoExeName = System.IO.File.Exists(Path.Combine(monoCrossBin, $"{toolPrefix}mono{exeExt}")) ? "mono" : "mono-sgen";
|
||||||
|
|
||||||
|
string compilerCommand = Path.Combine(monoCrossBin, $"{toolPrefix}{monoExeName}{exeExt}");
|
||||||
|
|
||||||
|
bool fullAot = (bool) ProjectSettings.GetSetting("mono/export/aot/full_aot");
|
||||||
|
|
||||||
|
string EscapeOption(string option) => option.Contains(',') ? $"\"{option}\"" : option;
|
||||||
|
string OptionsToString(IEnumerable<string> options) => string.Join(",", options.Select(EscapeOption));
|
||||||
|
|
||||||
|
var aotOptions = new List<string>();
|
||||||
|
var optimizerOptions = new List<string>();
|
||||||
|
|
||||||
|
if (fullAot)
|
||||||
|
aotOptions.Add("full");
|
||||||
|
|
||||||
|
aotOptions.Add(isDebug ? "soft-debug" : "nodebug");
|
||||||
|
|
||||||
|
if (platform == OS.Platforms.Android)
|
||||||
|
{
|
||||||
|
string abi = data["abi"];
|
||||||
|
|
||||||
|
string androidToolchain = (string) ProjectSettings.GetSetting("mono/export/aot/android_toolchain_path");
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(androidToolchain))
|
||||||
|
{
|
||||||
|
androidToolchain = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "android-toolchains", $"{abi}"); // TODO: $"{abi}-{apiLevel}{(clang?"clang":"")}"
|
||||||
|
|
||||||
|
if (!Directory.Exists(androidToolchain))
|
||||||
|
throw new FileNotFoundException("Missing android toolchain. Specify one in the AOT export settings.");
|
||||||
|
}
|
||||||
|
else if (!Directory.Exists(androidToolchain))
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException("Android toolchain not found: " + androidToolchain);
|
||||||
|
}
|
||||||
|
|
||||||
|
var androidToolPrefixes = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["armeabi-v7a"] = "arm-linux-androideabi-",
|
||||||
|
["arm64-v8a"] = "aarch64-linux-android-",
|
||||||
|
["x86"] = "i686-linux-android-",
|
||||||
|
["x86_64"] = "x86_64-linux-android-"
|
||||||
|
};
|
||||||
|
|
||||||
|
aotOptions.Add("tool-prefix=" + Path.Combine(androidToolchain, "bin", androidToolPrefixes[abi]));
|
||||||
|
|
||||||
|
string triple = GetAndroidTriple(abi);
|
||||||
|
aotOptions.Add ($"mtriple={triple}");
|
||||||
|
}
|
||||||
|
|
||||||
|
aotOptions.Add($"outfile={outputFilePath}");
|
||||||
|
|
||||||
|
var extraAotOptions = (string[]) ProjectSettings.GetSetting("mono/export/aot/extra_aot_options");
|
||||||
|
var extraOptimizerOptions = (string[]) ProjectSettings.GetSetting("mono/export/aot/extra_optimizer_options");
|
||||||
|
|
||||||
|
if (extraAotOptions.Length > 0)
|
||||||
|
aotOptions.AddRange(extraAotOptions);
|
||||||
|
|
||||||
|
if (extraOptimizerOptions.Length > 0)
|
||||||
|
optimizerOptions.AddRange(extraOptimizerOptions);
|
||||||
|
|
||||||
|
var compilerArgs = new List<string>();
|
||||||
|
|
||||||
|
if (isDebug)
|
||||||
|
compilerArgs.Add("--debug"); // Required for --aot=soft-debug
|
||||||
|
|
||||||
|
compilerArgs.Add(aotOptions.Count > 0 ? $"--aot={OptionsToString(aotOptions)}" : "--aot");
|
||||||
|
|
||||||
|
if (optimizerOptions.Count > 0)
|
||||||
|
compilerArgs.Add($"-O={OptionsToString(optimizerOptions)}");
|
||||||
|
|
||||||
|
compilerArgs.Add(ProjectSettings.GlobalizePath(assemblyPath));
|
||||||
|
|
||||||
|
// TODO: Once we move to .NET Standard 2.1 we can use ProcessStartInfo.ArgumentList instead
|
||||||
|
string CmdLineArgsToString(IEnumerable<string> args)
|
||||||
|
{
|
||||||
|
// Not perfect, but as long as we are careful...
|
||||||
|
return string.Join(" ", args.Select(arg => arg.Contains(" ") ? $@"""{arg}""" : arg));
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var process = new Process())
|
||||||
|
{
|
||||||
|
process.StartInfo = new ProcessStartInfo(compilerCommand, CmdLineArgsToString(compilerArgs))
|
||||||
|
{
|
||||||
|
UseShellExecute = false
|
||||||
|
};
|
||||||
|
|
||||||
|
string platformBclDir = DeterminePlatformBclDir(platform);
|
||||||
|
process.StartInfo.EnvironmentVariables.Add("MONO_PATH", string.IsNullOrEmpty(platformBclDir) ?
|
||||||
|
typeof(object).Assembly.Location.GetBaseDir() :
|
||||||
|
platformBclDir);
|
||||||
|
|
||||||
|
Console.WriteLine($"Running: \"{process.StartInfo.FileName}\" {process.StartInfo.Arguments}");
|
||||||
|
|
||||||
|
if (!process.Start())
|
||||||
|
throw new Exception("Failed to start process for Mono AOT compiler");
|
||||||
|
|
||||||
|
process.WaitForExit();
|
||||||
|
|
||||||
|
if (process.ExitCode != 0)
|
||||||
|
throw new Exception($"Mono AOT compiler exited with error code: {process.ExitCode}");
|
||||||
|
|
||||||
|
if (!System.IO.File.Exists(outputFilePath))
|
||||||
|
throw new Exception("Mono AOT compiler finished successfully but the output file is missing");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string DetermineMonoCrossDirName(string platform, IReadOnlyDictionary<string, string> data)
|
||||||
|
{
|
||||||
|
switch (platform)
|
||||||
|
{
|
||||||
|
case OS.Platforms.Windows:
|
||||||
|
case OS.Platforms.UWP:
|
||||||
|
{
|
||||||
|
string arch = data["bits"] == "64" ? "x86_64" : "i686";
|
||||||
|
return $"windows-{arch}";
|
||||||
|
}
|
||||||
|
case OS.Platforms.OSX:
|
||||||
|
{
|
||||||
|
string arch = "x86_64";
|
||||||
|
return $"{platform}-{arch}";
|
||||||
|
}
|
||||||
|
case OS.Platforms.X11:
|
||||||
|
case OS.Platforms.Server:
|
||||||
|
{
|
||||||
|
string arch = data["bits"] == "64" ? "x86_64" : "i686";
|
||||||
|
return $"linux-{arch}";
|
||||||
|
}
|
||||||
|
case OS.Platforms.Haiku:
|
||||||
|
{
|
||||||
|
string arch = data["bits"] == "64" ? "x86_64" : "i686";
|
||||||
|
return $"{platform}-{arch}";
|
||||||
|
}
|
||||||
|
case OS.Platforms.Android:
|
||||||
|
{
|
||||||
|
string abi = data["abi"];
|
||||||
|
return $"{platform}-{abi}";
|
||||||
|
}
|
||||||
|
case OS.Platforms.HTML5:
|
||||||
|
return "wasm-wasm32";
|
||||||
|
default:
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string DetermineToolPrefix(string monoCrossBin)
|
||||||
|
{
|
||||||
|
string exeExt = OS.IsWindows ? ".exe" : string.Empty;
|
||||||
|
|
||||||
|
if (System.IO.File.Exists(Path.Combine(monoCrossBin, $"mono{exeExt}")))
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
if (System.IO.File.Exists(Path.Combine(monoCrossBin, $"mono-sgen{exeExt}" + exeExt)))
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
var files = new DirectoryInfo(monoCrossBin).GetFiles($"*mono{exeExt}" + exeExt, SearchOption.TopDirectoryOnly);
|
||||||
|
if (files.Length > 0)
|
||||||
|
{
|
||||||
|
string fileName = files[0].Name;
|
||||||
|
return fileName.Substring(0, fileName.Length - $"mono{exeExt}".Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
files = new DirectoryInfo(monoCrossBin).GetFiles($"*mono-sgen{exeExt}" + exeExt, SearchOption.TopDirectoryOnly);
|
||||||
|
if (files.Length > 0)
|
||||||
|
{
|
||||||
|
string fileName = files[0].Name;
|
||||||
|
return fileName.Substring(0, fileName.Length - $"mono-sgen{exeExt}".Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new FileNotFoundException($"Cannot find the mono runtime executable in {monoCrossBin}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<string> GetEnabledAndroidAbis(string[] features)
|
||||||
|
{
|
||||||
|
var androidAbis = new[]
|
||||||
|
{
|
||||||
|
"armeabi-v7a",
|
||||||
|
"arm64-v8a",
|
||||||
|
"x86",
|
||||||
|
"x86_64"
|
||||||
|
};
|
||||||
|
|
||||||
|
return androidAbis.Where(features.Contains);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetAndroidTriple(string abi)
|
||||||
|
{
|
||||||
|
var abiArchs = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["armeabi-v7a"] = "armv7",
|
||||||
|
["arm64-v8a"] = "aarch64-v8a",
|
||||||
|
["x86"] = "i686",
|
||||||
|
["x86_64"] = "x86_64"
|
||||||
|
};
|
||||||
|
|
||||||
|
string arch = abiArchs[abi];
|
||||||
|
|
||||||
|
return $"{arch}-linux-android";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool PlatformHasTemplateDir(string platform)
|
private static bool PlatformHasTemplateDir(string platform)
|
||||||
|
@ -194,6 +561,43 @@ namespace GodotTools.Export
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string DeterminePlatformBclDir(string platform)
|
||||||
|
{
|
||||||
|
string templatesDir = Internal.FullTemplatesDir;
|
||||||
|
string platformBclDir = Path.Combine(templatesDir, "bcl", platform);
|
||||||
|
|
||||||
|
if (!File.Exists(Path.Combine(platformBclDir, "mscorlib.dll")))
|
||||||
|
{
|
||||||
|
string profile = DeterminePlatformBclProfile(platform);
|
||||||
|
platformBclDir = Path.Combine(templatesDir, "bcl", profile);
|
||||||
|
|
||||||
|
if (!File.Exists(Path.Combine(platformBclDir, "mscorlib.dll")))
|
||||||
|
platformBclDir = null; // Use the one we're running on
|
||||||
|
}
|
||||||
|
|
||||||
|
return platformBclDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string DeterminePlatformBclProfile(string platform)
|
||||||
|
{
|
||||||
|
switch (platform)
|
||||||
|
{
|
||||||
|
case OS.Platforms.Windows:
|
||||||
|
case OS.Platforms.UWP:
|
||||||
|
case OS.Platforms.OSX:
|
||||||
|
case OS.Platforms.X11:
|
||||||
|
case OS.Platforms.Server:
|
||||||
|
case OS.Platforms.Haiku:
|
||||||
|
return "net_4_x";
|
||||||
|
case OS.Platforms.Android:
|
||||||
|
return "monodroid";
|
||||||
|
case OS.Platforms.HTML5:
|
||||||
|
return "wasm";
|
||||||
|
default:
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static string DataDirName
|
private static string DataDirName
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
|
@ -416,7 +416,7 @@ namespace GodotTools
|
||||||
|
|
||||||
string settingsHintStr = "Disabled";
|
string settingsHintStr = "Disabled";
|
||||||
|
|
||||||
if (OS.IsWindows())
|
if (OS.IsWindows)
|
||||||
{
|
{
|
||||||
settingsHintStr += $",MonoDevelop:{(int) ExternalEditorId.MonoDevelop}" +
|
settingsHintStr += $",MonoDevelop:{(int) ExternalEditorId.MonoDevelop}" +
|
||||||
$",Visual Studio Code:{(int) ExternalEditorId.VsCode}";
|
$",Visual Studio Code:{(int) ExternalEditorId.VsCode}";
|
||||||
|
@ -427,7 +427,7 @@ namespace GodotTools
|
||||||
$",MonoDevelop:{(int) ExternalEditorId.MonoDevelop}" +
|
$",MonoDevelop:{(int) ExternalEditorId.MonoDevelop}" +
|
||||||
$",Visual Studio Code:{(int) ExternalEditorId.VsCode}";
|
$",Visual Studio Code:{(int) ExternalEditorId.VsCode}";
|
||||||
}
|
}
|
||||||
else if (OS.IsUnix())
|
else if (OS.IsUnixLike())
|
||||||
{
|
{
|
||||||
settingsHintStr += $",MonoDevelop:{(int) ExternalEditorId.MonoDevelop}" +
|
settingsHintStr += $",MonoDevelop:{(int) ExternalEditorId.MonoDevelop}" +
|
||||||
$",Visual Studio Code:{(int) ExternalEditorId.VsCode}";
|
$",Visual Studio Code:{(int) ExternalEditorId.VsCode}";
|
||||||
|
@ -444,6 +444,7 @@ namespace GodotTools
|
||||||
// Export plugin
|
// Export plugin
|
||||||
var exportPlugin = new ExportPlugin();
|
var exportPlugin = new ExportPlugin();
|
||||||
AddExportPlugin(exportPlugin);
|
AddExportPlugin(exportPlugin);
|
||||||
|
exportPlugin.RegisterExportSettings();
|
||||||
exportPluginWeak = WeakRef(exportPlugin);
|
exportPluginWeak = WeakRef(exportPlugin);
|
||||||
|
|
||||||
BuildManager.Initialize();
|
BuildManager.Initialize();
|
||||||
|
|
|
@ -107,7 +107,7 @@ namespace GodotTools.Ides.MonoDevelop
|
||||||
{EditorId.VisualStudioForMac, "com.microsoft.visual-studio"}
|
{EditorId.VisualStudioForMac, "com.microsoft.visual-studio"}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else if (OS.IsWindows())
|
else if (OS.IsWindows)
|
||||||
{
|
{
|
||||||
ExecutableNames = new Dictionary<EditorId, string>
|
ExecutableNames = new Dictionary<EditorId, string>
|
||||||
{
|
{
|
||||||
|
@ -118,7 +118,7 @@ namespace GodotTools.Ides.MonoDevelop
|
||||||
{EditorId.MonoDevelop, "MonoDevelop.exe"}
|
{EditorId.MonoDevelop, "MonoDevelop.exe"}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else if (OS.IsUnix())
|
else if (OS.IsUnixLike())
|
||||||
{
|
{
|
||||||
ExecutableNames = new Dictionary<EditorId, string>
|
ExecutableNames = new Dictionary<EditorId, string>
|
||||||
{
|
{
|
||||||
|
|
|
@ -52,7 +52,7 @@ namespace GodotTools.Internals
|
||||||
|
|
||||||
public static void ScriptEditorDebugger_ReloadScripts() => internal_ScriptEditorDebugger_ReloadScripts();
|
public static void ScriptEditorDebugger_ReloadScripts() => internal_ScriptEditorDebugger_ReloadScripts();
|
||||||
|
|
||||||
// Internal Calls
|
#region Internal
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||||
private static extern string internal_UpdateApiAssembliesFromPrebuilt(string config);
|
private static extern string internal_UpdateApiAssembliesFromPrebuilt(string config);
|
||||||
|
@ -110,5 +110,7 @@ namespace GodotTools.Internals
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||||
private static extern void internal_ScriptEditorDebugger_ReloadScripts();
|
private static extern void internal_ScriptEditorDebugger_ReloadScripts();
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ namespace GodotTools.Utils
|
||||||
return name.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
|
return name.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsWindows() => IsOS(Names.Windows);
|
public static bool IsWindows => IsOS(Names.Windows);
|
||||||
|
|
||||||
public static bool IsOSX => IsOS(Names.OSX);
|
public static bool IsOSX => IsOS(Names.OSX);
|
||||||
|
|
||||||
|
@ -72,23 +72,23 @@ namespace GodotTools.Utils
|
||||||
public static bool IsHTML5 => IsOS(Names.HTML5);
|
public static bool IsHTML5 => IsOS(Names.HTML5);
|
||||||
|
|
||||||
private static bool? _isUnixCache;
|
private static bool? _isUnixCache;
|
||||||
private static readonly string[] UnixPlatforms = {Names.OSX, Names.X11, Names.Server, Names.Haiku, Names.Android};
|
private static readonly string[] UnixLikePlatforms = {Names.OSX, Names.X11, Names.Server, Names.Haiku, Names.Android};
|
||||||
|
|
||||||
public static bool IsUnix()
|
public static bool IsUnixLike()
|
||||||
{
|
{
|
||||||
if (_isUnixCache.HasValue)
|
if (_isUnixCache.HasValue)
|
||||||
return _isUnixCache.Value;
|
return _isUnixCache.Value;
|
||||||
|
|
||||||
string osName = GetPlatformName();
|
string osName = GetPlatformName();
|
||||||
_isUnixCache = UnixPlatforms.Any(p => p.Equals(osName, StringComparison.OrdinalIgnoreCase));
|
_isUnixCache = UnixLikePlatforms.Any(p => p.Equals(osName, StringComparison.OrdinalIgnoreCase));
|
||||||
return _isUnixCache.Value;
|
return _isUnixCache.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static char PathSep => IsWindows() ? ';' : ':';
|
public static char PathSep => IsWindows ? ';' : ':';
|
||||||
|
|
||||||
public static string PathWhich(string name)
|
public static string PathWhich(string name)
|
||||||
{
|
{
|
||||||
string[] windowsExts = IsWindows() ? Environment.GetEnvironmentVariable("PATHEXT")?.Split(PathSep) : null;
|
string[] windowsExts = IsWindows ? Environment.GetEnvironmentVariable("PATHEXT")?.Split(PathSep) : null;
|
||||||
string[] pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep);
|
string[] pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep);
|
||||||
|
|
||||||
var searchDirs = new List<string>();
|
var searchDirs = new List<string>();
|
||||||
|
@ -102,7 +102,7 @@ namespace GodotTools.Utils
|
||||||
{
|
{
|
||||||
string path = Path.Combine(dir, name);
|
string path = Path.Combine(dir, name);
|
||||||
|
|
||||||
if (IsWindows() && windowsExts != null)
|
if (IsWindows && windowsExts != null)
|
||||||
{
|
{
|
||||||
foreach (var extension in windowsExts)
|
foreach (var extension in windowsExts)
|
||||||
{
|
{
|
||||||
|
@ -124,12 +124,14 @@ namespace GodotTools.Utils
|
||||||
|
|
||||||
public static void RunProcess(string command, IEnumerable<string> arguments)
|
public static void RunProcess(string command, IEnumerable<string> arguments)
|
||||||
{
|
{
|
||||||
|
// TODO: Once we move to .NET Standard 2.1 we can use ProcessStartInfo.ArgumentList instead
|
||||||
string CmdLineArgsToString(IEnumerable<string> args)
|
string CmdLineArgsToString(IEnumerable<string> args)
|
||||||
{
|
{
|
||||||
|
// Not perfect, but as long as we are careful...
|
||||||
return string.Join(" ", args.Select(arg => arg.Contains(" ") ? $@"""{arg}""" : arg));
|
return string.Join(" ", args.Select(arg => arg.Contains(" ") ? $@"""{arg}""" : arg));
|
||||||
}
|
}
|
||||||
|
|
||||||
ProcessStartInfo startInfo = new ProcessStartInfo(command, CmdLineArgsToString(arguments))
|
var startInfo = new ProcessStartInfo(command, CmdLineArgsToString(arguments))
|
||||||
{
|
{
|
||||||
RedirectStandardOutput = true,
|
RedirectStandardOutput = true,
|
||||||
RedirectStandardError = true,
|
RedirectStandardError = true,
|
||||||
|
|
|
@ -39,8 +39,8 @@
|
||||||
#include "editor/editor_settings.h"
|
#include "editor/editor_settings.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef __ANDROID__
|
#ifdef ANDROID_ENABLED
|
||||||
#include "utils/android_utils.h"
|
#include "mono_gd/gd_mono_android.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "mono_gd/gd_mono.h"
|
#include "mono_gd/gd_mono.h"
|
||||||
|
@ -164,8 +164,8 @@ private:
|
||||||
String data_mono_root_dir = data_dir_root.plus_file("Mono");
|
String data_mono_root_dir = data_dir_root.plus_file("Mono");
|
||||||
data_mono_etc_dir = data_mono_root_dir.plus_file("etc");
|
data_mono_etc_dir = data_mono_root_dir.plus_file("etc");
|
||||||
|
|
||||||
#if __ANDROID__
|
#ifdef ANDROID_ENABLED
|
||||||
data_mono_lib_dir = GDMonoUtils::Android::get_app_native_lib_dir();
|
data_mono_lib_dir = GDMonoAndroid::get_app_native_lib_dir();
|
||||||
#else
|
#else
|
||||||
data_mono_lib_dir = data_mono_root_dir.plus_file("lib");
|
data_mono_lib_dir = data_mono_root_dir.plus_file("lib");
|
||||||
#endif
|
#endif
|
||||||
|
@ -201,8 +201,8 @@ private:
|
||||||
String data_mono_root_dir = data_dir_root.plus_file("Mono");
|
String data_mono_root_dir = data_dir_root.plus_file("Mono");
|
||||||
data_mono_etc_dir = data_mono_root_dir.plus_file("etc");
|
data_mono_etc_dir = data_mono_root_dir.plus_file("etc");
|
||||||
|
|
||||||
#if __ANDROID__
|
#ifdef ANDROID_ENABLED
|
||||||
data_mono_lib_dir = GDMonoUtils::Android::get_app_native_lib_dir();
|
data_mono_lib_dir = GDMonoAndroid::get_app_native_lib_dir();
|
||||||
#else
|
#else
|
||||||
data_mono_lib_dir = data_mono_root_dir.plus_file("lib");
|
data_mono_lib_dir = data_mono_root_dir.plus_file("lib");
|
||||||
#endif
|
#endif
|
||||||
|
|
116
modules/mono/mono_gd/gd_mono_android.cpp
Normal file
116
modules/mono/mono_gd/gd_mono_android.cpp
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
#include "gd_mono_android.h"
|
||||||
|
|
||||||
|
#if defined(ANDROID_ENABLED)
|
||||||
|
|
||||||
|
#include <dlfcn.h> // dlopen, dlsym
|
||||||
|
#include <mono/utils/mono-dl-fallback.h>
|
||||||
|
|
||||||
|
#include "core/os/os.h"
|
||||||
|
#include "core/ustring.h"
|
||||||
|
#include "platform/android/thread_jandroid.h"
|
||||||
|
|
||||||
|
#include "../utils/path_utils.h"
|
||||||
|
#include "../utils/string_utils.h"
|
||||||
|
|
||||||
|
namespace GDMonoAndroid {
|
||||||
|
|
||||||
|
String app_native_lib_dir_cache;
|
||||||
|
|
||||||
|
String determine_app_native_lib_dir() {
|
||||||
|
JNIEnv *env = ThreadAndroid::get_env();
|
||||||
|
|
||||||
|
jclass activityThreadClass = env->FindClass("android/app/ActivityThread");
|
||||||
|
jmethodID currentActivityThread = env->GetStaticMethodID(activityThreadClass, "currentActivityThread", "()Landroid/app/ActivityThread;");
|
||||||
|
jobject activityThread = env->CallStaticObjectMethod(activityThreadClass, currentActivityThread);
|
||||||
|
jmethodID getApplication = env->GetMethodID(activityThreadClass, "getApplication", "()Landroid/app/Application;");
|
||||||
|
jobject ctx = env->CallObjectMethod(activityThread, getApplication);
|
||||||
|
|
||||||
|
jmethodID getApplicationInfo = env->GetMethodID(env->GetObjectClass(ctx), "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;");
|
||||||
|
jobject applicationInfo = env->CallObjectMethod(ctx, getApplicationInfo);
|
||||||
|
jfieldID nativeLibraryDirField = env->GetFieldID(env->GetObjectClass(applicationInfo), "nativeLibraryDir", "Ljava/lang/String;");
|
||||||
|
jstring nativeLibraryDir = (jstring)env->GetObjectField(applicationInfo, nativeLibraryDirField);
|
||||||
|
|
||||||
|
String result;
|
||||||
|
|
||||||
|
const char *const nativeLibraryDir_utf8 = env->GetStringUTFChars(nativeLibraryDir, NULL);
|
||||||
|
if (nativeLibraryDir_utf8) {
|
||||||
|
result.parse_utf8(nativeLibraryDir_utf8);
|
||||||
|
env->ReleaseStringUTFChars(nativeLibraryDir, nativeLibraryDir_utf8);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
String get_app_native_lib_dir() {
|
||||||
|
if (app_native_lib_dir_cache.empty())
|
||||||
|
app_native_lib_dir_cache = determine_app_native_lib_dir();
|
||||||
|
return app_native_lib_dir_cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
int gd_mono_convert_dl_flags(int flags) {
|
||||||
|
// from mono's runtime-bootstrap.c
|
||||||
|
|
||||||
|
int lflags = flags & MONO_DL_LOCAL ? 0 : RTLD_GLOBAL;
|
||||||
|
|
||||||
|
if (flags & MONO_DL_LAZY)
|
||||||
|
lflags |= RTLD_LAZY;
|
||||||
|
else
|
||||||
|
lflags |= RTLD_NOW;
|
||||||
|
|
||||||
|
return lflags;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *gd_mono_android_dlopen(const char *p_name, int p_flags, char **r_err, void *p_user_data) {
|
||||||
|
String name = String::utf8(p_name);
|
||||||
|
|
||||||
|
if (name.ends_with(".dll.so") || name.ends_with(".exe.so")) {
|
||||||
|
String app_native_lib_dir = get_app_native_lib_dir();
|
||||||
|
|
||||||
|
String orig_so_name = name.get_file();
|
||||||
|
String so_name = "lib-aot-" + orig_so_name;
|
||||||
|
String so_path = path::join(app_native_lib_dir, so_name);
|
||||||
|
|
||||||
|
if (!FileAccess::exists(so_path)) {
|
||||||
|
if (OS::get_singleton()->is_stdout_verbose())
|
||||||
|
OS::get_singleton()->print("Cannot find shared library: '%s'\n", so_path.utf8().get_data());
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lflags = gd_mono_convert_dl_flags(p_flags);
|
||||||
|
|
||||||
|
void *handle = dlopen(so_path.utf8().get_data(), lflags);
|
||||||
|
|
||||||
|
if (!handle) {
|
||||||
|
if (OS::get_singleton()->is_stdout_verbose())
|
||||||
|
OS::get_singleton()->print("Failed to open shared library: '%s'. Error: '%s'\n", so_path.utf8().get_data(), dlerror());
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OS::get_singleton()->is_stdout_verbose())
|
||||||
|
OS::get_singleton()->print("Successfully loaded AOT shared library: '%s'\n", so_path.utf8().get_data());
|
||||||
|
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *gd_mono_android_dlsym(void *p_handle, const char *p_name, char **r_err, void *p_user_data) {
|
||||||
|
void *sym_addr = dlsym(p_handle, p_name);
|
||||||
|
|
||||||
|
if (sym_addr)
|
||||||
|
return sym_addr;
|
||||||
|
|
||||||
|
if (r_err)
|
||||||
|
*r_err = str_format_new("%s\n", dlerror());
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void register_android_dl_fallback() {
|
||||||
|
mono_dl_fallback_register(gd_mono_android_dlopen, gd_mono_android_dlsym, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace GDMonoAndroid
|
||||||
|
|
||||||
|
#endif
|
18
modules/mono/mono_gd/gd_mono_android.h
Normal file
18
modules/mono/mono_gd/gd_mono_android.h
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#ifndef GD_MONO_ANDROID_H
|
||||||
|
#define GD_MONO_ANDROID_H
|
||||||
|
|
||||||
|
#if defined(ANDROID_ENABLED)
|
||||||
|
|
||||||
|
#include "core/ustring.h"
|
||||||
|
|
||||||
|
namespace GDMonoAndroid {
|
||||||
|
|
||||||
|
String get_app_native_lib_dir();
|
||||||
|
|
||||||
|
void register_android_dl_fallback();
|
||||||
|
|
||||||
|
} // namespace GDMonoAndroid
|
||||||
|
|
||||||
|
#endif // ANDROID_ENABLED
|
||||||
|
|
||||||
|
#endif // GD_MONO_ANDROID_H
|
|
@ -1,68 +0,0 @@
|
||||||
/*************************************************************************/
|
|
||||||
/* android_utils.cpp */
|
|
||||||
/*************************************************************************/
|
|
||||||
/* This file is part of: */
|
|
||||||
/* GODOT ENGINE */
|
|
||||||
/* https://godotengine.org */
|
|
||||||
/*************************************************************************/
|
|
||||||
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
|
|
||||||
/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
|
|
||||||
/* */
|
|
||||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
|
||||||
/* a copy of this software and associated documentation files (the */
|
|
||||||
/* "Software"), to deal in the Software without restriction, including */
|
|
||||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
|
||||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
|
||||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
|
||||||
/* the following conditions: */
|
|
||||||
/* */
|
|
||||||
/* The above copyright notice and this permission notice shall be */
|
|
||||||
/* included in all copies or substantial portions of the Software. */
|
|
||||||
/* */
|
|
||||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
|
||||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
|
||||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
|
||||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
|
||||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
|
||||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
|
||||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
||||||
/*************************************************************************/
|
|
||||||
|
|
||||||
#include "android_utils.h"
|
|
||||||
|
|
||||||
#ifdef __ANDROID__
|
|
||||||
|
|
||||||
#include "platform/android/thread_jandroid.h"
|
|
||||||
|
|
||||||
namespace GDMonoUtils {
|
|
||||||
namespace Android {
|
|
||||||
|
|
||||||
String get_app_native_lib_dir() {
|
|
||||||
JNIEnv *env = ThreadAndroid::get_env();
|
|
||||||
|
|
||||||
jclass activityThreadClass = env->FindClass("android/app/ActivityThread");
|
|
||||||
jmethodID currentActivityThread = env->GetStaticMethodID(activityThreadClass, "currentActivityThread", "()Landroid/app/ActivityThread;");
|
|
||||||
jobject activityThread = env->CallStaticObjectMethod(activityThreadClass, currentActivityThread);
|
|
||||||
jmethodID getApplication = env->GetMethodID(activityThreadClass, "getApplication", "()Landroid/app/Application;");
|
|
||||||
jobject ctx = env->CallObjectMethod(activityThread, getApplication);
|
|
||||||
|
|
||||||
jmethodID getApplicationInfo = env->GetMethodID(env->GetObjectClass(ctx), "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;");
|
|
||||||
jobject applicationInfo = env->CallObjectMethod(ctx, getApplicationInfo);
|
|
||||||
jfieldID nativeLibraryDirField = env->GetFieldID(env->GetObjectClass(applicationInfo), "nativeLibraryDir", "Ljava/lang/String;");
|
|
||||||
jstring nativeLibraryDir = (jstring)env->GetObjectField(applicationInfo, nativeLibraryDirField);
|
|
||||||
|
|
||||||
String result;
|
|
||||||
|
|
||||||
const char *const nativeLibraryDir_utf8 = env->GetStringUTFChars(nativeLibraryDir, NULL);
|
|
||||||
if (nativeLibraryDir_utf8) {
|
|
||||||
result.parse_utf8(nativeLibraryDir_utf8);
|
|
||||||
env->ReleaseStringUTFChars(nativeLibraryDir, nativeLibraryDir_utf8);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Android
|
|
||||||
} // namespace GDMonoUtils
|
|
||||||
|
|
||||||
#endif // __ANDROID__
|
|
|
@ -1,48 +0,0 @@
|
||||||
/*************************************************************************/
|
|
||||||
/* android_utils.h */
|
|
||||||
/*************************************************************************/
|
|
||||||
/* This file is part of: */
|
|
||||||
/* GODOT ENGINE */
|
|
||||||
/* https://godotengine.org */
|
|
||||||
/*************************************************************************/
|
|
||||||
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
|
|
||||||
/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
|
|
||||||
/* */
|
|
||||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
|
||||||
/* a copy of this software and associated documentation files (the */
|
|
||||||
/* "Software"), to deal in the Software without restriction, including */
|
|
||||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
|
||||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
|
||||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
|
||||||
/* the following conditions: */
|
|
||||||
/* */
|
|
||||||
/* The above copyright notice and this permission notice shall be */
|
|
||||||
/* included in all copies or substantial portions of the Software. */
|
|
||||||
/* */
|
|
||||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
|
||||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
|
||||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
|
||||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
|
||||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
|
||||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
|
||||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
||||||
/*************************************************************************/
|
|
||||||
|
|
||||||
#ifndef ANDROID_UTILS_H
|
|
||||||
#define ANDROID_UTILS_H
|
|
||||||
|
|
||||||
#ifdef __ANDROID__
|
|
||||||
|
|
||||||
#include "core/ustring.h"
|
|
||||||
|
|
||||||
namespace GDMonoUtils {
|
|
||||||
namespace Android {
|
|
||||||
|
|
||||||
String get_app_native_lib_dir();
|
|
||||||
|
|
||||||
} // namespace Android
|
|
||||||
} // namespace GDMonoUtils
|
|
||||||
|
|
||||||
#endif // __ANDROID__
|
|
||||||
|
|
||||||
#endif // ANDROID_UTILS_H
|
|
|
@ -216,6 +216,25 @@ String str_format(const char *p_format, ...) {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
String str_format(const char *p_format, va_list p_list) {
|
String str_format(const char *p_format, va_list p_list) {
|
||||||
|
char *buffer = str_format_new(p_format, p_list);
|
||||||
|
|
||||||
|
String res(buffer);
|
||||||
|
memdelete_arr(buffer);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *str_format_new(const char *p_format, ...) {
|
||||||
|
va_list list;
|
||||||
|
|
||||||
|
va_start(list, p_format);
|
||||||
|
char *res = str_format_new(p_format, list);
|
||||||
|
va_end(list);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *str_format_new(const char *p_format, va_list p_list) {
|
||||||
va_list list;
|
va_list list;
|
||||||
|
|
||||||
va_copy(list, p_list);
|
va_copy(list, p_list);
|
||||||
|
@ -230,8 +249,5 @@ String str_format(const char *p_format, va_list p_list) {
|
||||||
gd_vsnprintf(buffer, len, p_format, list);
|
gd_vsnprintf(buffer, len, p_format, list);
|
||||||
va_end(list);
|
va_end(list);
|
||||||
|
|
||||||
String res(buffer);
|
return buffer;
|
||||||
memdelete_arr(buffer);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,5 +56,7 @@ Error read_all_file_utf8(const String &p_path, String &r_content);
|
||||||
|
|
||||||
String str_format(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_1_2;
|
String str_format(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_1_2;
|
||||||
String str_format(const char *p_format, va_list p_list) _PRINTF_FORMAT_ATTRIBUTE_1_0;
|
String str_format(const char *p_format, va_list p_list) _PRINTF_FORMAT_ATTRIBUTE_1_0;
|
||||||
|
char *str_format_new(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_1_2;
|
||||||
|
char *str_format_new(const char *p_format, va_list p_list) _PRINTF_FORMAT_ATTRIBUTE_1_0;
|
||||||
|
|
||||||
#endif // STRING_FORMAT_H
|
#endif // STRING_FORMAT_H
|
||||||
|
|
Loading…
Reference in a new issue