From 9b861739cdabc84fe8fcd8669935367911b9ba96 Mon Sep 17 00:00:00 2001 From: Sergey Igushkin Date: Wed, 5 Feb 2020 21:22:57 +0300 Subject: [PATCH] Include the production artifacts in friend paths (KT-35942) In some setups, the friendArtifacts that we had in addition to direct associate compilations' output classes were insufficient. For example: 1. When an Android module's tests depend on another module which transitively depends on the module containing tests: then the AGP API doesn't tell us that there are artifacts from the tested variant, but instead there's just the module's artifact. 2. Similar case but without Android, and if the classes are read from the module's artifact rather than the production output classes dir, then they are definitely not on the friend paths (which include the production classes dirs and not the the production artifact). In both cases, the friendArtifacts mechanism allows us to add those potential sources of the friend classes to the friend paths. Issue #KT-35942 Fixed --- .../kotlin/gradle/KotlinGradlePluginIT.kt | 23 ++++++++++++++++++ .../testProject/kt-35942-android/build.gradle | 20 ++++++++++++++++ .../kt-35942-android/lib1/build.gradle | 19 +++++++++++++++ .../lib1/src/main/AndroidManifest.xml | 1 + .../java/com/example/lib1/InternalClass.kt | 10 ++++++++ .../test/java/com/example/lib1/UnitTest.kt | 17 +++++++++++++ .../kt-35942-android/lib2/build.gradle | 17 +++++++++++++ .../lib2/src/main/AndroidManifest.xml | 1 + .../kt-35942-android/settings.gradle | 1 + .../testProject/kt-35942-jvm/build.gradle | 17 +++++++++++++ .../kt-35942-jvm/lib1/build.gradle | 12 ++++++++++ .../kotlin/com/example/lib1/InternalClass.kt | 10 ++++++++ .../test/kotlin/com/example/lib1/UnitTest.kt | 17 +++++++++++++ .../kt-35942-jvm/lib2/build.gradle | 6 +++++ .../testProject/kt-35942-jvm/settings.gradle | 1 + .../gradle/plugin/mpp/kotlinCompilations.kt | 20 ++++++++++++---- .../android/Android25ProjectHandler.kt | 24 ++++++++++++++----- 17 files changed, 206 insertions(+), 10 deletions(-) create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-android/build.gradle create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-android/lib1/build.gradle create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-android/lib1/src/main/AndroidManifest.xml create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-android/lib1/src/main/java/com/example/lib1/InternalClass.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-android/lib1/src/test/java/com/example/lib1/UnitTest.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-android/lib2/build.gradle create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-android/lib2/src/main/AndroidManifest.xml create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-android/settings.gradle create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-jvm/build.gradle create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-jvm/lib1/build.gradle create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-jvm/lib1/src/main/kotlin/com/example/lib1/InternalClass.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-jvm/lib1/src/test/kotlin/com/example/lib1/UnitTest.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-jvm/lib2/build.gradle create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-jvm/settings.gradle diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinGradlePluginIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinGradlePluginIT.kt index 7fa8d603a8a..ec7bdb2dc38 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinGradlePluginIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinGradlePluginIT.kt @@ -22,6 +22,7 @@ import org.jetbrains.kotlin.gradle.plugin.MULTIPLE_KOTLIN_PLUGINS_SPECIFIC_PROJE import org.jetbrains.kotlin.gradle.scripting.internal.ScriptingGradleSubplugin import org.jetbrains.kotlin.gradle.tasks.USING_JVM_INCREMENTAL_COMPILATION_MESSAGE import org.jetbrains.kotlin.gradle.util.* +import org.jetbrains.kotlin.test.KotlinTestUtils import org.junit.Test import java.io.File import java.util.zip.ZipFile @@ -1003,4 +1004,26 @@ class KotlinGradleIT : BaseGradleIT() { assertNotContains(ScriptingGradleSubplugin.MISCONFIGURATION_MESSAGE_SUFFIX) } } + + @Test + fun testKtKt35942InternalsFromMainInTestViaTransitiveDeps() = with(Project("kt-35942-jvm")) { + build(":lib1:compileTestKotlin") { + assertSuccessful() + assertTasksExecuted(":lib1:compileKotlin", ":lib2:jar") + } + } + + @Test + fun testKtKt35942InternalsFromMainInTestViaTransitiveDepsAndroid() = with(Project("kt-35942-android")) { + build( + ":lib1:compileDebugUnitTestKotlin", + options = defaultBuildOptions().copy( + androidGradlePluginVersion = AGPVersion.v3_2_0, + androidHome = KotlinTestUtils.findAndroidSdk(), + ), + ) { + assertSuccessful() + assertTasksExecuted(":lib1:compileDebugKotlin") + } + } } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-android/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-android/build.gradle new file mode 100644 index 00000000000..442b6108a9a --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-android/build.gradle @@ -0,0 +1,20 @@ +buildscript { + repositories { + mavenLocal() + google() + jcenter() + + } + dependencies { + classpath "com.android.tools.build:gradle:$android_tools_version" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + mavenLocal() + google() + jcenter() + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-android/lib1/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-android/lib1/build.gradle new file mode 100644 index 00000000000..62aefd33f7f --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-android/lib1/build.gradle @@ -0,0 +1,19 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.2" + + defaultConfig { + minSdkVersion 21 + targetSdkVersion 29 + } +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7" + + testImplementation project(':lib2') + testImplementation 'junit:junit:4.13' +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-android/lib1/src/main/AndroidManifest.xml b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-android/lib1/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..77fb5f4db23 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-android/lib1/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-android/lib1/src/main/java/com/example/lib1/InternalClass.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-android/lib1/src/main/java/com/example/lib1/InternalClass.kt new file mode 100644 index 00000000000..754389cb9c1 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-android/lib1/src/main/java/com/example/lib1/InternalClass.kt @@ -0,0 +1,10 @@ +/* + * Copyright 2010-2020 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 com.example.lib1 + +internal object InternalClass { + fun hello() {} +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-android/lib1/src/test/java/com/example/lib1/UnitTest.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-android/lib1/src/test/java/com/example/lib1/UnitTest.kt new file mode 100644 index 00000000000..ec6c3d18d96 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-android/lib1/src/test/java/com/example/lib1/UnitTest.kt @@ -0,0 +1,17 @@ +/* + * Copyright 2010-2020 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 com.example.lib1 + +import org.junit.Test + +import org.junit.Assert.* + +class UnitTest { + @Test + fun `call internal class`() { + InternalClass.hello() + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-android/lib2/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-android/lib2/build.gradle new file mode 100644 index 00000000000..61eb714045d --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-android/lib2/build.gradle @@ -0,0 +1,17 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.2" + + defaultConfig { + minSdkVersion 21 + targetSdkVersion 29 + } +} + +dependencies { + api project(':lib1') + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7" +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-android/lib2/src/main/AndroidManifest.xml b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-android/lib2/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..8aab7020fbc --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-android/lib2/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-android/settings.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-android/settings.gradle new file mode 100644 index 00000000000..4252cfc9cf3 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-android/settings.gradle @@ -0,0 +1 @@ +include ':lib1', ':lib2' \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-jvm/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-jvm/build.gradle new file mode 100644 index 00000000000..93992385ef6 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-jvm/build.gradle @@ -0,0 +1,17 @@ +buildscript { + repositories { + mavenLocal() + jcenter() + + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + mavenLocal() + jcenter() + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-jvm/lib1/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-jvm/lib1/build.gradle new file mode 100644 index 00000000000..e4ab3ad2761 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-jvm/lib1/build.gradle @@ -0,0 +1,12 @@ +apply plugin: 'org.jetbrains.kotlin.jvm' + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7" + + testImplementation project(':lib2') + testImplementation 'junit:junit:4.13' +} + +kotlin.target.compilations.test { + compileDependencyFiles = compileDependencyFiles - kotlin.target.compilations.main.output.classesDirs +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-jvm/lib1/src/main/kotlin/com/example/lib1/InternalClass.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-jvm/lib1/src/main/kotlin/com/example/lib1/InternalClass.kt new file mode 100644 index 00000000000..754389cb9c1 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-jvm/lib1/src/main/kotlin/com/example/lib1/InternalClass.kt @@ -0,0 +1,10 @@ +/* + * Copyright 2010-2020 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 com.example.lib1 + +internal object InternalClass { + fun hello() {} +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-jvm/lib1/src/test/kotlin/com/example/lib1/UnitTest.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-jvm/lib1/src/test/kotlin/com/example/lib1/UnitTest.kt new file mode 100644 index 00000000000..ec6c3d18d96 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-jvm/lib1/src/test/kotlin/com/example/lib1/UnitTest.kt @@ -0,0 +1,17 @@ +/* + * Copyright 2010-2020 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 com.example.lib1 + +import org.junit.Test + +import org.junit.Assert.* + +class UnitTest { + @Test + fun `call internal class`() { + InternalClass.hello() + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-jvm/lib2/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-jvm/lib2/build.gradle new file mode 100644 index 00000000000..94bd53ad35a --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-jvm/lib2/build.gradle @@ -0,0 +1,6 @@ +apply plugin: 'org.jetbrains.kotlin.jvm' + +dependencies { + api project(':lib1') + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7" +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-jvm/settings.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-jvm/settings.gradle new file mode 100644 index 00000000000..4252cfc9cf3 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-35942-jvm/settings.gradle @@ -0,0 +1 @@ +include ':lib1', ':lib2' \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinCompilations.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinCompilations.kt index 3f8609330ba..609c60cbd1d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinCompilations.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinCompilations.kt @@ -16,6 +16,7 @@ import org.gradle.api.plugins.ExtraPropertiesExtension import org.gradle.api.provider.Property import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.TaskState +import org.gradle.api.tasks.bundling.AbstractArchiveTask import org.gradle.util.ConfigureUtil import org.jetbrains.kotlin.gradle.dsl.* import org.jetbrains.kotlin.gradle.plugin.* @@ -26,6 +27,7 @@ import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile import org.jetbrains.kotlin.gradle.tasks.locateTask import org.jetbrains.kotlin.gradle.utils.addExtendsFromRelation import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName +import java.io.File import java.util.* import java.util.concurrent.Callable @@ -198,12 +200,22 @@ abstract class AbstractKotlinCompilation( override fun toString(): String = "compilation '$compilationName' ($target)" - /** If a compilation is aware of its associate compilations' outputs being added to the classpath in a transformed or packaged way, + /** + * If a compilation is aware of its associate compilations' outputs being added to the classpath in a transformed or packaged way, * it should point to those friend artifact files via this property. - * This is a workaround for Android variants that are compiled against - * JARs of each other, which is not exposed in the API in any other way than in the consumer's classpath. */ + */ internal open val friendArtifacts: FileCollection - get() = target.project.files() + get() = with(target.project) { + if (associateWithTransitiveClosure.any { it.name == KotlinCompilation.MAIN_COMPILATION_NAME }) { + // In case the main artifact is transitively added to the test classpath via a test dependency on another module + // that depends on this module's production part, include the main artifact in the friend artifacts, lazily: + files( + provider { + listOfNotNull(tasks.withType(AbstractArchiveTask::class.java).findByName(target.artifactsTaskName)?.archiveFile) + } + ) + } else files() + } override val moduleName: String get() = KotlinCompilationsModuleGroups.getModuleLeaderCompilation(this).takeIf { it != this }?.ownModuleName ?: ownModuleName diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/Android25ProjectHandler.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/Android25ProjectHandler.kt index b014b3d8381..a121f25c198 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/Android25ProjectHandler.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/Android25ProjectHandler.kt @@ -12,6 +12,7 @@ import com.android.build.gradle.api.* import com.android.build.gradle.tasks.MergeResources import com.android.builder.model.SourceProvider import org.gradle.api.Project +import org.gradle.api.artifacts.component.ProjectComponentIdentifier import org.gradle.api.file.FileCollection import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.bundling.AbstractArchiveTask @@ -57,12 +58,23 @@ class Android25ProjectHandler( kotlinClasspath + project.files(AndroidGradleWrapper.getRuntimeJars(androidPlugin, androidExt)) } - // Find the classpath entries that comes from the tested variant and register it as the friend path, lazily - compilation.testedVariantArtifacts.set(project.files(project.provider { - variantData.getCompileClasspathArtifacts(preJavaClasspathKey) - .filter { it.id.componentIdentifier is TestedComponentIdentifier } - .map { it.file } - })) + // Find the classpath entries that come from the tested variant and register them as the friend paths, lazily + compilation.testedVariantArtifacts.set( + project.files( + project.provider { + variantData.getCompileClasspathArtifacts(preJavaClasspathKey) + .filter { + it.id.componentIdentifier is TestedComponentIdentifier || + // If tests depend on the main classes transitively, through a test dependency on another module which + // depends on this module, then there's no artifact with a TestedComponentIdentifier, so consider the artifact of the + // current module a friend path, too: + getTestedVariantData(variantData) != null && + (it.id.componentIdentifier as? ProjectComponentIdentifier)?.projectPath == project.path + } + .map { it.file } + } + ) + ) kotlinTask.javaOutputDir = javaTask.destinationDir