From de1b8f2ed8275ac5d35aee6d36619df3e437f847 Mon Sep 17 00:00:00 2001 From: Daniel Sommermann Date: Mon, 19 Jul 2021 08:05:10 -0700 Subject: [PATCH] Add per build type `cargoBuild${buildType}` tasks The goal of this change is to support explicitly building particular Cargo profiles per Android build type. This change makes it possible to build both release and debug tasks in a single gradle invocation without editing the use of the `cargo` extension. There are some backwards incompatible changes present: `profile` is deprecated and is replaced with the *required* map `buildTypeToProfile`. This map controls which `cargoBuild${buildType}` tasks are created and what Cargo profile is used for each. Once https://github.com/rust-lang/cargo/issues/6988 is resolved and stabilized, we should switch the implementation to use `cargo build --profile=$x` explicitly rather than `--release`. --- README.md | 31 +++++----- .../kotlin/com/nishtahir/CargoBuildTask.kt | 27 +++++--- .../kotlin/com/nishtahir/CargoExtension.kt | 2 +- .../kotlin/com/nishtahir/RustAndroidPlugin.kt | 61 +++++++++++-------- samples/app/build.gradle | 8 ++- samples/library/build.gradle | 8 ++- 6 files changed, 83 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index fa36265..5818353 100644 --- a/README.md +++ b/README.md @@ -53,15 +53,17 @@ rustup target add x86_64-pc-windows-msvc # for win32-x86-64-msvc ... ``` -Finally, run the `cargoBuild` task to cross compile: +Finally, run a `cargoBuild${buildType}` task (see `buildTypeToProfile`) to cross compile: ```sh -./gradlew cargoBuild +./gradlew cargoBuildDebug ``` Or add it as a dependency to one of your other build tasks, to build your rust code when you normally build your project: ```gradle tasks.whenTaskAdded { task -> - if ((task.name == 'javaPreCompileDebug' || task.name == 'javaPreCompileRelease')) { - task.dependsOn 'cargoBuild' + if (task.name == 'javaPreCompileDebug') + task.dependsOn 'cargoBuildDebug' + } else if (task.name == 'javaPreCompileRelease') { + task.dependsOn 'cargoBuildRelease' } } ``` @@ -99,11 +101,6 @@ which can be specified using the `apiLevel` option. This option defaults to the level. As of API level 21, 64-bit builds are possible; and conversely, the `arm64` and `x86_64` targets require `apiLevel >= 21`. -### Cargo release profile - -The `profile` option selects between the `--debug` and `--release` profiles in `cargo`. *Defaults -to `debug`!* - ### Extension reference ### module @@ -190,18 +187,24 @@ cargo { } ``` -### profile - -The Cargo [release profile](https://doc.rust-lang.org/book/second-edition/ch14-01-release-profiles.html#customizing-builds-with-release-profiles) to build. +### buildTypeToProfile -Defaults to `"debug"`. +This mandatory option specifies the Cargo [release profile](https://doc.rust-lang.org/book/second-edition/ch14-01-release-profiles.html#customizing-builds-with-release-profiles) to build per [Android build type](https://developer.android.com/studio/build/build-variants#build-types). +Each entry in the map causes a new `cargoBuild${buildType}` task to be created. ```groovy cargo { - profile = 'release' + buildTypeToProfile = [ + "debug": "debug", + "alpha": "debug", + "beta": "release", + "release": "release", + ] } ``` +The above example creates these targets: `cargoBuildDebug`, `cargoBuildAlpha`, `cargoBuildBeta`, `cargoBuildRelease`. + ### features Set the Cargo [features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section). diff --git a/plugin/src/main/kotlin/com/nishtahir/CargoBuildTask.kt b/plugin/src/main/kotlin/com/nishtahir/CargoBuildTask.kt index 4787ee7..88c9fb1 100644 --- a/plugin/src/main/kotlin/com/nishtahir/CargoBuildTask.kt +++ b/plugin/src/main/kotlin/com/nishtahir/CargoBuildTask.kt @@ -6,12 +6,16 @@ import org.gradle.api.DefaultTask import org.gradle.api.GradleException import org.gradle.api.Project import org.gradle.api.logging.LogLevel +import org.gradle.api.tasks.Input; import org.gradle.api.tasks.TaskAction import java.io.ByteArrayOutputStream import java.io.File open class CargoBuildTask : DefaultTask() { + @Input var toolchain: Toolchain? = null + @Input + var profile: String? = null @Suppress("unused") @@ -24,10 +28,15 @@ open class CargoBuildTask : DefaultTask() { throw GradleException("toolchain cannot be null") } + var profile = profile + if (profile == null) { + throw GradleException("profile cannot be null") + } + project.plugins.all { when (it) { - is AppPlugin -> buildProjectForTarget(project, toolchain, this) - is LibraryPlugin -> buildProjectForTarget(project, toolchain, this) + is AppPlugin -> buildProjectForTarget(project, toolchain, profile, this) + is LibraryPlugin -> buildProjectForTarget(project, toolchain, profile, this) } } // CARGO_TARGET_DIR can be used to force the use of a global, shared target directory @@ -68,7 +77,7 @@ open class CargoBuildTask : DefaultTask() { } } - inline fun buildProjectForTarget(project: Project, toolchain: Toolchain, cargoExtension: CargoExtension) { + inline fun buildProjectForTarget(project: Project, toolchain: Toolchain, profile: String, cargoExtension: CargoExtension) { val app = project.extensions[T::class] val apiLevel = cargoExtension.apiLevels[toolchain.platform]!! val defaultTargetTriple = getDefaultTargetTriple(project, cargoExtension.rustcCommand) @@ -120,12 +129,14 @@ open class CargoBuildTask : DefaultTask() { } } - 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. - theCommandLine.add("--${cargoExtension.profile}") + // TODO: When --profile is stabilized use it instead of --release + // https://github.com/rust-lang/cargo/issues/6988 + if (profile == "release") { + theCommandLine.add("--release") + } else if (profile != "dev") { + throw GradleException("Profile may only be 'dev' or 'release', got '${profile}'") } + if (toolchain.target != defaultTargetTriple) { // Only providing --target for the non-default targets means desktop builds // can share the build cache with `cargo build`/`cargo test`/etc invocations, diff --git a/plugin/src/main/kotlin/com/nishtahir/CargoExtension.kt b/plugin/src/main/kotlin/com/nishtahir/CargoExtension.kt index 791d314..e741e41 100644 --- a/plugin/src/main/kotlin/com/nishtahir/CargoExtension.kt +++ b/plugin/src/main/kotlin/com/nishtahir/CargoExtension.kt @@ -37,7 +37,7 @@ open class CargoExtension { var libname: String? = null var targets: List? = null var prebuiltToolchains: Boolean? = null - var profile: String = "debug" + var buildTypeToProfile: Map = mapOf() var verbose: Boolean? = null var targetDirectory: String? = null var targetIncludes: Array? = null diff --git a/plugin/src/main/kotlin/com/nishtahir/RustAndroidPlugin.kt b/plugin/src/main/kotlin/com/nishtahir/RustAndroidPlugin.kt index 461fcb8..2dd2192 100644 --- a/plugin/src/main/kotlin/com/nishtahir/RustAndroidPlugin.kt +++ b/plugin/src/main/kotlin/com/nishtahir/RustAndroidPlugin.kt @@ -172,6 +172,10 @@ open class RustAndroidPlugin : Plugin { throw GradleException("libname cannot be null") } + if (cargoExtension.buildTypeToProfile.isEmpty()) { + throw GradleException("buildTypeToProfile must have entries") + } + // Allow to set targets, including per-project, in local.properties. val localTargets: String? = cargoExtension.localProperties.getProperty("rust.targets.${project.name}") ?: @@ -256,38 +260,41 @@ open class RustAndroidPlugin : Plugin { includeEmptyDirs = false } - val buildTask = tasks.maybeCreate("cargoBuild", - DefaultTask::class.java).apply { - group = RUST_TASK_GROUP - description = "Build library (all targets)" - } + cargoExtension.buildTypeToProfile.forEach { buildType, theProfile -> + val buildTask = tasks.maybeCreate("cargoBuild${buildType.capitalize()}", + DefaultTask::class.java).apply { + group = RUST_TASK_GROUP + description = "Build library (all targets) for android build type ${buildType}" + } - cargoExtension.targets!!.forEach { target -> - val theToolchain = toolchains - .filter { - if (usePrebuilt) { - it.type != ToolchainType.ANDROID_GENERATED - } else { - it.type != ToolchainType.ANDROID_PREBUILT + cargoExtension.targets!!.forEach { target -> + val theToolchain = toolchains + .filter { + if (usePrebuilt) { + it.type != ToolchainType.ANDROID_GENERATED + } else { + it.type != ToolchainType.ANDROID_PREBUILT + } } - } - .find { it.platform == target } - if (theToolchain == null) { - throw GradleException("Target ${target} is not recognized (recognized targets: ${toolchains.map { it.platform }.sorted()}). Check `local.properties` and `build.gradle`.") - } + .find { it.platform == target } + if (theToolchain == null) { + throw GradleException("Target ${target} is not recognized (recognized targets: ${toolchains.map { it.platform }.sorted()}). Check `local.properties` and `build.gradle`.") + } - val targetBuildTask = tasks.maybeCreate("cargoBuild${target.capitalize()}", - CargoBuildTask::class.java).apply { - group = RUST_TASK_GROUP - description = "Build library ($target)" - toolchain = theToolchain - } + val targetBuildTask = tasks.maybeCreate("cargoBuild${target.capitalize()}${buildType.capitalize()}", + CargoBuildTask::class.java).apply { + group = RUST_TASK_GROUP + description = "Build library ($target) for android build type ${buildType}" + toolchain = theToolchain + profile = theProfile + } - if (!usePrebuilt) { - targetBuildTask.dependsOn(generateToolchain!!) + if (!usePrebuilt) { + targetBuildTask.dependsOn(generateToolchain!!) + } + targetBuildTask.dependsOn(generateLinkerWrapper) + buildTask.dependsOn(targetBuildTask) } - targetBuildTask.dependsOn(generateLinkerWrapper) - buildTask.dependsOn(targetBuildTask) } } } diff --git a/samples/app/build.gradle b/samples/app/build.gradle index 6317214..5dd76b6 100644 --- a/samples/app/build.gradle +++ b/samples/app/build.gradle @@ -37,6 +37,10 @@ cargo { module = "../rust" targets = ["arm", "x86", "x86_64", "arm64"] libname = "rust" + buildTypeToProfile = [ + "debug": "dev", + "release": "dev", + ] } repositories { @@ -54,13 +58,13 @@ dependencies { } afterEvaluate { - // The `cargoBuild` task isn't available until after evaluation. + // The `cargoBuild...` tasks aren't available until after evaluation. android.applicationVariants.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"]) + tasks["generate${productFlavor}${buildType}Assets"].dependsOn(tasks["cargoBuild${buildType}"]) } } diff --git a/samples/library/build.gradle b/samples/library/build.gradle index 97357ff..3a6e71f 100644 --- a/samples/library/build.gradle +++ b/samples/library/build.gradle @@ -42,6 +42,10 @@ cargo { "darwin", ] libname = "rust" + buildTypeToProfile = [ + "debug": "dev", + "release": "dev", + ] features { defaultAnd "foo", "bar" @@ -68,13 +72,13 @@ dependencies { } afterEvaluate { - // The `cargoBuild` task isn't available until after evaluation. + // The `cargoBuild...` tasks aren'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"]) + tasks["generate${productFlavor}${buildType}Assets"].dependsOn(tasks["cargoBuild${buildType}"]) } }