diff --git a/gradle-plugins/build.gradle.kts b/gradle-plugins/build.gradle.kts index 67e1db1fa5..bdc8d51c87 100644 --- a/gradle-plugins/build.gradle.kts +++ b/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") + } } } diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureJvmApplication.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureJvmApplication.kt index e248b01fcd..5f9a541bd1 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureJvmApplication.kt +++ b/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( @@ -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 diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractCheckNativeDistributionRuntime.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractCheckNativeDistributionRuntime.kt index 3e33619b59..68dd0518f4 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractCheckNativeDistributionRuntime.kt +++ b/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 = objects.notNullProperty() + val jdkHome: Property = objects.notNullProperty() private val taskDir = project.layout.buildDirectory.dir("compose/tmp/$name") @get:OutputFile val javaRuntimePropertiesFile: Provider = taskDir.map { it.file("properties.bin") } - @get:LocalState - val workingDir: Provider = 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 ?: ""}' - |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() 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 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) { - 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 } diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/preview/tasks/AbstractConfigureDesktopPreviewTask.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/preview/tasks/AbstractConfigureDesktopPreviewTask.kt index 013d101e58..b3257dc74f 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/preview/tasks/AbstractConfigureDesktopPreviewTask.kt +++ b/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) { diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/web/internal/configureExperimentalWebApplication.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/web/internal/configureExperimentalWebApplication.kt index a110831933..fd82075cc7 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/web/internal/configureExperimentalWebApplication.kt +++ b/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 { 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) diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/gradleUtils.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/gradleUtils.kt index 966c05a9e0..570262ba67 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/gradleUtils.kt +++ b/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 } diff --git a/gradle-plugins/jdk-version-probe/build.gradle.kts b/gradle-plugins/jdk-version-probe/build.gradle.kts new file mode 100644 index 0000000000..0dd1653676 --- /dev/null +++ b/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" +} \ No newline at end of file diff --git a/gradle-plugins/jdk-version-probe/src/main/java/org/jetbrains/compose/desktop/application/internal/JdkVersionProbe.java b/gradle-plugins/jdk-version-probe/src/main/java/org/jetbrains/compose/desktop/application/internal/JdkVersionProbe.java new file mode 100644 index 0000000000..35edb75f9d --- /dev/null +++ b/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 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); + } +} \ No newline at end of file diff --git a/gradle-plugins/settings.gradle.kts b/gradle-plugins/settings.gradle.kts index 701e7efb0d..162038bf7d 100644 --- a/gradle-plugins/settings.gradle.kts +++ b/gradle-plugins/settings.gradle.kts @@ -7,3 +7,4 @@ pluginManagement { include(":compose") include(":preview-rpc") +include(":jdk-version-probe")