diff --git a/README.md b/README.md index 5ad932d..2596eb3 100644 --- a/README.md +++ b/README.md @@ -1,110 +1,304 @@ -# AndroidRust Gradle Plugin +# Gradle Android Rust Plugin -This plugin helps with building Rust JNI libraries with Cargo for use in Android projects. +A Gradle plugin for building Rust libraries with Cargo for Android projects. -Link to the plugin on the gradle repository: -https://plugins.gradle.org/plugin/io.github.MatrixDev.android-rust +## Version 0.8.0 - New Features -# Usage +### 🚀 New in 0.8.0 -Add dependencies to the root `build.gradle.kts` file +#### **Cargo Clean Support** +New `cargoClean` feature allows you to run `cargo clean` on your Rust modules: + +**Manual clean (always available):** +```bash +./gradlew cargoClean # Clean all Rust modules +./gradlew cargoCleanMyLib # Clean specific module "mylib" +``` + +**Auto-clean with Gradle clean:** +Enable `cargoClean` to automatically run `cargo clean` when you run `./gradlew clean`: ```kotlin -buildscript { - repositories { - maven("https://plugins.gradle.org/m2/") - } +androidRust { + module("mylib") { + path = file("src/main/jni") + cargoClean = true // Enable auto-clean for this module - dependencies { - classpath("io.github.MatrixDev.android-rust:plugin:0.5.0") + buildType("release") { + cargoClean = true // Or enable only for specific build types + } } + + // Or enable globally for all modules + cargoClean = true } ``` -Add plugin to the module's `build.gradle.kts` file +**New Tasks:** +- `cargoClean` - Runs cargo clean for all Rust modules +- `cargoClean` - Runs cargo clean for a specific module -```kotlin -plugins { - id("io.github.MatrixDev.android-rust") -} -``` +--- + +## Version 0.7.0 Features -Add `androidRust` configuration +### 🚀 Major Improvements + +#### 1. **cargo-ndk Integration** +The plugin now uses `cargo-ndk` instead of raw `cargo` commands for building Android libraries. This eliminates common linking errors and simplifies the build process. + +**Benefits:** +- Automatic NDK environment configuration +- No manual environment variable setup needed +- Fewer linking errors and build failures +- Better compatibility with Android NDK +- Automatic rust home finding +- Fixed bugs + +The plugin will automatically install `cargo-ndk` if it's not already available. + +#### 2. **Full Windows Support** +Complete support for Windows development environment: +- Automatic rustup installation on Windows +- Proper handling of Windows executable paths (.cmd wrappers) +- Windows-specific rustup installation via PowerShell + +#### 3. **Gradle Build Cache Support** +Proper Gradle task input/output annotations for intelligent caching: +- `@InputFiles` - Tracks Rust source files (*.rs, Cargo.toml, Cargo.lock) +- `@OutputDirectory` - Tracks JNI libs output directory +- Incremental builds - Only rebuilds when Rust sources change +- Better build performance in CI/CD environments + +#### 4. **Parallel ABI Builds** +Multiple ABIs now build in parallel when using Gradle's `--parallel` flag: +- Significantly faster builds when targeting multiple architectures +- Optimal CPU utilization during compilation +- No sequential dependency chains between ABI builds + +#### 5. **Enhanced Error Messages** +Detailed, actionable error messages when builds fail: +- Clear indication of what went wrong +- Specific suggestions for common issues +- Direct guidance on how to fix problems +- Better developer experience + +#### 6. **Build Validation** +Pre-build validation to catch configuration errors early: +- Validates Rust project paths exist +- Checks for Cargo.toml presence +- Verifies NDK installation +- Ensures module configurations are complete + +### 🔧 Configuration + +#### Basic Setup ```kotlin androidRust { - module("rust-library") { - path = file("src/rust_library") + module("mylib") { + path = file("../rust/mylib") + targets = listOf("arm", "arm64", "x86", "x86_64") + + buildType("debug") { + profile = "dev" + runTests = true + } + + buildType("release") { + profile = "release" + } } } ``` -# Additional configurations - -This is the list of some additional flags that can be configured: +#### Advanced Options ```kotlin androidRust { - // MSRV, plugin will update rust if installed version is lower than requested - minimumSupportedRustVersion = "1.62.1" + minimumSupportedRustVersion = "1.70.0" - module("rust-library") { - // path to your rust library - path = file("src/rust_library") - - // default rust profile - profile = "release" - - // default abi targets - targets = listOf("arm", "arm64") - - // "debug" build type specific configuration + module("mylib") { + path = file("../rust/mylib") + targets = listOf("arm64") + runTests = true + disableAbiOptimization = false + buildType("debug") { - // use "dev" profile in rust profile = "dev" } - - // "release" build type specific configuration + buildType("release") { - // run rust tests before build - runTests = true - - // build all supported abi versions - targets = listOf("arm", "arm64", "x86", "x86_64") + profile = "release" } } - - // more than one library can be added - module("additional-library") { - // ... - } } ``` +### Configuration Options + +| Option | Description | Default | +|--------|-------------|---------| +| `minimumSupportedRustVersion` | Minimum Rust version required | `""` (no check) | +| `path` | Path to Rust project directory | **Required** | +| `targets` | List of target ABIs | `["arm", "arm64", "x86", "x86_64"]` | +| `profile` | Rust build profile | `"release"` | +| `runTests` | Run `cargo test` before building | `null` (disabled) | +| `disableAbiOptimization` | Disable IDE ABI injection | `null` (false) | +| `cargoClean` | Run `cargo clean` with `./gradlew clean` | `null` (disabled) | + +### Supported ABIs + +| Rust Name | Android Name | Architecture | +|-----------|--------------|--------------| +| `arm` | `armeabi-v7a` | 32-bit ARM | +| `arm64` | `arm64-v8a` | 64-bit ARM | +| `x86` | `x86` | 32-bit x86 | +| `x86_64` | `x86_64` | 64-bit x86 | + +### Requirements + +- Android Gradle Plugin 7.0+ +- Rust toolchain (will be auto-installed if missing) +- cargo-ndk (will be auto-installed if missing) +- Android NDK (install via Android Studio SDK Manager) + +### How It Works + +1. **Auto-Installation**: Plugin automatically installs rustup, Rust toolchain, cargo-ndk, and required target triples +2. **Validation**: Pre-build validation ensures all paths and configurations are correct +3. **Parallel Building**: cargo-ndk builds each target ABI, potentially in parallel +4. **Output Placement**: Compiled .so files are automatically placed in jniLibs directories +5. **Integration**: Android build system picks up the libraries automatically + +### How to install + +The puglin is avaiable here : https://plugins.gradle.org/plugin/io.github.rodroidmods.android-rust + +### Cargo.toml Requirements + +Your Rust library must be configured as a C dynamic library: + +```toml +[package] +name = "mylib" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +# your dependencies here +``` + +### Custom Rust Binary Paths + +If you have Rust installed in a custom location, create `local.properties`: + +```properties +cargo.bin=/custom/path/to/cargo/bin +``` -# Development support -Plugin will check for a magic property `android.injected.build.abi` set by Android Studio when -running application on device. This will limit ABI targets to only required by the device and -should speedup development quite a bit. +The plugin will look for `cargo`, `cargo-ndk`, `rustc`, and `rustup` in this directory. -In theory this should behave the same as a built-in support for the NdkBuild / CMake. +### Build Tasks +The plugin creates tasks for each build type and ABI combination: -# Goals -- Building multiple rust libraries with ease -- Allow builds to be configurable for common scenarios +- `cleanRustJniLibs` - Clean Rust build artifacts +- `testRust` - Run Rust tests (if enabled) +- `buildRust[]` - Build specific ABI +- `cargoClean` - Run cargo clean for all Rust modules +- `cargoClean` - Run cargo clean for a specific module +Example tasks: +- `buildReleaseMyLibRust[arm64-v8a]` +- `buildDebugMyLibRust[x86_64]` +- `testMyLibRust` +- `cargoClean` +- `cargoCleanMyLib` -# Non-goals -- Supporting all Gradle versions -- Allow builds to be configurable for exotic scenarios +### Gradle Build Cache +The plugin fully supports Gradle's build cache. To enable: -# IDE Enviroment PATH Workaround -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. +```bash +./gradlew build --build-cache +``` -To solve this problem cargo path can be provided in `local.properties` file: +Or add to `gradle.properties`: ```properties -sdk.dir=... -cargo.bin=/Users/{user}/.cargo/bin/ +org.gradle.caching=true +``` + +### Parallel Builds + +To build multiple ABIs in parallel: + +```bash +./gradlew build --parallel +``` + +Or add to `gradle.properties`: +```properties +org.gradle.parallel=true +``` + +### Troubleshooting + +#### cargo-ndk not found +The plugin will automatically install cargo-ndk, but if you see errors, manually install: +```bash +cargo install cargo-ndk + +and if you have issues with rust beign not founded, than just ad cargo.bin path in local.prop and problem will be fixed ``` + +#### NDK not found +Install NDK via Android Studio: Tools → SDK Manager → SDK Tools → NDK (Side by side) + +#### Library not found when running from Android Studio +If you see "library not found" errors when running from Android Studio, set: +```kotlin +androidRust { + module("mylib") { + disableAbiOptimization = true + } +} +``` + +This forces building all ABIs instead of just the IDE-injected target. + +#### Windows: rustup installation fails +Ensure PowerShell execution policy allows running scripts: +```powershell +Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser +``` + +### Migration from 0.6.0 + +The plugin now uses `cargo-ndk` internally. No configuration changes are required, but you may need to install cargo-ndk: + +```bash +cargo install cargo-ndk +``` + +All existing configurations will continue to work. + +### Note + +It is recommended to use the latest version 0.8.0, as previous versions have bugs that have been fixed. + +### Credits + ++ Rodroid Mods ++ Matrix dev + +## License + +MIT License + +## Contributing + +Contributions welcome! Please open an issue or pull request on GitHub. \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 4bee0f6..55d0d74 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,18 +1,17 @@ plugins { `java-gradle-plugin` `kotlin-dsl` - id("com.gradle.plugin-publish") version "1.3.0" + id("com.gradle.plugin-publish") version "2.0.0" } -val pluginId = "io.github.MatrixDev.android-rust" +val pluginId = "io.github.rodroidmods.android-rust" group = pluginId -version = "0.5.0" +version = "0.8.0" -@Suppress("UnstableApiUsage") gradlePlugin { - website = "https://github.com/MatrixDev/GradleAndroidRustPlugin" - vcsUrl = "https://github.com/MatrixDev/GradleAndroidRustPlugin.git" + website = "https://github.com/rodroidmods/GradleAndroidRustPlugin" + vcsUrl = "https://github.com/rodroidmods/GradleAndroidRustPlugin.git" plugins { create("AndroidRust") { @@ -44,4 +43,4 @@ dependencies { implementation(libs.agp) implementation(libs.agp.api) -} +} \ No newline at end of file diff --git a/example/app/build.gradle.kts b/example/app/build.gradle.kts index 5b9247b..2500274 100644 --- a/example/app/build.gradle.kts +++ b/example/app/build.gradle.kts @@ -1,25 +1,34 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) + alias(libs.plugins.compose.compiler) alias(libs.plugins.android.rust) } android { - namespace = "dev.matrix.rust" - compileSdk = 33 + namespace = "dev.rodroid.rust" + compileSdk = 36 ndkVersion = "25.2.9519653" defaultConfig { - applicationId = "dev.matrix.rust" - minSdk = 21 - targetSdk = 33 + applicationId = "dev.rodroid.rust" + minSdk = 26 + targetSdk = 36 versionCode = 1 versionName = "1.0" } + buildFeatures { + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = "1.5.15" + } + buildTypes { release { - isMinifyEnabled = false + isMinifyEnabled = true proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt")) proguardFiles("proguard-rules.pro") } @@ -27,24 +36,25 @@ android { sourceSets { getByName("main") { - java.srcDir("src/rust_library") + java.srcDir("src/main/jni") } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } - kotlinOptions { - jvmTarget = "1.8" + kotlin { + jvmToolchain(21) } } androidRust { module("library") { - path = file("src/rust_library") - + path = file("src/main/jni") + //targets = listOf("arm64", "x86_64", "arm", "x86") + targets = listOf("arm64") buildType("release") { runTests = true } @@ -53,10 +63,22 @@ androidRust { } dependencies { - implementation("androidx.core:core-ktx:1.9.0") - implementation("androidx.appcompat:appcompat:1.6.1") - implementation("androidx.constraintlayout:constraintlayout:2.1.4") - implementation("androidx.navigation:navigation-fragment-ktx:2.5.3") - implementation("androidx.navigation:navigation-ui-ktx:2.5.3") - implementation("com.google.android.material:material:1.8.0") + implementation(platform(libs.androidx.compose.bom)) + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.androidx.constraintlayout) + implementation(libs.google.material) + + implementation(libs.androidx.compose.ui) + implementation(libs.androidx.compose.ui.graphics) + implementation(libs.androidx.compose.ui.tooling.preview) + implementation(libs.androidx.compose.material3) + implementation(libs.androidx.compose.foundation) + implementation(libs.androidx.compose.animation) + implementation(libs.androidx.activity.compose) + implementation(libs.androidx.lifecycle.runtime.compose) + + debugImplementation(libs.androidx.compose.ui.tooling) + debugImplementation(libs.androidx.compose.ui.test.manifest) } diff --git a/example/app/src/main/AndroidManifest.xml b/example/app/src/main/AndroidManifest.xml index 1493687..1868b0d 100644 --- a/example/app/src/main/AndroidManifest.xml +++ b/example/app/src/main/AndroidManifest.xml @@ -4,7 +4,6 @@ diff --git a/example/app/src/main/java/dev/matrix/rust/MainActivity.kt b/example/app/src/main/java/dev/matrix/rust/MainActivity.kt deleted file mode 100644 index 94a5867..0000000 --- a/example/app/src/main/java/dev/matrix/rust/MainActivity.kt +++ /dev/null @@ -1,19 +0,0 @@ -package dev.matrix.rust - -import android.os.Bundle -import android.widget.Toast -import androidx.appcompat.app.AppCompatActivity - -class MainActivity : AppCompatActivity() { - private external fun callRustCode(): String - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - System.loadLibrary("rust_library") - - Toast.makeText(this, callRustCode(), Toast.LENGTH_LONG).show() - - finish() - } -} diff --git a/example/app/src/main/java/dev/rodroid/rust/MainActivity.kt b/example/app/src/main/java/dev/rodroid/rust/MainActivity.kt new file mode 100644 index 0000000..badca4e --- /dev/null +++ b/example/app/src/main/java/dev/rodroid/rust/MainActivity.kt @@ -0,0 +1,50 @@ +package dev.rodroid.rust + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.Surface +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import kotlinx.coroutines.delay +import androidx.compose.ui.text.style.TextAlign + +class MainActivity : ComponentActivity() { + + private external fun callRustCode(): String + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + System.loadLibrary("rust_library") + + setContent { + MaterialTheme { + Surface( + modifier = Modifier.fillMaxSize(), + color = Color.Black + ) { + Box( + modifier = Modifier + .fillMaxSize() + .background(Color.Black), + contentAlignment = Alignment.Center + ) { + Text( + text = callRustCode(), + color = Color.Red, + style = MaterialTheme.typography.headlineLarge, + textAlign = TextAlign.Center + ) + } + } + } + } + } +} \ No newline at end of file diff --git a/example/app/src/rust_library/.gitignore b/example/app/src/main/jni/.gitignore similarity index 100% rename from example/app/src/rust_library/.gitignore rename to example/app/src/main/jni/.gitignore diff --git a/example/app/src/main/jni/Cargo.toml b/example/app/src/main/jni/Cargo.toml new file mode 100644 index 0000000..0dc9dac --- /dev/null +++ b/example/app/src/main/jni/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "rust_library" +version = "0.1.0" +edition = "2024" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +jni = "0.19.0" + +[profile.release] +opt-level = "z" +lto = true +codegen-units = 1 +panic = "abort" +strip = "symbols" +debug = false +overflow-checks = false +incremental = false + +[profile.release.package."*"] +opt-level = "z" +codegen-units = 1 +strip = true +debug = false +overflow-checks = false \ No newline at end of file diff --git a/example/app/src/main/jni/src/lib.rs b/example/app/src/main/jni/src/lib.rs new file mode 100644 index 0000000..fb07cc5 --- /dev/null +++ b/example/app/src/main/jni/src/lib.rs @@ -0,0 +1,12 @@ +use jni::{JNIEnv, objects::JObject}; +use jni::sys::jstring; + +#[unsafe(no_mangle)] +pub extern "C" fn Java_dev_rodroid_rust_MainActivity_callRustCode( + env: JNIEnv, + _: JObject, +) -> jstring { + let message = "Hello from Rust"; + let java_string = env.new_string(message).expect("Couldn't create java string!"); + java_string.into_inner() +} \ No newline at end of file diff --git a/example/app/src/main/res/layout/activity_main.xml b/example/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index 1968bd8..0000000 --- a/example/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/example/app/src/main/res/layout/content_main.xml b/example/app/src/main/res/layout/content_main.xml deleted file mode 100644 index e416e1c..0000000 --- a/example/app/src/main/res/layout/content_main.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ No newline at end of file diff --git a/example/app/src/main/res/layout/fragment_first.xml b/example/app/src/main/res/layout/fragment_first.xml deleted file mode 100644 index fb44a3d..0000000 --- a/example/app/src/main/res/layout/fragment_first.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - -