[Gradle] Remove KotlinOutputDependency

The IdeBinaryDependencyResolver will filter respective
files by using a simpler check: testing if the files
do reside within the build dir of the current project.

^KT-60937 Verification Pending
This commit is contained in:
Sebastian Sellmair
2023-08-04 15:51:35 +02:00
committed by Space Team
parent aa0f0c8cb4
commit d978640c42
8 changed files with 126 additions and 161 deletions
@@ -1,109 +0,0 @@
/*
* Copyright 2010-2023 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.plugin
import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.FileCollectionDependency
import org.gradle.api.artifacts.component.ComponentIdentifier
import org.gradle.api.file.FileCollection
import org.gradle.api.internal.artifacts.dependencies.SelfResolvingDependencyInternal
import org.gradle.api.tasks.TaskDependency
import org.jetbrains.kotlin.gradle.InternalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.targets.native.internal.CInteropIdentifier
import java.io.File
/**
* Identifier used for [KotlinOutputDependency].
* All use cases where Kotlin needs to add 'file collection based' dependencies shall be modelled
* using [KotlinOutputDependency] where this identifier is able to provide
* further information about 'what kind of dependency' this is used.
*
* This identifier basically contains
* 1) The information that this dependency was added by the Kotlin Gradle Plugin
* 2) The information that the files are locally built
* 3) The information about the semantics of the dependency
*/
internal sealed class KotlinOutputDependencyIdentifier : ComponentIdentifier {
/**
* Indicates that the dependency is representing the compilation under
* the specified coordinates and that the reason for the dependency is a 'compilation association'
*
* e.g. 'test compilations' are associated with 'main compilations'
*/
internal class AssociateCompilation(
val projectPath: String, val target: String, val compilation: String,
) : KotlinOutputDependencyIdentifier() {
constructor(compilation: KotlinCompilation<*>) : this(
projectPath = compilation.project.path,
target = compilation.target.targetName,
compilation = compilation.compilationName
)
}
/**
* Indicates that the dependency represents the output of the 'cinterop tool' for a single cinterop,
* identified by the specified [identifier]
*/
internal class CInterop(
val identifier: CInteropIdentifier,
) : KotlinOutputDependencyIdentifier()
override fun getDisplayName(): String = when (this) {
is AssociateCompilation -> "Associate Compilation: '$projectPath/$target/$compilation'"
is CInterop -> "CInterop ${identifier.uniqueName}"
}
override fun toString(): String = displayName
}
/**
* Special 'Dependency' implementation that shall be used to add 'file collection' based dependencies
* to Kotlin entities (such as compilations). This dependency implementation retains information about
* the semantics of the file collection underneath (by providing a [KotlinOutputDependencyIdentifier])
*/
internal class KotlinOutputDependency internal constructor(
private val identifier: KotlinOutputDependencyIdentifier,
private val files: FileCollection,
) : FileCollectionDependency, SelfResolvingDependencyInternal {
private var reason: String? = null
override fun getTargetComponentId(): ComponentIdentifier = identifier
override fun getGroup(): String? = null
override fun getName(): String = "unspecified"
override fun getVersion(): String? = null
override fun contentEquals(dependency: Dependency): Boolean {
if (dependency !is KotlinOutputDependency) return false
return files == dependency.files
}
override fun copy(): KotlinOutputDependency {
return KotlinOutputDependency(identifier, files)
}
override fun getReason(): String? = reason
override fun because(reason: String?) {
this.reason = reason
}
override fun getBuildDependencies(): TaskDependency {
return files.buildDependencies
}
override fun resolve(): MutableSet<File> {
return files.files
}
override fun resolve(transitive: Boolean): MutableSet<File> {
return files.files
}
override fun getFiles(): FileCollection {
return files
}
}
@@ -17,7 +17,6 @@ import org.jetbrains.kotlin.gradle.idea.tcs.*
import org.jetbrains.kotlin.gradle.idea.tcs.extras.artifactsClasspath import org.jetbrains.kotlin.gradle.idea.tcs.extras.artifactsClasspath
import org.jetbrains.kotlin.gradle.idea.tcs.extras.isOpaqueFileDependency import org.jetbrains.kotlin.gradle.idea.tcs.extras.isOpaqueFileDependency
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinOutputDependencyIdentifier
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.ide.* import org.jetbrains.kotlin.gradle.plugin.ide.*
@@ -172,11 +171,13 @@ class IdeBinaryDependencyResolver @JvmOverloads constructor(
} }
is OpaqueComponentArtifactIdentifier -> { is OpaqueComponentArtifactIdentifier -> {
/* Such dependencies *would* require implementing a resolver */ /* Files within the build directory still require a custom resolver */
if (artifact.file.absoluteFile.startsWith(sourceSet.project.buildDir.absoluteFile)) return@mapNotNull null
IdeaKotlinResolvedBinaryDependency( IdeaKotlinResolvedBinaryDependency(
binaryType = binaryType, coordinates = IdeaKotlinBinaryCoordinates( binaryType = binaryType, coordinates = IdeaKotlinBinaryCoordinates(
group = "<file>", group = "<file>",
module = componentId.file.relativeOrAbsolute(sourceSet.project.rootDir), module = artifact.file.relativeOrAbsolute(sourceSet.project.rootDir),
version = null, version = null,
sourceSetName = null sourceSetName = null
), ),
@@ -186,12 +187,6 @@ class IdeBinaryDependencyResolver @JvmOverloads constructor(
} }
} }
/*
Such dependencies (e.g. associate compilations, cinterops, ...)
Will have dedicated resolvers implemented
*/
is KotlinOutputDependencyIdentifier -> null
else -> { else -> {
logger.warn("Unhandled componentId: ${componentId.javaClass}") logger.warn("Unhandled componentId: ${componentId.javaClass}")
null null
@@ -19,6 +19,9 @@ internal constructor(compilation: KotlinCompilationImpl) : AbstractKotlinCompila
final override val runtimeDependencyConfigurationName: String final override val runtimeDependencyConfigurationName: String
get() = compilation.runtimeDependencyConfigurationName ?: error("$compilation: Missing 'runtimeDependencyConfigurationName'") get() = compilation.runtimeDependencyConfigurationName ?: error("$compilation: Missing 'runtimeDependencyConfigurationName'")
final override var runtimeDependencyFiles: FileCollection = final override var runtimeDependencyFiles: FileCollection
compilation.runtimeDependencyFiles ?: error("$compilation: Missing 'runtimeDependencyFiles'") get() = compilation.runtimeDependencyFiles ?: error("$compilation: Missing 'runtimeDependencyFiles'")
set(value) {
compilation.runtimeDependencyFiles = value
}
} }
@@ -21,6 +21,9 @@ internal interface InternalKotlinCompilation<out T : KotlinCommonOptions> : Kotl
override val kotlinSourceSets: ObservableSet<KotlinSourceSet> override val kotlinSourceSets: ObservableSet<KotlinSourceSet>
override val allKotlinSourceSets: ObservableSet<KotlinSourceSet> override val allKotlinSourceSets: ObservableSet<KotlinSourceSet>
val associatedCompilations: ObservableSet<KotlinCompilation<*>>
val allAssociatedCompilations: ObservableSet<KotlinCompilation<*>>
val configurations: KotlinCompilationConfigurationsContainer val configurations: KotlinCompilationConfigurationsContainer
val friendPaths: Iterable<FileCollection> val friendPaths: Iterable<FileCollection>
val processResourcesTaskName: String? val processResourcesTaskName: String?
@@ -7,16 +7,17 @@ package org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.artifacts.Dependency import org.gradle.api.artifacts.Dependency
import org.jetbrains.kotlin.gradle.plugin.KotlinOutputDependency import org.gradle.api.file.FileCollection
import org.jetbrains.kotlin.gradle.plugin.KotlinOutputDependencyIdentifier import org.gradle.api.tasks.SourceSet
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.jvm import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.jvm
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.InternalKotlinCompilation import org.jetbrains.kotlin.gradle.plugin.launch
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinWithJavaTarget import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.mpp.isMain
import org.jetbrains.kotlin.gradle.plugin.mpp.isTest
import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget
import org.jetbrains.kotlin.gradle.utils.Future
import org.jetbrains.kotlin.gradle.utils.filesProvider import org.jetbrains.kotlin.gradle.utils.filesProvider
import org.jetbrains.kotlin.gradle.utils.future
import org.jetbrains.kotlin.gradle.utils.listProperty
internal fun interface KotlinCompilationAssociator { internal fun interface KotlinCompilationAssociator {
@@ -28,36 +29,35 @@ internal object DefaultKotlinCompilationAssociator : KotlinCompilationAssociator
val project = target.project val project = target.project
/* /*
we add dependencies to compileDependencyConfiguration ('compileClasspath' usually) and runtimeDependency This associator has two jobs
('runtimeClasspath') instead of modifying respective api/implementation/compileOnly/runtimeOnly configs 1) It will add the output of the 'main' compilation as compile & runtime dependency to the 'auxiliary' compilation
2) It will add all 'declared dependencies' present on 'main' to 'auxiliary'
This is needed because api/implementation/compileOnly/runtimeOnly are used in IDE Import and will leak For 1)
to dependencies of IDE modules. But they are not needed here, because IDE resolution works inherently This is necessary so that all symbols that are declared/produced in 'main' are available in 'auxiliary'.
transitively and symbols from associated compilation will be resolved from source sets of associated We use the 'compileOnlyConfiguration' and 'runtimeOnlyConfigurationName' to add the respective classes.
compilation itself (moreover, direct dependencies are not equivalent to transitive ones because of Note (a): This 'associate' function will be called for 'all' associated compilations (full transitive closure)
resolution order - e.g. in case of FQNs clash, so it's even harmful) Note (b): It is important that the compiled output of 'main' is prioritised in the compile path order:
We therefore ensure that the files are added to the front of the compile path.
This is necessary as other binaries might leak into the compile path which contain the same symbols but
are not marked as 'friend'. We ensure that associate dependencies are resolved first
For 2)
This is an agreed upon convention: 'test' is able to see all dependencies declared for 'main'
As described in 1b: It needs to be taken care of, that the dependencies are ordered after the output of 'main'
*/ */
project.dependencies.add( project.dependencies.add(auxiliary.compileOnlyConfigurationName, main.output.classesDirs)
auxiliary.compileOnlyConfigurationName, project.dependencies.add(auxiliary.runtimeOnlyConfigurationName, main.output.allOutputs)
KotlinOutputDependency(KotlinOutputDependencyIdentifier.AssociateCompilation(main), main.output.classesDirs)
)
project.dependencies.add(
auxiliary.runtimeOnlyConfigurationName,
KotlinOutputDependency(KotlinOutputDependencyIdentifier.AssociateCompilation(main), main.output.allOutputs)
)
// Adding classes that could be produced into non-default destination for JVM target // Adding classes that could be produced into non-default destination for JVM target
// Check KotlinSourceSetProcessor for details // Check KotlinSourceSetProcessor for details
project.dependencies.add( project.dependencies.add(
auxiliary.implementationConfigurationName, auxiliary.implementationConfigurationName,
KotlinOutputDependency( project.filesProvider { main.defaultSourceSet.kotlin.classesDirectory.orNull?.asFile }
KotlinOutputDependencyIdentifier.AssociateCompilation(main),
project.filesProvider { main.defaultSourceSet.kotlin.classesDirectory.orNull?.asFile }
)
) )
// Adding declared dependencies
auxiliary.compileDependencyConfigurationName.addAllDependenciesFromOtherConfigurations( auxiliary.compileDependencyConfigurationName.addAllDependenciesFromOtherConfigurations(
project, project,
main.apiConfigurationName, main.apiConfigurationName,
@@ -76,7 +76,6 @@ internal object DefaultKotlinCompilationAssociator : KotlinCompilationAssociator
internal object KotlinNativeCompilationAssociator : KotlinCompilationAssociator { internal object KotlinNativeCompilationAssociator : KotlinCompilationAssociator {
override fun associate(target: KotlinTarget, auxiliary: InternalKotlinCompilation<*>, main: InternalKotlinCompilation<*>) { override fun associate(target: KotlinTarget, auxiliary: InternalKotlinCompilation<*>, main: InternalKotlinCompilation<*>) {
auxiliary.compileDependencyFiles += auxiliary.compileDependencyFiles +=
main.output.classesDirs + target.project.filesProvider { main.compileDependencyFiles } main.output.classesDirs + target.project.filesProvider { main.compileDependencyFiles }
@@ -22,12 +22,13 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.InternalKotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.internal import org.jetbrains.kotlin.gradle.plugin.mpp.internal
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
import org.jetbrains.kotlin.gradle.tasks.locateTask import org.jetbrains.kotlin.gradle.tasks.locateTask
import org.jetbrains.kotlin.gradle.utils.MutableObservableSetImpl
import org.jetbrains.kotlin.gradle.utils.ObservableSet import org.jetbrains.kotlin.gradle.utils.ObservableSet
import org.jetbrains.kotlin.tooling.core.MutableExtras import org.jetbrains.kotlin.tooling.core.MutableExtras
import org.jetbrains.kotlin.tooling.core.mutableExtrasOf import org.jetbrains.kotlin.tooling.core.mutableExtrasOf
internal class KotlinCompilationImpl constructor( internal class KotlinCompilationImpl constructor(
private val params: Params private val params: Params,
) : InternalKotlinCompilation<KotlinCommonOptions> { ) : InternalKotlinCompilation<KotlinCommonOptions> {
//region Params //region Params
@@ -44,7 +45,7 @@ internal class KotlinCompilationImpl constructor(
val kotlinOptions: KotlinCommonOptions, val kotlinOptions: KotlinCommonOptions,
val compilationAssociator: KotlinCompilationAssociator, val compilationAssociator: KotlinCompilationAssociator,
val compilationFriendPathsResolver: KotlinCompilationFriendPathsResolver, val compilationFriendPathsResolver: KotlinCompilationFriendPathsResolver,
val compilationSourceSetInclusion: KotlinCompilationSourceSetInclusion val compilationSourceSetInclusion: KotlinCompilationSourceSetInclusion,
) )
//endregion //endregion
@@ -182,24 +183,37 @@ internal class KotlinCompilationImpl constructor(
// endregion // endregion
private val associateWithImpl = mutableSetOf<KotlinCompilation<*>>()
private val associatedCompilationsImpl = MutableObservableSetImpl<KotlinCompilation<*>>()
private val allAssociatedCompilationsImpl = MutableObservableSetImpl<KotlinCompilation<*>>()
override val associatedCompilations: ObservableSet<KotlinCompilation<*>>
get() = associatedCompilationsImpl
override val allAssociatedCompilations: ObservableSet<KotlinCompilation<*>>
get() = allAssociatedCompilationsImpl
override val associateWith: List<KotlinCompilation<*>> override val associateWith: List<KotlinCompilation<*>>
get() = associateWithImpl.toList() get() = associatedCompilationsImpl.toList()
override fun associateWith(other: KotlinCompilation<*>) { override fun associateWith(other: KotlinCompilation<*>) {
require(other.target == target) { "Only associations between compilations of a single target are supported" } require(other.target == target) { "Only associations between compilations of a single target are supported" }
if (!associateWithImpl.add(other)) return if (!associatedCompilationsImpl.add(other)) return
params.compilationAssociator.associate(target, this, other.internal) if (!allAssociatedCompilationsImpl.add(other)) return
other.internal.allAssociatedCompilations.forAll { compilation -> allAssociatedCompilationsImpl.add(compilation) }
} }
//region final init //region final init
init { init {
sourceSets.allKotlinSourceSets.forAll { sourceSet -> sourceSets.allKotlinSourceSets.forAll { sourceSet ->
params.compilationSourceSetInclusion.include(this, sourceSet) params.compilationSourceSetInclusion.include(this, sourceSet)
} }
allAssociatedCompilations.forAll { compilation ->
params.compilationAssociator.associate(target, this, compilation.internal)
}
} }
//endregion //endregion
@@ -158,11 +158,7 @@ open class KotlinNativeTargetConfigurator<T : KotlinNativeTarget> : AbstractKotl
val interopOutput = project.files(interopTask.map { it.outputFileProvider }) val interopOutput = project.files(interopTask.map { it.outputFileProvider })
with(compilation) { with(compilation) {
// Register the interop library as a dependency of the compilation to make IDE happy. compileDependencyFiles += interopOutput
project.dependencies.add(
compileDependencyConfigurationName,
KotlinOutputDependency(KotlinOutputDependencyIdentifier.CInterop(interop.identifier), interopOutput)
)
if (isMain()) { if (isMain()) {
// Add interop library to special CInteropApiElements configuration // Add interop library to special CInteropApiElements configuration
createCInteropApiElementsKlibArtifact(compilation.target, interop, interopTask) createCInteropApiElementsKlibArtifact(compilation.target, interop, interopTask)
@@ -180,7 +176,7 @@ open class KotlinNativeTargetConfigurator<T : KotlinNativeTarget> : AbstractKotl
// IDE doesn't see this library in module dependencies. So we have to manually add // IDE doesn't see this library in module dependencies. So we have to manually add
// main interop libraries in dependencies of the default test compilation. // main interop libraries in dependencies of the default test compilation.
target.compilations.findByName(TEST_COMPILATION_NAME)?.let { testCompilation -> target.compilations.findByName(TEST_COMPILATION_NAME)?.let { testCompilation ->
project.dependencies.add(testCompilation.compileDependencyConfigurationName, interopOutput) testCompilation.compileDependencyFiles += interopOutput
testCompilation.cinterops.all { testCompilation.cinterops.all {
it.dependencyFiles += interopOutput it.dependencyFiles += interopOutput
} }
@@ -0,0 +1,64 @@
/*
* Copyright 2010-2023 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.
*/
@file:Suppress("FunctionName")
package org.jetbrains.kotlin.gradle.unitTests
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.mpp.internal
import org.jetbrains.kotlin.gradle.util.*
import kotlin.test.Test
import kotlin.test.assertEquals
class AssociateCompilationsTest {
@Test
fun `test - associatedCompilations and allAssociatedCompilations sets`() {
val project = buildProjectWithMPP()
val kotlin = project.multiplatformExtension
kotlin.jvm()
val main = kotlin.jvm().compilations.main
val test = kotlin.jvm().compilations.test
val bar = kotlin.jvm().compilations.create("bar")
val foo = kotlin.jvm().compilations.create("foo")
// Check: foo is not associated with anything
assertEquals(emptySet(), foo.internal.associatedCompilations.toSet())
assertEquals(emptySet(), foo.internal.allAssociatedCompilations.toSet())
// Check: foo associates with test (which is already associated with main by default)
foo.associateWith(test)
assertEquals(setOf(test), foo.internal.associatedCompilations.toSet())
assertEquals(setOf(main, test), foo.internal.allAssociatedCompilations.toSet())
// Check: associate main with bar (should be reflected in foo transitively)
main.associateWith(bar)
assertEquals(setOf(test), foo.internal.associatedCompilations.toSet())
assertEquals(setOf(main, test, bar), foo.internal.allAssociatedCompilations.toSet())
}
@Test
fun `test - associate compilation dependency files are first in compile path`() {
val project = buildProject { enableDefaultStdlibDependency(false) }
val kotlin = project.applyMultiplatformPlugin()
val jvm = kotlin.jvm()
val foo = jvm.compilations.create("foo")
foo.defaultSourceSet.dependencies {
implementation(project.files("bar.jar"))
}
foo.associateWith(jvm.compilations.main)
assertEquals(
listOf(jvm.compilations.main.output.classesDirs, project.files("bar.jar")).flatten(),
foo.compileDependencyFiles.files.toList(),
"Expected 'main classesDirs' to be listed before file dependency 'bar.jar'"
)
}
}