Browse Source

Improve error messages when checking tasks (#3194)

* Improve error messages when checking tasks

Previously some errors in checkRuntime task
were reported as a nested exception.
By default, Gradle shows only top-level
error message of an exception, which
made some errors confusing.
For example, when javac was missing from JDK,
Gradle only showed "Could not infer Java runtime version for Java home directory".
The part that said javac was missing was only shown,
when Gradle was run with --stacktrace argument.
This is suboptimal UX, so this commit refactors
checkRuntime to make error messages more descriptive.

#3133

* Handle JDK 1.8 correctly

* Prebuild jdk version probe
pull/3220/head
Alexey Tsvetkov 2 years ago committed by GitHub
parent
commit
0319db1d06
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      gradle-plugins/build.gradle.kts
  2. 12
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureJvmApplication.kt
  3. 128
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractCheckNativeDistributionRuntime.kt
  4. 29
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/preview/tasks/AbstractConfigureDesktopPreviewTask.kt
  5. 8
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/web/internal/configureExperimentalWebApplication.kt
  6. 26
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/gradleUtils.kt
  7. 14
      gradle-plugins/jdk-version-probe/build.gradle.kts
  8. 38
      gradle-plugins/jdk-version-probe/src/main/java/org/jetbrains/compose/desktop/application/internal/JdkVersionProbe.java
  9. 1
      gradle-plugins/settings.gradle.kts

5
gradle-plugins/build.gradle.kts

@ -128,7 +128,10 @@ fun Project.configureGradlePlugin(
} }
tasks.register("publishToMavenLocal") { tasks.register("publishToMavenLocal") {
val publishToMavenLocal = this
for (subproject in subprojects) { for (subproject in subprojects) {
dependsOn(subproject.tasks.named("publishToMavenLocal")) subproject.plugins.withId("maven-publish") {
publishToMavenLocal.dependsOn("${subproject.path}:publishToMavenLocal")
}
} }
} }

12
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureJvmApplication.kt

@ -17,6 +17,7 @@ import org.jetbrains.compose.desktop.application.dsl.TargetFormat
import org.jetbrains.compose.desktop.application.internal.validation.validatePackageVersions import org.jetbrains.compose.desktop.application.internal.validation.validatePackageVersions
import org.jetbrains.compose.desktop.application.tasks.* import org.jetbrains.compose.desktop.application.tasks.*
import org.jetbrains.compose.desktop.tasks.AbstractUnpackDefaultComposeApplicationResourcesTask import org.jetbrains.compose.desktop.tasks.AbstractUnpackDefaultComposeApplicationResourcesTask
import org.jetbrains.compose.internal.utils.*
import org.jetbrains.compose.internal.utils.OS import org.jetbrains.compose.internal.utils.OS
import org.jetbrains.compose.internal.utils.currentOS import org.jetbrains.compose.internal.utils.currentOS
import org.jetbrains.compose.internal.utils.currentTarget import org.jetbrains.compose.internal.utils.currentTarget
@ -65,7 +66,12 @@ private fun JvmApplicationContext.configureCommonJvmDesktopTasks(): CommonJvmDes
taskNameAction = "check", taskNameAction = "check",
taskNameObject = "runtime" taskNameObject = "runtime"
) { ) {
javaHome.set(app.javaHomeProvider) jdkHome.set(app.javaHomeProvider)
jdkVersionProbeJar.from(
project.detachedComposeGradleDependency(
artifactId = "gradle-plugin-internal-jdk-version-probe"
).excludeTransitiveDependencies()
)
} }
val suggestRuntimeModules = tasks.register<AbstractSuggestModulesTask>( val suggestRuntimeModules = tasks.register<AbstractSuggestModulesTask>(
@ -249,9 +255,7 @@ private fun JvmApplicationContext.configureProguardTask(
mainClass.set(app.mainClass) mainClass.set(app.mainClass)
proguardVersion.set(settings.version) proguardVersion.set(settings.version)
proguardFiles.from(proguardVersion.map { proguardVersion -> proguardFiles.from(proguardVersion.map { proguardVersion ->
project.configurations.detachedConfiguration( project.detachedDependency(groupId = "com.guardsquare", artifactId = "proguard-gradle", version = proguardVersion)
project.dependencies.create("com.guardsquare:proguard-gradle:${proguardVersion}")
)
}) })
configurationFiles.from(settings.configurationFiles) configurationFiles.from(settings.configurationFiles)
// ProGuard uses -dontobfuscate option to turn off obfuscation, which is enabled by default // ProGuard uses -dontobfuscate option to turn off obfuscation, which is enabled by default

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

@ -5,7 +5,7 @@
package org.jetbrains.compose.desktop.application.tasks package org.jetbrains.compose.desktop.application.tasks
import org.gradle.api.file.Directory import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFile import org.gradle.api.file.RegularFile
import org.gradle.api.provider.Property import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider import org.gradle.api.provider.Provider
@ -16,7 +16,6 @@ import org.jetbrains.compose.internal.utils.ioFile
import org.jetbrains.compose.internal.utils.notNullProperty import org.jetbrains.compose.internal.utils.notNullProperty
import org.jetbrains.compose.desktop.application.internal.ExternalToolRunner import org.jetbrains.compose.desktop.application.internal.ExternalToolRunner
import org.jetbrains.compose.desktop.tasks.AbstractComposeDesktopTask import org.jetbrains.compose.desktop.tasks.AbstractComposeDesktopTask
import org.jetbrains.compose.internal.utils.clearDirs
import java.io.File import java.io.File
// __COMPOSE_NATIVE_DISTRIBUTIONS_MIN_JAVA_VERSION__ // __COMPOSE_NATIVE_DISTRIBUTIONS_MIN_JAVA_VERSION__
@ -24,53 +23,67 @@ internal const val MIN_JAVA_RUNTIME_VERSION = 17
@CacheableTask @CacheableTask
abstract class AbstractCheckNativeDistributionRuntime : AbstractComposeDesktopTask() { abstract class AbstractCheckNativeDistributionRuntime : AbstractComposeDesktopTask() {
@get:Classpath
val jdkVersionProbeJar: ConfigurableFileCollection = objects.fileCollection()
@get:PathSensitive(PathSensitivity.ABSOLUTE) @get:PathSensitive(PathSensitivity.ABSOLUTE)
@get:InputDirectory @get:InputDirectory
val javaHome: Property<String> = objects.notNullProperty() val jdkHome: Property<String> = objects.notNullProperty()
private val taskDir = project.layout.buildDirectory.dir("compose/tmp/$name") private val taskDir = project.layout.buildDirectory.dir("compose/tmp/$name")
@get:OutputFile @get:OutputFile
val javaRuntimePropertiesFile: Provider<RegularFile> = taskDir.map { it.file("properties.bin") } val javaRuntimePropertiesFile: Provider<RegularFile> = taskDir.map { it.file("properties.bin") }
@get:LocalState private val jdkHomeFile: File
val workingDir: Provider<Directory> = taskDir.map { it.dir("localState") } get() = File(jdkHome.orNull ?: error("Missing jdkHome value"))
private fun File.getJdkTool(toolName: String): File =
resolve("bin/${executableName(toolName)}")
private fun ensureToolsExist(vararg tools: File) {
val missingTools = tools.filter { !it.exists() }.map { "'${it.name}'" }
private val javaExec: File if (missingTools.isEmpty()) return
get() = getTool("java")
private val javacExec: File if (missingTools.size == 1) jdkDistributionProbingError("${missingTools.single()} is missing")
get() = getTool("javac")
private fun getTool(toolName: String): File { jdkDistributionProbingError("${missingTools.joinToString(", ")} are missing")
val javaHomeBin = File(javaHome.get()).resolve("bin") }
val tool = javaHomeBin.resolve(executableName(toolName))
check(tool.exists()) { "Could not find $tool at: ${tool.absolutePath}}" } private fun jdkDistributionProbingError(errorMessage: String): Nothing {
return tool val fullErrorMessage = buildString {
appendLine("Failed to check JDK distribution: $errorMessage")
appendLine("JDK distribution path: ${jdkHomeFile.absolutePath}")
}
error(fullErrorMessage)
} }
@TaskAction @TaskAction
fun run() { fun run() {
taskDir.ioFile.mkdirs() taskDir.ioFile.mkdirs()
val javaRuntimeVersion = try { val jdkHome = jdkHomeFile
getJavaRuntimeVersionUnsafe()?.toIntOrNull() ?: -1 val javaExecutable = jdkHome.getJdkTool("java")
} catch (e: Exception) { val jlinkExecutable = jdkHome.getJdkTool("jlink")
throw IllegalStateException( val jpackageExecutabke = jdkHome.getJdkTool("jpackage")
"Could not infer Java runtime version for Java home directory: ${javaHome.get()}", e ensureToolsExist(javaExecutable, jlinkExecutable, jpackageExecutabke)
)
} val jvmRuntimeVersionString = getJavaRuntimeVersion(javaExecutable)
check(javaRuntimeVersion >= MIN_JAVA_RUNTIME_VERSION) { val jvmRuntimeVersion = jvmRuntimeVersionString?.toIntOrNull()
"""|Packaging native distributions requires JDK runtime version >= $MIN_JAVA_RUNTIME_VERSION ?: jdkDistributionProbingError("JDK version '$jvmRuntimeVersionString' has unexpected format")
|Actual version: '${javaRuntimeVersion ?: "<unknown>"}'
|Java home: ${javaHome.get()} check(jvmRuntimeVersion >= MIN_JAVA_RUNTIME_VERSION) {
""".trimMargin() jdkDistributionProbingError(
"minimum required JDK version is '$MIN_JAVA_RUNTIME_VERSION', " +
"but actual version is '$jvmRuntimeVersion'"
)
} }
val modules = arrayListOf<String>() val modules = arrayListOf<String>()
runExternalTool( runExternalTool(
tool = javaExec, tool = javaExecutable,
args = listOf("--list-modules"), args = listOf("--list-modules"),
logToConsole = ExternalToolRunner.LogToConsole.Never, logToConsole = ExternalToolRunner.LogToConsole.Never,
processStdout = { stdout -> processStdout = { stdout ->
@ -83,67 +96,16 @@ abstract class AbstractCheckNativeDistributionRuntime : AbstractComposeDesktopTa
} }
) )
val properties = JvmRuntimeProperties(javaRuntimeVersion, modules) val properties = JvmRuntimeProperties(jvmRuntimeVersion, modules)
JvmRuntimeProperties.writeToFile(properties, javaRuntimePropertiesFile.ioFile) JvmRuntimeProperties.writeToFile(properties, javaRuntimePropertiesFile.ioFile)
} }
private fun getJavaRuntimeVersionUnsafe(): String? { private fun getJavaRuntimeVersion(javaExecutable: File): String? {
fileOperations.clearDirs(workingDir)
val workingDir = workingDir.ioFile
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 var javaRuntimeVersion: String? = null
runExternalTool( runExternalTool(
tool = javaExec, tool = javaExecutable,
args = listOf("-cp", classFilesDir.absolutePath, printJavaRuntimeClassName), args = listOf("-jar", jdkVersionProbeJar.files.single().absolutePath),
processStdout = { stdout -> processStdout = { stdout -> javaRuntimeVersion = stdout.trim() }
val m = "$javaVersionPrefix(.+)$javaVersionSuffix".toRegex().find(stdout)
javaRuntimeVersion = m?.groupValues?.get(1)
}
) )
return javaRuntimeVersion return javaRuntimeVersion
} }

29
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/preview/tasks/AbstractConfigureDesktopPreviewTask.kt

@ -6,12 +6,12 @@ import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider import org.gradle.api.provider.Provider
import org.gradle.api.tasks.* import org.gradle.api.tasks.*
import org.jetbrains.compose.ComposeBuildConfig import org.jetbrains.compose.desktop.tasks.AbstractComposeDesktopTask
import org.jetbrains.compose.desktop.ui.tooling.preview.rpc.*
import org.jetbrains.compose.internal.utils.*
import org.jetbrains.compose.internal.utils.currentTarget import org.jetbrains.compose.internal.utils.currentTarget
import org.jetbrains.compose.internal.utils.javaExecutable import org.jetbrains.compose.internal.utils.javaExecutable
import org.jetbrains.compose.internal.utils.notNullProperty import org.jetbrains.compose.internal.utils.notNullProperty
import org.jetbrains.compose.desktop.tasks.AbstractComposeDesktopTask
import org.jetbrains.compose.desktop.ui.tooling.preview.rpc.*
import java.io.File import java.io.File
abstract class AbstractConfigureDesktopPreviewTask : AbstractComposeDesktopTask() { abstract class AbstractConfigureDesktopPreviewTask : AbstractComposeDesktopTask() {
@ -37,14 +37,15 @@ abstract class AbstractConfigureDesktopPreviewTask : AbstractComposeDesktopTask(
project.providers.gradleProperty("compose.desktop.preview.ide.port") project.providers.gradleProperty("compose.desktop.preview.ide.port")
@get:InputFiles @get:InputFiles
internal val uiTooling: FileCollection = project.configurations.detachedConfiguration( internal val uiTooling: FileCollection =
project.dependencies.create("org.jetbrains.compose.ui:ui-tooling-desktop:${ComposeBuildConfig.composeVersion}") project.detachedComposeDependency(
).apply { isTransitive = false } groupId = "org.jetbrains.compose.ui",
artifactId = "ui-tooling-desktop",
).excludeTransitiveDependencies()
@get:InputFiles @get:InputFiles
internal val hostClasspath: FileCollection = project.configurations.detachedConfiguration( internal val hostClasspath: FileCollection =
project.dependencies.create("org.jetbrains.compose:preview-rpc:${ComposeBuildConfig.composeVersion}") project.detachedComposeGradleDependency(artifactId = "preview-rpc")
)
@TaskAction @TaskAction
fun run() { fun run() {
@ -95,10 +96,12 @@ abstract class AbstractConfigureDesktopPreviewTask : AbstractComposeDesktopTask(
} }
if (hasSkikoJvmRuntime) return emptyList() if (hasSkikoJvmRuntime) return emptyList()
if (hasSkikoJvm && skikoVersion != null && skikoVersion.isNotBlank()) { if (hasSkikoJvm && !skikoVersion.isNullOrBlank()) {
val skikoRuntimeConfig = project.configurations.detachedConfiguration( val skikoRuntimeConfig = project.detachedDependency(
project.dependencies.create("org.jetbrains.skiko:skiko-awt-runtime-${currentTarget.id}:$skikoVersion") groupId = "org.jetbrains.skiko",
).apply { isTransitive = false } artifactId = "skiko-awt-runtime-${currentTarget.id}",
version = skikoVersion
).excludeTransitiveDependencies()
return skikoRuntimeConfig.files return skikoRuntimeConfig.files
} }
} catch (e: Exception) { } catch (e: Exception) {

8
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/web/internal/configureExperimentalWebApplication.kt

@ -12,8 +12,9 @@ import org.gradle.api.artifacts.UnresolvedDependency
import org.gradle.api.provider.Provider import org.gradle.api.provider.Provider
import org.jetbrains.compose.ComposeBuildConfig import org.jetbrains.compose.ComposeBuildConfig
import org.jetbrains.compose.experimental.dsl.ExperimentalWebApplication import org.jetbrains.compose.experimental.dsl.ExperimentalWebApplication
import org.jetbrains.compose.internal.utils.registerTask
import org.jetbrains.compose.experimental.web.tasks.ExperimentalUnpackSkikoWasmRuntimeTask import org.jetbrains.compose.experimental.web.tasks.ExperimentalUnpackSkikoWasmRuntimeTask
import org.jetbrains.compose.internal.utils.*
import org.jetbrains.compose.internal.utils.registerTask
import org.jetbrains.compose.internal.utils.uppercaseFirstChar import org.jetbrains.compose.internal.utils.uppercaseFirstChar
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget
@ -43,8 +44,9 @@ private const val SKIKO_GROUP = "org.jetbrains.skiko"
private fun skikoVersionProvider(project: Project): Provider<String> { private fun skikoVersionProvider(project: Project): Provider<String> {
val composeVersion = ComposeBuildConfig.composeVersion val composeVersion = ComposeBuildConfig.composeVersion
val configurationWithSkiko = project.configurations.detachedConfiguration( val configurationWithSkiko = project.detachedComposeDependency(
project.dependencies.create("org.jetbrains.compose.ui:ui-graphics:$composeVersion") artifactId = "ui-graphics",
groupId = "org.jetbrains.compose.ui"
) )
return project.provider { return project.provider {
val skikoDependency = configurationWithSkiko.allDependenciesDescriptors.firstOrNull(::isSkikoDependency) val skikoDependency = configurationWithSkiko.allDependenciesDescriptors.firstOrNull(::isSkikoDependency)

26
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/gradleUtils.kt

@ -6,7 +6,9 @@
package org.jetbrains.compose.internal.utils package org.jetbrains.compose.internal.utils
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.logging.Logger import org.gradle.api.logging.Logger
import org.jetbrains.compose.ComposeBuildConfig
import java.util.* import java.util.*
internal inline fun Logger.info(fn: () -> String) { internal inline fun Logger.info(fn: () -> String) {
@ -35,3 +37,27 @@ fun Project.getLocalProperty(key: String): String? {
return null return null
} }
} }
internal fun Project.detachedComposeGradleDependency(
artifactId: String,
groupId: String = "org.jetbrains.compose",
): Configuration =
detachedDependency(groupId = groupId, artifactId = artifactId, version = ComposeBuildConfig.composeGradlePluginVersion)
internal fun Project.detachedComposeDependency(
artifactId: String,
groupId: String = "org.jetbrains.compose",
): Configuration =
detachedDependency(groupId = groupId, artifactId = artifactId, version = ComposeBuildConfig.composeVersion)
internal fun Project.detachedDependency(
groupId: String,
artifactId: String,
version: String
): Configuration =
project.configurations.detachedConfiguration(
project.dependencies.create("$groupId:$artifactId:$version")
)
internal fun Configuration.excludeTransitiveDependencies(): Configuration =
apply { isTransitive = false }

14
gradle-plugins/jdk-version-probe/build.gradle.kts

@ -0,0 +1,14 @@
plugins {
java
id("maven-publish")
}
mavenPublicationConfig {
displayName = "JDK version probe"
description = "JDK version probe (Internal)"
artifactId = "gradle-plugin-internal-jdk-version-probe"
}
tasks.jar.configure {
manifest.attributes["Main-Class"] = "org.jetbrains.compose.desktop.application.internal.JdkVersionProbe"
}

38
gradle-plugins/jdk-version-probe/src/main/java/org/jetbrains/compose/desktop/application/internal/JdkVersionProbe.java

@ -0,0 +1,38 @@
/*
* Copyright 2020-2023 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
package org.jetbrains.compose.desktop.application.internal;
import java.lang.reflect.Method;
public class JdkVersionProbe {
public static void main(String[] args) {
Class<Runtime> runtimeClass = Runtime.class;
try {
Method version = runtimeClass.getMethod("version");
Object runtimeVer = version.invoke(runtimeClass);
Class<?> 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) {
String javaVersion = System.getProperty("java.version");
String[] parts = javaVersion.split("\\.");
if (parts.length > 2 && "1".equalsIgnoreCase(parts[0])) {
printVersionAndHalt(parts[1]);
} else {
throw new IllegalStateException("Could not determine JDK version from string: '" + javaVersion + "'");
}
}
}
private static void printVersionAndHalt(String version) {
System.out.println(version);
Runtime.getRuntime().exit(0);
}
}

1
gradle-plugins/settings.gradle.kts

@ -7,3 +7,4 @@ pluginManagement {
include(":compose") include(":compose")
include(":preview-rpc") include(":preview-rpc")
include(":jdk-version-probe")

Loading…
Cancel
Save