diff --git a/sdks/android/Mentat/build.gradle b/sdks/android/Mentat/build.gradle index ee3ebc49..8d115452 100644 --- a/sdks/android/Mentat/build.gradle +++ b/sdks/android/Mentat/build.gradle @@ -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" diff --git a/sdks/android/Mentat/buildSrc/build.gradle b/sdks/android/Mentat/buildSrc/build.gradle new file mode 100644 index 00000000..1d311b31 --- /dev/null +++ b/sdks/android/Mentat/buildSrc/build.gradle @@ -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" +} diff --git a/sdks/android/Mentat/buildSrc/src/main/kotlin/com/nishtahir/CargoBuildTask.kt b/sdks/android/Mentat/buildSrc/src/main/kotlin/com/nishtahir/CargoBuildTask.kt new file mode 100644 index 00000000..7dba6c5d --- /dev/null +++ b/sdks/android/Mentat/buildSrc/src/main/kotlin/com/nishtahir/CargoBuildTask.kt @@ -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() + } +} diff --git a/sdks/android/Mentat/buildSrc/src/main/kotlin/com/nishtahir/CargoExtension.kt b/sdks/android/Mentat/buildSrc/src/main/kotlin/com/nishtahir/CargoExtension.kt new file mode 100644 index 00000000..51f2f2e9 --- /dev/null +++ b/sdks/android/Mentat/buildSrc/src/main/kotlin/com/nishtahir/CargoExtension.kt @@ -0,0 +1,27 @@ +package com.nishtahir + +open class CargoExtension { + var module: String = "" + var targets: List = 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" +} diff --git a/sdks/android/Mentat/buildSrc/src/main/kotlin/com/nishtahir/Extensions.kt b/sdks/android/Mentat/buildSrc/src/main/kotlin/com/nishtahir/Extensions.kt new file mode 100644 index 00000000..a7fb5649 --- /dev/null +++ b/sdks/android/Mentat/buildSrc/src/main/kotlin/com/nishtahir/Extensions.kt @@ -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 ExtensionContainer.get(type: KClass): T = getByType(type.java) + + + +fun Project.getToolchainDirectory(): File = File(projectDir, TOOLS_FOLDER) diff --git a/sdks/android/Mentat/buildSrc/src/main/kotlin/com/nishtahir/GenerateToolchainsTask.kt b/sdks/android/Mentat/buildSrc/src/main/kotlin/com/nishtahir/GenerateToolchainsTask.kt new file mode 100644 index 00000000..63089eec --- /dev/null +++ b/sdks/android/Mentat/buildSrc/src/main/kotlin/com/nishtahir/GenerateToolchainsTask.kt @@ -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(project) + is LibraryPlugin -> congfigureTask(project) + } + } + } + + inline fun 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") + } + } + } + +} \ No newline at end of file diff --git a/sdks/android/Mentat/buildSrc/src/main/kotlin/com/nishtahir/RustAndroidPlugin.kt b/sdks/android/Mentat/buildSrc/src/main/kotlin/com/nishtahir/RustAndroidPlugin.kt new file mode 100644 index 00000000..df1d9540 --- /dev/null +++ b/sdks/android/Mentat/buildSrc/src/main/kotlin/com/nishtahir/RustAndroidPlugin.kt @@ -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 { + + override fun apply(project: Project) { + with(project) { + + extensions.add("cargo", CargoExtension::class.java) + + afterEvaluate { + plugins.all { + when (it) { + is AppPlugin -> configurePlugin(this) + is LibraryPlugin -> configurePlugin(this) + } + } + } + + } + } + + private inline fun 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) + } +} diff --git a/sdks/android/Mentat/library/build.gradle b/sdks/android/Mentat/library/build.gradle index 24632eef..9e0b7a63 100644 --- a/sdks/android/Mentat/library/build.gradle +++ b/sdks/android/Mentat/library/build.gradle @@ -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 diff --git a/sdks/android/Mentat/library/src/main/jniLibs/arm64/libmentat_ffi.so b/sdks/android/Mentat/library/src/main/jniLibs/arm64/libmentat_ffi.so deleted file mode 120000 index 192961bd..00000000 --- a/sdks/android/Mentat/library/src/main/jniLibs/arm64/libmentat_ffi.so +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../target/aarch64-linux-android/release/libmentat_ffi.so \ No newline at end of file diff --git a/sdks/android/Mentat/library/src/main/jniLibs/armeabi/libmentat_ffi.so b/sdks/android/Mentat/library/src/main/jniLibs/armeabi/libmentat_ffi.so deleted file mode 120000 index f248755f..00000000 --- a/sdks/android/Mentat/library/src/main/jniLibs/armeabi/libmentat_ffi.so +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../target/armv7-linux-androideabi/release/libmentat_ffi.so \ No newline at end of file diff --git a/sdks/android/Mentat/library/src/main/jniLibs/x86/libmentat_ffi.so b/sdks/android/Mentat/library/src/main/jniLibs/x86/libmentat_ffi.so deleted file mode 120000 index 2d4f93c2..00000000 --- a/sdks/android/Mentat/library/src/main/jniLibs/x86/libmentat_ffi.so +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../target/i686-linux-android/release/libmentat_ffi.so \ No newline at end of file