From e26b421e6c150342d00da577263d111d35a2e311 Mon Sep 17 00:00:00 2001 From: Ilya Chernikov Date: Mon, 11 Sep 2017 15:20:43 +0200 Subject: [PATCH] Add JDK detection --- build.gradle.kts | 13 +- buildSrc/build.gradle.kts | 3 + buildSrc/src/main/kotlin/jdksFinder.kt | 183 +++++++++++++++++++++++++ 3 files changed, 197 insertions(+), 2 deletions(-) create mode 100644 buildSrc/src/main/kotlin/jdksFinder.kt diff --git a/build.gradle.kts b/build.gradle.kts index 4bcbeaff98f..df910a735ca 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -38,6 +38,13 @@ buildScan { setLicenseAgree("yes") } +val configuredJdks: List = + getConfiguredJdks().also { + it.forEach { + logger.info("Using ${it.majorVersion} home: ${it.homeDir}") + } + } + val buildNumber = "1.1-SNAPSHOT" extra["build.number"] = buildNumber @@ -288,8 +295,10 @@ tasks { } fun jdkPath(version: String): String { - val varName = "JDK_${version.replace(".", "")}" - return System.getenv(varName) ?: throw GradleException ("Please set environment variable $varName to point to JDK $version installation") + val jdkName = "JDK_${version.replace(".", "")}" + val jdkMajorVersion = JdkMajorVersion.valueOf(jdkName) + return configuredJdks.find { it.majorVersion == jdkMajorVersion }?.homeDir?.canonicalPath + ?: throw GradleException ("Please set environment variable $jdkName to point to JDK $version installation") } fun Project.configureJvmProject(javaHome: String, javaVersion: String) { diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 7514686354c..653e81fc6ee 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -29,9 +29,12 @@ repositories { System.getProperty("bootstrap.kotlin.repo")?.let { maven { setUrl(it) } } +// maven { setUrl("https://repo.gradle.org/gradle/libs-releases-local") } } dependencies { + compile(files("../dependencies/native-platform-uberjar.jar")) +// compile("net.rubygrapefruit:native-platform:0.14") // TODO: adding the dep to the plugin breaks the build unexpectedly, resolve and uncomment // compile("org.jetbrains.kotlin:kotlin-gradle-plugin:${rootProject.extra["bootstrap_kotlin_version"]}") } diff --git a/buildSrc/src/main/kotlin/jdksFinder.kt b/buildSrc/src/main/kotlin/jdksFinder.kt new file mode 100644 index 00000000000..d8677660167 --- /dev/null +++ b/buildSrc/src/main/kotlin/jdksFinder.kt @@ -0,0 +1,183 @@ +@file:Suppress("unused") // usages in build scripts are not tracked properly + +import net.rubygrapefruit.platform.Native +import net.rubygrapefruit.platform.WindowsRegistry +import org.gradle.api.GradleException +import org.gradle.api.Project +import java.io.File +import net.rubygrapefruit.platform.WindowsRegistry.Key.HKEY_LOCAL_MACHINE +import org.gradle.internal.os.OperatingSystem + +enum class JdkMajorVersion { + JDK_16, JDK_17, JDK_18, JDK_9 +} + +val jdkAlternativeVarNames = mapOf(JdkMajorVersion.JDK_9 to listOf("JDK_19")) + +data class JdkId(val explicit: Boolean, val majorVersion: JdkMajorVersion, var version: String, var homeDir: File) + +fun Project.getConfiguredJdks(): List { + val res = arrayListOf() + for (jdkMajorVersion in JdkMajorVersion.values()) { + val explicitJdkEnvVal = System.getenv(jdkMajorVersion.name) + ?: jdkAlternativeVarNames[jdkMajorVersion]?.mapNotNull { System.getenv(it) }?.firstOrNull() + ?: continue + val explicitJdk = File(explicitJdkEnvVal) + if (!explicitJdk.isDirectory) { + throw GradleException("Invalid environment value $jdkMajorVersion: $explicitJdkEnvVal, expecting JDK home path") + } + res.add(JdkId(true, jdkMajorVersion, "X", explicitJdk)) + } + if (res.size < JdkMajorVersion.values().size) { + res.discoverJdks(this) + } + return res +} + +// see JEP 223 +private val javaMajorVersionRegex = Regex("""(?:1\.)?(\d+).*""") +private val javaVersionRegex = Regex("""(?:1\.)?(\d+)(\.\d+)?([+-_]\w+){0,3}""") + +fun MutableCollection.addIfBetter(project: Project, version: String, id: String, homeDir: File): Boolean { + val matchString = javaMajorVersionRegex.matchEntire(version)?.groupValues?.get(1) + val majorJersion = when (matchString) { + "6" -> JdkMajorVersion.JDK_16 + "7" -> JdkMajorVersion.JDK_17 + "8" -> JdkMajorVersion.JDK_18 + "9" -> JdkMajorVersion.JDK_9 + else -> { + project.logger.info("Cannot recognize version string '$version' (found version '$matchString')") + return false + } + } + val prev = find { it.majorVersion == majorJersion } + if (prev == null) { + add(JdkId(false, majorJersion, version, homeDir)) + return true + } + if (prev.explicit) return false + val versionsComparisonRes = compareVersions(prev.version, version) + if (versionsComparisonRes < 0 || (versionsComparisonRes == 0 && id.contains("64"))) { // prefer 64-bit + prev.version = version + prev.homeDir = homeDir + return true + } + return false +} + +private fun compareVersions(left: String, right: String): Int { + if (left == right) return 0 + fun MatchResult.extractNumVer(): List = + groups.drop(2).map { + it?.value?.filter { it in '0'..'9' }?.toIntOrNull() ?: 0 + } + val lmi = (javaVersionRegex.matchEntire(left)?.extractNumVer() ?: emptyList()).iterator() + val rmi = (javaVersionRegex.matchEntire(right)?.extractNumVer() ?: emptyList()).iterator() + while (lmi.hasNext() && rmi.hasNext()) { + val l = lmi.next() + val r = rmi.next() + when { + l < r -> return -1 + l > r -> return 1 + } + } + return when { + rmi.hasNext() -> -1 + lmi.hasNext() -> 1 + else -> 0 + } +} + +fun MutableCollection.discoverJdks(project: Project) { + val os = OperatingSystem.current() + when { + os.isWindows -> discoverJdksOnWindows(project) + os.isMacOsX -> discoverJdksOnMacOS(project) + else -> discoverJdksOnUnix(project) + } +} + +private val macOsJavaHomeOutRegexes = listOf(Regex("""\s+(\S+),\s+(\S+):\s+".*?"\s+(.+)"""), + Regex("""\s+(\S+)\s+\((.*?)\):\s+(.+)""")) + +fun MutableCollection.discoverJdksOnMacOS(project: Project) { + val procBuilder = ProcessBuilder("/usr/libexec/java_home", "-V").redirectErrorStream(true) + val process = procBuilder.start() + val retCode = process.waitFor() + if (retCode != 0) throw GradleException("Unable to run 'java_home', return code $retCode") + process.inputStream.bufferedReader().forEachLine { line -> + for (rex in macOsJavaHomeOutRegexes) { + val matchResult = rex.matchEntire(line) + if (matchResult != null) { + addIfBetter(project, matchResult.groupValues[1], matchResult.groupValues[0], File(matchResult.groupValues[3])) + break + } + } + } +} + +private val unixConventionalJdkLocations = listOf( + "/usr/lib/jvm", // *deb, Arch + "/opt", // *rpm, Gentoo, HP/UX + "/usr/lib", // Slackware 32 + "/usr/lib64", // Slackware 64 + "/usr/local", // OpenBSD, FreeBSD + "/usr/pkg/java", // NetBSD + "/usr/jdk/instances") // Solaris + +private val unixConventionalJdkDirRex = Regex("jdk|jre|java|zulu") + +fun MutableCollection.discoverJdksOnUnix(project: Project) { + for (loc in unixConventionalJdkLocations) { + val installedJdks = File(loc).listFiles { dir -> + dir.isDirectory && + unixConventionalJdkDirRex.containsMatchIn(dir.name) && + File(dir, "bin", "java").isFile + } ?: continue + for (dir in installedJdks) { + val versionMatch = javaVersionRegex.find(dir.name) + if (versionMatch == null) { + project.logger.info("Unable to extract version from possible JDK dir: $dir") + } + else { + addIfBetter(project, versionMatch.value, dir.name, dir) + } + } + } +} + +private val windowsConventionalJdkRegistryPaths = listOf( + "SOFTWARE\\JavaSoft\\Java Development Kit", + "SOFTWARE\\Wow6432Node\\JavaSoft\\Java Development Kit") + +fun MutableCollection.discoverJdksOnWindows(project: Project) { + val registry = Native.get(WindowsRegistry::class.java) + for (regPath in windowsConventionalJdkRegistryPaths) { + val jdkKeys = try { + registry.getSubkeys(HKEY_LOCAL_MACHINE, regPath) + } catch (e: RuntimeException) { + // ignore missing nodes + continue + } + for (jdkKey in jdkKeys) { + try { + val javaHome = registry.getStringValue(HKEY_LOCAL_MACHINE, regPath + "\\" + jdkKey, "JavaHome") + val versionMatch = javaVersionRegex.find(jdkKey) + if (versionMatch == null) { + project.logger.info("Unable to extract version from possible JDK location: $javaHome ($jdkKey)") + } + else { + javaHome.takeIf { it.isNotEmpty() } + ?.let { File(it) } + ?.takeIf { it.isDirectory && File(it, "bin", "java.exe").isFile } + ?.let { + addIfBetter(project, versionMatch.value, jdkKey, it) + } + } + } + catch (e: RuntimeException) { + // Ignore + } + } + } +}