Skip to content

Commit 16c891f

Browse files
authored
[#4] Add ability to provide cargo folder path
1 parent f445165 commit 16c891f

File tree

6 files changed

+196
-19
lines changed

6 files changed

+196
-19
lines changed

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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.

README.md

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
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.3.2")
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+
```

src/main/kotlin/dev/matrix/agp/rust/AndroidRustPlugin.kt

+5-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package dev.matrix.agp.rust
22

33
import com.android.build.gradle.internal.tasks.factory.dependsOn
44
import dev.matrix.agp.rust.utils.Abi
5+
import dev.matrix.agp.rust.utils.RustBinaries
56
import dev.matrix.agp.rust.utils.SemanticVersion
67
import dev.matrix.agp.rust.utils.getAndroidComponentsExtension
78
import dev.matrix.agp.rust.utils.getAndroidExtension
@@ -15,9 +16,10 @@ import java.util.Locale
1516
// TODO: migrate to variant API with artifacts when JNI will be supported
1617
// https://developer.android.com/studio/build/extend-agp#access-modify-artifacts
1718
//
18-
@Suppress("unused", "UnstableApiUsage")
19+
@Suppress("unused")
1920
class AndroidRustPlugin : Plugin<Project> {
2021
override fun apply(project: Project) {
22+
val rustBinaries = RustBinaries(project)
2123
val extension = project.extensions.create("androidRust", AndroidRustExtension::class.java)
2224
val androidExtension = project.getAndroidExtension()
2325
val androidComponents = project.getAndroidComponentsExtension()
@@ -65,6 +67,7 @@ class AndroidRustPlugin : Plugin<Project> {
6567
for (rustAbi in rustAbiSet) {
6668
val buildTaskName = "build${buildTypeNameCap}${moduleNameCap}Rust[${rustAbi.androidName}]"
6769
val buildTask = project.tasks.register(buildTaskName, RustBuildTask::class.java) {
70+
this.rustBinaries.set(rustBinaries)
6871
this.abi.set(rustAbi)
6972
this.apiLevel.set(dsl.defaultConfig.minSdk ?: 21)
7073
this.ndkVersion.set(ndkVersion)
@@ -83,7 +86,7 @@ class AndroidRustPlugin : Plugin<Project> {
8386
}
8487

8588
val minimumSupportedRustVersion = SemanticVersion(extension.minimumSupportedRustVersion)
86-
installRustComponentsIfNeeded(project, minimumSupportedRustVersion, allRustAbiSet)
89+
installRustComponentsIfNeeded(project, minimumSupportedRustVersion, allRustAbiSet, rustBinaries)
8790
}
8891

8992
androidComponents.onVariants { variant ->

src/main/kotlin/dev/matrix/agp/rust/RustBuildTask.kt

+6-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package dev.matrix.agp.rust
22

33
import dev.matrix.agp.rust.utils.Abi
44
import dev.matrix.agp.rust.utils.Os
5+
import dev.matrix.agp.rust.utils.RustBinaries
56
import dev.matrix.agp.rust.utils.SemanticVersion
67
import org.gradle.api.DefaultTask
78
import org.gradle.api.provider.Property
@@ -10,6 +11,9 @@ import org.gradle.api.tasks.TaskAction
1011
import java.io.File
1112

1213
internal abstract class RustBuildTask : DefaultTask() {
14+
@get:Input
15+
abstract val rustBinaries: Property<RustBinaries>
16+
1317
@get:Input
1418
abstract val abi: Property<Abi>
1519

@@ -36,6 +40,7 @@ internal abstract class RustBuildTask : DefaultTask() {
3640

3741
@TaskAction
3842
fun taskAction() {
43+
val rustBinaries = rustBinaries.get()
3944
val abi = abi.get()
4045
val apiLevel = apiLevel.get()
4146
val ndkVersion = ndkVersion.get()
@@ -72,7 +77,7 @@ internal abstract class RustBuildTask : DefaultTask() {
7277
environment("CARGO_TARGET_DIR", cargoTargetDirectory.absolutePath)
7378
environment("CARGO_TARGET_${cargoTargetTriplet}_LINKER", cc)
7479

75-
commandLine("cargo")
80+
commandLine(rustBinaries.cargo)
7681

7782
args("build")
7883
args("--lib")

src/main/kotlin/dev/matrix/agp/rust/RustInstaller.kt

+18-16
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package dev.matrix.agp.rust
33
import dev.matrix.agp.rust.utils.Abi
44
import dev.matrix.agp.rust.utils.NullOutputStream
55
import dev.matrix.agp.rust.utils.Os
6+
import dev.matrix.agp.rust.utils.RustBinaries
67
import dev.matrix.agp.rust.utils.SemanticVersion
78
import dev.matrix.agp.rust.utils.log
89
import org.gradle.api.Project
@@ -12,38 +13,39 @@ internal fun installRustComponentsIfNeeded(
1213
project: Project,
1314
minimalVersion: SemanticVersion?,
1415
abiSet: Collection<Abi>,
16+
rustBinaries: RustBinaries,
1517
) {
1618
if (Os.current.isWindows) {
1719
return
1820
}
1921

2022
if (minimalVersion != null && minimalVersion.isValid) {
21-
val actualVersion = readRustCompilerVersion(project)
23+
val actualVersion = readRustCompilerVersion(project, rustBinaries)
2224
if (actualVersion < minimalVersion) {
23-
installRustUp(project)
24-
updateRust(project)
25+
installRustUp(project, rustBinaries)
26+
updateRust(project, rustBinaries)
2527
}
2628
}
2729

2830
if (abiSet.isNotEmpty()) {
29-
installRustUp(project)
31+
installRustUp(project, rustBinaries)
3032

31-
val installedAbiSet = readRustUpInstalledTargets(project)
33+
val installedAbiSet = readRustUpInstalledTargets(project, rustBinaries)
3234
for (abi in abiSet) {
3335
if (installedAbiSet.contains(abi)) {
3436
continue
3537
}
36-
installRustTarget(project, abi)
38+
installRustTarget(project, abi, rustBinaries)
3739
}
3840
}
3941
}
4042

41-
private fun installRustUp(project: Project) {
43+
private fun installRustUp(project: Project, rustBinaries: RustBinaries) {
4244
try {
4345
val result = project.exec {
4446
standardOutput = NullOutputStream
4547
errorOutput = NullOutputStream
46-
executable("rustup")
48+
executable(rustBinaries.rustup)
4749
args("-V")
4850
}
4951

@@ -62,34 +64,34 @@ private fun installRustUp(project: Project) {
6264
}.assertNormalExitValue()
6365
}
6466

65-
private fun updateRust(project: Project) {
67+
private fun updateRust(project: Project, rustBinaries: RustBinaries) {
6668
log("updating rust version")
6769

6870
project.exec {
6971
standardOutput = NullOutputStream
7072
errorOutput = NullOutputStream
71-
executable("rustup")
73+
executable(rustBinaries.rustup)
7274
args("update")
7375
}.assertNormalExitValue()
7476
}
7577

76-
private fun installRustTarget(project: Project, abi: Abi) {
78+
private fun installRustTarget(project: Project, abi: Abi, rustBinaries: RustBinaries) {
7779
log("installing rust target $abi (${abi.rustTargetTriple})")
7880

7981
project.exec {
8082
standardOutput = NullOutputStream
8183
errorOutput = NullOutputStream
82-
executable("rustup")
84+
executable(rustBinaries.rustup)
8385
args("target", "add", abi.rustTargetTriple)
8486
}.assertNormalExitValue()
8587
}
8688

87-
private fun readRustCompilerVersion(project: Project): SemanticVersion {
89+
private fun readRustCompilerVersion(project: Project, rustBinaries: RustBinaries): SemanticVersion {
8890
val output = ByteArrayOutputStream()
8991
project.exec {
9092
standardOutput = output
9193
errorOutput = NullOutputStream
92-
executable("rustc")
94+
executable(rustBinaries.rustc)
9395
args("--version")
9496
}.assertNormalExitValue()
9597

@@ -102,12 +104,12 @@ private fun readRustCompilerVersion(project: Project): SemanticVersion {
102104
return SemanticVersion(match.groupValues[1])
103105
}
104106

105-
private fun readRustUpInstalledTargets(project: Project): Set<Abi> {
107+
private fun readRustUpInstalledTargets(project: Project, rustBinaries: RustBinaries): Set<Abi> {
106108
val output = ByteArrayOutputStream()
107109
project.exec {
108110
standardOutput = output
109111
errorOutput = NullOutputStream
110-
executable("rustup")
112+
executable(rustBinaries.rustup)
111113
args("target", "list")
112114
}.assertNormalExitValue()
113115

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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+
}

0 commit comments

Comments
 (0)