From fc088bb84abf5806bb8cc6be76c10eca23c3eb62 Mon Sep 17 00:00:00 2001 From: Alexander Likhachev Date: Wed, 19 Oct 2022 12:12:59 +0200 Subject: [PATCH] [Gradle] Split & migrate AbstractKotlinAndroidGradleTests to the new dsl #KT-45748 In Progress --- .../AbstractKotlinAndroidGradleTests.kt | 1137 ----------------- .../kotlin/gradle/android/Kapt3AndroidIT.kt | 202 +++ .../kotlin/gradle/android/KotlinAndroidIT.kt | 284 ++++ .../android/KotlinAndroidIncrementalIT.kt | 183 +++ .../gradle/android/KotlinAndroidMppIT.kt | 706 ++++++++++ .../gradle/testbase/compilationAssertions.kt | 14 + .../gradle/testbase/outputAssertions.kt | 13 + .../kotlin/gradle/testbase/tasksAssertions.kt | 27 + .../util/testResolveAllConfigurations.kt | 7 +- .../AndroidDaggerProject/app/build.gradle | 1 + .../plugin/internal/state/TaskLoggers.kt | 2 +- 11 files changed, 1435 insertions(+), 1141 deletions(-) delete mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/AbstractKotlinAndroidGradleTests.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/android/KotlinAndroidIT.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/android/KotlinAndroidIncrementalIT.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/android/KotlinAndroidMppIT.kt diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/AbstractKotlinAndroidGradleTests.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/AbstractKotlinAndroidGradleTests.kt deleted file mode 100644 index fcc3871a491..00000000000 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/AbstractKotlinAndroidGradleTests.kt +++ /dev/null @@ -1,1137 +0,0 @@ -/* - * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package org.jetbrains.kotlin.gradle - -import org.gradle.api.logging.LogLevel -import org.gradle.api.logging.configuration.WarningMode -import org.gradle.util.GradleVersion -import org.jetbrains.kotlin.gradle.internal.ensureParentDirsCreated -import org.jetbrains.kotlin.gradle.testbase.NON_INCREMENTAL_COMPILATION_WILL_BE_PERFORMED -import org.jetbrains.kotlin.gradle.testbase.TestVersions -import org.jetbrains.kotlin.gradle.tooling.BuildKotlinToolingMetadataTask -import org.jetbrains.kotlin.gradle.util.* -import org.jetbrains.kotlin.test.util.KtTestUtil -import org.junit.Assume -import org.junit.Test -import java.io.File -import java.util.zip.ZipFile -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertNull -import kotlin.test.assertTrue - -open class KotlinAndroid41GradleIT : KotlinAndroid4GradleIT() { - override val androidGradlePluginVersion: AGPVersion - get() = AGPVersion.v4_1_0 - - // AGP 3.+ is not working well with Gradle 7+ - override val defaultGradleVersion: GradleVersionRequired - get() = GradleVersionRequired.Until(TestVersions.Gradle.G_6_9) - - @Test - fun testKaptUsingApOptionProvidersAsNestedInputOutput() = with(Project("AndroidProject")) { - setupWorkingDir() - - gradleBuildScript(subproject = "Android").appendText( - """ - - apply plugin: 'kotlin-kapt' - - class MyNested implements org.gradle.process.CommandLineArgumentProvider { - - @InputFile - File inputFile = null - - @Override - Iterable asArguments() { - // Read the arguments from a file, because changing them in a build script is treated as an - // implementation change by Gradle: - return [new File('args.txt').text] - } - } - - def nested = new MyNested() - nested.inputFile = file("${'$'}projectDir/in.txt") - - android.applicationVariants.all { - it.javaCompileOptions.annotationProcessorOptions.compilerArgumentProviders.add(nested) - } - """.trimIndent() - ) - - File(projectDir, "Android/in.txt").appendText("1234") - File(projectDir, "args.txt").appendText("1234") - - val kaptTasks = listOf(":Android:kaptFlavor1DebugKotlin") - val javacTasks = listOf(":Android:compileFlavor1DebugJavaWithJavac") - - val buildTasks = (kaptTasks + javacTasks).toTypedArray() - - build(*buildTasks) { - assertSuccessful() - assertTasksExecuted(kaptTasks + javacTasks) - } - - File(projectDir, "Android/in.txt").appendText("5678") - - build(*buildTasks) { - assertSuccessful() - assertTasksExecuted(kaptTasks) - assertTasksUpToDate(javacTasks) - } - - // Changing only the annotation provider arguments should not trigger the tasks to run, as the arguments may be outputs, - // internals or neither: - File(projectDir, "args.txt").appendText("5678") - - build(*buildTasks) { - assertSuccessful() - assertTasksUpToDate(javacTasks + kaptTasks) - } - } - - @Test - fun testAgpNestedArgsNotEvaluatedDuringConfiguration() = with(Project("AndroidProject")) { - setupWorkingDir() - - gradleBuildScript(subproject = "Android").appendText( - """ - - apply plugin: 'kotlin-kapt' - - class MyNested implements org.gradle.process.CommandLineArgumentProvider { - @Override - Iterable asArguments() { - throw new RuntimeException("This should not be invoked during configuration.") - } - } - - def nested = new MyNested() - - android.applicationVariants.all { - it.javaCompileOptions.annotationProcessorOptions.compilerArgumentProviders.add(nested) - } - """.trimIndent() - ) - - build(":Android:kaptFlavor1DebugKotlin", "--dry-run") { - assertSuccessful() - } - - build( - ":Android:kaptFlavor1DebugKotlin", "--dry-run", - options = defaultBuildOptions().copy(kaptOptions = KaptOptions(verbose = false)) - ) { - assertSuccessful() - } - } - - @Test - fun testOmittedStdlibVersion() = Project("AndroidProject").run { - setupWorkingDir() - - gradleBuildScript("Lib").modify { - - it.checkedReplace( - "kotlin-stdlib:\$kotlin_version", - "kotlin-stdlib" - ) + "\n" + - """ - apply plugin: 'maven-publish' - - android { - defaultPublishConfig 'flavor1Debug' - } - - afterEvaluate { - publishing { - publications { - flavorDebug(MavenPublication) { - from components.flavor1Debug - - group = 'com.example' - artifactId = 'flavor1Debug' - version = '1.0' - } - } - repositories { - maven { - url = "file://${'$'}buildDir/repo" - } - } - } - } - """.trimIndent() - } - - build(":Lib:assembleFlavor1Debug", ":Lib:publish") { - assertSuccessful() - assertTasksExecuted(":Lib:compileFlavor1DebugKotlin", ":Lib:publishFlavorDebugPublicationToMavenRepository") - val pomLines = File(projectDir, "Lib/build/repo/com/example/flavor1Debug/1.0/flavor1Debug-1.0.pom").readLines() - val stdlibVersionLineNumber = pomLines.indexOfFirst { "kotlin-stdlib" in it } + 1 - val versionLine = pomLines[stdlibVersionLineNumber] - assertTrue { "${defaultBuildOptions().kotlinVersion}" in versionLine } - } - } - - @Test - fun testAndroidMppSourceSets(): Unit = with( - Project("new-mpp-android-source-sets") - ) { - // AbstractReportTask#generate() task action was removed in Gradle 6.8+, - // that SourceSetTask is using: https://github.com/gradle/gradle/commit/4dac91ab87ea33ee8689d2a62b691b119198e7c7 - // leading to the issue that ":sourceSets" task is always in 'UP-TO-DATE' state. - // Skipping this check until test will start using AGP 7.0-alpha03+ - if (GradleVersion.version(chooseWrapperVersionOrFinishTest()) < GradleVersion.version("6.8")) { - build("sourceSets") { - assertSuccessful() - - assertContains("Android resources: [lib/src/main/res, lib/src/androidMain/res]") - assertContains("Assets: [lib/src/main/assets, lib/src/androidMain/assets]") - assertContains("AIDL sources: [lib/src/main/aidl, lib/src/androidMain/aidl]") - assertContains("RenderScript sources: [lib/src/main/rs, lib/src/androidMain/rs]") - assertContains("JNI sources: [lib/src/main/jni, lib/src/androidMain/jni]") - assertContains("JNI libraries: [lib/src/main/jniLibs, lib/src/androidMain/jniLibs]") - assertContains("Java-style resources: [lib/src/main/resources, lib/src/androidMain/resources]") - - assertContains("Android resources: [lib/src/androidTestDebug/res, lib/src/androidAndroidTestDebug/res]") - assertContains("Assets: [lib/src/androidTestDebug/assets, lib/src/androidAndroidTestDebug/assets]") - assertContains("AIDL sources: [lib/src/androidTestDebug/aidl, lib/src/androidAndroidTestDebug/aidl]") - assertContains("RenderScript sources: [lib/src/androidTestDebug/rs, lib/src/androidAndroidTestDebug/rs]") - assertContains("JNI sources: [lib/src/androidTestDebug/jni, lib/src/androidAndroidTestDebug/jni]") - assertContains("JNI libraries: [lib/src/androidTestDebug/jniLibs, lib/src/androidAndroidTestDebug/jniLibs]") - assertContains("Java-style resources: [lib/src/androidTestDebug/resources, lib/src/androidAndroidTestDebug/resources]") - - assertContains("Java-style resources: [lib/betaSrc/paidBeta/resources, lib/src/androidPaidBeta/resources]") - assertContains("Java-style resources: [lib/betaSrc/paidBetaDebug/resources, lib/src/androidPaidBetaDebug/resources]") - assertContains("Java-style resources: [lib/betaSrc/paidBetaRelease/resources, lib/src/androidPaidBetaRelease/resources]") - - assertContains("Java-style resources: [lib/betaSrc/freeBeta/resources, lib/src/androidFreeBeta/resources]") - assertContains("Java-style resources: [lib/betaSrc/freeBetaDebug/resources, lib/src/androidFreeBetaDebug/resources]") - assertContains("Java-style resources: [lib/betaSrc/freeBetaRelease/resources, lib/src/androidFreeBetaRelease/resources]") - } - } - - build("testFreeBetaDebug") { - assertFailed() - assertContains("CommonTest > fail FAILED") - assertContains("TestKotlin > fail FAILED") - assertContains("AndroidTestKotlin > fail FAILED") - assertContains("TestJava > fail FAILED") - } - - build("assemble") { - assertSuccessful() - } - - // Test for KT-35016: MPP should recognize android instrumented tests correctly - // TODO: https://issuetracker.google.com/issues/173770818 enable after fix in AGP - /* - build("connectedAndroidTest") { - assertFailed() - assertContains("No connected devices!") - } - */ - } - - @Test - fun testAndroidWithNewMppApp() = with(Project("new-mpp-android")) { - build("assemble", "compileDebugUnitTestJavaWithJavac", "printCompilerPluginOptions") { - assertSuccessful() - - // KT-30784 - assertNotContains("API 'variant.getPackageLibrary()' is obsolete and has been replaced") - - assertContains("KT-29964 OK") // Output from lib/build.gradle - - assertTasksExecuted( - ":lib:compileDebugKotlinAndroidLib", - ":lib:compileReleaseKotlinAndroidLib", - ":lib:compileKotlinJvmLib", - ":lib:compileKotlinJsLib", - ":lib:compileCommonMainKotlinMetadata", - ":app:compileDebugKotlinAndroidApp", - ":app:compileReleaseKotlinAndroidApp", - ":app:compileKotlinJvmApp", - ":app:compileKotlinJsApp", - ":app:compileCommonMainKotlinMetadata", - ":lib:compileDebugUnitTestJavaWithJavac", - ":app:compileDebugUnitTestJavaWithJavac" - ) - - listOf("debug", "release").forEach { variant -> - assertFileExists("lib/build/tmp/kotlin-classes/$variant/com/example/lib/ExpectedLibClass.class") - assertFileExists("lib/build/tmp/kotlin-classes/$variant/com/example/lib/CommonLibClass.class") - assertFileExists("lib/build/tmp/kotlin-classes/$variant/com/example/lib/AndroidLibClass.class") - - assertFileExists("app/build/tmp/kotlin-classes/$variant/com/example/app/AKt.class") - assertFileExists("app/build/tmp/kotlin-classes/$variant/com/example/app/KtUsageKt.class") - } - } - - val groupDir = "lib/build/repo/com/example/" - build("publish") { - assertSuccessful() - assertFileExists(groupDir + "lib-jvmlib") - assertFileExists(groupDir + "lib-jslib") - assertFileExists(groupDir + "lib-androidlib") - assertFileExists(groupDir + "lib-androidlib-debug") - projectDir.resolve(groupDir).deleteRecursively() - } - - // Choose a single variant to publish, check that it's there: - gradleBuildScript("lib").appendText("\nkotlin.android('androidLib').publishLibraryVariants = ['release']") - build("publish") { - assertSuccessful() - assertFileExists(groupDir + "lib-androidlib/1.0/lib-androidlib-1.0.aar") - assertFileExists(groupDir + "lib-androidlib/1.0/lib-androidlib-1.0-sources.jar") - assertNoSuchFile(groupDir + "lib-androidlib-debug") - projectDir.resolve(groupDir).deleteRecursively() - } - - // Enable publishing for all Android variants: - gradleBuildScript("lib").appendText("\nkotlin.android('androidLib') { publishAllLibraryVariants() }") - build("publish") { - assertSuccessful() - assertFileExists(groupDir + "lib-androidlib/1.0/lib-androidlib-1.0.aar") - assertFileExists(groupDir + "lib-androidlib/1.0/lib-androidlib-1.0-sources.jar") - assertFileExists(groupDir + "lib-androidlib-debug/1.0/lib-androidlib-debug-1.0.aar") - assertFileExists(groupDir + "lib-androidlib-debug/1.0/lib-androidlib-debug-1.0-sources.jar") - projectDir.resolve(groupDir).deleteRecursively() - } - - // Then group the variants by flavor and check that only one publication is created: - gradleBuildScript("lib").appendText("\nkotlin.android('androidLib').publishLibraryVariantsGroupedByFlavor = true") - build("publish") { - assertSuccessful() - assertFileExists(groupDir + "lib-androidlib/1.0/lib-androidlib-1.0.aar") - assertFileExists(groupDir + "lib-androidlib/1.0/lib-androidlib-1.0-sources.jar") - assertFileExists(groupDir + "lib-androidlib/1.0/lib-androidlib-1.0-debug.aar") - assertFileExists(groupDir + "lib-androidlib/1.0/lib-androidlib-1.0-debug-sources.jar") - projectDir.resolve(groupDir).deleteRecursively() - } - - // Add one flavor dimension with two flavors, check that the flavors produce grouped publications: - gradleBuildScript("lib").appendText( - "\nandroid { flavorDimensions('foo'); productFlavors { fooBar { dimension 'foo' }; fooBaz { dimension 'foo' } } }" - ) - build("publish") { - assertSuccessful() - listOf("foobar", "foobaz").forEach { flavor -> - assertFileExists(groupDir + "lib-androidlib-$flavor/1.0/lib-androidlib-$flavor-1.0.aar") - assertFileExists(groupDir + "lib-androidlib-$flavor/1.0/lib-androidlib-$flavor-1.0-sources.jar") - assertFileExists(groupDir + "lib-androidlib-$flavor/1.0/lib-androidlib-$flavor-1.0-debug.aar") - assertFileExists(groupDir + "lib-androidlib-$flavor/1.0/lib-androidlib-$flavor-1.0-debug-sources.jar") - } - projectDir.resolve(groupDir).deleteRecursively() - } - - // Disable the grouping and check that all the variants are published under separate artifactIds: - gradleBuildScript("lib").appendText( - "\nkotlin.android('androidLib') { publishLibraryVariantsGroupedByFlavor = false }" - ) - build("publish") { - assertSuccessful() - listOf("foobar", "foobaz").forEach { flavor -> - listOf("-debug", "").forEach { buildType -> - assertFileExists(groupDir + "lib-androidlib-$flavor$buildType/1.0/lib-androidlib-$flavor$buildType-1.0.aar") - assertFileExists(groupDir + "lib-androidlib-$flavor$buildType/1.0/lib-androidlib-$flavor$buildType-1.0-sources.jar") - } - } - projectDir.resolve(groupDir).deleteRecursively() - } - - // Convert the 'app' project to a library, publish the two without metadata, - // check that the dependencies in the POMs are correctly rewritten: - val appGroupDir = "app/build/repo/com/example/" - - gradleSettingsScript().modify { it.replace("enableFeaturePreview", "//") } - gradleBuildScript("app").modify { - it.replace("com.android.application", "com.android.library") - .replace("applicationId", "//") + "\n" + """ - apply plugin: 'maven-publish' - publishing { repositories { maven { url = uri("${'$'}buildDir/repo") } } } - kotlin.android('androidApp') { publishAllLibraryVariants() } - android { flavorDimensions('foo'); productFlavors { fooBar { dimension 'foo' }; fooBaz { dimension 'foo' } } } - """.trimIndent() - } - build("publish") { - assertSuccessful() - listOf("foobar", "foobaz").forEach { flavor -> - listOf("-debug", "").forEach { buildType -> - assertFileExists(appGroupDir + "app-androidapp-$flavor$buildType/1.0/app-androidapp-$flavor$buildType-1.0.aar") - assertFileExists(appGroupDir + "app-androidapp-$flavor$buildType/1.0/app-androidapp-$flavor$buildType-1.0-sources.jar") - val pomText = projectDir.resolve( - appGroupDir + "app-androidapp-$flavor$buildType/1.0/app-androidapp-$flavor$buildType-1.0.pom" - ).readText().replace("\\s+".toRegex(), "") - assertTrue { - "lib-androidlib-$flavor$buildType1.0runtime" in pomText - } - } - } - projectDir.resolve(groupDir).deleteRecursively() - } - - // Also check that api and runtimeOnly MPP dependencies get correctly published with the appropriate scope, KT-29476: - gradleBuildScript("app").modify { - it.replace("implementation project(':lib')", "api project(':lib')") + "\n" + """ - kotlin.sourceSets.commonMain.dependencies { - runtimeOnly(kotlin('reflect')) - } - """.trimIndent() - } - build("publish") { - assertSuccessful() - listOf("foobar", "foobaz").forEach { flavor -> - listOf("-debug", "").forEach { buildType -> - val pomText = projectDir.resolve( - appGroupDir + "app-androidapp-$flavor$buildType/1.0/app-androidapp-$flavor$buildType-1.0.pom" - ).readText().replace("\\s+".toRegex(), "") - assertTrue { - "lib-androidlib-$flavor$buildType1.0compile" in pomText - } - assertTrue { - val kotlinVersion = defaultBuildOptions().kotlinVersion - "kotlin-reflect$kotlinVersionruntime" in pomText - } - } - } - } - } - - @Test - fun testAndroidMppProductionDependenciesInTests() = with(Project("new-mpp-android")) { - // Test the fix for KT-29343 - setupWorkingDir() - - gradleBuildScript("lib").appendText( - "\n" + """ - kotlin.sourceSets { - commonMain { - dependencies { - implementation kotlin("stdlib-common") - } - } - androidLibDebug { - dependencies { - implementation kotlin("reflect") - } - } - androidLibRelease { - dependencies { - implementation kotlin("test-junit") - } - } - } - """.trimIndent() - ) - - val kotlinVersion = defaultBuildOptions().kotlinVersion - testResolveAllConfigurations("lib") { - assertSuccessful() - - // androidLibDebug: - assertContains(">> :lib:debugCompileClasspath --> kotlin-reflect-$kotlinVersion.jar") - assertNotContains(">> :lib:releaseCompileClasspath --> kotlin-reflect-$kotlinVersion.jar") - assertContains(">> :lib:debugAndroidTestCompileClasspath --> kotlin-reflect-$kotlinVersion.jar") - assertContains(">> :lib:debugUnitTestCompileClasspath --> kotlin-reflect-$kotlinVersion.jar") - assertNotContains(">> :lib:releaseUnitTestCompileClasspath --> kotlin-reflect-$kotlinVersion.jar") - - // androidLibRelease: - assertNotContains(">> :lib:debugCompileClasspath --> kotlin-test-junit-$kotlinVersion.jar") - assertContains(">> :lib:releaseCompileClasspath --> kotlin-test-junit-$kotlinVersion.jar") - assertNotContains(">> :lib:debugAndroidTestCompileClasspath --> kotlin-test-junit-$kotlinVersion.jar") - assertNotContains(">> :lib:debugUnitTestCompileClasspath --> kotlin-test-junit-$kotlinVersion.jar") - assertContains(">> :lib:releaseUnitTestCompileClasspath --> kotlin-test-junit-$kotlinVersion.jar") - } - } - - @Test - fun testCustomAttributesInAndroidTargets() = with(Project("new-mpp-android")) { - // Test the fix for KT-27714 - - setupWorkingDir() - - // Enable publishing for all Android variants: - gradleBuildScript("lib").appendText("\nkotlin.android('androidLib') { publishAllLibraryVariants() }") - - val groupDir = "lib/build/repo/com/example/" - - build("publish") { - assertSuccessful() - - // Also check that custom user-specified attributes are written in all Android modules metadata: - assertFileContains( - groupDir + "lib-androidlib/1.0/lib-androidlib-1.0.module", - "\"com.example.target\": \"androidLib\"", - "\"com.example.compilation\": \"release\"" - ) - assertFileContains( - groupDir + "lib-androidlib-debug/1.0/lib-androidlib-debug-1.0.module", - "\"com.example.target\": \"androidLib\"", - "\"com.example.compilation\": \"debug\"" - ) - - projectDir.resolve(groupDir).deleteRecursively() - } - - // Check that the consumer side uses custom attributes specified in the target and compilations: - run { - val appBuildScriptBackup = gradleBuildScript("app").readText() - - gradleBuildScript("lib").appendText( - "\n" + """ - kotlin.targets.all { - attributes.attribute( - Attribute.of("com.example.target", String), - targetName - ) - } - """.trimIndent() - ) - gradleBuildScript("app").appendText( - "\n" + """ - kotlin.targets.androidApp.attributes.attribute( - Attribute.of("com.example.target", String), - "notAndroidLib" - ) - """.trimIndent() - ) - - build(":app:compileDebugKotlinAndroidApp") { - assertFailed() // dependency resolution should fail - assertTrue( - "Required com.example.target 'notAndroidLib'" in output || - "attribute 'com.example.target' with value 'notAndroidLib'" in output - ) - } - - gradleBuildScript("lib").writeText( - appBuildScriptBackup + "\n" + """ - kotlin.targets.all { - compilations.all { - attributes.attribute( - Attribute.of("com.example.compilation", String), - targetName + compilationName.capitalize() - ) - } - } - """.trimIndent() - ) - gradleBuildScript("app").writeText( - appBuildScriptBackup + "\n" + """ - kotlin.targets.androidApp.compilations.all { - attributes.attribute( - Attribute.of("com.example.compilation", String), - "notDebug" - ) - } - """.trimIndent() - ) - - build(":app:compileDebugKotlinAndroidApp") { - assertFailed() - assertTrue( - "Required com.example.compilation 'notDebug'" in output || - "attribute 'com.example.compilation' with value 'notDebug'" in output - ) - } - } - } - - @Test - fun testLintInAndroidProjectsDependingOnMppWithoutAndroid() = with(Project("AndroidProject")) { - embedProject(Project("sample-lib", directoryPrefix = "new-mpp-lib-and-app")) - gradleBuildScript("Lib").appendText( - "\ndependencies { implementation(project(':sample-lib')) }" - ) - val lintTask = ":Lib:lintFlavor1Debug" - build(lintTask) { - assertSuccessful() - assertTasksExecuted(lintTask) // Check that the lint task ran successfully, KT-27170 - } - } - - @Test - fun testJvmWithJava() = with(Project("mppJvmWithJava")) { - setupWorkingDir() - - build("build") { - assertSuccessful() - } - - build("assemble") { - assertSuccessful() - } - } - - @Test - fun `test KotlinToolingMetadataArtifact is bundled into apk`(): Unit = with(Project("kotlinToolingMetadataAndroid")) { - build("assembleDebug") { - assertSuccessful() - assertTasksNotExecuted(":${BuildKotlinToolingMetadataTask.defaultTaskName}") - - val debugApk = project.projectDir.resolve("build/outputs/apk/debug/project-debug.apk") - assertTrue(debugApk.exists(), "Missing debug apk ${debugApk.path}") - ZipFile(debugApk).use { zip -> - assertNull(zip.getEntry("kotlin-tooling-metadata.json"), "Expected metadata *not* being packaged into debug apk") - } - } - - build("assembleRelease") { - assertSuccessful() - assertTasksExecuted(":${BuildKotlinToolingMetadataTask.defaultTaskName}") - val releaseApk = project.projectDir.resolve("build/outputs/apk/release/project-release-unsigned.apk") - - assertTrue(releaseApk.exists(), "Missing release apk ${releaseApk.path}") - ZipFile(releaseApk).use { zip -> - assertNotNull(zip.getEntry("kotlin-tooling-metadata.json"), "Expected metadata being packaged into release apk") - } - } - } - - @Test - fun `test MPP allTests task depending on Android unit tests`(): Unit = with(Project("new-mpp-android")) { - build(":lib:allTests", "--dry-run") { - assertSuccessful() - assertContains(":lib:testDebugUnitTest SKIPPED") - assertContains(":lib:testReleaseUnitTest SKIPPED") - } - } -} - -open class KotlinAndroid70GradleIT : KotlinAndroid41GradleIT() { - override val androidGradlePluginVersion: AGPVersion - get() = AGPVersion.v7_0_0 - - override val defaultGradleVersion: GradleVersionRequired - get() = GradleVersionRequired.AtLeast(TestVersions.Gradle.G_7_0) - - override fun defaultBuildOptions(): BuildOptions { - val javaHome = File(System.getProperty("jdk11Home") ?: error("jdk11Home not specified")) - Assume.assumeTrue("JDK 11 should be available", javaHome.isDirectory) - return super.defaultBuildOptions().copy(javaHome = javaHome, warningMode = WarningMode.Summary) - } - - /** - * Regression test for KT-49066. It is not really AGP 7.0 specific, but it is not added to the base class to avoid - * running it multiple times. - */ - @Test - fun testCustomModuleName() { - val project = Project("AndroidIncrementalMultiModule") - val options = defaultBuildOptions().copy(incremental = true, kotlinDaemonDebugPort = null) - - project.setupWorkingDir().also { - project.gradleBuildScript("libAndroid").appendText( - """ - - android.kotlinOptions { - moduleName = "custom_path" - } - """.trimIndent() - ) - } - - project.build("assembleDebug", options = options) { - assertSuccessful() - assertFileExists("libAndroid/build/tmp/kotlin-classes/debug/META-INF/custom_path.kotlin_module") - } - - val libAndroidUtilKt = project.projectDir.getFileByName("libAndroidUtil.kt") - libAndroidUtilKt.modify { it.replace("fun libAndroidUtil(): String", "fun libAndroidUtil(): CharSequence") } - project.build("assembleDebug", options = options) { - assertSuccessful() - val affectedSources = project.projectDir.getFilesByNames("libAndroidUtil.kt", "useLibAndroidUtil.kt") - assertCompiledKotlinSources(project.relativize(affectedSources)) - } - } - - @Test - fun testAndroidWithKaptIncremental() { - val project = Project("AndroidIncrementalMultiModule") - val options = defaultBuildOptions().copy(incremental = true) - project.setupWorkingDir().also { - project.gradleBuildScript("app").modify { - """ - apply plugin: 'org.jetbrains.kotlin.kapt' - $it - """.trimIndent() - } - } - project.build(":app:testDebugUnitTest", options = options) { - assertSuccessful() - } - - project.projectDir - .resolve("app/src/main/kotlin/com/example/KotlinActivity.kt") - .appendText( - """ - { - private val x = 1 - } - """.trimIndent() - ) - - project.build(":app:testDebugUnitTest", options = options) { - assertSuccessful() - assertNotContains(NON_INCREMENTAL_COMPILATION_WILL_BE_PERFORMED) - } - } - -} - -open class KotlinAndroid71GradleIT : KotlinAndroid70GradleIT() { - override val androidGradlePluginVersion: AGPVersion - get() = AGPVersion.v7_1_0 - - override val defaultGradleVersion: GradleVersionRequired - get() = GradleVersionRequired.AtLeast("7.2") - - /** - * Starting from AGP version 7.1.0-alpha13, a new attribute com.android.build.api.attributes.AgpVersionAttr was added. - * This attribute is *not intended* to be published. - */ - @Test - fun testKT49798AgpVersionAttrNotPublished() = with(Project("new-mpp-android")) { - build("publish") { - val debugPublicationDirectory = projectDir.resolve("lib/build/repo/com/example/lib-androidlib-debug") - val releasePublicationDirectory = projectDir.resolve("lib/build/repo/com/example/lib-androidlib") - - listOf(debugPublicationDirectory, releasePublicationDirectory).forEach { publicationDirectory -> - assertTrue(publicationDirectory.exists(), "Missing publication directory: $publicationDirectory") - val moduleFiles = publicationDirectory.walkTopDown().filter { file -> file.extension == "module" }.toList() - assertTrue(moduleFiles.isNotEmpty(), "Missing .module file in $publicationDirectory") - assertTrue(moduleFiles.size <= 1, "Multiple .module files in $publicationDirectory: $moduleFiles") - - val moduleFile = moduleFiles.single() - val moduleFileText = moduleFile.readText() - assertTrue("AgpVersionAttr" !in moduleFileText, ".module file $moduleFile leaks AgpVersionAttr") - } - } - } - - @Test - fun testAndroidMultiplatformPublicationAGPCompatibility() = with(Project("new-mpp-android-agp-compatibility")) { - /* Publish producer library with current version of AGP */ - build(":producer:publishAllPublicationsToBuildDirRepository") { - assertSuccessful() - - /* Check expected publication layout */ - assertFileExists("build/repo/com/example/producer-android") - assertFileExists("build/repo/com/example/producer-android-debug") - assertFileExists("build/repo/com/example/producer-jvm") - } - - val checkedConsumerAGPVersions = AGPVersion.testedVersions - // Special version added for testing KT-49798 - .plus(AGPVersion.fromString("7.1.0-beta02")) - .filter { version -> version >= AGPVersion.v4_2_0 } - // Current Gradle version within kotlin.git is not sufficient to build with higher AGP - .filter { version -> version < AGPVersion.v7_2_2 } - - checkedConsumerAGPVersions.forEach { agpVersion -> - - this.setupWorkingDir() - println("Testing compatibility for AGP consumer version $agpVersion") - val buildOptions = defaultBuildOptions().copy(androidGradlePluginVersion = agpVersion) - - /* - Project: multiplatformAndroidConsumer is a mpp project with jvm and android targets. - This project depends on the previous publication as 'commonMainImplementation' dependency - */ - build(":multiplatformAndroidConsumer:assemble", options = buildOptions) { - assertSuccessful( - "multiplatformAndroidConsumer build failed with consumer agpVersion $agpVersion (Producer: $androidGradlePluginVersion)" - ) - } - - /* - Project: plainAndroidConsumer only uses the 'kotlin("android")' plugin - This project depends on the previous publication as 'implementation' dependency - */ - build(":plainAndroidConsumer:assemble", options = buildOptions) { - assertSuccessful( - "plainAndroidConsumer build failed with consumer agpVersion $agpVersion (Producer: $androidGradlePluginVersion)" - ) - } - } - } - - @Test - fun `test associate compilation dependencies are passed correctly to android test compilations`() { - with(Project("kt-49877")) { - build("allTests") { - assertSuccessful() - assertTasksExecuted( - ":compileDebugKotlinAndroid", - ":compileReleaseKotlinAndroid", - ":compileDebugUnitTestKotlinAndroid", - ":compileReleaseUnitTestKotlinAndroid", - ":testDebugUnitTest", - ":testReleaseUnitTest", - ) - } - - // instrumented tests don't work without a device, so we only compile them - build("packageDebugAndroidTest") { - assertSuccessful() - assertTasksExecuted( - ":compileDebugAndroidTestKotlinAndroid", - ) - } - } - } -} - -abstract class KotlinAndroid4GradleIT : AbstractKotlinAndroidGradleTests() { - @Test - fun testAfterEvaluateOrdering() = with(Project("AndroidProject")) { - setupWorkingDir() - - gradleBuildScript("Lib").writeText( - """ - buildscript { - repositories { - mavenLocal() - google() - gradlePluginPortal() - } - dependencies { - classpath "com.android.tools.build:gradle:${'$'}android_tools_version" - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${'$'}kotlin_version" - } - } - - plugins { - id 'org.jetbrains.kotlin.multiplatform' - } - - class MyAction implements kotlin.jvm.functions.Function1 { - Void invoke (Project p) { - println("compilations: " + p.kotlin.targets.getByName("android").compilations.names) - } - } - - org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginKt.whenEvaluated(project, new MyAction ()) - - apply plugin : "android-library" - - android { - compileSdkVersion 22 - } - - kotlin { android("android") { } } - """.trimIndent() - ) - - build("help") { - assertSuccessful() - val reportedCompilations = output.lines() - .single { it.contains("compilations: ") } - .substringAfter("compilations: ") - .removeSurrounding("[", "]") - .split(", ") - .toSet() - assertEquals( - setOf("debug", "debugAndroidTest", "debugUnitTest", "release", "releaseUnitTest"), - reportedCompilations - ) - } - } -} - -abstract class AbstractKotlinAndroidGradleTests : BaseGradleIT() { - - abstract val androidGradlePluginVersion: AGPVersion - - override fun defaultBuildOptions() = - super.defaultBuildOptions().copy( - androidHome = KtTestUtil.findAndroidSdk(), - androidGradlePluginVersion = androidGradlePluginVersion - ) - - @Test - fun testSimpleCompile() { - val project = Project("AndroidProject") - - val modules = listOf("Android", "Lib") - val flavors = listOf("Flavor1", "Flavor2") - val buildTypes = listOf("Debug") - - val tasks = arrayListOf() - for (module in modules) { - for (flavor in flavors) { - for (buildType in buildTypes) { - tasks.add(":$module:compile$flavor${buildType}Kotlin") - } - } - } - - project.build("assembleDebug", "test") { - assertSuccessful() - // Before 3.0 AGP test only modules are compiled only against one flavor and one build type, - // and contain only the compileDebugKotlin task. - // After 3.0 AGP test only modules contain a compileKotlin task for each variant. - tasks.addAll(findTasksByPattern(":Test:compile[\\w\\d]+Kotlin")) - assertTasksExecuted(tasks) - assertContains("InternalDummyTest PASSED") - checkKotlinGradleBuildServices() - } - - // Run the build second time, assert everything is up-to-date - project.build("assembleDebug") { - assertSuccessful() - assertTasksUpToDate(tasks) - } - - // Run the build third time, re-run tasks - - project.build("assembleDebug", "--rerun-tasks") { - assertSuccessful() - assertTasksExecuted(tasks) - checkKotlinGradleBuildServices() - } - } - - @Test - fun testAssembleAndroidTestFirst() { - val project = Project("AndroidProject", minLogLevel = LogLevel.INFO) - - // Execute 'assembleAndroidTest' first, without 'build' side effects - project.build("assembleAndroidTest") { - assertSuccessful() - } - } - - @Test - fun testIncrementalCompile() { - val project = Project("AndroidIncrementalSingleModuleProject") - val options = defaultBuildOptions().copy(incremental = true) - - project.build("assembleDebug", options = options) { - assertSuccessful() - } - - val getSomethingKt = project.projectDir.walk().filter { it.isFile && it.name.endsWith("getSomething.kt") }.first() - getSomethingKt.writeText( - """ - package com.example - - fun getSomething() = 10 - """ - ) - - project.build("assembleDebug", options = options) { - assertSuccessful() - val affectedKotlinFiles = listOf( - "app/src/main/kotlin/com/example/KotlinActivity1.kt", - "app/src/main/kotlin/com/example/getSomething.kt" - ) - assertCompiledKotlinSources(affectedKotlinFiles) - assertCompiledJavaSources(listOf("app/src/main/java/com/example/JavaActivity.java"), weakTesting = true) - } - } - - @Test - fun testMultiModuleIC() { - val project = Project("AndroidIncrementalMultiModule") - val options = defaultBuildOptions().copy(incremental = true, kotlinDaemonDebugPort = null) - - project.build("assembleDebug", options = options) { - assertSuccessful() - } - - val libAndroidUtilKt = project.projectDir.getFileByName("libAndroidUtil.kt") - libAndroidUtilKt.modify { it.replace("fun libAndroidUtil(): String", "fun libAndroidUtil(): CharSequence") } - project.build("assembleDebug", options = options) { - assertSuccessful() - val affectedSources = project.projectDir.getFilesByNames("libAndroidUtil.kt", "useLibAndroidUtil.kt") - assertCompiledKotlinSources(project.relativize(affectedSources)) - } - - val libAndroidClassesOnlyUtilKt = project.projectDir.getFileByName("LibAndroidClassesOnlyUtil.kt") - libAndroidClassesOnlyUtilKt.modify { - it.replace( - "fun libAndroidClassesOnlyUtil(): String", - "fun libAndroidClassesOnlyUtil(): CharSequence" - ) - } - project.build("assembleDebug", options = options) { - assertSuccessful() - val affectedSources = project.projectDir.getFilesByNames("LibAndroidClassesOnlyUtil.kt", "useLibAndroidClassesOnlyUtil.kt") - assertCompiledKotlinSources(project.relativize(affectedSources)) - } - - val libJvmUtilKt = project.projectDir.getFileByName("LibJvmUtil.kt") - libJvmUtilKt.modify { it.replace("fun libJvmUtil(): String", "fun libJvmUtil(): CharSequence") } - project.build("assembleDebug", options = options) { - assertSuccessful() - val affectedSources = project.projectDir.getFilesByNames("LibJvmUtil.kt", "useLibJvmUtil.kt") - assertCompiledKotlinSources(project.relativize(affectedSources)) - } - } - - @Test - fun testIncrementalBuildWithNoChanges() { - val project = Project("AndroidIncrementalSingleModuleProject") - val tasksToExecute = listOf( - ":app:compileDebugKotlin", - ":app:compileDebugJavaWithJavac" - ) - - project.build("assembleDebug") { - assertSuccessful() - assertTasksExecuted(tasksToExecute) - } - - project.build("assembleDebug") { - assertSuccessful() - assertTasksUpToDate(tasksToExecute) - } - } - - @Test - fun testAndroidDaggerIC() { - val project = Project("AndroidDaggerProject") - val options = defaultBuildOptions().copy(incremental = true) - - project.build("assembleDebug", options = options) { - assertSuccessful() - output - } - - val androidModuleKt = project.projectDir.getFileByName("AndroidModule.kt") - androidModuleKt.modify { - it.replace( - "fun provideApplicationContext(): Context {", - "fun provideApplicationContext(): Context? {" - ) - } - - project.build(":app:assembleDebug", options = options) { - assertSuccessful() - assertTasksExecuted( - ":app:kaptGenerateStubsDebugKotlin", - ":app:kaptDebugKotlin", - ":app:compileDebugKotlin", - ":app:compileDebugJavaWithJavac" - ) - - // Output is combined with previous build, but we are only interested in the compilation - // from second build to avoid false positive test failure - val filteredOutput = output - .lineSequence() - .filter { it.contains("[KOTLIN] compile iteration:") } - .drop(1) - .joinToString(separator = "/n") - assertCompiledKotlinSources(project.relativize(androidModuleKt), output = filteredOutput) - } - } - - @Test - fun testAndroidIcepickProject() { - val project = Project("AndroidIcepickProject") - val options = defaultBuildOptions().copy(incremental = false) - - project.build("assembleDebug", options = options) { - assertSuccessful() - } - } - - @Test - fun testParcelize() { - val project = Project("AndroidParcelizeProject") - val options = defaultBuildOptions().copy(incremental = false) - - project.build("assembleDebug", options = options) { - assertSuccessful() - } - } - - @Test - fun shouldAllowToApplyPluginWhenAndroidPluginIsMissing() { - with(Project("simpleProject", minLogLevel = LogLevel.WARN)) { - setupWorkingDir() - - gradleBuildScript().modify { - it.lines().joinToString( - separator = "\n", - transform = jvmToAndroidModifier() - ) - } - gradleSettingsScript().modify { - it.lines().joinToString( - separator = "\n", - transform = jvmToAndroidModifier(true) - ) - } - - build("tasks") { - assertFailed() - assertContains("'kotlin-android' plugin requires one of the Android Gradle plugins.") - } - } - } - - @Test - fun testLintDependencyResolutionKt49483() = with(Project("AndroidProject")) { - setupWorkingDir() - - gradleBuildScript().modify { - """ - plugins { - id("com.android.lint") - } - - """.trimIndent() + it - } - - gradleBuildScript("Lib").appendText( - "\n" + """ - android { - lintOptions.checkDependencies = true - } - dependencies { - implementation(project(":java-lib")) - } - """.trimIndent() - ) - - gradleSettingsScript().appendText( - "\n" + """ - include("java-lib") - """.trimIndent() - ) - - with(projectDir.resolve("java-lib/build.gradle.kts")) { - ensureParentDirsCreated() - writeText( - """ - plugins { - id("java-library") - id("com.android.lint") - } - """.trimIndent() - ) - } - - build(":Lib:lintFlavor1Debug") { - assertSuccessful() - assertNotContains("as an external dependency and not analyze it.") - } - } - - private fun jvmToAndroidModifier( - appendKotlinVersion: Boolean = false - ): (String) -> CharSequence = { line -> - if (line.contains("id \"org.jetbrains.kotlin.jvm\"")) { - " id \"org.jetbrains.kotlin.android\"" + if (appendKotlinVersion) " version \"${'$'}kotlin_version\"" else "" - } else { - line - } - } -} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/android/Kapt3AndroidIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/android/Kapt3AndroidIT.kt index 098c0b4141b..61e0db3afbe 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/android/Kapt3AndroidIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/android/Kapt3AndroidIT.kt @@ -450,6 +450,208 @@ class Kapt3AndroidIT : Kapt3BaseIT() { } } + @DisplayName("kapt uses AGP annotation processor option providers as nested inputs") + @GradleAndroidTest + fun testKaptUsingApOptionProvidersAsNestedInputOutput( + gradleVersion: GradleVersion, + agpVersion: String, + jdkVersion: JdkVersions.ProvidedJdk, + ) { + project( + "AndroidProject", + gradleVersion, + buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion, kaptOptions = null), + buildJdk = jdkVersion.location + ) { + subProject("Android").buildGradle.appendText( + //language=Gradle + """ + + apply plugin: 'kotlin-kapt' + + class MyNested implements org.gradle.process.CommandLineArgumentProvider { + private final File argsFile + + MyNested(File argsFile) { + this.argsFile = argsFile + } + + @InputFile + File inputFile = null + + @Override + Iterable asArguments() { + // Read the arguments from a file, because changing them in a build script is treated as an + // implementation change by Gradle: + return [argsFile.text] + } + } + + def nested = new MyNested(rootProject.file("args.txt")) + nested.inputFile = file("${'$'}projectDir/in.txt") + + android.applicationVariants.all { + it.javaCompileOptions.annotationProcessorOptions.compilerArgumentProviders.add(nested) + } + """.trimIndent() + ) + + val inFile = subProject("Android").projectPath.resolve("in.txt") + inFile.writeText("1234") + val argsFile = projectPath.resolve("args.txt") + argsFile.writeText("1234") + + val kaptTasks = listOf(":Android:kaptFlavor1DebugKotlin") + val javacTasks = listOf(":Android:compileFlavor1DebugJavaWithJavac") + + val buildTasks = (kaptTasks + javacTasks).toTypedArray() + + build(*buildTasks) { + assertTasksExecuted(kaptTasks + javacTasks) + } + + inFile.appendText("5678") + + build(*buildTasks) { + assertTasksExecuted(kaptTasks) + assertTasksUpToDate(javacTasks) + } + + // Changing only the annotation provider arguments should not trigger the tasks to run, as the arguments may be outputs, + // internals or neither: + argsFile.appendText("5678") + + build(*buildTasks) { + assertTasksUpToDate(javacTasks + kaptTasks) + } + } + } + + @DisplayName("agp annotation processor nested arguments are not evaluated at configuration time") + @GradleAndroidTest + fun testAgpNestedArgsNotEvaluatedDuringConfiguration( + gradleVersion: GradleVersion, + agpVersion: String, + jdkVersion: JdkVersions.ProvidedJdk, + ) { + project( + "AndroidProject", + gradleVersion, + buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion), + buildJdk = jdkVersion.location + ) { + subProject("Android").buildGradle.appendText( + //language=Gradle + """ + + apply plugin: 'kotlin-kapt' + + class MyNested implements org.gradle.process.CommandLineArgumentProvider { + @Override + Iterable asArguments() { + throw new RuntimeException("This should not be invoked during configuration.") + } + } + + def nested = new MyNested() + + android.applicationVariants.all { + it.javaCompileOptions.annotationProcessorOptions.compilerArgumentProviders.add(nested) + } + """.trimIndent() + ) + + build(":Android:kaptFlavor1DebugKotlin", "--dry-run") + + build( + ":Android:kaptFlavor1DebugKotlin", "--dry-run", + buildOptions = buildOptions.copy(kaptOptions = BuildOptions.KaptOptions(verbose = false)) + ) + } + } + + @DisplayName("incremental compilation works with dagger") + @GradleAndroidTest + fun testAndroidDaggerIC( + gradleVersion: GradleVersion, + agpVersion: String, + jdkVersion: JdkVersions.ProvidedJdk, + ) { + project( + "AndroidDaggerProject", + gradleVersion, + buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion), + buildJdk = jdkVersion.location + ) { + build("assembleDebug") + + val androidModuleKt = subProject("app").javaSourcesDir().resolve("com/example/dagger/kotlin/AndroidModule.kt") + androidModuleKt.modify { + it.replace( + "fun provideApplicationContext(): Context {", + "fun provideApplicationContext(): Context? {" + ) + } + + build(":app:assembleDebug", buildOptions = buildOptions.copy(logLevel = LogLevel.DEBUG)) { + assertTasksExecuted( + ":app:kaptGenerateStubsDebugKotlin", + ":app:kaptDebugKotlin", + ":app:compileDebugKotlin", + ":app:compileDebugJavaWithJavac" + ) + + // Output is combined with previous build, but we are only interested in the compilation + // from second build to avoid false positive test failure + val filteredOutput = output + .lineSequence() + .filter { it.contains("[KOTLIN] compile iteration:") } + .drop(1) + .joinToString(separator = "/n") + assertCompiledKotlinSources(listOf(androidModuleKt).relativizeTo(projectPath), output = filteredOutput) + } + } + } + + @DisplayName("incremental compilation with android and kapt") + @GradleAndroidTest + fun testAndroidWithKaptIncremental( + gradleVersion: GradleVersion, + agpVersion: String, + jdkVersion: JdkVersions.ProvidedJdk, + ) { + project( + "AndroidIncrementalMultiModule", + gradleVersion, + buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion), + buildJdk = jdkVersion.location + ) { + val appProject = subProject("app") + appProject.buildGradle.modify { + //language=Gradle + """ + apply plugin: 'org.jetbrains.kotlin.kapt' + $it + """.trimIndent() + } + + build(":app:testDebugUnitTest") + + appProject.kotlinSourcesDir().resolve("com/example/KotlinActivity.kt").appendText( + //language=kt + """ + { + private val x = 1 + } + """.trimIndent() + ) + + build(":app:testDebugUnitTest") { + assertOutputDoesNotContain(NON_INCREMENTAL_COMPILATION_WILL_BE_PERFORMED) + } + } + } + private fun TestProject.setupDataBinding() { buildGradle.appendText( //language=Gradle diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/android/KotlinAndroidIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/android/KotlinAndroidIT.kt new file mode 100644 index 00000000000..545cc23f084 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/android/KotlinAndroidIT.kt @@ -0,0 +1,284 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.android + +import org.gradle.api.logging.LogLevel +import org.gradle.testkit.runner.BuildResult +import org.gradle.util.GradleVersion +import org.jetbrains.kotlin.gradle.testbase.* +import org.jetbrains.kotlin.gradle.util.checkedReplace +import org.junit.jupiter.api.DisplayName +import java.nio.file.Files +import kotlin.io.path.appendText +import kotlin.io.path.readLines +import kotlin.io.path.writeText +import kotlin.test.assertContains + +@DisplayName("base kotlin-android tests") +@AndroidGradlePluginTests +class KotlinAndroidIT : KGPBaseTest() { + + @DisplayName("android project compilation smoke test") + @GradleAndroidTest + fun testSimpleCompile( + gradleVersion: GradleVersion, + agpVersion: String, + jdkVersion: JdkVersions.ProvidedJdk, + ) { + fun BuildResult.assertKotlinGradleBuildServicesAreInitialized() { + assertOutputContainsExactTimes("Initialized KotlinGradleBuildServices", expectedRepetitionTimes = 1) + assertOutputContainsExactTimes("Disposed KotlinGradleBuildServices", expectedRepetitionTimes = 1) + } + + project( + "AndroidProject", + gradleVersion, + buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion, logLevel = LogLevel.DEBUG), + buildJdk = jdkVersion.location + ) { + val modules = listOf("Android", "Lib") + val flavors = listOf("Flavor1", "Flavor2") + val buildTypes = listOf("Debug") + + val expectedTasks = mutableListOf() + for (module in modules) { + for (flavor in flavors) { + for (buildType in buildTypes) { + expectedTasks.add(":$module:compile$flavor${buildType}Kotlin") + } + } + } + + build("assembleDebug", "test") { + val pattern = ":Test:compile[\\w\\d]+Kotlin" + expectedTasks.addAll(findTasksByPattern(pattern.toRegex())) + assertTasksExecuted(expectedTasks) + assertOutputContains("InternalDummyTest PASSED") + assertKotlinGradleBuildServicesAreInitialized() + } + + // Run the a build second time, assert everything is up-to-date + build("assembleDebug") { + assertTasksUpToDate(expectedTasks) + assertKotlinGradleBuildServicesAreInitialized() + } + } + } + + @DisplayName("KT-16897: the `assembleAndroidTest` task builds fine without the `build` task") + @GradleAndroidTest + fun testAssembleAndroidTest( + gradleVersion: GradleVersion, + agpVersion: String, + jdkVersion: JdkVersions.ProvidedJdk, + ) { + project( + "AndroidProject", + gradleVersion, + buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion), + buildJdk = jdkVersion.location + ) { + build("assembleAndroidTest") + } + } + + @DisplayName("KT-12303: compilation works fine with icepick") + @GradleAndroidTest + fun testAndroidIcepickProject( + gradleVersion: GradleVersion, + agpVersion: String, + jdkVersion: JdkVersions.ProvidedJdk, + ) { + project( + "AndroidIcepickProject", + gradleVersion, + buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion), + buildJdk = jdkVersion.location + ) { + build("assembleDebug") + } + } + + @DisplayName("compilation works fine with parcelize") + @GradleAndroidTest + fun testParcelize( + gradleVersion: GradleVersion, + agpVersion: String, + jdkVersion: JdkVersions.ProvidedJdk, + ) { + project( + "AndroidParcelizeProject", + gradleVersion, + buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion), + buildJdk = jdkVersion.location + ) { + build("assembleDebug") + } + } + + @DisplayName("KT-46626: kotlin-android produces clear error message when no android plugin is applied") + @GradleAndroidTest + fun shouldAllowToApplyPluginWhenAndroidPluginIsMissing( + gradleVersion: GradleVersion, + agpVersion: String, + jdkVersion: JdkVersions.ProvidedJdk, + ) { + project( + "simpleProject", + gradleVersion, + buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion), + buildJdk = jdkVersion.location + ) { + fun replaceKotlinJvmByKotlinAndroid(specifyPluginVersion: Boolean): (String) -> CharSequence = { line -> + if (line.contains("id \"org.jetbrains.kotlin.jvm\"")) { + " id \"org.jetbrains.kotlin.android\"" + if (specifyPluginVersion) " version \"${'$'}kotlin_version\"" else "" + } else { + line + } + } + + buildGradle.modify { + it.lines().joinToString( + separator = "\n", + transform = replaceKotlinJvmByKotlinAndroid(false) + ) + } + settingsGradle.modify { + it.lines().joinToString( + separator = "\n", + transform = replaceKotlinJvmByKotlinAndroid(true) + ) + } + + buildAndFail("tasks") { + assertOutputContains("'kotlin-android' plugin requires one of the Android Gradle plugins.") + } + } + } + + @DisplayName("KT-49483: lint dependencies are resolved properly") + @GradleAndroidTest + fun testLintDependencyResolutionKt49483( + gradleVersion: GradleVersion, + agpVersion: String, + jdkVersion: JdkVersions.ProvidedJdk, + ) { + project( + "AndroidProject", + gradleVersion, + buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion), + buildJdk = jdkVersion.location + ) { + buildGradle.modify { + it.replace( + "plugins {", + """ + plugins { + id("com.android.lint") + + """.trimIndent() + ) + } + + subProject("Lib").buildGradle.appendText( + //language=Gradle + """ + + android { + lintOptions.checkDependencies = true + } + dependencies { + implementation(project(":java-lib")) + } + """.trimIndent() + ) + + settingsGradle.appendText( + //language=Gradle + """ + + include("java-lib") + """.trimIndent() + ) + + subProject("java-lib").buildGradle.apply { + Files.createDirectories(parent) + writeText( + //language=Gradle + """ + plugins { + id("java-library") + id("com.android.lint") + } + """.trimIndent() + ) + } + + // Gradle 6 + AGP 4 produce a deprecation warning on parallel executions about resolving a configuration from another project + build(":Lib:lintFlavor1Debug", buildOptions = buildOptions.copy(parallel = gradleVersion >= GradleVersion.version("7.0"))) { + assertOutputDoesNotContain("as an external dependency and not analyze it.") + } + } + } + + @DisplayName("publish with omitted stdlib version sets proper version") + @GradleAndroidTest + fun testOmittedStdlibVersion( + gradleVersion: GradleVersion, + agpVersion: String, + jdkVersion: JdkVersions.ProvidedJdk, + ) { + project( + "AndroidProject", + gradleVersion, + buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion), + buildJdk = jdkVersion.location + ) { + subProject("Lib").buildGradle.modify { + it.checkedReplace( + "kotlin-stdlib:\$kotlin_version", + "kotlin-stdlib" + ) + + //language=Gradle + """ + + apply plugin: 'maven-publish' + + android { + defaultPublishConfig 'flavor1Debug' + } + + afterEvaluate { + publishing { + publications { + flavorDebug(MavenPublication) { + from components.flavor1Debug + + group = 'com.example' + artifactId = 'flavor1Debug' + version = '1.0' + } + } + repositories { + maven { + url = "file://${'$'}buildDir/repo" + } + } + } + } + """.trimIndent() + } + + build(":Lib:assembleFlavor1Debug", ":Lib:publish") { + assertTasksExecuted(":Lib:compileFlavor1DebugKotlin", ":Lib:publishFlavorDebugPublicationToMavenRepository") + val pomLines = subProject("Lib").projectPath.resolve("build/repo/com/example/flavor1Debug/1.0/flavor1Debug-1.0.pom").readLines() + val stdlibVersionLineNumber = pomLines.indexOfFirst { "kotlin-stdlib" in it } + 1 + val versionLine = pomLines[stdlibVersionLineNumber] + assertContains(versionLine, "${buildOptions.kotlinVersion}") + } + } + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/android/KotlinAndroidIncrementalIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/android/KotlinAndroidIncrementalIT.kt new file mode 100644 index 00000000000..a20b4ecc86d --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/android/KotlinAndroidIncrementalIT.kt @@ -0,0 +1,183 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.android + +import org.gradle.api.logging.LogLevel +import org.gradle.util.GradleVersion +import org.jetbrains.kotlin.gradle.testbase.* +import org.jetbrains.kotlin.gradle.util.getFileByName +import org.jetbrains.kotlin.gradle.util.getFilesByNames +import org.jetbrains.kotlin.gradle.util.modify +import org.junit.jupiter.api.DisplayName +import kotlin.io.path.appendText +import kotlin.io.path.writeText + +@DisplayName("kotlin-android incremental compilation tests") +@AndroidGradlePluginTests +class KotlinAndroidIncrementalIT : KGPBaseTest() { + @DisplayName("incremental compilation works with single-module android projects") + @GradleAndroidTest + fun testIncrementalCompilation( + gradleVersion: GradleVersion, + agpVersion: String, + jdkVersion: JdkVersions.ProvidedJdk, + ) { + project( + "AndroidIncrementalSingleModuleProject", + gradleVersion, + buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion), + buildJdk = jdkVersion.location + ) { + build("assembleDebug") + + val modifiedSrc = subProject("app").kotlinSourcesDir().resolve("com/example/getSomething.kt") + modifiedSrc.writeText( + //language=kt + """ + package com.example + + fun getSomething() = 10 + """.trimIndent() + ) + + build("assembleDebug", buildOptions = buildOptions.copy(logLevel = LogLevel.DEBUG)) { + val affectedKotlinFiles = listOf( + "app/src/main/kotlin/com/example/KotlinActivity1.kt", + "app/src/main/kotlin/com/example/getSomething.kt", + ) + val affectedJavaFiles = listOf( + "app/src/main/java/com/example/JavaActivity.java", + ) + assertCompiledKotlinSources(affectedKotlinFiles.toPaths(), output) + assertCompiledJavaSources(affectedJavaFiles.toPaths(), output) + } + } + } + + @DisplayName("incremental compilation works with multi-module android projects") + @GradleAndroidTest + fun testMultiModuleIC( + gradleVersion: GradleVersion, + agpVersion: String, + jdkVersion: JdkVersions.ProvidedJdk, + ) { + project( + "AndroidIncrementalMultiModule", + gradleVersion, + buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion), + buildJdk = jdkVersion.location + ) { + build("assembleDebug") + + val libAndroidUtilKt = subProject("libAndroid").kotlinSourcesDir().resolve("com/example/libAndroidUtil.kt") + libAndroidUtilKt.modify { it.replace("fun libAndroidUtil(): String", "fun libAndroidUtil(): CharSequence") } + + val optionsWithDebug = buildOptions.copy(logLevel = LogLevel.DEBUG) + + build("assembleDebug", buildOptions = optionsWithDebug) { + val affectedSources = listOf( + libAndroidUtilKt, + subProject("app").kotlinSourcesDir().resolve("com/example/useLibAndroidUtil.kt") + ).relativizeTo(projectPath) + assertCompiledKotlinSources(affectedSources, output) + } + + val libAndroidClassesOnlyUtilKt = subProject("libAndroidClassesOnly").kotlinSourcesDir().resolve("com/example/LibAndroidClassesOnlyUtil.kt") + libAndroidClassesOnlyUtilKt.modify { + it.replace( + "fun libAndroidClassesOnlyUtil(): String", + "fun libAndroidClassesOnlyUtil(): CharSequence" + ) + } + + build("assembleDebug", buildOptions = optionsWithDebug) { + val affectedSources = listOf( + libAndroidClassesOnlyUtilKt, + subProject("app").kotlinSourcesDir().resolve("com/example/useLibAndroidClassesOnlyUtil.kt") + ).relativizeTo(projectPath) + assertCompiledKotlinSources(affectedSources, output) + } + + val libJvmUtilKt = subProject("libJvmClassesOnly").kotlinSourcesDir().resolve("com/example/LibJvmUtil.kt") + libJvmUtilKt.modify { it.replace("fun libJvmUtil(): String", "fun libJvmUtil(): CharSequence") } + + build("assembleDebug", buildOptions = optionsWithDebug) { + val affectedSources = listOf( + libJvmUtilKt, + subProject("app").kotlinSourcesDir().resolve("com/example/useLibJvmUtil.kt") + ).relativizeTo(projectPath) + assertCompiledKotlinSources(affectedSources, output) + } + } + } + + @DisplayName("incremental compilation does nothing without changes") + @GradleAndroidTest + fun testIncrementalBuildWithNoChanges( + gradleVersion: GradleVersion, + agpVersion: String, + jdkVersion: JdkVersions.ProvidedJdk, + ) { + project( + "AndroidIncrementalSingleModuleProject", + gradleVersion, + buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion), + buildJdk = jdkVersion.location + ) { + val expectedTasks = listOf( + ":app:compileDebugKotlin", + ":app:compileDebugJavaWithJavac" + ) + + build("assembleDebug") { + assertTasksExecuted(expectedTasks) + } + + build("assembleDebug") { + assertTasksUpToDate(expectedTasks) + } + } + } + + @DisplayName("KT-49066: incremental compilation with custom module name") + @GradleAndroidTest + fun testCustomModuleName( + gradleVersion: GradleVersion, + agpVersion: String, + jdkVersion: JdkVersions.ProvidedJdk, + ) { + project( + "AndroidIncrementalMultiModule", + gradleVersion, + buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion), + buildJdk = jdkVersion.location + ) { + subProject("libAndroid").buildGradle.appendText( + //language=Gradle + """ + + android.kotlinOptions { + moduleName = "custom_path" + } + """.trimIndent() + ) + + build("assembleDebug") { + assertFileInProjectExists("libAndroid/build/tmp/kotlin-classes/debug/META-INF/custom_path.kotlin_module") + } + + val libAndroidUtilKt = subProject("libAndroid").kotlinSourcesDir().resolve("com/example/libAndroidUtil.kt") + libAndroidUtilKt.modify { it.replace("fun libAndroidUtil(): String", "fun libAndroidUtil(): CharSequence") } + build("assembleDebug", buildOptions = buildOptions.copy(logLevel = LogLevel.DEBUG)) { + val affectedSources = listOf( + libAndroidUtilKt, + subProject("app").kotlinSourcesDir().resolve("com/example/useLibAndroidUtil.kt") + ).relativizeTo(projectPath) + assertCompiledKotlinSources(affectedSources, output) + } + } + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/android/KotlinAndroidMppIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/android/KotlinAndroidMppIT.kt new file mode 100644 index 00000000000..490515b39f9 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/android/KotlinAndroidMppIT.kt @@ -0,0 +1,706 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.android + +import org.gradle.util.GradleVersion +import org.jetbrains.kotlin.gradle.testbase.* +import org.jetbrains.kotlin.gradle.tooling.BuildKotlinToolingMetadataTask +import org.jetbrains.kotlin.gradle.util.AGPVersion +import org.jetbrains.kotlin.gradle.util.testResolveAllConfigurations +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.DisplayName +import java.io.File +import java.nio.file.Files +import java.util.zip.ZipFile +import kotlin.io.path.appendText +import kotlin.io.path.extension +import kotlin.io.path.readText +import kotlin.io.path.writeText +import kotlin.streams.toList +import kotlin.test.* + +@DisplayName("kotlin-android with mpp") +@AndroidGradlePluginTests +class KotlinAndroidMppIT : KGPBaseTest() { + @DisplayName("KT-50736: whenEvaluated waits for AGP being applied later") + @GradleAndroidTest + fun testAfterEvaluateOrdering( + gradleVersion: GradleVersion, + agpVersion: String, + jdkVersion: JdkVersions.ProvidedJdk, + ) { + project( + "AndroidProject", + gradleVersion, + buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion), + buildJdk = jdkVersion.location + ) { + subProject("Lib").buildGradle.writeText( + //language=Gradle + """ + buildscript { + repositories { + mavenLocal() + google() + gradlePluginPortal() + } + dependencies { + classpath "com.android.tools.build:gradle:${'$'}android_tools_version" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${'$'}kotlin_version" + } + } + + plugins { + id 'org.jetbrains.kotlin.multiplatform' + } + + class MyAction implements kotlin.jvm.functions.Function1 { + Void invoke (Project p) { + println("compilations: " + p.kotlin.targets.getByName("android").compilations.names) + } + } + + org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginKt.whenEvaluated(project, new MyAction ()) + + apply plugin : "android-library" + + android { + compileSdkVersion 22 + } + + kotlin { android("android") { } } + """.trimIndent() + ) + + build("help") { + val reportedCompilations = output.lines() + .single { it.contains("compilations: ") } + .substringAfter("compilations: ") + .removeSurrounding("[", "]") + .split(", ") + .toSet() + assertEquals( + setOf("debug", "debugAndroidTest", "debugUnitTest", "release", "releaseUnitTest"), + reportedCompilations + ) + } + } + } + + @DisplayName("KotlinToolingMetadataArtifact is bundled into apk") + @GradleAndroidTest + fun testKotlinToolingMetadataBundle( + gradleVersion: GradleVersion, + agpVersion: String, + jdkVersion: JdkVersions.ProvidedJdk, + ) { + project( + "kotlinToolingMetadataAndroid", + gradleVersion, + buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion), + buildJdk = jdkVersion.location + ) { + build("assembleDebug") { + assertTasksAreNotInTaskGraph(":${BuildKotlinToolingMetadataTask.defaultTaskName}") + + val debugApk = projectPath.resolve("build/outputs/apk/debug/project-debug.apk") + assertFileExists(debugApk) + ZipFile(debugApk.toFile()).use { zip -> + assertNull(zip.getEntry("kotlin-tooling-metadata.json"), "Expected metadata *not* being packaged into debug apk") + } + } + + build("assembleRelease") { + assertTasksExecuted(":${BuildKotlinToolingMetadataTask.defaultTaskName}") + val releaseApk = projectPath.resolve("build/outputs/apk/release/project-release-unsigned.apk") + + assertFileExists(releaseApk) + ZipFile(releaseApk.toFile()).use { zip -> + assertNotNull(zip.getEntry("kotlin-tooling-metadata.json"), "Expected metadata being packaged into release apk") + } + } + } + } + + @DisplayName("mpp source sets are registered in AGP") + @GradleAndroidTest + @AndroidTestVersions(minVersion = TestVersions.AGP.AGP_42) + fun testAndroidMppSourceSets( + gradleVersion: GradleVersion, + agpVersion: String, + jdkVersion: JdkVersions.ProvidedJdk, + ) { + project( + "new-mpp-android-source-sets", + gradleVersion, + buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion), + buildJdk = jdkVersion.location + ) { + // AbstractReportTask#generate() task action was removed in Gradle 6.8+, + // that SourceSetTask is using: https://github.com/gradle/gradle/commit/4dac91ab87ea33ee8689d2a62b691b119198e7c7 + // leading to the issue that ":sourceSets" task is always in 'UP-TO-DATE' state. + // Skipping this check until the test will start using AGP 7.0-alpha03+ + // AGP 4.x is not compatible with Gradle 7.0, so just skip when the Gradle version is lower than 7.0 + if (gradleVersion >= GradleVersion.version("7.0")) { + build("sourceSets") { + fun assertOutputContainsOsIndependent(expectedString: String) { + assertOutputContains(expectedString.replace("/", File.separator)) + } + assertOutputContainsOsIndependent("Android resources: [lib/src/main/res, lib/src/androidMain/res]") + assertOutputContainsOsIndependent("Assets: [lib/src/main/assets, lib/src/androidMain/assets]") + assertOutputContainsOsIndependent("AIDL sources: [lib/src/main/aidl, lib/src/androidMain/aidl]") + assertOutputContainsOsIndependent("RenderScript sources: [lib/src/main/rs, lib/src/androidMain/rs]") + assertOutputContainsOsIndependent("JNI sources: [lib/src/main/jni, lib/src/androidMain/jni]") + assertOutputContainsOsIndependent("JNI libraries: [lib/src/main/jniLibs, lib/src/androidMain/jniLibs]") + assertOutputContainsOsIndependent("Java-style resources: [lib/src/main/resources, lib/src/androidMain/resources]") + + assertOutputContainsOsIndependent("Android resources: [lib/src/androidTestDebug/res, lib/src/androidAndroidTestDebug/res]") + assertOutputContainsOsIndependent("Assets: [lib/src/androidTestDebug/assets, lib/src/androidAndroidTestDebug/assets]") + assertOutputContainsOsIndependent("AIDL sources: [lib/src/androidTestDebug/aidl, lib/src/androidAndroidTestDebug/aidl]") + assertOutputContainsOsIndependent("RenderScript sources: [lib/src/androidTestDebug/rs, lib/src/androidAndroidTestDebug/rs]") + assertOutputContainsOsIndependent("JNI sources: [lib/src/androidTestDebug/jni, lib/src/androidAndroidTestDebug/jni]") + assertOutputContainsOsIndependent("JNI libraries: [lib/src/androidTestDebug/jniLibs, lib/src/androidAndroidTestDebug/jniLibs]") + assertOutputContainsOsIndependent("Java-style resources: [lib/src/androidTestDebug/resources, lib/src/androidAndroidTestDebug/resources]") + + assertOutputContainsOsIndependent("Java-style resources: [lib/betaSrc/paidBeta/resources, lib/src/androidPaidBeta/resources]") + assertOutputContainsOsIndependent("Java-style resources: [lib/betaSrc/paidBetaDebug/resources, lib/src/androidPaidBetaDebug/resources]") + assertOutputContainsOsIndependent("Java-style resources: [lib/betaSrc/paidBetaRelease/resources, lib/src/androidPaidBetaRelease/resources]") + + assertOutputContainsOsIndependent("Java-style resources: [lib/betaSrc/freeBeta/resources, lib/src/androidFreeBeta/resources]") + assertOutputContainsOsIndependent("Java-style resources: [lib/betaSrc/freeBetaDebug/resources, lib/src/androidFreeBetaDebug/resources]") + assertOutputContainsOsIndependent("Java-style resources: [lib/betaSrc/freeBetaRelease/resources, lib/src/androidFreeBetaRelease/resources]") + } + } + + buildAndFail("testFreeBetaDebug") { + assertOutputContains("CommonTest > fail FAILED") + assertOutputContains("TestKotlin > fail FAILED") + assertOutputContains("AndroidTestKotlin > fail FAILED") + assertOutputContains("TestJava > fail FAILED") + } + + build("assemble") + + buildAndFail("connectedAndroidTest") { + assertOutputContains("No connected devices!") + } + } + } + + @DisplayName("android app can depend on mpp lib") + @GradleAndroidTest + fun testAndroidWithNewMppApp( + gradleVersion: GradleVersion, + agpVersion: String, + jdkVersion: JdkVersions.ProvidedJdk, + ) { + project( + "new-mpp-android", + gradleVersion, + buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion), + buildJdk = jdkVersion.location + ) { + build("assemble", "compileDebugUnitTestJavaWithJavac", "printCompilerPluginOptions") { + // KT-30784 + assertOutputDoesNotContain("API 'variant.getPackageLibrary()' is obsolete and has been replaced") + + assertOutputContains("KT-29964 OK") // Output from lib/build.gradle + + assertTasksExecuted( + ":lib:compileDebugKotlinAndroidLib", + ":lib:compileReleaseKotlinAndroidLib", + ":lib:compileKotlinJvmLib", + ":lib:compileKotlinJsLib", + ":lib:compileCommonMainKotlinMetadata", + ":app:compileDebugKotlinAndroidApp", + ":app:compileReleaseKotlinAndroidApp", + ":app:compileKotlinJvmApp", + ":app:compileKotlinJsApp", + ":app:compileCommonMainKotlinMetadata", + ":lib:compileDebugUnitTestJavaWithJavac", + ":app:compileDebugUnitTestJavaWithJavac" + ) + + listOf("debug", "release").forEach { variant -> + assertFileInProjectExists("lib/build/tmp/kotlin-classes/$variant/com/example/lib/ExpectedLibClass.class") + assertFileInProjectExists("lib/build/tmp/kotlin-classes/$variant/com/example/lib/CommonLibClass.class") + assertFileInProjectExists("lib/build/tmp/kotlin-classes/$variant/com/example/lib/AndroidLibClass.class") + + assertFileInProjectExists("app/build/tmp/kotlin-classes/$variant/com/example/app/AKt.class") + assertFileInProjectExists("app/build/tmp/kotlin-classes/$variant/com/example/app/KtUsageKt.class") + } + } + + val groupDir = subProject("lib").projectPath.resolve("build/repo/com/example") + build("publish") { + assertDirectoryExists(groupDir.resolve("lib-jvmlib")) + assertDirectoryExists(groupDir.resolve("lib-jslib")) + assertDirectoryExists(groupDir.resolve("lib-androidlib")) + assertDirectoryExists(groupDir.resolve("lib-androidlib-debug")) + } + groupDir.deleteRecursively() + + // Choose a single variant to publish, check that it's there: + subProject("lib").buildGradle.appendText("\nkotlin.android('androidLib').publishLibraryVariants = ['release']") + build("publish") { + assertFileExists(groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0.aar")) + assertFileExists(groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0-sources.jar")) + assertFileInProjectNotExists("$groupDir/lib-androidlib-debug") + } + groupDir.deleteRecursively() + + // Enable publishing for all Android variants: + subProject("lib").buildGradle.appendText("\nkotlin.android('androidLib') { publishAllLibraryVariants() }") + build("publish") { + assertFileExists(groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0.aar")) + assertFileExists(groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0-sources.jar")) + assertFileExists(groupDir.resolve("lib-androidlib-debug/1.0/lib-androidlib-debug-1.0.aar")) + assertFileExists(groupDir.resolve("lib-androidlib-debug/1.0/lib-androidlib-debug-1.0-sources.jar")) + } + groupDir.deleteRecursively() + + // Then group the variants by flavor and check that only one publication is created: + subProject("lib").buildGradle.appendText("\nkotlin.android('androidLib').publishLibraryVariantsGroupedByFlavor = true") + build("publish") { + assertFileExists(groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0.aar")) + assertFileExists(groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0-sources.jar")) + assertFileExists(groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0-debug.aar")) + assertFileExists(groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0-debug-sources.jar")) + } + groupDir.deleteRecursively() + + // Add one flavor dimension with two flavors, check that the flavors produce grouped publications: + subProject("lib").buildGradle.appendText( + "\nandroid { flavorDimensions('foo'); productFlavors { fooBar { dimension 'foo' }; fooBaz { dimension 'foo' } } }" + ) + build("publish") { + listOf("foobar", "foobaz").forEach { flavor -> + assertFileExists(groupDir.resolve("lib-androidlib-$flavor/1.0/lib-androidlib-$flavor-1.0.aar")) + assertFileExists(groupDir.resolve("lib-androidlib-$flavor/1.0/lib-androidlib-$flavor-1.0-sources.jar")) + assertFileExists(groupDir.resolve("lib-androidlib-$flavor/1.0/lib-androidlib-$flavor-1.0-debug.aar")) + assertFileExists(groupDir.resolve("lib-androidlib-$flavor/1.0/lib-androidlib-$flavor-1.0-debug-sources.jar")) + } + } + groupDir.deleteRecursively() + + // Disable the grouping and check that all the variants are published under separate artifactIds: + subProject("lib").buildGradle.appendText( + "\nkotlin.android('androidLib') { publishLibraryVariantsGroupedByFlavor = false }" + ) + build("publish") { + listOf("foobar", "foobaz").forEach { flavor -> + listOf("-debug", "").forEach { buildType -> + assertFileExists(groupDir.resolve("lib-androidlib-$flavor$buildType/1.0/lib-androidlib-$flavor$buildType-1.0.aar")) + assertFileExists(groupDir.resolve("lib-androidlib-$flavor$buildType/1.0/lib-androidlib-$flavor$buildType-1.0-sources.jar")) + } + } + } + groupDir.deleteRecursively() + + // Convert the 'app' project to a library, publish the two without metadata, + // check that the dependencies in the POMs are correctly rewritten: + val appGroupDir = subProject("app").projectPath.resolve("build/repo/com/example") + + subProject("app").buildGradle.modify { + it.replace("com.android.application", "com.android.library") + .replace("applicationId", "//") + + //language=Gradle + """ + + apply plugin: 'maven-publish' + publishing { repositories { maven { url = uri("${'$'}buildDir/repo") } } } + kotlin.android('androidApp') { publishAllLibraryVariants() } + android { flavorDimensions('foo'); productFlavors { fooBar { dimension 'foo' }; fooBaz { dimension 'foo' } } } + """.trimIndent() + } + build("publish") { + listOf("foobar", "foobaz").forEach { flavor -> + listOf("-debug", "").forEach { buildType -> + assertFileExists(appGroupDir.resolve("app-androidapp-$flavor$buildType/1.0/app-androidapp-$flavor$buildType-1.0.aar")) + assertFileExists(appGroupDir.resolve("app-androidapp-$flavor$buildType/1.0/app-androidapp-$flavor$buildType-1.0-sources.jar")) + val pomText = + appGroupDir.resolve("app-androidapp-$flavor$buildType/1.0/app-androidapp-$flavor$buildType-1.0.pom").readText() + .replace("\\s+".toRegex(), "") + assertContains( + pomText, + "lib-androidlib-$flavor$buildType1.0runtime" + ) + } + } + } + appGroupDir.deleteRecursively() + + // Also check that api and runtimeOnly MPP dependencies get correctly published with the appropriate scope, KT-29476: + subProject("app").buildGradle.modify { + it.replace("implementation project(':lib')", "api project(':lib')") + + //language=Gradle + """ + + kotlin.sourceSets.commonMain.dependencies { + runtimeOnly(kotlin('reflect')) + } + """.trimIndent() + } + build("publish") { + listOf("foobar", "foobaz").forEach { flavor -> + listOf("-debug", "").forEach { buildType -> + val pomText = + appGroupDir.resolve("app-androidapp-$flavor$buildType/1.0/app-androidapp-$flavor$buildType-1.0.pom").readText() + .replace("\\s+".toRegex(), "") + assertContains( + pomText, + "lib-androidlib-$flavor$buildType1.0compile" + ) + assertContains( + pomText, + "kotlin-reflect${buildOptions.kotlinVersion}runtime" + ) + } + } + } + } + } + + @DisplayName("KT-29343: mpp source set dependencies are propagated to android tests") + @GradleAndroidTest + fun testAndroidMppProductionDependenciesInTests( + gradleVersion: GradleVersion, + agpVersion: String, + jdkVersion: JdkVersions.ProvidedJdk, + ) { + project( + "new-mpp-android", + gradleVersion, + buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion), + buildJdk = jdkVersion.location + ) { + // Test the fix for KT-29343 + subProject("lib").buildGradle.appendText( + //language=Gradle + """ + + kotlin.sourceSets { + commonMain { + dependencies { + implementation kotlin("stdlib-common") + } + } + androidLibDebug { + dependencies { + implementation kotlin("reflect") + } + } + androidLibRelease { + dependencies { + implementation kotlin("test-junit") + } + } + } + """.trimIndent() + ) + + val kotlinVersion = buildOptions.kotlinVersion + testResolveAllConfigurations("lib") { _, buildResult -> + // androidLibDebug: + buildResult.assertOutputContains(">> :lib:debugCompileClasspath --> kotlin-reflect-$kotlinVersion.jar") + buildResult.assertOutputDoesNotContain(">> :lib:releaseCompileClasspath --> kotlin-reflect-$kotlinVersion.jar") + buildResult.assertOutputContains(">> :lib:debugAndroidTestCompileClasspath --> kotlin-reflect-$kotlinVersion.jar") + buildResult.assertOutputContains(">> :lib:debugUnitTestCompileClasspath --> kotlin-reflect-$kotlinVersion.jar") + buildResult.assertOutputDoesNotContain(">> :lib:releaseUnitTestCompileClasspath --> kotlin-reflect-$kotlinVersion.jar") + + // androidLibRelease: + buildResult.assertOutputDoesNotContain(">> :lib:debugCompileClasspath --> kotlin-test-junit-$kotlinVersion.jar") + buildResult.assertOutputContains(">> :lib:releaseCompileClasspath --> kotlin-test-junit-$kotlinVersion.jar") + buildResult.assertOutputDoesNotContain(">> :lib:debugAndroidTestCompileClasspath --> kotlin-test-junit-$kotlinVersion.jar") + buildResult.assertOutputDoesNotContain(">> :lib:debugUnitTestCompileClasspath --> kotlin-test-junit-$kotlinVersion.jar") + buildResult.assertOutputContains(">> :lib:releaseUnitTestCompileClasspath --> kotlin-test-junit-$kotlinVersion.jar") + } + } + } + + @DisplayName("KT-27714: custom attributes are copied to android compilation configurations") + @GradleAndroidTest + fun testCustomAttributesInAndroidTargets( + gradleVersion: GradleVersion, + agpVersion: String, + jdkVersion: JdkVersions.ProvidedJdk, + ) { + project( + "new-mpp-android", + gradleVersion, + buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion), + buildJdk = jdkVersion.location + ) { + val libBuildScript = subProject("lib").buildGradle + val appBuildScript = subProject("app").buildGradle + + // Enable publishing for all Android variants: + libBuildScript.appendText( + //language=Gradle + """ + + kotlin.android('androidLib') { publishAllLibraryVariants() } + """.trimIndent() + ) + + val groupDir = subProject("lib").projectPath.resolve("build/repo/com/example") + + build("publish") { + // Also check that custom user-specified attributes are written in all Android modules metadata: + assertFileContains( + groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0.module"), + "\"com.example.target\": \"androidLib\"", + "\"com.example.compilation\": \"release\"" + ) + assertFileContains( + groupDir.resolve("lib-androidlib-debug/1.0/lib-androidlib-debug-1.0.module"), + "\"com.example.target\": \"androidLib\"", + "\"com.example.compilation\": \"debug\"" + ) + } + groupDir.deleteRecursively() + + // Check that the consumer side uses custom attributes specified in the target and compilations: + val appBuildScriptBackup = appBuildScript.readText() + + libBuildScript.appendText( + //language=Gradle + """ + + kotlin.targets.all { + attributes.attribute( + Attribute.of("com.example.target", String), + targetName + ) + } + """.trimIndent() + ) + appBuildScript.appendText( + //language=Gradle + """ + + kotlin.targets.androidApp.attributes.attribute( + Attribute.of("com.example.target", String), + "notAndroidLib" + ) + """.trimIndent() + ) + + buildAndFail(":app:compileDebugKotlinAndroidApp") { + // dependency resolution should fail + assertOutputContainsAny( + "Required com.example.target 'notAndroidLib'", + "attribute 'com.example.target' with value 'notAndroidLib'", + ) + } + + libBuildScript.writeText( + appBuildScriptBackup + + //language=Gradle + """ + + kotlin.targets.all { + compilations.all { + attributes.attribute( + Attribute.of("com.example.compilation", String), + targetName + compilationName.capitalize() + ) + } + } + """.trimIndent() + ) + appBuildScript.writeText( + appBuildScriptBackup + + //language=Gradle + """ + + kotlin.targets.androidApp.compilations.all { + attributes.attribute( + Attribute.of("com.example.compilation", String), + "notDebug" + ) + } + """.trimIndent() + ) + + buildAndFail(":app:compileDebugKotlinAndroidApp") { + assertOutputContainsAny( + "Required com.example.compilation 'notDebug'", + "attribute 'com.example.compilation' with value 'notDebug'", + ) + } + } + } + + @DisplayName("KT-27170: android lint works with dependency on non-android mpp project") + @GradleAndroidTest + @Disabled // not fully migrated yet + fun testLintInAndroidProjectsDependingOnMppWithoutAndroid( + gradleVersion: GradleVersion, + agpVersion: String, + jdkVersion: JdkVersions.ProvidedJdk, + ) { + project( + "AndroidProject", + gradleVersion, + buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion), + buildJdk = jdkVersion.location + ) { +// embedProject(Project("sample-lib", directoryPrefix = "new-mpp-lib-and-app")) // TODO + subProject("lib").buildGradle.appendText( + //language=Gradle + """ + + dependencies { implementation(project(':sample-lib')) } + """.trimIndent() + ) + val lintTask = ":Lib:lintFlavor1Debug" + build(lintTask) { + assertTasksExecuted(lintTask) // Check that the lint task ran successfully, KT-27170 + } + } + } + + @DisplayName("MPP allTests task depending on Android unit tests") + @GradleAndroidTest + fun testMppAllTests( + gradleVersion: GradleVersion, + agpVersion: String, + jdkVersion: JdkVersions.ProvidedJdk, + ) { + project( + "new-mpp-android", + gradleVersion, + buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion), + buildJdk = jdkVersion.location + ) { + build(":lib:allTests", "--dry-run") { + assertOutputContains(":lib:testDebugUnitTest SKIPPED") + assertOutputContains(":lib:testReleaseUnitTest SKIPPED") + } + } + } + + /** + * Starting from AGP version 7.1.0-alpha13, a new attribute com.android.build.api.attributes.AgpVersionAttr was added. + * This attribute is *not intended* to be published. + */ + @DisplayName("KT-49798: com.android.build.api.attributes.AgpVersionAttr is not published") + @GradleAndroidTest + @AndroidTestVersions(minVersion = TestVersions.AGP.AGP_71) + fun testKT49798AgpVersionAttrNotPublished( + gradleVersion: GradleVersion, + agpVersion: String, + jdkVersion: JdkVersions.ProvidedJdk, + ) { + project( + "new-mpp-android", + gradleVersion, + buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion), + buildJdk = jdkVersion.location + ) { + build("publish") { + val libProject = subProject("lib") + val debugPublicationDirectory = libProject.projectPath.resolve("build/repo/com/example/lib-androidlib-debug") + val releasePublicationDirectory = libProject.projectPath.resolve("build/repo/com/example/lib-androidlib") + + listOf(debugPublicationDirectory, releasePublicationDirectory).forEach { publicationDirectory -> + assertDirectoryExists(publicationDirectory) + val moduleFiles = Files.walk(publicationDirectory).use { it.filter { file -> file.extension == "module" }.toList() } + assertTrue(moduleFiles.isNotEmpty(), "Missing .module file in $publicationDirectory") + assertTrue(moduleFiles.size == 1, "Multiple .module files in $publicationDirectory: $moduleFiles") + + val moduleFile = moduleFiles.single() + val moduleFileText = moduleFile.readText() + assertTrue("AgpVersionAttr" !in moduleFileText, ".module file $moduleFile leaks AgpVersionAttr") + } + } + } + } + + @DisplayName("produced artifacts are consumable by projects with various AGP versions") + @GradleAndroidTest + @AndroidTestVersions(minVersion = TestVersions.AGP.AGP_71) + fun testAndroidMultiplatformPublicationAGPCompatibility( + gradleVersion: GradleVersion, + agpVersion: String, + jdkVersion: JdkVersions.ProvidedJdk, + ) { + project( + "new-mpp-android-agp-compatibility", + gradleVersion, + buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion), + buildJdk = jdkVersion.location + ) { + /* Publish a producer library with the current version of AGP */ + build(":producer:publishAllPublicationsToBuildDirRepository") { + /* Check expected publication layout */ + assertDirectoryInProjectExists("build/repo/com/example/producer-android") + assertDirectoryInProjectExists("build/repo/com/example/producer-android-debug") + assertDirectoryInProjectExists("build/repo/com/example/producer-jvm") + } + + val checkedConsumerAGPVersions = AGPVersion.testedVersions + // Special version added for testing KT-49798 + .plus(AGPVersion.fromString("7.1.0-beta02")) + .filter { version -> version >= AGPVersion.fromString(TestVersions.AGP.AGP_42) } + .filter { version -> version < AGPVersion.fromString(TestVersions.AGP.MAX_SUPPORTED) } + .map { it.toString() } + + checkedConsumerAGPVersions.forEach { consumerAgpVersion -> + println("Testing compatibility for AGP consumer version $consumerAgpVersion (Producer: $agpVersion)") + val buildOptions = buildOptions.copy(androidVersion = consumerAgpVersion) + + /* + Project: multiplatformAndroidConsumer is a mpp project with jvm and android targets. + This project depends on the previous publication as 'commonMainImplementation' dependency + */ + build(":multiplatformAndroidConsumer:assemble", buildOptions = buildOptions) + + /* + Project: plainAndroidConsumer only uses the 'kotlin("android")' plugin + This project depends on the previous publication as 'implementation' dependency + */ + build(":plainAndroidConsumer:assemble", buildOptions = buildOptions) + println("Successfully tested compatibility for AGP consumer version $consumerAgpVersion (Producer: $agpVersion)") + } + } + } + + @DisplayName("KT-49877, KT-35916: associate compilation dependencies are passed correctly to android test compilations") + @GradleAndroidTest + @AndroidTestVersions(minVersion = TestVersions.AGP.AGP_71) + fun testAssociateCompilationDependenciesArePassedToAndroidTestCompilations( + gradleVersion: GradleVersion, + agpVersion: String, + jdkVersion: JdkVersions.ProvidedJdk, + ) { + project( + "kt-49877", + gradleVersion, + buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion), + buildJdk = jdkVersion.location + ) { + build("allTests") { + assertTasksExecuted( + ":compileDebugKotlinAndroid", + ":compileReleaseKotlinAndroid", + ":compileDebugUnitTestKotlinAndroid", + ":compileReleaseUnitTestKotlinAndroid", + ":testDebugUnitTest", + ":testReleaseUnitTest", + ) + } + + // instrumented tests don't work without a device, so we only compile them + build("packageDebugAndroidTest") { + assertTasksExecuted( + ":compileDebugAndroidTestKotlinAndroid", + ) + } + } + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/compilationAssertions.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/compilationAssertions.kt index 1c92cbd4793..614fd061112 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/compilationAssertions.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/compilationAssertions.kt @@ -85,6 +85,20 @@ fun assertCompiledKotlinSources( assertSameFiles(expectedSources, actualSources, "Compiled Kotlin files differ${errorMessageSuffix}:\n") } +/** + * Asserts all the .java files from [expectedSources] and only they are compiled + * + * Note: log level of output should be set to [LogLevel.DEBUG] + */ +fun GradleProject.assertCompiledJavaSources( + expectedSources: Iterable, + output: String, + errorMessageSuffix: String = "" +) { + val actualSources = extractCompiledJavaFiles(projectPath.toRealPath().toFile(), output) + assertSameFiles(expectedSources, actualSources, "Compiled Java files differ${errorMessageSuffix}:\n") +} + /** * Asserts that compilation was non-incremental. * diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/outputAssertions.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/outputAssertions.kt index 4f98ef291d3..31049ce3098 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/outputAssertions.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/outputAssertions.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.gradle.testbase +import org.gradle.api.logging.LogLevel import org.gradle.testkit.runner.BuildResult /** @@ -19,6 +20,18 @@ fun BuildResult.assertOutputContains( } } +/** + * Asserts Gradle output contains any of [expectedSubString] strings. + */ +fun BuildResult.assertOutputContainsAny( + vararg expectedSubStrings: String +) { + assert(expectedSubStrings.any { output.contains(it) }) { + printBuildOutput() + "Build output does not contain any of \"$expectedSubStrings\"" + } +} + /** * Asserts Gradle output contains [expectedSubString] string exact times. */ diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/tasksAssertions.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/tasksAssertions.kt index 4fb390673d3..e3360b84543 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/tasksAssertions.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/tasksAssertions.kt @@ -8,6 +8,26 @@ package org.jetbrains.kotlin.gradle.testbase import org.gradle.testkit.runner.BuildResult import org.gradle.testkit.runner.TaskOutcome +/** + * Asserts given tasks are not present in the build task graph + */ +fun BuildResult.assertTasksAreNotInTaskGraph(vararg tasks: String) { + val presentTasks = tasks.filter { task(it) != null } + assert(presentTasks.isEmpty()) { + printBuildOutput() + "Tasks ${tasks.joinToString(prefix = "[", postfix = "]")} shouldn't be present in the task graph, but $presentTasks were present" + } +} + +/** + * Returns all the affected during the build tasks, whose [org.gradle.api.Task.getPath] satisfies the [pattern] + */ +fun BuildResult.findTasksByPattern(pattern: Regex): Set { + return tasks.map { it.path }.filter { taskPath -> + pattern.matches(taskPath) + }.toSet() +} + /** * Asserts given [tasks] have 'SUCCESS' execution state. */ @@ -51,6 +71,13 @@ fun BuildResult.assertTasksUpToDate(vararg tasks: String) { } } +/** + * Asserts given [tasks] have 'UP-TO-DATE' execution state. + */ +fun BuildResult.assertTasksUpToDate(tasks: Collection) { + assertTasksUpToDate(*tasks.toTypedArray()) +} + /** * Asserts given [tasks] have 'SKIPPED' execution state. */ diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/util/testResolveAllConfigurations.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/util/testResolveAllConfigurations.kt index 1a769272916..59f064129b3 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/util/testResolveAllConfigurations.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/util/testResolveAllConfigurations.kt @@ -53,12 +53,13 @@ fun TestProject.testResolveAllConfigurations( } ) { if (!skipSetup) { + val targetProject = subproject?.let { subProject(it) } ?: this when { - Files.exists(buildGradle) -> buildGradle + Files.exists(targetProject.buildGradle) -> targetProject.buildGradle .append("\n${generateResolveAllConfigurationsTask(excludeConfigurations)}") - Files.exists(buildGradleKts) -> buildGradleKts + Files.exists(targetProject.buildGradleKts) -> targetProject.buildGradleKts .append("\n${generateResolveAllConfigurationsTaskKts(excludeConfigurations)}") - else -> error("Build script does not exists under $projectPath") + else -> error("Build script does not exist under $projectPath") } } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/AndroidDaggerProject/app/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/AndroidDaggerProject/app/build.gradle index 10602cbfd53..2b7cb057b45 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/AndroidDaggerProject/app/build.gradle +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/AndroidDaggerProject/app/build.gradle @@ -49,5 +49,6 @@ dependencies { implementation 'com.google.dagger:dagger:2.9' kapt 'com.google.dagger:dagger-compiler:2.9' + kapt 'javax.annotation:javax.annotation-api:1.3.2' compileOnly 'org.glassfish:javax.annotation:10.0-b28' } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/internal/state/TaskLoggers.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/internal/state/TaskLoggers.kt index 01689b5f17e..fe528e1a0f6 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/internal/state/TaskLoggers.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/internal/state/TaskLoggers.kt @@ -9,7 +9,7 @@ import org.gradle.api.logging.Logger import java.lang.ref.WeakReference import java.util.HashMap -// todo: remove when https://github.com/gradle/gradle/issues/2678 is resolved +// todo: remove when https://github.com/gradle/gradle/issues/16991 is resolved internal object TaskLoggers { private val taskLoggers = HashMap>()