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{