diff --git a/README.md b/README.md index ad43a7c..34a35b4 100644 --- a/README.md +++ b/README.md @@ -211,3 +211,4 @@ However, you can specify a custom installation directory by setting it in your * - [Java](./sample/java) - A java sample with 1 method - [Kotlin](./sample/kotlin) - A kotlin sample with 1 method - [Game](./sample/game) - A simple game without any engine like cocos2d, just Rust and Android +- [REST API](./sample/restapi) - A REST API client using Tokio and reqwest in pure Rust diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md index eb3e23d..49de3ca 100644 --- a/doc/CHANGELOG.md +++ b/doc/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.1.0 (2026-02-06) +### Fixed +- Fixed rust path resolution in ReflectionJVM and ReflectionNative to resolve relative to project root instead of Gradle daemon directory + +### Added +- Pass CC_ and AR_ environment variables to cargo build, enabling cross-compilation of Rust crates that depend on C code via cc-rs (e.g. ring, openssl-sys, libsqlite3-sys) +- New REST API sample (`sample/restapi`) demonstrating HTTP client in Rust using Tokio, reqwest, and serde with the Dog API + ## 0.0.27 (2025-08-07) ### Fixed - Fixed the support for Cargo checking on Windows diff --git a/gradle-plugin/build.gradle.kts b/gradle-plugin/build.gradle.kts index d679e6a..d9e1ca7 100644 --- a/gradle-plugin/build.gradle.kts +++ b/gradle-plugin/build.gradle.kts @@ -9,7 +9,7 @@ repositories { google() } -version = "0.0.27" +version = "0.1.0" group = "io.github.andrefigas.rustjni" gradlePlugin { diff --git a/gradle-plugin/src/main/kotlin/io/github/andrefigas/rustjni/RustJNI.kt b/gradle-plugin/src/main/kotlin/io/github/andrefigas/rustjni/RustJNI.kt index 69671fa..ffa8c52 100644 --- a/gradle-plugin/src/main/kotlin/io/github/andrefigas/rustjni/RustJNI.kt +++ b/gradle-plugin/src/main/kotlin/io/github/andrefigas/rustjni/RustJNI.kt @@ -44,8 +44,9 @@ class RustJNI : Plugin { private fun runCargoCommand(arguments: List, dir: File = rustDir, - outputProcessor: ((String) -> Unit)? = null) { - runCommand("cargo", arguments, dir, outputProcessor) + outputProcessor: ((String) -> Unit)? = null, + extraEnv: Map = emptyMap()) { + runCommand("cargo", arguments, dir, outputProcessor, extraEnv) } private fun runRustupCommand(arguments: List, @@ -341,8 +342,20 @@ class RustJNI : Plugin { } private fun buildRustForArchitectures() { + val prebuiltPath = getPrebuiltPath() extension.architecturesList.forEach { archConfig -> - runCargoCommand(listOf("build", "--target", archConfig.target, "--release", "--verbose")) + val targetEnvKey = archConfig.target.replace('-', '_') + val linker = OSHelper.addLinkerExtensionIfNeeded(archConfig.linker) + val ccPath = "$prebuiltPath$linker" + val arPath = "$prebuiltPath${archConfig.ar}" + val envVars = mapOf( + "CC_$targetEnvKey" to ccPath, + "AR_$targetEnvKey" to arPath + ) + runCargoCommand( + listOf("build", "--target", archConfig.target, "--release", "--verbose"), + extraEnv = envVars + ) } } diff --git a/gradle-plugin/src/main/kotlin/io/github/andrefigas/rustjni/reflection/ReflectionJVM.kt b/gradle-plugin/src/main/kotlin/io/github/andrefigas/rustjni/reflection/ReflectionJVM.kt index 6d8df5c..92846af 100644 --- a/gradle-plugin/src/main/kotlin/io/github/andrefigas/rustjni/reflection/ReflectionJVM.kt +++ b/gradle-plugin/src/main/kotlin/io/github/andrefigas/rustjni/reflection/ReflectionJVM.kt @@ -95,7 +95,7 @@ internal object ReflectionJVM { isKotlinFile: Boolean ): String { val jniHost = extension.jniHost.trim() - val methodsToGenerate = parseRustJniFunctions(extension, jniHost, isKotlinFile) + val methodsToGenerate = parseRustJniFunctions(project, extension, jniHost, isKotlinFile) if (methodsToGenerate.isEmpty()) { throw org.gradle.api.GradleException("No JNI methods found for class $jniHost in lib.rs") @@ -105,11 +105,12 @@ internal object ReflectionJVM { } private fun parseRustJniFunctions( + project: Project, extension: RustJniExtension, jniHost: String, isKotlinFile: Boolean ): List { - val rustLibContent = readRustJniFile(extension) + val rustLibContent = readRustJniFile(project, extension) val jniFunctionPattern = Regex( """(?s)#\s*\[\s*no_mangle\s*\]\s*pub\s+extern\s+"C"\s+fn\s+(Java_\w+)\s*\((.*?)\)\s*(->\s*[\w:]+)?\s*\{""", @@ -177,8 +178,8 @@ internal object ReflectionJVM { return MethodSignature(jniFunctionName, returnType, parameters) } - private fun readRustJniFile(extension: RustJniExtension): String { - val rustLibFile = FileUtils.getRustSrcFile(File(extension.rustPath)) + private fun readRustJniFile(project: Project, extension: RustJniExtension): String { + val rustLibFile = FileUtils.getRustSrcFile(FileUtils.getRustDir(project, extension)) if (!rustLibFile.exists()) { throw org.gradle.api.GradleException("Could not find '${rustLibFile.name}' file at ${rustLibFile.absolutePath}") } diff --git a/gradle-plugin/src/main/kotlin/io/github/andrefigas/rustjni/reflection/ReflectionNative.kt b/gradle-plugin/src/main/kotlin/io/github/andrefigas/rustjni/reflection/ReflectionNative.kt index ffca7a8..9d883ab 100644 --- a/gradle-plugin/src/main/kotlin/io/github/andrefigas/rustjni/reflection/ReflectionNative.kt +++ b/gradle-plugin/src/main/kotlin/io/github/andrefigas/rustjni/reflection/ReflectionNative.kt @@ -51,10 +51,11 @@ internal object ReflectionNative { // Extract method signatures val kotlinMethodSignatures = ReflectionJVM.extractMethodSignaturesFromClass(fileContent, isKotlinFile) - val rustMethodSignatures = parseRustJniFunctions(extension, jniHost, isKotlinFile) + val rustMethodSignatures = parseRustJniFunctions(project, extension, jniHost, isKotlinFile) compareMethodSignatures(kotlinMethodSignatures, rustMethodSignatures, isKotlinFile).forEach { methodSignature -> updateRustFileIfMethodNotExists( + project, extension, methodSignature.methodName, methodSignature.parameters, @@ -69,11 +70,12 @@ internal object ReflectionNative { } private fun parseRustJniFunctions( + project: Project, extension: RustJniExtension, jniHost: String, isKotlinFile: Boolean ): List { - val rustLibContent = readRustJniFile(extension) + val rustLibContent = readRustJniFile(project, extension) val jniFunctionPattern = Regex( """(?s)#\s*\[\s*no_mangle\s*\]\s*pub\s+extern\s+"C"\s+fn\s+(Java_\w+)\s*\((.*?)\)\s*(->\s*[\w:]+)?\s*\{""", @@ -166,8 +168,8 @@ internal object ReflectionNative { return classNameParts.joinToString(".").replace('_', '.') } - private fun readRustJniFile(extension: RustJniExtension): String { - val rustLibFile = FileUtils.getRustSrcFile(File(extension.rustPath)) + private fun readRustJniFile(project: Project, extension: RustJniExtension): String { + val rustLibFile = FileUtils.getRustSrcFile(FileUtils.getRustDir(project, extension)) if (!rustLibFile.exists()) { throw org.gradle.api.GradleException("Could not find '${rustLibFile.name}' file at ${rustLibFile.absolutePath}") } @@ -206,6 +208,7 @@ internal object ReflectionNative { // Updates the Rust file if the corresponding method does not exist private fun updateRustFileIfMethodNotExists( + project: Project, extension: RustJniExtension, methodName: String, parameters: List, @@ -216,7 +219,7 @@ internal object ReflectionNative { if (RustJniExtension.shouldSkipAddingMethods(jniHost, extension)) return - val rustFilePath = FileUtils.getRustSrcFile(File(extension.rustPath)) + val rustFilePath = FileUtils.getRustSrcFile(FileUtils.getRustDir(project, extension)) addMethodToRust(rustFilePath, methodName, parameters, returnType, jniHost, isKotlinFile) } diff --git a/sample/game/app/build.gradle.kts b/sample/game/app/build.gradle.kts index 9a5444d..7030aad 100644 --- a/sample/game/app/build.gradle.kts +++ b/sample/game/app/build.gradle.kts @@ -3,7 +3,7 @@ import io.github.andrefigas.rustjni.reflection.Visibility plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) - id("io.github.andrefigas.rustjni") version "0.0.22" + id("io.github.andrefigas.rustjni") version "0.1.0" } rustJni{ diff --git a/sample/java/app/build.gradle.kts b/sample/java/app/build.gradle.kts index 0ba3dd0..4de87a0 100644 --- a/sample/java/app/build.gradle.kts +++ b/sample/java/app/build.gradle.kts @@ -3,7 +3,7 @@ import io.github.andrefigas.rustjni.reflection.Visibility plugins { alias(libs.plugins.android.application) alias(libs.plugins.jetbrains.kotlin.android) - id("io.github.andrefigas.rustjni") version "0.0.27" + id("io.github.andrefigas.rustjni") version "0.1.0" } rustJni{ diff --git a/sample/kotlin/app/build.gradle.kts b/sample/kotlin/app/build.gradle.kts index 0ba3dd0..4de87a0 100644 --- a/sample/kotlin/app/build.gradle.kts +++ b/sample/kotlin/app/build.gradle.kts @@ -3,7 +3,7 @@ import io.github.andrefigas.rustjni.reflection.Visibility plugins { alias(libs.plugins.android.application) alias(libs.plugins.jetbrains.kotlin.android) - id("io.github.andrefigas.rustjni") version "0.0.27" + id("io.github.andrefigas.rustjni") version "0.1.0" } rustJni{ diff --git a/sample/restapi/.gitignore b/sample/restapi/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/sample/restapi/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/sample/restapi/app/.gitignore b/sample/restapi/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/sample/restapi/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/sample/restapi/app/build.gradle.kts b/sample/restapi/app/build.gradle.kts new file mode 100644 index 0000000..c110e1b --- /dev/null +++ b/sample/restapi/app/build.gradle.kts @@ -0,0 +1,64 @@ +import io.github.andrefigas.rustjni.reflection.Visibility + +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.jetbrains.kotlin.android) + id("io.github.andrefigas.rustjni") version "0.1.0" +} + +rustJni { + rustPath = "./app/src/main/rust" + jniHost = "com.devfigas.rustjni.restapi.MainActivity" + jniMethodsVisibility = Visibility.PRIVATE + ndkVersion = "27.1.12297006" + architectures { + armv7_linux_androideabi("armv7a-linux-androideabi21-clang") + aarch64_linux_android("aarch64-linux-android21-clang") + i686_linux_android("i686-linux-android21-clang") + x86_64_linux_android("x86_64-linux-android21-clang") + } + rustVersion = ">=1.64.0" +} + +android { + namespace = "com.devfigas.rustjni.restapi" + compileSdk = 34 + + defaultConfig { + applicationId = "com.devfigas.rustjni.restapi" + minSdk = 24 + targetSdk = 34 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.material) + implementation(libs.androidx.activity) + implementation(libs.androidx.constraintlayout) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) +} diff --git a/sample/restapi/app/proguard-rules.pro b/sample/restapi/app/proguard-rules.pro new file mode 100644 index 0000000..b19b309 --- /dev/null +++ b/sample/restapi/app/proguard-rules.pro @@ -0,0 +1,6 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html diff --git a/sample/restapi/app/src/main/AndroidManifest.xml b/sample/restapi/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..27b5c5d --- /dev/null +++ b/sample/restapi/app/src/main/AndroidManifest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + diff --git a/sample/restapi/app/src/main/java/com/devfigas/rustjni/restapi/MainActivity.kt b/sample/restapi/app/src/main/java/com/devfigas/rustjni/restapi/MainActivity.kt new file mode 100644 index 0000000..87af9d4 --- /dev/null +++ b/sample/restapi/app/src/main/java/com/devfigas/rustjni/restapi/MainActivity.kt @@ -0,0 +1,62 @@ +package com.devfigas.rustjni.restapi + +import android.os.Bundle +import android.widget.Button +import android.widget.EditText +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity + +class MainActivity : AppCompatActivity() { + + // + // auto-generated code + + private external fun getRandomDog(): String + + private external fun listBreeds(): String + + private external fun getBreedImage(breed: String): String + + init { System.loadLibrary("rust_rest_api") } + + // + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + val tvResult = findViewById(R.id.tvResult) + val etBreed = findViewById(R.id.etBreed) + val btnRandomDog = findViewById