diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinProjectExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinProjectExtension.kt index 45ddca8927e..03991f5572d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinProjectExtension.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinProjectExtension.kt @@ -35,6 +35,9 @@ internal fun Project.createKotlinExtension(extensionClass: KClass) { + if (kotlinCompilation is KotlinCompilation<*>) { // FIXME support compiler plugins with PM20 subpluginEnvironment.addSubpluginOptions(project, kotlinCompilation) } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt index 05b9b990a72..63b1d2f82b8 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt @@ -86,9 +86,6 @@ abstract class KotlinBasePluginWrapper : Plugin { 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) = diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GenerateProjectStructureMetadata.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GenerateProjectStructureMetadata.kt index 3f54f846aad..5b0915a16f2 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GenerateProjectStructureMetadata.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GenerateProjectStructureMetadata.kt @@ -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) } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GradleProjectModuleBuilder.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GradleProjectModuleBuilder.kt index 2bc0a862b75..7e575c0ebf2 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GradleProjectModuleBuilder.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GradleProjectModuleBuilder.kt @@ -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() 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, sourceSets: Iterable< @Suppress("unused") class GradleProjectModuleBuilder(private val addInferredSourceSetVisibilityAsExplicit: Boolean) { + private fun getModulesFromPm20Project(project: Project) = project.pm20Extension.modules.toList() + fun buildModulesFromProject(project: Project): List { + 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 attributeString(container: AttributeContainer, attributeKey: Attribute): 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() - .flatMap { it.kotlinComponents.filterIsInstance() } - .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() + .flatMap { it.kotlinComponents.filterIsInstance() } + .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") + } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GranularMetadataTransformation.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GranularMetadataTransformation.kt index a63cdf24ae4..55e6478083a 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GranularMetadataTransformation.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GranularMetadataTransformation.kt @@ -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, - visibleSourceSetNamesExcludingDependsOn: Set, - visibleTransitiveDependencies: Set, - 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, + visibleSourceSetNamesExcludingDependsOn: Set, + visibleTransitiveDependencies: Set, + 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, 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 = 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().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 } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinProjectStructureMetadata.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinProjectStructureMetadata.kt index f4ec41648b2..48dcc0be61e 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinProjectStructureMetadata.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinProjectStructureMetadata.kt @@ -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 expandVariantKeys(map: Map) = + 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 KotlinProjectStructureMetadata.serialize( serializer: Serializer, node: Serializer.(name: String, Serializer.() -> Unit) -> Unit, diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt index 44c9edfea7c..5b8d94e78b4 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt @@ -84,9 +84,13 @@ abstract class KotlinSoftwareComponent( return _usages } - val sourcesArtifacts: Set 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" } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/ModuleIds.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/ModuleIds.kt index 9df4166a486..f473003d9eb 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/ModuleIds.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/ModuleIds.kt @@ -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 +} + +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 + get() = emptyList() } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/SourceSetVisibilityProvider.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/SourceSetVisibilityProvider.kt index 48688576722..4c76cab173b 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/SourceSetVisibilityProvider.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/SourceSetVisibilityProvider.kt @@ -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( diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/TransformKotlinGranularMetadata.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/TransformKotlinGranularMetadata.kt index fab40dcfb95..d6417e57129 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/TransformKotlinGranularMetadata.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/TransformKotlinGranularMetadata.kt @@ -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 + internal val filesByResolution: Map 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 +) : ResolvedMetadataFilesProvider { + override val buildDependencies: Iterable> = listOf(taskProvider) + override val metadataResolutions: Iterable by taskProvider.map { it.metadataDependencyResolutions } + override val metadataFilesByResolution: Map by taskProvider.map { it.filesByResolution } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinCompilations.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinCompilations.kt index 138e6a50317..ece42ea2b50 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinCompilations.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinCompilations.kt @@ -236,6 +236,12 @@ abstract class AbstractKotlinCompilation( } else files() } + override val friendPaths: Iterable + get() = mutableListOf().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 diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/DependencyGraphResolver.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/DependencyGraphResolver.kt index e99289ae2f5..65a60896980 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/DependencyGraphResolver.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/DependencyGraphResolver.kt @@ -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() .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) ) } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/DependencyResolution.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/DependencyResolution.kt index b471a32bdd7..8f77b483460 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/DependencyResolution.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/DependencyResolution.kt @@ -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( diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/FragmentGranularMetadataResolver.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/FragmentGranularMetadataResolver.kt new file mode 100644 index 00000000000..38977000a9b --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/FragmentGranularMetadataResolver.kt @@ -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> +) { + val resolutions: Iterable by lazy { + doResolveMetadataDependencies() + } + + private val project: Project + get() = requestingFragment.containingModule.project + + private val parentResultsByModuleIdentifier: Map> 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 { + val configurationToResolve = configurationToResolveMetadataDependencies(project, requestingFragment.containingModule) + val resolvedComponentsByModuleId = + configurationToResolve.incoming.resolutionResult.allComponents.associateBy { it.toModuleIdentifier() } + val resolvedDependenciesByModuleId = + configurationToResolve.incoming.resolutionResult.allDependencies.filterIsInstance() + .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() + val fragmentResolutionQueue = ArrayDeque().apply { + addAll(requestedDependencies) + } + + val results = mutableSetOf() + + 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().singleOrNull()?.selected + ?: resolvedComponentResult + else -> resolvedComponentResult + } + + val parentResolutionsForDependency = + parentResultsByModuleIdentifier[metadataSourceComponent.toModuleIdentifier()].orEmpty() + val fragmentsVisibleByParents = + parentResolutionsForDependency.filterIsInstance() + .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 + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/GradlePlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/GradlePlugin.kt index 435022f633a..49a29069b88 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/GradlePlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/GradlePlugin.kt @@ -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 { override fun apply(project: Project) { @@ -43,21 +62,17 @@ abstract class KotlinPm20GradlePlugin : Plugin { 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 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 = + mutableMapOf() + + internal var rootPublication: MavenPublication? = null } val KotlinGradleModule.jvm: KotlinJvmVariant diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinCompilationData.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinCompilationData.kt index 2d24716f308..ada9cef3c06 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinCompilationData.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinCompilationData.kt @@ -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 { val project: Project @@ -34,4 +36,11 @@ interface KotlinCompilationData { val ownModuleName: String val kotlinOptions: T + + val friendPaths: Iterable +} + +fun KotlinCompilationData<*>.isMain(): Boolean = when (this) { + is KotlinCompilation<*> -> isMain() + else -> compilationPurpose == KotlinGradleModule.MAIN_MODULE_NAME } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinFragmentMetadataCompilationData.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinFragmentMetadataCompilationData.kt new file mode 100644 index 00000000000..622b63eb78c --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinFragmentMetadataCompilationData.kt @@ -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, + val metadataCompilationRegistry: MetadataCompilationRegistry, + private val resolvedMetadataFiles: Lazy> +) : KotlinCompilationData { + + 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 + 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 + get() = metadataCompilationRegistry.run { + fragment.refinesClosure.minus(fragment) + .map { + metadataCompilationRegistry.byFragment(it).output.classesDirs + } + } +} + +internal class MetadataCompilationRegistry { + private val compilationDataPerFragment = mutableMapOf() + + 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 + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleFragment.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleFragment.kt index 01b93d0288b..21c15ca4709 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleFragment.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleFragment.kt @@ -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 - 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 + get() = (this as KotlinModuleFragment).refinesClosure.map { it as KotlinGradleFragment }.toSet() diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleModule.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleModule.kt index b3add60ea83..59fd7a0e4c2 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleModule.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleModule.kt @@ -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 = + variants.filter { fragment in it.refinesClosure } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleModuleFactory.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleModuleFactory.kt index 537607dae41..44ea820ae6f 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleModuleFactory.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleModuleFactory.kt @@ -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) { diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinJvmVariant.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinJvmVariant.kt index dd925031df9..07da82a5c1a 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinJvmVariant.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinJvmVariant.kt @@ -58,6 +58,13 @@ class KotlinJvmVariantCompilationData(val variant: KotlinJvmVariant) : KotlinCom override val platformType: KotlinPlatformType get() = variant.platformType + override val friendPaths: Iterable + // 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 diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/TransformKotlinGranularMetadataForFragment.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/TransformKotlinGranularMetadataForFragment.kt new file mode 100644 index 00000000000..bc10daf624f --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/TransformKotlinGranularMetadataForFragment.kt @@ -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> 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>> 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 by project.provider { + transformation.resolutions + } + + private val extractableFilesByResolution: Map + get() = metadataDependencyResolutions + .filterIsInstance() + .associateWith { it.getExtractableMetadataFiles(outputsDir) } + + @get:Internal + internal val filesByResolution: Map + 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 +) : ResolvedMetadataFilesProvider { + override val buildDependencies: Iterable> = listOf(taskProvider) + override val metadataResolutions: Iterable by taskProvider.map { it.metadataDependencyResolutions } + override val metadataFilesByResolution: Map by taskProvider.map { it.filesByResolution } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/abstractVariants.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/abstractVariants.kt index b62da769547..1a351462c8d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/abstractVariants.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/abstractVariants.kt @@ -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 - 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 + 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 + get() = super.gradleVariantNames + + listOf(runtimeElementsConfigurationName, publishedConfigurationName(runtimeElementsConfigurationName)) } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/fragmentsMetadataCompilation.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/fragmentsMetadataCompilation.kt new file mode 100644 index 00000000000..81e6a86465e --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/fragmentsMetadataCompilation.kt @@ -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().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() + 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(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(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, + metadataCompilationRegistry: MetadataCompilationRegistry +): KotlinCompilationData { + val module = fragment.containingModule + val project = module.project + + val metadataCompilationData = + KotlinFragmentMetadataCompilationData( + project, + fragment, + module, + compileAllTask, + metadataCompilationRegistry, + lazy { + fragment.refinesClosure.map { + FragmentResolvedMetadataProvider( + project.tasks.withType() + .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")) \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/modelClasses.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/modelClasses.kt index 31cb0adceda..e47c909ef25 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/modelClasses.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/modelClasses.kt @@ -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(module) { override fun instantiateFragment(name: String) = KotlinJvmVariant(module, name) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/metadata/KotlinMetadataTargetConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/metadata/KotlinMetadataTargetConfigurator.kt index afa8f9d1a93..3410136b2a1 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/metadata/KotlinMetadataTargetConfigurator.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/metadata/KotlinMetadataTargetConfigurator.kt @@ -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(transformGranularMetadataTaskName(hierarchySourceSet.name)) - } - - val allResolutionsByComponentId: Map> = - mutableMapOf>().apply { - transformationTaskHolders.forEach { - val resolutions = it.get().metadataDependencyResolutions - resolutions.forEach { resolution -> - getOrPut(resolution.dependency.id) { mutableListOf() }.add(resolution) - } - } - } - - val transformedFilesByResolution: Map = - 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().apply { - addAll(dependsOnCompilationOutputs) - artifactView.artifacts.forEach { artifact -> - val resolutions = allResolutionsByComponentId[artifact.id.componentIdentifier] - if (resolutions == null) { - add(artifact.file) - } else { - val chooseVisibleSourceSets = - resolutions.filterIsInstance() - - 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() - val resultFiles = project.files(Callable { - val originalFiles = fromFiles.toSet() - val filesToAdd = mutableSetOf() - val filesToExclude = mutableSetOf() - - 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(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 = - project.registerTask("generateProjectStructureMetadata") { task -> - task.lazyKotlinProjectStructureMetadata = lazy { checkNotNull(buildKotlinProjectStructureMetadata(project)) } +internal fun Project.createGenerateProjectStructureMetadataTask(moduleClassifier: String? = null): TaskProvider = + project.registerTask(lowerCamelCaseName("generate", moduleClassifier, "ProjectStructureMetadata")) { task -> + task.lazyKotlinProjectStructureMetadata = lazy { checkNotNull(buildKotlinProjectStructureMetadata(project)) } + } + +internal interface ResolvedMetadataFilesProvider { + val buildDependencies: Iterable> + val metadataResolutions: Iterable + val metadataFilesByResolution: Map +} + +internal fun createTransformedMetadataClasspath( + project: Project, + fromFiles: Configuration, + parentCompiledMetadataFiles: Lazy>, + metadataResolutionProviders: Lazy> +): FileCollection { + return project.files( + project.provider { + val allResolutionsByComponentId: Map> = + mutableMapOf>().apply { + metadataResolutionProviders.value.forEach { + it.metadataResolutions.forEach { resolution -> + getOrPut(resolution.dependency.id) { mutableListOf() }.add(resolution) + } + } + } + + val transformedFilesByResolution: Map = + 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().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() + + 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 = - sourceSet.getSourceSetHierarchy().toMutableSet().apply { - /** exclude self from the results of [getSourceSetHierarchy] */ - remove(sourceSet) + sourceSet.resolveAllDependsOnSourceSets().toMutableSet().apply { addAll(getVisibleSourceSetsFromAssociateCompilations(project, sourceSet)) } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinCompileCommon.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinCompileCommon.kt index d29b181f786..89c4e609bee 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinCompileCommon.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinCompileCommon.kt @@ -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 { + 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 } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt index 4bb3ae4d671..054c92f8da5 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt @@ -307,15 +307,7 @@ abstract class AbstractKotlinCompile() : 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().also { allCollections -> - associateWithTransitiveClosure.forEach { allCollections.add(it.output.classesDirs) } - allCollections.add(friendArtifacts) - } - } - } + project.provider { taskData.compilation.friendPaths } ) private val kotlinLogger by lazy { GradleKotlinLogger(logger) } diff --git a/libraries/tools/kotlin-project-model/src/main/kotlin/DependencyResolution.kt b/libraries/tools/kotlin-project-model/src/main/kotlin/DependencyResolution.kt index 7b43bec5ab6..b076510e626 100644 --- a/libraries/tools/kotlin-project-model/src/main/kotlin/DependencyResolution.kt +++ b/libraries/tools/kotlin-project-model/src/main/kotlin/DependencyResolution.kt @@ -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> -) \ No newline at end of file +) { + override fun toString(): String = "node ${module}" +} \ No newline at end of file diff --git a/libraries/tools/kotlin-project-model/src/main/kotlin/KotlinAttributes.kt b/libraries/tools/kotlin-project-model/src/main/kotlin/KotlinAttributes.kt index 271ce96e373..e52c5018034 100644 --- a/libraries/tools/kotlin-project-model/src/main/kotlin/KotlinAttributes.kt +++ b/libraries/tools/kotlin-project-model/src/main/kotlin/KotlinAttributes.kt @@ -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" } diff --git a/libraries/tools/kotlin-project-model/src/main/kotlin/KotlinModule.kt b/libraries/tools/kotlin-project-model/src/main/kotlin/KotlinModule.kt index 18085e2dd13..77e02d58ba8 100644 --- a/libraries/tools/kotlin-project-model/src/main/kotlin/KotlinModule.kt +++ b/libraries/tools/kotlin-project-model/src/main/kotlin/KotlinModule.kt @@ -45,7 +45,7 @@ interface KotlinModule { // TODO: isSynthetic? } -class BasicKotlinModule( +open class BasicKotlinModule( override val moduleIdentifier: KotlinModuleIdentifier ) : KotlinModule { override val fragments = mutableListOf()