Rework source sets metadata consumption
* Add `ResolvedMppVariantsProvider` that provides variant names,
platform artifacts, and metadata artifacts for MPP dependencies
* Rework SourceSetVisibilityProvider.kt so that it uses the
`ResolvedMppVariantsProvider` to determine which source sets should be
read from which metadata artifacts
* Rework `GranularMetadataTransformation` and its consumers so that:
* it uses the
visibility result and extracts the source set metadata from the
host-specific artifacts whenever `SourceSetsVisibilityProvider`
yields host-specific artifacts in the visibility results
* it uses the new Gradle API for resolutionResults / artifactView
* Rework module IDs in the code base, unify the logic of their
construction, also fix possible false-positive visibility in
`applyToConfiguration` that used a module ID that mismatched the
published module ID.
This commit is contained in:
+122
-127
@@ -7,6 +7,9 @@ package org.jetbrains.kotlin.gradle.plugin.mpp
|
||||
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.artifacts.*
|
||||
import org.gradle.api.artifacts.component.*
|
||||
import org.gradle.api.artifacts.result.ResolvedComponentResult
|
||||
import org.gradle.api.artifacts.result.ResolvedDependencyResult
|
||||
import org.gradle.api.file.FileCollection
|
||||
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
|
||||
@@ -20,8 +23,8 @@ import java.util.zip.ZipOutputStream
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
|
||||
internal sealed class MetadataDependencyResolution(
|
||||
val dependency: ResolvedDependency,
|
||||
val projectDependency: ProjectDependency?
|
||||
val dependency: ResolvedComponentResult,
|
||||
val projectDependency: Project?
|
||||
) {
|
||||
override fun toString(): String {
|
||||
val verb = when (this) {
|
||||
@@ -33,22 +36,22 @@ internal sealed class MetadataDependencyResolution(
|
||||
}
|
||||
|
||||
class KeepOriginalDependency(
|
||||
dependency: ResolvedDependency,
|
||||
projectDependency: ProjectDependency?
|
||||
dependency: ResolvedComponentResult,
|
||||
projectDependency: Project?
|
||||
) : MetadataDependencyResolution(dependency, projectDependency)
|
||||
|
||||
class ExcludeAsUnrequested(
|
||||
dependency: ResolvedDependency,
|
||||
projectDependency: ProjectDependency?
|
||||
dependency: ResolvedComponentResult,
|
||||
projectDependency: Project?
|
||||
) : MetadataDependencyResolution(dependency, projectDependency)
|
||||
|
||||
abstract class ChooseVisibleSourceSets(
|
||||
dependency: ResolvedDependency,
|
||||
projectDependency: ProjectDependency?,
|
||||
dependency: ResolvedComponentResult,
|
||||
projectDependency: Project?,
|
||||
val projectStructureMetadata: KotlinProjectStructureMetadata,
|
||||
val allVisibleSourceSetNames: Set<String>,
|
||||
val visibleSourceSetNamesExcludingDependsOn: Set<String>,
|
||||
val visibleTransitiveDependencies: Set<ResolvedDependency>
|
||||
val visibleTransitiveDependencies: Set<ResolvedDependencyResult>
|
||||
) : MetadataDependencyResolution(dependency, projectDependency) {
|
||||
/** Returns the mapping of source set names to files which should be used as the [dependency] parts representing the source sets.
|
||||
* If any temporary files need to be created, their paths are built from the [baseDir].
|
||||
@@ -64,64 +67,28 @@ internal sealed class MetadataDependencyResolution(
|
||||
}
|
||||
}
|
||||
|
||||
private typealias ModuleId = Pair<String?, String> // group ID, artifact ID
|
||||
|
||||
private val ResolvedDependency.moduleId: ModuleId
|
||||
get() = moduleGroup to moduleName
|
||||
|
||||
private val Dependency.moduleId: ModuleId
|
||||
get() = group to name
|
||||
|
||||
internal class GranularMetadataTransformation(
|
||||
val project: Project,
|
||||
val kotlinSourceSet: KotlinSourceSet,
|
||||
/** A list of scopes that the dependencies from [kotlinSourceSet] are treated as requested dependencies. */
|
||||
val sourceSetRequestedScopes: List<KotlinDependencyScope>,
|
||||
private val sourceSetRequestedScopes: List<KotlinDependencyScope>,
|
||||
/** A configuration that holds the dependencies of the appropriate scope for all Kotlin source sets in the project */
|
||||
val allSourceSetsConfiguration: Configuration,
|
||||
val parentTransformations: Lazy<Iterable<GranularMetadataTransformation>>
|
||||
private val allSourceSetsConfiguration: Configuration,
|
||||
private val parentTransformations: Lazy<Iterable<GranularMetadataTransformation>>
|
||||
) {
|
||||
val metadataDependencyResolutions: Iterable<MetadataDependencyResolution> by lazy { doTransform() }
|
||||
|
||||
// Keep parents of each dependency, too. We need a dependency's parent when it's an MPP's metadata module dependency:
|
||||
// in this case, the parent is the MPP's root module.
|
||||
private data class ResolvedDependencyWithParent(
|
||||
val dependency: ResolvedDependency,
|
||||
val parent: ResolvedDependency?
|
||||
val dependency: ResolvedComponentResult,
|
||||
val parent: ResolvedComponentResult?
|
||||
)
|
||||
|
||||
private fun collectProjectDependencies(
|
||||
requestedDependencies: Iterable<ProjectDependency>,
|
||||
resolvedDependencies: Iterable<ResolvedDependency>
|
||||
): Map<ModuleId, ProjectDependency> {
|
||||
val result = mutableMapOf<ModuleId, ProjectDependency>()
|
||||
|
||||
val resolvedDependenciesMap: Map<ModuleId, ResolvedDependency> = resolvedDependencies.associateBy { it.moduleId }
|
||||
|
||||
fun visitProjectDependency(projectDependency: ProjectDependency) {
|
||||
val moduleId = projectDependency.group to projectDependency.name
|
||||
|
||||
if (moduleId in result) return
|
||||
result[moduleId] = projectDependency
|
||||
|
||||
val resolvedDependency = resolvedDependenciesMap[moduleId] ?: return
|
||||
|
||||
projectDependency.dependencyProject.configurations.getByName(resolvedDependency.configuration)
|
||||
.allDependencies
|
||||
.withType(ProjectDependency::class.java)
|
||||
.forEach(::visitProjectDependency)
|
||||
}
|
||||
|
||||
requestedDependencies.forEach(::visitProjectDependency)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
private val requestedDependencies: Iterable<Dependency> by lazy {
|
||||
fun collectScopedDependenciesFromSourceSet(sourceSet: KotlinSourceSet): Set<Dependency> =
|
||||
sourceSetRequestedScopes.flatMapTo(mutableSetOf()) { scope ->
|
||||
project.sourceSetDependencyConfigurationByScope(sourceSet, scope).allDependencies
|
||||
project.sourceSetDependencyConfigurationByScope(sourceSet, scope).incoming.dependencies
|
||||
}
|
||||
|
||||
val ownDependencies = collectScopedDependenciesFromSourceSet(kotlinSourceSet)
|
||||
@@ -130,7 +97,7 @@ internal class GranularMetadataTransformation(
|
||||
ownDependencies + parentDependencies
|
||||
}
|
||||
|
||||
private val resolvedConfiguration: LenientConfiguration by lazy {
|
||||
private val configurationToResolve: Configuration by lazy {
|
||||
/** If [kotlinSourceSet] is not a published source set, its dependencies are not included in [allSourceSetsConfiguration].
|
||||
* In that case, to resolve the dependencies of the source set in a way that is consistent with the published source sets,
|
||||
* we need to create a new configuration with the dependencies from both [allSourceSetsConfiguration] and the
|
||||
@@ -147,70 +114,73 @@ internal class GranularMetadataTransformation(
|
||||
}
|
||||
}
|
||||
|
||||
(modifiedConfiguration ?: allSourceSetsConfiguration).resolvedConfiguration.lenientConfiguration
|
||||
modifiedConfiguration ?: allSourceSetsConfiguration
|
||||
}
|
||||
|
||||
private fun doTransform(): Iterable<MetadataDependencyResolution> {
|
||||
val result = mutableListOf<MetadataDependencyResolution>()
|
||||
|
||||
val parentResolutions =
|
||||
parentTransformations.value.flatMap { it.metadataDependencyResolutions }.groupBy { it.dependency.moduleId }
|
||||
parentTransformations.value.flatMap { it.metadataDependencyResolutions }.groupBy {
|
||||
ModuleIds.fromComponent(project, it.dependency)
|
||||
}
|
||||
|
||||
val allRequestedDependencies = requestedDependencies
|
||||
|
||||
val allModuleDependencies = resolvedConfiguration.allModuleDependencies
|
||||
|
||||
val knownProjectDependencies = collectProjectDependencies(
|
||||
allRequestedDependencies.filterIsInstance<ProjectDependency>(),
|
||||
allModuleDependencies
|
||||
)
|
||||
val resolutionResult = configurationToResolve.incoming.resolutionResult
|
||||
val allModuleDependencies =
|
||||
configurationToResolve.incoming.resolutionResult.allDependencies.filterIsInstance<ResolvedDependencyResult>()
|
||||
|
||||
val resolvedDependencyQueue: Queue<ResolvedDependencyWithParent> = ArrayDeque<ResolvedDependencyWithParent>().apply {
|
||||
val requestedModules: Set<ModuleId> = allRequestedDependencies.mapTo(mutableSetOf()) { it.moduleId }
|
||||
val requestedModules: Set<ModuleDependencyIdentifier> = allRequestedDependencies.mapTo(mutableSetOf()) {
|
||||
ModuleIds.fromDependency(it)
|
||||
}
|
||||
|
||||
addAll(
|
||||
resolvedConfiguration.firstLevelModuleDependencies
|
||||
.filter { it.moduleId in requestedModules }
|
||||
.map { ResolvedDependencyWithParent(it, null) }
|
||||
resolutionResult.root.dependencies
|
||||
.filter { ModuleIds.fromComponentSelector(project, it.requested) in requestedModules }
|
||||
.filterIsInstance<ResolvedDependencyResult>()
|
||||
.map { ResolvedDependencyWithParent(it.selected, null) }
|
||||
)
|
||||
}
|
||||
|
||||
val visitedDependencies = mutableSetOf<ResolvedDependency>()
|
||||
val visitedDependencies = mutableSetOf<ResolvedComponentResult>()
|
||||
|
||||
while (resolvedDependencyQueue.isNotEmpty()) {
|
||||
val (resolvedDependency, parent: ResolvedDependency?) = resolvedDependencyQueue.poll()
|
||||
val (resolvedDependency: ResolvedComponentResult, parent: ResolvedComponentResult?) = resolvedDependencyQueue.poll()
|
||||
|
||||
val projectDependency: ProjectDependency? = knownProjectDependencies[resolvedDependency.moduleId]
|
||||
val resolvedToProject: Project? = (resolvedDependency.id as? ProjectComponentIdentifier)?.projectPath?.let(project::project)
|
||||
|
||||
visitedDependencies.add(resolvedDependency)
|
||||
|
||||
val dependencyResult = processDependency(
|
||||
resolvedDependency,
|
||||
parentResolutions[resolvedDependency.moduleId].orEmpty(),
|
||||
parentResolutions[ModuleIds.fromComponent(project, resolvedDependency)].orEmpty(),
|
||||
parent,
|
||||
projectDependency
|
||||
resolvedToProject
|
||||
)
|
||||
|
||||
result.add(dependencyResult)
|
||||
|
||||
val transitiveDependenciesToVisit = when (dependencyResult) {
|
||||
is MetadataDependencyResolution.KeepOriginalDependency -> resolvedDependency.children
|
||||
is MetadataDependencyResolution.KeepOriginalDependency ->
|
||||
resolvedDependency.dependencies.filterIsInstance<ResolvedDependencyResult>()
|
||||
is MetadataDependencyResolution.ChooseVisibleSourceSets -> dependencyResult.visibleTransitiveDependencies
|
||||
is MetadataDependencyResolution.ExcludeAsUnrequested -> error("a visited dependency is erroneously considered unrequested")
|
||||
}
|
||||
|
||||
resolvedDependencyQueue.addAll(
|
||||
transitiveDependenciesToVisit.filter { it !in visitedDependencies }
|
||||
.map { ResolvedDependencyWithParent(it, resolvedDependency) }
|
||||
transitiveDependenciesToVisit.filter { it.selected !in visitedDependencies }
|
||||
.map { ResolvedDependencyWithParent(it.selected, resolvedDependency) }
|
||||
)
|
||||
}
|
||||
|
||||
allModuleDependencies.forEach { resolvedDependency ->
|
||||
if (resolvedDependency !in visitedDependencies) {
|
||||
if (resolvedDependency.selected !in visitedDependencies) {
|
||||
result.add(
|
||||
MetadataDependencyResolution.ExcludeAsUnrequested(
|
||||
resolvedDependency,
|
||||
knownProjectDependencies[resolvedDependency.moduleGroup to resolvedDependency.moduleName]
|
||||
resolvedDependency.selected,
|
||||
(resolvedDependency.selected.id as? ProjectComponentIdentifier)?.let { project.project(it.projectPath) }
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -234,30 +204,43 @@ internal class GranularMetadataTransformation(
|
||||
* source sets in *S*, then consider only these transitive dependencies, ignore the others;
|
||||
*/
|
||||
private fun processDependency(
|
||||
module: ResolvedDependency,
|
||||
module: ResolvedComponentResult,
|
||||
parentResolutionsForModule: Iterable<MetadataDependencyResolution>,
|
||||
parent: ResolvedDependency?,
|
||||
projectDependency: ProjectDependency?
|
||||
parent: ResolvedComponentResult?,
|
||||
resolvedToProject: Project?
|
||||
): MetadataDependencyResolution {
|
||||
val resolvedMppVariantsProvider = ResolvedMppVariantsProvider.get(project)
|
||||
|
||||
val mppDependencyMetadataExtractor = when {
|
||||
projectDependency != null -> ProjectMppDependencyMetadataExtractor(project, module, projectDependency.dependencyProject)
|
||||
parent != null -> JarArtifactMppDependencyMetadataExtractor(project, module)
|
||||
else -> null
|
||||
val mppDependencyMetadataExtractor = if (resolvedToProject != null) {
|
||||
ProjectMppDependencyMetadataExtractor(project, module, resolvedToProject)
|
||||
} else {
|
||||
val metadataArtifact = resolvedMppVariantsProvider.getPlatformArtifactByPlatformModule(
|
||||
ModuleIds.fromComponent(project, module),
|
||||
configurationToResolve
|
||||
)
|
||||
if (metadataArtifact != null) {
|
||||
JarArtifactMppDependencyMetadataExtractor(project, module, metadataArtifact)
|
||||
} else null
|
||||
}
|
||||
|
||||
val projectStructureMetadata = mppDependencyMetadataExtractor?.getProjectStructureMetadata()
|
||||
?: return MetadataDependencyResolution.KeepOriginalDependency(module, projectDependency)
|
||||
?: return MetadataDependencyResolution.KeepOriginalDependency(module, resolvedToProject)
|
||||
|
||||
val allVisibleSourceSets =
|
||||
SourceSetVisibilityProvider(project).getVisibleSourceSetNames(
|
||||
val sourceSetVisibility =
|
||||
SourceSetVisibilityProvider(project).getVisibleSourceSets(
|
||||
kotlinSourceSet,
|
||||
sourceSetRequestedScopes,
|
||||
parent ?: module,
|
||||
parent, module,
|
||||
projectStructureMetadata,
|
||||
projectDependency?.dependencyProject
|
||||
resolvedToProject
|
||||
)
|
||||
|
||||
if (mppDependencyMetadataExtractor is JarArtifactMppDependencyMetadataExtractor) {
|
||||
mppDependencyMetadataExtractor.metadataArtifactBySourceSet.putAll(sourceSetVisibility.hostSpecificMetadataArtifactBySourceSet)
|
||||
}
|
||||
|
||||
val allVisibleSourceSets = sourceSetVisibility.visibleSourceSetNames
|
||||
|
||||
val sourceSetsVisibleInParents = parentResolutionsForModule
|
||||
.filterIsInstance<MetadataDependencyResolution.ChooseVisibleSourceSets>()
|
||||
.flatMapTo(mutableSetOf()) { it.allVisibleSourceSetNames }
|
||||
@@ -265,36 +248,49 @@ internal class GranularMetadataTransformation(
|
||||
// Keep only the transitive dependencies requested by the visible source sets:
|
||||
// Visit the transitive dependencies visible by parents, too (i.e. allVisibleSourceSets), as this source set might get a more
|
||||
// concrete view on them:
|
||||
val requestedTransitiveDependencies: Set<ModuleId> =
|
||||
mutableSetOf<ModuleId>().apply {
|
||||
val requestedTransitiveDependencies: Set<ModuleDependencyIdentifier> =
|
||||
mutableSetOf<ModuleDependencyIdentifier>().apply {
|
||||
projectStructureMetadata.sourceSetModuleDependencies.forEach { (sourceSetName, moduleDependencies) ->
|
||||
if (sourceSetName in allVisibleSourceSets) {
|
||||
addAll(moduleDependencies.map { ModuleId(it.groupId, it.moduleId) })
|
||||
addAll(moduleDependencies.map { ModuleDependencyIdentifier(it.groupId, it.moduleId) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val transitiveDependenciesToVisit = module.children.filterTo(mutableSetOf()) {
|
||||
(it.moduleId) in requestedTransitiveDependencies
|
||||
}
|
||||
val transitiveDependenciesToVisit = module.dependencies
|
||||
.filterIsInstance<ResolvedDependencyResult>()
|
||||
.filterTo(mutableSetOf()) { ModuleIds.fromComponent(project, it.selected) in requestedTransitiveDependencies }
|
||||
|
||||
val visibleSourceSetsExcludingDependsOn = allVisibleSourceSets.filterTo(mutableSetOf()) { it !in sourceSetsVisibleInParents }
|
||||
|
||||
return object : MetadataDependencyResolution.ChooseVisibleSourceSets(
|
||||
module,
|
||||
projectDependency,
|
||||
projectStructureMetadata,
|
||||
allVisibleSourceSets,
|
||||
visibleSourceSetsExcludingDependsOn,
|
||||
transitiveDependenciesToVisit
|
||||
) {
|
||||
override fun getMetadataFilesBySourceSet(baseDir: File, doProcessFiles: Boolean): Map<String, FileCollection> =
|
||||
mppDependencyMetadataExtractor.getVisibleSourceSetsMetadata(visibleSourceSetsExcludingDependsOn, baseDir, doProcessFiles)
|
||||
}
|
||||
return ChooseVisibleSourceSetsImpl(
|
||||
module, resolvedToProject, projectStructureMetadata, allVisibleSourceSets, visibleSourceSetsExcludingDependsOn,
|
||||
transitiveDependenciesToVisit, mppDependencyMetadataExtractor
|
||||
)
|
||||
}
|
||||
|
||||
private class ChooseVisibleSourceSetsImpl(
|
||||
dependency: ResolvedComponentResult,
|
||||
projectDependency: Project?,
|
||||
projectStructureMetadata: KotlinProjectStructureMetadata,
|
||||
allVisibleSourceSetNames: Set<String>,
|
||||
visibleSourceSetNamesExcludingDependsOn: Set<String>,
|
||||
visibleTransitiveDependencies: Set<ResolvedDependencyResult>,
|
||||
private val metadataExtractor: MppDependencyMetadataExtractor
|
||||
) : MetadataDependencyResolution.ChooseVisibleSourceSets(
|
||||
dependency,
|
||||
projectDependency,
|
||||
projectStructureMetadata,
|
||||
allVisibleSourceSetNames,
|
||||
visibleSourceSetNamesExcludingDependsOn,
|
||||
visibleTransitiveDependencies
|
||||
) {
|
||||
override fun getMetadataFilesBySourceSet(baseDir: File, doProcessFiles: Boolean): Map<String, FileCollection> =
|
||||
metadataExtractor.getVisibleSourceSetsMetadata(visibleSourceSetNamesExcludingDependsOn, baseDir, doProcessFiles)
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class MppDependencyMetadataExtractor(val project: Project, val dependency: ResolvedDependency) {
|
||||
private abstract class MppDependencyMetadataExtractor(val project: Project, val dependency: ResolvedComponentResult) {
|
||||
abstract fun getProjectStructureMetadata(): KotlinProjectStructureMetadata?
|
||||
abstract fun getVisibleSourceSetsMetadata(
|
||||
visibleSourceSetNames: Set<String>,
|
||||
@@ -305,7 +301,7 @@ private abstract class MppDependencyMetadataExtractor(val project: Project, val
|
||||
|
||||
private class ProjectMppDependencyMetadataExtractor(
|
||||
project: Project,
|
||||
dependency: ResolvedDependency,
|
||||
dependency: ResolvedComponentResult,
|
||||
private val dependencyProject: Project
|
||||
) : MppDependencyMetadataExtractor(project, dependency) {
|
||||
override fun getProjectStructureMetadata(): KotlinProjectStructureMetadata? =
|
||||
@@ -323,16 +319,14 @@ private class ProjectMppDependencyMetadataExtractor(
|
||||
|
||||
private class JarArtifactMppDependencyMetadataExtractor(
|
||||
project: Project,
|
||||
dependency: ResolvedDependency
|
||||
dependency: ResolvedComponentResult,
|
||||
val primaryArtifact: File
|
||||
) : MppDependencyMetadataExtractor(project, dependency) {
|
||||
|
||||
private val artifact: ResolvedArtifact?
|
||||
get() = dependency.moduleArtifacts.singleOrNull { it.extension == "jar" }
|
||||
val metadataArtifactBySourceSet: MutableMap<String, File> = mutableMapOf()
|
||||
|
||||
override fun getProjectStructureMetadata(): KotlinProjectStructureMetadata? {
|
||||
val artifactFile = artifact?.file ?: return null
|
||||
|
||||
return ZipFile(artifactFile).use { zip ->
|
||||
return ZipFile(primaryArtifact).use { zip ->
|
||||
val metadata = zip.getEntry("META-INF/$MULTIPLATFORM_PROJECT_METADATA_FILE_NAME")
|
||||
?: return null
|
||||
|
||||
@@ -349,21 +343,24 @@ private class JarArtifactMppDependencyMetadataExtractor(
|
||||
baseDir: File,
|
||||
doProcessFiles: Boolean
|
||||
): Map<String, FileCollection> {
|
||||
val jarArtifact = artifact ?: return emptyMap()
|
||||
val artifactFile = jarArtifact.file
|
||||
val moduleId = jarArtifact.moduleVersion.id
|
||||
val primaryArtifact = primaryArtifact
|
||||
val moduleId = ModuleIds.fromComponent(project, dependency)
|
||||
|
||||
return extractSourceSetMetadataFromJar(moduleId, visibleSourceSetNames, artifactFile, baseDir, doProcessFiles)
|
||||
return extractSourceSetMetadataFromArtifacts(
|
||||
moduleId,
|
||||
baseDir,
|
||||
doProcessFiles,
|
||||
visibleSourceSetNames.associate { it to (metadataArtifactBySourceSet[it] ?: primaryArtifact) }
|
||||
)
|
||||
}
|
||||
|
||||
private fun extractSourceSetMetadataFromJar(
|
||||
module: ModuleVersionIdentifier,
|
||||
chooseSourceSetsByNames: Set<String>,
|
||||
artifactJar: File,
|
||||
private fun extractSourceSetMetadataFromArtifacts(
|
||||
module: ModuleDependencyIdentifier,
|
||||
baseDir: File,
|
||||
doProcessFiles: Boolean
|
||||
doProcessFiles: Boolean,
|
||||
artifactBySourceSet: Map<String, File>
|
||||
): Map<String, FileCollection> {
|
||||
val moduleString = "${module.group}-${module.name}-${module.version}"
|
||||
val moduleString = "${module.groupId}-${module.moduleId}"
|
||||
val transformedModuleRoot = run { baseDir.resolve(moduleString).also { it.mkdirs() } }
|
||||
|
||||
val resultFiles = mutableMapOf<String, FileCollection>()
|
||||
@@ -372,12 +369,10 @@ private class JarArtifactMppDependencyMetadataExtractor(
|
||||
"can't extract metadata from a module without project structure metadata"
|
||||
}
|
||||
|
||||
ZipFile(artifactJar).use { zip ->
|
||||
val entriesBySourceSet = zip.entries().asSequence()
|
||||
.groupBy { it.name.substringBefore("/") }
|
||||
.filterKeys { it in chooseSourceSetsByNames }
|
||||
artifactBySourceSet.forEach { (sourceSetName, artifact) ->
|
||||
ZipFile(artifact).use { zip ->
|
||||
val entries = zip.entries().asSequence().filter { it.name.startsWith("$sourceSetName/") }
|
||||
|
||||
entriesBySourceSet.forEach { (sourceSetName, entries) ->
|
||||
// TODO: once IJ supports non-JAR metadata dependencies, extract to a directory, not a JAR
|
||||
// Also, if both IJ and the CLI compiler can read metadata from a path inside a JAR, then no operations will be needed
|
||||
val extension =
|
||||
|
||||
+3
-6
@@ -10,7 +10,6 @@ import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.Internal
|
||||
import org.gradle.api.tasks.Nested
|
||||
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull
|
||||
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
|
||||
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope
|
||||
import org.jetbrains.kotlin.gradle.plugin.sources.getSourceSetHierarchy
|
||||
import org.jetbrains.kotlin.gradle.plugin.sources.sourceSetDependencyConfigurationByScope
|
||||
@@ -22,7 +21,7 @@ import org.w3c.dom.NodeList
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
|
||||
data class ModuleDependencyIdentifier(
|
||||
val groupId: String,
|
||||
val groupId: String?,
|
||||
val moduleId: String
|
||||
)
|
||||
|
||||
@@ -68,7 +67,7 @@ data class KotlinProjectStructureMetadata(
|
||||
@Suppress("UNUSED") // Gradle input
|
||||
@get:Input
|
||||
internal val sourceSetModuleDependenciesInput: Map<String, Set<Pair<String, String>>>
|
||||
get() = sourceSetModuleDependencies.mapValues { (_, ids) -> ids.map { (group, module) -> group to module }.toSet() }
|
||||
get() = sourceSetModuleDependencies.mapValues { (_, ids) -> ids.map { (group, module) -> group.orEmpty() to module }.toSet() }
|
||||
|
||||
companion object {
|
||||
internal const val FORMAT_VERSION_0_1 = "0.1"
|
||||
@@ -111,9 +110,7 @@ internal fun buildKotlinProjectStructureMetadata(project: Project): KotlinProjec
|
||||
}.distinct()
|
||||
else -> project.configurations.getByName(sourceSet.apiConfigurationName).allDependencies
|
||||
}
|
||||
sourceSet.name to sourceSetExportedDependencies.map {
|
||||
ModuleDependencyIdentifier(it.group.orEmpty(), it.name)
|
||||
}.toSet()
|
||||
sourceSet.name to sourceSetExportedDependencies.map { ModuleIds.fromDependency(it) }.toSet()
|
||||
},
|
||||
hostSpecificSourceSets = getHostSpecificSourceSets(project).map { it.name }.toSet(),
|
||||
sourceSetBinaryLayout = sourceSetsWithMetadataCompilations.keys.associate { sourceSet ->
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.mpp
|
||||
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.artifacts.Dependency
|
||||
import org.gradle.api.artifacts.ProjectDependency
|
||||
import org.gradle.api.artifacts.component.*
|
||||
import org.gradle.api.artifacts.result.ResolvedComponentResult
|
||||
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
|
||||
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull
|
||||
|
||||
internal object ModuleIds {
|
||||
fun fromDependency(dependency: Dependency) = when (dependency) {
|
||||
is ProjectDependency -> idOfRootModule(dependency.dependencyProject)
|
||||
else -> ModuleDependencyIdentifier(dependency.group, dependency.name)
|
||||
}
|
||||
|
||||
fun fromComponentSelector(
|
||||
thisProject: Project,
|
||||
componentSelector: ComponentSelector
|
||||
): ModuleDependencyIdentifier = when (componentSelector) {
|
||||
is ProjectComponentSelector -> idOfRootModuleByProjectPath(thisProject, componentSelector.projectPath)
|
||||
is ModuleComponentSelector -> ModuleDependencyIdentifier(componentSelector.group, componentSelector.module)
|
||||
else -> idFromName(componentSelector.displayName)
|
||||
}
|
||||
|
||||
fun fromComponentId(
|
||||
thisProject: Project,
|
||||
componentIdentifier: ComponentIdentifier
|
||||
): ModuleDependencyIdentifier =
|
||||
when (componentIdentifier) {
|
||||
is ProjectComponentIdentifier -> idOfRootModuleByProjectPath(thisProject, componentIdentifier.projectPath)
|
||||
is ModuleComponentIdentifier -> ModuleDependencyIdentifier(componentIdentifier.group, componentIdentifier.module)
|
||||
else -> idFromName(componentIdentifier.displayName)
|
||||
}
|
||||
|
||||
fun fromComponent(thisProject: Project, component: ResolvedComponentResult) =
|
||||
fromComponentId(thisProject, component.id)
|
||||
|
||||
private fun idOfRootModule(project: Project): ModuleDependencyIdentifier =
|
||||
if (project.multiplatformExtensionOrNull != null) {
|
||||
val rootPublication = project.multiplatformExtension.rootSoftwareComponent.publicationDelegate
|
||||
val group = rootPublication?.groupId ?: project.group.toString()
|
||||
val name = rootPublication?.artifactId ?: project.name
|
||||
ModuleDependencyIdentifier(group, name)
|
||||
} else {
|
||||
ModuleDependencyIdentifier(project.group.toString(), project.name)
|
||||
}
|
||||
|
||||
private fun idFromName(name: String) =
|
||||
ModuleDependencyIdentifier(null, name)
|
||||
|
||||
private fun idOfRootModuleByProjectPath(thisProject: Project, projectPath: String): ModuleDependencyIdentifier =
|
||||
idOfRootModule(thisProject.project(projectPath))
|
||||
}
|
||||
+218
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.mpp
|
||||
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.artifacts.Configuration
|
||||
import org.gradle.api.artifacts.component.ComponentIdentifier
|
||||
import org.gradle.api.artifacts.component.ProjectComponentIdentifier
|
||||
import org.gradle.api.artifacts.result.ResolvedArtifactResult
|
||||
import org.gradle.api.artifacts.result.ResolvedComponentResult
|
||||
import org.gradle.api.artifacts.result.ResolvedDependencyResult
|
||||
import org.gradle.api.artifacts.result.ResolvedVariantResult
|
||||
import org.gradle.api.attributes.Usage
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
|
||||
import org.jetbrains.kotlin.gradle.plugin.usageByName
|
||||
import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast
|
||||
import java.io.File
|
||||
|
||||
internal class ResolvedMppVariantsProvider private constructor(private val project: Project) {
|
||||
companion object {
|
||||
|
||||
fun get(project: Project): ResolvedMppVariantsProvider = with(project.extensions.extraProperties) {
|
||||
val propertyName = "kotlin.mpp.internal.resolvedModuleVariantsProvider"
|
||||
if (!has(propertyName)) {
|
||||
set(propertyName, ResolvedMppVariantsProvider(project))
|
||||
}
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
get(propertyName) as ResolvedMppVariantsProvider
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets the name of the variant that the module specified by the [moduleIdentifier] resolved to in the given [configuration].
|
||||
* The [moduleIdentifier] may be either the root module or a platform-specific module, the result is the same for the two cases. */
|
||||
fun getResolvedVariantName(moduleIdentifier: ModuleDependencyIdentifier, configuration: Configuration): String? =
|
||||
getEntryForModule(moduleIdentifier).run {
|
||||
if (configuration !in resolvedVariantsByConfiguration) {
|
||||
resolveConfigurationAndSaveVariants(configuration, artifactResolutionMode = ArtifactResolutionMode.NONE)
|
||||
}
|
||||
val variants = resolvedVariantsByConfiguration.getOrPut(configuration) { null }
|
||||
variants?.singleOrNull()?.displayName
|
||||
}
|
||||
|
||||
/** Gets the artifact that contains the common code metadata for the given [rootModuleIdentifier], which can only denote the root
|
||||
* module of a multiplatform project, not one of its platform-specific modules, as seen in the given [configuration].
|
||||
* If the [configuration] requests platform artifacts and not the common code metadata, then this function will resolve its
|
||||
* dependencies to metadata separately. */
|
||||
fun getMetadataArtifactByRootModule(rootModuleIdentifier: ModuleDependencyIdentifier, configuration: Configuration): File? {
|
||||
val rootModuleEntry = getEntryForModule(rootModuleIdentifier)
|
||||
|
||||
val platformModuleEntry = rootModuleEntry.run {
|
||||
if (configuration !in chosenPlatformModuleByConfiguration) {
|
||||
resolveConfigurationAndSaveVariants(configuration, artifactResolutionMode = ArtifactResolutionMode.METADATA)
|
||||
}
|
||||
chosenPlatformModuleByConfiguration.getOrPut(configuration) { null }
|
||||
}
|
||||
|
||||
return platformModuleEntry?.run {
|
||||
// The condition might be true if the configuration has only been resolved with resolution mode NORMAL
|
||||
if (configuration !in resolvedMetadataArtifactByConfiguration) {
|
||||
resolveConfigurationAndSaveVariants(configuration, artifactResolutionMode = ArtifactResolutionMode.METADATA)
|
||||
}
|
||||
resolvedMetadataArtifactByConfiguration.getOrPut(configuration) { null }
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets the artifact of a particular MPP platform-specific [moduleIdentifier] as resolved in the [configuration]. */
|
||||
fun getPlatformArtifactByPlatformModule(moduleIdentifier: ModuleDependencyIdentifier, configuration: Configuration): File? =
|
||||
getEntryForModule(moduleIdentifier).run {
|
||||
if (configuration !in resolvedArtifactByConfiguration) {
|
||||
resolveConfigurationAndSaveVariants(configuration, artifactResolutionMode = ArtifactResolutionMode.NORMAL)
|
||||
}
|
||||
resolvedArtifactByConfiguration.getOrPut(configuration) { null }
|
||||
}
|
||||
|
||||
private fun getEntryForModule(moduleIdentifier: ModuleDependencyIdentifier) =
|
||||
entriesCache.getOrPut(moduleIdentifier, { ModuleEntry(moduleIdentifier) })
|
||||
|
||||
private val entriesCache: MutableMap<ModuleDependencyIdentifier, ModuleEntry> = mutableMapOf()
|
||||
|
||||
private val mppComponentIdsByConfiguration: MutableMap<Configuration, Set<ComponentIdentifier>> = mutableMapOf()
|
||||
|
||||
private enum class ArtifactResolutionMode {
|
||||
NONE, NORMAL, METADATA
|
||||
}
|
||||
|
||||
private fun resolveConfigurationAndSaveVariants(
|
||||
configuration: Configuration,
|
||||
artifactResolutionMode: ArtifactResolutionMode
|
||||
) {
|
||||
val mppComponentIds: Set<ComponentIdentifier> = mppComponentIdsByConfiguration.getOrPut(configuration) {
|
||||
resolveMppComponents(configuration)
|
||||
}
|
||||
|
||||
if (artifactResolutionMode != ArtifactResolutionMode.NONE) {
|
||||
val artifacts = resolveArtifacts(artifactResolutionMode, configuration, mppComponentIds)
|
||||
matchMppComponentsWithResolvedArtifacts(mppComponentIds, artifacts, configuration, artifactResolutionMode)
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveMppComponents(configuration: Configuration): MutableSet<ComponentIdentifier> {
|
||||
val result = mutableListOf<ResolvedComponentResult>()
|
||||
|
||||
configuration.incoming.resolutionResult.allComponents { component ->
|
||||
val moduleId = ModuleIds.fromComponent(project, component)
|
||||
val variants = component.variantsCompatible
|
||||
|
||||
val isMpp = variants.any { variant -> variant.attributes.keySet().any { it.name == KotlinPlatformType.attribute.name } }
|
||||
if (isMpp) {
|
||||
result.add(component)
|
||||
val moduleEntry = getEntryForModule(moduleId)
|
||||
moduleEntry.resolvedVariantsByConfiguration[configuration] = variants
|
||||
|
||||
moduleEntry.dependenciesByConfiguration[configuration] = component.dependencies
|
||||
.filterIsInstance<ResolvedDependencyResult>()
|
||||
.map { dependency -> ModuleIds.fromComponent(project, dependency.selected) }
|
||||
|
||||
if (component.id is ProjectComponentIdentifier) {
|
||||
// Then the platform variant chosen for this module is definitely inside the module itself:
|
||||
moduleEntry.chosenPlatformModuleByConfiguration[configuration] = moduleEntry
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result.mapTo(mutableSetOf()) { it.id }
|
||||
}
|
||||
|
||||
private fun resolveArtifacts(
|
||||
artifactResolutionMode: ArtifactResolutionMode,
|
||||
configuration: Configuration,
|
||||
mppComponentIds: Set<ComponentIdentifier>
|
||||
): Map<ComponentIdentifier, ResolvedArtifactResult> {
|
||||
val artifactsConfiguration =
|
||||
if (
|
||||
artifactResolutionMode == ArtifactResolutionMode.NORMAL ||
|
||||
configuration.attributes.getAttribute(Usage.USAGE_ATTRIBUTE)?.name == KotlinUsages.KOTLIN_METADATA
|
||||
) {
|
||||
configuration
|
||||
} else {
|
||||
configuration.copyRecursive().apply {
|
||||
attributes.attribute(Usage.USAGE_ATTRIBUTE, project.usageByName(KotlinUsages.KOTLIN_METADATA))
|
||||
}
|
||||
}
|
||||
|
||||
return artifactsConfiguration.incoming.artifactView { view ->
|
||||
view.componentFilter { it in mppComponentIds }
|
||||
view.attributes { attrs -> attrs.attribute(Usage.USAGE_ATTRIBUTE, project.usageByName(KotlinUsages.KOTLIN_METADATA)) }
|
||||
}.artifacts.associateBy { it.id.componentIdentifier }
|
||||
}
|
||||
|
||||
private fun matchMppComponentsWithResolvedArtifacts(
|
||||
mppComponentIds: Set<ComponentIdentifier>,
|
||||
artifacts: Map<ComponentIdentifier, ResolvedArtifactResult>,
|
||||
configuration: Configuration,
|
||||
artifactResolutionMode: ArtifactResolutionMode
|
||||
) {
|
||||
val mppModuleIds = mppComponentIds.mapTo(mutableSetOf()) { ModuleIds.fromComponentId(project, it) }
|
||||
|
||||
mppComponentIds.forEach { componentId ->
|
||||
val moduleEntry = getEntryForModule(ModuleIds.fromComponentId(project, componentId))
|
||||
val artifact = artifacts[componentId]
|
||||
when {
|
||||
// With project dependencies, we don't need the host-specific metadata artifacts, as we have the compilation outputs:
|
||||
componentId is ProjectComponentIdentifier -> {
|
||||
moduleEntry.resolvedMetadataArtifactByConfiguration[configuration] = null
|
||||
}
|
||||
|
||||
// We found the host-specific artifact for a platform variant of some MPP:
|
||||
artifact != null -> {
|
||||
val resolvedArtifactMap = when (artifactResolutionMode) {
|
||||
ArtifactResolutionMode.NORMAL -> moduleEntry.resolvedArtifactByConfiguration
|
||||
ArtifactResolutionMode.METADATA -> moduleEntry.resolvedMetadataArtifactByConfiguration
|
||||
else -> error("unexpected $artifactResolutionMode")
|
||||
}
|
||||
resolvedArtifactMap[configuration] = artifact.file
|
||||
}
|
||||
|
||||
// Otherwise, this may be a root module of some MPP that resolved to a variant in another module. Take a note of that.
|
||||
else -> {
|
||||
// TODO: there's an assumption that resolving a root MPP module to a host-specific metadata artifact and to a platform
|
||||
// artifact will choose variants that are published within the same Maven module; change this code if that's not
|
||||
// true anymore.
|
||||
val singleDependencyId = moduleEntry.dependenciesByConfiguration.getValue(configuration).singleOrNull()
|
||||
if (singleDependencyId != null && singleDependencyId in mppModuleIds) {
|
||||
moduleEntry.chosenPlatformModuleByConfiguration[configuration] =
|
||||
getEntryForModule(singleDependencyId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores resolution results of the module denoted by [moduleIdentifier] in different configurations of the project.
|
||||
* The [moduleIdentifier] may point to a root module of a multiplatform project (then it has meaningful
|
||||
* [chosenPlatformModuleByConfiguration]) or to a platform module.
|
||||
*/
|
||||
private class ModuleEntry(
|
||||
@Suppress("unused") // simplify debugging
|
||||
val moduleIdentifier: ModuleDependencyIdentifier
|
||||
) {
|
||||
val dependenciesByConfiguration: MutableMap<Configuration, List<ModuleDependencyIdentifier>> = HashMap()
|
||||
val resolvedVariantsByConfiguration: MutableMap<Configuration, List<ResolvedVariantResult?>?> = HashMap()
|
||||
val resolvedArtifactByConfiguration: MutableMap<Configuration, File?> = HashMap()
|
||||
val resolvedMetadataArtifactByConfiguration: MutableMap<Configuration, File?> = HashMap()
|
||||
val chosenPlatformModuleByConfiguration: MutableMap<Configuration, ModuleEntry?> = HashMap()
|
||||
}
|
||||
}
|
||||
|
||||
private val ResolvedComponentResult.variantsCompatible: List<ResolvedVariantResult>
|
||||
get() = if (isGradleVersionAtLeast(5, 2)) {
|
||||
variants
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
listOf(variant)
|
||||
}
|
||||
+91
-33
@@ -7,76 +7,135 @@ package org.jetbrains.kotlin.gradle.plugin.mpp
|
||||
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.artifacts.Configuration
|
||||
import org.gradle.api.artifacts.ResolvedDependency
|
||||
import org.gradle.api.artifacts.result.ResolvedComponentResult
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilationToRunnableFiles
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
|
||||
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope
|
||||
import org.jetbrains.kotlin.gradle.targets.metadata.getPublishedPlatformCompilations
|
||||
import java.io.File
|
||||
|
||||
internal data class SourceSetVisibilityResult(
|
||||
/**
|
||||
* Names of source sets that the consumer sees from the requested dependency.
|
||||
*/
|
||||
val visibleSourceSetNames: Set<String>,
|
||||
|
||||
/**
|
||||
* For some of the [visibleSourceSetNames], additional artifacts may be present that
|
||||
* the consumer should read the compiled source set metadata from.
|
||||
*/
|
||||
val hostSpecificMetadataArtifactBySourceSet: Map<String, File>
|
||||
)
|
||||
|
||||
internal class SourceSetVisibilityProvider(
|
||||
private val project: Project
|
||||
) {
|
||||
private val resolvedVariantsProvider = ResolvedMppVariantsProvider.get(project)
|
||||
|
||||
/**
|
||||
* Determine which source sets of the [resolvedMppDependency] are visible in the [visibleFrom] source set.
|
||||
* Determine which source sets of the [resolvedRootMppDependency] are visible in the [visibleFrom] source set.
|
||||
*
|
||||
* This requires resolving dependencies of the compilations which [visibleFrom] takes part in, in order to find which variants the
|
||||
* [resolvedMppDependency] got resolved to for those compilations. The [resolvedMppDependency] should therefore be the dependency
|
||||
* [resolvedRootMppDependency] got resolved to for those compilations. The [resolvedRootMppDependency] should therefore be the dependency
|
||||
* on the 'root' module of the MPP (such as 'com.example:lib-foo', not 'com.example:lib-foo-metadata').
|
||||
*
|
||||
* Once the variants are known, they are checked against the [dependencyProjectStructureMetadata], and the
|
||||
* source sets of the dependency are determined that are compiled for all those variants and thus should be visible here.
|
||||
*
|
||||
* If the [resolvedMppDependency] is a project dependency, its project should be passed as [resolvedToOtherProject], as
|
||||
* If the [resolvedRootMppDependency] is a project dependency, its project should be passed as [resolvedToOtherProject], as
|
||||
* the Gradle API for dependency variants behaves differently for project dependencies and published ones.
|
||||
*/
|
||||
fun getVisibleSourceSetNames(
|
||||
fun getVisibleSourceSets(
|
||||
visibleFrom: KotlinSourceSet,
|
||||
dependencyScopes: Iterable<KotlinDependencyScope>,
|
||||
resolvedMppDependency: ResolvedDependency,
|
||||
resolvedRootMppDependency: ResolvedComponentResult?,
|
||||
resolvedMetadataDependency: ResolvedComponentResult,
|
||||
dependencyProjectStructureMetadata: KotlinProjectStructureMetadata,
|
||||
resolvedToOtherProject: Project?
|
||||
): Set<String> {
|
||||
): SourceSetVisibilityResult {
|
||||
val compilations = CompilationSourceSetUtil.compilationsBySourceSets(project).getValue(visibleFrom)
|
||||
|
||||
var visiblePlatformVariantNames: Set<String> =
|
||||
val mppModuleIdentifier = ModuleIds.fromComponent(project, resolvedRootMppDependency ?: resolvedMetadataDependency)
|
||||
|
||||
/**
|
||||
* When Gradle resolves a project dependency, as opposed to an external dependency, the variant name it returns in the resolution
|
||||
* results is the name of the configuration that is chosen during variant-aware resolution, not the actual name of the variant that
|
||||
* is written to the Gradle module metadata and the Kotlin project structure metadata. To fix this, get the published Kotlin
|
||||
* variants of the dependency project and find among them the one that owns the configuration by the given name.
|
||||
*/
|
||||
val projectPublishedCompilations = resolvedToOtherProject?.let(::getPublishedPlatformCompilations)?.keys
|
||||
fun platformVariantName(resolvedToVariantName: String): String =
|
||||
projectPublishedCompilations?.first { it.dependencyConfigurationName == resolvedToVariantName }?.name ?: resolvedToVariantName
|
||||
|
||||
val firstConfigurationByVariant = mutableMapOf<String, Configuration>()
|
||||
|
||||
val visiblePlatformVariantNames: Set<String> =
|
||||
compilations
|
||||
.filter { it.target.platformType != KotlinPlatformType.common }
|
||||
.flatMapTo(mutableSetOf()) { compilation ->
|
||||
val configurations = dependencyScopes.mapNotNullTo(mutableSetOf()) { scope ->
|
||||
project.resolvableConfigurationFromCompilationByScope(compilation, scope)
|
||||
}
|
||||
configurations.mapNotNull { configuration ->
|
||||
// Resolve the configuration but don't trigger artifacts download, only download component metadata:
|
||||
configuration.incoming.resolutionResult.allComponents
|
||||
.find {
|
||||
it.moduleVersion?.group == resolvedMppDependency.moduleGroup &&
|
||||
it.moduleVersion?.name == resolvedMppDependency.moduleName
|
||||
}
|
||||
?.variant?.displayName
|
||||
}
|
||||
// To find out which variant the MPP dependency got resolved for each compilation, take the resolvable configurations
|
||||
// that we have in the compilations:
|
||||
dependencyScopes.mapNotNull { scope -> project.resolvableConfigurationFromCompilationByScope(compilation, scope) }
|
||||
}
|
||||
.mapNotNullTo(mutableSetOf()) { configuration ->
|
||||
val resolvedVariant = resolvedVariantsProvider.getResolvedVariantName(mppModuleIdentifier, configuration)
|
||||
?.let { platformVariantName(it) }
|
||||
?: return@mapNotNullTo null
|
||||
|
||||
firstConfigurationByVariant.putIfAbsent(resolvedVariant, configuration)
|
||||
resolvedVariant
|
||||
}
|
||||
|
||||
if (visiblePlatformVariantNames.isEmpty()) {
|
||||
return emptySet()
|
||||
return SourceSetVisibilityResult(emptySet(), emptyMap())
|
||||
}
|
||||
|
||||
if (resolvedToOtherProject != null) {
|
||||
val publishedVariants = getPublishedPlatformCompilations(resolvedToOtherProject).keys
|
||||
|
||||
visiblePlatformVariantNames = visiblePlatformVariantNames.mapTo(mutableSetOf()) { configurationName ->
|
||||
publishedVariants.first { it.dependencyConfigurationName == configurationName }.name
|
||||
}
|
||||
}
|
||||
|
||||
return dependencyProjectStructureMetadata.sourceSetNamesByVariantName
|
||||
val visibleSourceSetNames = dependencyProjectStructureMetadata.sourceSetNamesByVariantName
|
||||
.filterKeys { it in visiblePlatformVariantNames }
|
||||
.values.let { if (it.isEmpty()) emptySet() else it.reduce { acc, item -> acc intersect item } }
|
||||
|
||||
val hostSpecificArtifactBySourceSet: Map<String, File> =
|
||||
if (resolvedToOtherProject != null) {
|
||||
/**
|
||||
* When a dependency resolves to a project, we don't need any artifacts from it, we can
|
||||
* instead use the compilation outputs directly:
|
||||
*/
|
||||
emptyMap()
|
||||
} else {
|
||||
val hostSpecificSourceSets = visibleSourceSetNames.intersect(dependencyProjectStructureMetadata.hostSpecificSourceSets)
|
||||
|
||||
/**
|
||||
* As all of the variants normally contain the same metadata for each of the relevant host-specific source sets,
|
||||
* any of the variants that we resolved can be used, so choose the first one that satisfies both:
|
||||
*
|
||||
* - it contains the host-specific source set, and
|
||||
* - we have resolved it for some compilation
|
||||
*/
|
||||
val someVariantByHostSpecificSourceSet =
|
||||
hostSpecificSourceSets.associate { sourceSetName ->
|
||||
sourceSetName to dependencyProjectStructureMetadata.sourceSetNamesByVariantName
|
||||
.filterKeys { it in firstConfigurationByVariant }
|
||||
.filterValues { sourceSetName in it }
|
||||
.keys.first()
|
||||
}
|
||||
|
||||
someVariantByHostSpecificSourceSet.mapValues { (_, variantName) ->
|
||||
val configuration = firstConfigurationByVariant.getValue(variantName)
|
||||
resolvedVariantsProvider.getMetadataArtifactByRootModule(mppModuleIdentifier, configuration)
|
||||
?: error("Couldn't resolve metadata artifact for $mppModuleIdentifier in $configuration")
|
||||
}
|
||||
}
|
||||
|
||||
return SourceSetVisibilityResult(
|
||||
visibleSourceSetNames,
|
||||
hostSpecificArtifactBySourceSet
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Project.resolvableConfigurationFromCompilationByScope(
|
||||
private fun Project.resolvableConfigurationFromCompilationByScope(
|
||||
compilation: KotlinCompilation<*>,
|
||||
scope: KotlinDependencyScope
|
||||
): Configuration? {
|
||||
@@ -89,5 +148,4 @@ internal fun Project.resolvableConfigurationFromCompilationByScope(
|
||||
}
|
||||
|
||||
return project.configurations.getByName(configurationName)
|
||||
}
|
||||
|
||||
}
|
||||
+3
-4
@@ -16,9 +16,8 @@ import org.jetbrains.kotlin.build.DEFAULT_KOTLIN_SOURCE_FILES_EXTENSIONS
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
|
||||
import org.jetbrains.kotlin.gradle.plugin.LanguageSettingsBuilder
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.DefaultKotlinDependencyHandler
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.*
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.GranularMetadataTransformation
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinProjectStructureMetadata
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution
|
||||
import org.jetbrains.kotlin.gradle.utils.*
|
||||
import java.io.File
|
||||
@@ -156,7 +155,7 @@ class DefaultKotlinSourceSet(
|
||||
internal fun getDependenciesTransformation(scope: KotlinDependencyScope): Iterable<MetadataDependencyTransformation> {
|
||||
val metadataDependencyResolutionByModule =
|
||||
dependencyTransformations[scope]?.metadataDependencyResolutions
|
||||
?.associateBy { it.dependency.moduleGroup to it.dependency.moduleName }
|
||||
?.associateBy { ModuleIds.fromComponent(project, it.dependency) }
|
||||
?: emptyMap()
|
||||
|
||||
val baseDir = project.buildDir.resolve("tmp/kotlinMetadata/$name/${scope.scopeName}")
|
||||
@@ -169,7 +168,7 @@ class DefaultKotlinSourceSet(
|
||||
|
||||
return metadataDependencyResolutionByModule.mapNotNull { (groupAndName, resolution) ->
|
||||
val (group, name) = groupAndName
|
||||
val projectPath = resolution.projectDependency?.dependencyProject?.path
|
||||
val projectPath = resolution.projectDependency?.path
|
||||
when (resolution) {
|
||||
is MetadataDependencyResolution.KeepOriginalDependency -> null
|
||||
|
||||
|
||||
+29
-19
@@ -31,7 +31,6 @@ import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
|
||||
import org.jetbrains.kotlin.gradle.utils.setArchiveAppendixCompatible
|
||||
import org.jetbrains.kotlin.gradle.utils.setArchiveClassifierCompatible
|
||||
import org.jetbrains.kotlin.statistics.metrics.BooleanMetrics
|
||||
import java.io.File
|
||||
import java.util.concurrent.Callable
|
||||
|
||||
internal const val ALL_COMPILE_METADATA_CONFIGURATION_NAME = "allSourceSetsCompileDependenciesMetadata"
|
||||
@@ -374,17 +373,25 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) :
|
||||
val (unrequested, requested) = metadataDependencyResolutions
|
||||
.partition { it is MetadataDependencyResolution.ExcludeAsUnrequested }
|
||||
|
||||
unrequested.forEach { configuration.exclude(mapOf("group" to it.dependency.moduleGroup, "module" to it.dependency.moduleName)) }
|
||||
unrequested.forEach {
|
||||
val (group, name) = it.projectDependency?.run {
|
||||
/** Note: the project dependency notation here should be exactly this, group:name,
|
||||
* not from [ModuleIds.fromProjectPathDependency], as `exclude` checks it against the project's group:name */
|
||||
ModuleDependencyIdentifier(group.toString(), name)
|
||||
} ?: ModuleIds.fromComponent(project, it.dependency)
|
||||
configuration.exclude(mapOf("group" to group, "module" to name))
|
||||
}
|
||||
|
||||
requested.forEach {
|
||||
val notation = listOf(it.dependency.moduleGroup, it.dependency.moduleName, it.dependency.moduleVersion).joinToString(":")
|
||||
requested.filter { it.projectDependency == null }.forEach {
|
||||
val (group, name) = ModuleIds.fromComponent(project, it.dependency)
|
||||
val notation = listOfNotNull(group.orEmpty(), name, it.dependency.moduleVersion?.version).joinToString(":")
|
||||
configuration.resolutionStrategy.force(notation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createTransformedMetadataClasspath(
|
||||
fromFiles: Iterable<File>,
|
||||
fromFiles: Configuration,
|
||||
compilation: AbstractKotlinCompilation<*>
|
||||
): FileCollection {
|
||||
val project = compilation.target.project
|
||||
@@ -400,17 +407,12 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) :
|
||||
project.locateTask<TransformKotlinGranularMetadata>(transformGranularMetadataTaskName(hierarchySourceSet.name))
|
||||
}
|
||||
|
||||
val allResolutionsByArtifactFile: Map<File, Iterable<MetadataDependencyResolution>> =
|
||||
mutableMapOf<File, MutableList<MetadataDependencyResolution>>().apply {
|
||||
val allResolutionsByModule: Map<ModuleDependencyIdentifier, List<MetadataDependencyResolution>> =
|
||||
mutableMapOf<ModuleDependencyIdentifier, MutableList<MetadataDependencyResolution>>().apply {
|
||||
transformationTaskHolders.forEach {
|
||||
val resolutions = it.get().metadataDependencyResolutions
|
||||
|
||||
resolutions.forEach { resolution ->
|
||||
val artifacts = resolution.dependency.moduleArtifacts.map { it.file }
|
||||
|
||||
artifacts.forEach { artifactFile ->
|
||||
getOrPut(artifactFile) { mutableListOf() }.add(resolution)
|
||||
}
|
||||
getOrPut(ModuleIds.fromComponent(project, resolution.dependency)) { mutableListOf() }.add(resolution)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -423,14 +425,22 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) :
|
||||
dependencyCompilation.output.classesDirs.takeIf { hierarchySourceSet != sourceSet }
|
||||
}
|
||||
|
||||
val artifactView = fromFiles.incoming.artifactView { view ->
|
||||
view.componentFilter { id ->
|
||||
val moduleId = ModuleIds.fromComponentId(project, id)
|
||||
allResolutionsByModule[moduleId].let { resolutions ->
|
||||
resolutions == null || resolutions.any { it !is MetadataDependencyResolution.ExcludeAsUnrequested }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutableSetOf<Any /* File | FileCollection */>().apply {
|
||||
|
||||
addAll(dependsOnCompilationOutputs)
|
||||
|
||||
fromFiles.forEach { file ->
|
||||
val resolutions = allResolutionsByArtifactFile[file]
|
||||
artifactView.artifacts.forEach { artifact ->
|
||||
val resolutions =
|
||||
allResolutionsByModule[ModuleIds.fromComponentId(project, artifact.id.componentIdentifier)]
|
||||
if (resolutions == null) {
|
||||
add(file)
|
||||
add(artifact.file)
|
||||
} else {
|
||||
val chooseVisibleSourceSets =
|
||||
resolutions.filterIsInstance<MetadataDependencyResolution.ChooseVisibleSourceSets>()
|
||||
@@ -439,7 +449,7 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) :
|
||||
// Wrap the list into a FileCollection, as some older Gradle version failed to resolve the classpath
|
||||
add(project.files(chooseVisibleSourceSets.map { transformedFilesByResolution.getValue(it) }))
|
||||
} else if (resolutions.any { it is MetadataDependencyResolution.KeepOriginalDependency }) {
|
||||
add(file)
|
||||
add(artifact.file)
|
||||
} // else: all dependency transformations exclude this dependency as unrequested; don't add any files
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user