[sdks/android] DO NOT LAND - Build libmentat_ffi.so as part of Gradle build.

This uses a very rudimentary Gradle plugin, `rust-android-gradle`,
with custom fixes and extensions.  It works pretty well for what it
is!
This commit is contained in:
Nick Alexander 2018-07-18 15:05:44 -07:00
parent bf7f0ef6a4
commit 6898bac83d
11 changed files with 317 additions and 4 deletions

View file

@ -17,6 +17,7 @@ buildscript {
jcenter()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

View file

@ -0,0 +1,64 @@
buildscript {
repositories {
jcenter()
google()
}
ext.kotlin_version = '1.2.41'
dependencies {
classpath 'com.android.tools.build:gradle:3.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
repositories {
jcenter()
google()
}
apply plugin: "java-gradle-plugin"
// apply plugin: "maven-publish"
apply plugin: "kotlin"
gradlePlugin {
plugins {
simplePlugin {
id = "com.nishtahir.rust-android"
implementationClass = "com.nishtahir.RustAndroidPlugin"
}
}
}
// group 'com.nishtahir'
// version '0.0.2'
// publishing {
// repositories {
// maven {
// url "../samples/maven-repo"
// }
// }
// publications {
// maven(MavenPublication) {
// groupId 'com.nishtahir'
// artifactId 'rust-android'
// version '0.0.2'
// from components.java
// }
// }
// }
dependencies {
compileOnly gradleApi()
implementation "com.android.tools.build:gradle:3.1.2"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}

View file

@ -0,0 +1,55 @@
package com.nishtahir;
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.tasks.TaskAction
import java.io.File
open class CargoBuildTask : DefaultTask() {
@Suppress("unused")
@TaskAction
fun build() = with(project) {
extensions[CargoExtension::class].apply {
targets.forEach { target ->
val toolchain = toolchains.find { (arch) -> arch == target }
if (toolchain != null) {
buildProjectForTarget(project, toolchain, this)
val targetDirectory = targetDirectory ?: "${module}/target"
copy { spec ->
spec.into(File(buildDir, "rustJniLibs/${toolchain.folder}"))
spec.from(File(project.projectDir, "${targetDirectory}/${toolchain.target}/${profile}"))
spec.include(targetInclude)
}
} else {
println("No such target $target")
}
}
}
}
private fun buildProjectForTarget(project: Project, toolchain: Toolchain, cargoExtension: CargoExtension) {
project.exec { spec ->
val cc = "${project.getToolchainDirectory()}/${toolchain.cc()}"
val ar = "${project.getToolchainDirectory()}/${toolchain.ar()}"
println("using CC: $cc")
println("using AR: $ar")
with(spec) {
standardOutput = System.out
workingDir = File(project.project.projectDir, cargoExtension.module)
environment("CC", cc)
environment("AR", ar)
environment("RUSTFLAGS", "-C linker=$cc")
commandLine = listOf("cargo", "build", "--target=${toolchain.target}")
if (cargoExtension.profile != "debug") {
// Cargo is rigid: it accepts "--release" for release (and
// nothing for dev). This is a cheap way of allowing only
// two values.
commandLine.add("--${cargoExtension.profile}")
}
}
}.assertNormalExitValue()
}
}

View file

@ -0,0 +1,27 @@
package com.nishtahir
open class CargoExtension {
var module: String = ""
var targets: List<String> = emptyList()
/**
* The Cargo [release profile](https://doc.rust-lang.org/book/second-edition/ch14-01-release-profiles.html#customizing-builds-with-release-profiles) to build.
*
* Defaults to `"debug"`.
*/
var profile: String = "debug"
/**
* The target directory into Cargo which writes built outputs.
*
* Defaults to `${module}/target`.
*/
var targetDirectory: String? = null
/**
* Which Cargo built outputs to consider JNI libraries.
*
* Defaults to `"*.so"`.
*/
var targetInclude: String = "*.so"
}

View file

@ -0,0 +1,14 @@
package com.nishtahir
import org.gradle.api.Project
import org.gradle.api.plugins.ExtensionContainer
import java.io.File
import kotlin.reflect.KClass
const val TOOLS_FOLDER = ".cargo/toolchain"
operator fun <T : Any> ExtensionContainer.get(type: KClass<T>): T = getByType(type.java)
fun Project.getToolchainDirectory(): File = File(projectDir, TOOLS_FOLDER)

View file

@ -0,0 +1,45 @@
package com.nishtahir
import com.android.build.gradle.*
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.tasks.TaskAction
open class GenerateToolchainsTask : DefaultTask() {
@TaskAction
@Suppress("unused")
fun generateToolchainTask() {
project.plugins.all {
when (it) {
is AppPlugin -> congfigureTask<AppExtension>(project)
is LibraryPlugin -> congfigureTask<LibraryExtension>(project)
}
}
}
inline fun <reified T : BaseExtension> congfigureTask(project: Project) {
val app = project.extensions[T::class]
val minApi = app.defaultConfig.minSdkVersion.apiLevel
val ndkPath = app.ndkDirectory
if (project.getToolchainDirectory().exists()) {
println("Existing toolchain found.")
return
}
toolchains
.filterNot { (arch) -> minApi < 21 && arch.endsWith("64") }
.forEach { (arch) ->
project.exec { spec ->
spec.standardOutput = System.out
spec.errorOutput = System.out
spec.commandLine("$ndkPath/build/tools/make_standalone_toolchain.py")
spec.args("--arch=$arch", "--api=$minApi",
"--install-dir=${project.getToolchainDirectory()}/$arch",
"--force")
}
}
}
}

View file

@ -0,0 +1,88 @@
package com.nishtahir
import com.android.build.gradle.*
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.plugins.ide.idea.IdeaPlugin
import org.gradle.plugins.ide.idea.model.IdeaModel
import java.io.File
const val RUST_TASK_GROUP = "rust"
val toolchains = listOf(
Toolchain("arm",
"arm-linux-androideabi",
"bin/arm-linux-androideabi-clang",
"bin/arm-linux-androideabi-ar",
"armeabi"),
Toolchain("arm64",
"aarch64-linux-android",
"bin/aarch64-linux-android-clang",
"bin/aarch64-linux-android-ar",
"aarch64"),
Toolchain("mips",
"mipsel-linux-android",
"bin/mipsel-linux-android-clang",
"bin/mipsel-linux-android-ar",
"mips"),
Toolchain("x86",
"i686-linux-android",
"bin/i686-linux-android-clang",
"bin/i686-linux-android-ar",
"x86"),
Toolchain("x86_64",
"x86_64-linux-android",
"bin/x86_64-linux-android-clang",
"bin/x86_64-linux-android-ar",
"x86_64")
)
data class Toolchain(val platform: String,
val target: String,
val cc: String,
val ar: String,
val folder: String) {
fun cc(): String = "$platform/$cc"
fun ar(): String = "$platform/$ar"
}
@Suppress("unused")
open class RustAndroidPlugin : Plugin<Project> {
override fun apply(project: Project) {
with(project) {
extensions.add("cargo", CargoExtension::class.java)
afterEvaluate {
plugins.all {
when (it) {
is AppPlugin -> configurePlugin<AppExtension>(this)
is LibraryPlugin -> configurePlugin<LibraryExtension>(this)
}
}
}
}
}
private inline fun <reified T : BaseExtension> configurePlugin(project: Project) = with(project) {
extensions[T::class].apply {
sourceSets.getByName("main").jniLibs.srcDir(File("$buildDir/rustJniLibs/"))
}
val generateToolchain = tasks.maybeCreate("generateToolchains",
GenerateToolchainsTask::class.java).apply {
group = RUST_TASK_GROUP
description = "Generate standard toolchain for given architectures"
}
val buildTask = tasks.maybeCreate("cargoBuild",
CargoBuildTask::class.java).apply {
group = RUST_TASK_GROUP
description = "Build library"
}
buildTask.dependsOn(generateToolchain)
}
}

View file

@ -1,4 +1,6 @@
apply plugin: 'com.android.library'
apply plugin: 'com.nishtahir.rust-android'
apply plugin: 'kotlin-android'
apply plugin: 'com.jfrog.bintray'
// Simply applying this plugin gets bintray to publish a pom file.
@ -33,6 +35,14 @@ android {
}
}
cargo {
module = '../../../../ffi'
targetDirectory = '../../../../target'
targetInclude = 'libmentat_ffi.so'
targets = ['x86']
}
dependencies {
androidTestImplementation 'com.android.support:support-annotations:27.1.1'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
@ -41,10 +51,23 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'net.java.dev.jna:jna:4.5.1'
}
repositories {
mavenCentral()
}
afterEvaluate {
// The `cargoBuild` tasks isn't available until after evaluation.
android.libraryVariants.all { variant ->
def productFlavor = ""
variant.productFlavors.each {
productFlavor += "${it.name.capitalize()}"
}
def buildType = "${variant.buildType.name.capitalize()}"
tasks["generate${productFlavor}${buildType}Assets"].dependsOn(tasks["cargoBuild"])
}
}
// Publishing to jcenter/bintray.
def libGroupId = properties.libGroupId
def libRepoName = properties.libRepositoryName
@ -71,7 +94,6 @@ artifacts {
archives sourcesJar
}
group = libGroupId
archivesBaseName = libProjectName

View file

@ -1 +0,0 @@
../../../../../../../../target/aarch64-linux-android/release/libmentat_ffi.so

View file

@ -1 +0,0 @@
../../../../../../../../target/armv7-linux-androideabi/release/libmentat_ffi.so

View file

@ -1 +0,0 @@
../../../../../../../../target/i686-linux-android/release/libmentat_ffi.so