[Gradle] Remove ':kotlin-project-model' and ':kotlin-project-model-tests-generator'

KT-61463
This commit is contained in:
Sebastian Sellmair
2023-08-25 16:54:00 +02:00
committed by Space Team
parent 6d38a314cc
commit 02c2c76ffe
49 changed files with 12 additions and 1966 deletions
-2
View File
@@ -318,8 +318,6 @@
/libraries/tools/kotlin-osgi-bundle/ "Kotlin Build Tools"
/libraries/tools/kotlin-noarg/ "Kotlin Build Tools"
/libraries/tools/kotlin-prepush-hook/ "Kotlin Build Infrastructure"
/libraries/tools/kotlin-project-model/ "Kotlin Multiplatform"
/libraries/tools/kotlin-project-model-tests-generator/ "Kotlin Multiplatform"
/libraries/tools/kotlin-sam-with-receiver/ "Kotlin Build Tools"
/libraries/tools/kotlin-script-util/ "Kotlin Compiler Core"
/libraries/tools/kotlin-serialization/ "Kotlin Build Tools"
@@ -1442,3 +1442,11 @@ public final class org/jetbrains/kotlin/gradle/tasks/UsesKotlinJavaToolchain$Def
public static fun getKotlinJavaToolchain (Lorg/jetbrains/kotlin/gradle/tasks/UsesKotlinJavaToolchain;)Lorg/jetbrains/kotlin/gradle/tasks/KotlinJavaToolchain;
}
public abstract interface class org/jetbrains/kotlin/project/model/LanguageSettings {
public abstract fun getApiVersion ()Ljava/lang/String;
public abstract fun getEnabledLanguageFeatures ()Ljava/util/Set;
public abstract fun getLanguageVersion ()Ljava/lang/String;
public abstract fun getOptInAnnotationsInUse ()Ljava/util/Set;
public abstract fun getProgressiveMode ()Z
}
@@ -13,7 +13,6 @@ dependencies {
commonApi(platform(project(":kotlin-gradle-plugins-bom")))
commonApi(project(":kotlin-gradle-plugin-annotations"))
commonApi(project(":native:kotlin-native-utils"))
commonApi(project(":kotlin-project-model"))
commonApi(project(":kotlin-tooling-core"))
commonCompileOnly(project(":kotlin-gradle-compiler-types"))
@@ -1,8 +1,10 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2023 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.
*/
@file:Suppress("PackageDirectoryMismatch")
package org.jetbrains.kotlin.project.model
interface LanguageSettings {
@@ -11,4 +13,4 @@ interface LanguageSettings {
val progressiveMode: Boolean
val enabledLanguageFeatures: Set<String>
val optInAnnotationsInUse: Set<String>
}
}
@@ -63,7 +63,6 @@ dependencies {
testImplementation(project(":kotlin-gradle-plugin-model"))
testImplementation(project(":kotlin-gradle-build-metrics"))
testImplementation(project(":kotlin-project-model"))
testImplementation(project(":kotlin-tooling-metadata"))
testImplementation(kotlinGradlePluginTest)
testImplementation(project(":kotlin-gradle-subplugin-example"))
@@ -14,13 +14,9 @@ import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileCollection
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*
import org.gradle.process.ExecOperations
import org.jetbrains.kotlin.build.report.metrics.BuildMetricsReporter
import org.jetbrains.kotlin.build.report.metrics.GradleBuildPerformanceMetric
import org.jetbrains.kotlin.build.report.metrics.GradleBuildTime
import org.jetbrains.kotlin.cli.common.arguments.K2NativeCompilerArguments
import org.jetbrains.kotlin.compilerRunner.*
import org.jetbrains.kotlin.gradle.dsl.*
@@ -29,7 +25,6 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinCompilerArgumentsProducer.Create
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.cocoapods.asValidFrameworkName
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.report.GradleBuildMetricsReporter
import org.jetbrains.kotlin.gradle.report.UsesBuildMetricsService
import org.jetbrains.kotlin.gradle.targets.native.UsesKonanPropertiesBuildService
import org.jetbrains.kotlin.gradle.targets.native.tasks.CompilerPluginData
@@ -1,29 +0,0 @@
plugins {
kotlin("jvm")
id("jps-compatible")
id("java-test-fixtures")
}
dependencies {
testImplementation(kotlinStdlib())
testApi(projectTests(":compiler:cli"))
testApi(projectTests(":generators:test-generator"))
testApi(testFixtures(project(":kotlin-project-model")))
testApi(project(":kotlin-project-model"))
testApi(projectTests(":kotlin-gradle-plugin-integration-tests"))
testCompileOnly(commonDependency("org.jetbrains.kotlin:kotlin-reflect")) { isTransitive = false }
testImplementation(commonDependency("org.jetbrains.kotlin:kotlin-reflect")) { isTransitive = false }
testImplementation(projectTests(":compiler:test-infrastructure-utils"))
testImplementation(projectTests(":compiler:test-infrastructure"))
testImplementation(projectTests(":compiler:tests-common-new"))
testImplementation(projectTests(":js:js.tests"))
testApiJUnit5()
if (Ide.IJ()) {
testCompileOnly(jpsBuildTest())
testApi(jpsBuildTest())
}
}
val generateKpmTests by generator("org.jetbrains.kotlin.kpm.GenerateKpmTestsKt") {
}
@@ -1,14 +0,0 @@
/*
* Copyright 2010-2022 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.kpm
import org.jetbrains.kotlin.project.model.infra.generate.generateKpmTestCases
fun main() {
generateKpmTestCases {
// Add generated tests here
}
}
@@ -1,41 +0,0 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
plugins {
kotlin("jvm")
id("jps-compatible")
id("java-test-fixtures")
}
publish()
standardPublicJars()
dependencies {
implementation(kotlinStdlib())
implementation(project(":kotlin-tooling-core"))
testApiJUnit5(runner = true)
testFixturesImplementation(project(":kotlin-tooling-core"))
testFixturesImplementation(project(":core:util.runtime"))
testFixturesImplementation(projectTests(":generators:test-generator"))
testFixturesImplementation(commonDependency("org.jetbrains.kotlin:kotlin-reflect")) { isTransitive = false }
}
projectTest(jUnitMode = JUnitMode.JUnit5) {
useJUnitPlatform()
}
configureKotlinCompileTasksGradleCompatibility()
tasks.named<KotlinJvmCompile>("compileTestFixturesKotlin") {
kotlinOptions {
freeCompilerArgs += listOf(
"-XXLanguage:+AllowSealedInheritorsInDifferentFilesOfSamePackage",
"-XXLanguage:+SealedInterfaces",
"-Xjvm-default=all"
)
}
}
tasks.named<Jar>("jar") {
manifestAttributes(manifest)
}
@@ -1,84 +0,0 @@
# Dependency Resolution in the Project Model
Dependency resolution is not a single task, it is a complex pipeline where the requested dependencies, considered an input, are transformed
to mutliple possible kinds of results. To produce one kind of the results, another sort of dependency resolution result may be needed.
Therefore, one may think of the process of resolving dependencies as of a pipeline that may produce results of different shapes when
requested.
Some use cases only require a dependency resolution result of one kind (for example, 'get all other fragments that a given
fragment can see'), and for them, it might be convenient to have a single facade returning just that kind of result.
This document describes all the granular tasks that one might put before dependency resolution implementations that work with the Kotlin
Project Model, without attaching them to specific use cases.
## Internal dependency expansion
> We may remove this section if it turns out that we are going to use modules for test-on-production dependencies and the like
We allow declaring only some of the fragment dependencies between fragments inside a single module, and the fragment dependencies for the
depending fragment's refines-children are inferred automatically. Also, declaring such a dependency implies that all refines-parents of the
dependency fragment should be visible, too, as well as everything it sees via its own expanded module fragment dependencies (as well as
transitive expansion results of the first-level expansion results, and so on).
The interface for this is `InternalDependencyExpansion` with a function that takes a `fragment` and returns all other fragments of the same
module that the fragment should see. For ease of explanation and diagnostics, the results are grouped by the actual declared fragment
dependency that led to each particular fragment having been added to the result.
Example:
```
commonMain commonTestFixtures < - - - - -commonTest
| | |
jvmAndJsMain < - - - - jvmAndJsTestFixtures jvmAndJsTest
| | |
jvmMain jvmTestFixtures jvmTest
```
Here, dashed arrows denote declared fragment dependencies. When we ask for fragments that `jvmAndJsTest` sees, we get:
`commonTestFixtures` and `jvmAndJsTestFixtures` because `commonTest` depends on `commonTestFixtures, and then also `commonMain` and
`jvmAndJsMain` because `jvmAndJsTestFixtures` depends on `jvmAndJsMain`.
Currently, the `DefaultInternalDependencyExpansion` implementation takes a function that matches variants of the module as if they take
part in variant-aware dependency resolution. This function may actually perform matching of the variants or may rely on additional
information (until attributes matching is implemented, it may search for explicit dependencies added between the variants).
## Dependency discovery
Given that dependency resolution may bring transtive dependencies, it is not enough to know what *declared dependencies* a fragment has to
be able to properly inspect the resolution results. You With just those, you won't be able to ask
'what fragments does this fragment sees from module `foo`?' if the module `foo` is only a transitive dependency.
This defines a task of *discovering module dependencies*, that is, finding which modules a fragment actually depends on, including the
modules brought in as transitive dependencies.
With the granular modules of the Kotlin Project Model, this not a trivial task. A module may reference another module as a dependency onl in
some of its fragments. Therefore, to decide whether a particular module `m` should be transitively included in the resolution results for
one of our fragments `f` we have to first find out whether `f` sees any other fragment that declares a dependency on `m`. This is another
dependency resolution task that is covered below.
## Module resolution
Given a module dependency, we should be able to build a Kotlin module for that dependency, as we will have to decompose the dependency on
the whole module to dependencies on its granular parts. These granular parts are the variants and fragments. So it is reasonable to build
a Kotlin module for a resolved dependency and reason about it in the same terms as we use for locally built modules.
This task in dependency resolution takes a module dependency and returns a Kotlin module. This may in fact be a Kotlin module that is built
locally (for dependencies that point to it directly).
## Variant resolution
This is a simple task that is very much like Gradle's variant aware dependency resolution: given a variant (not just a fragment) of a
consuming module and another dependency module, we should tell which of the dependency module's variants is the best match.
When we look at a consumer's variant, it is important to be able to pick a dependency's variant (and not as set of fragments), because
variants produce final binaries, and for the consumer to produce a final binary from its variant, it needs a guarantee that the producer
also could generate a complete binary (with all `expect`s covered by `actual`s). The confirmation of this is exactly the producer's variant.
## Fragment resolution
This is the task of dependency resolution that we ultimately need to find out which declarations a module fragment sees. Namely, given a
module dependency, we have to determine which of the dependency's fragments the consumer's fragment may see.
To do that, we have to perform variant resolution for each of the variants that the depending variant participates in, and then intersect
the refines-closures of the resolved variants, this will be the safe set of fragments whose declarations we may use because they are
available in any variant that we will compile the fragment for.
@@ -1,30 +0,0 @@
/*
* 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
// TODO: Add better Kotlin Variant Attributes management
// Ideally we should respect the nature of Attributes and express values in form of Complete Lattice
// Moreover we can add support for default and most used Value Types (eg. Enums)
open class KotlinAttributeKey(
val uniqueName: String
) {
override fun equals(other: Any?): Boolean =
other is KotlinAttributeKey && uniqueName == other.uniqueName
override fun hashCode(): Int =
uniqueName.hashCode()
}
// TODO: Introduce ENUM
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"
}
object KotlinNativeTargetAttribute : KotlinAttributeKey("org.jetbrains.kotlin.native.target")
@@ -1,126 +0,0 @@
/*
* 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.project.model
import java.io.File
/**
* Adapts Kotlin Compiler Plugin for Multiplatform Kotlin Project Model
* Build System uses this interface to identify applicable plugin artifacts and its options
* before executing actual Kotlin Compilation
*/
interface KpmCompilerPlugin {
/**
* Returns [PluginData] when applicable for [fragment] compilation
* Returns [null] if not applicable
*/
fun forMetadataCompilation(fragment: KpmFragment): PluginData?
/**
* Returns [PluginData] when applicable for [fragment] compilation
* Returns [null] if not applicable
*/
fun forNativeMetadataCompilation(fragment: KpmFragment): PluginData?
/**
* Returns [PluginData] when applicable for [variant] compilation
* Returns [null] if not applicable
*/
fun forPlatformCompilation(variant: KpmVariant): PluginData?
}
/**
* Plugin data can be used for changing some compilation request
*/
data class PluginData(
val pluginId: String,
val artifact: ArtifactCoordinates,
val options: List<PluginOption>
) {
// FIXME: (?) Is it common thing or gradle/maven centric?
data class ArtifactCoordinates(
val group: String,
val artifact: String,
val version: String? = null
)
}
sealed class PluginOption {
abstract val key: String
/**
* Indicates whether value of [PluginOption] should be stored for incremental build checks.
* Value changes of non-transient [PluginOption] will invalidate incremental caches.
*/
abstract val isTransient: Boolean
}
data class StringOption(
override val key: String,
val value: String,
override val isTransient: Boolean = false
) : PluginOption()
data class FilesOption(
override val key: String,
val files: List<File>,
/**
* Indicates whether FilesOption is used as input or output during compilation
* false means input
* true means output
*/
val isOutput: Boolean = false,
override val isTransient: Boolean = false
) : PluginOption()
// TODO: It should be part of "Compilation Process": KotlinModule.compilationRequestFor(METADATA | PLATFORM) -> CompilationRequest
// But there is no such thing at the moment :)
fun KpmFragment.metadataCompilationPluginData(): List<PluginData> =
containingModule
.plugins
.mapNotNull { plugin -> plugin.forMetadataCompilation(this) }
fun KpmFragment.nativeMetadataCompilationPluginData(): List<PluginData> =
containingModule
.plugins
.mapNotNull { plugin -> plugin.forNativeMetadataCompilation(this) }
fun KpmVariant.platformCompilationPluginData(): List<PluginData> =
containingModule
.plugins
.mapNotNull { plugin -> plugin.forPlatformCompilation(this) }
/**
* Represents trivial Compiler Plugin adapter for Kotlin Project Model
* Where Compiler Plugin can have common and native artifacts
*/
abstract class BasicKpmCompilerPlugin : KpmCompilerPlugin {
abstract val pluginId: String
protected abstract fun commonPluginArtifact(): PluginData.ArtifactCoordinates?
protected abstract fun nativePluginArtifact(): PluginData.ArtifactCoordinates?
protected abstract val pluginOptions: List<PluginOption>
override fun forMetadataCompilation(fragment: KpmFragment) = pluginDataOrNull(commonPluginArtifact())
override fun forNativeMetadataCompilation(fragment: KpmFragment) = pluginDataOrNull(nativePluginArtifact())
override fun forPlatformCompilation(variant: KpmVariant) =
when (variant.platform) {
KotlinPlatformTypeAttribute.NATIVE -> nativePluginArtifact()
else -> commonPluginArtifact()
}.let(::pluginDataOrNull)
private fun pluginDataOrNull(artifact: PluginData.ArtifactCoordinates?) =
if (artifact != null) PluginData(pluginId, artifact, pluginOptions)
else null
}
@@ -1,25 +0,0 @@
/*
* Copyright 2010-2022 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
interface KpmDependencyGraphResolver {
fun resolveDependencyGraph(requestingModule: KpmModule): KpmDependencyGraphResolution
}
sealed class KpmDependencyGraphResolution(open val requestingModule: KpmModule) {
class Unknown(requestingModule: KpmModule) : KpmDependencyGraphResolution(requestingModule)
open class KpmDependencyGraph(
requestingModule: KpmModule, open val root: KpmDependencyGraphNode
) : KpmDependencyGraphResolution(requestingModule)
}
// TODO: should this be a single graph for all dependency scopes as well, not just for all fragments?
open class KpmDependencyGraphNode(
open val module: KpmModule,
open val dependenciesByFragment: Map<KpmFragment, Iterable<KpmDependencyGraphNode>>
) {
override fun toString(): String = "node ${module}"
}
@@ -1,63 +0,0 @@
/*
* 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 org.jetbrains.kotlin.tooling.core.closure
import org.jetbrains.kotlin.tooling.core.withClosure
import java.io.File
interface KpmFragment {
val containingModule: KpmModule
val fragmentName: String
val languageSettings: LanguageSettings?
// TODO: should this be source roots or source files?
val kotlinSourceRoots: Iterable<File>
// TODO: scopes
val declaredModuleDependencies: Iterable<KpmModuleDependency>
val declaredRefinesDependencies: Iterable<KpmFragment>
val refinesClosure: Set<KpmFragment>
get() = this.closure { it.declaredRefinesDependencies }
val withRefinesClosure: Set<KpmFragment>
get() = this.withClosure { it.declaredRefinesDependencies }
companion object
}
val KpmFragment.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 KpmVariant.platform get() = variantAttributes[KotlinPlatformTypeAttribute]
val KpmVariant.nativeTarget get() = variantAttributes[KotlinNativeTargetAttribute]
open class KpmBasicFragment(
override val containingModule: KpmModule,
override val fragmentName: String,
override val languageSettings: LanguageSettings? = null
) : KpmFragment {
override val declaredRefinesDependencies: MutableSet<KpmBasicFragment> = mutableSetOf()
override val declaredModuleDependencies: MutableSet<KpmModuleDependency> = mutableSetOf()
override var kotlinSourceRoots: Iterable<File> = emptyList()
override fun toString(): String = "fragment $fragmentName"
}
@@ -1,66 +0,0 @@
/*
* 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 KpmFragmentsResolver {
fun getChosenFragments(
requestingFragment: KpmFragment,
dependencyModule: KpmModule
): KpmFragmentResolution
}
sealed class KpmFragmentResolution(val requestingFragment: KpmFragment, val dependencyModule: KpmModule) {
class ChosenFragments(
requestingFragment: KpmFragment,
dependencyModule: KpmModule,
val visibleFragments: Iterable<KpmFragment>,
val variantResolutions: Iterable<KpmVariantResolution>
) : KpmFragmentResolution(requestingFragment, dependencyModule)
class NotRequested(requestingFragment: KpmFragment, dependencyModule: KpmModule) :
KpmFragmentResolution(requestingFragment, dependencyModule)
// TODO: think about restricting calls with the type system to avoid partial functions in resolvers?
class Unknown(requestingFragment: KpmFragment, dependencyModule: KpmModule) :
KpmFragmentResolution(requestingFragment, dependencyModule)
}
class KpmDefaultFragmentsResolver(
private val variantResolver: KpmModuleVariantResolver
) : KpmFragmentsResolver {
override fun getChosenFragments(
requestingFragment: KpmFragment,
dependencyModule: KpmModule
): KpmFragmentResolution {
val dependingModule = requestingFragment.containingModule
val containingVariants = dependingModule.variantsContainingFragment(requestingFragment)
val chosenVariants = containingVariants.map { variantResolver.getChosenVariant(it, dependencyModule) }
// TODO: extend this to more cases with non-matching variants, revisit the behavior when no matching variant is found once we fix
// local publishing of libraries with missing host-specific parts (it breaks transitive dependencies now)
if (chosenVariants.none { it is KpmVariantResolution.KpmVariantMatch })
return KpmFragmentResolution.NotRequested(requestingFragment, dependencyModule)
val chosenFragments = chosenVariants.map { variantResolution ->
when (variantResolution) {
is KpmVariantResolution.KpmVariantMatch -> variantResolution.chosenVariant.withRefinesClosure
else -> emptySet()
}
}
val result = if (chosenFragments.isEmpty())
emptyList<KpmFragment>()
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 KpmFragmentResolution.ChosenFragments(requestingFragment, dependencyModule, result, chosenVariants)
}
}
@@ -1,31 +0,0 @@
/*
* 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
// TODO Gradle allows having multiple capabilities in a published module, we need to figure out how we can include them in the module IDs
interface KpmModule {
val moduleIdentifier: KpmModuleIdentifier
val fragments: Iterable<KpmFragment>
val variants: Iterable<KpmVariant>
get() = fragments.filterIsInstance<KpmVariant>()
val plugins: Iterable<KpmCompilerPlugin>
// TODO: isSynthetic?
}
open class KpmBasicModule(
override val moduleIdentifier: KpmModuleIdentifier
) : KpmModule {
override val fragments = mutableListOf<KpmBasicFragment>()
override val plugins = mutableListOf<KpmCompilerPlugin>()
override fun toString(): String = "module $moduleIdentifier"
}
@@ -1,15 +0,0 @@
/*
* 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
data class KpmModuleDependency(val moduleIdentifier: KpmModuleIdentifier) {
override fun toString(): String = "dependency $moduleIdentifier"
}
/**
* TODO other kinds of dependencies: non-Kotlin: cinterop, CocoaPods, NPM dependencies?
* support with different moduleIdentifiers? Introduce other kinds of dependencies than ModuleDependency?
*/
@@ -1,13 +0,0 @@
/*
* 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
// TODO ensure that resolvers are pluggable + custom dependency kinds (& result kinds?)
// 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 KpmModuleDependencyResolver {
fun resolveDependency(requestingModule: KpmModule, moduleDependency: KpmModuleDependency): KpmModule?
}
@@ -1,33 +0,0 @@
/*
* Copyright 2010-2022 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 java.io.Serializable
sealed class KpmModuleIdentifier(open val moduleClassifier: String?) : Serializable
data class KpmLocalModuleIdentifier(
val buildId: String,
val projectId: String,
override val moduleClassifier: String?
) : KpmModuleIdentifier(moduleClassifier) {
companion object {
private const val SINGLE_BUILD_ID = ":"
}
override fun toString(): String =
"project '$projectId'" +
moduleClassifier?.let { " / $it" }.orEmpty() +
buildId.takeIf { it != SINGLE_BUILD_ID }?.let { "(build '$it')" }.orEmpty()
}
data class KpmMavenModuleIdentifier(
val group: String,
val name: String,
override val moduleClassifier: String?
) : KpmModuleIdentifier(moduleClassifier) {
override fun toString(): String = "$group:$name" + moduleClassifier?.let { " / $it" }.orEmpty()
}
@@ -1,92 +0,0 @@
/*
* 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 KpmModuleVariantResolver {
/**
* Find the matching module variant of the [dependencyModule] for the consumer's [requestingVariant].
*
* This is a partial function. A particular resolver may not be capable to do variant matching for some [dependencyModule] or
* some [requestingVariant] (to such an extent that a resolver may only know how to resolve a single [dependencyModule]'s variants, or
* how to resolve module variants for a particular [requestingVariant]).
* In this case it should return [KpmVariantResolution.Unknown], and the caller (which might be an aggregating [KpmModuleVariantResolver])
* may consult other sources to get the variant match.
*/
fun getChosenVariant(requestingVariant: KpmVariant, dependencyModule: KpmModule): KpmVariantResolution
}
/**
* Represents the results of [dependencyModule]'s variant resolution for the [requestingVariant],
* usually done by some [KpmModuleVariantResolver].
*/
sealed class KpmVariantResolution(
val requestingVariant: KpmVariant,
val dependencyModule: KpmModule
) {
companion object {
fun fromMatchingVariants(
requestingVariant: KpmVariant,
dependencyModule: KpmModule,
matchingVariants: Collection<KpmVariant>
) = when (matchingVariants.size) {
0 -> KpmNoVariantMatch(requestingVariant, dependencyModule)
1 -> KpmVariantMatch(requestingVariant, dependencyModule, matchingVariants.single())
else -> KpmAmbiguousVariants(requestingVariant, dependencyModule, matchingVariants)
}
}
override fun toString(): String = when (this) {
is KpmVariantMatch -> "match: ${chosenVariant.fragmentName}"
is Unknown -> "unknown"
is NotRequested -> "not requested"
is KpmNoVariantMatch -> "no match"
is KpmAmbiguousVariants -> "ambiguity: ${matchingVariants.joinToString { it.fragmentName }}"
}
/**
* The resolver decided that the [chosenVariant] is the best variant match.
*/
class KpmVariantMatch(
requestingVariant: KpmVariant,
dependencyModule: KpmModule,
val chosenVariant: KpmVariant
) : KpmVariantResolution(requestingVariant, dependencyModule)
// TODO: think about restricting calls with the type system to avoid partial functions in resolvers?
class Unknown(requestingVariant: KpmVariant, dependencyModule: KpmModule) :
KpmVariantResolution(requestingVariant, dependencyModule)
/**
* Returned when the resolver detects that the [requestingVariant] does not depend on [dependencyModule] and therefore should not get
* any variant of that module at all.
*/
class NotRequested(requestingVariant: KpmVariant, dependencyModule: KpmModule) :
KpmVariantResolution(requestingVariant, dependencyModule)
/**
* Returned when the resolver could not find any matching of the [dependencyModule] for the [requestingVariant], or variant matching was
* done externally and the external system did not provide any details of the failure.
*/
class KpmNoVariantMatch(
requestingVariant: KpmVariant,
dependencyModule: KpmModule
) : KpmVariantResolution(requestingVariant, dependencyModule)
/**
* Returned when the resolver found multiple matching variants in the [dependencyModule] and failed to choose one of them as the
* best match for the [requestingVariant].
*/
class KpmAmbiguousVariants(
requestingVariant: KpmVariant,
dependencyModule: KpmModule,
val matchingVariants: Iterable<KpmVariant>
) : KpmVariantResolution(requestingVariant, dependencyModule)
}
@@ -1,19 +0,0 @@
/*
* Copyright 2010-2022 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
interface KpmVariant : KpmFragment {
val variantAttributes: Map<KotlinAttributeKey, String>
}
class KpmBasicVariant(
containingModule: KpmModule, fragmentName: String, languageSettings: LanguageSettings? = null
) : KpmBasicFragment(
containingModule, fragmentName, languageSettings
), KpmVariant {
override val variantAttributes: MutableMap<KotlinAttributeKey, String> = mutableMapOf()
override fun toString(): String = "variant $fragmentName"
}
@@ -1,20 +0,0 @@
/*
* 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.KpmModule
import org.jetbrains.kotlin.project.model.KpmFragment
import org.jetbrains.kotlin.project.model.KpmVariant
import org.jetbrains.kotlin.tooling.core.closure
fun KpmModule.variantsContainingFragment(fragment: KpmFragment): Iterable<KpmVariant> =
variants.filter { variant -> fragment in variant.withRefinesClosure }
fun KpmModule.findRefiningFragments(fragment: KpmFragment): Iterable<KpmFragment> {
return fragment.closure { seedFragment ->
fragments.filter { otherFragment -> seedFragment in otherFragment.declaredRefinesDependencies }
}
}
@@ -1,62 +0,0 @@
/*
* 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.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Assumptions.assumeTrue
import org.junit.jupiter.api.Test
internal class DefaultModuleFragmentsResolverTest {
val bundleFoo = simpleModuleBundle("foo")
val bundleBar = simpleModuleBundle("bar")
val fragmentResolver = KpmDefaultFragmentsResolver(MatchVariantsByExactAttributes())
@Test
fun testFragmentVisibility() {
val moduleFooMain = bundleFoo.main
val moduleBarMain = bundleBar.main
val expectedVisibleFragments = mapOf(
"common" to setOf("common"),
"jvmAndJs" to setOf("common", "jvmAndJs"),
"jsAndLinux" to setOf("common", "jsAndLinux"),
"jvm" to setOf("common", "jvmAndJs", "jvm"),
"js" to setOf("common", "jvmAndJs", "jsAndLinux", "js"),
"linux" to setOf("common", "jsAndLinux", "linux")
)
moduleBarMain.fragments.forEach { consumingFragment ->
val result = fragmentResolver.getChosenFragments(consumingFragment, moduleFooMain)
assertTrue(result is KpmFragmentResolution.ChosenFragments)
val expected = expectedVisibleFragments.getValue(consumingFragment.fragmentName)
assertEquals(expected, (result as KpmFragmentResolution.ChosenFragments).visibleFragments.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 = simpleModuleBundle("baz").main.apply {
variant("linux").variantAttributes.replace(KotlinNativeTargetAttribute, "notLinux")
}
val moduleFooMain = bundleFoo.main
val variantResolution = MatchVariantsByExactAttributes().getChosenVariant(dependingModule.variant("linux"), moduleFooMain)
assumeTrue(variantResolution is KpmVariantResolution.KpmNoVariantMatch)
val (commonMainResult, jsAndLinuxResult) = listOf("common", "jsAndLinux").map {
val chosenFragments = fragmentResolver.getChosenFragments(dependingModule.fragment(it), moduleFooMain)
assertTrue(chosenFragments is KpmFragmentResolution.ChosenFragments)
(chosenFragments as KpmFragmentResolution.ChosenFragments).visibleFragments.map { it.fragmentName }.toSet()
}
assertEquals(setOf("common", "jvmAndJs"), commonMainResult)
assertEquals(setOf("common", "jvmAndJs", "jsAndLinux", "js"), jsAndLinuxResult)
}
}
@@ -1,18 +0,0 @@
/*
* 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 : KpmModuleVariantResolver {
override fun getChosenVariant(requestingVariant: KpmVariant, dependencyModule: KpmModule): KpmVariantResolution {
val candidates = dependencyModule.variants
return candidates.filter { candidate ->
candidate.variantAttributes.all { (attributeKey, candidateValue) ->
attributeKey !in requestingVariant.variantAttributes.keys ||
candidateValue == requestingVariant.variantAttributes.getValue(attributeKey)
}
}.let { KpmVariantResolution.fromMatchingVariants(requestingVariant, dependencyModule, it) }
}
}
@@ -1,93 +0,0 @@
/*
* 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, classifier: String? = null) = KpmBasicModule(KpmLocalModuleIdentifier("current", name, classifier))
fun KpmBasicModule.fragment(vararg nameParts: String): KpmBasicFragment =
fragment(nameParts.drop(1).joinToString("", nameParts.first()) { it.capitalize() })
fun KpmBasicModule.fragment(name: String): KpmBasicFragment =
fragments.firstOrNull { it.fragmentName == name } ?: KpmBasicFragment(this, name).also { fragments.add(it) }
fun KpmBasicModule.variant(vararg nameParts: String): KpmBasicVariant =
variant(nameParts.drop(1).joinToString("", nameParts.first()) { it.capitalize() })
fun KpmBasicModule.variant(name: String): KpmBasicVariant =
fragments.firstOrNull { it.fragmentName == name }
?.let { it as? KpmBasicVariant ?: error("$name is not a variant") }
?: KpmBasicVariant(this, name).also { fragments.add(it) }
fun KpmModuleIdentifier.equalsWithoutClassifier(other: KpmModuleIdentifier) = when (this) {
is KpmLocalModuleIdentifier -> other is KpmLocalModuleIdentifier &&
KpmLocalModuleIdentifier(buildId, projectId, null) == KpmLocalModuleIdentifier(other.buildId, other.projectId, null)
is KpmMavenModuleIdentifier -> other is KpmMavenModuleIdentifier &&
KpmMavenModuleIdentifier(group, name, null) == KpmMavenModuleIdentifier(other.group, other.name, null)
else -> error("can't check equality yet")
}
fun KpmBasicFragment.depends(module: KpmBasicModule) {
this.declaredModuleDependencies += KpmModuleDependency(module.moduleIdentifier)
}
fun KpmBasicFragment.refinedBy(fragment: KpmBasicFragment) {
fragment.refines(this)
}
fun KpmBasicFragment.refines(fragment: KpmBasicFragment) {
require(fragment.containingModule == containingModule)
declaredRefinesDependencies.add(fragment)
}
// ---
internal data class ModuleBundle(val modules: List<KpmBasicModule>) {
val main: KpmBasicModule
get() = modules.single { it.moduleIdentifier.moduleClassifier == null }
operator fun get(modulePurpose: String): KpmBasicModule = when (modulePurpose) {
"main" -> main
else -> modules.single { it.moduleIdentifier.moduleClassifier == modulePurpose }
}
}
internal fun simpleModuleBundle(name: String): ModuleBundle {
fun createModule(purpose: String): KpmBasicModule =
module(name, purpose.takeIf { it != "main" }).apply {
val common = fragment("common")
val (jvm, js, linux) = listOf("jvm", "js", "linux").map { platform ->
variant(platform).apply {
variantAttributes[KotlinPlatformTypeAttribute] = when (platform) {
"jvm" -> KotlinPlatformTypeAttribute.JVM
"js" -> KotlinPlatformTypeAttribute.JS
else -> {
variantAttributes[KotlinNativeTargetAttribute] = platform
KotlinPlatformTypeAttribute.NATIVE
}
}
}
}
fragment("jvmAndJs").apply {
refines(common)
refinedBy(jvm)
refinedBy(js)
}
fragment("jsAndLinux").apply {
refines(common)
refinedBy(js)
refinedBy(linux)
}
}
val main = createModule("main")
val test = createModule("test")
test.fragment("common").depends(main)
return ModuleBundle(listOf(main, test))
}
@@ -1,56 +0,0 @@
/*
* Copyright 2010-2022 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.infra
import org.intellij.lang.annotations.Language
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
class KpmCoreCasesRenderingTests : KpmCoreCasesTestRunner {
@Test
override fun testSimpleProjectToProject(case: KpmTestCase) {
case.expectRenderedDsl(
"""
val SimpleProjectToProject = describeCase("SimpleProjectToProject") {
project("a") {
module("main") {
jvm()
macosX64()
common depends project("b")
}
}
project("b") {
module("main") {
jvm()
macosX64()
}
}
}
""".trimIndent()
)
}
@Test
override fun testSimpleTwoLevel(case: KpmTestCase) {
case.expectRenderedDsl(
"""
val SimpleTwoLevel = describeCase("SimpleTwoLevel") {
project("p") {
module("main") {
jvm()
}
}
}
""".trimIndent()
)
}
private fun KpmTestCase.expectRenderedDsl(@Language("kotlin") expected: String) {
Assertions.assertEquals(expected.trim(), this.renderDeclarationDsl().trim())
}
}
@@ -1,78 +0,0 @@
/*
* Copyright 2010-2022 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.coreCases
import org.jetbrains.kotlin.project.model.infra.KpmTestCase
/**
* A representation of a Core Test Case.
*
* # Main idea:
* - provides unified format for declaring project structure of core cases
* - sync tested core cases across all subsystems
*
* # How it works:
* - Core cases are defined inside [org.jetbrains.kotlin.project.model.coreCases]
* (see "Conventions" below for exact format to follow) with the help of a DSL in
* [org.jetbrains.kotlin.project.model.testDsl] package
*
* - Inherit [org.jetbrains.kotlin.project.model.infra.KpmCoreCasesTestRunner] in your
* test runner. It will:
* a) ensure that all core cases are covered and warn you when new one is added
* b) inject core cases instances for you automatically
*
* - You can also use [org.jetbrains.kotlin.kpm.GenerateKpmTests]
* if you don't need custom per-test-method assertions; it will generate test
* cases which just call `doTest` on a passed [KpmTestCaseDescriptor] in a manner, similar
* to other `*TestsGenerated`-runners
*
* # Conventions
* 1. All Core Cases should inherit [KpmTestCaseDescriptor] marker
* 2. All Core cases must reside in the [org.jetbrains.kotlin.project.model.coreCases]-package
* 3. Each Core Case should be declared in a separate .kt-file, with the name equal
* to the name of the case itself (`MyTestCase` -> `MyTestCase.kt`)
* 4. Test Runner-class should consist of methods, which are named in the following
* pattern: `test$caseName`
* 5. (Optional) Test methods can declare a parameter of a [KpmTestCase]-type. Testing
* infrastructure will inject an instance of corresponding [KpmTestCase]
* (correspondence is determined based on test method naming convention from pt. 4)
*
* # Custom data, assertions, etc.
* It is an explicit non-goal of this infrastructure to provide a DSL capable of
* expressing assertions needed by an arbitrary subsystem. Instead, two extensibility
* mechanisms are provided:
* - `extras`, as in [KpmTestCase.extras]: essentially a way to attach custom userdata
* to a given entity
* - if needed, one can declare a new Core Case based on the previous one by calling
* `describeCase` from "super-case" and then adding additional configuration
*/
sealed interface KpmTestCaseDescriptor {
val name: String
get() = this::class.simpleName ?: error("Can't get simpleName of a KpmTestCaseDescriptor ${this::class}. Is it an anonymous class?")
fun KpmTestCase.describeCase()
companion object {
val allCaseDescriptorsByNames: Map<String, KpmTestCaseDescriptor> by lazy {
doGetAllCases()
}
val allCasesNames: Set<String>
get() = allCaseDescriptorsByNames.keys
private fun doGetAllCases(): Map<String, KpmTestCaseDescriptor> = KpmTestCaseDescriptor::class.sealedSubclasses
.map {
requireNotNull(it.objectInstance) {
"Can't get object instance for $it. Check that it is declared as an `object` "
}
}
.associateBy { it.name }
}
}
fun KpmTestCaseDescriptor.instantiateCase(): KpmTestCase = KpmTestCase(name).apply { describeCase() }
@@ -1,23 +0,0 @@
/*
* Copyright 2010-2022 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.coreCases
import org.jetbrains.kotlin.project.model.infra.KpmTestCase
import org.jetbrains.kotlin.project.model.testDsl.*
object SimpleProjectToProject : KpmTestCaseDescriptor {
override fun KpmTestCase.describeCase() {
allModules {
jvm()
macosX64()
}
val a = project("a")
val b = project("b")
a.depends(b)
}
}
@@ -1,20 +0,0 @@
/*
* Copyright 2010-2022 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.coreCases
import org.jetbrains.kotlin.project.model.infra.KpmTestCase
import org.jetbrains.kotlin.project.model.testDsl.*
object SimpleTwoLevel : KpmTestCaseDescriptor {
override fun KpmTestCase.describeCase() {
project("p") {
allModules {
jvm()
macosX64()
}
}
}
}
@@ -1,43 +0,0 @@
/*
* Copyright 2010-2022 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.infra
import org.jetbrains.kotlin.project.model.coreCases.KpmTestCaseDescriptor
import org.jetbrains.kotlin.project.model.coreCases.instantiateCase
import org.junit.jupiter.api.extension.*
import java.lang.reflect.Method
class KpmCoreCasesJunitParameterResolver : ParameterResolver {
override fun supportsParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Boolean =
parameterContext.parameter.type == KpmTestCaseDescriptor::class.java
override fun resolveParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Any {
require(parameterContext.parameter.type == KpmTestCaseDescriptor::class.java)
val kpmCaseName = extensionContext.requiredTestMethod.kpmCaseName
val caseDescriptor = KpmTestCaseDescriptor.allCaseDescriptorsByNames[kpmCaseName]
requireNotNull(caseDescriptor) {
"Can't find KpmCoreCase for name $kpmCaseName while " +
"\n injecting parameter ${parameterContext.parameter} into \n" +
"${extensionContext.requiredTestMethod}"
}
return caseDescriptor.instantiateCase()
}
}
private val Method.kpmCaseName: String
get() {
val testCaseName = this.name.substringAfter("test")
require(testCaseName in KpmTestCaseDescriptor.allCasesNames) {
"Can't find matching core case for name ${testCaseName}.\n" +
"Please check that the test method follow pattern 'test\$caseName', \n" +
"where '\$caseName' is a name as declared in 'o.j.k.project.model.coreCases'-package\n" +
"\n" +
"Known cases:\n" +
"${KpmTestCaseDescriptor.allCasesNames}"
}
return testCaseName
}
@@ -1,67 +0,0 @@
/*
* Copyright 2010-2022 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.infra
import org.jetbrains.kotlin.project.model.coreCases.KpmTestCaseDescriptor
import org.jetbrains.kotlin.project.model.infra.generate.generateTestMethodsTemplateForCases
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
/**
* Base class for testing KPM Core cases
*
* All core cases are listed here as abstract tests and will be required,
* to be overridden introducing a compile-time check that as soon as new
* case is added, it will be covered in all inheritors.
*
* For situation when a new Core Case is added, but is mistakenly not added
* to this interface, there's a [checkAllCoreCasesCovered], which will enforce
* that all Core Cases have a respective method in this interface
*
* Additionally, this interface uses [KpmCoreCasesJunitParameterResolver], which
* will inject a corresponding [KpmTestCaseDescriptor] into a test-method based on the
* method's name
*/
@ExtendWith(KpmCoreCasesJunitParameterResolver::class)
interface KpmCoreCasesTestRunner {
@Test
@Throws(Exception::class)
fun testSimpleProjectToProject(case: KpmTestCase)
@Test
@Throws(Exception::class)
fun testSimpleTwoLevel(case: KpmTestCase)
@Test
@Throws(Exception::class)
fun checkAllCoreCasesCovered() {
val testRunnerClass = this::class.java
val testCasesNames = testRunnerClass.methods.asSequence()
.map { it.name }
.filter { it.startsWith("test") }
.map { it.substringAfter("test") }
.toSet()
val uncoveredCases = KpmTestCaseDescriptor.allCasesNames - testCasesNames
if (uncoveredCases.isNotEmpty()) {
Assertions.fail<Nothing>(
"""
Test runner '${testRunnerClass.canonicalName}'
has some KPM Core Test Cases uncovered:
${uncoveredCases.joinToString()}
""".trimIndent() + "\n\n" + fixSuggestion(uncoveredCases)
)
}
}
}
private fun fixSuggestion(missingCases: Set<String>): String =
"Please re-run \"Generate KPM Tests\"-task to generate missing methods,\n" +
"or insert the following scaffold if the test runner uses custom assertions:\n\n" +
generateTestMethodsTemplateForCases(missingCases)
@@ -1,79 +0,0 @@
/*
* 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.project.model.infra
import org.jetbrains.kotlin.project.model.*
import org.jetbrains.kotlin.project.model.testDsl.*
import org.jetbrains.kotlin.project.model.utils.ObservableIndexedCollection
import org.jetbrains.kotlin.tooling.core.MutableExtras
import org.jetbrains.kotlin.tooling.core.mutableExtrasOf
import java.io.File
interface KpmTestEntity {
val name: String
}
class KpmTestCase(
override val name: String,
) : KpmTestEntity {
val projects: ObservableIndexedCollection<TestKpmModuleContainer> = ObservableIndexedCollection()
val extras: MutableExtras = mutableExtrasOf()
override fun toString(): String = "Case $name"
}
class TestKpmModuleContainer(
val containingCase: KpmTestCase,
override val name: String,
) : KpmTestEntity {
val modules: ObservableIndexedCollection<TestKpmModule> = ObservableIndexedCollection()
val extras: MutableExtras = mutableExtrasOf()
fun applyDefaults() {
module("main")
}
override fun toString(): String = ":$name"
}
class TestKpmModule(
val containingProject: TestKpmModuleContainer,
override val moduleIdentifier: KpmModuleIdentifier,
) : KpmTestEntity, KpmModule {
override val fragments: ObservableIndexedCollection<TestKpmFragment> = ObservableIndexedCollection()
override val plugins: MutableSet<KpmCompilerPlugin> = mutableSetOf()
val extras: MutableExtras = mutableExtrasOf()
override val name: String
get() = moduleIdentifier.moduleClassifier ?: "main"
fun applyDefaults() {
fragment("common")
}
}
open class TestKpmFragment(
override val containingModule: TestKpmModule,
override val fragmentName: String,
) : KpmTestEntity, KpmFragment {
override var languageSettings: LanguageSettings? = null
val extras: MutableExtras = mutableExtrasOf()
override val kotlinSourceRoots: MutableList<File> = mutableListOf()
override val declaredModuleDependencies: MutableList<KpmModuleDependency> = mutableListOf()
override val declaredRefinesDependencies: MutableList<TestKpmFragment> = mutableListOf()
override val name: String get() = fragmentName
fun applyDefaults() {
refines(containingModule.common)
}
}
class TestKpmVariant(
containingModule: TestKpmModule,
fragmentName: String,
) : TestKpmFragment(containingModule, fragmentName), KpmTestEntity, KpmVariant {
override val variantAttributes: MutableMap<KotlinAttributeKey, String> = mutableMapOf()
}
@@ -1,25 +0,0 @@
/*
* Copyright 2010-2022 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.infra.generate
import org.jetbrains.kotlin.generators.MethodGenerator
import org.jetbrains.kotlin.generators.model.MethodModel
import org.jetbrains.kotlin.test.util.KtTestUtil
import org.jetbrains.kotlin.utils.Printer
object KpmCoreCaseTestMethodGenerator : MethodGenerator<KpmCoreCaseTestMethodModel>() {
override val kind: MethodModel.Kind
get() = KpmCoreCaseTestMethodModel.Kind
override fun generateSignature(method: KpmCoreCaseTestMethodModel, p: Printer) {
p.print("public void test${method.name}(KpmTestCase kpmCase) throws Exception")
}
override fun generateBody(method: KpmCoreCaseTestMethodModel, p: Printer) {
val filePath = KtTestUtil.getFilePath(method.pathToTestCase)
p.println("runTest(SourcesKt.addSourcesFromCanonicalFileStructure(kpmCase, new File(\"$filePath\")));")
}
}
@@ -1,30 +0,0 @@
/*
* Copyright 2010-2022 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.infra.generate
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.generators.model.MethodModel
import org.jetbrains.kotlin.test.util.KtTestUtil
import java.io.File
class KpmCoreCaseTestMethodModel(
override val name: String, // equals to name of corresponding KpmCoreCase
internal val pathToTestSourcesRootDir: File,
internal val pathToTestCase: File,
) : MethodModel {
object Kind : MethodModel.Kind()
override val dataString: String
get() {
val path = FileUtil.getRelativePath(pathToTestSourcesRootDir, pathToTestCase)!!
return KtTestUtil.getFilePath(File(path))
}
override val tags: List<String>
get() = emptyList()
override val kind: MethodModel.Kind
get() = Kind
}
@@ -1,95 +0,0 @@
/*
* Copyright 2010-2022 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.infra.generate
import org.jetbrains.kotlin.generators.model.AnnotationModel
import org.jetbrains.kotlin.generators.model.MethodModel
import org.jetbrains.kotlin.generators.model.TestClassModel
import org.jetbrains.kotlin.project.model.coreCases.KpmTestCaseDescriptor
import org.jetbrains.kotlin.project.model.infra.KpmTestCase
import org.jetbrains.kotlin.project.model.infra.generateTemplateCanonicalFileStructure
import org.jetbrains.kotlin.test.util.KtTestUtil
import java.io.File
class KpmCoreCasesTestClassModel(
// Root of testdata folder, inside it expected to find one folder per testcase
// E.g. [additionalTestDataRoot] = "foo/bar", and we have KpmCoreCases "A" and "B"
// Then folders "foo/bar/A" and "foo/bar/B" expected to exist and contain sources
// for cases A, B. If they do not exist, they will be auto-generated with template
// sources.
private val additionalTestDataRoot: File,
) : TestClassModel() {
// Metadata-annotations enable IDE support for tests, like "Navigate to test data" action
override val dataString: String // Will be used in @TestMetadata, needs to be path to folder with test data
get() = KtTestUtil.getFilePath(additionalTestDataRoot)
override val dataPathRoot: String? // Will be used in @com.intellij.testFramework.TestDataPath
get() = null // $PROJECT_ROOT will be used instead
override val name: String
get() = error("Unused by infra, shouldn't be called")
override val tags: List<String>
get() = emptyList()
override val innerTestClasses: Collection<TestClassModel>
get() = emptyList()
override val methods: Collection<MethodModel> by lazy {
doCollectMethods()
}
override val isEmpty: Boolean
get() = methods.isEmpty()
override val imports: Set<Class<*>>
get() = super.imports + setOf(
KpmTestCase::class.java,
this::class.java.classLoader.loadClass("org.jetbrains.kotlin.project.model.infra.SourcesKt") // file-facade :(
)
override val annotations: Collection<AnnotationModel>
get() = emptyList() // Don't need to annotate with `@ExtendWith`, because we inherit [KpmCoreCasesTestRunner]
private fun doCollectMethods(): Collection<MethodModel> {
val allCoreCasesNames: Set<String> = KpmTestCaseDescriptor.allCasesNames
val allAdditionalTestdata: Set<File> = additionalTestDataRoot.listFiles().orEmpty().toSet()
val methodModelsForCoreCasesWithExistingTestData = allAdditionalTestdata.map { testDataForCase ->
check(!testDataForCase.isFile) {
"Expected to find only folders in testdata root ${additionalTestDataRoot.absolutePath}\n," +
"but found a file ${testDataForCase.absolutePath}"
}
check(testDataForCase.name in allCoreCasesNames) {
"Each folder name in test data should correspond to some KPM Core Case.\n" +
" Found folder ${testDataForCase.name}\n" +
" All core cases: $allCoreCasesNames"
}
KpmCoreCaseTestMethodModel(
testDataForCase.name,
additionalTestDataRoot,
testDataForCase
)
}
val coreCasesNamesWithoutTestData = allCoreCasesNames - methodModelsForCoreCasesWithExistingTestData.mapTo(mutableSetOf()) {
it.name
}
val methodModelsForCoreCasesWithoutTestData = coreCasesNamesWithoutTestData.map {
val expectedTestDataPath = additionalTestDataRoot.resolve(it)
val kpmTestCaseDescriptor = KpmTestCaseDescriptor.allCaseDescriptorsByNames[it]!!
println("Generating template sources testdata for uncovered KPM Core Case $it at ${additionalTestDataRoot.path}")
kpmTestCaseDescriptor.generateTemplateCanonicalFileStructure(expectedTestDataPath)
KpmCoreCaseTestMethodModel(it, additionalTestDataRoot, expectedTestDataPath)
}
return methodModelsForCoreCasesWithExistingTestData + methodModelsForCoreCasesWithoutTestData
}
}
@@ -1,57 +0,0 @@
/*
* Copyright 2010-2022 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.infra.generate
import org.jetbrains.kotlin.generators.*
import org.jetbrains.kotlin.generators.model.*
import org.jetbrains.kotlin.generators.util.TestGeneratorUtil
import org.jetbrains.kotlin.project.model.infra.KpmCoreCasesTestRunner
import java.io.File
fun generateKpmTestCases(
dryRun: Boolean = false,
init: TestGroupSuite.() -> Unit,
) {
val suite = TestGroupSuite(DefaultTargetBackendComputer).apply {
init()
}
val mainClassName = TestGeneratorUtil.getMainClassName()
suite.forEachTestClassParallel { testClass ->
val (changed, testSourceFilePath) = NewTestGeneratorImpl(
listOf(KpmCoreCaseTestMethodGenerator)
).generateAndSave(testClass, dryRun, mainClassName)
if (changed) {
InconsistencyChecker.inconsistencyChecker(dryRun).add(testSourceFilePath)
}
}
}
// NB: [testRunnersSourceRoot] is a root-folder against which will be resolved a usual java-like folder structure
// based on packages of T
// E.g.: testRunnersSourceRoot = "foo/bar", T == "org.jetbrains.kotlin.AbstractMyClass"
// Then the resulting file will be created at "foo/bar/org/jetbrains/kotlin"
inline fun <reified T : KpmCoreCasesTestRunner> TestGroupSuite.kpmRunnerWithSources(testRunnersSourceRoot: String, testSourcesPath: String) {
testGroup(testRunnersSourceRoot, testSourcesPath) {
testClass<T> {
testModels += KpmCoreCasesTestClassModel(File(testSourcesPath))
}
}
}
internal fun generateTestMethodsTemplateForCases(cases: Set<String>, generateTestMethodBody: (String) -> String = { "TODO()" }): String {
return buildString {
for (case in cases) {
append(
"""
fun test$case(case: KpmTestCase) {
${generateTestMethodBody(case)}
}
""".trimIndent()
)
}
}
}
@@ -1,111 +0,0 @@
/*
* Copyright 2010-2022 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.infra
import org.jetbrains.kotlin.project.model.KpmLocalModuleIdentifier
import org.jetbrains.kotlin.project.model.KpmMavenModuleIdentifier
import org.jetbrains.kotlin.project.model.nativeTarget
import org.jetbrains.kotlin.project.model.platform
import org.jetbrains.kotlin.utils.Printer
@Suppress("unused") // useful for debugging
fun KpmTestCase.renderDeclarationDsl(): String {
val p = Printer(StringBuilder())
p.render(this)
return p.toString()
}
private fun Printer.render(case: KpmTestCase) {
println("val ${case.name} = describeCase(\"${case.name}\") {")
pushIndent()
val projectsSorted = case.projects.sortedBy { it.name }
for (project in projectsSorted) {
render(project)
if (project !== projectsSorted.last()) println()
}
popIndent()
println("}")
}
private fun Printer.render(project: TestKpmModuleContainer) {
println("project(\"${project.name}\") {")
pushIndent()
val modulesSorted = project.modules.sortedBy { it.name }
for (module in modulesSorted) {
render(module)
if (module !== modulesSorted.last()) println()
}
popIndent()
println("}")
}
private fun Printer.render(module: TestKpmModule) {
println("module(\"${module.name}\") {")
pushIndent()
val fragmentsSorted = module.fragments.sortedBy { it.name }
// printedFragmentDeclaration is needed for pretty-printing separating new line between fragments
// declarations and their dependencies only in case both are present (i.e. no trailing newlines)
var printedFragmentDeclaration = false
for (fragment in fragmentsSorted) {
printedFragmentDeclaration = printedFragmentDeclaration or renderFragmentDeclaration(fragment)
}
for (fragment in fragmentsSorted) {
renderFragmentDependencies(fragment, printedFragmentDeclaration)
}
popIndent()
println("}")
}
private fun Printer.renderFragmentDeclaration(fragment: TestKpmFragment): Boolean {
if (fragment.name == "common") return false
when {
fragment !is TestKpmVariant -> println("fragment(\"${fragment.name}\")")
fragment.platform == "jvm" -> println("jvm()")
fragment.platform == "js" -> println("js()")
fragment.nativeTarget == "linux" -> println("linux()")
fragment.nativeTarget == "macosX64" -> println("macosX64()")
fragment.nativeTarget == "android" -> println("android()")
else -> error("Unknown platform: ${fragment.platform}, nativeTarget=${fragment.nativeTarget}")
}
return true
}
private fun Printer.renderFragmentDependencies(fragment: TestKpmFragment, prependWithEmptyLine: Boolean) {
var printEmptyLineOnce: Boolean = prependWithEmptyLine
fun println(text: String) {
if (printEmptyLineOnce) {
this.println()
printEmptyLineOnce = false
}
this.println(text)
}
for (refinedFragment in fragment.declaredRefinesDependencies.sortedBy { it.name }) {
if (refinedFragment.name == "common") continue
println("${fragment.name} refines ${refinedFragment.name}")
}
val moduleDependenciesSorted = fragment.declaredModuleDependencies.sortedBy { it.moduleIdentifier.toString() }
for (dependencyModule in moduleDependenciesSorted) {
when (val id = dependencyModule.moduleIdentifier) {
is KpmLocalModuleIdentifier -> {
val projectId = id.projectId
println("${fragment.name} depends ${"project(\"$projectId\")"}")
}
is KpmMavenModuleIdentifier -> {
val group = id.group
val name = id.name
println("${fragment.name} depends ${"maven(\"$group\", \"$name\")"}")
}
}
}
}
@@ -1,111 +0,0 @@
/*
* Copyright 2010-2022 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.infra
import org.jetbrains.kotlin.project.model.coreCases.KpmTestCaseDescriptor
import org.jetbrains.kotlin.project.model.coreCases.instantiateCase
import java.io.File
/**
* In this file you can find support for decorating an existing [KpmTestCase] with sources
*
* The low-level API is [addSources]
*
* For the convenience, the concept of "canonical sources file structure" is introduced.
* You can ask to generate template of this structure in designated place by calling [generateTemplateCanonicalFileStructure],
* which is useful when you have a [KpmTestCase] and want to add some manually-written sources (test data, usually) to it.
*
* After that, you can use [addSourcesFromCanonicalFileStructure] to automatically decorate an existing
* [KpmTestCase] with sources pulled from the specified folder
*
* See [generateTemplateCanonicalFileStructure] KDoc for details on the canonical sources file structure
*/
/**
* Warning! Mutates passed test case
*/
fun KpmTestCase.addSourcesFromCanonicalFileStructure(root: File): KpmTestCase {
return addSources { fragment ->
val canonicalFragmentTestdataFolder = fragment.canonicalSourceFolderAbsolute(root)
require(canonicalFragmentTestdataFolder.exists()) {
"Can't find testdata for fragment $fragment at ${canonicalFragmentTestdataFolder.absolutePath}"
}
canonicalFragmentTestdataFolder.listFiles()?.toList().orEmpty()
}
}
/**
* Warning! Mutates passed test case
*/
fun KpmTestCase.addSources(sourcesForFragment: (TestKpmFragment) -> Iterable<File>): KpmTestCase {
val allFragments = projects.flatMap { it.modules.flatMap { it.fragments } }
for (fragment in allFragments) {
val sources = sourcesForFragment(fragment)
fragment.kotlinSourceRoots.addAll(sources)
}
return this
}
/**
* Generates a canonical source-files structure at designated [root] for a given [KpmTestCaseDescriptor]
*
* In general, each [TestKpmFragment] will have a folder formed as following:
* `root/$fragmentProject.name/$fragmentModule.name/$fragment.name`
*
* However, in order to reduce nesting for simple common cases, two amendments are applied:
* - if a given [KpmTestCaseDescriptor] has only one single [TestKpmModuleContainer], then the project folder is omitted,
* and modules of that project are embedded straight into the [root]
* - similarly, if a given [TestKpmModuleContainer] has only one single [TestKpmModule], then the module folder is omitted,
* and fragments of that module are embedded straight into the parent-directory.
* This rule is applied for each project separately.
*
* Both amendments can be applied simultaneously, so for a test case with a single project and single module, fragments will live
* directly inside [root]
*/
fun KpmTestCaseDescriptor.generateTemplateCanonicalFileStructure(root: File) {
val case = instantiateCase()
val allFragments = case.projects.flatMap { it.modules.flatMap { it.fragments } }
allFragments.forEach { fragment ->
val canonicalFragmentTestdataFolder = fragment.canonicalSourceFolderAbsolute(root)
canonicalFragmentTestdataFolder.mkdirs()
val templateSources = canonicalFragmentTestdataFolder.resolve(fragment.fragmentName + ".kt")
templateSources.writeText(PLACEHOLDER_SOURCES_TEXT)
}
}
private fun TestKpmFragment.canonicalSourceFolderAbsolute(root: File): File {
val pathRelativeToRoot = canonicalSourceFolderRelative()
return root.resolve(pathRelativeToRoot)
}
private fun TestKpmFragment.canonicalSourceFolderRelative(): File {
val module = containingModule
val project = module.containingProject
val case = project.containingCase
val isSingleModule = project.modules.size == 1
val isSingleProject = case.projects.size == 1
val pathParts = listOfNotNull(
project.name.takeIf { !isSingleProject },
module.name.takeIf { !isSingleModule },
fragmentName
)
return File(pathParts.joinToString(separator = File.separator))
}
private val PLACEHOLDER_SOURCES_TEXT = """
/**
* Generated by [org.jetbrains.kotlin.project.model.infra.${KpmTestCaseDescriptor::generateTemplateCanonicalFileStructure.name}]
*
* Write your testdata sources here, or remove the file, if it is not needed
*/
""".trimIndent()
@@ -1,24 +0,0 @@
/*
* Copyright 2010-2022 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.
*/
@file:Suppress("UnusedReceiverParameter") // receivers are convenient for DSL scoping
package org.jetbrains.kotlin.project.model.testDsl
import org.jetbrains.kotlin.project.model.KpmLocalModuleIdentifier
import org.jetbrains.kotlin.project.model.KpmMavenModuleIdentifier
import org.jetbrains.kotlin.project.model.infra.KpmTestEntity
fun KpmTestEntity.project(projectId: String): KpmLocalModuleIdentifier = KpmLocalModuleIdentifier("", projectId, null)
val KpmLocalModuleIdentifier.test: KpmLocalModuleIdentifier
get() = KpmLocalModuleIdentifier(buildId, projectId, "test")
// TODO: custom aux modules
// TODO: scopes
fun KpmTestEntity.maven(group: String, name: String): KpmMavenModuleIdentifier = KpmMavenModuleIdentifier(group, name, null)
// TODO: published aux modules
@@ -1,39 +0,0 @@
/*
* Copyright 2010-2022 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.testDsl
import org.jetbrains.kotlin.project.model.infra.KpmTestCase
import org.jetbrains.kotlin.project.model.infra.TestKpmFragment
import org.jetbrains.kotlin.project.model.infra.TestKpmModule
import org.jetbrains.kotlin.project.model.infra.TestKpmModuleContainer
fun KpmTestCase.project(
name: String,
applyDefaults: Boolean = true,
configure: TestKpmModuleContainer.() -> Unit = { }
): TestKpmModuleContainer {
val project = projects.getOrPut(name) { TestKpmModuleContainer(this, name) }
if (applyDefaults) project.applyDefaults()
project.configure()
return project
}
fun KpmTestCase.projectNamed(name: String) = projects[name]
?: error("Project with name $name doesn't exist. Existing projects: ${projects.joinToString { it.name }}")
fun KpmTestCase.allModules(configure: TestKpmModule.() -> Unit) {
projects.withAll {
modules.withAll(configure)
}
}
fun KpmTestCase.allFragments(configure: TestKpmFragment.() -> Unit) {
projects.withAll {
modules.withAll {
fragments.withAll(configure)
}
}
}
@@ -1,26 +0,0 @@
/*
* Copyright 2010-2022 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.
*/
@file:Suppress("unused")
package org.jetbrains.kotlin.project.model.testDsl
import org.jetbrains.kotlin.project.model.KpmModuleDependency
import org.jetbrains.kotlin.project.model.infra.TestKpmFragment
import org.jetbrains.kotlin.project.model.infra.TestKpmModule
fun TestKpmFragment.depends(module: TestKpmModule): TestKpmFragment {
declaredModuleDependencies += KpmModuleDependency(module.moduleIdentifier)
return this
}
fun TestKpmFragment.refines(fragment: TestKpmFragment): TestKpmFragment {
declaredRefinesDependencies += fragment
return this
}
fun TestKpmFragment.fragment(name: String, applyDefaults: Boolean = true, configure: TestKpmFragment.() -> Unit = { }): TestKpmFragment {
return containingModule.fragment(name, applyDefaults, configure).also { subFragment -> subFragment.refines(this) }
}
@@ -1,43 +0,0 @@
/*
* Copyright 2010-2022 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.
*/
@file:Suppress("unused")
package org.jetbrains.kotlin.project.model.testDsl
import org.jetbrains.kotlin.project.model.KpmLocalModuleIdentifier
import org.jetbrains.kotlin.project.model.infra.TestKpmModuleContainer
import org.jetbrains.kotlin.project.model.infra.TestKpmModule
fun TestKpmModuleContainer.allModules(action: TestKpmModule.() -> Unit) {
modules.withAll(action)
}
fun TestKpmModuleContainer.module(name: String, applyDefaults: Boolean = true, configure: TestKpmModule.() -> Unit = { }): TestKpmModule {
val module = modules.getOrPut(name) {
val id = KpmLocalModuleIdentifier(
buildId = "",
projectId = this.name,
moduleClassifier = name.takeIf { it != "main" }
)
val module = TestKpmModule(this, id)
if (applyDefaults) module.applyDefaults()
module
}
configure(module)
return module
}
fun TestKpmModuleContainer.moduleNamed(name: String): TestKpmModule =
modules[name] ?: error("Module with name $name doesn't exist. Existing modules: ${modules.joinToString { it.name }}")
val TestKpmModuleContainer.main get() = moduleNamed("main")
val TestKpmModuleContainer.test get() = moduleNamed("test")
fun TestKpmModuleContainer.depends(other: TestKpmModuleContainer): TestKpmModuleContainer {
main.depends(other)
return this
}
@@ -1,52 +0,0 @@
/*
* Copyright 2010-2022 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.testDsl
import org.jetbrains.kotlin.project.model.KotlinPlatformTypeAttribute
import org.jetbrains.kotlin.project.model.infra.TestKpmFragment
import org.jetbrains.kotlin.project.model.infra.TestKpmModuleContainer
import org.jetbrains.kotlin.project.model.infra.TestKpmModule
import org.jetbrains.kotlin.project.model.infra.TestKpmVariant
fun TestKpmModule.fragment(name: String, applyDefaults: Boolean = true, configure: TestKpmFragment.() -> Unit = { }): TestKpmFragment {
val result = fragments.getOrPut(name) {
val fragment = TestKpmFragment(this, name)
fragment
}
if (applyDefaults) result.applyDefaults()
return result.also(configure)
}
fun TestKpmModule.fragmentNamed(name: String): TestKpmFragment =
fragments[name] ?: error("Fragment with name $name doesn't exist. Existing fragments ${fragments.joinToString { it.name }}")
fun TestKpmModule.variant(
name: String,
platform: String, // from KotlinPlatformTypeAttribute
applyDefaults: Boolean = true,
configure: TestKpmVariant.() -> Unit = { }
): TestKpmVariant {
val result = fragments.getOrPut(name) {
val variant = TestKpmVariant(this, name)
variant.variantAttributes[KotlinPlatformTypeAttribute] = platform
variant
} as TestKpmVariant
if (applyDefaults) result.applyDefaults()
result.configure()
return result
}
fun TestKpmModule.depends(otherModule: TestKpmModule): TestKpmModule {
common.depends(otherModule)
return this
}
fun TestKpmModule.depends(otherProject: TestKpmModuleContainer): TestKpmModule {
common.depends(otherProject.main)
return this
}
val TestKpmModule.common: TestKpmFragment get() = fragmentNamed("common")
@@ -1,32 +0,0 @@
/*
* Copyright 2010-2022 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.
*/
@file:Suppress("unused")
package org.jetbrains.kotlin.project.model.testDsl
import org.jetbrains.kotlin.project.model.KotlinNativeTargetAttribute
import org.jetbrains.kotlin.project.model.KotlinPlatformTypeAttribute
import org.jetbrains.kotlin.project.model.infra.TestKpmModule
import org.jetbrains.kotlin.project.model.infra.TestKpmVariant
fun TestKpmModule.jvm(configure: TestKpmVariant.() -> Unit = { }) =
variant("jvm", KotlinPlatformTypeAttribute.JVM, configure = configure)
fun TestKpmModule.js(configure: TestKpmVariant.() -> Unit = { }) =
variant("jvm", KotlinPlatformTypeAttribute.JS, configure = configure)
fun TestKpmModule.android(configure: TestKpmVariant.() -> Unit = { }) =
variant("android", KotlinPlatformTypeAttribute.ANDROID_JVM, configure = configure)
fun TestKpmModule.nativeVariant(name: String, nativeTarget: String, configure: TestKpmVariant.() -> Unit = { }): TestKpmVariant {
val variant = variant(name, KotlinPlatformTypeAttribute.NATIVE)
variant.variantAttributes[KotlinNativeTargetAttribute] = nativeTarget
variant.configure()
return variant
}
fun TestKpmModule.linux(configure: TestKpmVariant.() -> Unit = { }) = nativeVariant("linux", "linux", configure)
fun TestKpmModule.macosX64(configure: TestKpmVariant.() -> Unit = { }) = nativeVariant("macosX64", "macosX64", configure)
@@ -1,31 +0,0 @@
/*
* Copyright 2010-2022 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.infra.KpmTestEntity
class ObservableIndexedCollection<T : KpmTestEntity> private constructor(
private val _items: MutableMap<String, T>
) : Collection<T> by _items.values {
constructor() : this(mutableMapOf())
private val allItemsActions = mutableListOf<T.() -> Unit>()
fun add(item: T) {
_items[item.name] = item
allItemsActions.forEach { action -> action(item) }
}
fun withAll(action: T.() -> Unit) {
_items.values.forEach(action)
allItemsActions.add(action)
}
fun getOrPut(name: String, defaultValue: () -> T): T =
if (!_items.contains(name)) defaultValue().also { add(it) } else _items[name]!!
operator fun get(name: String): T? = _items[name]
}
@@ -106,38 +106,6 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-project-model</artifactId>
<version>ArtifactsTest.version</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<artifactId>kotlin-reflect</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
</exclusion>
<exclusion>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
</exclusion>
<exclusion>
<artifactId>kotlin-stdlib-jdk7</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
</exclusion>
<exclusion>
<artifactId>kotlin-stdlib</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
</exclusion>
<exclusion>
<artifactId>kotlin-script-runtime</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
</exclusion>
<exclusion>
<artifactId>kotlin-stdlib-common</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-tooling-core</artifactId>
@@ -52,7 +52,6 @@ fun updateCompilerXml() {
"libraries/tools/kotlin-noarg",
"libraries/tools/kotlin-osgi-bundle",
"libraries/tools/kotlin-prepush-hook",
"libraries/tools/kotlin-project-model",
"libraries/tools/kotlin-sam-with-receiver",
"libraries/tools/kotlin-serialization",
"libraries/tools/kotlin-serialization-unshaded",
@@ -69,7 +68,6 @@ fun updateCompilerXml() {
"native/commonizer-api",
"libraries/examples",
"libraries/tools/kotlin-gradle-plugin-idea-proto",
"libraries/tools/kotlin-project-model-tests-generator",
"repo/gradle-settings-conventions",
"plugins/fir-plugin-prototype/plugin-annotations",
)
@@ -47,7 +47,6 @@ val kotlinGradlePluginAndItsRequired = arrayOf(
":kotlin-compiler-runner",
":kotlin-daemon-embeddable",
":kotlin-daemon-client",
":kotlin-project-model",
":kotlin-gradle-plugins-bom",
":kotlin-gradle-plugin-api",
":kotlin-gradle-plugin-annotations",
-4
View File
@@ -191,8 +191,6 @@ include ":kotlin-imports-dumper-compiler-plugin",
":generators:test-generator",
":tools:kotlinp",
":kotlin-build-tools-enum-compat",
":kotlin-project-model",
":kotlin-project-model-tests-generator",
":kotlin-gradle-compiler-types",
":kotlin-gradle-plugin-api",
":kotlin-gradle-plugin-annotations",
@@ -737,8 +735,6 @@ project(':kotlin-assignment-compiler-plugin.embeddable').projectDir = "$rootDir/
project(':tools:kotlinp').projectDir = "$rootDir/libraries/tools/kotlinp" as File
project(":kotlin-build-tools-enum-compat").projectDir = "$rootDir/libraries/tools/kotlin-build-tools-enum-compat" as File
project(':kotlin-project-model').projectDir = "$rootDir/libraries/tools/kotlin-project-model" as File
project(':kotlin-project-model-tests-generator').projectDir = "$rootDir/libraries/tools/kotlin-project-model-tests-generator" as File
project(':kotlin-gradle-compiler-types').projectDir = "$rootDir/libraries/tools/kotlin-gradle-compiler-types" as File
project(':kotlin-gradle-plugin-api').projectDir = "$rootDir/libraries/tools/kotlin-gradle-plugin-api" as File
project(':kotlin-gradle-plugin-annotations').projectDir = "$rootDir/libraries/tools/kotlin-gradle-plugin-annotations" as File