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 11 months 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") {
val publishToMavenLocal = this
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.tasks.*
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.currentOS
import org.jetbrains.compose.internal.utils.currentTarget
@ -65,7 +66,12 @@ private fun JvmApplicationContext.configureCommonJvmDesktopTasks(): CommonJvmDes
taskNameAction = "check",
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>(
@ -249,9 +255,7 @@ private fun JvmApplicationContext.configureProguardTask(
mainClass.set(app.mainClass)
proguardVersion.set(settings.version)
proguardFiles.from(proguardVersion.map { proguardVersion ->
project.configurations.detachedConfiguration(
project.dependencies.create("com.guardsquare:proguard-gradle:${proguardVersion}")
)
project.detachedDependency(groupId = "com.guardsquare", artifactId = "proguard-gradle", version = proguardVersion)
})
configurationFiles.from(settings.configurationFiles)
// 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
import org.gradle.api.file.Directory
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFile
import org.gradle.api.provider.Property
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.desktop.application.internal.ExternalToolRunner
import org.jetbrains.compose.desktop.tasks.AbstractComposeDesktopTask
import org.jetbrains.compose.internal.utils.clearDirs
import java.io.File
// __COMPOSE_NATIVE_DISTRIBUTIONS_MIN_JAVA_VERSION__
@ -24,53 +23,67 @@ internal const val MIN_JAVA_RUNTIME_VERSION = 17
@CacheableTask
abstract class AbstractCheckNativeDistributionRuntime : AbstractComposeDesktopTask() {
@get:Classpath
val jdkVersionProbeJar: ConfigurableFileCollection = objects.fileCollection()
@get:PathSensitive(PathSensitivity.ABSOLUTE)
@get:InputDirectory
val javaHome: Property<String> = objects.notNullProperty()
val jdkHome: Property<String> = objects.notNullProperty()
private val taskDir = project.layout.buildDirectory.dir("compose/tmp/$name")
@get:OutputFile
val javaRuntimePropertiesFile: Provider<RegularFile> = taskDir.map { it.file("properties.bin") }
@get:LocalState
val workingDir: Provider<Directory> = taskDir.map { it.dir("localState") }
private val jdkHomeFile: File
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
get() = getTool("java")
if (missingTools.isEmpty()) return
private val javacExec: File
get() = getTool("javac")
if (missingTools.size == 1) jdkDistributionProbingError("${missingTools.single()} is missing")
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
jdkDistributionProbingError("${missingTools.joinToString(", ")} are missing")
}
private fun jdkDistributionProbingError(errorMessage: String): Nothing {
val fullErrorMessage = buildString {
appendLine("Failed to check JDK distribution: $errorMessage")
appendLine("JDK distribution path: ${jdkHomeFile.absolutePath}")
}
error(fullErrorMessage)
}
@TaskAction
fun run() {
taskDir.ioFile.mkdirs()
val javaRuntimeVersion = try {
getJavaRuntimeVersionUnsafe()?.toIntOrNull() ?: -1
} catch (e: Exception) {
throw IllegalStateException(
"Could not infer Java runtime version for Java home directory: ${javaHome.get()}", e
)
}
val jdkHome = jdkHomeFile
val javaExecutable = jdkHome.getJdkTool("java")
val jlinkExecutable = jdkHome.getJdkTool("jlink")
val jpackageExecutabke = jdkHome.getJdkTool("jpackage")
ensureToolsExist(javaExecutable, jlinkExecutable, jpackageExecutabke)
val jvmRuntimeVersionString = getJavaRuntimeVersion(javaExecutable)
check(javaRuntimeVersion >= 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 jvmRuntimeVersion = jvmRuntimeVersionString?.toIntOrNull()
?: jdkDistributionProbingError("JDK version '$jvmRuntimeVersionString' has unexpected format")
check(jvmRuntimeVersion >= MIN_JAVA_RUNTIME_VERSION) {
jdkDistributionProbingError(
"minimum required JDK version is '$MIN_JAVA_RUNTIME_VERSION', " +
"but actual version is '$jvmRuntimeVersion'"
)
}
val modules = arrayListOf<String>()
runExternalTool(
tool = javaExec,
tool = javaExecutable,
args = listOf("--list-modules"),
logToConsole = ExternalToolRunner.LogToConsole.Never,
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)
}
private fun getJavaRuntimeVersionUnsafe(): 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
)
)
private fun getJavaRuntimeVersion(javaExecutable: File): String? {
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)
}
tool = javaExecutable,
args = listOf("-jar", jdkVersionProbeJar.files.single().absolutePath),
processStdout = { stdout -> javaRuntimeVersion = stdout.trim() }
)
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.Provider
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.javaExecutable
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
abstract class AbstractConfigureDesktopPreviewTask : AbstractComposeDesktopTask() {
@ -37,14 +37,15 @@ abstract class AbstractConfigureDesktopPreviewTask : AbstractComposeDesktopTask(
project.providers.gradleProperty("compose.desktop.preview.ide.port")
@get:InputFiles
internal val uiTooling: FileCollection = project.configurations.detachedConfiguration(
project.dependencies.create("org.jetbrains.compose.ui:ui-tooling-desktop:${ComposeBuildConfig.composeVersion}")
).apply { isTransitive = false }
internal val uiTooling: FileCollection =
project.detachedComposeDependency(
groupId = "org.jetbrains.compose.ui",
artifactId = "ui-tooling-desktop",
).excludeTransitiveDependencies()
@get:InputFiles
internal val hostClasspath: FileCollection = project.configurations.detachedConfiguration(
project.dependencies.create("org.jetbrains.compose:preview-rpc:${ComposeBuildConfig.composeVersion}")
)
internal val hostClasspath: FileCollection =
project.detachedComposeGradleDependency(artifactId = "preview-rpc")
@TaskAction
fun run() {
@ -95,10 +96,12 @@ abstract class AbstractConfigureDesktopPreviewTask : AbstractComposeDesktopTask(
}
if (hasSkikoJvmRuntime) return emptyList()
if (hasSkikoJvm && skikoVersion != null && skikoVersion.isNotBlank()) {
val skikoRuntimeConfig = project.configurations.detachedConfiguration(
project.dependencies.create("org.jetbrains.skiko:skiko-awt-runtime-${currentTarget.id}:$skikoVersion")
).apply { isTransitive = false }
if (hasSkikoJvm && !skikoVersion.isNullOrBlank()) {
val skikoRuntimeConfig = project.detachedDependency(
groupId = "org.jetbrains.skiko",
artifactId = "skiko-awt-runtime-${currentTarget.id}",
version = skikoVersion
).excludeTransitiveDependencies()
return skikoRuntimeConfig.files
}
} 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.jetbrains.compose.ComposeBuildConfig
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.internal.utils.*
import org.jetbrains.compose.internal.utils.registerTask
import org.jetbrains.compose.internal.utils.uppercaseFirstChar
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> {
val composeVersion = ComposeBuildConfig.composeVersion
val configurationWithSkiko = project.configurations.detachedConfiguration(
project.dependencies.create("org.jetbrains.compose.ui:ui-graphics:$composeVersion")
val configurationWithSkiko = project.detachedComposeDependency(
artifactId = "ui-graphics",
groupId = "org.jetbrains.compose.ui"
)
return project.provider {
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
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.logging.Logger
import org.jetbrains.compose.ComposeBuildConfig
import java.util.*
internal inline fun Logger.info(fn: () -> String) {
@ -35,3 +37,27 @@ fun Project.getLocalProperty(key: String): String? {
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(":preview-rpc")
include(":jdk-version-probe")

Loading…
Cancel
Save