Project Model 2.0 initial commit: core entities
This commit is contained in:
committed by
TeamCityServer
parent
6db3960d56
commit
f189ebc983
@@ -44,6 +44,7 @@ dependencies {
|
||||
implementation(project(":kotlin-util-klib"))
|
||||
implementation(project(":native:kotlin-klib-commonizer-api"))
|
||||
implementation(project(":kotlin-tooling-metadata"))
|
||||
implementation(project(":kotlin-project-model"))
|
||||
compileOnly(project(":native:kotlin-native-utils"))
|
||||
compileOnly(project(":kotlin-reflect-api"))
|
||||
compileOnly(project(":kotlin-android-extensions"))
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
id("jps-compatible")
|
||||
}
|
||||
|
||||
publish()
|
||||
|
||||
standardPublicJars()
|
||||
|
||||
dependencies {
|
||||
implementation(kotlinStdlib())
|
||||
|
||||
testImplementation(kotlin("test-junit"))
|
||||
}
|
||||
|
||||
pill {
|
||||
variant = org.jetbrains.kotlin.pill.PillExtension.Variant.FULL
|
||||
}
|
||||
|
||||
kotlin.target.compilations.all {
|
||||
kotlinOptions.languageVersion = "1.3"
|
||||
kotlinOptions.apiVersion = "1.3"
|
||||
kotlinOptions.freeCompilerArgs += listOf("-Xskip-prerelease-check")
|
||||
}
|
||||
|
||||
tasks.named<Jar>("jar") {
|
||||
callGroovy("manifestAttributes", manifest, project)
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.project.model
|
||||
|
||||
import org.jetbrains.kotlin.project.model.utils.variantsContainingFragment
|
||||
import java.util.ArrayDeque
|
||||
|
||||
interface InternalDependencyExpansion {
|
||||
fun expandInternalFragmentDependencies(consumingFragment: KotlinModuleFragment): InternalDependencyExpansionResult
|
||||
}
|
||||
|
||||
class InternalDependencyExpansionResult(
|
||||
val entries: Iterable<Entry>
|
||||
) {
|
||||
sealed class ExpansionOutcome(val variantMatchingResults: Iterable<KotlinVariantMatchingResult>) {
|
||||
class VisibleFragments(
|
||||
val fragments: Iterable<KotlinModuleFragment>,
|
||||
variantMatchingResults: Iterable<KotlinVariantMatchingResult>
|
||||
) : ExpansionOutcome(
|
||||
variantMatchingResults
|
||||
)
|
||||
|
||||
class Failure(variantMatchingResults: Iterable<KotlinVariantMatchingResult>) : ExpansionOutcome(variantMatchingResults)
|
||||
}
|
||||
|
||||
class Entry(
|
||||
val dependingFragment: KotlinModuleFragment,
|
||||
val dependencyFragment: KotlinModuleFragment,
|
||||
val outcome: ExpansionOutcome
|
||||
)
|
||||
}
|
||||
|
||||
fun InternalDependencyExpansionResult.visibleFragments(): List<KotlinModuleFragment> =
|
||||
entries.flatMap { entry -> entry.outcome.visibleFragments() }
|
||||
|
||||
fun InternalDependencyExpansionResult.ExpansionOutcome.visibleFragments(): Iterable<KotlinModuleFragment> = when (this) {
|
||||
is InternalDependencyExpansionResult.ExpansionOutcome.VisibleFragments -> fragments
|
||||
else -> emptyList()
|
||||
}
|
||||
|
||||
class DefaultInternalDependencyExpansion(
|
||||
private val variantResolver: ContainingModuleVariantResolver
|
||||
) : InternalDependencyExpansion {
|
||||
interface ContainingModuleVariantResolver {
|
||||
fun getChosenVariant(
|
||||
dependingVariant: KotlinModuleVariant,
|
||||
candidateVariants: Iterable<KotlinModuleVariant>
|
||||
): KotlinVariantMatchingResult
|
||||
}
|
||||
|
||||
override fun expandInternalFragmentDependencies(consumingFragment: KotlinModuleFragment): InternalDependencyExpansionResult {
|
||||
val visited = mutableSetOf(consumingFragment)
|
||||
val answer = mutableListOf<InternalDependencyExpansionResult.Entry>()
|
||||
|
||||
/**
|
||||
* For each fragment **t** that we consider a source of declared dependencies that we should expand for [consumingFragment]:
|
||||
* - also consider the refines-closure of **t** as sources of declared dependencies
|
||||
* - expand the declared dependencies of **t** to a set of fragments **F**
|
||||
* - add the expansion result to the answer
|
||||
* - also consider all fragments in **F** as sources of declared dependencies
|
||||
*/
|
||||
val declaredDependencySourceQueue = ArrayDeque<KotlinModuleFragment>().apply { add(consumingFragment) }
|
||||
while (declaredDependencySourceQueue.isNotEmpty()) {
|
||||
val declaredDependencySource = declaredDependencySourceQueue.removeFirst()
|
||||
declaredDependencySourceQueue.addAll(declaredDependencySource.refinesClosure.filter(visited::add))
|
||||
val declaredDependenciesToExpand = declaredDependencySource.declaredContainingModuleFragmentDependencies
|
||||
declaredDependenciesToExpand.forEach { declaredDependency ->
|
||||
val answerEntry = expandSingleDeclaredDependency(consumingFragment, declaredDependencySource, declaredDependency)
|
||||
answer += answerEntry
|
||||
declaredDependencySourceQueue.addAll(answerEntry.outcome.visibleFragments())
|
||||
}
|
||||
}
|
||||
|
||||
return InternalDependencyExpansionResult(answer)
|
||||
}
|
||||
|
||||
private fun expandSingleDeclaredDependency(
|
||||
consumingFragment: KotlinModuleFragment,
|
||||
declaredDependencySource: KotlinModuleFragment,
|
||||
declaredDependency: KotlinModuleFragment
|
||||
): InternalDependencyExpansionResult.Entry {
|
||||
/**
|
||||
* Go over the variants containing the fragment that we infer the expansion for (note: it's the original consuming fragment, which
|
||||
* may not necessarily be the same as the fragment holding the declared dependency if the latter comes from the closure).
|
||||
*
|
||||
* For each such containing variant v_i, let w_i be the variant containing the declared dependency fragment. Failure to choose
|
||||
* prevents us from inferring the dependency expansion and is reported as a failure to expand the dependency.
|
||||
*
|
||||
* Then intersect the refined fragments of each w_i and use the intersection as the result.
|
||||
*/
|
||||
|
||||
val (consumingVariants, producingVariants) =
|
||||
listOf(consumingFragment, declaredDependency).map { it.containingModule.variantsContainingFragment(it).toSet() }
|
||||
|
||||
val chosenVariants = consumingVariants.map { consumingVariant ->
|
||||
if (consumingVariant in producingVariants)
|
||||
VariantMatch(consumingVariant, consumingVariant.containingModule, consumingVariant)
|
||||
else
|
||||
variantResolver.getChosenVariant(consumingVariant, producingVariants)
|
||||
}
|
||||
|
||||
val mismatchedConsumingVariants = chosenVariants.filter { it !is VariantMatch }
|
||||
|
||||
val outcome =
|
||||
if (mismatchedConsumingVariants.isNotEmpty())
|
||||
InternalDependencyExpansionResult.ExpansionOutcome.Failure(chosenVariants)
|
||||
else
|
||||
InternalDependencyExpansionResult.ExpansionOutcome.VisibleFragments(
|
||||
chosenVariants.map { (it as VariantMatch).chosenVariant.refinesClosure }.reduce { acc, it -> acc.intersect(it) },
|
||||
chosenVariants
|
||||
)
|
||||
|
||||
return InternalDependencyExpansionResult.Entry(declaredDependencySource, declaredDependency, outcome)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.project.model
|
||||
|
||||
open class KotlinAttributeKey(
|
||||
val uniqueName: String
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean =
|
||||
other is KotlinAttributeKey && uniqueName == other.uniqueName
|
||||
|
||||
override fun hashCode(): Int =
|
||||
uniqueName.hashCode()
|
||||
}
|
||||
|
||||
object KotlinPlatformTypeAttribute : KotlinAttributeKey("org.jetbrains.kotlin.platform.type") {
|
||||
const val JVM = "jvm"
|
||||
const val JS = "js"
|
||||
const val NATIVE = "native"
|
||||
}
|
||||
|
||||
object KotlinNativeTargetAttribute : KotlinAttributeKey("org.jetbrains.kotlin.native.target")
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.project.model
|
||||
|
||||
sealed class ModuleSource {
|
||||
class LocalBuild(val buildId: String) : ModuleSource()
|
||||
class ExternalDependency(val dependencyId: String) : ModuleSource()
|
||||
}
|
||||
|
||||
interface KotlinModule {
|
||||
val moduleName: String
|
||||
val moduleSource: ModuleSource
|
||||
|
||||
val fragments: Iterable<KotlinModuleFragment>
|
||||
|
||||
val variants: Iterable<KotlinModuleVariant>
|
||||
get() = fragments.filterIsInstance<KotlinModuleVariant>()
|
||||
}
|
||||
|
||||
class BasicKotlinModule(
|
||||
override val moduleName: String,
|
||||
override val moduleSource: ModuleSource
|
||||
) : KotlinModule {
|
||||
override val fragments = mutableListOf<BasicKotlinModuleFragment>()
|
||||
|
||||
override fun toString(): String = "module '$moduleName'"
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.project.model
|
||||
|
||||
import org.jetbrains.kotlin.project.model.utils.variantsContainingFragment
|
||||
import java.io.File
|
||||
|
||||
interface KotlinModuleFragment {
|
||||
val containingModule: KotlinModule
|
||||
|
||||
val fragmentName: String
|
||||
val directRefinesDependencies: Iterable<KotlinModuleFragment>
|
||||
|
||||
val declaredContainingModuleFragmentDependencies: Iterable<KotlinModuleFragment>
|
||||
|
||||
val kotlinSourceRoots: Iterable<File>
|
||||
}
|
||||
|
||||
interface KotlinModuleVariant : KotlinModuleFragment {
|
||||
val variantAttributes: Map<KotlinAttributeKey, String>
|
||||
|
||||
var isExported: Boolean
|
||||
}
|
||||
|
||||
val KotlinModuleFragment.fragmentAttributeSets: Map<KotlinAttributeKey, Set<String>>
|
||||
get() = mutableMapOf<KotlinAttributeKey, MutableSet<String>>().apply {
|
||||
containingModule.variantsContainingFragment(this@fragmentAttributeSets).forEach { variant ->
|
||||
variant.variantAttributes.forEach { attribute, value ->
|
||||
getOrPut(attribute) { mutableSetOf() }.add(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val KotlinModuleFragment.refinesClosure: Set<KotlinModuleFragment>
|
||||
get() = mutableSetOf<KotlinModuleFragment>().apply {
|
||||
fun visit(moduleFragment: KotlinModuleFragment) {
|
||||
if (add(moduleFragment))
|
||||
moduleFragment.directRefinesDependencies.forEach(::visit)
|
||||
}
|
||||
visit(this@refinesClosure)
|
||||
}
|
||||
|
||||
|
||||
open class BasicKotlinModuleFragment(
|
||||
override val containingModule: KotlinModule,
|
||||
override val fragmentName: String
|
||||
) : KotlinModuleFragment {
|
||||
|
||||
override val directRefinesDependencies: MutableList<BasicKotlinModuleFragment> = mutableListOf()
|
||||
|
||||
override val declaredContainingModuleFragmentDependencies: MutableList<BasicKotlinModuleFragment> = mutableListOf()
|
||||
override val kotlinSourceRoots: Iterable<File> = emptyList()
|
||||
override fun toString(): String = "fragment $fragmentName"
|
||||
}
|
||||
|
||||
class BasicKotlinVariant(
|
||||
containingModule: KotlinModule,
|
||||
fragmentName: String
|
||||
) : BasicKotlinModuleFragment (
|
||||
containingModule,
|
||||
fragmentName
|
||||
), KotlinModuleVariant {
|
||||
override var isExported: Boolean = true
|
||||
override val variantAttributes: MutableMap<KotlinAttributeKey, String> = mutableMapOf()
|
||||
override fun toString(): String = "variant $fragmentName"
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.project.model
|
||||
|
||||
import org.jetbrains.kotlin.project.model.utils.variantsContainingFragment
|
||||
|
||||
interface KotlinModuleFragmentResolver {
|
||||
fun getChosenFragments(dependingFragment: KotlinModuleFragment, dependencyModule: KotlinModule): KotlinChosenFragments
|
||||
}
|
||||
|
||||
class KotlinChosenFragments(
|
||||
val module: KotlinModule,
|
||||
val chosenFragments: Iterable<KotlinModuleFragment>,
|
||||
val variantMatchingResults: Iterable<KotlinVariantMatchingResult>
|
||||
)
|
||||
|
||||
class DefaultKotlinModuleFragmentResolver(
|
||||
private val variantResolver: KotlinModuleVariantResolver
|
||||
) : KotlinModuleFragmentResolver {
|
||||
override fun getChosenFragments(dependingFragment: KotlinModuleFragment, dependencyModule: KotlinModule): KotlinChosenFragments {
|
||||
val dependingModule = dependingFragment.containingModule
|
||||
val containingVariants = dependingModule.variantsContainingFragment(dependingFragment)
|
||||
|
||||
val chosenVariants = containingVariants.map { variantResolver.getChosenVariant(it, dependencyModule) }
|
||||
|
||||
val chosenFragments = chosenVariants.map { variantResolution ->
|
||||
when (variantResolution) {
|
||||
is VariantMatch -> variantResolution.chosenVariant.refinesClosure
|
||||
else -> emptySet()
|
||||
}
|
||||
}
|
||||
|
||||
val result = if (chosenFragments.isEmpty())
|
||||
emptyList<KotlinModuleFragment>()
|
||||
else chosenFragments
|
||||
// Note this emulates the existing behavior that is lenient wrt to unresolved modules, but gives imprecise results. TODO revisit
|
||||
.filter { it.isNotEmpty() }
|
||||
.reduce { acc, it -> acc.intersect(it) }
|
||||
|
||||
return KotlinChosenFragments(dependencyModule, result, chosenVariants)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.project.model
|
||||
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
interface KotlinModuleVariantResolver {
|
||||
fun getChosenVariant(dependingVariant: KotlinModuleVariant, dependencyModule: KotlinModule): KotlinVariantMatchingResult
|
||||
}
|
||||
|
||||
sealed class KotlinVariantMatchingResult(
|
||||
val requestingVariant: KotlinModuleVariant,
|
||||
val dependencyModule: KotlinModule
|
||||
) {
|
||||
companion object {
|
||||
fun fromMatchingVariants(
|
||||
requestingVariant: KotlinModuleVariant,
|
||||
dependencyModule: KotlinModule,
|
||||
matchingVariants: Collection<KotlinModuleVariant>
|
||||
) = when (matchingVariants.size) {
|
||||
0 -> NoVariantMatch(requestingVariant, dependencyModule)
|
||||
1 -> VariantMatch(requestingVariant, dependencyModule, matchingVariants.single())
|
||||
else -> AmbiguousVariants(requestingVariant, dependencyModule, matchingVariants)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VariantMatch(
|
||||
requestingVariant: KotlinModuleVariant,
|
||||
dependencyModule: KotlinModule,
|
||||
val chosenVariant: KotlinModuleVariant
|
||||
) : KotlinVariantMatchingResult(requestingVariant, dependencyModule)
|
||||
|
||||
class NoVariantMatch(
|
||||
requestingVariant: KotlinModuleVariant,
|
||||
dependencyModule: KotlinModule
|
||||
) : KotlinVariantMatchingResult(requestingVariant, dependencyModule)
|
||||
|
||||
class AmbiguousVariants(
|
||||
requestingVariant: KotlinModuleVariant,
|
||||
dependencyModule: KotlinModule,
|
||||
val matchingVariants: Iterable<KotlinModuleVariant>
|
||||
) : KotlinVariantMatchingResult(requestingVariant, dependencyModule)
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.project.model.utils
|
||||
|
||||
import org.jetbrains.kotlin.project.model.KotlinModule
|
||||
import org.jetbrains.kotlin.project.model.KotlinModuleFragment
|
||||
import org.jetbrains.kotlin.project.model.KotlinModuleVariant
|
||||
import org.jetbrains.kotlin.project.model.refinesClosure
|
||||
|
||||
fun KotlinModule.variantsContainingFragment(fragment: KotlinModuleFragment): Iterable<KotlinModuleVariant> =
|
||||
variants.filter { fragment in it.refinesClosure }
|
||||
|
||||
fun KotlinModule.findRefiningFragments(fragment: KotlinModuleFragment): Iterable<KotlinModuleFragment> {
|
||||
val refining = mutableSetOf<KotlinModuleFragment>()
|
||||
val notRefining = mutableSetOf<KotlinModuleFragment>()
|
||||
|
||||
fun isRefining(other: KotlinModuleFragment): Boolean = when {
|
||||
other in refining -> true
|
||||
other in notRefining -> false
|
||||
fragment in other.directRefinesDependencies -> true.also { refining.add(other) }
|
||||
fragment.directRefinesDependencies.any { isRefining(it) } -> true.also { refining.add(other) }
|
||||
else -> false.also { notRefining.add(other) }
|
||||
}
|
||||
|
||||
return fragments.filter(::isRefining)
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.project.model
|
||||
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class DefaultInternalDependencyExpansionTest {
|
||||
/** Create a module without internal depends edges, those need to be added manually afterwards */
|
||||
fun createTemplateModule(vararg purposes: String) = module("foo").apply {
|
||||
listOf(*purposes).forEach { purpose ->
|
||||
val common = fragment("common", purpose)
|
||||
|
||||
val (jvm, js, linux) = listOf("jvm", "js", "linux").map { platform ->
|
||||
variant(platform, purpose).apply {
|
||||
variantAttributes[KotlinPlatformTypeAttribute] = when (platform) {
|
||||
"jvm" -> KotlinPlatformTypeAttribute.JVM
|
||||
"js" -> KotlinPlatformTypeAttribute.JS
|
||||
else -> {
|
||||
variantAttributes[KotlinNativeTargetAttribute] = platform
|
||||
KotlinPlatformTypeAttribute.NATIVE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val jvmAndJs = fragment("jvmAndJs", purpose).apply {
|
||||
refines(common)
|
||||
refinedBy(jvm)
|
||||
refinedBy(js)
|
||||
}
|
||||
val jsAndLinux = fragment("jsAndLinux", purpose).apply {
|
||||
refines(common)
|
||||
refinedBy(js)
|
||||
refinedBy(linux)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val expansion = DefaultInternalDependencyExpansion(AssociateVariants())
|
||||
|
||||
@Test
|
||||
fun testSimpleCommonTestToCommonMainDependency() {
|
||||
val module = createTemplateModule("main", "test").apply {
|
||||
// Manually associate the variants:
|
||||
listOf("jvm", "js", "linux").forEach {
|
||||
variant(it, "test").depends(variant(it, "main"))
|
||||
}
|
||||
// Add just a single dependency from commonTest to commonMain
|
||||
fragment("commonTest").depends(fragment("commonMain"))
|
||||
}
|
||||
val expectedFragments = mapOf(
|
||||
"commonTest" to setOf("commonMain"),
|
||||
"jvmAndJsTest" to setOf("jvmAndJsMain", "commonMain"),
|
||||
"jsAndLinuxTest" to setOf("jsAndLinuxMain", "commonMain"),
|
||||
"jvmTest" to setOf("jvmMain", "jvmAndJsMain", "commonMain"),
|
||||
"jsTest" to setOf("jsMain", "jsAndLinuxMain", "jvmAndJsMain", "commonMain"),
|
||||
"linuxTest" to setOf("linuxMain", "jsAndLinuxMain", "commonMain")
|
||||
)
|
||||
module.fragments.forEach { fragment ->
|
||||
val result =
|
||||
expansion.expandInternalFragmentDependencies(fragment).visibleFragments().map { it.fragmentName }.toSet()
|
||||
val expected = expectedFragments[fragment.fragmentName].orEmpty()
|
||||
assertEquals(expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDeclaredDependenciesFromExpansion() {
|
||||
val module = createTemplateModule("main", "test", "integration").apply {
|
||||
/** Manually associate the variants: */
|
||||
listOf("jvm", "js", "linux").forEach {
|
||||
variant(it, "test").depends(variant(it, "main"))
|
||||
variant(it, "integration").depends(variant(it, "test"))
|
||||
variant(it, "integration").depends(variant(it, "main"))
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a dependency from `commonIntegration` to `commonTest` and from (a more specific fragment)
|
||||
* `jvmAndJsTest` to `jvmAndJsMain`.
|
||||
*/
|
||||
fragment("commonIntegration").depends(fragment("commonTest"))
|
||||
fragment("jvmAndJsTest").depends(fragment("jvmAndJsMain"))
|
||||
|
||||
/**
|
||||
* Expect that the fragment `jvmAndJsIntegration` receives the dependency on `jvmAndJsMain` through
|
||||
* the declared dependency added to the fragment `jvmAndJsTest`, which is only visible in expansion.
|
||||
*/
|
||||
val result = expansion.expandInternalFragmentDependencies(fragment("jvmAndJsIntegration"))
|
||||
val expected = setOf("commonMain", "jvmAndJsMain", "commonTest", "jvmAndJsTest")
|
||||
assertEquals(expected, result.visibleFragments().map { it.fragmentName }.toSet())
|
||||
assertTrue {
|
||||
result.entries.any {
|
||||
it.dependingFragment == fragment("jvmAndJsTest") &&
|
||||
it.dependencyFragment == fragment("jvmAndJsMain") &&
|
||||
it.outcome.visibleFragments().map { it.fragmentName }.toSet() == setOf("commonMain", "jvmAndJsMain")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.project.model
|
||||
|
||||
import org.junit.Assume
|
||||
import org.junit.Assume.assumeTrue
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class DefaultKotlinModuleFragmentResolverTest {
|
||||
val moduleFoo = simpleModule("foo")
|
||||
val moduleBar = simpleModule("bar")
|
||||
|
||||
val fragmentResolver = DefaultKotlinModuleFragmentResolver(MatchVariantsByExactAttributes())
|
||||
|
||||
@Test
|
||||
fun testFragmentVisibility() {
|
||||
val expectedVisibleFragments = mapOf(
|
||||
"common" to setOf("commonMain"),
|
||||
"jvmAndJs" to setOf("commonMain", "jvmAndJsMain"),
|
||||
"jsAndLinux" to setOf("commonMain", "jsAndLinuxMain"),
|
||||
"jvm" to setOf("commonMain", "jvmAndJsMain", "jvmMain"),
|
||||
"js" to setOf("commonMain", "jvmAndJsMain", "jsAndLinuxMain", "jsMain"),
|
||||
"linux" to setOf("commonMain", "jsAndLinuxMain", "linuxMain")
|
||||
)
|
||||
|
||||
moduleBar.fragments.forEach { consumingFragment ->
|
||||
val result = fragmentResolver.getChosenFragments(consumingFragment, moduleFoo)
|
||||
val expected = expectedVisibleFragments.getValue(consumingFragment.fragmentName.removeSuffix("Main").removeSuffix("Test"))
|
||||
assertEquals(expected, result.chosenFragments.map { it.fragmentName }.toSet())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testVisibilityWithMismatchedVariant() {
|
||||
// TODO this behavior replicates 1.3.x MPP where a mismatched variant gets ignored and only matched variants are intersected.
|
||||
// This helps with non-published local native targets.
|
||||
// Consider making it more strict when we have a solution to the original problem.
|
||||
val dependingModule = simpleModule("baz").apply {
|
||||
variant("linuxMain").variantAttributes.replace(KotlinNativeTargetAttribute, "notLinux")
|
||||
}
|
||||
assumeTrue(MatchVariantsByExactAttributes().getChosenVariant(dependingModule.variant("linuxMain"), moduleFoo) is NoVariantMatch)
|
||||
|
||||
val (commonMainResult, jsAndLinuxResult) = listOf("commonMain", "jsAndLinuxMain").map {
|
||||
fragmentResolver.getChosenFragments(dependingModule.fragment(it), moduleFoo).chosenFragments.map { it.fragmentName }.toSet()
|
||||
}
|
||||
|
||||
assertEquals(setOf("commonMain", "jvmAndJsMain"), commonMainResult)
|
||||
assertEquals(setOf("commonMain", "jvmAndJsMain", "jsAndLinuxMain", "jsMain"), jsAndLinuxResult)
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.project.model
|
||||
|
||||
class MatchVariantsByExactAttributes : KotlinModuleVariantResolver {
|
||||
override fun getChosenVariant(dependingVariant: KotlinModuleVariant, dependencyModule: KotlinModule): KotlinVariantMatchingResult {
|
||||
val candidates = dependencyModule.variants
|
||||
return candidates.filter { candidate ->
|
||||
candidate.isExported && candidate.variantAttributes.all { (attributeKey, candidateValue) ->
|
||||
attributeKey !in dependingVariant.variantAttributes.keys ||
|
||||
candidateValue == dependingVariant.variantAttributes.getValue(attributeKey)
|
||||
}
|
||||
}.let { KotlinVariantMatchingResult.fromMatchingVariants(dependingVariant, dependencyModule, it) }
|
||||
}
|
||||
}
|
||||
|
||||
class AssociateVariants : DefaultInternalDependencyExpansion.ContainingModuleVariantResolver {
|
||||
override fun getChosenVariant(
|
||||
dependingVariant: KotlinModuleVariant,
|
||||
candidateVariants: Iterable<KotlinModuleVariant>
|
||||
): KotlinVariantMatchingResult {
|
||||
val result = candidateVariants.filter { it in dependingVariant.declaredContainingModuleFragmentDependencies }
|
||||
return KotlinVariantMatchingResult.fromMatchingVariants(dependingVariant, dependingVariant.containingModule, result)
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.project.model
|
||||
|
||||
fun module(name: String) = BasicKotlinModule(name, ModuleSource.LocalBuild("current"))
|
||||
|
||||
fun BasicKotlinModule.fragment(vararg nameParts: String): BasicKotlinModuleFragment =
|
||||
fragment(nameParts.drop(1).joinToString("", nameParts.first()) { it.capitalize() })
|
||||
|
||||
fun BasicKotlinModule.fragment(name: String): BasicKotlinModuleFragment =
|
||||
fragments.firstOrNull { it.fragmentName == name } ?: BasicKotlinModuleFragment(this, name).also { fragments.add(it) }
|
||||
|
||||
fun BasicKotlinModule.variant(vararg nameParts: String): BasicKotlinVariant =
|
||||
variant(nameParts.drop(1).joinToString("", nameParts.first()) { it.capitalize() })
|
||||
|
||||
fun BasicKotlinModule.variant(name: String): BasicKotlinVariant =
|
||||
fragments.firstOrNull { it.fragmentName == name }
|
||||
?.let { it as? BasicKotlinVariant ?: error("$name is not a variant") }
|
||||
?: BasicKotlinVariant(this, name).also { fragments.add(it) }
|
||||
|
||||
|
||||
fun BasicKotlinModuleFragment.depends(fragment: BasicKotlinModuleFragment) {
|
||||
require(fragment.containingModule == containingModule)
|
||||
declaredContainingModuleFragmentDependencies.add(fragment)
|
||||
}
|
||||
|
||||
fun BasicKotlinModuleFragment.refinedBy(fragment: BasicKotlinModuleFragment) {
|
||||
fragment.refines(this)
|
||||
}
|
||||
|
||||
fun BasicKotlinModuleFragment.refines(fragment: BasicKotlinModuleFragment) {
|
||||
require(fragment.containingModule == containingModule)
|
||||
directRefinesDependencies.add(fragment)
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
fun simpleModule(name: String) = module(name).apply {
|
||||
listOf("main", "test").forEach { purpose ->
|
||||
val common = fragment("common", purpose)
|
||||
if (purpose == "test")
|
||||
common.depends(fragment("common", "main"))
|
||||
|
||||
val (jvm, js, linux) = listOf("jvm", "js", "linux").map { platform ->
|
||||
variant(platform, purpose).apply {
|
||||
variantAttributes[KotlinPlatformTypeAttribute] = when (platform) {
|
||||
"jvm" -> KotlinPlatformTypeAttribute.JVM
|
||||
"js" -> KotlinPlatformTypeAttribute.JS
|
||||
else -> {
|
||||
variantAttributes[KotlinNativeTargetAttribute] = platform
|
||||
KotlinPlatformTypeAttribute.NATIVE
|
||||
}
|
||||
}
|
||||
if (purpose == "test") {
|
||||
isExported = false
|
||||
depends(variant(platform, "main"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val jvmAndJs = fragment("jvmAndJs", purpose).apply {
|
||||
refines(common)
|
||||
refinedBy(jvm)
|
||||
refinedBy(js)
|
||||
}
|
||||
val jsAndLinux = fragment("jsAndLinux", purpose).apply {
|
||||
refines(common)
|
||||
refinedBy(js)
|
||||
refinedBy(linux)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -243,6 +243,7 @@ include ":benchmarks",
|
||||
":generators",
|
||||
":generators:test-generator",
|
||||
":tools:kotlinp",
|
||||
":kotlin-project-model",
|
||||
":kotlin-gradle-plugin-api",
|
||||
":kotlin-gradle-plugin-dsl-codegen",
|
||||
":kotlin-gradle-plugin-npm-versions-codegen",
|
||||
@@ -510,6 +511,7 @@ project(':noarg-ide-plugin').projectDir = "$rootDir/plugins/noarg/noarg-ide" as
|
||||
project(':kotlin-sam-with-receiver-compiler-plugin').projectDir = "$rootDir/plugins/sam-with-receiver/sam-with-receiver-cli" as File
|
||||
project(':sam-with-receiver-ide-plugin').projectDir = "$rootDir/plugins/sam-with-receiver/sam-with-receiver-ide" as File
|
||||
project(':tools:kotlinp').projectDir = "$rootDir/libraries/tools/kotlinp" as File
|
||||
project(':kotlin-project-model').projectDir = "$rootDir/libraries/tools/kotlin-project-model" as File
|
||||
project(':kotlin-gradle-plugin-api').projectDir = "$rootDir/libraries/tools/kotlin-gradle-plugin-api" as File
|
||||
project(':kotlin-gradle-plugin-dsl-codegen').projectDir = "$rootDir/libraries/tools/kotlin-gradle-plugin-dsl-codegen" as File
|
||||
project(':kotlin-gradle-plugin-npm-versions-codegen').projectDir = "$rootDir/libraries/tools/kotlin-gradle-plugin-npm-versions-codegen" as File
|
||||
|
||||
Reference in New Issue
Block a user