From 58077e01639c6f737d31c9f954f38713fdd6511c Mon Sep 17 00:00:00 2001 From: Pete Bentley Date: Sun, 8 Feb 2026 18:11:46 +0000 Subject: [PATCH] Add NativeBuildResolver in buildSrc as Kotlin replacement for NativeBuildInfo Extracts the native build platform/architecture configuration from the Groovy NativeBuildInfo enum in openjdk/build.gradle into well-tested Kotlin in buildSrc/. This is a no-op change -- nothing references these classes yet. The intent is to land and iterate on the API before wiring it into the build in a follow-up PR. Deliberate differences from the Groovy NativeBuildInfo: - findAll(os, arch) takes both os and arch, where the Groovy version takes only os. This is equivalent for all current variants (macOS crossCompile=true always returns both archs; Linux returns one) but allows for future expansion. - buildDir is an immutable Provider passed at construction time, replacing the mutable static "String buildDir = FIXME" that was set later by the build script. This uses Gradle lazy configuration properly and eliminates shared mutable state. - crossCompile is an explicit boolean field on the enum rather than being implicit in the macOS libDir() override pattern. --- buildSrc/build.gradle.kts | 12 ++ .../src/main/kotlin/NativeBuildResolver.kt | 121 ++++++++++++++++ .../test/kotlin/NativeBuildResolverTest.kt | 132 ++++++++++++++++++ 3 files changed, 265 insertions(+) create mode 100644 buildSrc/build.gradle.kts create mode 100644 buildSrc/src/main/kotlin/NativeBuildResolver.kt create mode 100644 buildSrc/src/test/kotlin/NativeBuildResolverTest.kt diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 000000000..5b870f37e --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,12 @@ +plugins { + `kotlin-dsl` +} + +repositories { + mavenCentral() +} + +dependencies { + testImplementation(kotlin("test")) + testImplementation(gradleTestKit()) +} diff --git a/buildSrc/src/main/kotlin/NativeBuildResolver.kt b/buildSrc/src/main/kotlin/NativeBuildResolver.kt new file mode 100644 index 000000000..9f995c6a9 --- /dev/null +++ b/buildSrc/src/main/kotlin/NativeBuildResolver.kt @@ -0,0 +1,121 @@ +import org.gradle.api.file.Directory +import org.gradle.api.provider.Provider +import org.gradle.nativeplatform.platform.NativePlatform +import java.io.File + +/** + * Gradle mostly uses Java os.arch names for architectures which feeds into default + * targetPlatform names. Notable exception Gradle 6.9.x reports MacOS/ARM as arm-v8. + * + * The Maven osdetector plugin (which we recommend to developers) uses different + * arch names, so that's what we need for artifacts. + * + * This class encapsulates both naming schemes as well as other per-platform information + * about native builds. + * + * This information is project independent and should rarely change over time. + */ +enum class NativeBuildVariant( + val os: String, + val mavenArch: String, // osdetector / Maven architecture name + val gradleArch: String, // Gradle name, used for NDK / toolchain etc + val boringBuildDir: String = "build64", // Where to find prebuilt BoringSSL libcrypto + val crossCompile: Boolean = false // Whether we can cross-compile for this architecture +) { + WINDOWS_X64("windows", "x86_64", "x86-64"), + LINUX_X64("linux", "x86_64", "x86-64"), + OSX_X64("osx", "x86_64", "x86-64", "build.x86", true), + OSX_ARM64("osx", "aarch_64", "aarch64", "build.arm", true); + + override fun toString(): String = + "" + + companion object { + /** + * Finds the NativeBuildVariant for a particular Maven osdetector architecture. + */ + fun find(os: String, arch: String) = values().find { it.os == os && it.mavenArch == arch } + + /** + * Finds the NativeBuildVariant for a particular Gradle architecture. + */ + fun findForGradle(os: String, arch: String) = + values().find { it.os == os && it.gradleArch == arch } + + /** + * Finds all the NativeBuildVariants which can be built on a particular + * host OS. + */ + fun findAll(os: String, arch: String) = values().filter { + it.os == os && (it.mavenArch == arch || it.crossCompile) + } + } +} + +/** + * Encapsulates native information for the current project, i.e. a combination of the + * NativeBuildVariant and information about the current project such as its build directory. + */ +data class NativeBuildInformation( + val buildDir: Provider, + private val variant: NativeBuildVariant +) { + /** Classifier used for jars etc */ + val mavenClassifier: String = "${variant.os}-${variant.mavenArch}" + + /** Target platform name as used for Gradle native builds */ + val targetPlatform: String = "${variant.os}_${variant.gradleArch}" + + /** Directory for native resources for this builds */ + val nativeResourcesDir: String + get() = File(buildDir.get().asFile, "$mavenClassifier/native-resources").absolutePath + + /** Directory for jar resources for this builds */ + val jarNativeResourcesDir: String + get() = File(nativeResourcesDir, "META-INF/native").absolutePath + + /** Name of the native library directory in $BORINGSSL_HOME */ + val boringBuildDir + get() = variant.boringBuildDir + + override fun toString(): String = + "NativeBuildInfo" +} + +/** + * Replacement for the Groovy NativeBuildInfo enum using the above classes. + * + * Needs to be instantiated with the buildDir of the current build and then makes this + * available where needed in NativeBuildInformation objects. + */ +class NativeBuildResolver(private val buildDir: Provider) { + // Wraps an immutable NativeBuildVariant with project information for this build */ + private fun wrap(variant: NativeBuildVariant?) = variant?.let { + NativeBuildInformation(buildDir, it) + } ?: error("Null build variant") + + + /** + * Returns a NativeBuildInformation for the provided Maven architecture in the current project. + */ + fun find(os: String, arch: String) = wrap(NativeBuildVariant.find(os, arch)) + + /** + * Returns a NativeBuildInformation for the provided Gradle NativePlatform in the current project. + */ + fun find(nativePlatform: NativePlatform) = wrap( + NativeBuildVariant.findForGradle( + nativePlatform.operatingSystem.name, + nativePlatform.architecture.name + ) + ) + + /** + * Returns a list of NativeBuildInformation for all the architectures buildable on a + * particular host OS and Architecture. + */ + fun findAll(os: String, arch: String): List = + NativeBuildVariant.findAll(os, arch).map { + NativeBuildInformation(buildDir, it) + } +} diff --git a/buildSrc/src/test/kotlin/NativeBuildResolverTest.kt b/buildSrc/src/test/kotlin/NativeBuildResolverTest.kt new file mode 100644 index 000000000..e41e52810 --- /dev/null +++ b/buildSrc/src/test/kotlin/NativeBuildResolverTest.kt @@ -0,0 +1,132 @@ +import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform +import org.gradle.testfixtures.ProjectBuilder +import java.nio.file.Files +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue + +class NativeBuildResolverTest { + // Verify lookup by Maven osdetector names (os + maven arch). + @Test + fun findByOsdetectorExact() { + assertEquals( + NativeBuildVariant.OSX_ARM64, + NativeBuildVariant.find("osx", "aarch_64") + ) + assertEquals( + NativeBuildVariant.LINUX_X64, + NativeBuildVariant.find("linux", "x86_64") + ) + } + + // Verify lookup by Gradle native platform names (os + gradle arch). + @Test + fun findByGradleExact() { + assertEquals( + NativeBuildVariant.OSX_X64, + NativeBuildVariant.findForGradle("osx", "x86-64") + ) + assertEquals( + NativeBuildVariant.OSX_ARM64, + NativeBuildVariant.findForGradle("osx", "aarch64") + ) + } + + // Unknown or unsupported platform/arch combinations return null. + @Test + fun findUnknownReturnsNull() { + assertNull(NativeBuildVariant.find("linux", "armv7")) + assertNull(NativeBuildVariant.findForGradle("windows", "aarch64")) + } + + // macOS returns both x86_64 and aarch64 variants (cross-compile). + @Test + fun findAllByOs() { + val osx = NativeBuildVariant.findAll("osx", "aarch_64").toSet() + assertEquals(setOf(NativeBuildVariant.OSX_X64, NativeBuildVariant.OSX_ARM64), osx) + } + + // Linux returns only the matching variant (no cross-compile (yet)). + @Test + fun findAllLinuxReturnsSingleVariant() { + val linux = NativeBuildVariant.findAll("linux", "x86_64") + assertEquals(listOf(NativeBuildVariant.LINUX_X64), linux) + } + + // Unknown OS returns an empty list. + @Test + fun findAllUnknownReturnsEmpty() { + assertTrue(NativeBuildVariant.findAll("solaris", "sparc").isEmpty()) + } + + // Windows x86_64 variant is resolvable. + @Test + fun findWindowsVariant() { + assertEquals( + NativeBuildVariant.WINDOWS_X64, + NativeBuildVariant.find("windows", "x86_64") + ) + } + + // Maven classifier, Gradle target, and BoringSSL dir strings are correct. + @Test + fun computedStringsAreStable() { + assertEquals( + "osx-aarch_64", + NativeBuildVariant.OSX_ARM64.let { "${it.os}-${it.mavenArch}" }) + assertEquals( + "osx_aarch64", + NativeBuildVariant.OSX_ARM64.let { "${it.os}_${it.gradleArch}" }) + assertEquals("build.arm", NativeBuildVariant.OSX_ARM64.boringBuildDir) + assertEquals("build64", NativeBuildVariant.LINUX_X64.boringBuildDir) + } + + // NativeBuildInformation derives resource paths from project build dir. + @Test + fun directoriesAreDerivedCorrectlyFromBuilddir() { + val tmp = Files.createTempDirectory("nativeBuildTest").toFile().apply { deleteOnExit() } + val project = ProjectBuilder.builder().withProjectDir(tmp).build() + val info = NativeBuildInformation(project.layout.buildDirectory, NativeBuildVariant.OSX_X64) + + assertTrue( + info.nativeResourcesDir.replace('\\', '/') + .endsWith("osx-x86_64/native-resources") + ) + assertTrue( + info.jarNativeResourcesDir.replace('\\', '/') + .endsWith("osx-x86_64/native-resources/META-INF/native") + ) + assertEquals("osx-x86_64", info.mavenClassifier) + assertEquals("osx_x86-64", info.targetPlatform) + } + + // NativeBuildResolver maps a Gradle NativePlatform to the correct variant. + @Test + fun resolverFindsByNativePlatform() { + val project = ProjectBuilder.builder().build() + val resolver = NativeBuildResolver(project.layout.buildDirectory) + val platform = DefaultNativePlatform("osx_aarch64").apply { + operatingSystem("osx") + architecture("aarch64") + } + + val info = resolver.find(platform) + assertNotNull(info) + assertEquals("osx-aarch_64", info.mavenClassifier) + assertEquals("osx_aarch64", info.targetPlatform) + } + + // NativeBuildResolver wraps variants with project build directory. + @Test + fun resolverWrapsVariants() { + val project = ProjectBuilder.builder().build() + val resolver = NativeBuildResolver(project.layout.buildDirectory) + + // There should only be a single Linux variant for now. + val info = resolver.findAll("linux", "x86_64").single() + assertEquals("linux-x86_64", info.mavenClassifier) + assertEquals(project.layout.buildDirectory.get().asFile, info.buildDir.get().asFile) + } +}