Skip to content

Commit

Permalink
Improve error messages when checking tasks (#3194)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
AlexeyTsvetkov committed May 26, 2023
1 parent dab4531 commit 0319db1
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 104 deletions.
5 changes: 4 additions & 1 deletion gradle-plugins/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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>(
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -16,61 +16,74 @@ 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__
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 ->
Expand All @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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() {
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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 changes: 14 additions & 0 deletions gradle-plugins/jdk-version-probe/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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"
}
Original file line number Diff line number Diff line change
@@ -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);
}
}

0 comments on commit 0319db1

Please sign in to comment.