205 lines
8.0 KiB
Groovy
205 lines
8.0 KiB
Groovy
// file: Android/gradle/sasedev-rust-android.gradle
|
|
// version: 2
|
|
|
|
import java.util.Collections
|
|
import java.util.zip.ZipFile
|
|
import javax.inject.Inject
|
|
import org.gradle.process.ExecOperations
|
|
|
|
abstract class BuildSasedevRustAndroidTask extends DefaultTask {
|
|
@InputFile
|
|
@PathSensitive(PathSensitivity.RELATIVE)
|
|
abstract RegularFileProperty getSdl3Aar()
|
|
|
|
@InputFiles
|
|
@PathSensitive(PathSensitivity.RELATIVE)
|
|
abstract ConfigurableFileCollection getRustInputs()
|
|
|
|
@Input
|
|
abstract Property<String> getGameFeature()
|
|
|
|
@Input
|
|
abstract Property<String> getAbi()
|
|
|
|
@Input
|
|
abstract Property<Integer> getAndroidApi()
|
|
|
|
@Input
|
|
abstract Property<String> getNdkVersion()
|
|
|
|
@Input
|
|
abstract Property<Boolean> getReleaseBuild()
|
|
|
|
@Internal
|
|
abstract DirectoryProperty getRepositoryRoot()
|
|
|
|
@OutputDirectory
|
|
abstract DirectoryProperty getOutputDirectory()
|
|
|
|
@Inject
|
|
abstract ExecOperations getExecOperations()
|
|
|
|
@TaskAction
|
|
void buildNative() {
|
|
def selectedAbi = abi.get()
|
|
def selectedFeature = gameFeature.get()
|
|
def aar = sdl3Aar.get().asFile
|
|
def repository = repositoryRoot.get().asFile
|
|
def output = outputDirectory.get().asFile
|
|
def runtimeDirectory = new File(output, selectedAbi)
|
|
def linkDirectory = new File(temporaryDir, "link")
|
|
|
|
project.delete(output)
|
|
project.delete(linkDirectory)
|
|
runtimeDirectory.mkdirs()
|
|
linkDirectory.mkdirs()
|
|
|
|
byte[] sdl3Payload
|
|
new ZipFile(aar).withCloseable { archive ->
|
|
def candidates = [
|
|
"prefab/modules/SDL3/libs/android.${selectedAbi}/libSDL3.so",
|
|
"jni/${selectedAbi}/libSDL3.so",
|
|
]
|
|
def entry = candidates.collect { candidate -> archive.getEntry(candidate) }.find { candidate -> candidate != null }
|
|
if (entry == null) {
|
|
def suffix = "/libs/android.${selectedAbi}/libSDL3.so"
|
|
def fallback = Collections.list(archive.entries()).findAll { candidate ->
|
|
!candidate.directory && candidate.name.endsWith(suffix)
|
|
}
|
|
if (fallback.size() != 1) {
|
|
throw new GradleException(
|
|
"SDL3 shared library for ABI ${selectedAbi} is missing from ${aar}; " +
|
|
"expected Prefab member ${candidates[0]}"
|
|
)
|
|
}
|
|
entry = fallback[0]
|
|
}
|
|
sdl3Payload = archive.getInputStream(entry).withCloseable { stream -> stream.bytes }
|
|
}
|
|
|
|
def linkLibrary = new File(linkDirectory, "libSDL3.so")
|
|
def runtimeLibrary = new File(runtimeDirectory, "libSDL3.so")
|
|
linkLibrary.bytes = sdl3Payload
|
|
runtimeLibrary.bytes = sdl3Payload
|
|
|
|
def androidHome = System.getenv("ANDROID_HOME") ?: System.getenv("ANDROID_SDK_ROOT")
|
|
if (!androidHome) {
|
|
throw new GradleException("ANDROID_HOME or ANDROID_SDK_ROOT is required to resolve the project NDK")
|
|
}
|
|
def configuredNdk = new File(androidHome, "ndk/${ndkVersion.get()}")
|
|
if (!configuredNdk.isDirectory()) {
|
|
throw new GradleException("configured Android NDK is missing: ${configuredNdk}")
|
|
}
|
|
|
|
def inheritedRustFlags = System.getenv("RUSTFLAGS")?.trim()
|
|
def linkFlag = "-Lnative=${linkDirectory}"
|
|
def rustFlags = inheritedRustFlags ? "${inheritedRustFlags} ${linkFlag}" : linkFlag
|
|
|
|
def cargoCommand = [
|
|
"cargo",
|
|
"ndk",
|
|
"-t",
|
|
selectedAbi,
|
|
"-o",
|
|
output.absolutePath,
|
|
"build",
|
|
"-p",
|
|
"game-android-entrypoint",
|
|
]
|
|
if (releaseBuild.get()) {
|
|
cargoCommand.add("--release")
|
|
}
|
|
cargoCommand.addAll([
|
|
"--no-default-features",
|
|
"--features",
|
|
selectedFeature,
|
|
])
|
|
|
|
execOperations.exec { spec ->
|
|
spec.workingDir(repository)
|
|
spec.environment("ANDROID_NDK_HOME", configuredNdk.absolutePath)
|
|
spec.environment("CARGO_NDK_PLATFORM", androidApi.get().toString())
|
|
spec.environment("RUSTFLAGS", rustFlags)
|
|
spec.commandLine(cargoCommand)
|
|
}
|
|
|
|
def rustLibrary = new File(runtimeDirectory, "libgame_android_entrypoint.so")
|
|
if (!runtimeLibrary.isFile()) {
|
|
throw new GradleException("missing staged SDL3 Android library: ${runtimeLibrary}")
|
|
}
|
|
if (!rustLibrary.isFile()) {
|
|
throw new GradleException("missing Rust Android library after cargo-ndk build: ${rustLibrary}")
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!project.ext.has("sasedevRustGameFeature")) {
|
|
throw new GradleException("sasedevRustGameFeature must select exactly one Cargo game feature")
|
|
}
|
|
if (!project.ext.has("sasedevRustAndroidAbis")) {
|
|
throw new GradleException("sasedevRustAndroidAbis must declare at least one Android ABI")
|
|
}
|
|
if (!project.ext.has("sasedevRustAndroidApi")) {
|
|
throw new GradleException("sasedevRustAndroidApi must declare the Rust Android API level")
|
|
}
|
|
|
|
def selectedGameFeature = project.ext.sasedevRustGameFeature as String
|
|
def selectedAbis = (project.ext.sasedevRustAndroidAbis as List).collect { configuredAbi -> configuredAbi as String }
|
|
def selectedAndroidApi = project.ext.sasedevRustAndroidApi as Integer
|
|
def configuredSdl3AarName = providers.gradleProperty("sdl3AarName").get()
|
|
def configuredNdkVersion = providers.gradleProperty("androidNdkVersion").get()
|
|
def repositoryDirectory = rootProject.projectDir.parentFile
|
|
|
|
def supportedAbis = ["arm64-v8a", "armeabi-v7a", "x86_64", "x86"] as Set
|
|
if (selectedAbis.isEmpty()) {
|
|
throw new GradleException("sasedevRustAndroidAbis must not be empty")
|
|
}
|
|
if (selectedAbis.toSet().size() != selectedAbis.size()) {
|
|
throw new GradleException("sasedevRustAndroidAbis must not contain duplicates: ${selectedAbis}")
|
|
}
|
|
def unsupportedAbis = selectedAbis.findAll { configuredAbi -> !supportedAbis.contains(configuredAbi) }
|
|
if (!unsupportedAbis.isEmpty()) {
|
|
throw new GradleException("unsupported Rust Android ABI(s): ${unsupportedAbis.join(', ')}")
|
|
}
|
|
|
|
androidComponents {
|
|
onVariants(selector().all()) { variant ->
|
|
def selectedReleaseBuild = variant.buildType == "release"
|
|
selectedAbis.each { selectedAbi ->
|
|
def variantSuffix = variant.name.substring(0, 1).toUpperCase() + variant.name.substring(1)
|
|
def abiSuffix = selectedAbi
|
|
.split(/[^A-Za-z0-9]+/)
|
|
.findAll { segment -> !segment.isEmpty() }
|
|
.collect { segment -> segment.substring(0, 1).toUpperCase() + segment.substring(1) }
|
|
.join("")
|
|
def nativeTask = tasks.register(
|
|
"build${variantSuffix}SasedevRust${abiSuffix}",
|
|
BuildSasedevRustAndroidTask,
|
|
) {
|
|
sdl3Aar.set(rootProject.layout.projectDirectory.file("libs/${configuredSdl3AarName}"))
|
|
rustInputs.from(rootProject.file("../Cargo.toml"))
|
|
rustInputs.from(rootProject.file("../.cargo/config.toml"))
|
|
rustInputs.from(rootProject.fileTree("../crates") {
|
|
include "**/*.rs"
|
|
include "**/Cargo.toml"
|
|
include "**/build.rs"
|
|
})
|
|
gameFeature.set(selectedGameFeature)
|
|
abi.set(selectedAbi)
|
|
androidApi.set(selectedAndroidApi)
|
|
ndkVersion.set(configuredNdkVersion)
|
|
releaseBuild.set(selectedReleaseBuild)
|
|
repositoryRoot.set(repositoryDirectory)
|
|
outputDirectory.set(layout.buildDirectory.dir("generated/sasedevNative/${variant.name}/${selectedAbi}/jniLibs"))
|
|
}
|
|
|
|
if (variant.sources.jniLibs == null) {
|
|
throw new GradleException("AGP variant ${variant.name} does not expose jniLibs sources")
|
|
}
|
|
variant.sources.jniLibs.addGeneratedSourceDirectory(nativeTask) { task ->
|
|
task.outputDirectory
|
|
}
|
|
}
|
|
}
|
|
}
|