diff --git a/gradle-plugin-test/src/main/kotlin/io/github/andrefigas/rustjni/test/TestRunner.kt b/gradle-plugin-test/src/main/kotlin/io/github/andrefigas/rustjni/test/TestRunner.kt
index 4fad9aa..a376009 100644
--- a/gradle-plugin-test/src/main/kotlin/io/github/andrefigas/rustjni/test/TestRunner.kt
+++ b/gradle-plugin-test/src/main/kotlin/io/github/andrefigas/rustjni/test/TestRunner.kt
@@ -7,6 +7,7 @@ import io.github.andrefigas.rustjni.test.jvm.content.KotlinContentProvider
import org.gradle.api.Project
import org.gradle.api.Task
import java.io.File
+import java.util.Properties
object JVMTestRunner {
@@ -25,9 +26,16 @@ object JVMTestRunner {
"rust"
)
+ val props = Properties()
+ project.file("${project.rootProject.projectDir}${File.separator}local.properties")
+ .inputStream().use { props.load(it) }
+
+ val ndkDir = "${props.getProperty("sdk.dir")}${File.separator}ndk"
+
apply(
project,
task,
+ ndkDir,
rustFile,
jniHost,
contentProvider,
@@ -78,6 +86,7 @@ object JVMTestRunner {
private fun apply(project: Project,
task: Task,
+ ndkDir: String,
rustFile : File,
jniHost : File,
provider : JVMContentProvider,
@@ -86,7 +95,13 @@ object JVMTestRunner {
clean(rustFile, jniHost, provider)
jniHost.writeText(data)
- TestCases(project, task, jniHost, File(rustFile, "src${File.separator}lib.rs")).apply {
+ TestCases(project,
+ task,
+ jniHost,
+ ndkDir = ndkDir,
+ rustFile = File(rustFile, "src${File.separator}lib.rs"),
+ cargoConfigFile = File(rustFile,".cargo/config.toml"),
+ ).apply {
all()
finish()
}
diff --git a/gradle-plugin-test/src/main/kotlin/io/github/andrefigas/rustjni/test/cases/TestCases.kt b/gradle-plugin-test/src/main/kotlin/io/github/andrefigas/rustjni/test/cases/TestCases.kt
index 885df1b..bd1abe0 100644
--- a/gradle-plugin-test/src/main/kotlin/io/github/andrefigas/rustjni/test/cases/TestCases.kt
+++ b/gradle-plugin-test/src/main/kotlin/io/github/andrefigas/rustjni/test/cases/TestCases.kt
@@ -1,6 +1,7 @@
package io.github.andrefigas.rustjni.test.cases
import io.github.andrefigas.rustjni.test.JVMTestRunner
+import io.github.andrefigas.rustjni.test.toml.TomlContentProvider
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.Task
@@ -11,11 +12,16 @@ class TestCases(
private val project: Project,
private val task: Task,
private val jniHost: File,
- private val rustFile: File
+ private val rustFile: File,
+ private val cargoConfigFile : File,
+ ndkDir : String,
) {
+ private val tomlContentProvider = TomlContentProvider(ndkDir)
private val rustContent by lazy { rustFile.readText() }
private val jvmContent by lazy { jniHost.readText() }
+ private val cargoConfigContent
+ get() = cargoConfigFile.readText()
private val isKotlin = jniHost.toString().endsWith(JVMTestRunner.KT)
private val logger = project.logger
@@ -31,6 +37,44 @@ class TestCases(
assert(true, "given a generated code, it should compile successfully")
}
+ @Test
+ fun assertCargoConfigIsSuccessfullyGenerated(){
+ assertCargoConfigContains(
+ tomlContentProvider.cargoConfig().trim(),
+ "given a generated code, it should generate the correct .cargo/config.toml"
+ )
+ }
+
+ @Test
+ fun assertCargoConfigEditsIsPreserved(){
+
+ val content = buildString {
+ appendLine("[section1]")
+ appendLine("definition1 = \"definition1\"")
+ appendLine(tomlContentProvider.cargoConfig().trim())
+ appendLine("[section2]")
+ appendLine("definition2 = \"definition2\"")
+ }
+
+ cargoConfigFile.writeText(
+ content
+ )
+
+ project.tasks.getByName("rust-jni-compile").actions.forEach { action ->
+ action.execute(task)
+ }
+
+ assertCargoConfigContains(
+ "[section1]\ndefinition1 = \"definition1\"",
+ "given a generated .cargo/config.toml edited manually, it should not remove the existing content before the generated one"
+ )
+
+ assertCargoConfigContains(
+ "[section2]\ndefinition2 = \"definition2\"",
+ "given a generated .cargo/config.toml edited manually, it should not remove the existing content after the generated one"
+ )
+ }
+
private fun assert(condition : Boolean, useCase : String, errorMessage : String = ""){
if(condition){
logger.lifecycle("RustJNI Test: 🦀 $useCase: ✅")
@@ -50,6 +94,10 @@ class TestCases(
assertContains(jvmContent, jniHost.toString(), substring, useCase)
}
+ private fun assertCargoConfigContains(substring: String, useCase: String) {
+ assertContains(cargoConfigContent, cargoConfigFile.toString(), substring, useCase)
+ }
+
private fun assertContains(text: String, path : String , substring: String, useCase: String) {
val normalizedText = text.replace("\\s".toRegex(), "")
@@ -58,7 +106,7 @@ class TestCases(
if (!normalizedText.contains(normalizedSubstring)) {
val missingPart = substring.lines().firstOrNull { !text.contains(it.trim()) }
- val errorMessage = "assertContains fails:\n$path\nDoes not contain:\n$substring\nMissing part: $missingPart"
+ val errorMessage = "assertContains fails:\nfile:\n$path\n\ncontent:\n$text\n\nDoes not contain:\n$substring\n\nMissing part: $missingPart"
assert(false, useCase, errorMessage)
} else {
assert(true, useCase)
@@ -78,6 +126,8 @@ class TestCases(
fun all() {
assertCompilation()
+ assertCargoConfigIsSuccessfullyGenerated()
+ assertCargoConfigEditsIsPreserved()
assertIntParam()
assertLongParam()
diff --git a/gradle-plugin-test/src/main/kotlin/io/github/andrefigas/rustjni/test/jvm/content/JVMContentBuilder.kt b/gradle-plugin-test/src/main/kotlin/io/github/andrefigas/rustjni/test/jvm/content/JVMContentBuilder.kt
deleted file mode 100644
index 693e613..0000000
--- a/gradle-plugin-test/src/main/kotlin/io/github/andrefigas/rustjni/test/jvm/content/JVMContentBuilder.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-package io.github.andrefigas.rustjni.test.jvm.content
-
-import org.gradle.api.Project
-import org.gradle.api.Task
-import java.io.File
-
-internal class JVMContentBuilder(
- private val rustFile: File,
- private val jniHost: File,
- val provider: JVMContentProvider,
- private val project: Project,
- private val task: Task
-) {
-
- private val logger = project.logger
-
- private fun clean() {
- if(rustFile.exists()){
- rustFile.deleteRecursively()
- }
-
- jniHost.writeText(
- provider.restoreJVMContent.trimIndent()
- )
- }
-
- fun apply(data: String) {
- clean()
- logger.lifecycle("🦀 Starting jvm-test-cases")
- jniHost.writeText(data)
-
- project.tasks.getByName("rust-jni-compile").actions.forEach { action ->
- action.execute(task)
- }
-
- logger.lifecycle("🦀 jvm-test-cases finished successfully ✅")
- clean()
- }
-
-}
\ No newline at end of file
diff --git a/gradle-plugin-test/src/main/kotlin/io/github/andrefigas/rustjni/test/toml/TomlContentProvider.kt b/gradle-plugin-test/src/main/kotlin/io/github/andrefigas/rustjni/test/toml/TomlContentProvider.kt
new file mode 100644
index 0000000..3c2adc9
--- /dev/null
+++ b/gradle-plugin-test/src/main/kotlin/io/github/andrefigas/rustjni/test/toml/TomlContentProvider.kt
@@ -0,0 +1,49 @@
+package io.github.andrefigas.rustjni.test.toml
+
+class TomlContentProvider(ndkPath: String) {
+
+ val osName = System.getProperty("os.name").toLowerCase()
+ val defaultPrebuilt = when {
+ osName.contains("win") -> "windows-x86_64"
+ osName.contains("mac") -> "darwin-x86_64"
+ osName.contains("linux") -> "linux-x86_64"
+ else -> throw org.gradle.api.GradleException("Unsupported operating system: $osName")
+ }
+
+ private val binPath = "$ndkPath/27.1.12297006/toolchains/llvm/prebuilt/$defaultPrebuilt/bin"
+
+ val armv7_linux_androideabi = "[target.armv7-linux-androideabi]\n" +
+ "ar = \"$binPath/llvm-ar\"\n" +
+ "linker = \"$binPath/armv7a-linux-androideabi21-clang\""
+
+ val aarch64_linux_android = "[target.aarch64-linux-android]\n" +
+ "ar = \"$binPath/llvm-ar\"\n" +
+ "linker = \"$binPath/aarch64-linux-android21-clang\""
+
+ val i686_linux_android = "[target.i686-linux-android]\n" +
+ "ar = \"$binPath/llvm-ar\"\n" +
+ "linker = \"$binPath/i686-linux-android21-clang\""
+
+ val x86_64_linux_android = "[target.x86_64-linux-android]\n" +
+ "ar = \"$binPath/llvm-ar\"\n" +
+ "linker = \"$binPath/x86_64-linux-android21-clang\""
+
+ val all = listOf(
+ armv7_linux_androideabi,
+ aarch64_linux_android,
+ i686_linux_android,
+ x86_64_linux_android
+ )
+
+ fun cargoConfig(): String {
+ return buildString {
+ appendLine("#")
+ appendLine("#auto-generated code")
+ all.forEach {
+ appendLine(it)
+ appendLine()
+ }
+ appendLine("#")
+ }.trimStart()
+ }
+}
diff --git a/gradle-plugin/build.gradle.kts b/gradle-plugin/build.gradle.kts
index e7ae126..81d8994 100644
--- a/gradle-plugin/build.gradle.kts
+++ b/gradle-plugin/build.gradle.kts
@@ -9,7 +9,7 @@ repositories {
google()
}
-version = "0.0.22"
+version = "0.0.23"
group = "io.github.andrefigas.rustjni"
gradlePlugin {
diff --git a/gradle-plugin/src/main/kotlin/io/github/andrefigas/rustjni/OSHelper.kt b/gradle-plugin/src/main/kotlin/io/github/andrefigas/rustjni/OSHelper.kt
index ee8c93a..d76b394 100644
--- a/gradle-plugin/src/main/kotlin/io/github/andrefigas/rustjni/OSHelper.kt
+++ b/gradle-plugin/src/main/kotlin/io/github/andrefigas/rustjni/OSHelper.kt
@@ -2,12 +2,20 @@ package io.github.andrefigas.rustjni
import java.io.File
-internal object OSHelper {
+object OSHelper {
fun isWindows(): Boolean {
return System.getProperty("os.name").toLowerCase().contains("win")
}
+ fun isMac(): Boolean {
+ return System.getProperty("os.name").toLowerCase().contains("mac")
+ }
+
+ fun isLinux(): Boolean {
+ return System.getProperty("os.name").toLowerCase().contains("linux")
+ }
+
fun doubleSeparatorIfNeeded(path: String): String {
return if (isWindows()) {
path.replace(File.separator, "${File.separator}${File.separator}")
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 70f58b2..2991943 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
@@ -321,15 +321,52 @@ class RustJNI : Plugin {
* This file tells cargo where to find the *compiler* and *linker* for different architectures when compiling for Android. */
private fun generateConfigToml() {
val configToml = File(rustDir, ".cargo${File.separator}config.toml")
- if (configToml.exists()) {
- configToml.delete()
- }
+
val prebuiltPath = getPrebuiltPath()
configToml.parentFile.mkdirs()
- configToml.writeText(buildConfigTomlContent(prebuiltPath))
+
+ val before = extractBeforeRustJniBlock(configToml)
+ val after = extractAfterRustJniBlock(configToml)
+
+ val finalContent = buildConfigTomlContent(
+ prebuiltPath = prebuiltPath,
+ beforeAutogenerated = before,
+ afterAutogenerated = after
+ ).trimStart()
+
+ configToml.writeText(finalContent)
+
+ }
+
+ /** Extracts the content of the `config.toml` file before the `#` block. */
+ private fun extractBeforeRustJniBlock(file: File): String {
+ if (!file.exists()) return ""
+
+ val content = file.readText()
+
+ val startPattern = Regex(
+ pattern = "(?s)(.*?)^[ \\t]*#.*?$",
+ options = setOf(RegexOption.MULTILINE)
+ )
+
+ return startPattern.find(content)?.groupValues?.get(1) ?: content
}
- private fun buildConfigTomlContent(prebuiltPath: String): String {
+ /** Extracts the content of the `config.toml` file after the `#` block. */
+ private fun extractAfterRustJniBlock(file: File): String {
+ if (!file.exists()) return ""
+
+ val content = file.readText()
+
+ val endPattern = Regex(
+ pattern = "(?s)^.*?#[ \\t]*(.*)",
+ options = setOf(RegexOption.MULTILINE)
+ )
+
+ return endPattern.find(content)?.groupValues?.get(1) ?: ""
+ }
+
+ private fun buildConfigTomlContent(prebuiltPath: String, beforeAutogenerated : String = "", afterAutogenerated : String = ""): String {
val architectures = extension.architecturesList
if (architectures.isEmpty()) {
throw org.gradle.api.GradleException("No architectures specified in rustJni extension")
@@ -338,6 +375,9 @@ class RustJNI : Plugin {
val prebuiltPath = OSHelper.doubleSeparatorIfNeeded(prebuiltPath)
return buildString {
+ if(beforeAutogenerated.isNotEmpty()){
+ appendLine(beforeAutogenerated.trimEnd())
+ }
appendLine("#")
appendLine("#auto-generated code")
appendLine()
@@ -349,6 +389,9 @@ class RustJNI : Plugin {
appendLine()
}
appendLine("#")
+ if(afterAutogenerated.isNotEmpty()){
+ appendLine(afterAutogenerated.trimStart())
+ }
}
}
diff --git a/sample/java/app/build.gradle.kts b/sample/java/app/build.gradle.kts
index efc49cb..6d9a7da 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.22"
+ id("io.github.andrefigas.rustjni") version "0.0.23"
}
rustJni{
diff --git a/sample/kotlin/app/build.gradle.kts b/sample/kotlin/app/build.gradle.kts
index efc49cb..6d9a7da 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.22"
+ id("io.github.andrefigas.rustjni") version "0.0.23"
}
rustJni{