diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/diagnosticUtils.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/diagnosticUtils.kt new file mode 100644 index 0000000000..bdcb5de64e --- /dev/null +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/diagnosticUtils.kt @@ -0,0 +1,10 @@ +package org.jetbrains.compose.desktop.application.internal + +import java.io.PrintWriter +import java.io.StringWriter +import java.lang.Exception + +internal fun Exception.stacktraceToString(): String = + StringWriter().also { w -> + PrintWriter(w).use { pw -> printStackTrace(pw) } + }.toString() \ No newline at end of file diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/FileCopyingProcessor.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/files/FileCopyingProcessor.kt similarity index 62% rename from gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/FileCopyingProcessor.kt rename to gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/files/FileCopyingProcessor.kt index 18f6d9f858..3bda8e1e74 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/FileCopyingProcessor.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/files/FileCopyingProcessor.kt @@ -1,4 +1,4 @@ -package org.jetbrains.compose.desktop.application.internal +package org.jetbrains.compose.desktop.application.internal.files import java.io.File diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/MacJarSignFileCopyingProcessor.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/files/MacJarSignFileCopyingProcessor.kt similarity index 95% rename from gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/MacJarSignFileCopyingProcessor.kt rename to gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/files/MacJarSignFileCopyingProcessor.kt index f5926e2c2b..81012bf974 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/MacJarSignFileCopyingProcessor.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/files/MacJarSignFileCopyingProcessor.kt @@ -1,6 +1,8 @@ -package org.jetbrains.compose.desktop.application.internal +package org.jetbrains.compose.desktop.application.internal.files import org.gradle.process.ExecOperations +import org.jetbrains.compose.desktop.application.internal.MacUtils +import org.jetbrains.compose.desktop.application.internal.isJarFile import org.jetbrains.compose.desktop.application.internal.validation.ValidatedMacOSSigningSettings import java.io.* import java.util.regex.Pattern diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/SimpleFileCopyingProcessor.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/files/SimpleFileCopyingProcessor.kt similarity index 73% rename from gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/SimpleFileCopyingProcessor.kt rename to gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/files/SimpleFileCopyingProcessor.kt index b70a192fdc..132cdf3737 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/SimpleFileCopyingProcessor.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/files/SimpleFileCopyingProcessor.kt @@ -1,4 +1,4 @@ -package org.jetbrains.compose.desktop.application.internal +package org.jetbrains.compose.desktop.application.internal.files import java.io.File diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/files/fileUtils.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/files/fileUtils.kt new file mode 100644 index 0000000000..87ce0fa4fe --- /dev/null +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/files/fileUtils.kt @@ -0,0 +1,20 @@ +package org.jetbrains.compose.desktop.application.internal.files + +import java.io.File +import java.security.DigestInputStream +import java.security.MessageDigest + +internal fun fileHash(file: File): String { + val md5 = MessageDigest.getInstance("MD5") + file.inputStream().buffered().use { fis -> + DigestInputStream(fis, md5).use { ds -> + while (ds.read() != -1) {} + } + } + val digest = md5.digest() + return buildString(digest.size * 2) { + for (byte in digest) { + append(Integer.toHexString(0xFF and byte.toInt())) + } + } +} \ No newline at end of file diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJPackageTask.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJPackageTask.kt index 9c3fe447c6..cdc826da11 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJPackageTask.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJPackageTask.kt @@ -1,13 +1,11 @@ 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.RegularFileProperty +import org.gradle.api.file.* 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.gradle.api.tasks.Optional import org.gradle.process.ExecResult import org.gradle.process.ExecSpec import org.gradle.work.ChangeType @@ -15,10 +13,19 @@ import org.gradle.work.InputChanges import org.jetbrains.compose.desktop.application.dsl.MacOSSigningSettings import org.jetbrains.compose.desktop.application.dsl.TargetFormat import org.jetbrains.compose.desktop.application.internal.* +import org.jetbrains.compose.desktop.application.internal.files.MacJarSignFileCopyingProcessor +import org.jetbrains.compose.desktop.application.internal.files.SimpleFileCopyingProcessor +import org.jetbrains.compose.desktop.application.internal.files.fileHash import org.jetbrains.compose.desktop.application.internal.validation.ValidatedMacOSSigningSettings import org.jetbrains.compose.desktop.application.internal.validation.validate import java.io.File +import java.io.Serializable +import java.util.* import javax.inject.Inject +import kotlin.collections.HashMap +import kotlin.collections.HashSet +import java.io.ObjectInputStream +import java.io.ObjectOutputStream abstract class AbstractJPackageTask @Inject constructor( @get:Input @@ -166,12 +173,28 @@ abstract class AbstractJPackageTask @Inject constructor( @get:LocalState protected val signDir: Provider = project.layout.buildDirectory.dir("compose/tmp/sign") + @get:Internal + private val libsDir: Provider = workingDir.map { + it.dir("libs") + } + + @get:Internal + private val libsMappingFile: Provider = workingDir.map { + it.file("libs-mapping.txt") + } + + @get:Internal + private val libsMapping = FilesMapping() + override fun makeArgs(tmpDir: File): MutableList = super.makeArgs(tmpDir).apply { if (targetFormat == TargetFormat.AppImage || appImage.orNull == null) { // Args, that can only be used, when creating an app image or an installer w/o --app-image parameter cliArg("--input", tmpDir) cliArg("--runtime-image", runtimeImage) - cliArg("--main-jar", launcherMainJar.ioFile.name) + + val mappedJar = libsMapping[launcherMainJar.ioFile] + ?: error("Main jar was not processed correctly: ${launcherMainJar.ioFile}") + cliArg("--main-jar", mappedJar) cliArg("--main-class", launcherMainClass) when (currentOS) { @@ -241,10 +264,45 @@ abstract class AbstractJPackageTask @Inject constructor( } } - override fun prepareWorkingDir(inputChanges: InputChanges) { - val workingDir = workingDir.ioFile + private fun invalidateMappedLibs( + inputChanges: InputChanges + ): Set { + val outdatedLibs = HashSet() + val libsDirFile = libsDir.ioFile + + fun invalidateAllLibs() { + outdatedLibs.addAll(files.files) + outdatedLibs.add(launcherMainJar.ioFile) + + logger.debug("Clearing all files in working dir: $libsDirFile") + fileOperations.delete(libsDirFile) + libsDirFile.mkdirs() + } + + if (inputChanges.isIncremental) { + val allChanges = inputChanges.getFileChanges(files).asSequence() + + inputChanges.getFileChanges(launcherMainJar) - // todo: parallel processing + try { + for (change in allChanges) { + libsMapping.remove(change.file)?.let { fileOperations.delete(it) } + if (change.changeType != ChangeType.REMOVED) { + outdatedLibs.add(change.file) + } + } + } catch (e: Exception) { + logger.debug("Could remove outdated libs incrementally: ${e.stacktraceToString()}") + invalidateAllLibs() + } + } else { + invalidateAllLibs() + } + + return outdatedLibs + } + + override fun prepareWorkingDir(inputChanges: InputChanges) { + val libsDirFile = libsDir.ioFile val fileProcessor = withValidatedMacOSSigning { signing -> val tmpDirForSign = signDir.ioFile @@ -258,35 +316,17 @@ abstract class AbstractJPackageTask @Inject constructor( ) } ?: SimpleFileCopyingProcessor - if (inputChanges.isIncremental) { - logger.debug("Updating working dir incrementally: $workingDir") - val allChanges = inputChanges.getFileChanges(files).asSequence() + - inputChanges.getFileChanges(launcherMainJar) - allChanges.forEach { fileChange -> - val sourceFile = fileChange.file - val targetFile = workingDir.resolve(sourceFile.name) - - if (fileChange.changeType == ChangeType.REMOVED) { - fileOperations.delete(targetFile) - logger.debug("Deleted: $targetFile") - } else { - fileProcessor.copy(sourceFile, targetFile) - logger.debug("Updated: $targetFile") - } - } - } else { - logger.debug("Updating working dir non-incrementally: $workingDir") - fileOperations.delete(workingDir) - fileOperations.mkdir(workingDir) - - files.forEach { sourceFile -> - val targetFile = workingDir.resolve(sourceFile.name) - if (targetFile.exists()) { - // todo: handle possible clashes - logger.warn("w: File already exists: $targetFile") - } - fileProcessor.copy(sourceFile, targetFile) - } + val outdatedLibs = invalidateMappedLibs(inputChanges) + for (sourceFile in outdatedLibs) { + assert(sourceFile.exists()) { "Lib file does not exist: $sourceFile" } + + val targetFileName = + if (sourceFile.isJarFile) + "${sourceFile.nameWithoutExtension}-${fileHash(sourceFile)}.jar" + else sourceFile.name + val targetFile = libsDirFile.resolve(targetFileName) + fileProcessor.copy(sourceFile, targetFile) + libsMapping[sourceFile] = targetFile } } @@ -309,4 +349,69 @@ abstract class AbstractJPackageTask @Inject constructor( val outputFile = findOutputFileOrDir(destinationDir.ioFile, targetFormat) logger.lifecycle("The distribution is written to ${outputFile.canonicalPath}") } + + override fun initState() { + val mappingFile = libsMappingFile.ioFile + if (mappingFile.exists()) { + try { + libsMapping.loadFrom(mappingFile) + } catch (e: Exception) { + fileOperations.delete(mappingFile) + throw e + } + logger.debug("Loaded libs mapping from $mappingFile") + } + } + + override fun saveStateAfterFinish() { + val mappingFile = libsMappingFile.ioFile + libsMapping.saveTo(mappingFile) + logger.debug("Saved libs mapping to $mappingFile") + } +} + +// Serializable is only needed to avoid breaking configuration cache: +// https://docs.gradle.org/current/userguide/configuration_cache.html#config_cache:requirements +private class FilesMapping : Serializable { + private var mapping = HashMap() + + operator fun get(key: File): File? = + mapping[key] + + operator fun set(key: File, value: File) { + mapping[key] = value + } + + fun remove(key: File): File? = + mapping.remove(key) + + fun loadFrom(mappingFile: File) { + mappingFile.readLines().forEach { line -> + if (line.isNotBlank()) { + val (k, v) = line.split(File.pathSeparatorChar) + mapping[File(k)] = File(v) + } + } + } + + fun saveTo(mappingFile: File) { + mappingFile.parentFile.mkdirs() + mappingFile.bufferedWriter().use { writer -> + mapping.entries + .sortedBy { (k, _) -> k.absolutePath } + .forEach { (k, v) -> + writer.append(k.absolutePath) + writer.append(File.pathSeparatorChar) + writer.appendln(v.absolutePath) + } + } + } + + private fun writeObject(stream: ObjectOutputStream) { + stream.writeObject(mapping) + } + + private fun readObject(stream: ObjectInputStream) { + mapping = stream.readObject() as HashMap + } } \ No newline at end of file diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJvmToolOperationTask.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJvmToolOperationTask.kt index e37a338159..54a95153de 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJvmToolOperationTask.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJvmToolOperationTask.kt @@ -16,8 +16,6 @@ import org.gradle.process.ExecSpec import org.gradle.work.InputChanges import org.jetbrains.compose.desktop.application.internal.* import org.jetbrains.compose.desktop.application.internal.ComposeProperties -import org.jetbrains.compose.desktop.application.internal.OS -import org.jetbrains.compose.desktop.application.internal.currentOS import org.jetbrains.compose.desktop.application.internal.notNullProperty import java.io.File import javax.inject.Inject @@ -68,6 +66,7 @@ abstract class AbstractJvmToolOperationTask(private val toolName: String) : Defa @TaskAction fun run(inputChanges: InputChanges) { + initState() val javaHomePath = javaHome.get() val jtool = File(javaHomePath).resolve("bin/${executableName(toolName)}") @@ -96,5 +95,9 @@ abstract class AbstractJvmToolOperationTask(private val toolName: String) : Defa fileOperations.delete(workingDir) } } + saveStateAfterFinish() } + + protected open fun initState() {} + protected open fun saveStateAfterFinish() {} } \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/DesktopApplicationTest.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/DesktopApplicationTest.kt index 69af794acd..905a986dd0 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/DesktopApplicationTest.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/DesktopApplicationTest.kt @@ -5,7 +5,6 @@ import org.jetbrains.compose.desktop.application.internal.OS import org.jetbrains.compose.desktop.application.internal.currentOS import org.jetbrains.compose.desktop.application.internal.currentTarget import org.jetbrains.compose.test.* -import org.junit.jupiter.api.Assertions.assertArrayEquals import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import java.util.jar.JarFile @@ -93,4 +92,14 @@ 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) + check.logContains("Called lib1#util()") + check.logContains("Called lib2#util()") + } + } } diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/FileHashTest.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/FileHashTest.kt new file mode 100644 index 0000000000..5055288caf --- /dev/null +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/FileHashTest.kt @@ -0,0 +1,78 @@ +package org.jetbrains.compose + +import org.gradle.internal.impldep.org.testng.Assert +import org.jetbrains.compose.desktop.application.internal.OS +import org.jetbrains.compose.desktop.application.internal.currentOS +import org.jetbrains.compose.desktop.application.internal.files.fileHash +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir +import java.io.File +import java.util.concurrent.TimeUnit + +class FileHashTest { + @TempDir + lateinit var workingDir: File + + private val inputDir: File + get() = workingDir.resolve("inputs").apply { mkdirs() } + + private fun inputFile(name: String, content: String): File { + return inputDir.resolve(name).apply { writeText(content) } + } + + private fun outputFile(name: String) = + workingDir.resolve("outputs/$name").apply { + parentFile.mkdirs() + } + + @Test + fun testFileHashIsAffectedByContent() { + val input1 = inputFile("input1.txt", "1") + + val initJar = createJar("init", input1) + input1.writeText("2") + val modifiedJar = createJar("modified", input1) + + val initHash = fileHash(initJar) + val modifiedHash = fileHash(modifiedJar) + Assert.assertNotEquals(modifiedHash, initHash) + } + + private fun createJar(outputFileName: String, vararg files: File): File { + val outputFile = outputFile(outputFileName) + + val cmd = arrayListOf(jarUtilFile.absolutePath, "cvf", outputFile.absolutePath) + for (file in files) { + cmd.add(file.relativeTo(inputDir).path) + } + val outFile = workingDir.resolve("jar-stdout.txt").apply { delete() } + val errFile = workingDir.resolve("jar-error.txt").apply { delete() } + val process = ProcessBuilder(cmd).run { + redirectOutput(outFile) + redirectError(errFile) + directory(inputDir) + start() + } + if (!process.waitFor(30, TimeUnit.SECONDS)) { + error("Process hang up: [${cmd.joinToString(" ")}]") + } + val exitCode = process.exitValue() + check(exitCode == 0) { + """ + Stdout log: $outFile + Error log: $errFile + Process exited with error: $exitCode + """ + } + outFile.delete() + errFile.delete() + + return outputFile + } +} + +private val jarUtilFile = run { + val javaHome = File(System.getProperty("java.home")) + val executableName = if (currentOS == OS.Windows) "jar.exe" else "jar" + javaHome.resolve("bin/$executableName") +} \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/TestProject.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/TestProject.kt index f33b631e75..7be1753b42 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/TestProject.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/TestProject.kt @@ -36,6 +36,7 @@ data class TestProject( withGradleVersion(TestProperties.gradleVersionForTests) withProjectDir(workingDir) withArguments(args.toList() + additionalArgs) + forwardOutput() } @Suppress("DeprecatedCallableAddReplaceWith") diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/TestProjects.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/TestProjects.kt index 7dbe1dbb62..938cb46f93 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/TestProjects.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/TestProjects.kt @@ -4,4 +4,5 @@ object TestProjects { const val jvm = "application/jvm" const val mpp = "application/mpp" const val jvmKotlinDsl = "application/jvmKotlinDsl" + const val moduleClashCli = "application/moduleClashCli" } \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/assertUtils.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/assertUtils.kt index 930955e392..55661cf0cf 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/assertUtils.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/assertUtils.kt @@ -1,9 +1,45 @@ package org.jetbrains.compose.test +import org.gradle.testkit.runner.BuildResult +import org.gradle.testkit.runner.TaskOutcome + internal fun Collection.checkContains(vararg elements: T) { val expectedElements = elements.toMutableSet() forEach { expectedElements.remove(it) } if (expectedElements.isNotEmpty()) { error("Expected elements are missing from the collection: [${expectedElements.joinToString(", ")}]") } +} + +internal fun BuildResult.checks(fn: (BuildResultChecks) -> Unit) { + fn(BuildResultChecks(this)) +} + +internal class BuildResultChecks(private val result: BuildResult) { + fun logContains(substring: String) { + if (!result.output.contains(substring)) { + throw AssertionError("Test output does not contain the expected string: '$substring'") + } + } + + fun taskOutcome(task: String, expectedOutcome: TaskOutcome) { + val actualOutcome = result.task(task)?.outcome + if (actualOutcome != expectedOutcome) { + throw AssertionError( + """|Unexpected outcome for task '$task' + |Expected: $expectedOutcome + |Actual: $actualOutcome + """.trimMargin()) + } + } +} + +internal fun BuildResult.checkOutputLogContains(substring: String) { + if (output.contains(substring)) return + + println("Test output:") + output.lineSequence().forEach { + println(" > $it") + } + throw AssertionError("Test output does not contain the expected string: '$substring'") } \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/application/moduleClashCli/app/build.gradle b/gradle-plugins/compose/src/test/test-projects/application/moduleClashCli/app/build.gradle new file mode 100644 index 0000000000..e0e128c688 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/application/moduleClashCli/app/build.gradle @@ -0,0 +1,23 @@ +import org.jetbrains.compose.desktop.application.dsl.TargetFormat + +plugins { + id "org.jetbrains.kotlin.jvm" + id "org.jetbrains.compose" +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib" + implementation compose.desktop.currentOs + + implementation project(":lib1:utils") + implementation project(":lib2:utils") +} + +compose.desktop { + application { + mainClass = "MainKt" + nativeDistributions { + targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) + } + } +} \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/application/moduleClashCli/app/src/main/kotlin/main.kt b/gradle-plugins/compose/src/test/test-projects/application/moduleClashCli/app/src/main/kotlin/main.kt new file mode 100644 index 0000000000..c587f06e2f --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/application/moduleClashCli/app/src/main/kotlin/main.kt @@ -0,0 +1,7 @@ +import lib1.* +import lib2.* + +fun main() { + lib1.util() + lib2.util() +} \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/application/moduleClashCli/build.gradle b/gradle-plugins/compose/src/test/test-projects/application/moduleClashCli/build.gradle new file mode 100644 index 0000000000..45f07d3637 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/application/moduleClashCli/build.gradle @@ -0,0 +1,15 @@ +plugins { + id "org.jetbrains.kotlin.jvm" apply false + id "org.jetbrains.compose" apply false +} + +subprojects { + repositories { + google() + mavenCentral() + jcenter() + maven { + url "https://maven.pkg.jetbrains.space/public/p/compose/dev" + } + } +} diff --git a/gradle-plugins/compose/src/test/test-projects/application/moduleClashCli/lib1/utils/build.gradle b/gradle-plugins/compose/src/test/test-projects/application/moduleClashCli/lib1/utils/build.gradle new file mode 100644 index 0000000000..2e359f3153 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/application/moduleClashCli/lib1/utils/build.gradle @@ -0,0 +1,7 @@ +plugins { + id "org.jetbrains.kotlin.jvm" +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib" +} \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/application/moduleClashCli/lib1/utils/src/main/kotlin/lib1/utils.kt b/gradle-plugins/compose/src/test/test-projects/application/moduleClashCli/lib1/utils/src/main/kotlin/lib1/utils.kt new file mode 100644 index 0000000000..b02d89958c --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/application/moduleClashCli/lib1/utils/src/main/kotlin/lib1/utils.kt @@ -0,0 +1,5 @@ +package lib1 + +fun util() { + println("Called lib1#util()") +} \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/application/moduleClashCli/lib2/utils/build.gradle b/gradle-plugins/compose/src/test/test-projects/application/moduleClashCli/lib2/utils/build.gradle new file mode 100644 index 0000000000..2e359f3153 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/application/moduleClashCli/lib2/utils/build.gradle @@ -0,0 +1,7 @@ +plugins { + id "org.jetbrains.kotlin.jvm" +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib" +} \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/application/moduleClashCli/lib2/utils/src/main/kotlin/lib2/utils.kt b/gradle-plugins/compose/src/test/test-projects/application/moduleClashCli/lib2/utils/src/main/kotlin/lib2/utils.kt new file mode 100644 index 0000000000..1ae0a5d634 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/application/moduleClashCli/lib2/utils/src/main/kotlin/lib2/utils.kt @@ -0,0 +1,5 @@ +package lib2 + +fun util() { + println("Called lib2#util()") +} \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/application/moduleClashCli/settings.gradle b/gradle-plugins/compose/src/test/test-projects/application/moduleClashCli/settings.gradle new file mode 100644 index 0000000000..e5b770dca2 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/application/moduleClashCli/settings.gradle @@ -0,0 +1,11 @@ +pluginManagement { + plugins { + id 'org.jetbrains.kotlin.jvm' version 'KOTLIN_VERSION_PLACEHOLDER' + id 'org.jetbrains.compose' version 'COMPOSE_VERSION_PLACEHOLDER' + } + repositories { + mavenLocal() + gradlePluginPortal() + } +} +include ':lib1:utils', ':lib2:utils', ':app' \ No newline at end of file