Model mapping experiment: map source sets to KPM fragments
TODO: keep type compatibility,
inject the behavior in DefaultKotlinSourceSet
This commit is contained in:
+5
@@ -16,6 +16,8 @@ import org.jetbrains.kotlin.gradle.plugin.*
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsSingleTargetPreset
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinWithJavaTarget
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinGradleModule
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinGradleModuleFactory
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinPm20ProjectExtension
|
||||
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService
|
||||
import org.jetbrains.kotlin.gradle.targets.js.calculateJsCompilerType
|
||||
@@ -115,6 +117,9 @@ open class KotlinProjectExtension @Inject constructor(project: Project) : Kotlin
|
||||
internal set(value) {
|
||||
DslObject(this).extensions.add("sourceSets", value)
|
||||
}
|
||||
|
||||
internal val kpmModules: NamedDomainObjectContainer<KotlinGradleModule> =
|
||||
project.objects.domainObjectContainer(KotlinGradleModule::class.java, KotlinGradleModuleFactory(project))
|
||||
}
|
||||
|
||||
abstract class KotlinSingleTargetExtension(project: Project) : KotlinProjectExtension(project) {
|
||||
|
||||
+6
-2
@@ -73,8 +73,12 @@ private fun KotlinTarget.excludeStdlibAndKotlinTestCommonFromPlatformCompilation
|
||||
compilations.all {
|
||||
listOfNotNull(
|
||||
it.compileDependencyConfigurationName,
|
||||
it.defaultSourceSet.apiMetadataConfigurationName,
|
||||
it.defaultSourceSet.implementationMetadataConfigurationName,
|
||||
if (!PropertiesProvider(project).experimentalKpmModelMapping)
|
||||
it.defaultSourceSet.apiMetadataConfigurationName
|
||||
else null,
|
||||
if (!PropertiesProvider(project).experimentalKpmModelMapping)
|
||||
it.defaultSourceSet.implementationMetadataConfigurationName
|
||||
else null,
|
||||
(it as? KotlinCompilationToRunnableFiles<*>)?.runtimeDependencyConfigurationName,
|
||||
|
||||
// Additional configurations for (old) jvmWithJava-preset. Remove it when we drop it completely
|
||||
|
||||
+4
-1
@@ -35,6 +35,7 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinUsages
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinPm20GradlePlugin
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinPm20ProjectExtension
|
||||
import org.jetbrains.kotlin.gradle.plugin.sources.DefaultKotlinSourceSetFactory
|
||||
import org.jetbrains.kotlin.gradle.plugin.sources.kpm.FragmentMappedKotlinSourceSetFactory
|
||||
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService
|
||||
import org.jetbrains.kotlin.gradle.report.BuildMetricsReporterService
|
||||
import org.jetbrains.kotlin.gradle.report.HttpReportService
|
||||
@@ -68,7 +69,9 @@ abstract class KotlinBasePluginWrapper : Plugin<Project> {
|
||||
open val projectExtensionClass: KClass<out KotlinTopLevelExtension> get() = KotlinProjectExtension::class
|
||||
|
||||
internal open fun kotlinSourceSetFactory(project: Project): NamedDomainObjectFactory<KotlinSourceSet> =
|
||||
DefaultKotlinSourceSetFactory(project)
|
||||
if (PropertiesProvider(project).experimentalKpmModelMapping)
|
||||
FragmentMappedKotlinSourceSetFactory(project)
|
||||
else DefaultKotlinSourceSetFactory(project)
|
||||
|
||||
override fun apply(project: Project) {
|
||||
val kotlinPluginVersion = project.getKotlinPluginVersion()
|
||||
|
||||
+3
@@ -220,6 +220,9 @@ internal class PropertiesProvider private constructor(private val project: Proje
|
||||
val wasmStabilityNoWarn: Boolean
|
||||
get() = booleanProperty("kotlin.wasm.stability.nowarn") ?: false
|
||||
|
||||
val experimentalKpmModelMapping: Boolean
|
||||
get() = booleanProperty("kotlin.kpm.experimentalModelMapping") ?: false
|
||||
|
||||
val ignoreDisabledNativeTargets: Boolean?
|
||||
get() = booleanProperty(DisabledNativeTargetsReporter.DISABLE_WARNING_PROPERTY_NAME)
|
||||
|
||||
|
||||
+9
@@ -14,8 +14,10 @@ import org.gradle.api.plugins.JavaBasePlugin
|
||||
import org.gradle.api.publish.PublishingExtension
|
||||
import org.gradle.api.publish.maven.MavenPublication
|
||||
import org.gradle.util.GradleVersion
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinTopLevelExtension
|
||||
import org.jetbrains.kotlin.gradle.dsl.pm20Extension
|
||||
import org.jetbrains.kotlin.gradle.dsl.topLevelExtensionOrNull
|
||||
import org.jetbrains.kotlin.gradle.internal.customizeKotlinDependencies
|
||||
import org.jetbrains.kotlin.gradle.utils.checkGradleCompatibility
|
||||
import org.jetbrains.kotlin.project.model.KotlinModuleIdentifier
|
||||
@@ -117,6 +119,13 @@ abstract class KotlinPm20GradlePlugin @Inject constructor(
|
||||
fun rootPublicationComponentName(module: KotlinGradleModule) =
|
||||
module.disambiguateName("root")
|
||||
|
||||
internal val Project.kpmModules: NamedDomainObjectContainer<KotlinGradleModule>
|
||||
get() = when (val ext = project.topLevelExtensionOrNull) {
|
||||
is KotlinPm20ProjectExtension -> ext.modules
|
||||
is KotlinProjectExtension -> ext.kpmModules
|
||||
else -> error("can't find Kotlin KPM modules in $project")
|
||||
}
|
||||
|
||||
open class KotlinPm20ProjectExtension(project: Project) : KotlinTopLevelExtension(project) {
|
||||
val modules: NamedDomainObjectContainer<KotlinGradleModule> =
|
||||
project.objects.domainObjectContainer(
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ open class KotlinGradleModuleFactory(private val project: Project) : NamedDomain
|
||||
.matching { it.fragmentName == KotlinGradleFragment.COMMON_FRAGMENT_NAME }
|
||||
.configureEach { commonFragment ->
|
||||
commonFragment.dependencies {
|
||||
api(module.project.pm20Extension.modules.getByName(KotlinGradleModule.MAIN_MODULE_NAME))
|
||||
api(module.project.kpmModules.getByName(KotlinGradleModule.MAIN_MODULE_NAME))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -52,6 +52,9 @@ private class DefaultKotlinFragmentNameDisambiguation(
|
||||
internal fun KotlinModuleFragment.disambiguateName(simpleName: String) =
|
||||
KotlinModuleFragment.disambiguateName(containingModule, fragmentName, simpleName)
|
||||
|
||||
internal val KotlinModuleFragment.unambiguousNameInProject
|
||||
get() = disambiguateName("")
|
||||
|
||||
internal fun KotlinModuleFragment.Companion.disambiguateName(module: KotlinModule, fragmentName: String, simpleName: String) =
|
||||
lowerCamelCaseName(fragmentName, module.moduleIdentifier.moduleClassifier ?: KotlinGradleModule.MAIN_MODULE_NAME, simpleName)
|
||||
|
||||
|
||||
+1
-1
@@ -249,7 +249,7 @@ internal fun KotlinSourceSet.disambiguateName(simpleName: String): String {
|
||||
return lowerCamelCaseName(*nameParts.toTypedArray())
|
||||
}
|
||||
|
||||
private fun createDefaultSourceDirectorySet(project: Project, name: String?): SourceDirectorySet =
|
||||
internal fun createDefaultSourceDirectorySet(project: Project, name: String?): SourceDirectorySet =
|
||||
project.objects.sourceDirectorySet(name!!, name)
|
||||
|
||||
/**
|
||||
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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.sources.kpm
|
||||
|
||||
import groovy.lang.Closure
|
||||
import org.gradle.api.InvalidUserDataException
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.file.SourceDirectorySet
|
||||
import org.jetbrains.kotlin.build.DEFAULT_KOTLIN_SOURCE_FILES_EXTENSIONS
|
||||
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
|
||||
import org.jetbrains.kotlin.gradle.plugin.LanguageSettingsBuilder
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.*
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.unambiguousNameInProject
|
||||
import org.jetbrains.kotlin.gradle.plugin.sources.createDefaultSourceDirectorySet
|
||||
|
||||
class FragmentMappedKotlinSourceSet(
|
||||
private val project: Project,
|
||||
internal val underlyingFragment: KotlinGradleFragment
|
||||
) : KotlinSourceSet {
|
||||
val displayName: String
|
||||
get() = underlyingFragment.unambiguousNameInProject
|
||||
|
||||
override val apiConfigurationName: String get() = underlyingFragment.apiConfigurationName
|
||||
override val implementationConfigurationName: String get() = underlyingFragment.implementationConfigurationName
|
||||
override val compileOnlyConfigurationName: String get() = underlyingFragment.compileOnlyConfigurationName
|
||||
override val runtimeOnlyConfigurationName: String get() = underlyingFragment.runtimeOnlyConfigurationName
|
||||
|
||||
// FIXME deprecate the property in the supertype?
|
||||
override val apiMetadataConfigurationName: String
|
||||
get() = error("should not be used with KPM mapped model")
|
||||
override val implementationMetadataConfigurationName: String
|
||||
get() = error("should not be used with KPM mapped model")
|
||||
override val compileOnlyMetadataConfigurationName: String
|
||||
get() = error("should not be used with KPM mapped model")
|
||||
override val runtimeOnlyMetadataConfigurationName: String
|
||||
get() = error("should not be used with KPM mapped model")
|
||||
|
||||
override val kotlin: SourceDirectorySet = underlyingFragment.kotlinSourceRoots
|
||||
|
||||
override val languageSettings: LanguageSettingsBuilder = underlyingFragment.languageSettings
|
||||
|
||||
// FIXME: not supported yet
|
||||
override val resources: SourceDirectorySet = createDefaultSourceDirectorySet(project, "$name resources")
|
||||
|
||||
override fun kotlin(configureClosure: Closure<Any?>): SourceDirectorySet =
|
||||
kotlin.apply { project.configure(this, configureClosure) }
|
||||
|
||||
override fun languageSettings(configureClosure: Closure<Any?>): LanguageSettingsBuilder = languageSettings.apply {
|
||||
project.configure(this, configureClosure)
|
||||
}
|
||||
|
||||
override fun languageSettings(configure: LanguageSettingsBuilder.() -> Unit): LanguageSettingsBuilder =
|
||||
languageSettings.apply { configure(this) }
|
||||
|
||||
override fun getName(): String = displayName
|
||||
|
||||
override fun dependencies(configure: KotlinDependencyHandler.() -> Unit): Unit =
|
||||
underlyingFragment.dependencies(configure)
|
||||
|
||||
override fun dependencies(configureClosure: Closure<Any?>) =
|
||||
underlyingFragment.dependencies(configureClosure)
|
||||
|
||||
override fun dependsOn(other: KotlinSourceSet) {
|
||||
if (other !is FragmentMappedKotlinSourceSet) {
|
||||
throw InvalidUserDataException("Could set up dependsOn relationship with an unknown source set $other")
|
||||
}
|
||||
val otherFragment = other.underlyingFragment
|
||||
underlyingFragment.refines(otherFragment)
|
||||
}
|
||||
|
||||
override val dependsOn: Set<KotlinSourceSet>
|
||||
get() = project.kotlinExtension.sourceSets.filter {
|
||||
it is FragmentMappedKotlinSourceSet && it.underlyingFragment in underlyingFragment.directRefinesDependencies
|
||||
}.toSet()
|
||||
|
||||
override fun toString(): String = "source set $name"
|
||||
|
||||
override val requiresVisibilityOf: Set<KotlinSourceSet>
|
||||
get() = emptySet()
|
||||
|
||||
override fun requiresVisibilityOf(other: KotlinSourceSet) {
|
||||
throw UnsupportedOperationException("requiresVisibilityOf is not supported for the mapped model")
|
||||
}
|
||||
|
||||
// region Ported with copy & paste
|
||||
// FIXME move to fragment?
|
||||
private val explicitlyAddedCustomSourceFilesExtensions = ArrayList<String>()
|
||||
|
||||
override val customSourceFilesExtensions: Iterable<String>
|
||||
get() = Iterable {
|
||||
val fromExplicitFilters = kotlin.filter.includes.mapNotNull { pattern ->
|
||||
pattern.substringAfterLast('.')
|
||||
}
|
||||
val merged = (fromExplicitFilters + explicitlyAddedCustomSourceFilesExtensions).filterNot { extension ->
|
||||
DEFAULT_KOTLIN_SOURCE_FILES_EXTENSIONS.any { extension.equals(it, ignoreCase = true) }
|
||||
|| extension.any { it == '\\' || it == '/' }
|
||||
}.distinct()
|
||||
merged.iterator()
|
||||
}
|
||||
|
||||
override fun addCustomSourceFilesExtensions(extensions: List<String>) {
|
||||
explicitlyAddedCustomSourceFilesExtensions.addAll(extensions)
|
||||
}
|
||||
//endregion
|
||||
}
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.sources.kpm
|
||||
|
||||
import org.gradle.api.NamedDomainObjectFactory
|
||||
import org.gradle.api.Project
|
||||
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
|
||||
|
||||
class FragmentMappedKotlinSourceSetFactory constructor(private val project: Project) :
|
||||
NamedDomainObjectFactory<KotlinSourceSet> {
|
||||
|
||||
override fun create(name: String): FragmentMappedKotlinSourceSet {
|
||||
val locator = SourceSetMappedFragmentLocator.get(project)
|
||||
val location = locator.locateFragmentForSourceSet(project, name) ?: error("Couldn't map the source set name $name to KPM fragment")
|
||||
val (moduleName, fragmentName) = location
|
||||
val modules = project.multiplatformExtension.kpmModules
|
||||
val fragment = modules.maybeCreate(moduleName).fragments.maybeCreate(fragmentName)
|
||||
/** TODO setup JS-specific attributes similar to [org.jetbrains.kotlin.gradle.plugin.sources.DefaultKotlinSourceSetFactory]*/
|
||||
return FragmentMappedKotlinSourceSet(project, fragment)
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.sources.kpm
|
||||
|
||||
import org.gradle.api.Project
|
||||
import org.jetbrains.kotlin.gradle.dsl.*
|
||||
import org.jetbrains.kotlin.gradle.dsl.pm20Extension
|
||||
import org.jetbrains.kotlin.gradle.dsl.topLevelExtensionOrNull
|
||||
|
||||
interface SourceSetMappedFragmentLocator {
|
||||
data class FragmentLocation(val moduleName: String, val fragmentName: String) {
|
||||
init {
|
||||
require(moduleName.isNotEmpty()) { "module name should not be empty, got empty one and fragment name $fragmentName" }
|
||||
require(fragmentName.isNotEmpty()) { "fragment name should not be empty, got empty one and module name $moduleName" }
|
||||
}
|
||||
}
|
||||
|
||||
fun locateFragmentForSourceSet(project: Project, sourceSetName: String): FragmentLocation?
|
||||
|
||||
companion object {
|
||||
fun get(project: Project): SourceSetMappedFragmentLocator = when (project.topLevelExtensionOrNull) {
|
||||
is KotlinMultiplatformExtension -> MultiplatformSourceSetMappedFragmentLocator()
|
||||
is KotlinSingleTargetExtension -> TODO()
|
||||
else -> error("couldn't provide model mapping utilities for project $project")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MultiplatformSourceSetMappedFragmentLocator : SourceSetMappedFragmentLocator {
|
||||
override fun locateFragmentForSourceSet(project: Project, sourceSetName: String): SourceSetMappedFragmentLocator.FragmentLocation? {
|
||||
val camelCaseParts = sourceSetName.camelCaseParts()
|
||||
if (camelCaseParts.size < 2) {
|
||||
return null
|
||||
}
|
||||
val candidateModuleNames = (1..camelCaseParts.size).asSequence().map { camelCaseParts.takeLast(it).joinToString("").decapitalize() }
|
||||
val moduleName =
|
||||
candidateModuleNames.firstOrNull { project.kotlinExtension.kpmModules.findByName(it) != null } ?: candidateModuleNames.first()
|
||||
val fragmentName = sourceSetName.dropLast(moduleName.length)
|
||||
|
||||
return SourceSetMappedFragmentLocator.FragmentLocation(moduleName, fragmentName)
|
||||
}
|
||||
|
||||
private fun String.camelCaseParts(): List<String> {
|
||||
val capitalizedMatches = Regex("[A-Z][^A-Z]*").findAll(this).toList()
|
||||
val firstNonCapitalizedWord = capitalizedMatches.firstOrNull()?.let { substring(it.range.first) } ?: this
|
||||
return listOf(firstNonCapitalizedWord) + capitalizedMatches.map { it.value }
|
||||
}
|
||||
}
|
||||
|
||||
private class SingleTargetSourceSetMappedFragmentLocator : SourceSetMappedFragmentLocator {
|
||||
override fun locateFragmentForSourceSet(project: Project, sourceSetName: String): SourceSetMappedFragmentLocator.FragmentLocation {
|
||||
return SourceSetMappedFragmentLocator.FragmentLocation(sourceSetName, SINGLE_PLATFORM_FRAGMENT_NAME)
|
||||
}
|
||||
}
|
||||
|
||||
internal const val SINGLE_PLATFORM_FRAGMENT_NAME = "common"
|
||||
+22
-21
@@ -358,40 +358,41 @@ class KotlinMetadataTargetConfigurator :
|
||||
}
|
||||
)
|
||||
|
||||
(sourceSet as DefaultKotlinSourceSet).dependencyTransformations[scope] = granularMetadataTransformation
|
||||
|
||||
val sourceSetMetadataConfigurationByScope = project.sourceSetMetadataConfigurationByScope(sourceSet, scope)
|
||||
|
||||
granularMetadataTransformation.applyToConfiguration(sourceSetMetadataConfigurationByScope)
|
||||
if (sourceSet is DefaultKotlinSourceSet)
|
||||
sourceSet.dependencyTransformations[scope] = granularMetadataTransformation
|
||||
|
||||
val sourceSetDependencyConfigurationByScope = project.sourceSetDependencyConfigurationByScope(sourceSet, scope)
|
||||
|
||||
// All source set dependencies except for compileOnly agree in versions with all other published runtime dependencies:
|
||||
if (scope != KotlinDependencyScope.COMPILE_ONLY_SCOPE) {
|
||||
if (isSourceSetPublished) {
|
||||
if (isSourceSetPublished) {
|
||||
if (scope != KotlinDependencyScope.COMPILE_ONLY_SCOPE) {
|
||||
project.addExtendsFromRelation(
|
||||
ALL_RUNTIME_METADATA_CONFIGURATION_NAME,
|
||||
sourceSetDependencyConfigurationByScope.name
|
||||
)
|
||||
}
|
||||
project.addExtendsFromRelation(
|
||||
sourceSetMetadataConfigurationByScope.name,
|
||||
ALL_RUNTIME_METADATA_CONFIGURATION_NAME
|
||||
)
|
||||
}
|
||||
|
||||
// All source set dependencies except for runtimeOnly agree in versions with all other published compile dependencies:
|
||||
if (scope != KotlinDependencyScope.RUNTIME_ONLY_SCOPE) {
|
||||
if (isSourceSetPublished) {
|
||||
if (scope != KotlinDependencyScope.RUNTIME_ONLY_SCOPE) {
|
||||
project.addExtendsFromRelation(
|
||||
ALL_COMPILE_METADATA_CONFIGURATION_NAME,
|
||||
sourceSetDependencyConfigurationByScope.name
|
||||
)
|
||||
}
|
||||
project.addExtendsFromRelation(
|
||||
sourceSetMetadataConfigurationByScope.name,
|
||||
ALL_COMPILE_METADATA_CONFIGURATION_NAME
|
||||
)
|
||||
}
|
||||
|
||||
if (!PropertiesProvider(project).experimentalKpmModelMapping) {
|
||||
val sourceSetMetadataConfigurationByScope = project.sourceSetMetadataConfigurationByScope(sourceSet, scope)
|
||||
granularMetadataTransformation.applyToConfiguration(sourceSetMetadataConfigurationByScope)
|
||||
if (scope != KotlinDependencyScope.COMPILE_ONLY_SCOPE) {
|
||||
project.addExtendsFromRelation(
|
||||
sourceSetMetadataConfigurationByScope.name,
|
||||
ALL_COMPILE_METADATA_CONFIGURATION_NAME
|
||||
)
|
||||
}
|
||||
if (scope != KotlinDependencyScope.RUNTIME_ONLY_SCOPE) {
|
||||
project.addExtendsFromRelation(
|
||||
sourceSetMetadataConfigurationByScope.name,
|
||||
ALL_COMPILE_METADATA_CONFIGURATION_NAME
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user