├── example
├── app
│ ├── .gitignore
│ ├── src
│ │ ├── rust_library
│ │ │ ├── .gitignore
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ │ └── lib.rs
│ │ └── main
│ │ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── strings.xml
│ │ │ │ └── themes.xml
│ │ │ ├── values-land
│ │ │ │ └── dimens.xml
│ │ │ ├── values-w600dp
│ │ │ │ └── dimens.xml
│ │ │ ├── values-w1240dp
│ │ │ │ └── dimens.xml
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── menu
│ │ │ │ └── menu_main.xml
│ │ │ ├── xml
│ │ │ │ ├── backup_rules.xml
│ │ │ │ └── data_extraction_rules.xml
│ │ │ ├── values-night
│ │ │ │ └── themes.xml
│ │ │ ├── layout
│ │ │ │ ├── content_main.xml
│ │ │ │ ├── fragment_second.xml
│ │ │ │ ├── fragment_first.xml
│ │ │ │ └── activity_main.xml
│ │ │ ├── navigation
│ │ │ │ └── nav_graph.xml
│ │ │ ├── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ │ └── drawable
│ │ │ │ └── ic_launcher_background.xml
│ │ │ ├── java
│ │ │ └── dev
│ │ │ │ └── matrix
│ │ │ │ └── rust
│ │ │ │ └── MainActivity.kt
│ │ │ └── AndroidManifest.xml
│ ├── proguard-rules.pro
│ └── build.gradle.kts
├── gradle
│ ├── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
│ └── libs.versions.toml
├── .gitignore
├── build.gradle.kts
├── settings.gradle.kts
├── gradle.properties
├── gradlew.bat
└── gradlew
├── gradle
├── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
└── libs.versions.toml
├── .gitignore
├── src
└── main
│ └── kotlin
│ └── dev
│ └── matrix
│ └── agp
│ └── rust
│ ├── utils
│ ├── Logger.kt
│ ├── NullOutputStream.kt
│ ├── Os.kt
│ ├── RustBinaries.kt
│ ├── ProjectExt.kt
│ ├── SemanticVersion.kt
│ └── Abi.kt
│ ├── RustCleanTask.kt
│ ├── RustTestTask.kt
│ ├── AndroidRustExtension.kt
│ ├── RustBuildTask.kt
│ ├── RustInstaller.kt
│ └── AndroidRustPlugin.kt
├── LICENSE
├── gradlew.bat
├── README.md
└── gradlew
/example/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/example/app/src/rust_library/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /Cargo.lock
3 |
--------------------------------------------------------------------------------
/example/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 16dp
3 |
--------------------------------------------------------------------------------
/example/app/src/main/res/values-land/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 48dp
3 |
--------------------------------------------------------------------------------
/example/app/src/main/res/values-w600dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 48dp
3 |
--------------------------------------------------------------------------------
/example/app/src/main/res/values-w1240dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 200dp
3 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatrixDev/GradleAndroidRustPlugin/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/example/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatrixDev/GradleAndroidRustPlugin/HEAD/example/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.gradle
2 | /.idea/
3 | /build/
4 | /captures/
5 | /local.properties
6 |
7 | .DS_Store/
8 | .externalNativeBuild/
9 | .cxx/
10 | *.iml
11 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | /.gradle
2 | /.idea/
3 | /build/
4 | /captures/
5 | /local.properties
6 |
7 | .DS_Store/
8 | .externalNativeBuild/
9 | .cxx/
10 | *.iml
11 |
--------------------------------------------------------------------------------
/example/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatrixDev/GradleAndroidRustPlugin/HEAD/example/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/example/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatrixDev/GradleAndroidRustPlugin/HEAD/example/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/example/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatrixDev/GradleAndroidRustPlugin/HEAD/example/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/example/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatrixDev/GradleAndroidRustPlugin/HEAD/example/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/example/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatrixDev/GradleAndroidRustPlugin/HEAD/example/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/src/main/kotlin/dev/matrix/agp/rust/utils/Logger.kt:
--------------------------------------------------------------------------------
1 | package dev.matrix.agp.rust.utils
2 |
3 | internal fun log(message: String) {
4 | println("AndroidRust: $message")
5 | }
6 |
--------------------------------------------------------------------------------
/example/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatrixDev/GradleAndroidRustPlugin/HEAD/example/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/example/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatrixDev/GradleAndroidRustPlugin/HEAD/example/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/example/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatrixDev/GradleAndroidRustPlugin/HEAD/example/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/example/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatrixDev/GradleAndroidRustPlugin/HEAD/example/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/example/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatrixDev/GradleAndroidRustPlugin/HEAD/example/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/example/app/src/rust_library/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "rust_library"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [lib]
7 | crate-type = ["cdylib"]
8 |
9 | [dependencies]
10 | jni = "0.19.0"
11 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | agp = "8.7.2"
3 |
4 | [libraries]
5 | agp = { module = "com.android.tools.build:gradle", version.ref = "agp" }
6 | agp-api = { module = "com.android.tools.build:gradle-api", version.ref = "agp" }
7 |
--------------------------------------------------------------------------------
/src/main/kotlin/dev/matrix/agp/rust/utils/NullOutputStream.kt:
--------------------------------------------------------------------------------
1 | package dev.matrix.agp.rust.utils
2 |
3 | import java.io.OutputStream
4 |
5 | internal object NullOutputStream : OutputStream() {
6 | override fun write(value: Int) = Unit
7 | }
8 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Dec 04 11:21:35 EET 2024
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/example/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Dec 04 11:21:35 EET 2024
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/example/build.gradle.kts:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | plugins {
3 | alias(libs.plugins.android.application) apply false
4 | alias(libs.plugins.kotlin.android) apply false
5 | alias(libs.plugins.android.rust) apply false
6 | }
7 |
--------------------------------------------------------------------------------
/example/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/example/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/example/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/example/app/src/rust_library/src/lib.rs:
--------------------------------------------------------------------------------
1 | use jni::JNIEnv;
2 | use jni::objects::JObject;
3 | use jni::sys::jstring;
4 |
5 | #[no_mangle]
6 | extern "C" fn Java_dev_matrix_rust_MainActivity_callRustCode(env: JNIEnv, _: JObject) -> jstring {
7 | env.new_string("Hello from rust!").unwrap().into_inner()
8 | }
9 |
10 | #[cfg(test)]
11 | mod test {
12 | #[test]
13 | fn test() {
14 | println!("Hello from rust test!")
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/example/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/example/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/example/app/src/main/java/dev/matrix/rust/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package dev.matrix.rust
2 |
3 | import android.os.Bundle
4 | import android.widget.Toast
5 | import androidx.appcompat.app.AppCompatActivity
6 |
7 | class MainActivity : AppCompatActivity() {
8 | private external fun callRustCode(): String
9 |
10 | override fun onCreate(savedInstanceState: Bundle?) {
11 | super.onCreate(savedInstanceState)
12 |
13 | System.loadLibrary("rust_library")
14 |
15 | Toast.makeText(this, callRustCode(), Toast.LENGTH_LONG).show()
16 |
17 | finish()
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/example/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | GradlePlugin
3 | Settings
4 |
5 | First Fragment
6 | Second Fragment
7 | Next
8 | Previous
9 |
10 | Hello first fragment
11 | Hello second fragment. Arg: %1$s
12 |
--------------------------------------------------------------------------------
/src/main/kotlin/dev/matrix/agp/rust/RustCleanTask.kt:
--------------------------------------------------------------------------------
1 | package dev.matrix.agp.rust
2 |
3 | import org.gradle.api.DefaultTask
4 | import org.gradle.api.provider.Property
5 | import org.gradle.api.tasks.Input
6 | import org.gradle.api.tasks.TaskAction
7 | import java.io.File
8 |
9 | internal abstract class RustCleanTask : DefaultTask() {
10 | @get:Input
11 | abstract val variantJniLibsDirectory: Property
12 |
13 | @TaskAction
14 | fun taskAction() {
15 | val variantJniLibsDirectory = variantJniLibsDirectory.get()
16 |
17 | project.delete {
18 | delete(variantJniLibsDirectory)
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/example/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/example/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/example/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google {
4 | content {
5 | includeGroupByRegex("com\\.android.*")
6 | includeGroupByRegex("com\\.google.*")
7 | includeGroupByRegex("androidx.*")
8 | }
9 | }
10 | mavenCentral()
11 | gradlePluginPortal()
12 | }
13 | }
14 |
15 | dependencyResolutionManagement {
16 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
17 | repositories {
18 | mavenCentral()
19 | google()
20 | maven("https://plugins.gradle.org/m2/")
21 | }
22 | }
23 |
24 | rootProject.name = "android-rust-example"
25 | include(":app")
26 |
27 | includeBuild("..") {
28 | dependencySubstitution {
29 | substitute(module("io.github.MatrixDev.android-rust:plugin"))
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/example/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/src/main/kotlin/dev/matrix/agp/rust/utils/Os.kt:
--------------------------------------------------------------------------------
1 | package dev.matrix.agp.rust.utils
2 |
3 | import org.gradle.internal.os.OperatingSystem
4 |
5 | internal enum class Os {
6 | Linux,
7 | MacOs,
8 | Windows,
9 | Unknown;
10 |
11 | companion object {
12 | val current: Os
13 |
14 | init {
15 | val os = OperatingSystem.current()
16 | current = when {
17 | os.isLinux -> Linux
18 | os.isMacOsX -> MacOs
19 | os.isWindows -> Windows
20 | else -> Unknown
21 | }
22 | }
23 | }
24 |
25 | val isLinux: Boolean
26 | get() {
27 | return this == Linux
28 | }
29 |
30 | val isMacOs: Boolean
31 | get() {
32 | return this == MacOs
33 | }
34 |
35 | val isWindows: Boolean
36 | get() {
37 | return this == Windows
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/example/app/src/main/res/layout/content_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
19 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Rostyslav Lesovyi
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/example/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
15 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/example/app/src/main/res/navigation/nav_graph.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
17 |
18 |
23 |
24 |
27 |
28 |
--------------------------------------------------------------------------------
/example/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
17 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/example/app/src/main/res/layout/fragment_second.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
27 |
--------------------------------------------------------------------------------
/src/main/kotlin/dev/matrix/agp/rust/utils/RustBinaries.kt:
--------------------------------------------------------------------------------
1 | package dev.matrix.agp.rust.utils
2 |
3 | import org.gradle.api.Project
4 | import java.io.File
5 | import java.io.Serializable
6 | import java.util.Properties
7 |
8 | @Suppress("SpellCheckingInspection")
9 | internal data class RustBinaries(
10 | val cargo: String = "cargo",
11 | val rustc: String = "rustc",
12 | val rustup: String = "rustup",
13 | ) : Serializable {
14 | companion object {
15 | operator fun invoke(project: Project): RustBinaries {
16 | var path = RustBinaries()
17 | try {
18 | val file = project.rootProject.file("local.properties")
19 | val properties = Properties().also {
20 | it.load(file.inputStream())
21 | }
22 |
23 | val bin = File(properties.getProperty("cargo.bin").orEmpty())
24 | if (bin.exists()) {
25 | path = path.copy(
26 | cargo = File(bin, path.cargo).absolutePath,
27 | rustc = File(bin, path.rustc).absolutePath,
28 | rustup = File(bin, path.rustup).absolutePath,
29 | )
30 | }
31 | } catch (ignore: Exception) {
32 | }
33 | return path
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/example/app/src/main/res/layout/fragment_first.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
18 |
19 |
28 |
--------------------------------------------------------------------------------
/src/main/kotlin/dev/matrix/agp/rust/RustTestTask.kt:
--------------------------------------------------------------------------------
1 | package dev.matrix.agp.rust
2 |
3 | import dev.matrix.agp.rust.utils.RustBinaries
4 | import org.gradle.api.DefaultTask
5 | import org.gradle.api.provider.Property
6 | import org.gradle.api.tasks.Input
7 | import org.gradle.api.tasks.TaskAction
8 | import org.gradle.process.ExecOperations
9 | import java.io.File
10 | import javax.inject.Inject
11 |
12 | internal abstract class RustTestTask : DefaultTask() {
13 | @get:Inject
14 | abstract val execOperations: ExecOperations
15 |
16 | @get:Input
17 | abstract val rustBinaries: Property
18 |
19 | @get:Input
20 | abstract val rustProjectDirectory: Property
21 |
22 | @get:Input
23 | abstract val cargoTargetDirectory: Property
24 |
25 | @TaskAction
26 | fun taskAction() {
27 | val rustBinaries = rustBinaries.get()
28 | val rustProjectDirectory = rustProjectDirectory.get()
29 | val cargoTargetDirectory = cargoTargetDirectory.get()
30 |
31 | execOperations.exec {
32 | standardOutput = System.out
33 | errorOutput = System.out
34 | workingDir = rustProjectDirectory
35 |
36 | environment("CARGO_TARGET_DIR", cargoTargetDirectory.absolutePath)
37 |
38 | commandLine(rustBinaries.cargo)
39 |
40 | args("test")
41 | }.assertNormalExitValue()
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/example/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/src/main/kotlin/dev/matrix/agp/rust/utils/ProjectExt.kt:
--------------------------------------------------------------------------------
1 | package dev.matrix.agp.rust.utils
2 |
3 | import com.android.build.api.variant.ApplicationAndroidComponentsExtension
4 | import com.android.build.api.variant.LibraryAndroidComponentsExtension
5 | import com.android.build.gradle.AppExtension
6 | import com.android.build.gradle.AppPlugin
7 | import com.android.build.gradle.LibraryExtension
8 | import com.android.build.gradle.LibraryPlugin
9 | import org.gradle.api.Project
10 |
11 | internal fun Project.findAndroidPlugin() = plugins.asSequence()
12 | .mapNotNull { it as? AppPlugin ?: it as? LibraryPlugin }
13 | .firstOrNull()
14 |
15 | internal fun Project.getAndroidPlugin() = checkNotNull(findAndroidPlugin()) {
16 | "couldn't find android AppPlugin or LibraryPlugin"
17 | }
18 |
19 | internal fun Project.findAndroidExtension() = extensions.findByType(AppExtension::class.java)
20 | ?: extensions.findByType(LibraryExtension::class.java)
21 |
22 | internal fun Project.getAndroidExtension() = checkNotNull(findAndroidExtension()) {
23 | "couldn't find android AppExtension or LibraryExtension"
24 | }
25 |
26 | internal fun Project.findAndroidComponentsExtension() = when (val it = project.extensions.getByName("androidComponents")) {
27 | is LibraryAndroidComponentsExtension -> it
28 | is ApplicationAndroidComponentsExtension -> it
29 | else -> null
30 | }
31 |
32 | internal fun Project.getAndroidComponentsExtension() = checkNotNull(findAndroidComponentsExtension()) {
33 | "couldn't find android components extension"
34 | }
35 |
--------------------------------------------------------------------------------
/example/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
20 |
21 |
22 |
23 |
24 |
25 |
33 |
34 |
--------------------------------------------------------------------------------
/src/main/kotlin/dev/matrix/agp/rust/utils/SemanticVersion.kt:
--------------------------------------------------------------------------------
1 | package dev.matrix.agp.rust.utils
2 |
3 | import java.io.Serializable
4 |
5 | internal data class SemanticVersion(
6 | val major: Int,
7 | val minor: Int,
8 | val patch: Int,
9 | ) : Serializable, Comparable {
10 | val isValid: Boolean
11 | get() = major != 0 || minor != 0 || patch != 0
12 |
13 | override fun toString() = "$major.$minor.$patch"
14 |
15 | override fun compareTo(other: SemanticVersion): Int {
16 | var result = major.compareTo(other.major)
17 | if (result == 0) {
18 | result = minor.compareTo(other.minor)
19 | }
20 | if (result == 0) {
21 | result = patch.compareTo(other.patch)
22 | }
23 | return result
24 | }
25 | }
26 |
27 | internal fun SemanticVersion(version: String?): SemanticVersion {
28 | if (version.isNullOrEmpty()) {
29 | return SemanticVersion(0, 0, 0)
30 | }
31 |
32 | val parts = version.split(".")
33 | val major = requireNotNull(parts.getOrNull(0)?.toIntOrNull()) {
34 | "failed to parse 'major' part of the version from '$version'"
35 | }
36 | val minor = requireNotNull(parts.getOrNull(1)?.toIntOrNull()) {
37 | "failed to parse 'minor' part of the version from '$version'"
38 | }
39 | val patch = requireNotNull(parts.getOrNull(2)?.toIntOrNull()) {
40 | "failed to parse 'patch' part of the version from '$version'"
41 | }
42 |
43 | return SemanticVersion(
44 | major = major,
45 | minor = minor,
46 | patch = patch,
47 | )
48 | }
49 |
--------------------------------------------------------------------------------
/example/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.application)
3 | alias(libs.plugins.kotlin.android)
4 | alias(libs.plugins.android.rust)
5 | }
6 |
7 | android {
8 | namespace = "dev.matrix.rust"
9 | compileSdk = 33
10 | ndkVersion = "25.2.9519653"
11 |
12 | defaultConfig {
13 | applicationId = "dev.matrix.rust"
14 | minSdk = 21
15 | targetSdk = 33
16 | versionCode = 1
17 | versionName = "1.0"
18 | }
19 |
20 | buildTypes {
21 | release {
22 | isMinifyEnabled = false
23 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"))
24 | proguardFiles("proguard-rules.pro")
25 | }
26 | }
27 |
28 | sourceSets {
29 | getByName("main") {
30 | java.srcDir("src/rust_library")
31 | }
32 | }
33 |
34 | compileOptions {
35 | sourceCompatibility = JavaVersion.VERSION_1_8
36 | targetCompatibility = JavaVersion.VERSION_1_8
37 | }
38 |
39 | kotlinOptions {
40 | jvmTarget = "1.8"
41 | }
42 | }
43 |
44 | androidRust {
45 | module("library") {
46 | path = file("src/rust_library")
47 |
48 | buildType("release") {
49 | runTests = true
50 | }
51 | }
52 | minimumSupportedRustVersion = "1.91.1"
53 | }
54 |
55 | dependencies {
56 | implementation("androidx.core:core-ktx:1.9.0")
57 | implementation("androidx.appcompat:appcompat:1.6.1")
58 | implementation("androidx.constraintlayout:constraintlayout:2.1.4")
59 | implementation("androidx.navigation:navigation-fragment-ktx:2.5.3")
60 | implementation("androidx.navigation:navigation-ui-ktx:2.5.3")
61 | implementation("com.google.android.material:material:1.8.0")
62 | }
63 |
--------------------------------------------------------------------------------
/example/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/example/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | agp = "8.7.3"
3 | kotlin = "2.0.0"
4 | coreKtx = "1.15.0"
5 | junit = "4.13.2"
6 | junitVersion = "1.1.5"
7 | espressoCore = "3.5.1"
8 | lifecycleRuntimeKtx = "2.8.7"
9 | activityCompose = "1.9.3"
10 | composeBom = "2024.04.01"
11 |
12 | [libraries]
13 | androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
14 | junit = { group = "junit", name = "junit", version.ref = "junit" }
15 | androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
16 | androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
17 | androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
18 | androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
19 | androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
20 | androidx-ui = { group = "androidx.compose.ui", name = "ui" }
21 | androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
22 | androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
23 | androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
24 | androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
25 | androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
26 | androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
27 |
28 | [plugins]
29 | android-application = { id = "com.android.application", version.ref = "agp" }
30 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
31 | android-rust = { id = "io.github.MatrixDev.android-rust", version = "0.5.0" }
32 |
--------------------------------------------------------------------------------
/src/main/kotlin/dev/matrix/agp/rust/utils/Abi.kt:
--------------------------------------------------------------------------------
1 | package dev.matrix.agp.rust.utils
2 |
3 | import org.gradle.api.Project
4 |
5 | internal enum class Abi(
6 | val rustName: String,
7 | val androidName: String,
8 | val compilerTriple: String,
9 | val binUtilsTriple: String,
10 | val rustTargetTriple: String,
11 | ) {
12 | X86(
13 | rustName = "x86",
14 | androidName = "x86",
15 | compilerTriple = "i686-linux-android",
16 | binUtilsTriple = "i686-linux-android",
17 | rustTargetTriple = "i686-linux-android",
18 | ),
19 | X86_64(
20 | rustName = "x86_64",
21 | androidName = "x86_64",
22 | compilerTriple = "x86_64-linux-android",
23 | binUtilsTriple = "x86_64-linux-android",
24 | rustTargetTriple = "x86_64-linux-android",
25 | ),
26 | Arm(
27 | rustName = "arm",
28 | androidName = "armeabi-v7a",
29 | compilerTriple = "armv7a-linux-androideabi",
30 | binUtilsTriple = "arm-linux-androideabi",
31 | rustTargetTriple = "armv7-linux-androideabi",
32 | ),
33 | Arm64(
34 | rustName = "arm64",
35 | androidName = "arm64-v8a",
36 | compilerTriple = "aarch64-linux-android",
37 | binUtilsTriple = "aarch64-linux-android",
38 | rustTargetTriple = "aarch64-linux-android",
39 | );
40 |
41 | @Suppress("unused", "MemberVisibilityCanBePrivate")
42 | companion object {
43 | fun fromRustName(value: String) = values().find { it.rustName.equals(value, ignoreCase = true) }
44 | fun fromAndroidName(value: String) = values().find { it.androidName.equals(value, ignoreCase = true) }
45 |
46 | fun fromInjectedBuildAbi(project: Project): Set {
47 | val values = project.properties["android.injected.build.abi"] ?: return emptySet()
48 | return values.toString().split(",")
49 | .asSequence()
50 | .mapNotNull { fromAndroidName(it.trim()) }
51 | .toSet()
52 | }
53 |
54 | fun fromRustNames(names: Collection): Set {
55 | return names.asSequence()
56 | .map { requireNotNull(fromRustName(it)) { "unsupported abi version string: $it" } }
57 | .toSet()
58 | }
59 | }
60 |
61 | fun cc(apiLevel: Int) = when (Os.current.isWindows) {
62 | true -> "${compilerTriple}${apiLevel}-clang.cmd"
63 | else -> "${compilerTriple}${apiLevel}-clang"
64 | }
65 |
66 | fun ccx(apiLevel: Int) = when (Os.current.isWindows) {
67 | true -> "${compilerTriple}${apiLevel}-clang++.cmd"
68 | else -> "${compilerTriple}${apiLevel}-clang++"
69 | }
70 |
71 | fun ar(ndkVersionMajor: Int) = when (ndkVersionMajor >= 23) {
72 | true -> "llvm-ar"
73 | else -> "${binUtilsTriple}-ar"
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/example/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 | @rem SPDX-License-Identifier: Apache-2.0
17 | @rem
18 |
19 | @if "%DEBUG%"=="" @echo off
20 | @rem ##########################################################################
21 | @rem
22 | @rem Gradle startup script for Windows
23 | @rem
24 | @rem ##########################################################################
25 |
26 | @rem Set local scope for the variables with windows NT shell
27 | if "%OS%"=="Windows_NT" setlocal
28 |
29 | set DIRNAME=%~dp0
30 | if "%DIRNAME%"=="" set DIRNAME=.
31 | @rem This is normally unused
32 | set APP_BASE_NAME=%~n0
33 | set APP_HOME=%DIRNAME%
34 |
35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
37 |
38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
40 |
41 | @rem Find java.exe
42 | if defined JAVA_HOME goto findJavaFromJavaHome
43 |
44 | set JAVA_EXE=java.exe
45 | %JAVA_EXE% -version >NUL 2>&1
46 | if %ERRORLEVEL% equ 0 goto execute
47 |
48 | echo. 1>&2
49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
50 | echo. 1>&2
51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
52 | echo location of your Java installation. 1>&2
53 |
54 | goto fail
55 |
56 | :findJavaFromJavaHome
57 | set JAVA_HOME=%JAVA_HOME:"=%
58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
59 |
60 | if exist "%JAVA_EXE%" goto execute
61 |
62 | echo. 1>&2
63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
64 | echo. 1>&2
65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
66 | echo location of your Java installation. 1>&2
67 |
68 | goto fail
69 |
70 | :execute
71 | @rem Setup the command line
72 |
73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
74 |
75 |
76 | @rem Execute Gradle
77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
78 |
79 | :end
80 | @rem End local scope for the variables with windows NT shell
81 | if %ERRORLEVEL% equ 0 goto mainEnd
82 |
83 | :fail
84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
85 | rem the _cmd.exe /c_ return code!
86 | set EXIT_CODE=%ERRORLEVEL%
87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
89 | exit /b %EXIT_CODE%
90 |
91 | :mainEnd
92 | if "%OS%"=="Windows_NT" endlocal
93 |
94 | :omega
95 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AndroidRust Gradle Plugin
2 |
3 | This plugin helps with building Rust JNI libraries with Cargo for use in Android projects.
4 |
5 | Link to the plugin on the gradle repository:
6 | https://plugins.gradle.org/plugin/io.github.MatrixDev.android-rust
7 |
8 | # Usage
9 |
10 | Add dependencies to the root `build.gradle.kts` file
11 |
12 | ```kotlin
13 | buildscript {
14 | repositories {
15 | maven("https://plugins.gradle.org/m2/")
16 | }
17 |
18 | dependencies {
19 | classpath("io.github.MatrixDev.android-rust:plugin:0.5.0")
20 | }
21 | }
22 | ```
23 |
24 | Add plugin to the module's `build.gradle.kts` file
25 |
26 | ```kotlin
27 | plugins {
28 | id("io.github.MatrixDev.android-rust")
29 | }
30 | ```
31 |
32 | Add `androidRust` configuration
33 |
34 | ```kotlin
35 | androidRust {
36 | module("rust-library") {
37 | path = file("src/rust_library")
38 | }
39 | }
40 | ```
41 |
42 | # Additional configurations
43 |
44 | This is the list of some additional flags that can be configured:
45 |
46 | ```kotlin
47 | androidRust {
48 | // MSRV, plugin will update rust if installed version is lower than requested
49 | minimumSupportedRustVersion = "1.62.1"
50 |
51 | module("rust-library") {
52 | // path to your rust library
53 | path = file("src/rust_library")
54 |
55 | // default rust profile
56 | profile = "release"
57 |
58 | // default abi targets
59 | targets = listOf("arm", "arm64")
60 |
61 | // "debug" build type specific configuration
62 | buildType("debug") {
63 | // use "dev" profile in rust
64 | profile = "dev"
65 | }
66 |
67 | // "release" build type specific configuration
68 | buildType("release") {
69 | // run rust tests before build
70 | runTests = true
71 |
72 | // build all supported abi versions
73 | targets = listOf("arm", "arm64", "x86", "x86_64")
74 | }
75 | }
76 |
77 | // more than one library can be added
78 | module("additional-library") {
79 | // ...
80 | }
81 | }
82 | ```
83 |
84 |
85 | # Development support
86 | Plugin will check for a magic property `android.injected.build.abi` set by Android Studio when
87 | running application on device. This will limit ABI targets to only required by the device and
88 | should speedup development quite a bit.
89 |
90 | In theory this should behave the same as a built-in support for the NdkBuild / CMake.
91 |
92 |
93 | # Goals
94 | - Building multiple rust libraries with ease
95 | - Allow builds to be configurable for common scenarios
96 |
97 |
98 | # Non-goals
99 | - Supporting all Gradle versions
100 | - Allow builds to be configurable for exotic scenarios
101 |
102 |
103 | # IDE Enviroment PATH Workaround
104 | On some systems (notably MacOS) gradle task might fail to locate rust binaries. At this moment there are multiple issues/discussions for both gradle and IntelliJ IDEs.
105 |
106 | To solve this problem cargo path can be provided in `local.properties` file:
107 | ```properties
108 | sdk.dir=...
109 | cargo.bin=/Users/{user}/.cargo/bin/
110 | ```
111 |
--------------------------------------------------------------------------------
/src/main/kotlin/dev/matrix/agp/rust/AndroidRustExtension.kt:
--------------------------------------------------------------------------------
1 | package dev.matrix.agp.rust
2 |
3 | import java.io.File
4 |
5 | @DslMarker
6 | annotation class AndroidRustDslMarker
7 |
8 | @AndroidRustDslMarker
9 | @Suppress("unused")
10 | open class AndroidRustExtension : AndroidRustConfiguration() {
11 | /**
12 | * Specify minimum supported Rust version.
13 | *
14 | * Plugin will automatically use `rustup update` command to
15 | * update rust version in case installed versions is not high enough.
16 | */
17 | var minimumSupportedRustVersion = ""
18 |
19 | /**
20 | * Configuration map of all Rust libraries to build.
21 | *
22 | * Keys of this map are Rust crates names.
23 | */
24 | var modules = mutableMapOf()
25 |
26 | /**
27 | * Configure Rust module/library to build.
28 | *
29 | * @param name Rust crate name.
30 | */
31 | fun module(name: String, configure: AndroidRustModule.() -> Unit) {
32 | modules.getOrPut(name, ::AndroidRustModule).configure()
33 | }
34 | }
35 |
36 | @AndroidRustDslMarker
37 | @Suppress("unused")
38 | class AndroidRustModule : AndroidRustConfiguration() {
39 | /**
40 | * Path to the Rust project folder.
41 | *
42 | * This is the folder containing `Cargo.toml` file.
43 | */
44 | lateinit var path: File
45 |
46 | /**
47 | * All supported build type configurations.
48 | *
49 | * Keys of this map should correspond to the current project build variants.
50 | */
51 | var buildTypes = hashMapOf(
52 | "debug" to AndroidRustBuildType().also {
53 | it.profile = "dev"
54 | },
55 | "release" to AndroidRustBuildType().also {
56 | it.profile = "release"
57 | },
58 | )
59 |
60 | /**
61 | * Configure Rust build options.
62 | *
63 | * @param name current project build variant.
64 | */
65 | fun buildType(name: String, configure: AndroidRustBuildType.() -> Unit) {
66 | buildTypes.getOrPut(name, ::AndroidRustBuildType).configure()
67 | }
68 | }
69 |
70 | @AndroidRustDslMarker
71 | @Suppress("unused")
72 | class AndroidRustBuildType : AndroidRustConfiguration()
73 |
74 | @AndroidRustDslMarker
75 | @Suppress("unused")
76 | open class AndroidRustConfiguration {
77 | /**
78 | * Rust profile (dev, release, etc.).
79 | *
80 | * See: https://doc.rust-lang.org/cargo/reference/profiles.html
81 | */
82 | var profile = ""
83 |
84 | /**
85 | * List of ABIs to build.
86 | */
87 | var targets = listOf()
88 |
89 | /**
90 | * Run tests after the build.
91 | *
92 | * This will run `cargo test` command and check its result.
93 | */
94 | var runTests: Boolean? = null
95 |
96 | /**
97 | * Disable IDE ABI injection optimization.
98 | * - When `true`, all requested ABIs will be built regardless of IDE deployment target.
99 | * - When `false` (default), only the IDE target ABI will be built to speed up development builds.
100 | *
101 | * Set to `true` if you experience "library not found" errors when running from Android Studio.
102 | * See: https://github.com/MatrixDev/GradleAndroidRustPlugin/issues/3
103 | */
104 | var disableAbiOptimization: Boolean? = null
105 | }
106 |
--------------------------------------------------------------------------------
/src/main/kotlin/dev/matrix/agp/rust/RustBuildTask.kt:
--------------------------------------------------------------------------------
1 | package dev.matrix.agp.rust
2 |
3 | import dev.matrix.agp.rust.utils.Abi
4 | import dev.matrix.agp.rust.utils.Os
5 | import dev.matrix.agp.rust.utils.RustBinaries
6 | import dev.matrix.agp.rust.utils.SemanticVersion
7 | import org.gradle.api.DefaultTask
8 | import org.gradle.api.provider.Property
9 | import org.gradle.api.tasks.Input
10 | import org.gradle.api.tasks.TaskAction
11 | import org.gradle.process.ExecOperations
12 | import java.io.File
13 | import javax.inject.Inject
14 |
15 | internal abstract class RustBuildTask : DefaultTask() {
16 | @get:Inject
17 | abstract val execOperations: ExecOperations
18 |
19 | @get:Input
20 | abstract val rustBinaries: Property
21 |
22 | @get:Input
23 | abstract val abi: Property
24 |
25 | @get:Input
26 | abstract val apiLevel: Property
27 |
28 | @get:Input
29 | abstract val ndkVersion: Property
30 |
31 | @get:Input
32 | abstract val ndkDirectory: Property
33 |
34 | @get:Input
35 | abstract val rustProfile: Property
36 |
37 | @get:Input
38 | abstract val rustProjectDirectory: Property
39 |
40 | @get:Input
41 | abstract val cargoTargetDirectory: Property
42 |
43 | @get:Input
44 | abstract val variantJniLibsDirectory: Property
45 |
46 | @TaskAction
47 | fun taskAction() {
48 | val rustBinaries = rustBinaries.get()
49 | val abi = abi.get()
50 | val apiLevel = apiLevel.get()
51 | val ndkVersion = ndkVersion.get()
52 | val ndkDirectory = ndkDirectory.get()
53 | val rustProfile = rustProfile.get()
54 | val rustProjectDirectory = rustProjectDirectory.get()
55 | val cargoTargetDirectory = cargoTargetDirectory.get()
56 | val variantJniLibsDirectory = variantJniLibsDirectory.get()
57 |
58 | val platform = when (Os.current) {
59 | Os.Linux -> "linux-x86_64"
60 | Os.MacOs -> "darwin-x86_64"
61 | Os.Windows -> "windows-x86_64"
62 | Os.Unknown -> throw Exception("OS is not supported")
63 | }
64 |
65 | val toolchainFolder = File(ndkDirectory, "toolchains/llvm/prebuilt/$platform/bin")
66 | val cc = File(toolchainFolder, abi.cc(apiLevel))
67 | val cxx = File(toolchainFolder, abi.ccx(apiLevel))
68 | val ar = File(toolchainFolder, abi.ar(ndkVersion.major))
69 |
70 | val cargoTargetTriplet = abi.rustTargetTriple
71 | .replace('-', '_')
72 | .uppercase()
73 |
74 | execOperations.exec {
75 | standardOutput = System.out
76 | errorOutput = System.out
77 | workingDir = rustProjectDirectory
78 |
79 | environment("CC_${abi.rustTargetTriple}", cc)
80 | environment("CXX_${abi.rustTargetTriple}", cxx)
81 | environment("AR_${abi.rustTargetTriple}", ar)
82 | environment("CARGO_TARGET_DIR", cargoTargetDirectory.absolutePath)
83 | environment("CARGO_TARGET_${cargoTargetTriplet}_LINKER", cc)
84 |
85 | commandLine(rustBinaries.cargo)
86 |
87 | args("build")
88 | args("--lib")
89 | args("--target", abi.rustTargetTriple)
90 |
91 | if (rustProfile.isNotEmpty()) {
92 | args("--profile", rustProfile)
93 | }
94 | }.assertNormalExitValue()
95 |
96 | project.copy {
97 | val dir = when (rustProfile == "dev") {
98 | true -> "debug"
99 | else -> rustProfile
100 | }
101 | include("*.so")
102 | from(File(cargoTargetDirectory, "${abi.rustTargetTriple}/${dir}/"))
103 | into(File(variantJniLibsDirectory, abi.androidName))
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/main/kotlin/dev/matrix/agp/rust/RustInstaller.kt:
--------------------------------------------------------------------------------
1 | package dev.matrix.agp.rust
2 |
3 | import dev.matrix.agp.rust.utils.Abi
4 | import dev.matrix.agp.rust.utils.NullOutputStream
5 | import dev.matrix.agp.rust.utils.Os
6 | import dev.matrix.agp.rust.utils.RustBinaries
7 | import dev.matrix.agp.rust.utils.SemanticVersion
8 | import dev.matrix.agp.rust.utils.log
9 | import org.gradle.process.ExecOperations
10 | import java.io.ByteArrayOutputStream
11 |
12 | internal fun installRustComponentsIfNeeded(
13 | execOperations: ExecOperations,
14 | minimalVersion: SemanticVersion?,
15 | abiSet: Collection,
16 | rustBinaries: RustBinaries,
17 | ) {
18 | if (Os.current.isWindows) {
19 | return
20 | }
21 |
22 | if (minimalVersion != null && minimalVersion.isValid) {
23 | val actualVersion = readRustCompilerVersion(execOperations, rustBinaries)
24 | if (actualVersion < minimalVersion) {
25 | installRustUp(execOperations, rustBinaries)
26 | updateRust(execOperations, rustBinaries)
27 | }
28 | }
29 |
30 | if (abiSet.isNotEmpty()) {
31 | installRustUp(execOperations, rustBinaries)
32 |
33 | val installedAbiSet = readRustUpInstalledTargets(execOperations, rustBinaries)
34 | for (abi in abiSet) {
35 | if (installedAbiSet.contains(abi)) {
36 | continue
37 | }
38 | installRustTarget(execOperations, abi, rustBinaries)
39 | }
40 | }
41 | }
42 |
43 | private fun installRustUp(execOperations: ExecOperations, rustBinaries: RustBinaries) {
44 | try {
45 | val result = execOperations.exec {
46 | standardOutput = NullOutputStream
47 | errorOutput = NullOutputStream
48 | executable(rustBinaries.rustup)
49 | args("-V")
50 | }
51 |
52 | if (result.exitValue == 0) {
53 | return
54 | }
55 | } catch (_: Exception) {
56 | }
57 |
58 | log("installing rustup")
59 |
60 | execOperations.exec {
61 | standardOutput = NullOutputStream
62 | errorOutput = NullOutputStream
63 | commandLine("bash", "-c", "\"curl\" --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y")
64 | }.assertNormalExitValue()
65 | }
66 |
67 | private fun updateRust(execOperations: ExecOperations, rustBinaries: RustBinaries) {
68 | log("updating rust version")
69 |
70 | execOperations.exec {
71 | standardOutput = NullOutputStream
72 | errorOutput = NullOutputStream
73 | executable(rustBinaries.rustup)
74 | args("update")
75 | }.assertNormalExitValue()
76 | }
77 |
78 | private fun installRustTarget(execOperations: ExecOperations, abi: Abi, rustBinaries: RustBinaries) {
79 | log("installing rust target $abi (${abi.rustTargetTriple})")
80 |
81 | execOperations.exec {
82 | standardOutput = NullOutputStream
83 | errorOutput = NullOutputStream
84 | executable(rustBinaries.rustup)
85 | args("target", "add", abi.rustTargetTriple)
86 | }.assertNormalExitValue()
87 | }
88 |
89 | private fun readRustCompilerVersion(execOperations: ExecOperations, rustBinaries: RustBinaries): SemanticVersion {
90 | val output = ByteArrayOutputStream()
91 | execOperations.exec {
92 | standardOutput = output
93 | errorOutput = NullOutputStream
94 | executable(rustBinaries.rustc)
95 | args("--version")
96 | }.assertNormalExitValue()
97 |
98 | val outputText = String(output.toByteArray())
99 | val regex = Regex("^rustc (\\d+\\.\\d+\\.\\d+)(-nightly)? .*$", RegexOption.DOT_MATCHES_ALL)
100 | val match = checkNotNull(regex.matchEntire(outputText)) {
101 | "failed to parse rust compiler version: $outputText"
102 | }
103 |
104 | return SemanticVersion(match.groupValues[1])
105 | }
106 |
107 | private fun readRustUpInstalledTargets(execOperations: ExecOperations, rustBinaries: RustBinaries): Set {
108 | val output = ByteArrayOutputStream()
109 | execOperations.exec {
110 | standardOutput = output
111 | errorOutput = NullOutputStream
112 | executable(rustBinaries.rustup)
113 | args("target", "list")
114 | }.assertNormalExitValue()
115 |
116 | val regex = Regex("^(\\S+) \\(installed\\)$", RegexOption.MULTILINE)
117 | return regex.findAll(String(output.toByteArray()))
118 | .mapNotNull { target ->
119 | Abi.values().find { it.rustTargetTriple == target.groupValues[1] }
120 | }
121 | .toSet()
122 | }
123 |
--------------------------------------------------------------------------------
/example/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/example/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/src/main/kotlin/dev/matrix/agp/rust/AndroidRustPlugin.kt:
--------------------------------------------------------------------------------
1 | package dev.matrix.agp.rust
2 |
3 | import com.android.build.gradle.internal.tasks.factory.dependsOn
4 | import dev.matrix.agp.rust.utils.Abi
5 | import dev.matrix.agp.rust.utils.RustBinaries
6 | import dev.matrix.agp.rust.utils.SemanticVersion
7 | import dev.matrix.agp.rust.utils.getAndroidComponentsExtension
8 | import dev.matrix.agp.rust.utils.getAndroidExtension
9 | import org.gradle.api.Plugin
10 | import org.gradle.api.Project
11 | import org.gradle.api.tasks.TaskProvider
12 | import org.gradle.process.ExecOperations
13 | import java.io.File
14 | import java.util.Locale
15 | import javax.inject.Inject
16 |
17 | //
18 | // TODO: migrate to variant API with artifacts when JNI will be supported
19 | // https://developer.android.com/studio/build/extend-agp#access-modify-artifacts
20 | //
21 | @Suppress("unused")
22 | abstract class AndroidRustPlugin @Inject constructor(
23 | private val execOperations: ExecOperations,
24 | ) : Plugin {
25 | override fun apply(project: Project) {
26 | val rustBinaries = RustBinaries(project)
27 | val extension = project.extensions.create("androidRust", AndroidRustExtension::class.java)
28 | val androidExtension = project.getAndroidExtension()
29 | val androidComponents = project.getAndroidComponentsExtension()
30 | val tasksByBuildType = HashMap>>()
31 |
32 | androidComponents.finalizeDsl { dsl ->
33 | val allRustAbiSet = mutableSetOf()
34 | val ndkDirectory = androidExtension.ndkDirectory
35 | val ndkVersion = SemanticVersion(androidExtension.ndkVersion)
36 | val extensionBuildDirectory = project.layout.buildDirectory.dir("intermediates/rust").get().asFile
37 |
38 | for (buildType in dsl.buildTypes) {
39 | val buildTypeNameCap = buildType.name.replaceFirstChar(Char::titlecase)
40 |
41 | val variantBuildDirectory = File(extensionBuildDirectory, buildType.name)
42 | val variantJniLibsDirectory = File(variantBuildDirectory, "jniLibs")
43 |
44 | val cleanTaskName = "clean${buildTypeNameCap}RustJniLibs"
45 | val cleanTask = project.tasks.register(cleanTaskName, RustCleanTask::class.java) {
46 | this.variantJniLibsDirectory.set(variantJniLibsDirectory)
47 | }
48 |
49 | for ((moduleName, module) in extension.modules) {
50 | val moduleNameCap = moduleName.replaceFirstChar(Char::titlecase)
51 | val moduleBuildDirectory = File(variantBuildDirectory, "lib_$moduleName")
52 |
53 | val rustBuildType = module.buildTypes[buildType.name]
54 | val rustConfiguration = mergeRustConfigurations(rustBuildType, module, extension)
55 |
56 | val testTask = when (rustConfiguration.runTests) {
57 | true -> {
58 | val testTaskName = "test${moduleNameCap}Rust"
59 | project.tasks.register(testTaskName, RustTestTask::class.java) {
60 | this.rustBinaries.set(rustBinaries)
61 | this.rustProjectDirectory.set(module.path)
62 | this.cargoTargetDirectory.set(moduleBuildDirectory)
63 | }.dependsOn(cleanTask)
64 | }
65 |
66 | else -> null
67 | }
68 |
69 | val rustAbiSet = resolveAbiList(project, rustConfiguration)
70 | allRustAbiSet.addAll(rustAbiSet)
71 |
72 | for (rustAbi in rustAbiSet) {
73 | val buildTaskName = "build${buildTypeNameCap}${moduleNameCap}Rust[${rustAbi.androidName}]"
74 | val buildTask = project.tasks.register(buildTaskName, RustBuildTask::class.java) {
75 | this.rustBinaries.set(rustBinaries)
76 | this.abi.set(rustAbi)
77 | this.apiLevel.set(dsl.defaultConfig.minSdk ?: 21)
78 | this.ndkVersion.set(ndkVersion)
79 | this.ndkDirectory.set(ndkDirectory)
80 | this.rustProfile.set(rustConfiguration.profile)
81 | this.rustProjectDirectory.set(module.path)
82 | this.cargoTargetDirectory.set(moduleBuildDirectory)
83 | this.variantJniLibsDirectory.set(variantJniLibsDirectory)
84 | }
85 | buildTask.dependsOn(testTask ?: cleanTask)
86 | tasksByBuildType.getOrPut(buildType.name, ::ArrayList).add(buildTask)
87 | }
88 | }
89 |
90 | dsl.sourceSets.findByName(buildType.name)?.jniLibs?.srcDir(variantJniLibsDirectory)
91 | }
92 |
93 | val minimumSupportedRustVersion = SemanticVersion(extension.minimumSupportedRustVersion)
94 | installRustComponentsIfNeeded(
95 | execOperations,
96 | minimumSupportedRustVersion,
97 | allRustAbiSet,
98 | rustBinaries
99 | )
100 | }
101 |
102 | androidComponents.onVariants { variant ->
103 | val tasks = tasksByBuildType[variant.buildType] ?: return@onVariants
104 | val variantName = variant.name.replaceFirstChar(Char::titlecase)
105 |
106 | project.afterEvaluate {
107 | val parentTask = project.tasks.getByName("pre${variantName}Build")
108 | for (task in tasks) {
109 | parentTask.dependsOn(task)
110 | }
111 | }
112 | }
113 | }
114 |
115 | private fun resolveAbiList(project: Project, config: AndroidRustConfiguration): Collection {
116 | val requestedAbi = Abi.fromRustNames(config.targets)
117 |
118 | // If optimization is disabled, build all requested ABIs
119 | if (config.disableAbiOptimization == true) {
120 | return requestedAbi
121 | }
122 |
123 | // Otherwise, use IDE ABI injection optimization for faster development builds
124 | val injectedAbi = Abi.fromInjectedBuildAbi(project)
125 | if (injectedAbi.isEmpty()) {
126 | return requestedAbi
127 | }
128 |
129 | val intersectionAbi = requestedAbi.intersect(injectedAbi)
130 | check(intersectionAbi.isNotEmpty()) {
131 | "ABIs requested by IDE ($injectedAbi) are not supported by the build config (${config.targets})"
132 | }
133 |
134 | // Return all intersecting ABIs, not just one
135 | // The original logic only returned a single ABI which caused deployment issues
136 | return intersectionAbi.toList()
137 | }
138 |
139 | /**
140 | * Merge configurations with the first one having the highest priority
141 | */
142 | private fun mergeRustConfigurations(vararg configurations: AndroidRustConfiguration?): AndroidRustConfiguration {
143 | val defaultConfiguration = AndroidRustConfiguration().also {
144 | it.profile = "release"
145 | it.targets = Abi.values().mapTo(ArrayList(), Abi::rustName)
146 | it.runTests = null
147 | it.disableAbiOptimization = null
148 | }
149 |
150 | return configurations.asSequence()
151 | .filterNotNull()
152 | .plus(defaultConfiguration)
153 | .reduce { result, base ->
154 | if (result.profile.isEmpty()) {
155 | result.profile = base.profile
156 | }
157 | if (result.targets.isEmpty()) {
158 | result.targets = base.targets
159 | }
160 | if (result.runTests == null) {
161 | result.runTests = base.runTests
162 | }
163 | if (result.disableAbiOptimization == null) {
164 | result.disableAbiOptimization = base.disableAbiOptimization
165 | }
166 | result
167 | }
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 | # SPDX-License-Identifier: Apache-2.0
19 | #
20 |
21 | ##############################################################################
22 | #
23 | # Gradle start up script for POSIX generated by Gradle.
24 | #
25 | # Important for running:
26 | #
27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
28 | # noncompliant, but you have some other compliant shell such as ksh or
29 | # bash, then to run this script, type that shell name before the whole
30 | # command line, like:
31 | #
32 | # ksh Gradle
33 | #
34 | # Busybox and similar reduced shells will NOT work, because this script
35 | # requires all of these POSIX shell features:
36 | # * functions;
37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
39 | # * compound commands having a testable exit status, especially «case»;
40 | # * various built-in commands including «command», «set», and «ulimit».
41 | #
42 | # Important for patching:
43 | #
44 | # (2) This script targets any POSIX shell, so it avoids extensions provided
45 | # by Bash, Ksh, etc; in particular arrays are avoided.
46 | #
47 | # The "traditional" practice of packing multiple parameters into a
48 | # space-separated string is a well documented source of bugs and security
49 | # problems, so this is (mostly) avoided, by progressively accumulating
50 | # options in "$@", and eventually passing that to Java.
51 | #
52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
54 | # see the in-line comments for details.
55 | #
56 | # There are tweaks for specific operating systems such as AIX, CygWin,
57 | # Darwin, MinGW, and NonStop.
58 | #
59 | # (3) This script is generated from the Groovy template
60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
61 | # within the Gradle project.
62 | #
63 | # You can find Gradle at https://github.com/gradle/gradle/.
64 | #
65 | ##############################################################################
66 |
67 | # Attempt to set APP_HOME
68 |
69 | # Resolve links: $0 may be a link
70 | app_path=$0
71 |
72 | # Need this for daisy-chained symlinks.
73 | while
74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
75 | [ -h "$app_path" ]
76 | do
77 | ls=$( ls -ld "$app_path" )
78 | link=${ls#*' -> '}
79 | case $link in #(
80 | /*) app_path=$link ;; #(
81 | *) app_path=$APP_HOME$link ;;
82 | esac
83 | done
84 |
85 | # This is normally unused
86 | # shellcheck disable=SC2034
87 | APP_BASE_NAME=${0##*/}
88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
90 | ' "$PWD" ) || exit
91 |
92 | # Use the maximum available, or set MAX_FD != -1 to use that value.
93 | MAX_FD=maximum
94 |
95 | warn () {
96 | echo "$*"
97 | } >&2
98 |
99 | die () {
100 | echo
101 | echo "$*"
102 | echo
103 | exit 1
104 | } >&2
105 |
106 | # OS specific support (must be 'true' or 'false').
107 | cygwin=false
108 | msys=false
109 | darwin=false
110 | nonstop=false
111 | case "$( uname )" in #(
112 | CYGWIN* ) cygwin=true ;; #(
113 | Darwin* ) darwin=true ;; #(
114 | MSYS* | MINGW* ) msys=true ;; #(
115 | NONSTOP* ) nonstop=true ;;
116 | esac
117 |
118 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
119 |
120 |
121 | # Determine the Java command to use to start the JVM.
122 | if [ -n "$JAVA_HOME" ] ; then
123 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
124 | # IBM's JDK on AIX uses strange locations for the executables
125 | JAVACMD=$JAVA_HOME/jre/sh/java
126 | else
127 | JAVACMD=$JAVA_HOME/bin/java
128 | fi
129 | if [ ! -x "$JAVACMD" ] ; then
130 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
131 |
132 | Please set the JAVA_HOME variable in your environment to match the
133 | location of your Java installation."
134 | fi
135 | else
136 | JAVACMD=java
137 | if ! command -v java >/dev/null 2>&1
138 | then
139 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
140 |
141 | Please set the JAVA_HOME variable in your environment to match the
142 | location of your Java installation."
143 | fi
144 | fi
145 |
146 | # Increase the maximum file descriptors if we can.
147 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
148 | case $MAX_FD in #(
149 | max*)
150 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
151 | # shellcheck disable=SC2039,SC3045
152 | MAX_FD=$( ulimit -H -n ) ||
153 | warn "Could not query maximum file descriptor limit"
154 | esac
155 | case $MAX_FD in #(
156 | '' | soft) :;; #(
157 | *)
158 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
159 | # shellcheck disable=SC2039,SC3045
160 | ulimit -n "$MAX_FD" ||
161 | warn "Could not set maximum file descriptor limit to $MAX_FD"
162 | esac
163 | fi
164 |
165 | # Collect all arguments for the java command, stacking in reverse order:
166 | # * args from the command line
167 | # * the main class name
168 | # * -classpath
169 | # * -D...appname settings
170 | # * --module-path (only if needed)
171 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
172 |
173 | # For Cygwin or MSYS, switch paths to Windows format before running java
174 | if "$cygwin" || "$msys" ; then
175 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
176 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
177 |
178 | JAVACMD=$( cygpath --unix "$JAVACMD" )
179 |
180 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
181 | for arg do
182 | if
183 | case $arg in #(
184 | -*) false ;; # don't mess with options #(
185 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
186 | [ -e "$t" ] ;; #(
187 | *) false ;;
188 | esac
189 | then
190 | arg=$( cygpath --path --ignore --mixed "$arg" )
191 | fi
192 | # Roll the args list around exactly as many times as the number of
193 | # args, so each arg winds up back in the position where it started, but
194 | # possibly modified.
195 | #
196 | # NB: a `for` loop captures its iteration list before it begins, so
197 | # changing the positional parameters here affects neither the number of
198 | # iterations, nor the values presented in `arg`.
199 | shift # remove old arg
200 | set -- "$@" "$arg" # push replacement arg
201 | done
202 | fi
203 |
204 |
205 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
206 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
207 |
208 | # Collect all arguments for the java command:
209 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
210 | # and any embedded shellness will be escaped.
211 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
212 | # treated as '${Hostname}' itself on the command line.
213 |
214 | set -- \
215 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
216 | -classpath "$CLASSPATH" \
217 | org.gradle.wrapper.GradleWrapperMain \
218 | "$@"
219 |
220 | # Stop when "xargs" is not available.
221 | if ! command -v xargs >/dev/null 2>&1
222 | then
223 | die "xargs is not available"
224 | fi
225 |
226 | # Use "xargs" to parse quoted args.
227 | #
228 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
229 | #
230 | # In Bash we could simply go:
231 | #
232 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
233 | # set -- "${ARGS[@]}" "$@"
234 | #
235 | # but POSIX shell has neither arrays nor command substitution, so instead we
236 | # post-process each arg (as a line of input to sed) to backslash-escape any
237 | # character that might be a shell metacharacter, then use eval to reverse
238 | # that process (while maintaining the separation between arguments), and wrap
239 | # the whole thing up as a single "set" statement.
240 | #
241 | # This will of course break if any of these variables contains a newline or
242 | # an unmatched quote.
243 | #
244 |
245 | eval "set -- $(
246 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
247 | xargs -n1 |
248 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
249 | tr '\n' ' '
250 | )" '"$@"'
251 |
252 | exec "$JAVACMD" "$@"
253 |
--------------------------------------------------------------------------------