From 196f6566cbe966a5033dc4fa7c3f1a5bcce5aceb Mon Sep 17 00:00:00 2001 From: Mariusz Klochowicz Date: Tue, 27 Jan 2026 23:52:16 +1030 Subject: [PATCH] fix: add Gradle 9 compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gradle 9 removed several deprecated APIs that this plugin relied on. This commit updates the plugin to use the new APIs and modernizes the build configuration to support the changes. 1. Core API Changes (Gradle 9 removals) - Replace `fileMode = 493` with `filePermissions { unix("rwxr-xr-x") }` The CopySpec.fileMode property was deprecated in Gradle 8 and removed in Gradle 9. The new filePermissions API provides the same functionality. - Replace `project.exec {}` with injected `ExecOperations.exec {}` The Project.exec() method was deprecated in Gradle 7.5 and removed in Gradle 9. Tasks must now use service injection to obtain ExecOperations. This requires changing task classes from `open class` to `abstract class`. 2. Build Configuration Updates These changes are required because the new Gradle 9 APIs (filePermissions, ExecOperations) are only available in Gradle 8.3+, so we must compile against a newer Gradle version: - Gradle wrapper: 7.1.1 → 8.5 Required to compile against the new APIs (filePermissions added in 8.3) - Kotlin: 1.3.50 → 1.9.22 The old Kotlin Gradle plugin uses APIs removed in Gradle 8.x - AGP: 4.0.1 → 8.2.0 Required for Gradle 8.5+ compatibility - Java target: 1.8 → 17 Required by both Gradle 8.5+ and AGP 8.x - plugin-publish: 0.14.0 → 1.2.1 The old version uses the `pluginBundle {}` DSL which was removed. Metadata (website, vcsUrl, tags) now goes in `gradlePlugin {}` block. - Add Guava dependency Versions.groovy uses Guava collections which were previously provided transitively by AGP 4.x but are no longer included in AGP 8.x. - Add `null` branch to `when` expression in CargoBuildTask Kotlin 1.7+ requires exhaustive `when` for sealed classes with nullable types. 3. Test Matrix Updates - Updated supportedVersions to test AGP 8.x with Gradle 8.x - Updated jdkVersionFor() to use Java 17 for AGP 8.x Fixes #160 --- build.gradle | 6 ++- gradle/wrapper/gradle-wrapper.properties | 2 +- plugin/build.gradle | 46 +++++++++++-------- .../kotlin/com/nishtahir/CargoBuildTask.kt | 18 +++++--- .../com/nishtahir/GenerateToolchainsTask.kt | 8 +++- .../kotlin/com/nishtahir/RustAndroidPlugin.kt | 4 +- 6 files changed, 53 insertions(+), 31 deletions(-) diff --git a/build.gradle b/build.gradle index 3882b4de..02d12e97 100644 --- a/build.gradle +++ b/build.gradle @@ -2,8 +2,10 @@ buildscript { Properties versionProperties = new Properties() versionProperties.load(new FileInputStream("$project.rootDir/version.properties")) - ext.kotlin_version = '1.3.50' - ext.agp_version = '4.0.1' + // Kotlin 1.9.22: Required for Gradle 8.5+ (old Kotlin Gradle plugin uses removed APIs) + ext.kotlin_version = '1.9.22' + // AGP 8.2.0: Required for Gradle 8.5+ compatibility + ext.agp_version = '8.2.0' ext.plugin_version = versionProperties.getProperty("version") repositories { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 05679dc3..a5952066 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/plugin/build.gradle b/plugin/build.gradle index 2251d37e..ee80bbb7 100644 --- a/plugin/build.gradle +++ b/plugin/build.gradle @@ -2,8 +2,9 @@ import groovy.json.JsonBuilder import org.gradle.util.VersionNumber plugins { - id 'com.gradle.plugin-publish' version '0.14.0' - id "org.gradle.test-retry" version "1.2.0" + // plugin-publish 1.2.1: Required for Gradle 8.5+ (old version uses removed pluginBundle API) + id 'com.gradle.plugin-publish' version '1.2.1' + id "org.gradle.test-retry" version "1.5.8" } apply plugin: "java-gradle-plugin" @@ -12,12 +13,16 @@ apply plugin: "groovy" apply plugin: "kotlin" gradlePlugin { + // website/vcsUrl/tags moved here from pluginBundle (removed in plugin-publish 1.0+) + website = 'https://github.com/mozilla/rust-android-gradle' + vcsUrl = 'https://github.com/mozilla/rust-android-gradle.git' plugins { rustAndroidGradlePlugin { id = 'org.mozilla.rust-android-gradle.rust-android' implementationClass = 'com.nishtahir.RustAndroidPlugin' displayName = 'Plugin for building Rust with Cargo in Android projects' description = 'A plugin that helps build Rust JNI libraries with Cargo for use in Android projects.' + tags.set(['rust', 'cargo', 'android']) } } } @@ -28,14 +33,13 @@ version "$plugin_version" def isCI = (System.getenv('CI') ?: 'false').toBoolean() // Maps supported Android plugin versions to the versions of Gradle that support it +// Updated for Gradle 8.5+ compatibility (required for Gradle 9 support) def supportedVersions = [ - "7.0.0": ["7.1.1"], - "4.2.2": ["6.8.3", "7.1.1"], - "4.1.3": ["6.5.1", "6.8.3"], - "4.0.2": ["6.1.1", "6.8.3"], - "3.6.4": ["5.6.4", "6.8.3"], - "3.5.4": ["5.4.1", "5.6.4", "6.8.3"], - "3.1.2": ["4.10.2"] + "8.2.0": ["8.5"], + "8.3.0": ["8.5"], + "8.4.0": ["8.6"], + "8.5.0": ["8.7"], + "8.6.0": ["8.7"], ] // A local repo we publish our library to for testing in order to workaround limitations @@ -51,6 +55,8 @@ publishing { dependencies { implementation gradleApi() + // Guava: Required for Versions.groovy (was transitively provided by old AGP) + implementation "com.google.guava:guava:32.1.3-jre" compileOnly "com.android.tools.build:gradle:${agp_version}" testImplementation gradleTestKit() @@ -61,17 +67,17 @@ dependencies { testImplementation "org.junit.jupiter:junit-jupiter-api" } +// Java 17: Required for Gradle 8.5+ and AGP 8.x +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} + compileKotlin { - kotlinOptions.jvmTarget = "1.8" + kotlinOptions.jvmTarget = "17" } compileTestKotlin { - kotlinOptions.jvmTarget = "1.8" -} - -pluginBundle { - website = 'https://github.com/mozilla/rust-android-gradle' - vcsUrl = 'https://github.com/mozilla/rust-android-gradle.git' - tags = ['rust', 'cargo', 'android'] + kotlinOptions.jvmTarget = "17" } @@ -152,7 +158,9 @@ static def normalizeVersion(String version) { } static def jdkVersionFor(String version) { - def jdkVersion = VersionNumber.parse(version) > VersionNumber.parse("7.0.0-alpha01") ? 11 : 8 - + // AGP 8.x requires Java 17 + def jdkVersion = VersionNumber.parse(version) >= VersionNumber.parse("8.0.0") ? 17 : + VersionNumber.parse(version) > VersionNumber.parse("7.0.0-alpha01") ? 11 : 8 + return JavaLanguageVersion.of(jdkVersion) } diff --git a/plugin/src/main/kotlin/com/nishtahir/CargoBuildTask.kt b/plugin/src/main/kotlin/com/nishtahir/CargoBuildTask.kt index 363f0ec4..4831a2e8 100644 --- a/plugin/src/main/kotlin/com/nishtahir/CargoBuildTask.kt +++ b/plugin/src/main/kotlin/com/nishtahir/CargoBuildTask.kt @@ -8,10 +8,14 @@ import org.gradle.api.Project import org.gradle.api.logging.LogLevel import org.gradle.api.tasks.Input import org.gradle.api.tasks.TaskAction +import org.gradle.process.ExecOperations import java.io.ByteArrayOutputStream import java.io.File +import javax.inject.Inject -open class CargoBuildTask : DefaultTask() { +abstract class CargoBuildTask : DefaultTask() { + @get:Inject + abstract val execOperations: ExecOperations @Input var toolchain: Toolchain? = null @@ -48,7 +52,7 @@ open class CargoBuildTask : DefaultTask() { ?: targetDirectory ?: "${module!!}/target" - val defaultTargetTriple = getDefaultTargetTriple(project, rustcCommand) + val defaultTargetTriple = getDefaultTargetTriple(execOperations, project, rustcCommand) var cargoOutputDir = File(if (toolchain.target == defaultTargetTriple) { "${target}/${profile}" @@ -84,9 +88,9 @@ open class CargoBuildTask : DefaultTask() { inline fun buildProjectForTarget(project: Project, toolchain: Toolchain, ndk: Ndk, cargoExtension: CargoExtension) { val apiLevel = cargoExtension.apiLevels[toolchain.platform]!! - val defaultTargetTriple = getDefaultTargetTriple(project, cargoExtension.rustcCommand) + val defaultTargetTriple = getDefaultTargetTriple(execOperations, project, cargoExtension.rustcCommand) - project.exec { spec -> + execOperations.exec { spec -> with(spec) { standardOutput = System.out val module = File(cargoExtension.module!!) @@ -120,6 +124,7 @@ open class CargoBuildTask : DefaultTask() { // there's a way to specify them in the cargo command line -- rustc accepts // them if passed in directly with `--cfg`, and cargo will pass them to rustc // if you use them as default featureSpec. + // Kotlin 1.9+ requires exhaustive when for sealed classes with nullable types when (features) { is Features.All -> { theCommandLine.add("--all-features") @@ -137,6 +142,7 @@ open class CargoBuildTask : DefaultTask() { theCommandLine.add(features.featureSet.joinToString(" ")) } } + null -> { /* Use default features */ } } if (cargoExtension.profile != "debug") { @@ -249,9 +255,9 @@ open class CargoBuildTask : DefaultTask() { } // This can't be private/internal as it's called from `buildProjectForTarget`. -fun getDefaultTargetTriple(project: Project, rustc: String): String? { +fun getDefaultTargetTriple(execOperations: ExecOperations, project: Project, rustc: String): String? { val stdout = ByteArrayOutputStream() - val result = project.exec { spec -> + val result = execOperations.exec { spec -> spec.standardOutput = stdout spec.commandLine = listOf(rustc, "--version", "--verbose") } diff --git a/plugin/src/main/kotlin/com/nishtahir/GenerateToolchainsTask.kt b/plugin/src/main/kotlin/com/nishtahir/GenerateToolchainsTask.kt index a8d3e7d5..0b088aa9 100644 --- a/plugin/src/main/kotlin/com/nishtahir/GenerateToolchainsTask.kt +++ b/plugin/src/main/kotlin/com/nishtahir/GenerateToolchainsTask.kt @@ -7,8 +7,12 @@ import org.gradle.api.DefaultTask import org.gradle.api.GradleException import org.gradle.api.Project import org.gradle.api.tasks.TaskAction +import org.gradle.process.ExecOperations +import javax.inject.Inject -open class GenerateToolchainsTask : DefaultTask() { +abstract class GenerateToolchainsTask : DefaultTask() { + @get:Inject + abstract val execOperations: ExecOperations @TaskAction @Suppress("unused") @@ -44,7 +48,7 @@ open class GenerateToolchainsTask : DefaultTask() { // already. It is fast to do so and fixes any issues // with partially reclaimed temporary files. val dir = File(cargoExtension.toolchainDirectory, arch + "-" + apiLevel) - project.exec { spec -> + execOperations.exec { spec -> spec.standardOutput = System.out spec.errorOutput = System.out spec.commandLine(cargoExtension.pythonCommand) diff --git a/plugin/src/main/kotlin/com/nishtahir/RustAndroidPlugin.kt b/plugin/src/main/kotlin/com/nishtahir/RustAndroidPlugin.kt index 51662749..23273543 100644 --- a/plugin/src/main/kotlin/com/nishtahir/RustAndroidPlugin.kt +++ b/plugin/src/main/kotlin/com/nishtahir/RustAndroidPlugin.kt @@ -276,7 +276,9 @@ open class RustAndroidPlugin : Plugin { eachFile { it.path = it.path.replaceFirst("com/nishtahir", "") } - fileMode = 493 // 0755 in decimal; Kotlin doesn't have octal literals (!). + filePermissions { permissions -> + permissions.unix("rwxr-xr-x") // 0755 + } includeEmptyDirs = false duplicatesStrategy = DuplicatesStrategy.EXCLUDE }