Support published MPP consumption with PM2.0 projects

This commit is contained in:
Sergey Igushkin
2021-02-10 13:19:55 +03:00
parent 70b88ada93
commit f1f97b4104
32 changed files with 1113 additions and 290 deletions
@@ -35,6 +35,9 @@ internal fun Project.createKotlinExtension(extensionClass: KClass<out KotlinTopL
internal val Project.topLevelExtension: KotlinTopLevelExtension
get() = extensions.getByName(KOTLIN_PROJECT_EXTENSION_NAME) as KotlinTopLevelExtension
internal val Project.topLevelExtensionOrNull: KotlinTopLevelExtension?
get() = extensions.findByName(KOTLIN_PROJECT_EXTENSION_NAME) as KotlinTopLevelExtension?
internal val Project.kotlinExtensionOrNull: KotlinProjectExtension?
get() = extensions.findByName(KOTLIN_PROJECT_EXTENSION_NAME) as? KotlinProjectExtension
@@ -36,6 +36,7 @@ import org.jetbrains.kotlin.gradle.logging.kotlinDebug
import org.jetbrains.kotlin.gradle.model.builder.KotlinModelBuilder
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinCompilationData
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.isMain
import org.jetbrains.kotlin.gradle.scripting.internal.ScriptingGradleSubplugin
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsBinaryMode
import org.jetbrains.kotlin.gradle.targets.js.ir.*
@@ -283,14 +284,14 @@ internal class Kotlin2JsSourceSetProcessor(
val baseName = if (kotlinCompilation.isMain()) {
project.name
} else {
"${project.name}_${kotlinCompilation.name}"
"${project.name}_${kotlinCompilation.compilationPurpose}"
}
kotlinTaskInstance.kotlinOptions.freeCompilerArgs += "$MODULE_NAME=${project.klibModuleName(baseName)}"
}
}
val subpluginEnvironment: SubpluginEnvironment = SubpluginEnvironment.loadSubplugins(project, kotlinPluginVersion)
if (kotlinCompilation is KotlinCompilation<*>) {
if (kotlinCompilation is KotlinCompilation<*>) { // FIXME support compiler plugins with PM20
subpluginEnvironment.addSubpluginOptions(project, kotlinCompilation)
}
}
@@ -86,9 +86,6 @@ abstract class KotlinBasePluginWrapper : Plugin<Project> {
kotlinGradleBuildServices.detectKotlinPluginLoadedInMultipleProjects(project, kotlinPluginVersion)
project.createKotlinExtension(projectExtensionClass).apply {
if (this is KotlinPm20ProjectExtension)
this.project = project // refactor this code to remove the specific plugin class?
coreLibrariesVersion = kotlinPluginVersion
fun kotlinSourceSetContainer(factory: NamedDomainObjectFactory<KotlinSourceSet>) =
@@ -11,10 +11,6 @@ import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import java.io.File
import javax.xml.transform.OutputKeys
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult
open class GenerateProjectStructureMetadata : DefaultTask() {
@get:Internal
@@ -25,14 +21,14 @@ open class GenerateProjectStructureMetadata : DefaultTask() {
get() = lazyKotlinProjectStructureMetadata.value
@get:OutputFile
val resultXmlFile: File
val resultFile: File
get() = project.buildDir.resolve("kotlinProjectStructureMetadata/$MULTIPLATFORM_PROJECT_METADATA_JSON_FILE_NAME")
@TaskAction
fun generateMetadataXml() {
resultXmlFile.parentFile.mkdirs()
resultFile.parentFile.mkdirs()
val resultString = kotlinProjectStructureMetadata.toJson()
resultXmlFile.writeText(resultString)
resultFile.writeText(resultString)
}
}
@@ -7,13 +7,15 @@ package org.jetbrains.kotlin.gradle.plugin.mpp
import org.gradle.api.Named
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.ModuleDependency
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.artifacts.result.ResolvedComponentResult
import org.gradle.api.artifacts.result.ResolvedDependencyResult
import org.gradle.api.attributes.Attribute
import org.gradle.api.attributes.AttributeContainer
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinSingleTargetExtension
import org.jetbrains.kotlin.gradle.dsl.*
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
@@ -28,45 +30,56 @@ class ProjectStructureMetadataModuleBuilder {
private val modulesCache = mutableMapOf<KotlinModuleIdentifier, KotlinModule>()
private fun buildModuleFromProjectStructureMetadata(
moduleIdentifier: KotlinModuleIdentifier,
component: ResolvedComponentResult,
metadata: KotlinProjectStructureMetadata
): KotlinModule =
BasicKotlinModule(moduleIdentifier).apply {
metadata.sourceSetNamesByVariantName.keys.forEach { variantName ->
fragments.add(BasicKotlinModuleVariant(this@apply, variantName))
}
fun fragment(sourceSetName: String): BasicKotlinModuleFragment {
if (fragments.none { it.fragmentName == sourceSetName })
fragments.add(BasicKotlinModuleFragment(this@apply, sourceSetName))
return fragmentByName(sourceSetName)
}
metadata.sourceSetNamesByVariantName.forEach { (variantName, sourceSets) ->
val variant = fragmentByName(variantName)
sourceSets.forEach { sourceSetName ->
variant.directRefinesDependencies.add(fragment(sourceSetName))
): KotlinModule {
return ExternalImportedKotlinModule(
BasicKotlinModule(component.toModuleIdentifier()).apply {
metadata.sourceSetNamesByVariantName.keys.forEach { variantName ->
fragments.add(BasicKotlinModuleVariant(this@apply, variantName))
}
}
metadata.sourceSetModuleDependencies.forEach { (sourceSetName, dependencies) ->
val fragment = fragment(sourceSetName)
dependencies.forEach { dependency ->
fragment.declaredModuleDependencies.add(
KotlinModuleDependency(MavenModuleIdentifier(dependency.groupId.orEmpty(), dependency.moduleId, null /* TODO */))
)
fun fragment(sourceSetName: String): BasicKotlinModuleFragment {
if (fragments.none { it.fragmentName == sourceSetName })
fragments.add(BasicKotlinModuleFragment(this@apply, sourceSetName))
return fragmentByName(sourceSetName)
}
metadata.sourceSetNamesByVariantName.forEach { (variantName, sourceSets) ->
val variant = fragmentByName(variantName)
sourceSets.forEach { sourceSetName ->
variant.directRefinesDependencies.add(fragment(sourceSetName))
}
}
metadata.sourceSetModuleDependencies.forEach { (sourceSetName, dependencies) ->
val fragment = fragment(sourceSetName)
dependencies.forEach { dependency ->
fragment.declaredModuleDependencies.add(
KotlinModuleDependency(
MavenModuleIdentifier(
dependency.groupId.orEmpty(),
dependency.moduleId,
null /* TODO */
)
)
)
}
}
}
metadata.sourceSetsDependsOnRelation.forEach { (depending, dependencies) ->
val dependingFragment = fragment(depending)
dependencies.forEach { dependency ->
dependingFragment.directRefinesDependencies.add(fragment(dependency))
metadata.sourceSetsDependsOnRelation.forEach { (depending, dependencies) ->
val dependingFragment = fragment(depending)
dependencies.forEach { dependency ->
dependingFragment.directRefinesDependencies.add(fragment(dependency))
}
}
}
}
},
metadata
)
}
fun getModule(moduleIdentifier: KotlinModuleIdentifier, projectStructureMetadata: KotlinProjectStructureMetadata): KotlinModule {
return modulesCache.getOrPut(moduleIdentifier) {
fun getModule(component: ResolvedComponentResult, projectStructureMetadata: KotlinProjectStructureMetadata): KotlinModule {
val moduleId = component.toModuleIdentifier()
return modulesCache.getOrPut(moduleId) {
buildModuleFromProjectStructureMetadata(
moduleIdentifier,
component,
projectStructureMetadata
)
}
@@ -107,7 +120,12 @@ private fun detectModules(targets: Iterable<KotlinTarget>, sourceSets: Iterable<
@Suppress("unused")
class GradleProjectModuleBuilder(private val addInferredSourceSetVisibilityAsExplicit: Boolean) {
private fun getModulesFromPm20Project(project: Project) = project.pm20Extension.modules.toList()
fun buildModulesFromProject(project: Project): List<KotlinModule> {
if (project.topLevelExtension is KotlinPm20ProjectExtension)
return getModulesFromPm20Project(project)
val extension = project.multiplatformExtensionOrNull
?: project.kotlinExtension
?: return emptyList()
@@ -217,15 +235,6 @@ class GradleProjectModuleBuilder(private val addInferredSourceSetVisibilityAsExp
}
}
private fun <T : Any> attributeString(container: AttributeContainer, attributeKey: Attribute<T>): String {
val value = container.getAttribute(attributeKey)
return when (value) {
is Named -> value.name
else -> value.toString()
}
}
internal fun Dependency.toModuleDependency(
project: Project
): KotlinModuleDependency {
@@ -266,21 +275,7 @@ class GradleModuleVariantResolver(val project: Project) : ModuleVariantResolver
// This implementation can only resolve variants for the current project's KotlinModule
require(module.representsProject(project))
val targets =
project.multiplatformExtensionOrNull?.targets ?: listOf((project.kotlinExtension as KotlinSingleTargetExtension).target)
val compilation =
targets.filterIsInstance<AbstractKotlinTarget>()
.flatMap { it.kotlinComponents.filterIsInstance<KotlinVariant>() }
.flatMap { it.usages }
.firstOrNull { it.name == requestingVariant.fragmentName }
?.compilation
?: targets.asSequence().flatMap { it.compilations.asSequence() }.single {
it.defaultSourceSetName == requestingVariant.fragmentName
} // TODO: generalize the mapping PM2.0 <-> MPP
?: return VariantResolution.Unknown(requestingVariant, dependencyModule)
val compileClasspath = project.configurations.getByName(compilation.compileDependencyConfigurationName)
val compileClasspath = getCompileDependenciesConfigurationForVariant(project, requestingVariant)
// TODO optimize O(n) search, store the mapping
val component = compileClasspath.incoming.resolutionResult.allComponents.find { it.id.matchesModule(dependencyModule) }
@@ -288,7 +283,9 @@ class GradleModuleVariantResolver(val project: Project) : ModuleVariantResolver
val dependencyModuleId = ModuleIds.fromComponent(project, component)
// FIXME check composite builds, it's likely that resolvedVariantProvider fails on them?
val variantName = resolvedVariantProvider.getResolvedVariantName(dependencyModuleId, compileClasspath)
val variantName =
resolvedVariantProvider.getResolvedVariantName(dependencyModuleId, compileClasspath)
?.let(::kotlinVariantNameFromPublishedVariantName)
val resultVariant = dependencyModule.variants.singleOrNull { it.fragmentName == variantName }
@@ -297,4 +294,31 @@ class GradleModuleVariantResolver(val project: Project) : ModuleVariantResolver
else
VariantResolution.VariantMatch(requestingVariant, dependencyModule, resultVariant)
}
private fun getCompileDependenciesConfigurationForVariant(project: Project, requestingVariant: KotlinModuleVariant): Configuration =
when (project.topLevelExtension) {
is KotlinMultiplatformExtension, is KotlinSingleTargetExtension -> {
val targets =
project.multiplatformExtensionOrNull?.targets ?: listOf((project.kotlinExtension as KotlinSingleTargetExtension).target)
val compilation =
targets.filterIsInstance<AbstractKotlinTarget>()
.flatMap { it.kotlinComponents.filterIsInstance<KotlinVariant>() }
.flatMap { it.usages }
.firstOrNull { it.name == requestingVariant.fragmentName }
?.compilation
?: targets.asSequence().flatMap { it.compilations.asSequence() }.single {
it.defaultSourceSetName == requestingVariant.fragmentName
} // TODO: generalize the mapping PM2.0 <-> MPP
?: error("could not find a compilation that produces the variant $requestingVariant in $project")
project.configurations.getByName(compilation.compileDependencyConfigurationName)
}
is KotlinPm20ProjectExtension -> {
project.configurations.getByName((requestingVariant as KotlinGradleVariant).compileDependencyConfigurationName)
}
else -> error("could not find the compile dependencies configuration for variant $requestingVariant")
}
}
@@ -13,8 +13,12 @@ import org.gradle.api.artifacts.result.ResolvedComponentResult
import org.gradle.api.artifacts.result.ResolvedDependencyResult
import org.gradle.api.attributes.Attribute
import org.gradle.api.file.FileCollection
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.topLevelExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinPm20ProjectExtension
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.toModuleIdentifier
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope
import org.jetbrains.kotlin.gradle.plugin.sources.sourceSetDependencyConfigurationByScope
import org.jetbrains.kotlin.gradle.targets.metadata.ALL_COMPILE_METADATA_CONFIGURATION_NAME
@@ -207,7 +211,7 @@ internal class GranularMetadataTransformation(
resolveViaAvailableAt = false // we will process the available-at module as a dependency later in the queue
)
val resolvedToProject: Project? = (mppDependencyMetadataExtractor as? ProjectMppDependencyMetadataExtractor)?.dependencyProject
val resolvedToProject: Project? = module.toProjectOrNull(project)
val projectStructureMetadata = mppDependencyMetadataExtractor?.getProjectStructureMetadata()
?: return MetadataDependencyResolution.KeepOriginalDependency(module, resolvedToProject)
@@ -254,25 +258,33 @@ internal class GranularMetadataTransformation(
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 getExtractableMetadataFiles(baseDir: File): ExtractableMetadataFiles =
metadataExtractor.getExtractableMetadataFiles(visibleSourceSetNamesExcludingDependsOn, baseDir)
internal 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 getExtractableMetadataFiles(baseDir: File): ExtractableMetadataFiles =
metadataExtractor.getExtractableMetadataFiles(visibleSourceSetNamesExcludingDependsOn, baseDir)
}
internal fun ResolvedComponentResult.toProjectOrNull(currentProject: Project): Project? {
val identifier = id
return when {
identifier is ProjectComponentIdentifier && identifier.build.isCurrentBuild -> currentProject.project(identifier.projectPath)
else -> null
}
}
@@ -345,7 +357,7 @@ internal fun requestedDependencies(
}
private abstract class MppDependencyMetadataExtractor(val project: Project, val dependency: ResolvedComponentResult) {
internal abstract class MppDependencyMetadataExtractor(val project: Project, val dependency: ResolvedComponentResult) {
abstract fun getProjectStructureMetadata(): KotlinProjectStructureMetadata?
abstract fun getExtractableMetadataFiles(
@@ -366,9 +378,19 @@ private class ProjectMppDependencyMetadataExtractor(
visibleSourceSetNames: Set<String>,
baseDir: File
): ExtractableMetadataFiles {
val result = dependencyProject.multiplatformExtension.targets.getByName(KotlinMultiplatformPlugin.METADATA_TARGET_NAME).compilations
.filter { it.name in visibleSourceSetNames }
.associate { it.defaultSourceSet.name to it.output.classesDirs }
val result = when (val projectExtension = dependencyProject.topLevelExtension) {
is KotlinMultiplatformExtension -> projectExtension.targets.getByName(KotlinMultiplatformPlugin.METADATA_TARGET_NAME).compilations
.filter { it.name in visibleSourceSetNames }.associate { it.defaultSourceSet.name to it.output.classesDirs }
is KotlinPm20ProjectExtension -> {
val moduleId = dependency.toModuleIdentifier()
val module = projectExtension.modules.single { it.moduleIdentifier == moduleId }
val metadataCompilationRegistry = projectExtension.metadataCompilationRegistryByModuleId.getValue(moduleId)
visibleSourceSetNames.associateWith {
metadataCompilationRegistry.byFragment(module.fragments.getByName(it)).output.classesDirs
}
}
else -> error("unexpected top-level Kotlin extension $projectExtension")
}
return object : ExtractableMetadataFiles() {
override fun getMetadataFilesPerSourceSet(doProcessFiles: Boolean): Map<String, FileCollection> = result
@@ -502,32 +524,45 @@ private open class JarArtifactMppDependencyMetadataExtractor(
}
}
private fun getMetadataExtractor(
internal fun getMetadataExtractor(
project: Project,
module: ResolvedComponentResult,
resolvedComponentResult: ResolvedComponentResult,
configuration: Configuration,
resolveViaAvailableAt: Boolean
): MppDependencyMetadataExtractor? {
val resolvedMppVariantsProvider = ResolvedMppVariantsProvider.get(project)
val moduleIdentifier = ModuleIds.fromComponent(project, module)
val moduleIdentifier = ModuleIds.fromComponent(project, resolvedComponentResult)
// TODO check how this code works with multi-capability resolutions
var resolvedViaAvailableAt = false
val metadataArtifact = resolvedMppVariantsProvider.getResolvedArtifactByPlatformModule(
moduleIdentifier,
configuration
) ?: if (resolveViaAvailableAt) resolvedMppVariantsProvider.getHostSpecificMetadataArtifactByRootModule(
moduleIdentifier,
configuration
) else null
) ?: if (resolveViaAvailableAt) {
resolvedMppVariantsProvider.getHostSpecificMetadataArtifactByRootModule(
moduleIdentifier,
configuration
)?.also {
resolvedViaAvailableAt = true
}
} else null
val moduleId = module.id
val actualComponent = if (resolvedViaAvailableAt) {
resolvedComponentResult.dependencies.filterIsInstance<ResolvedDependencyResult>().singleOrNull()?.selected
?: resolvedComponentResult
} else resolvedComponentResult
val moduleId = actualComponent.id
return when {
moduleId is ProjectComponentIdentifier -> when {
moduleId.build.isCurrentBuild ->
ProjectMppDependencyMetadataExtractor(project, module, project.project(moduleId.projectPath))
ProjectMppDependencyMetadataExtractor(project, actualComponent, project.project(moduleId.projectPath))
metadataArtifact != null ->
IncludedBuildMetadataExtractor(project, module, metadataArtifact)
IncludedBuildMetadataExtractor(project, actualComponent, metadataArtifact)
else -> null
}
metadataArtifact != null -> JarArtifactMppDependencyMetadataExtractor(project, module, metadataArtifact)
metadataArtifact != null -> JarArtifactMppDependencyMetadataExtractor(project, actualComponent, metadataArtifact)
else -> null
}
}
@@ -10,10 +10,16 @@ import com.google.gson.JsonObject
import com.google.gson.JsonParser
import com.google.gson.stream.JsonWriter
import org.gradle.api.Project
import org.gradle.api.provider.Provider
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.dsl.topLevelExtension
import org.jetbrains.kotlin.gradle.dsl.topLevelExtensionOrNull
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinGradleModule
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinPm20ProjectExtension
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.refinesClosure
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope
import org.jetbrains.kotlin.gradle.plugin.sources.withAllDependsOnSourceSets
import org.jetbrains.kotlin.gradle.plugin.sources.sourceSetDependencyConfigurationByScope
@@ -27,10 +33,40 @@ import org.w3c.dom.NodeList
import java.io.StringWriter
import javax.xml.parsers.DocumentBuilderFactory
data class ModuleDependencyIdentifier(
val groupId: String?,
val moduleId: String
)
// FIXME support module classifiers for PM2.0 or drop this class in favor of KotlinModuleIdentifier
open class ModuleDependencyIdentifier(
open val groupId: String?,
open val moduleId: String
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is ModuleDependencyIdentifier) return false
if (groupId != other.groupId) return false
if (moduleId != other.moduleId) return false
return true
}
override fun hashCode(): Int {
var result = groupId?.hashCode() ?: 0
result = 31 * result + moduleId.hashCode()
return result
}
operator fun component1(): String? = groupId
operator fun component2(): String = moduleId
}
class ChangingModuleDependencyIdentifier(
val groupIdProvider: () -> String?,
val moduleIdProvider: () -> String
) : ModuleDependencyIdentifier(groupIdProvider(), moduleIdProvider()) {
override val groupId: String?
get() = groupIdProvider()
override val moduleId: String
get() = moduleIdProvider()
}
sealed class SourceSetMetadataLayout(
@get:Input
@@ -96,6 +132,12 @@ data class KotlinProjectStructureMetadata(
}
internal fun buildKotlinProjectStructureMetadata(project: Project): KotlinProjectStructureMetadata? {
val topLevelExtensionOrNull = project.topLevelExtensionOrNull
if (topLevelExtensionOrNull is KotlinPm20ProjectExtension) {
// FIXME: get module by ID, don't just take the main module
return buildProjectStructureMetadata(topLevelExtensionOrNull.main)
}
val sourceSetsWithMetadataCompilations =
project.multiplatformExtensionOrNull?.targets?.getByName(KotlinMultiplatformPlugin.METADATA_TARGET_NAME)?.compilations?.associate {
it.defaultSourceSet to it
@@ -142,6 +184,37 @@ internal fun buildKotlinProjectStructureMetadata(project: Project): KotlinProjec
)
}
internal fun buildProjectStructureMetadata(module: KotlinGradleModule): KotlinProjectStructureMetadata {
val kotlinVariantToGradleVariantNames = module.variants.associate { it.name to it.gradleVariantNames }
fun <T> expandVariantKeys(map: Map<String, T>) =
map.entries.flatMap { (key, value) ->
kotlinVariantToGradleVariantNames[key].orEmpty().plus(key).map { it to value }
}.toMap()
val kotlinFragmentsPerKotlinVariant =
module.variants.associate { variant -> variant.name to variant.refinesClosure.map { it.name }.toSet() }
val fragmentRefinesRelation =
module.fragments.associate { it.name to it.directRefinesDependencies.map { it.name }.toSet() }
// FIXME: support native implementation-as-api-dependencies
val fragmentDependencies =
module.fragments.associate { fragment ->
fragment.name to fragment.declaredModuleDependencies.map {
ModuleIds.lossyFromModuleIdentifier(module.project, it.moduleIdentifier)
}.toSet()
}
return KotlinProjectStructureMetadata(
expandVariantKeys(kotlinFragmentsPerKotlinVariant),
fragmentRefinesRelation,
module.fragments.associate { it.name to SourceSetMetadataLayout.KLIB },
fragmentDependencies,
emptySet(),
isPublishedAsRoot = true
)
}
internal fun <Serializer> KotlinProjectStructureMetadata.serialize(
serializer: Serializer,
node: Serializer.(name: String, Serializer.() -> Unit) -> Unit,
@@ -84,9 +84,13 @@ abstract class KotlinSoftwareComponent(
return _usages
}
val sourcesArtifacts: Set<PublishArtifact> by lazy {
val sourcesJarTask = sourcesJarTask(project, lazy { project.kotlinExtension.sourceSets.toSet() }, null, name.toLowerCase())
val sourcesJarTask = sourcesJarTask(
project,
lazy { project.kotlinExtension.sourceSets.associate { it.name to it.kotlin } },
null,
name.toLowerCase()
)
val sourcesJarArtifact = project.artifacts.add(Dependency.ARCHIVES_CONFIGURATION, sourcesJarTask) { sourcesJarArtifact ->
sourcesJarArtifact.classifier = "sources"
}
@@ -10,8 +10,18 @@ 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.gradle.api.publish.maven.MavenPublication
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull
import org.jetbrains.kotlin.gradle.dsl.topLevelExtension
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinPm20ProjectExtension
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.currentBuildId
import org.jetbrains.kotlin.gradle.utils.getValue
import org.jetbrains.kotlin.project.model.KotlinModuleIdentifier
import org.jetbrains.kotlin.project.model.LocalModuleIdentifier
import org.jetbrains.kotlin.project.model.MavenModuleIdentifier
import java.lang.IllegalArgumentException
internal object ModuleIds {
fun fromDependency(dependency: Dependency) = when (dependency) {
@@ -60,4 +70,57 @@ internal object ModuleIds {
private fun idOfRootModuleByProjectPath(thisProject: Project, projectPath: String): ModuleDependencyIdentifier =
idOfRootModule(thisProject.project(projectPath))
// FIXME use capabilities to point to auxiliary modules
fun lossyFromModuleIdentifier(thisProject: Project, moduleIdentifier: KotlinModuleIdentifier): ModuleDependencyIdentifier {
when (moduleIdentifier) {
is LocalModuleIdentifier -> {
check(moduleIdentifier.buildId == thisProject.currentBuildId().name)
val dependencyProject = thisProject.project(moduleIdentifier.projectId)
val getRootPublication: () -> MavenPublication? = when (val topLevelExtension = dependencyProject.topLevelExtension) {
is KotlinMultiplatformExtension -> {
{ topLevelExtension.rootSoftwareComponent.publicationDelegate }
}
is KotlinPm20ProjectExtension -> {
{ topLevelExtension.rootPublication }
}
else -> error("unexpected top-level extension $topLevelExtension")
}
val coordinatesProvider = MultiplatformRootPublicationCoordinatesProvider(dependencyProject, getRootPublication)
return ChangingModuleDependencyIdentifier({ coordinatesProvider.group }, { coordinatesProvider.name })
}
is MavenModuleIdentifier -> {
return ModuleDependencyIdentifier(moduleIdentifier.group, moduleIdentifier.name)
}
else -> error("unexpected module identifier $moduleIdentifier")
}
}
}
interface PublishedModuleCoordinatesProvider {
val group: String
val name: String
val version: String
val capabilities: Iterable<String>
}
class MultiplatformRootPublicationCoordinatesProvider(
project: Project,
val getPublication: () -> MavenPublication?
) : PublishedModuleCoordinatesProvider {
override val group: String by project.provider {
getPublication()?.groupId ?: project.group.toString()
}
override val name: String by project.provider {
getPublication()?.artifactId ?: project.group.toString()
}
override val version: String by project.provider {
getPublication()?.artifactId ?: project.version.toString()
}
override val capabilities: Iterable<String>
get() = emptyList()
}
@@ -123,7 +123,7 @@ internal class SourceSetVisibilityProvider(
}
}
private fun kotlinVariantNameFromPublishedVariantName(resolvedToVariantName: String): String =
internal fun kotlinVariantNameFromPublishedVariantName(resolvedToVariantName: String): String =
originalVariantNameFromPublished(resolvedToVariantName) ?: resolvedToVariantName
private fun Project.resolvableConfigurationFromCompilationByScope(
@@ -11,10 +11,12 @@ import org.gradle.api.tasks.*
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.TransformKotlinGranularMetadataForFragment
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope.*
import org.jetbrains.kotlin.gradle.plugin.sources.withAllDependsOnSourceSets
import org.jetbrains.kotlin.gradle.targets.metadata.ALL_COMPILE_METADATA_CONFIGURATION_NAME
import org.jetbrains.kotlin.gradle.targets.metadata.KotlinMetadataTargetConfigurator
import org.jetbrains.kotlin.gradle.targets.metadata.ResolvedMetadataFilesProvider
import org.jetbrains.kotlin.gradle.targets.metadata.dependsOnClosureWithInterCompilationDependencies
import org.jetbrains.kotlin.gradle.utils.getValue
import java.io.File
@@ -100,9 +102,9 @@ open class TransformKotlinGranularMetadata
.associateWith { it.getExtractableMetadataFiles(outputsDir) }
@get:Internal
internal val filesByResolution: Map<out MetadataDependencyResolution, FileCollection>
internal val filesByResolution: Map<MetadataDependencyResolution, FileCollection>
get() = extractableFilesByResolution.mapValues { (_, value) ->
project.files(value.getMetadataFilesPerSourceSet(false).values)
project.files(value.getMetadataFilesPerSourceSet(false).values).builtBy(this)
}
private val extractableFiles by project.provider { extractableFilesByResolution.values }
@@ -117,3 +119,11 @@ open class TransformKotlinGranularMetadata
extractableFiles.forEach { it.getMetadataFilesPerSourceSet(doProcessFiles = true) }
}
}
internal class SourceSetResolvedMetadataProvider(
taskProvider: TaskProvider<out TransformKotlinGranularMetadata>
) : ResolvedMetadataFilesProvider {
override val buildDependencies: Iterable<TaskProvider<*>> = listOf(taskProvider)
override val metadataResolutions: Iterable<MetadataDependencyResolution> by taskProvider.map { it.metadataDependencyResolutions }
override val metadataFilesByResolution: Map<MetadataDependencyResolution, FileCollection> by taskProvider.map { it.filesByResolution }
}
@@ -236,6 +236,12 @@ abstract class AbstractKotlinCompilation<T : KotlinCommonOptions>(
} else files()
}
override val friendPaths: Iterable<FileCollection>
get() = mutableListOf<FileCollection>().also { allCollections ->
associateWithTransitiveClosure.forEach { allCollections.add(it.output.classesDirs) }
allCollections.add(friendArtifacts)
}
override val moduleName: String
get() = KotlinCompilationsModuleGroups.getModuleLeaderCompilation(this).takeIf { it != this }?.ownModuleName() ?: ownModuleName
@@ -10,22 +10,33 @@ import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.result.ResolvedComponentResult
import org.gradle.api.artifacts.result.ResolvedDependencyResult
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.dsl.topLevelExtension
import org.jetbrains.kotlin.gradle.plugin.mpp.resolvableMetadataConfiguration
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope
import org.jetbrains.kotlin.project.model.*
class GradleKotlinDependencyGraphResolver(
private val project: Project,
private val moduleResolver: ModuleDependencyResolver,
private val fragmentsResolver: ModuleFragmentsResolver
) : KotlinDependencyGraphResolver {
internal fun resolvableMetadataConfiguration(
module: KotlinGradleModule
) = module.project.configurations.getByName(module.resolvableMetadataConfigurationName)
private val configurationToResolve: Configuration
get() = resolvableMetadataConfiguration(
internal fun configurationToResolveMetadataDependencies(project: Project, requestingModule: KotlinModule): Configuration =
when (project.topLevelExtension) {
is KotlinPm20ProjectExtension -> resolvableMetadataConfiguration(requestingModule as KotlinGradleModule)
else -> resolvableMetadataConfiguration(
project,
project.kotlinExtension.sourceSets, // take dependencies from all source sets; TODO introduce consistency scopes?
KotlinDependencyScope.compileScopes
)
}
class GradleKotlinDependencyGraphResolver(
private val project: Project,
private val moduleResolver: ModuleDependencyResolver
) : KotlinDependencyGraphResolver {
private fun configurationToResolve(requestingModule: KotlinModule): Configuration =
configurationToResolveMetadataDependencies(project, requestingModule)
override fun resolveDependencyGraph(requestingModule: KotlinModule): DependencyGraphResolution {
if (!requestingModule.representsProject(project))
@@ -40,11 +51,17 @@ class GradleKotlinDependencyGraphResolver(
return nodeByModuleId.getOrPut(id) {
val module = if (isRoot)
requestingModule
else moduleResolver.resolveDependency(component.toModuleDependency())
else moduleResolver.resolveDependency(requestingModule, component.toModuleDependency())
.takeIf { component !in excludeLegacyMetadataModulesFromResult }
?: buildStubModule(component, component.variants.singleOrNull()?.displayName ?: "default")
?: buildSyntheticModule(component, component.variants.singleOrNull()?.displayName ?: "default")
val resolvedComponentDependencies = component.dependencies
val componentContainingTransitiveDependencies =
(module as? ExternalImportedKotlinModule)
?.takeIf { it.hasLegacyMetadataModule }
?.let { (component.dependencies.singleOrNull() as? ResolvedDependencyResult)?.selected }
?: component
val resolvedComponentDependencies = componentContainingTransitiveDependencies.dependencies
.filterIsInstance<ResolvedDependencyResult>()
.flatMap { dependency -> dependency.requested.toModuleIdentifiers().map { id -> id to dependency.selected } }
.toMap()
@@ -61,7 +78,7 @@ class GradleKotlinDependencyGraphResolver(
return DependencyGraphResolution.DependencyGraph(
requestingModule,
nodeFromComponent(configurationToResolve.incoming.resolutionResult.root, isRoot = true)
nodeFromComponent(configurationToResolve(requestingModule).incoming.resolutionResult.root, isRoot = true)
)
}
}
@@ -13,8 +13,7 @@ import org.gradle.api.artifacts.result.ResolvedDependencyResult
import org.gradle.api.capabilities.Capability
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.mpp.ProjectStructureMetadataModuleBuilder
import org.jetbrains.kotlin.gradle.plugin.mpp.GradleProjectModuleBuilder
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.mpp.getProjectStructureMetadata
import org.jetbrains.kotlin.gradle.plugin.mpp.resolvableMetadataConfiguration
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope
@@ -27,15 +26,11 @@ class GradleModuleDependencyResolver(
private val projectModuleBuilder: GradleProjectModuleBuilder
) : ModuleDependencyResolver {
private val configurationToResolve: Configuration
get() = resolvableMetadataConfiguration(
project,
project.kotlinExtension.sourceSets, // take dependencies from all source sets; TODO introduce consistency scopes?
listOf(KotlinDependencyScope.API_SCOPE, KotlinDependencyScope.IMPLEMENTATION_SCOPE, KotlinDependencyScope.COMPILE_ONLY_SCOPE)
)
private fun configurationToResolve(requestingModule: KotlinModule): Configuration =
configurationToResolveMetadataDependencies(project, requestingModule)
override fun resolveDependency(moduleDependency: KotlinModuleDependency): KotlinModule? {
val allComponents = configurationToResolve.incoming.resolutionResult.allComponents
override fun resolveDependency(requestingModule: KotlinModule, moduleDependency: KotlinModuleDependency): KotlinModule? {
val allComponents = configurationToResolve(requestingModule).incoming.resolutionResult.allComponents
// TODO: optimize O(n) search, store the resolved components with dependency keys?
val component = allComponents.find { it.id.matchesModuleDependency(moduleDependency) }
val id = component?.id
@@ -48,8 +43,9 @@ class GradleModuleDependencyResolver(
projectModuleBuilder.buildModulesFromProject(project.project(id.projectPath))
.find { it.moduleIdentifier.moduleClassifier == classifier }
id is ModuleComponentIdentifier -> {
val metadata = getProjectStructureMetadata(project, component, configurationToResolve) ?: return null
projectStructureMetadataModuleBuilder.getModule(id.toModuleIdentifier(classifier), metadata)
val metadata = getProjectStructureMetadata(project, component, configurationToResolve(requestingModule)) ?: return null
val result = projectStructureMetadataModuleBuilder.getModule(component, metadata)
result
}
else -> null
}
@@ -110,9 +106,9 @@ class FragmentDependenciesDiscovery(
val component = resolvedComponentQueue.removeFirst()
visited.add(component)
val module = moduleResolver.resolveDependency(component.toModuleDependency())
val module = moduleResolver.resolveDependency(fragment.containingModule, component.toModuleDependency())
.takeIf { component !in excludeLegacyMetadataModulesFromResult }
?: buildStubModule(component, component.variants.singleOrNull()?.displayName ?: "default")
?: buildSyntheticModule(component, component.variants.singleOrNull()?.displayName ?: "default")
val transitiveDepsToVisit = when (val fragmentsResolution = fragmentsResolver.getChosenFragments(fragment, module)) {
is FragmentResolution.ChosenFragments ->
@@ -126,9 +122,7 @@ class FragmentDependenciesDiscovery(
// With legacy publication scheme, the root MPP module may have a single 'dependency' (available-at) to the metadata module
val isMetadataModulePublishedSeparately =
getProjectStructureMetadata(project, component, configurationToResolve)
?.let { !it.isPublishedAsRoot }
?: false
(module is ExternalImportedKotlinModule) && module.hasLegacyMetadataModule
val singleDependencyComponentOrNull = (component.dependencies.singleOrNull() as? ResolvedDependencyResult)?.selected
if (isMetadataModulePublishedSeparately && singleDependencyComponentOrNull != null) {
newTransitiveDeps.add(singleDependencyComponentOrNull)
@@ -144,9 +138,9 @@ class FragmentDependenciesDiscovery(
// refactor extract to a separate class/interface
// TODO think about multi-variant stub modules for non-Kotlin modules which got more than one chosen variant
internal fun buildStubModule(resolvedComponentResult: ResolvedComponentResult, singleVariantName: String): KotlinModule {
internal fun buildSyntheticModule(resolvedComponentResult: ResolvedComponentResult, singleVariantName: String): ExternalSyntheticKotlinModule {
val moduleDependency = resolvedComponentResult.toModuleDependency()
return BasicKotlinModule(moduleDependency.moduleIdentifier).apply {
return ExternalSyntheticKotlinModule(BasicKotlinModule(moduleDependency.moduleIdentifier).apply {
BasicKotlinModuleVariant(this@apply, singleVariantName).apply {
fragments.add(this)
this.declaredModuleDependencies.addAll(
@@ -155,7 +149,20 @@ internal fun buildStubModule(resolvedComponentResult: ResolvedComponentResult, s
.map { it.selected.toModuleDependency() }
)
}
}
})
}
internal class ExternalSyntheticKotlinModule(private val moduleData: BasicKotlinModule) : KotlinModule by moduleData {
override fun toString(): String = "synthetic $moduleData"
}
internal class ExternalImportedKotlinModule(
private val moduleData: BasicKotlinModule,
val projectStructureMetadata: KotlinProjectStructureMetadata
) : KotlinModule by moduleData {
val hasLegacyMetadataModule = !projectStructureMetadata.isPublishedAsRoot
override fun toString(): String = "imported $moduleData"
}
class VariantDependencyDiscovery(
@@ -0,0 +1,131 @@
/*
* Copyright 2010-2021 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.pm20
import org.gradle.api.Project
import org.gradle.api.artifacts.result.ResolvedDependencyResult
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.mpp.ChooseVisibleSourceSetsImpl
import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution
import org.jetbrains.kotlin.gradle.plugin.mpp.getMetadataExtractor
import org.jetbrains.kotlin.gradle.plugin.mpp.getProjectStructureMetadata
import org.jetbrains.kotlin.project.model.*
import org.jetbrains.kotlin.utils.addToStdlib.flattenTo
import java.util.ArrayDeque
internal class FragmentGranularMetadataResolver(
val requestingFragment: KotlinGradleFragment,
val refinesParentResolvers: Lazy<Iterable<FragmentGranularMetadataResolver>>
) {
val resolutions: Iterable<MetadataDependencyResolution> by lazy {
doResolveMetadataDependencies()
}
private val project: Project
get() = requestingFragment.containingModule.project
private val parentResultsByModuleIdentifier: Map<KotlinModuleIdentifier, List<MetadataDependencyResolution>> by lazy {
refinesParentResolvers.value.flatMap { it.resolutions }.groupBy { it.dependency.toModuleIdentifier() }
}
private val metadataModuleBuilder = ProjectStructureMetadataModuleBuilder()
private val projectModuleBuilder = GradleProjectModuleBuilder(true)
private val moduleResolver = GradleModuleDependencyResolver(project, metadataModuleBuilder, projectModuleBuilder)
private val variantResolver = GradleModuleVariantResolver(project)
private val fragmentResolver = DefaultModuleFragmentsResolver(variantResolver)
private val dependencyGraphResolver = GradleKotlinDependencyGraphResolver(project, moduleResolver)
private fun doResolveMetadataDependencies(): Iterable<MetadataDependencyResolution> {
val configurationToResolve = configurationToResolveMetadataDependencies(project, requestingFragment.containingModule)
val resolvedComponentsByModuleId =
configurationToResolve.incoming.resolutionResult.allComponents.associateBy { it.toModuleIdentifier() }
val resolvedDependenciesByModuleId =
configurationToResolve.incoming.resolutionResult.allDependencies.filterIsInstance<ResolvedDependencyResult>()
.flatMap { dependency -> dependency.requested.toModuleIdentifiers().map { id -> id to dependency } }.toMap()
val dependencyGraph = dependencyGraphResolver.resolveDependencyGraph(requestingFragment.containingModule)
if (dependencyGraph is DependencyGraphResolution.Unknown)
error("unexpected failure in dependency graph resolution for $requestingFragment in $project")
dependencyGraph as DependencyGraphResolution.DependencyGraph
val fragmentsToInclude = requestingFragment.refinesClosure
val requestedDependencies = dependencyGraph.root.dependenciesByFragment.filterKeys { it in fragmentsToInclude }.values.flatten()
val visited = mutableSetOf<DependencyGraphNode>()
val fragmentResolutionQueue = ArrayDeque<DependencyGraphNode>().apply {
addAll(requestedDependencies)
}
val results = mutableSetOf<MetadataDependencyResolution>()
while (fragmentResolutionQueue.isNotEmpty()) {
val dependencyNode = fragmentResolutionQueue.removeFirst()
visited.add(dependencyNode)
val dependencyModule = dependencyNode.module
val fragmentVisibility = fragmentResolver.getChosenFragments(requestingFragment, dependencyModule)
val visibleFragments = (fragmentVisibility as? FragmentResolution.ChosenFragments)?.visibleFragments?.toList().orEmpty()
val variantResolutions =
(fragmentVisibility as? FragmentResolution.ChosenFragments)?.variantResolutions?.associateBy { it.requestingVariant }
val visibleTransitiveDependencies =
dependencyNode.dependenciesByFragment.filterKeys { it in visibleFragments }.values.flattenTo(mutableSetOf())
//FIXME host-specific fragments
fragmentResolutionQueue.addAll(visibleTransitiveDependencies.filter { it !in visited })
val resolvedComponentResult = resolvedComponentsByModuleId.getValue(dependencyModule.moduleIdentifier)
val isResolvedAsProject = resolvedComponentResult.toProjectOrNull(project)
val result = when (dependencyModule) {
is ExternalSyntheticKotlinModule -> {
MetadataDependencyResolution.KeepOriginalDependency(resolvedComponentResult, isResolvedAsProject)
}
else -> run {
val projectStructureMetadata = (dependencyModule as? ExternalImportedKotlinModule)?.projectStructureMetadata
?: checkNotNull(getProjectStructureMetadata(project, resolvedComponentResult, configurationToResolve))
// FIXME host-specific metadata!!!
val metadataSourceComponent = when {
dependencyModule is ExternalImportedKotlinModule && dependencyModule.hasLegacyMetadataModule ->
resolvedComponentResult.dependencies.filterIsInstance<ResolvedDependencyResult>().singleOrNull()?.selected
?: resolvedComponentResult
else -> resolvedComponentResult
}
val parentResolutionsForDependency =
parentResultsByModuleIdentifier[metadataSourceComponent.toModuleIdentifier()].orEmpty()
val fragmentsVisibleByParents =
parentResolutionsForDependency.filterIsInstance<ChooseVisibleSourceSetsImpl>()
.flatMapTo(mutableSetOf()) { it.allVisibleSourceSetNames }
val visibleFragmentNames = visibleFragments.map { it.fragmentName }.toSet()
ChooseVisibleSourceSetsImpl(
metadataSourceComponent,
isResolvedAsProject,
projectStructureMetadata,
visibleFragmentNames,
visibleFragmentNames.minus(fragmentsVisibleByParents),
visibleTransitiveDependencies.map { resolvedDependenciesByModuleId.getValue(it.module.moduleIdentifier) }.toSet(),
checkNotNull(getMetadataExtractor(project, resolvedComponentResult, configurationToResolve, true))
)
}
}
results.add(result)
fragmentResolutionQueue.addAll(visibleTransitiveDependencies.filterNot(visited::contains))
}
val resultSourceComponents = results.mapTo(mutableSetOf()) { it.dependency }
resolvedComponentsByModuleId.values.minus(resultSourceComponents).forEach {
results.add(MetadataDependencyResolution.ExcludeAsUnrequested(it, it.toProjectOrNull(project)))
}
return results
}
}
@@ -5,16 +5,35 @@
package org.jetbrains.kotlin.gradle.plugin.mpp.pm20
import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.NamedDomainObjectProvider
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.*
import org.gradle.api.attributes.Usage
import org.gradle.api.plugins.JavaBasePlugin
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.tasks.TaskProvider
import org.gradle.jvm.tasks.Jar
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformCommonOptions
import org.jetbrains.kotlin.gradle.dsl.KotlinTopLevelExtension
import org.jetbrains.kotlin.gradle.dsl.pm20Extension
import org.jetbrains.kotlin.gradle.internal.customizeKotlinDependencies
import org.jetbrains.kotlin.gradle.plugin.KotlinCommonSourceSetProcessor
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.mpp.MULTIPLATFORM_PROJECT_METADATA_JSON_FILE_NAME
import org.jetbrains.kotlin.gradle.plugin.mpp.addSourcesToKotlinCompileTask
import org.jetbrains.kotlin.gradle.plugin.mpp.buildKotlinProjectStructureMetadata
import org.jetbrains.kotlin.gradle.plugin.usageByName
import org.jetbrains.kotlin.gradle.targets.metadata.createGenerateProjectStructureMetadataTask
import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider
import org.jetbrains.kotlin.gradle.tasks.registerTask
import org.jetbrains.kotlin.gradle.tasks.withType
import org.jetbrains.kotlin.gradle.utils.addExtendsFromRelation
import org.jetbrains.kotlin.gradle.utils.checkGradleCompatibility
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import org.jetbrains.kotlin.project.model.KotlinModuleFragment
import org.jetbrains.kotlin.project.model.KotlinModuleIdentifier
import java.util.concurrent.Callable
abstract class KotlinPm20GradlePlugin : Plugin<Project> {
override fun apply(project: Project) {
@@ -43,21 +62,17 @@ abstract class KotlinPm20GradlePlugin : Plugin<Project> {
modules.create(KotlinGradleModule.TEST_MODULE_NAME)
main { makePublic() }
}
setupMetadataCompilationAndDependencyResolution(project)
}
private fun setupMetadataCompilation() {
// TODO dependency transformations in tasks
// TODO classpath configurations
// TODO compilation data -> tasks
// TODO project structure metadata -> metadata artifact
// TODO task outputs -> metadata artifact
// TODO export the metadata artifact in consumable configurations
private fun setupMetadataCompilationAndDependencyResolution(project: Project) {
project.pm20Extension.modules.all { module ->
configureMetadataResolutionAndBuild(module)
}
}
}
open class KotlinPm20ProjectExtension : KotlinTopLevelExtension() {
internal lateinit var project: Project
open class KotlinPm20ProjectExtension(project: Project) : KotlinTopLevelExtension(project) {
val modules: NamedDomainObjectContainer<KotlinGradleModule> by lazy {
project.objects.domainObjectContainer(
KotlinGradleModule::class.java,
@@ -79,6 +94,11 @@ open class KotlinPm20ProjectExtension : KotlinTopLevelExtension() {
fun main(configure: KotlinGradleModule.() -> Unit = { }) = main.apply(configure)
fun test(configure: KotlinGradleModule.() -> Unit = { }) = test.apply(configure)
internal val metadataCompilationRegistryByModuleId: MutableMap<KotlinModuleIdentifier, MetadataCompilationRegistry> =
mutableMapOf()
internal var rootPublication: MavenPublication? = null
}
val KotlinGradleModule.jvm: KotlinJvmVariant
@@ -9,9 +9,11 @@ import org.gradle.api.Project
import org.gradle.api.file.FileCollection
import org.gradle.api.file.SourceDirectorySet
import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilationOutput
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.LanguageSettingsBuilder
import org.jetbrains.kotlin.gradle.plugin.mpp.isMain
interface KotlinCompilationData<T : KotlinCommonOptions> {
val project: Project
@@ -34,4 +36,11 @@ interface KotlinCompilationData<T : KotlinCommonOptions> {
val ownModuleName: String
val kotlinOptions: T
val friendPaths: Iterable<FileCollection>
}
fun KotlinCompilationData<*>.isMain(): Boolean = when (this) {
is KotlinCompilation<*> -> isMain()
else -> compilationPurpose == KotlinGradleModule.MAIN_MODULE_NAME
}
@@ -0,0 +1,115 @@
/*
* Copyright 2010-2021 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.pm20
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.file.FileCollection
import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.plugins.BasePluginConvention
import org.gradle.api.tasks.TaskProvider
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformCommonOptions
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformCommonOptionsImpl
import org.jetbrains.kotlin.gradle.dsl.pm20Extension
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilationOutput
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.LanguageSettingsBuilder
import org.jetbrains.kotlin.gradle.plugin.mpp.DefaultKotlinCompilationOutput
import org.jetbrains.kotlin.gradle.plugin.mpp.filterModuleName
import org.jetbrains.kotlin.gradle.plugin.sources.DefaultLanguageSettingsBuilder
import org.jetbrains.kotlin.gradle.targets.metadata.ResolvedMetadataFilesProvider
import org.jetbrains.kotlin.gradle.targets.metadata.createTransformedMetadataClasspath
import org.jetbrains.kotlin.gradle.utils.getValue
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
internal class KotlinFragmentMetadataCompilationData(
override val project: Project,
val fragment: KotlinGradleFragment,
private val module: KotlinGradleModule,
private val compileAllTask: TaskProvider<DefaultTask>,
val metadataCompilationRegistry: MetadataCompilationRegistry,
private val resolvedMetadataFiles: Lazy<Iterable<ResolvedMetadataFilesProvider>>
) : KotlinCompilationData<KotlinMultiplatformCommonOptions> {
override val owner
get() = project.pm20Extension
override val compilationPurpose: String
get() = fragment.fragmentName
override val compilationClassifier: String
get() = lowerCamelCaseName(module.name, "metadata")
override val kotlinSourceDirectoriesByFragmentName: Map<String, SourceDirectorySet>
get() = mapOf(fragment.fragmentName to fragment.kotlinSourceRoots)
override val compileKotlinTaskName: String
get() = lowerCamelCaseName("compile", fragment.disambiguateName(""), "KotlinMetadata")
override val compileAllTaskName: String
get() = compileAllTask.name
override val compileDependencyFiles: FileCollection by project.provider {
createTransformedMetadataClasspath(
project,
resolvableMetadataConfiguration(fragment.containingModule),
lazy { fragment.refinesClosure.minus(fragment).map { metadataCompilationRegistry.byFragment(it).output.classesDirs } },
resolvedMetadataFiles
)
}
override val output: KotlinCompilationOutput = DefaultKotlinCompilationOutput(
project,
project.provider { project.buildDir.resolve("processedResources/${fragment.disambiguateName("metadata")}") }
)
override val languageSettings: LanguageSettingsBuilder = DefaultLanguageSettingsBuilder() // FIXME apply settings
override val platformType: KotlinPlatformType
get() = KotlinPlatformType.common
override val moduleName: String
get() { // FIXME deduplicate with ownModuleName
val baseName = project.convention.findPlugin(BasePluginConvention::class.java)?.archivesBaseName
?: project.name
val suffix = if (module.moduleClassifier == null) "" else "_${module.moduleClassifier}"
return filterModuleName("$baseName$suffix")
}
override val ownModuleName: String
get() = moduleName
override val kotlinOptions: KotlinMultiplatformCommonOptions = // FIXME expose for configuration
KotlinMultiplatformCommonOptionsImpl()
override val friendPaths: Iterable<FileCollection>
get() = metadataCompilationRegistry.run {
fragment.refinesClosure.minus(fragment)
.map {
metadataCompilationRegistry.byFragment(it).output.classesDirs
}
}
}
internal class MetadataCompilationRegistry {
private val compilationDataPerFragment = mutableMapOf<KotlinGradleFragment, KotlinFragmentMetadataCompilationData>()
fun register(fragment: KotlinGradleFragment, compilationData: KotlinFragmentMetadataCompilationData) {
compilationDataPerFragment[fragment]?.let { error("compilation data for fragment $fragment already registered") }
compilationDataPerFragment[fragment] = compilationData
withAllCallbacks.forEach { it.invoke(compilationData) }
}
fun byFragment(fragment: KotlinGradleFragment): KotlinFragmentMetadataCompilationData =
compilationDataPerFragment.getValue(fragment)
private val withAllCallbacks = mutableListOf<(KotlinFragmentMetadataCompilationData) -> Unit>()
fun withAll(action: (KotlinFragmentMetadataCompilationData) -> Unit) {
compilationDataPerFragment.forEach { action(it.value) }
withAllCallbacks += action
}
}
@@ -16,11 +16,13 @@ import org.jetbrains.kotlin.gradle.plugin.HasKotlinDependencies
import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler
import org.jetbrains.kotlin.gradle.plugin.LanguageSettingsBuilder
import org.jetbrains.kotlin.gradle.plugin.mpp.DefaultKotlinDependencyHandler
import org.jetbrains.kotlin.gradle.plugin.mpp.toModuleDependency
import org.jetbrains.kotlin.gradle.plugin.sources.DefaultLanguageSettingsBuilder
import org.jetbrains.kotlin.gradle.utils.addExtendsFromRelation
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import org.jetbrains.kotlin.project.model.KotlinModuleDependency
import org.jetbrains.kotlin.project.model.KotlinModuleFragment
import org.jetbrains.kotlin.project.model.refinesClosure
import javax.inject.Inject
open class KotlinGradleFragment @Inject constructor(
@@ -31,6 +33,8 @@ open class KotlinGradleFragment @Inject constructor(
override fun getName(): String = fragmentName
// TODO pull up to KotlinModuleFragment
// FIXME apply to compilation
// FIXME check for consistency
val languageSettings: LanguageSettingsBuilder = DefaultLanguageSettingsBuilder()
protected val project: Project
@@ -80,7 +84,7 @@ open class KotlinGradleFragment @Inject constructor(
get() = _directRefinesDependencies.map { it.get() }
override val declaredModuleDependencies: Iterable<KotlinModuleDependency>
get() = TODO("Not yet implemented")
get() = project.configurations.getByName(apiConfigurationName).allDependencies.map { it.toModuleDependency(project) } // FIXME impl
override val kotlinSourceRoots: SourceDirectorySet by lazy {
project.objects.sourceDirectorySet(
@@ -104,7 +108,12 @@ open class KotlinGradleFragment @Inject constructor(
companion object {
const val COMMON_FRAGMENT_NAME = "common"
}
override fun toString(): String = "fragment $fragmentName in $containingModule"
}
internal fun KotlinModuleFragment.disambiguateName(simpleName: String) =
lowerCamelCaseName(fragmentName, containingModule.moduleIdentifier.moduleClassifier ?: KotlinGradleModule.MAIN_MODULE_NAME, simpleName)
val KotlinGradleFragment.refinesClosure: Set<KotlinGradleFragment>
get() = (this as KotlinModuleFragment).refinesClosure.map { it as KotlinGradleFragment }.toSet()
@@ -12,9 +12,8 @@ import org.gradle.api.NamedDomainObjectSet
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.plugin.HasKotlinDependencies
import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler
import org.jetbrains.kotlin.project.model.KotlinModule
import org.jetbrains.kotlin.project.model.KotlinModuleIdentifier
import org.jetbrains.kotlin.project.model.LocalModuleIdentifier
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import org.jetbrains.kotlin.project.model.*
import javax.inject.Inject
open class KotlinGradleModule(
@@ -87,7 +86,15 @@ open class KotlinGradleModule(
override val runtimeOnlyConfigurationName: String
get() = common.runtimeOnlyConfigurationName
override fun toString(): String = "$moduleIdentifier (Gradle)"
}
internal val KotlinGradleModule.resolvableMetadataConfigurationName: String
get() = lowerCamelCaseName(name, "DependenciesMetadata")
internal val KotlinGradleModule.isMain
get() = moduleIdentifier.moduleClassifier == null
fun KotlinGradleModule.variantsContainingFragment(fragment: KotlinModuleFragment): Iterable<KotlinGradleVariant> =
variants.filter { fragment in it.refinesClosure }
@@ -20,7 +20,7 @@ open class KotlinGradleModuleFactory(private val project: Project) : NamedDomain
}
protected open fun registerDefaultCommonFragment(module: KotlinGradleModule) {
module.fragments.create(KotlinGradleFragment.COMMON_FRAGMENT_NAME, KotlinGradleFragment::class.java)
module.fragments.register(KotlinGradleFragment.COMMON_FRAGMENT_NAME, KotlinGradleFragment::class.java)
}
protected open fun addDefaultRefinementDependencyOnCommon(module: KotlinGradleModule) {
@@ -58,6 +58,13 @@ class KotlinJvmVariantCompilationData(val variant: KotlinJvmVariant) : KotlinCom
override val platformType: KotlinPlatformType
get() = variant.platformType
override val friendPaths: Iterable<FileCollection>
// TODO for now, all output classes of the module are considered friends, even those not on the classpath
get() {
// FIXME support compiling against the JARs
return variant.containingModule.project.pm20Extension.modules.flatMap { it.variants.map { it.compilationOutputs.classesDirs } }
}
override val moduleName: String
get() = // TODO accurate module names that don't rely on all variants having a main counterpart
variant.containingModule.project.pm20Extension.modules
@@ -0,0 +1,97 @@
/*
* Copyright 2010-2021 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.pm20
import org.gradle.api.DefaultTask
import org.gradle.api.file.FileCollection
import org.gradle.api.model.ObjectFactory
import org.gradle.api.tasks.*
import org.jetbrains.kotlin.gradle.plugin.mpp.ExtractableMetadataFiles
import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution
import org.jetbrains.kotlin.gradle.targets.metadata.ResolvedMetadataFilesProvider
import org.jetbrains.kotlin.gradle.utils.getValue
import java.io.File
import javax.inject.Inject
internal open class TransformKotlinGranularMetadataForFragment
@Inject constructor(
@get:Internal
@field:Transient
val fragment: KotlinGradleFragment,
//FIXME annotations
private val transformation: FragmentGranularMetadataResolver
) : DefaultTask() {
@get:OutputDirectory
val outputsDir: File by project.provider {
project.buildDir.resolve("kotlinFragmentDependencyMetadata").resolve(fragment.disambiguateName(""))
}
@Suppress("unused") // Gradle input
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
internal val allSourceSetsMetadataConfiguration: FileCollection by lazy {
project.files(resolvableMetadataConfiguration(fragment.containingModule))
}
@Suppress("unused") // Gradle input
@get:Input
internal val inputFragmentsAndVariants: Map<String, Iterable<String>> by project.provider {
val participatingFragments = fragment.refinesClosure
participatingFragments.associateWith { it.containingModule.variantsContainingFragment(it) }
.entries.associate { (fragment, variants) ->
fragment.name to variants.map { it.fragmentName }.sorted()
}
}
@Suppress("unused") // Gradle input
@get:Input
internal val inputVariantDependencies: Map<String, Set<List<String?>>> by project.provider {
val participatingFragments = fragment.refinesClosure
val participatingCompilations = participatingFragments.flatMap { it.containingModule.variantsContainingFragment(it) }
participatingCompilations.associate { variant ->
variant.fragmentName to project.configurations.getByName(variant.compileDependencyConfigurationName)
.allDependencies.map { listOf(it.group, it.name, it.version) }.toSet()
}
}
@get:Internal
@delegate:Transient // exclude from Gradle instant execution state
internal val metadataDependencyResolutions: Iterable<MetadataDependencyResolution> by project.provider {
transformation.resolutions
}
private val extractableFilesByResolution: Map<out MetadataDependencyResolution, ExtractableMetadataFiles>
get() = metadataDependencyResolutions
.filterIsInstance<MetadataDependencyResolution.ChooseVisibleSourceSets>()
.associateWith { it.getExtractableMetadataFiles(outputsDir) }
@get:Internal
internal val filesByResolution: Map<MetadataDependencyResolution, FileCollection>
get() = extractableFilesByResolution.mapValues { (_, value) ->
project.files(value.getMetadataFilesPerSourceSet(false).values).builtBy(this)
}
private val extractableFiles by project.provider { extractableFilesByResolution.values }
@TaskAction
fun transformMetadata() {
if (outputsDir.isDirectory) {
outputsDir.deleteRecursively()
}
outputsDir.mkdirs()
extractableFiles.forEach { it.getMetadataFilesPerSourceSet(doProcessFiles = true) }
}
}
internal class FragmentResolvedMetadataProvider(
taskProvider: TaskProvider<out TransformKotlinGranularMetadataForFragment>
) : ResolvedMetadataFilesProvider {
override val buildDependencies: Iterable<TaskProvider<*>> = listOf(taskProvider)
override val metadataResolutions: Iterable<MetadataDependencyResolution> by taskProvider.map { it.metadataDependencyResolutions }
override val metadataFilesByResolution: Map<MetadataDependencyResolution, FileCollection> by taskProvider.map { it.filesByResolution }
}
@@ -12,7 +12,7 @@ import org.gradle.api.tasks.bundling.AbstractArchiveTask
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilationOutput
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.mpp.DefaultKotlinCompilationOutput
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.fromPlatform
import org.jetbrains.kotlin.gradle.plugin.mpp.publishedConfigurationName
import org.jetbrains.kotlin.gradle.tasks.withType
import org.jetbrains.kotlin.project.model.KotlinAttributeKey
import org.jetbrains.kotlin.project.model.KotlinModuleVariant
@@ -26,7 +26,7 @@ abstract class KotlinGradleVariant(
abstract val platformType: KotlinPlatformType
override val variantAttributes: Map<KotlinAttributeKey, String>
get() = mapOf(KotlinPlatformTypeAttribute to KotlinPlatformTypeAttribute.fromPlatform(platformType)) // TODO user attributes
get() = mapOf(KotlinPlatformTypeAttribute to kotlinPlatformTypeAttributeFromPlatform(platformType)) // TODO user attributes
// TODO generalize with KotlinCompilation?
val compileDependencyConfigurationName: String
@@ -48,8 +48,15 @@ abstract class KotlinGradleVariant(
// TODO generalize exposing outputs: what if a variant has more than one such configurations or none?
val apiElementsConfigurationName: String
get() = disambiguateName("apiElements")
open val gradleVariantNames: Set<String>
get() = listOf(apiElementsConfigurationName, publishedConfigurationName(apiElementsConfigurationName)).toSet()
override fun toString(): String = "variant $fragmentName in $containingModule"
}
private fun kotlinPlatformTypeAttributeFromPlatform(platformType: KotlinPlatformType) = platformType.name
// TODO: rewrite with the artifacts API
internal val KotlinGradleVariant.defaultSourceArtifactTaskName: String
get() = disambiguateName("sourcesJar")
@@ -71,4 +78,8 @@ abstract class KotlinGradleVariantWithRuntimeDependencies(
// TODO generalize exposing outputs: what if a variant has more than one such configurations or none?
val runtimeElementsConfigurationName: String
get() = disambiguateName("runtimeElements")
override val gradleVariantNames: Set<String>
get() = super.gradleVariantNames +
listOf(runtimeElementsConfigurationName, publishedConfigurationName(runtimeElementsConfigurationName))
}
@@ -0,0 +1,198 @@
/*
* Copyright 2010-2021 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.pm20
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.attributes.Usage
import org.gradle.api.tasks.TaskProvider
import org.gradle.jvm.tasks.Jar
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformCommonOptions
import org.jetbrains.kotlin.gradle.dsl.pm20Extension
import org.jetbrains.kotlin.gradle.plugin.KotlinCommonSourceSetProcessor
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.mpp.MULTIPLATFORM_PROJECT_METADATA_JSON_FILE_NAME
import org.jetbrains.kotlin.gradle.plugin.mpp.addSourcesToKotlinCompileTask
import org.jetbrains.kotlin.gradle.plugin.mpp.buildKotlinProjectStructureMetadata
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.disambiguateName
import org.jetbrains.kotlin.gradle.plugin.usageByName
import org.jetbrains.kotlin.gradle.targets.metadata.createGenerateProjectStructureMetadataTask
import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider
import org.jetbrains.kotlin.gradle.tasks.registerTask
import org.jetbrains.kotlin.gradle.tasks.withType
import org.jetbrains.kotlin.gradle.utils.addExtendsFromRelation
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import org.jetbrains.kotlin.project.model.KotlinModuleFragment
import java.util.concurrent.Callable
internal fun configureMetadataResolutionAndBuild(module: KotlinGradleModule) {
val project = module.project
createResolvableMetadataConfigurationForModule(module)
val metadataCompilationRegistry = MetadataCompilationRegistry()
project.pm20Extension.metadataCompilationRegistryByModuleId[module.moduleIdentifier] =
metadataCompilationRegistry
configureMetadataCompilationsAndCreateRegistry(module, metadataCompilationRegistry)
configureMetadataJarTask(module, metadataCompilationRegistry)
generateAndExportProjectStructureMetadata(module)
}
private fun generateAndExportProjectStructureMetadata(
module: KotlinGradleModule
) {
val project = module.project
val projectStructureMetadata = project.createGenerateProjectStructureMetadataTask(module.moduleClassifier)
project.tasks.withType<Jar>().named(metadataJarName(module)).configure { jar ->
jar.from(projectStructureMetadata.map { it.resultFile }) { spec ->
spec.into("META-INF")
.rename { MULTIPLATFORM_PROJECT_METADATA_JSON_FILE_NAME }
}
}
GlobalProjectStructureMetadataStorage.registerProjectStructureMetadata(project) {
checkNotNull(buildKotlinProjectStructureMetadata(project))
}
}
private fun createResolvableMetadataConfigurationForModule(module: KotlinGradleModule) {
val project = module.project
project.configurations.create(module.resolvableMetadataConfigurationName).apply {
isCanBeConsumed = false
isCanBeResolved = true
attributes.attribute(Usage.USAGE_ATTRIBUTE, project.usageByName(KotlinUsages.KOTLIN_METADATA))
module.fragments.all { fragment ->
project.addExtendsFromRelation(name, fragment.apiConfigurationName)
project.addExtendsFromRelation(name, fragment.implementationConfigurationName)
}
}
}
private fun configureMetadataCompilationsAndCreateRegistry(
module: KotlinGradleModule,
metadataCompilationRegistry: MetadataCompilationRegistry
) {
val project = module.project
val metadataResolutionByFragment = mutableMapOf<KotlinGradleFragment, FragmentGranularMetadataResolver>()
module.fragments.all { fragment ->
val transformation = FragmentGranularMetadataResolver(fragment, lazy {
fragment.refinesClosure.minus(fragment).map {
metadataResolutionByFragment.getValue(it)
}
})
metadataResolutionByFragment[fragment] = transformation
createExtractMetadataTask(project, fragment, transformation)
}
val compileAllTask = project.registerTask<DefaultTask>(lowerCamelCaseName(module.moduleClassifier, "metadataClasses"))
module.fragments.all { fragment ->
createMetadataCompilation(fragment, compileAllTask, metadataCompilationRegistry)
}
}
private fun configureMetadataJarTask(
module: KotlinGradleModule,
registry: MetadataCompilationRegistry
) {
val project = module.project
val allMetadataJar = project.registerTask<Jar>(metadataJarName(module)) { task ->
task.archiveAppendix.set("metadata")
task.from()
}
registry.withAll { compilationData ->
allMetadataJar.configure { jar ->
jar.from(compilationData.output.allOutputs) { spec ->
spec.into(compilationData.fragment.fragmentName)
}
}
}
}
private fun metadataJarName(module: KotlinGradleModule) =
lowerCamelCaseName(module.moduleClassifier, "metadataJar")
private fun createMetadataCompilation(
fragment: KotlinGradleFragment,
compileAllTask: TaskProvider<DefaultTask>,
metadataCompilationRegistry: MetadataCompilationRegistry
): KotlinCompilationData<out KotlinMultiplatformCommonOptions> {
val module = fragment.containingModule
val project = module.project
val metadataCompilationData =
KotlinFragmentMetadataCompilationData(
project,
fragment,
module,
compileAllTask,
metadataCompilationRegistry,
lazy {
fragment.refinesClosure.map {
FragmentResolvedMetadataProvider(
project.tasks.withType<TransformKotlinGranularMetadataForFragment>()
.named(transformFragmentMetadataTaskName(it))
)
}
}
)
metadataCompilationRegistry.register(fragment, metadataCompilationData)
addSourcesToKotlinCompileTask(
project,
metadataCompilationData.compileKotlinTaskName,
/*FIXME*/ emptyList(),
{ fragment.kotlinSourceRoots },
lazyOf(true)
)
KotlinCommonSourceSetProcessor(
metadataCompilationData,
KotlinTasksProvider(),
project.getKotlinPluginVersion() ?: "undefined"
).run()
return metadataCompilationData
}
private fun createExtractMetadataTask(
project: Project,
fragment: KotlinGradleFragment,
transformation: FragmentGranularMetadataResolver
) {
project.tasks.register(
transformFragmentMetadataTaskName(fragment),
TransformKotlinGranularMetadataForFragment::class.java,
fragment,
transformation
).configure { task ->
task.dependsOn(Callable {
fragment.refinesClosure.mapNotNull { refined ->
if (refined !== fragment)
project.tasks.named(transformFragmentMetadataTaskName(refined))
else null
}
})
}
}
// FIXME: use this function once more than one platform is supported
private fun disableMetadataCompilationIfNotYetSupported(
metadataCompilationData: KotlinFragmentMetadataCompilationData
) {
val fragment = metadataCompilationData.fragment
val platforms = fragment.containingModule.variantsContainingFragment(fragment).map { it.platformType }.toSet()
if (platforms != setOf(KotlinPlatformType.native) && platforms.size == 1
|| platforms == setOf(KotlinPlatformType.jvm, KotlinPlatformType.androidJvm)
) {
fragment.containingModule.project.tasks.named(metadataCompilationData.compileKotlinTaskName).configure {
it.enabled = false
}
}
}
private fun transformFragmentMetadataTaskName(fragment: KotlinModuleFragment) =
lowerCamelCaseName("resolve", fragment.disambiguateName("Metadata"))
@@ -5,36 +5,18 @@
package org.jetbrains.kotlin.gradle.plugin.mpp.pm20
import org.gradle.api.*
import org.gradle.api.file.FileCollection
import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.plugins.BasePluginConvention
import org.gradle.api.tasks.TaskProvider
import org.gradle.jvm.tasks.Jar
import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptionsImpl
import org.jetbrains.kotlin.gradle.dsl.pm20Extension
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.Kotlin2JvmSourceSetProcessor
import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
import org.jetbrains.kotlin.gradle.plugin.mpp.addSourcesToKotlinCompileTask
import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider
import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask
import org.jetbrains.kotlin.gradle.utils.dashSeparatedName
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import org.jetbrains.kotlin.project.model.*
import org.jetbrains.kotlin.project.model.refinesClosure
import org.jetbrains.kotlin.project.model.utils.variantsContainingFragment
import java.util.concurrent.Callable
//region Fragment
//endregion Fragment
//region Variant
open class KotlinJvmVariantFactory(module: KotlinGradleModule) :
AbstractKotlinGradleVariantWithRuntimeDependenciesFactory<KotlinJvmVariant>(module) {
override fun instantiateFragment(name: String) = KotlinJvmVariant(module, name)
@@ -15,13 +15,13 @@ import org.gradle.api.plugins.BasePlugin
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.bundling.Jar
import org.gradle.api.tasks.bundling.Zip
import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions
import org.jetbrains.kotlin.gradle.dsl.*
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.mpp.CompilationSourceSetUtil.compilationsBySourceSets
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinPm20ProjectExtension
import org.jetbrains.kotlin.gradle.plugin.sources.*
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService
import org.jetbrains.kotlin.gradle.targets.native.internal.*
@@ -38,7 +38,8 @@ internal const val ALL_COMPILE_METADATA_CONFIGURATION_NAME = "allSourceSetsCompi
internal const val ALL_RUNTIME_METADATA_CONFIGURATION_NAME = "allSourceSetsRuntimeDependenciesMetadata"
internal val Project.isKotlinGranularMetadataEnabled: Boolean
get() = PropertiesProvider(rootProject).enableGranularSourceSetsMetadata == true
get() = project.topLevelExtension is KotlinPm20ProjectExtension ||
PropertiesProvider(rootProject).enableGranularSourceSetsMetadata == true
internal val Project.isCompatibilityMetadataVariantEnabled: Boolean
get() = PropertiesProvider(this).enableCompatibilityMetadataVariant == true
@@ -231,7 +232,7 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) :
val generateMetadata = project.createGenerateProjectStructureMetadataTask()
allMetadataJar.configure {
it.from(generateMetadata.map { it.resultXmlFile }) { spec ->
it.from(generateMetadata.map { it.resultFile }) { spec ->
spec.into("META-INF").rename { MULTIPLATFORM_PROJECT_METADATA_JSON_FILE_NAME }
}
}
@@ -437,91 +438,28 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) :
): FileCollection {
val project = compilation.target.project
// Adjust metadata compilation to support source set hierarchies, i.e. use both the outputs of dependsOn source set compilation
// and their dependencies metadata transformed for compilation:
return project.files(
project.provider {
val sourceSet = compilation.defaultSourceSet
val sourceSet = compilation.defaultSourceSet
val transformationTaskHolders = sourceSet.withAllDependsOnSourceSets().mapNotNull { hierarchySourceSet ->
project.locateTask<TransformKotlinGranularMetadata>(transformGranularMetadataTaskName(hierarchySourceSet.name))
}
val allResolutionsByComponentId: Map<ComponentIdentifier, List<MetadataDependencyResolution>> =
mutableMapOf<ComponentIdentifier, MutableList<MetadataDependencyResolution>>().apply {
transformationTaskHolders.forEach {
val resolutions = it.get().metadataDependencyResolutions
resolutions.forEach { resolution ->
getOrPut(resolution.dependency.id) { mutableListOf() }.add(resolution)
}
}
}
val transformedFilesByResolution: Map<MetadataDependencyResolution, FileCollection> =
transformationTaskHolders.flatMap { it.get().filesByResolution.toList() }.toMap()
val dependsOnCompilationOutputs = sourceSet.resolveAllDependsOnSourceSets().mapNotNull { hierarchySourceSet ->
val dependencyCompilation = project.getMetadataCompilationForSourceSet(hierarchySourceSet)
dependencyCompilation?.output?.classesDirs
}
val artifactView = fromFiles.incoming.artifactView { view ->
view.componentFilter { id ->
allResolutionsByComponentId[id].let { resolutions ->
resolutions == null || resolutions.any { it !is MetadataDependencyResolution.ExcludeAsUnrequested }
}
}
}
mutableSetOf<Any /* File | FileCollection */>().apply {
addAll(dependsOnCompilationOutputs)
artifactView.artifacts.forEach { artifact ->
val resolutions = allResolutionsByComponentId[artifact.id.componentIdentifier]
if (resolutions == null) {
add(artifact.file)
} else {
val chooseVisibleSourceSets =
resolutions.filterIsInstance<MetadataDependencyResolution.ChooseVisibleSourceSets>()
if (chooseVisibleSourceSets.isNotEmpty()) {
// 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(artifact.file)
} // else: all dependency transformations exclude this dependency as unrequested; don't add any files
}
}
// Add a build dependency on the granular metadata transformations of the dependency source sets
add(project.files().builtBy(transformationTaskHolders))
}
val dependsOnCompilationOutputs = lazy {
sourceSet.withAllDependsOnSourceSets().mapNotNull { hierarchySourceSet ->
val dependencyCompilation = project.getMetadataCompilationForSourceSet(hierarchySourceSet)
dependencyCompilation?.output?.classesDirs.takeIf { hierarchySourceSet != sourceSet }
}
)
/*
val transformedFilesByOriginalFiles = project.provider {
transformationTaskHolders
.flatMap { it.get().filesByOriginalFiles.toList() }
.groupBy({ it.first }, valueTransform = { it.second })
}
val builtBySet = mutableSetOf<FileCollection>()
val resultFiles = project.files(Callable {
val originalFiles = fromFiles.toSet()
val filesToAdd = mutableSetOf<File>()
val filesToExclude = mutableSetOf<File>()
transformedFilesByOriginalFiles.get().forEach { (original, replacement) ->
if (original.all { it in originalFiles }) {
builtBySet.addAll(replacement)
filesToAdd += replacement.flatMap { it.files }
filesToExclude += original
}
val resolvedMetadataFilesProviders = lazy {
val transformationTaskHolders = sourceSet.withAllDependsOnSourceSets().mapNotNull { hierarchySourceSet ->
project.locateTask<TransformKotlinGranularMetadata>(transformGranularMetadataTaskName(hierarchySourceSet.name))
}
transformationTaskHolders.map { SourceSetResolvedMetadataProvider(it) }
}
originalFiles - filesToExclude + filesToAdd
})
return resultFiles.builtBy(builtBySet)
*/
return createTransformedMetadataClasspath(
project,
fromFiles,
dependsOnCompilationOutputs,
resolvedMetadataFilesProviders
)
}
private fun createCommonMainElementsConfiguration(target: KotlinMetadataTarget) {
@@ -545,11 +483,69 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) :
project.artifacts.add(name, project.tasks.getByName(target.legacyArtifactsTaskName))
}
}
}
private fun Project.createGenerateProjectStructureMetadataTask(): TaskProvider<GenerateProjectStructureMetadata> =
project.registerTask("generateProjectStructureMetadata") { task ->
task.lazyKotlinProjectStructureMetadata = lazy { checkNotNull(buildKotlinProjectStructureMetadata(project)) }
internal fun Project.createGenerateProjectStructureMetadataTask(moduleClassifier: String? = null): TaskProvider<GenerateProjectStructureMetadata> =
project.registerTask(lowerCamelCaseName("generate", moduleClassifier, "ProjectStructureMetadata")) { task ->
task.lazyKotlinProjectStructureMetadata = lazy { checkNotNull(buildKotlinProjectStructureMetadata(project)) }
}
internal interface ResolvedMetadataFilesProvider {
val buildDependencies: Iterable<TaskProvider<*>>
val metadataResolutions: Iterable<MetadataDependencyResolution>
val metadataFilesByResolution: Map<MetadataDependencyResolution, FileCollection>
}
internal fun createTransformedMetadataClasspath(
project: Project,
fromFiles: Configuration,
parentCompiledMetadataFiles: Lazy<Iterable<FileCollection>>,
metadataResolutionProviders: Lazy<Iterable<ResolvedMetadataFilesProvider>>
): FileCollection {
return project.files(
project.provider {
val allResolutionsByComponentId: Map<ComponentIdentifier, List<MetadataDependencyResolution>> =
mutableMapOf<ComponentIdentifier, MutableList<MetadataDependencyResolution>>().apply {
metadataResolutionProviders.value.forEach {
it.metadataResolutions.forEach { resolution ->
getOrPut(resolution.dependency.id) { mutableListOf() }.add(resolution)
}
}
}
val transformedFilesByResolution: Map<MetadataDependencyResolution, FileCollection> =
metadataResolutionProviders.value.flatMap { it.metadataFilesByResolution.toList() }.toMap()
val artifactView = fromFiles.incoming.artifactView { view ->
view.componentFilter { id ->
allResolutionsByComponentId[id].let { resolutions ->
resolutions == null || resolutions.any { it !is MetadataDependencyResolution.ExcludeAsUnrequested }
}
}
}
mutableSetOf<Any /* File | FileCollection */>().apply {
addAll(metadataResolutionProviders.value.map { project.files().builtBy(it.buildDependencies) })
addAll(parentCompiledMetadataFiles.value)
artifactView.artifacts.forEach { artifact ->
val resolutions = allResolutionsByComponentId[artifact.id.componentIdentifier]
if (resolutions == null) {
add(artifact.file)
} else {
val chooseVisibleSourceSets =
resolutions.filterIsInstance<MetadataDependencyResolution.ChooseVisibleSourceSets>()
if (chooseVisibleSourceSets.isNotEmpty()) {
// 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(artifact.file)
} // else: all dependency transformations exclude this dependency as unrequested; don't add any files
}
}
}
}
)
}
internal fun isSharedNativeSourceSet(project: Project, sourceSet: KotlinSourceSet): Boolean {
@@ -560,9 +556,7 @@ internal fun isSharedNativeSourceSet(project: Project, sourceSet: KotlinSourceSe
}
internal fun dependsOnClosureWithInterCompilationDependencies(project: Project, sourceSet: KotlinSourceSet): Set<KotlinSourceSet> =
sourceSet.getSourceSetHierarchy().toMutableSet().apply {
/** exclude self from the results of [getSourceSetHierarchy] */
remove(sourceSet)
sourceSet.resolveAllDependsOnSourceSets().toMutableSet().apply {
addAll(getVisibleSourceSetsFromAssociateCompilations(project, sourceSet))
}
@@ -34,6 +34,8 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinCommonCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinFragmentMetadataCompilationData
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.refinesClosure
import org.jetbrains.kotlin.gradle.utils.getValue
import org.jetbrains.kotlin.gradle.plugin.sources.resolveAllDependsOnSourceSets
import org.jetbrains.kotlin.incremental.ChangedFiles
@@ -60,7 +62,9 @@ open class KotlinCompileCommon : AbstractKotlinCompile<K2MetadataCompilerArgumen
args.moduleName = this@KotlinCompileCommon.moduleName
if ((taskData.compilation as? KotlinCommonCompilation)?.isKlibCompilation == true) {
if ((taskData.compilation as? KotlinCommonCompilation)?.isKlibCompilation == true ||
taskData.compilation is KotlinFragmentMetadataCompilationData
) {
args.expectActualLinker = true
}
@@ -97,6 +101,14 @@ open class KotlinCompileCommon : AbstractKotlinCompile<K2MetadataCompilerArgumen
val defaultKotlinSourceSet: KotlinSourceSet = compilation.defaultSourceSet
outputPathsFromMetadataCompilationsOf(defaultKotlinSourceSet.resolveAllDependsOnSourceSets())
}
is KotlinFragmentMetadataCompilationData -> {
val fragment = compilation.fragment
project.files(
fragment.refinesClosure.minus(fragment).map {
compilation.metadataCompilationRegistry.byFragment(it).output.classesDirs
}
)
}
else -> error("unexpected compilation type") // FIXME support PM20 variant
}
}
@@ -307,15 +307,7 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractKo
@get:Internal // takes part in the compiler arguments
val friendPaths: FileCollection = project.files(
project.provider {
taskData.compilation.run {
if (this !is AbstractKotlinCompilation<*>) return@run project.files() // FIXME support PM20
mutableListOf<FileCollection>().also { allCollections ->
associateWithTransitiveClosure.forEach { allCollections.add(it.output.classesDirs) }
allCollections.add(friendArtifacts)
}
}
}
project.provider { taskData.compilation.friendPaths }
)
private val kotlinLogger by lazy { GradleKotlinLogger(logger) }
@@ -9,7 +9,7 @@ package org.jetbrains.kotlin.project.model
// TODO think about state management: unresolved -> (known dependency graph?) ... -> completely resolved
// it seems to be important to learn whether or not the model is final
interface ModuleDependencyResolver {
fun resolveDependency(moduleDependency: KotlinModuleDependency): KotlinModule?
fun resolveDependency(requestingModule: KotlinModule, moduleDependency: KotlinModuleDependency): KotlinModule?
}
// TODO merge with ModuleDependencyResolver?
@@ -34,4 +34,6 @@ sealed class DependencyGraphResolution(val requestingModule: KotlinModule) {
class DependencyGraphNode(
val module: KotlinModule,
val dependenciesByFragment: Map<KotlinModuleFragment, Iterable<DependencyGraphNode>>
)
) {
override fun toString(): String = "node ${module}"
}
@@ -17,6 +17,7 @@ open class KotlinAttributeKey(
object KotlinPlatformTypeAttribute : KotlinAttributeKey("org.jetbrains.kotlin.platform.type") {
const val JVM = "jvm"
const val ANDROID_JVM = "androidJvm"
const val JS = "js"
const val NATIVE = "native"
}
@@ -45,7 +45,7 @@ interface KotlinModule {
// TODO: isSynthetic?
}
class BasicKotlinModule(
open class BasicKotlinModule(
override val moduleIdentifier: KotlinModuleIdentifier
) : KotlinModule {
override val fragments = mutableListOf<BasicKotlinModuleFragment>()