C#: Fallback to CoreCLR/MonoVM hosting APIs when hostfxr/NativeAOT fails

Some platforms don't support hostfxr but we can use the coreclr/monosgen library directly to initialize the runtime.

Android exports now use the `android` runtime identifier instead of `linux-bionic`, this removes the restrictions we previously had:
- Adds support for all Android architectures (arm32, arm64, x32, and x64), previously only the 64-bit architectures were supported.
- Loads `System.Security.Cryptography.Native.Android` (the .NET library that binds to the Android OS crypto functions).
This commit is contained in:
Raul Santos
2024-02-09 17:26:53 +01:00
parent a75bacebef
commit 0aa46e19c5
14 changed files with 301 additions and 41 deletions

View File

@@ -29,6 +29,8 @@ allprojects {
configurations {
// Initializes a placeholder for the devImplementation dependency configuration.
devImplementation {}
// Initializes a placeholder for the monoImplementation dependency configuration.
monoImplementation {}
}
dependencies {
@@ -42,9 +44,9 @@ dependencies {
} else {
// Godot gradle build mode. In this scenario this project is the only one around and the Godot
// library is available through the pre-generated godot-lib.*.aar android archive files.
debugImplementation fileTree(dir: 'libs/debug', include: ['*.jar', '*.aar'])
devImplementation fileTree(dir: 'libs/dev', include: ['*.jar', '*.aar'])
releaseImplementation fileTree(dir: 'libs/release', include: ['*.jar', '*.aar'])
debugImplementation fileTree(dir: 'libs/debug', include: ['**/*.jar', '*.aar'])
devImplementation fileTree(dir: 'libs/dev', include: ['**/*.jar', '*.aar'])
releaseImplementation fileTree(dir: 'libs/release', include: ['**/*.jar', '*.aar'])
}
// Godot user plugins remote dependencies
@@ -60,6 +62,12 @@ dependencies {
if (pluginsBinaries != null && pluginsBinaries.size() > 0) {
implementation files(pluginsBinaries)
}
// .NET dependencies
String jar = '../../../../modules/mono/thirdparty/libSystem.Security.Cryptography.Native.Android.jar'
if (file(jar).exists()) {
monoImplementation files(jar)
}
}
android {
@@ -155,6 +163,10 @@ android {
}
}
buildFeatures {
buildConfig = true
}
buildTypes {
debug {
@@ -192,6 +204,13 @@ android {
}
}
flavorDimensions 'edition'
productFlavors {
standard {}
mono {}
}
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
@@ -207,7 +226,8 @@ android {
applicationVariants.all { variant ->
variant.outputs.all { output ->
output.outputFileName = "android_${variant.name}.apk"
String filenameSuffix = variant.flavorName == "mono" ? variant.name : variant.buildType.name
output.outputFileName = "android_${filenameSuffix}.apk"
}
}
}
@@ -220,12 +240,20 @@ task copyAndRenameBinary(type: Copy) {
String exportPath = getExportPath()
String exportFilename = getExportFilename()
String exportEdition = getExportEdition()
String exportBuildType = getExportBuildType()
String exportBuildTypeCapitalized = exportBuildType.capitalize()
String exportFormat = getExportFormat()
boolean isAab = exportFormat == "aab"
String sourceFilepath = isAab ? "$buildDir/outputs/bundle/$exportBuildType/build-${exportBuildType}.aab" : "$buildDir/outputs/apk/$exportBuildType/android_${exportBuildType}.apk"
String sourceFilename = isAab ? "build-${exportBuildType}.aab" : "android_${exportBuildType}.apk"
boolean isMono = exportEdition == "mono"
String filenameSuffix = exportBuildType
if (isMono) {
filenameSuffix = isAab ? "${exportEdition}-${exportBuildType}" : "${exportEdition}${exportBuildTypeCapitalized}"
}
String sourceFilename = isAab ? "build-${filenameSuffix}.aab" : "android_${filenameSuffix}.apk"
String sourceFilepath = isAab ? "$buildDir/outputs/bundle/${exportEdition}${exportBuildTypeCapitalized}/$sourceFilename" : "$buildDir/outputs/apk/$exportEdition/$exportBuildType/$sourceFilename"
from sourceFilepath
into exportPath

View File

@@ -224,6 +224,14 @@ ext.getExportFilename = {
return exportFilename
}
ext.getExportEdition = {
String exportEdition = project.hasProperty("export_edition") ? project.property("export_edition") : ""
if (exportEdition == null || exportEdition.isEmpty()) {
exportEdition = "standard"
}
return exportEdition
}
ext.getExportBuildType = {
String exportBuildType = project.hasProperty("export_build_type") ? project.property("export_build_type") : ""
if (exportBuildType == null || exportBuildType.isEmpty()) {

View File

@@ -33,14 +33,29 @@ package com.godot.game;
import org.godotengine.godot.GodotActivity;
import android.os.Bundle;
import android.util.Log;
import androidx.core.splashscreen.SplashScreen;
import com.godot.game.BuildConfig;
/**
* Template activity for Godot Android builds.
* Feel free to extend and modify this class for your custom logic.
*/
public class GodotApp extends GodotActivity {
static {
// .NET libraries.
if (BuildConfig.FLAVOR.equals("mono")) {
try {
Log.v("GODOT", "Loading System.Security.Cryptography.Native.Android library");
System.loadLibrary("System.Security.Cryptography.Native.Android");
} catch (UnsatisfiedLinkError e) {
Log.e("GODOT", "Unable to load System.Security.Cryptography.Native.Android library");
}
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
SplashScreen.installSplashScreen(this);

View File

@@ -30,6 +30,7 @@ ext {
"editor": ["dev", "debug", "release"],
"template": ["dev", "debug", "release"]
]
supportedEditions = ["standard", "mono"]
// Used by gradle to specify which architecture to build for by default when running
// `./gradlew build` (this command is usually used by Android Studio).
@@ -53,7 +54,7 @@ def getSconsTaskName(String flavor, String buildType, String abi) {
* The zip file also includes some gradle tools to enable gradle builds from the Godot Editor.
*/
task zipGradleBuild(type: Zip) {
onlyIf { generateGodotTemplates.state.executed || generateDevTemplate.state.executed }
onlyIf { generateGodotTemplates.state.executed || generateGodotMonoTemplates.state.executed || generateDevTemplate.state.executed }
doFirst {
logger.lifecycle("Generating Godot gradle build template")
}
@@ -94,15 +95,22 @@ def templateExcludedBuildTask() {
/**
* Generates the build tasks for the given flavor
* @param flavor Must be one of the supported flavors ('template' / 'editor')
* @param edition Must be one of the supported editions ('standard' / 'mono')
* @param androidDistro Must be one of the supported Android distributions ('android' / 'horizonos')
*/
def generateBuildTasks(String flavor = "template", String androidDistro = "android") {
def generateBuildTasks(String flavor = "template", String edition = "standard", String androidDistro = "android") {
if (!supportedFlavors.contains(flavor)) {
throw new GradleException("Invalid build flavor: $flavor")
}
if (!supportedAndroidDistributions.contains(androidDistro)) {
throw new GradleException("Invalid Android distribution: $androidDistro")
}
if (!supportedEditions.contains(edition)) {
throw new GradleException("Invalid build edition: $edition")
}
if (edition == "mono" && flavor != "template") {
throw new GradleException("'mono' edition only supports the 'template' flavor.")
}
String capitalizedAndroidDistro = androidDistro.capitalize()
def buildTasks = []
@@ -126,6 +134,7 @@ def generateBuildTasks(String flavor = "template", String androidDistro = "andro
&& targetLibs.listFiles().length > 0)) {
String capitalizedTarget = target.capitalize()
String capitalizedEdition = edition.capitalize()
if (isTemplate) {
// Copy the Godot android library archive file into the app module libs directory.
// Depends on the library build task to ensure the AAR file is generated prior to copying.
@@ -157,15 +166,16 @@ def generateBuildTasks(String flavor = "template", String androidDistro = "andro
// Copy the generated binary template into the Godot bin directory.
// Depends on the app build task to ensure the binary is generated prior to copying.
String copyBinaryTaskName = "copy${capitalizedTarget}BinaryToBin"
String copyBinaryTaskName = "copy${capitalizedEdition}${capitalizedTarget}BinaryToBin"
if (tasks.findByName(copyBinaryTaskName) != null) {
buildTasks += tasks.getByName(copyBinaryTaskName)
} else {
buildTasks += tasks.create(name: copyBinaryTaskName, type: Copy) {
dependsOn ":app:assemble${capitalizedTarget}"
from("app/build/outputs/apk/${target}")
String filenameSuffix = edition == "mono" ? "${edition}${capitalizedTarget}" : target
dependsOn ":app:assemble${capitalizedEdition}${capitalizedTarget}"
from("app/build/outputs/apk/${edition}/${target}")
into(binDir)
include("android_${target}.apk")
include("android_${filenameSuffix}.apk")
}
}
} else {
@@ -212,7 +222,7 @@ def generateBuildTasks(String flavor = "template", String androidDistro = "andro
*/
task generateGodotEditor {
gradle.startParameter.excludedTaskNames += templateExcludedBuildTask()
dependsOn = generateBuildTasks("editor", "android")
dependsOn = generateBuildTasks("editor", "standard", "android")
}
/**
@@ -224,7 +234,7 @@ task generateGodotEditor {
*/
task generateGodotHorizonOSEditor {
gradle.startParameter.excludedTaskNames += templateExcludedBuildTask()
dependsOn = generateBuildTasks("editor", "horizonos")
dependsOn = generateBuildTasks("editor", "standard", "horizonos")
}
/**
@@ -237,6 +247,17 @@ task generateGodotTemplates {
finalizedBy 'zipGradleBuild'
}
/**
* Master task used to coordinate the tasks defined above to generate the set of Godot templates
* for the 'mono' edition of the engine.
*/
task generateGodotMonoTemplates {
gradle.startParameter.excludedTaskNames += templateExcludedBuildTask()
dependsOn = generateBuildTasks("template", "mono")
finalizedBy 'zipGradleBuild'
}
/**
* Generates the same output as generateGodotTemplates but with dev symbols
*/
@@ -295,6 +316,9 @@ task cleanGodotTemplates(type: Delete) {
delete("$binDir/android_debug.apk")
delete("$binDir/android_dev.apk")
delete("$binDir/android_release.apk")
delete("$binDir/android_monoDebug.apk")
delete("$binDir/android_monoDev.apk")
delete("$binDir/android_monoRelease.apk")
delete("$binDir/android_source.zip")
delete("$binDir/godot-lib.template_debug.aar")
delete("$binDir/godot-lib.template_debug.dev.aar")