Browse Source

Require JDK 15+ for creating native distributions

Resolves #428
pull/456/head
Alexey Tsvetkov 3 years ago committed by Alexey Tsvetkov
parent
commit
a99b3eaa83
  1. 8
      gradle-plugins/compose/build.gradle.kts
  2. 29
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/JavaRuntimeProperties.kt
  3. 10
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureApplication.kt
  4. 141
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractCheckNativeDistributionRuntime.kt
  5. 5
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractComposeDesktopTask.kt
  6. 2
      tutorials/Signing_and_notarization_on_macOS/README.md

8
gradle-plugins/compose/build.gradle.kts

@ -62,9 +62,9 @@ val jar = tasks.named<Jar>("jar") {
val minGradleVersionForTests = "6.4"
val maxGradleVersionForTests = "6.8.3"
val javaHomeForTests: String? = when {
JavaVersion.current() >= JavaVersion.VERSION_14 -> System.getProperty("java.home")
else -> System.getenv("JDK_14")
?: System.getenv("JDK_15")
// __COMPOSE_NATIVE_DISTRIBUTIONS_MIN_JAVA_VERSION__
JavaVersion.current() >= JavaVersion.VERSION_15 -> System.getProperty("java.home")
else -> System.getenv("JDK_15")
?: System.getenv("JDK_FOR_GRADLE_TESTS")
}
val isWindows = getCurrentOperatingSystem().isWindows
@ -94,7 +94,7 @@ fun Test.configureTest(gradleVersion: String) {
val executableFileName = if (isWindows) "java.exe" else "java"
executable = File(javaHomeForTests).resolve("bin/$executableFileName").absolutePath
} else {
doFirst { error("Use JDK 14+ to run tests or set up JDK_14/JDK_15/JDK_FOR_GRADLE_TESTS env. var") }
doFirst { error("Use JDK 15+ to run tests or set up JDK_15/JDK_FOR_GRADLE_TESTS env. var") }
}
}

29
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/JavaRuntimeProperties.kt

@ -0,0 +1,29 @@
package org.jetbrains.compose.desktop.application.internal
import java.io.File
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.io.Serializable
internal data class JavaRuntimeProperties(
val availableModules: List<String>
) : Serializable {
companion object {
@Suppress("unused")
private val serialVersionUid: Long = 0
fun writeToFile(properties: JavaRuntimeProperties, file: File) {
file.parentFile.mkdirs()
file.delete()
file.createNewFile()
ObjectOutputStream(file.outputStream().buffered()).use { oos ->
oos.writeObject(properties)
}
}
fun readFromFile(file: File): JavaRuntimeProperties =
ObjectInputStream(file.inputStream().buffered()).use { ois ->
ois.readObject() as JavaRuntimeProperties
}
}
}

10
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureApplication.kt

@ -54,9 +54,19 @@ internal fun Project.configureFromMppPlugin(mainApplication: Application) {
internal fun Project.configurePackagingTasks(apps: Collection<Application>) {
for (app in apps) {
val checkRuntime = tasks.composeTask<AbstractCheckNativeDistributionRuntime>(
taskName("checkRuntime", app)
) {
javaHome.set(provider { app.javaHomeOrDefault() })
javaRuntimePropertiesFile.set(
project.layout.buildDirectory.file("compose/tmp/${app.name}/runtime-properties/properties.bin")
)
}
val createRuntimeImage = tasks.composeTask<AbstractJLinkTask>(
taskName("createRuntimeImage", app)
) {
dependsOn(checkRuntime)
javaHome.set(provider { app.javaHomeOrDefault() })
modules.set(provider { app.nativeDistributions.modules })
destinationDir.set(project.layout.buildDirectory.dir("compose/tmp/${app.name}/runtime"))

141
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractCheckNativeDistributionRuntime.kt

@ -0,0 +1,141 @@
package org.jetbrains.compose.desktop.application.tasks
import org.gradle.api.file.Directory
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*
import org.jetbrains.compose.desktop.application.internal.JavaRuntimeProperties
import org.jetbrains.compose.desktop.application.internal.executableName
import org.jetbrains.compose.desktop.application.internal.ioFile
import org.jetbrains.compose.desktop.application.internal.notNullProperty
import java.io.File
// __COMPOSE_NATIVE_DISTRIBUTIONS_MIN_JAVA_VERSION__
private const val MIN_JAVA_RUNTIME_VERSION = 15
@CacheableTask
abstract class AbstractCheckNativeDistributionRuntime : AbstractComposeDesktopTask() {
@get:PathSensitive(PathSensitivity.ABSOLUTE)
@get:Input
val javaHome: Property<String> = objects.notNullProperty()
@get:OutputFile
val javaRuntimePropertiesFile: RegularFileProperty = objects.fileProperty()
@get:LocalState
val workingDir: Provider<Directory> = project.layout.buildDirectory.dir("compose/tmp/$name")
@get:Internal
private val javaExec: File
get() = getTool("java")
@get:Internal
private val javacExec: File
get() = getTool("javac")
private fun getTool(toolName: String): File {
val javaHomeBin = File(javaHome.get()).resolve("bin")
val tool = javaHomeBin.resolve(executableName(toolName))
check(tool.exists()) { "Could not find $tool at: ${tool.absolutePath}}" }
return tool
}
@TaskAction
fun run() {
val javaRuntimeVersion = try {
getJavaRuntimeVersionUnsafe()
} catch (e: Exception) {
throw IllegalStateException(
"Could not infer Java runtime version for Java home directory: ${javaHome.get()}", e
)
}
check((javaRuntimeVersion?.toIntOrNull() ?: -1) >= MIN_JAVA_RUNTIME_VERSION) {
"""|Packaging native distributions requires JDK runtime version >= $MIN_JAVA_RUNTIME_VERSION
|Actual version: '${javaRuntimeVersion ?: "<unknown>"}'
|Java home: ${javaHome.get()}
""".trimMargin()
}
val modules = arrayListOf<String>()
runExternalTool(
tool = javaExec,
args = listOf("--list-modules"),
forceLogToFile = true,
processStdout = { stdout ->
stdout.lineSequence().forEach { line ->
val moduleName = line.trim().substringBefore("@")
if (moduleName.isNotBlank()) {
modules.add(moduleName)
}
}
}
)
val properties = JavaRuntimeProperties(modules)
JavaRuntimeProperties.writeToFile(properties, javaRuntimePropertiesFile.ioFile)
}
private fun getJavaRuntimeVersionUnsafe(): String? {
val workingDir = workingDir.ioFile
fileOperations.delete(workingDir)
fileOperations.mkdir(workingDir)
val printJavaRuntimeClassName = "PrintJavaRuntimeVersion"
val javaVersionPrefix = "Java runtime version = '"
val javaVersionSuffix = "'"
val printJavaRuntimeJava = workingDir.resolve("java/$printJavaRuntimeClassName.java").apply {
parentFile.mkdirs()
writeText("""
import java.lang.reflect.Method;
public class $printJavaRuntimeClassName {
public static void main(String[] args) {
Class<Runtime> runtimeClass = Runtime.class;
try {
Method version = runtimeClass.getMethod("version");
Object runtimeVer = version.invoke(runtimeClass);
Class<? extends Object> runtimeVerClass = runtimeVer.getClass();
try {
int feature = (int) runtimeVerClass.getMethod("feature").invoke(runtimeVer);
printVersionAndHalt((Integer.valueOf(feature)).toString());
} catch (NoSuchMethodException e) {
int major = (int) runtimeVerClass.getMethod("major").invoke(runtimeVer);
printVersionAndHalt((Integer.valueOf(major)).toString());
}
} catch (Exception e) {
printVersionAndHalt(System.getProperty("java.version"));
}
}
private static void printVersionAndHalt(String version) {
System.out.println("$javaVersionPrefix" + version + "$javaVersionSuffix");
Runtime.getRuntime().exit(0);
}
}
""".trimIndent())
}
val classFilesDir = workingDir.resolve("out-classes")
runExternalTool(
tool = javacExec,
args = listOf(
"-source", "1.8",
"-target", "1.8",
"-d", classFilesDir.absolutePath,
printJavaRuntimeJava.absolutePath
)
)
var javaRuntimeVersion: String? = null
runExternalTool(
tool = javaExec,
args = listOf("-cp", classFilesDir.absolutePath, printJavaRuntimeClassName),
processStdout = { stdout ->
val m = "$javaVersionPrefix(.+)$javaVersionSuffix".toRegex().find(stdout)
javaRuntimeVersion = m?.groupValues?.get(1)
}
)
return javaRuntimeVersion
}
}

5
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractComposeDesktopTask.kt

@ -49,13 +49,14 @@ abstract class AbstractComposeDesktopTask : DefaultTask() {
environment: Map<String, Any> = emptyMap(),
workingDir: File? = null,
checkExitCodeIsNormal: Boolean = true,
processStdout: Function1<String, Unit>? = null
processStdout: Function1<String, Unit>? = null,
forceLogToFile: Boolean = false
): ExecResult {
val logsDir = logsDir.ioFile
logsDir.mkdirs()
val toolName = tool.nameWithoutExtension
val logToConsole = verbose.get()
val logToConsole = verbose.get() && !forceLogToFile
val outFile = logsDir.resolve("${toolName}-${currentTimeStamp()}-out.txt")
val errFile = logsDir.resolve("${toolName}-${currentTimeStamp()}-err.txt")

2
tutorials/Signing_and_notarization_on_macOS/README.md

@ -13,7 +13,7 @@ for distribution on macOS.
## Prerequisites
* [Xcode](https://developer.apple.com/xcode/). The tutorial was checked with Xcode 12.3.
* JDK 15+ (JDK 14 is not guaranteed to work). The tutorial was checked with OpenJDK 15.0.1.
* JDK 15+. The tutorial was checked with OpenJDK 15.0.1.
## Preparing a Developer ID certificate

Loading…
Cancel
Save