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
This commit is contained in:
Sergey Igushkin
2020-02-05 21:22:57 +03:00
parent 4eb04af01e
commit 9b861739cd
17 changed files with 206 additions and 10 deletions
@@ -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")
}
}
}
@@ -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()
}
}
@@ -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'
}
@@ -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() {}
}
@@ -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()
}
}
@@ -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"
}
@@ -0,0 +1,17 @@
buildscript {
repositories {
mavenLocal()
jcenter()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
mavenLocal()
jcenter()
}
}
@@ -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
}
@@ -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() {}
}
@@ -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()
}
}
@@ -0,0 +1,6 @@
apply plugin: 'org.jetbrains.kotlin.jvm'
dependencies {
api project(':lib1')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7"
}
@@ -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<T : KotlinCommonOptions>(
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
@@ -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