diff --git a/gradle-plugins/compose/build.gradle.kts b/gradle-plugins/compose/build.gradle.kts index 60237a7c..3c9085dc 100644 --- a/gradle-plugins/compose/build.gradle.kts +++ b/gradle-plugins/compose/build.gradle.kts @@ -88,6 +88,7 @@ val jar = tasks.named("jar") { val supportedGradleVersions = project.property("compose.tests.gradle.versions") .toString().split(",") .map { it.trim() } + .map { GradleVersion.version(it) } val gradleTestsPattern = "org.jetbrains.compose.test.tests.integration.*" @@ -107,9 +108,13 @@ tasks.test { } for (gradleVersion in supportedGradleVersions) { - tasks.registerVerificationTask("testGradle-$gradleVersion") { + tasks.registerVerificationTask("testGradle-${gradleVersion.version}") { classpath = tasks.test.get().classpath - systemProperty("compose.tests.gradle.version", gradleVersion) + + if (gradleVersion >= GradleVersion.version("7.6")) { + systemProperty("compose.tests.gradle.configuration.cache", "true") + } + systemProperty("compose.tests.gradle.version", gradleVersion.version) filter { includeTestsMatching(gradleTestsPattern) } 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 1a32d92c..0fa5ae28 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 @@ -248,6 +248,11 @@ private fun JvmApplicationContext.configureProguardTask( val settings = buildType.proguard mainClass.set(app.mainClass) proguardVersion.set(settings.version) + proguardFiles.from(proguardVersion.map { proguardVersion -> + project.configurations.detachedConfiguration( + project.dependencies.create("com.guardsquare:proguard-gradle:${proguardVersion}") + ) + }) configurationFiles.from(settings.configurationFiles) // ProGuard uses -dontobfuscate option to turn off obfuscation, which is enabled by default // We want to disable obfuscation by default, because often diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/packageVersions.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/packageVersions.kt index 7362fbcc..f81151e4 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/packageVersions.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/packageVersions.kt @@ -55,7 +55,8 @@ internal fun JvmApplicationContext.packageBuildVersionFor( private fun JvmApplicationDistributions.packageBuildVersionFor( targetFormat: TargetFormat ): String? { - check(targetFormat.targetOS == OS.MacOS) + if (targetFormat.targetOS != OS.MacOS) return null + val formatSpecificVersion: String? = when (targetFormat) { TargetFormat.AppImage -> null TargetFormat.Dmg -> macOS.dmgPackageBuildVersion diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractProguardTask.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractProguardTask.kt index 0fd0f4b9..dc8b9187 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractProguardTask.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractProguardTask.kt @@ -5,11 +5,7 @@ package org.jetbrains.compose.desktop.application.tasks -import org.gradle.api.file.ConfigurableFileCollection -import org.gradle.api.file.Directory -import org.gradle.api.file.DirectoryProperty -import org.gradle.api.file.RegularFile -import org.gradle.api.file.RegularFileProperty +import org.gradle.api.file.* import org.gradle.api.provider.Property import org.gradle.api.provider.Provider import org.gradle.api.tasks.* @@ -52,6 +48,9 @@ abstract class AbstractProguardTask : AbstractComposeDesktopTask() { @get:Input val proguardVersion: Property = objects.notNullProperty() + @get:InputFiles + val proguardFiles: ConfigurableFileCollection = objects.fileCollection() + @get:Input val javaHome: Property = objects.notNullProperty(System.getProperty("java.home")) @@ -74,9 +73,6 @@ abstract class AbstractProguardTask : AbstractComposeDesktopTask() { @TaskAction fun execute() { val javaHome = File(javaHome.get()) - val proguardFiles = project.configurations.detachedConfiguration( - project.dependencies.create("com.guardsquare:proguard-gradle:${proguardVersion.get()}") - ).files fileOperations.clearDirs(destinationDir, workingDir) val destinationDir = destinationDir.ioFile.absoluteFile 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 30028ffc..a1108319 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 @@ -5,24 +5,87 @@ package org.jetbrains.compose.experimental.web.internal +import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration +import org.gradle.api.artifacts.ResolvedDependency +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.uppercaseFirstChar import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget internal fun KotlinJsIrTarget.configureExperimentalWebApplication(app: ExperimentalWebApplication) { val mainCompilation = compilations.getByName("main") val unpackedRuntimeDir = project.layout.buildDirectory.dir("compose/skiko-wasm/$targetName") - val taskName = "unpackSkikoWasmRuntime${targetName.capitalize()}" + val taskName = "unpackSkikoWasmRuntime${targetName.uppercaseFirstChar()}" mainCompilation.defaultSourceSet.resources.srcDir(unpackedRuntimeDir) - val unpackRuntime = project.registerTask(taskName) { - runtimeClasspath = project.configurations.getByName(mainCompilation.runtimeDependencyConfigurationName) - outputDir.set(unpackedRuntimeDir) + + val skikoJsWasmRuntimeDependency = skikoVersionProvider(project) + .map { skikoVersion -> + project.dependencies.create("org.jetbrains.skiko:skiko-js-wasm-runtime:$skikoVersion") + } + val skikoJsWasmRuntimeConfiguration = project.configurations.create("COMPOSE_SKIKO_JS_WASM_RUNTIME").defaultDependencies { + it.addLater(skikoJsWasmRuntimeDependency) } - mainCompilation.compileKotlinTaskProvider.configure { compileTask -> - compileTask.dependsOn(unpackRuntime) + val unpackRuntime = project.registerTask(taskName) { + skikoRuntimeFiles = skikoJsWasmRuntimeConfiguration + outputDir.set(unpackedRuntimeDir) } project.tasks.named(mainCompilation.processResourcesTaskName).configure { processResourcesTask -> processResourcesTask.dependsOn(unpackRuntime) } +} + +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") + ) + return project.provider { + val skikoDependency = configurationWithSkiko.allDependenciesDescriptors.firstOrNull(::isSkikoDependency) + skikoDependency?.version + ?: error("Cannot determine the version of Skiko for Compose '$composeVersion'") + } +} + +private fun isSkikoDependency(dep: DependencyDescriptor): Boolean = + dep.group == SKIKO_GROUP && dep.version != null + +private val Configuration.allDependenciesDescriptors: Sequence + get() = with (resolvedConfiguration.lenientConfiguration) { + allModuleDependencies.asSequence().map { ResolvedDependencyDescriptor(it) } + + unresolvedModuleDependencies.asSequence().map { UnresolvedDependencyDescriptor(it) } + } + +private abstract class DependencyDescriptor { + abstract val group: String? + abstract val name: String? + abstract val version: String? +} + +private class ResolvedDependencyDescriptor(private val dependency: ResolvedDependency) : DependencyDescriptor() { + override val group: String? + get() = dependency.moduleGroup + + override val name: String? + get() = dependency.moduleName + + override val version: String? + get() = dependency.moduleVersion +} + +private class UnresolvedDependencyDescriptor(private val dependency: UnresolvedDependency) : DependencyDescriptor() { + override val group: String? + get() = dependency.selector.group + + override val name: String? + get() = dependency.selector.name + + override val version: String? + get() = dependency.selector.version } \ No newline at end of file diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/web/tasks/ExperimentalUnpackSkikoWasmRuntimeTask.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/web/tasks/ExperimentalUnpackSkikoWasmRuntimeTask.kt index 6bb67c0e..867f0efd 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/web/tasks/ExperimentalUnpackSkikoWasmRuntimeTask.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/web/tasks/ExperimentalUnpackSkikoWasmRuntimeTask.kt @@ -6,53 +6,45 @@ package org.jetbrains.compose.experimental.web.tasks import org.gradle.api.DefaultTask -import org.gradle.api.artifacts.Configuration -import org.gradle.api.artifacts.component.ModuleComponentIdentifier +import org.gradle.api.file.ArchiveOperations import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.FileCollection import org.gradle.api.file.FileSystemOperations import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction import org.jetbrains.compose.internal.utils.clearDirs -import org.jetbrains.compose.internal.utils.debug +import java.io.File import javax.inject.Inject abstract class ExperimentalUnpackSkikoWasmRuntimeTask : DefaultTask() { @get:InputFiles - lateinit var runtimeClasspath: Configuration + lateinit var skikoRuntimeFiles: FileCollection @get:OutputDirectory abstract val outputDir: DirectoryProperty + @get:Inject + internal abstract val archiveOperations: ArchiveOperations + @get:Inject internal abstract val fileOperations: FileSystemOperations @TaskAction fun run() { fileOperations.clearDirs(outputDir) - val runtimeArtifacts = runtimeClasspath.resolvedConfiguration.resolvedArtifacts - for (artifact in runtimeArtifacts) { - logger.debug { "Checking artifact: id=${artifact.id}, file=${artifact.file}" } - val id = artifact.id.componentIdentifier - if (id is ModuleComponentIdentifier && id.group == "org.jetbrains.skiko") { - logger.debug { "Found skiko artifact: $artifact" } - unpackSkikoRuntime(id.version) + + for (file in skikoRuntimeFiles.files) { + if (file.name.endsWith(".jar", ignoreCase = true)) { + unpackJar(file) } } } - private fun unpackSkikoRuntime(skikoVersion: String) { - val skikoRuntimeConfig = project.configurations.detachedConfiguration( - project.dependencies.create("org.jetbrains.skiko:skiko-js-wasm-runtime:$skikoVersion") - ) - - for (file in skikoRuntimeConfig.resolve()) { - if (file.name.endsWith(".jar", ignoreCase = true)) { - project.copy { copySpec -> - copySpec.from(project.zipTree(file)) - copySpec.into(outputDir) - } - } + private fun unpackJar(file: File) { + fileOperations.copy { copySpec -> + copySpec.from(archiveOperations.zipTree(file)) + copySpec.into(outputDir) } } } \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/DesktopApplicationTest.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/DesktopApplicationTest.kt index 417633b5..54dcd830 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/DesktopApplicationTest.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/DesktopApplicationTest.kt @@ -6,7 +6,6 @@ package org.jetbrains.compose.test.tests.integration import org.gradle.internal.impldep.org.testng.Assert -import org.gradle.testkit.runner.TaskOutcome import org.jetbrains.compose.internal.utils.MacUtils import org.jetbrains.compose.internal.utils.OS import org.jetbrains.compose.internal.utils.currentArch @@ -17,13 +16,12 @@ import org.jetbrains.compose.test.utils.* import java.io.File import java.util.* -import java.util.jar.JarFile -import kotlin.collections.HashSet import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assumptions import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +import java.util.jar.JarFile class DesktopApplicationTest : GradlePluginTestBase() { @Test @@ -41,25 +39,25 @@ class DesktopApplicationTest : GradlePluginTestBase() { } """.trimIndent() } - gradle("run").build().let { result -> - assertEquals(TaskOutcome.SUCCESS, result.task(":run")?.outcome) + gradle("run").checks { + check.taskSuccessful(":run") } - gradle("runDistributable").build().let { result -> - assertEquals(TaskOutcome.SUCCESS, result.task(":createDistributable")!!.outcome) - assertEquals(TaskOutcome.SUCCESS, result.task(":runDistributable")?.outcome) + gradle("runDistributable").checks { + check.taskSuccessful(":createDistributable") + check.taskSuccessful(":runDistributable") } } @Test fun testRunMpp() = with(testProject(TestProjects.mpp)) { val logLine = "Kotlin MPP app is running!" - gradle("run").build().checks { check -> - check.taskOutcome(":run", TaskOutcome.SUCCESS) + gradle("run").checks { + check.taskSuccessful(":run") check.logContains(logLine) } - gradle("runDistributable").build().checks { check -> - check.taskOutcome(":createDistributable", TaskOutcome.SUCCESS) - check.taskOutcome(":runDistributable", TaskOutcome.SUCCESS) + gradle("runDistributable").checks { + check.taskSuccessful(":createDistributable") + check.taskSuccessful(":runDistributable") check.logContains(logLine) } } @@ -102,7 +100,7 @@ class DesktopApplicationTest : GradlePluginTestBase() { ).checkCustomComposeCompiler() private fun TestProject.checkCustomComposeCompiler() { - gradle(":runDistributable").build().checks { + gradle(":runDistributable").checks { val actualMainImage = file("main-image.actual.png") val expectedMainImage = file("main-image.expected.png") assert(actualMainImage.readBytes().contentEquals(expectedMainImage.readBytes())) { @@ -113,8 +111,8 @@ class DesktopApplicationTest : GradlePluginTestBase() { @Test fun kotlinDsl(): Unit = with(testProject(TestProjects.jvmKotlinDsl)) { - gradle(":packageDistributionForCurrentOS", "--dry-run").build() - gradle(":packageReleaseDistributionForCurrentOS", "--dry-run").build() + gradle(":packageDistributionForCurrentOS", "--dry-run") + gradle(":packageReleaseDistributionForCurrentOS", "--dry-run") } @Test @@ -146,8 +144,8 @@ class DesktopApplicationTest : GradlePluginTestBase() { } checkImageBeforeBuild() - gradle(":runReleaseDistributable").build().checks { check -> - check.taskOutcome(":proguardReleaseJars", TaskOutcome.SUCCESS) + gradle(":runReleaseDistributable").checks { + check.taskSuccessful(":proguardReleaseJars") checkImageAfterBuild() assertEqualTextFiles(file("main-methods.actual.txt"), file("main-methods.expected.txt")) } @@ -155,8 +153,8 @@ class DesktopApplicationTest : GradlePluginTestBase() { file("build.gradle").modify { "$it\n$enableObfuscation" } actualMainImage.delete() checkImageBeforeBuild() - gradle(":runReleaseDistributable").build().checks { check -> - check.taskOutcome(":proguardReleaseJars", TaskOutcome.SUCCESS) + gradle(":runReleaseDistributable").checks { + check.taskSuccessful(":proguardReleaseJars") checkImageAfterBuild() assertNotEqualTextFiles(file("main-methods.actual.txt"), file("main-methods.expected.txt")) } @@ -178,13 +176,13 @@ class DesktopApplicationTest : GradlePluginTestBase() { } val packagingTask = ":packageDistributionForCurrentOS" - gradle(packagingTask).build().checks { check -> - check.taskOutcome(packagingTask, TaskOutcome.SUCCESS) + gradle(packagingTask).checks { + check.taskSuccessful(packagingTask) } - gradle("clean", packagingTask).build().checks { check -> - check.taskOutcome(":checkRuntime", TaskOutcome.FROM_CACHE) - check.taskOutcome(packagingTask, TaskOutcome.SUCCESS) + gradle("clean", packagingTask).checks { + check.taskFromCache(":checkRuntime") + check.taskSuccessful(packagingTask) } } @@ -200,7 +198,7 @@ class DesktopApplicationTest : GradlePluginTestBase() { private fun TestProject.testPackageJvmDistributions() { - val result = gradle(":packageDistributionForCurrentOS").build() + val result = gradle(":packageDistributionForCurrentOS") val mainClass = file("build/classes").walk().single { it.isFile && it.name == "MainKt.class" } val bytecodeVersion = readClassFileVersion(mainClass) @@ -236,8 +234,10 @@ class DesktopApplicationTest : GradlePluginTestBase() { } else { Assert.assertEquals(packageFile.name, "TestPackage-1.0.0.$ext", "Unexpected package name") } - assertEquals(TaskOutcome.SUCCESS, result.task(":package${ext.uppercaseFirstChar()}")?.outcome) - assertEquals(TaskOutcome.SUCCESS, result.task(":packageDistributionForCurrentOS")?.outcome) + result.checks { + check.taskSuccessful(":package${ext.uppercaseFirstChar()}") + check.taskSuccessful(":packageDistributionForCurrentOS") + } } @Test @@ -278,8 +278,8 @@ class DesktopApplicationTest : GradlePluginTestBase() { } private fun TestProject.testPackageUberJarForCurrentOS() { - gradle(":packageUberJarForCurrentOS").build().let { result -> - assertEquals(TaskOutcome.SUCCESS, result.task(":packageUberJarForCurrentOS")?.outcome) + gradle(":packageUberJarForCurrentOS").checks { + check.taskSuccessful(":packageUberJarForCurrentOS") val resultJarFile = file("build/compose/jars/TestPackage-${currentTarget.id}-1.0.0.jar") resultJarFile.checkExists() @@ -297,9 +297,9 @@ class DesktopApplicationTest : GradlePluginTestBase() { @Test fun testModuleClash() = with(testProject(TestProjects.moduleClashCli)) { - gradle(":app:runDistributable").build().checks { check -> - check.taskOutcome(":app:createDistributable", TaskOutcome.SUCCESS) - check.taskOutcome(":app:runDistributable", TaskOutcome.SUCCESS) + gradle(":app:runDistributable").checks { + check.taskSuccessful(":app:createDistributable") + check.taskSuccessful(":app:runDistributable") check.logContains("Called lib1#util()") check.logContains("Called lib2#util()") } @@ -307,8 +307,8 @@ class DesktopApplicationTest : GradlePluginTestBase() { @Test fun testJavaLogger() = with(testProject(TestProjects.javaLogger)) { - gradle(":runDistributable").build().checks { check -> - check.taskOutcome(":runDistributable", TaskOutcome.SUCCESS) + gradle(":runDistributable").checks { + check.taskSuccessful(":runDistributable") check.logContains("Compose Gradle plugin test log warning!") } } @@ -324,8 +324,8 @@ class DesktopApplicationTest : GradlePluginTestBase() { Assumptions.assumeTrue(currentOS == OS.MacOS) with(testProject(TestProjects.macOptions)) { - gradle(":runDistributable").build().checks { check -> - check.taskOutcome(":runDistributable", TaskOutcome.SUCCESS) + gradle(":runDistributable").checks { + check.taskSuccessful(":runDistributable") check.logContains("Hello, from Mac OS!") val appDir = testWorkDir.resolve("build/compose/binaries/main/app/TestPackage.app/Contents/") val actualInfoPlist = appDir.resolve("Info.plist").checkExists() @@ -373,8 +373,8 @@ class DesktopApplicationTest : GradlePluginTestBase() { security("default-keychain", "-s", keychain) security("unlock-keychain", "-p", password, keychain) - gradle(":createDistributable").build().checks { check -> - check.taskOutcome(":createDistributable", TaskOutcome.SUCCESS) + gradle(":createDistributable").checks { + check.taskSuccessful(":createDistributable") val appDir = testWorkDir.resolve("build/compose/binaries/main/app/TestPackage.app/") val result = runProcess(MacUtils.codesign, args = listOf("--verify", "--verbose", appDir.absolutePath)) val actualOutput = result.err.trim() @@ -385,8 +385,8 @@ class DesktopApplicationTest : GradlePluginTestBase() { Assert.assertEquals(expectedOutput, actualOutput) } - gradle(":runDistributable").build().checks { check -> - check.taskOutcome(":runDistributable", TaskOutcome.SUCCESS) + gradle(":runDistributable").checks { + check.taskSuccessful(":runDistributable") check.logContains("Signed app successfully started!") } } @@ -397,8 +397,8 @@ class DesktopApplicationTest : GradlePluginTestBase() { fun testOptionsWithSpaces() { with(testProject(TestProjects.optionsWithSpaces)) { fun testRunTask(runTask: String) { - gradle(runTask).build().checks { check -> - check.taskOutcome(runTask, TaskOutcome.SUCCESS) + gradle(runTask).checks { + check.taskSuccessful(runTask) check.logContains("Running test options with spaces!") check.logContains("Arg #1=Value 1!") check.logContains("Arg #2=Value 2!") @@ -409,8 +409,8 @@ class DesktopApplicationTest : GradlePluginTestBase() { testRunTask(":runDistributable") testRunTask(":run") - gradle(":packageDistributionForCurrentOS").build().checks { check -> - check.taskOutcome(":packageDistributionForCurrentOS", TaskOutcome.SUCCESS) + gradle(":packageDistributionForCurrentOS").checks { + check.taskSuccessful(":packageDistributionForCurrentOS") } } } @@ -419,8 +419,8 @@ class DesktopApplicationTest : GradlePluginTestBase() { fun testDefaultArgs() { with(testProject(TestProjects.defaultArgs)) { fun testRunTask(runTask: String) { - gradle(runTask).build().checks { check -> - check.taskOutcome(runTask, TaskOutcome.SUCCESS) + gradle(runTask).checks { + check.taskSuccessful(runTask) check.logContains("compose.application.configure.swing.globals=true") } } @@ -428,8 +428,8 @@ class DesktopApplicationTest : GradlePluginTestBase() { testRunTask(":runDistributable") testRunTask(":run") - gradle(":packageDistributionForCurrentOS").build().checks { check -> - check.taskOutcome(":packageDistributionForCurrentOS", TaskOutcome.SUCCESS) + gradle(":packageDistributionForCurrentOS").checks { + check.taskSuccessful(":packageDistributionForCurrentOS") } } } @@ -438,8 +438,8 @@ class DesktopApplicationTest : GradlePluginTestBase() { fun testDefaultArgsOverride() { with(testProject(TestProjects.defaultArgsOverride)) { fun testRunTask(runTask: String) { - gradle(runTask).build().checks { check -> - check.taskOutcome(runTask, TaskOutcome.SUCCESS) + gradle(runTask).checks { + check.taskSuccessful(runTask) check.logContains("compose.application.configure.swing.globals=false") } } @@ -447,8 +447,8 @@ class DesktopApplicationTest : GradlePluginTestBase() { testRunTask(":runDistributable") testRunTask(":run") - gradle(":packageDistributionForCurrentOS").build().checks { check -> - check.taskOutcome(":packageDistributionForCurrentOS", TaskOutcome.SUCCESS) + gradle(":packageDistributionForCurrentOS").checks { + check.taskSuccessful(":packageDistributionForCurrentOS") } } } @@ -456,8 +456,8 @@ class DesktopApplicationTest : GradlePluginTestBase() { @Test fun testSuggestModules() { with(testProject(TestProjects.jvm)) { - gradle(":suggestRuntimeModules").build().checks { check -> - check.taskOutcome(":suggestRuntimeModules", TaskOutcome.SUCCESS) + gradle(":suggestRuntimeModules").checks { + check.taskSuccessful(":suggestRuntimeModules") check.logContains("Suggested runtime modules to include:") check.logContains("modules(\"java.instrument\", \"jdk.unsupported\")") } @@ -467,8 +467,8 @@ class DesktopApplicationTest : GradlePluginTestBase() { @Test fun testUnpackSkiko() { with(testProject(TestProjects.unpackSkiko)) { - gradle(":runDistributable").build().checks { check -> - check.taskOutcome(":runDistributable", TaskOutcome.SUCCESS) + gradle(":runDistributable").checks { + check.taskSuccessful(":runDistributable") val libraryPathPattern = "Read skiko library path: '(.*)'".toRegex() val m = libraryPathPattern.find(check.log) @@ -490,12 +490,12 @@ class DesktopApplicationTest : GradlePluginTestBase() { @Test fun resources() = with(testProject(TestProjects.resources)) { - gradle(":run").build().checks { check -> - check.taskOutcome(":run", TaskOutcome.SUCCESS) + gradle(":run").checks { + check.taskSuccessful(":run") } - gradle(":runDistributable").build().checks { check -> - check.taskOutcome(":runDistributable", TaskOutcome.SUCCESS) + gradle(":runDistributable").checks { + check.taskSuccessful(":runDistributable") } } } diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/GradlePluginTest.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/GradlePluginTest.kt index 5e392275..aa04f365 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/GradlePluginTest.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/GradlePluginTest.kt @@ -16,10 +16,17 @@ import java.net.SocketTimeoutException import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicInteger import kotlin.concurrent.thread -import org.gradle.testkit.runner.TaskOutcome import org.junit.jupiter.api.Test class GradlePluginTest : GradlePluginTestBase() { + @Test + fun skikoWasm() = with(testProject(TestProjects.skikoWasm)) { + gradle(":build").checks { + check.taskSuccessful(":unpackSkikoWasmRuntimeJs") + check.taskSuccessful(":compileKotlinJs") + } + } + @Test fun jsMppIsNotBroken() = with( @@ -30,18 +37,13 @@ class GradlePluginTest : GradlePluginTestBase() { ) ) ) { - gradle(":compileKotlinJs").build().checks { check -> - check.taskOutcome(":compileKotlinJs", TaskOutcome.SUCCESS) + gradle(":compileKotlinJs").checks { + check.taskSuccessful(":compileKotlinJs") } } @Test - fun configurePreviewWithoutConfigurationCache() = configurePreview(withConfigurationCache = false) - - @Test - fun configurePreviewWithConfigurationCache() = configurePreview(withConfigurationCache = true) - - private fun configurePreview(withConfigurationCache: Boolean) { + fun configurePreview() { val isAlive = AtomicBoolean(true) val receivedConfigCount = AtomicInteger(0) val port = AtomicInteger(-1) @@ -82,7 +84,7 @@ class GradlePluginTest : GradlePluginTestBase() { } try { - testConfigureDesktopPreviewImpl(port.get(), withConfigurationCache) + testConfigureDesktopPreviewImpl(port.get()) } finally { isAlive.set(false) connectionThread.interrupt() @@ -96,35 +98,20 @@ class GradlePluginTest : GradlePluginTestBase() { } } - private fun testConfigureDesktopPreviewImpl(port: Int, withConfigurationCache: Boolean) { + private fun testConfigureDesktopPreviewImpl(port: Int) { check(port > 0) { "Invalid port: $port" } with(testProject(TestProjects.jvmPreview)) { val portProperty = "-Pcompose.desktop.preview.ide.port=$port" val previewTargetProperty = "-Pcompose.desktop.preview.target=PreviewKt.ExamplePreview" val jvmTask = ":jvm:configureDesktopPreview" - val configurationCacheArg = "--configuration-cache" - val jvmRunner = if (withConfigurationCache) { - gradle(jvmTask, portProperty, previewTargetProperty, configurationCacheArg) - } else { - gradle(jvmTask, portProperty, previewTargetProperty) + gradle(jvmTask, portProperty, previewTargetProperty).checks { + check.taskSuccessful(jvmTask) } - jvmRunner - .build() - .checks { check -> - check.taskOutcome(jvmTask, TaskOutcome.SUCCESS) - } val mppTask = ":mpp:configureDesktopPreviewDesktop" - val mppRunner = if (withConfigurationCache) { - gradle(mppTask, portProperty, previewTargetProperty, configurationCacheArg) - } else { - gradle(mppTask, portProperty, previewTargetProperty) + gradle(mppTask, portProperty, previewTargetProperty).checks { + check.taskSuccessful(mppTask) } - mppRunner - .build() - .checks { check -> - check.taskOutcome(mppTask, TaskOutcome.SUCCESS) - } } } diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/KotlinCompatabilityTest.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/KotlinCompatabilityTest.kt index 83311c2b..dc396b5c 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/KotlinCompatabilityTest.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/KotlinCompatabilityTest.kt @@ -5,13 +5,10 @@ package org.jetbrains.compose.test.tests.integration -import org.gradle.testkit.runner.TaskOutcome -import org.gradle.testkit.runner.UnexpectedBuildFailure import org.jetbrains.compose.test.utils.GradlePluginTestBase import org.jetbrains.compose.test.utils.TestProjects import org.jetbrains.compose.test.utils.checks import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows class KotlinCompatabilityTest : GradlePluginTestBase() { @Test @@ -33,8 +30,8 @@ class KotlinCompatabilityTest : GradlePluginTestBase() { ) ) { val logLine = "Kotlin MPP app is running!" - gradle("run").build().checks { check -> - check.taskOutcome(":run", TaskOutcome.SUCCESS) + gradle("run").checks { + check.taskSuccessful(":run") check.logContains(logLine) } } @@ -45,8 +42,8 @@ class KotlinCompatabilityTest : GradlePluginTestBase() { testEnvironment = defaultTestEnvironment.copy(kotlinVersion = kotlinVersion) ) ) { - gradle(":compileKotlinJs").build().checks { check -> - check.taskOutcome(":compileKotlinJs", TaskOutcome.SUCCESS) + gradle(":compileKotlinJs").checks { + check.taskSuccessful(":compileKotlinJs") } } } diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProject.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProject.kt index 20e39180..e59076ba 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProject.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProject.kt @@ -5,7 +5,9 @@ package org.jetbrains.compose.test.utils +import org.gradle.testkit.runner.BuildResult import org.gradle.testkit.runner.GradleRunner +import org.gradle.util.GradleVersion import org.jetbrains.compose.desktop.application.internal.ComposeProperties import java.io.File import java.util.Properties @@ -64,18 +66,48 @@ class TestProject( } } - fun gradle(vararg args: String): GradleRunner = - GradleRunner.create().apply { - withGradleVersion(TestProperties.gradleVersionForTests) - withProjectDir(testEnvironment.workingDir) - withArguments(args.toList() + additionalArgs) - forwardOutput() + internal fun gradle(vararg args: String): BuildResult { + if (TestProperties.gradleConfigurationCache) { + if (GradleVersion.version(TestProperties.gradleVersionForTests) < GradleVersion.version("8.0-rc-1")) { + // Gradle 7.* does not use the configuration cache in the same build. + // In other words, if cache misses, Gradle performs configuration, + // but does not, use the serialized task graph. + // So in order to test the cache, we need to perform dry-run before the actual run. + // This should be fixed in https://github.com/gradle/gradle/issues/21985 (which is planned for 8.0 RC 1) + gradleRunner(args.withDryRun()).build() + } } - @Suppress("DeprecatedCallableAddReplaceWith") - @Deprecated("Do not commit!") - fun gradleDebug(vararg args: String): GradleRunner = - gradle(*args).withDebug(true) + return gradleRunner(args).build() + } + + private fun Array.withDryRun(): Array { + var sawDryRun = false + val dryRunArgs = ArrayList(size) + for (arg in this) { + sawDryRun = sawDryRun || arg.trim() in listOf("-m", "--dry-run") + dryRunArgs.add(arg) + } + if (!sawDryRun) { + dryRunArgs.add("--dry-run") + } + return dryRunArgs.toTypedArray() + } + + private fun gradleRunner(args: Array): GradleRunner { + val allArgs = args.toMutableList() + allArgs.addAll(additionalArgs) + if (TestProperties.gradleConfigurationCache) { + allArgs.add("--configuration-cache") + } + + return GradleRunner.create().apply { + withGradleVersion(TestProperties.gradleVersionForTests) + withProjectDir(testEnvironment.workingDir) + withArguments(allArgs) + forwardOutput() + } + } fun file(path: String): File = testEnvironment.workingDir.resolve(path) diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProjects.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProjects.kt index 81f0611c..27475fbb 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProjects.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProjects.kt @@ -22,5 +22,6 @@ object TestProjects { const val unpackSkiko = "application/unpackSkiko" const val resources = "application/resources" const val jsMpp = "misc/jsMpp" + const val skikoWasm = "misc/skikoWasm" const val jvmPreview = "misc/jvmPreview" } \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProperties.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProperties.kt index 51949bfe..8de829c9 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProperties.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProperties.kt @@ -23,6 +23,9 @@ object TestProperties { val gradleVersionForTests: String? get() = System.getProperty("compose.tests.gradle.version") + val gradleConfigurationCache: Boolean + get() = System.getProperty("compose.tests.gradle.configuration.cache") == "true" + val summaryFile: File? get() = System.getProperty("compose.tests.summary.file")?.let { File(it) } diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/assertUtils.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/assertUtils.kt index 765b5512..bc8b0ff8 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/assertUtils.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/assertUtils.kt @@ -18,10 +18,13 @@ internal fun Collection.checkContains(vararg elements: T) { } } -internal fun BuildResult.checks(fn: (BuildResultChecks) -> Unit) { - fn(BuildResultChecks(this)) +internal fun BuildResult.checks(fn: ChecksWrapper.() -> Unit) { + fn(ChecksWrapper(BuildResultChecks(this))) } +@JvmInline +internal value class ChecksWrapper(val check: BuildResultChecks) + internal class BuildResultChecks(private val result: BuildResult) { val log: String get() = result.output @@ -32,7 +35,19 @@ internal class BuildResultChecks(private val result: BuildResult) { } } - fun taskOutcome(task: String, expectedOutcome: TaskOutcome) { + fun taskSuccessful(task: String) { + taskOutcome(task, TaskOutcome.SUCCESS) + } + + fun taskFailed(task: String) { + taskOutcome(task, TaskOutcome.FAILED) + } + + fun taskFromCache(task: String) { + taskOutcome(task, TaskOutcome.FROM_CACHE) + } + + private fun taskOutcome(task: String, expectedOutcome: TaskOutcome) { val actualOutcome = result.task(task)?.outcome if (actualOutcome != expectedOutcome) { throw AssertionError( diff --git a/gradle-plugins/compose/src/test/test-projects/misc/skikoWasm/build.gradle b/gradle-plugins/compose/src/test/test-projects/misc/skikoWasm/build.gradle new file mode 100644 index 00000000..236b7e40 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/misc/skikoWasm/build.gradle @@ -0,0 +1,31 @@ +import org.jetbrains.compose.desktop.application.dsl.TargetFormat + +plugins { + id "org.jetbrains.kotlin.multiplatform" + id "org.jetbrains.compose" +} + +repositories { + jetbrainsCompose() +} + +kotlin { + js(IR) { + browser() + binaries.executable() + } + sourceSets { + commonMain { + dependencies { + implementation(compose.ui) + implementation(compose.foundation) + implementation(compose.material) + implementation(compose.runtime) + } + } + } +} + +compose.experimental { + web.application {} +} \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/misc/skikoWasm/gradle.properties b/gradle-plugins/compose/src/test/test-projects/misc/skikoWasm/gradle.properties new file mode 100644 index 00000000..9c8f6b37 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/misc/skikoWasm/gradle.properties @@ -0,0 +1 @@ +org.jetbrains.compose.experimental.jscanvas.enabled=true diff --git a/gradle-plugins/compose/src/test/test-projects/misc/skikoWasm/settings.gradle b/gradle-plugins/compose/src/test/test-projects/misc/skikoWasm/settings.gradle new file mode 100644 index 00000000..c8796489 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/misc/skikoWasm/settings.gradle @@ -0,0 +1,12 @@ +pluginManagement { + plugins { + id 'org.jetbrains.kotlin.multiplatform' version 'KOTLIN_VERSION_PLACEHOLDER' + id 'org.jetbrains.compose' version 'COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER' + } + repositories { + mavenLocal() + gradlePluginPortal() + maven { url "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev" } + } +} +rootProject.name = "skikoWasm" \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/misc/skikoWasm/src/commonMain/kotlin/composable.kt b/gradle-plugins/compose/src/test/test-projects/misc/skikoWasm/src/commonMain/kotlin/composable.kt new file mode 100644 index 00000000..cbb7ba3e --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/misc/skikoWasm/src/commonMain/kotlin/composable.kt @@ -0,0 +1,7 @@ +import androidx.compose.material.Text +import androidx.compose.runtime.* + +@Composable +fun HelloWorld() { + Text("Hello, World!") +} \ No newline at end of file