[KPM] Implement initial kpm/idea dependency resolution

^KT-51386 Verification Pending
This commit is contained in:
sebastian.sellmair
2022-03-07 11:12:22 +01:00
committed by Space
parent 8b84ed4978
commit 1a5fc84080
61 changed files with 2278 additions and 120 deletions
@@ -6,6 +6,6 @@ plugins {
dependencies {
api(project(":native:kotlin-native-utils"))
api(project(":kotlin-project-model"))
implementation(project(":kotlin-tooling-core"))
compileOnly("com.android.tools.build:gradle:3.4.0")
}
}
@@ -13,7 +13,9 @@ import org.gradle.api.file.SourceDirectorySet
import org.jetbrains.kotlin.gradle.plugin.HasKotlinDependencies
import org.jetbrains.kotlin.gradle.plugin.LanguageSettingsBuilder
import org.jetbrains.kotlin.project.model.KotlinModuleFragment
import org.jetbrains.kotlin.project.model.withRefinesClosure
import org.jetbrains.kotlin.project.model.utils.variantsContainingFragment
import org.jetbrains.kotlin.tooling.core.closure
import org.jetbrains.kotlin.tooling.core.withClosure
interface KotlinGradleFragment : KotlinModuleFragment, HasKotlinDependencies, KotlinFragmentDependencyConfigurations, Named {
override val kotlinSourceRoots: SourceDirectorySet
@@ -58,5 +60,14 @@ interface KotlinGradleFragment : KotlinModuleFragment, HasKotlinDependencies, Ko
listOf(transitiveApiConfiguration.name, transitiveImplementationConfiguration.name)
}
val KotlinGradleFragment.withRefinesClosure: Set<KotlinGradleFragment>
get() = this.withClosure { it.directRefinesDependencies }
val KotlinGradleFragment.refinesClosure: Set<KotlinGradleFragment>
get() = (this as KotlinModuleFragment).withRefinesClosure.mapTo(mutableSetOf()) { it as KotlinGradleFragment }
get() = this.closure { it.directRefinesDependencies }
val KotlinGradleFragment.path: String
get() = "${project.path}/${containingModule.name}/$fragmentName"
val KotlinGradleFragment.containingVariants: Set<KotlinGradleVariant>
get() = containingModule.variantsContainingFragment(this).map { it as KotlinGradleVariant }.toSet()
@@ -1,5 +1,6 @@
plugins {
kotlin("jvm")
`java-test-fixtures`
}
object BackwardsCompatibilityTestConfiguration {
@@ -18,9 +19,14 @@ dependencies {
testImplementation(gradleKotlinDsl())
testImplementation(project(":kotlin-gradle-plugin"))
testImplementation(project(":kotlin-test:kotlin-test-junit"))
testImplementation("org.reflections:reflections:0.10.2") {
because("Tests on the object graph are performed. This library will find implementations of interfaces at runtime")
}
testFixturesImplementation(gradleApi())
testFixturesImplementation(gradleKotlinDsl())
testFixturesImplementation(project(":kotlin-test:kotlin-test-junit"))
}
publish()
@@ -39,7 +45,13 @@ run {
val version = if (isSnapshotTest) properties["defaultSnapshotVersion"].toString()
else BackwardsCompatibilityTestConfiguration.minimalBackwardsCompatibleVersion
val minimalBackwardsCompatibleVersionTestClasspath by configurations.creating
val minimalBackwardsCompatibleVersionTestClasspath by configurations.creating {
isCanBeResolved = true
isCanBeConsumed = false
attributes {
attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME))
}
}
dependencies {
minimalBackwardsCompatibleVersionTestClasspath("org.jetbrains.kotlin:kotlin-gradle-plugin-idea:$version")
@@ -51,9 +63,17 @@ run {
if (isSnapshotTest) logger.quiet("Running test against snapshot: $version")
else logger.quiet("Running test against $version")
val resolvedClasspath = minimalBackwardsCompatibleVersionTestClasspath.files
if (resolvedClasspath.none { "kotlin-gradle-plugin-idea-$version.jar" in it.path }) {
throw IllegalStateException(buildString {
appendLine("Bad backwardsCompatibilityClasspath: $resolvedClasspath")
appendLine("Dependencies:${minimalBackwardsCompatibleVersionTestClasspath.allDependencies.joinToString()}")
})
}
systemProperty(
"backwardsCompatibilityClasspath",
minimalBackwardsCompatibleVersionTestClasspath.files.joinToString(";") { it.absolutePath }
resolvedClasspath.joinToString(";") { it.absolutePath }
)
}
}
@@ -0,0 +1,131 @@
/*
* 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("FunctionName")
package org.jetbrains.kotlin.gradle.kpm.idea
import org.jetbrains.kotlin.gradle.kpm.KotlinExternalModelContainer
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinDependency.Companion.CLASSPATH_BINARY_TYPE
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinDependency.Companion.DOCUMENTATION_BINARY_TYPE
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinDependency.Companion.SOURCES_BINARY_TYPE
import java.io.File
import java.io.Serializable
sealed interface IdeaKotlinDependency : Serializable {
val external: KotlinExternalModelContainer
companion object {
const val CLASSPATH_BINARY_TYPE = "org.jetbrains.binary.type.classpath"
const val SOURCES_BINARY_TYPE = "org.jetbrains.binary.type.sources"
const val DOCUMENTATION_BINARY_TYPE = "org.jetbrains.binary.type.documentation"
}
}
sealed interface IdeaKotlinSourceDependency : IdeaKotlinDependency {
val buildId: String
val projectPath: String
val projectName: String
val kotlinModuleName: String
val kotlinModuleClassifier: String?
val kotlinFragmentName: String
}
sealed interface IdeaKotlinBinaryCoordinates : Serializable {
val group: String
val module: String
val version: String
val kotlinModuleName: String?
val kotlinFragmentName: String?
}
sealed interface IdeaKotlinBinaryDependency : IdeaKotlinDependency {
val coordinates: IdeaKotlinBinaryCoordinates?
}
sealed interface IdeaKotlinUnresolvedBinaryDependency : IdeaKotlinBinaryDependency {
val cause: String?
}
sealed interface IdeaKotlinResolvedBinaryDependency : IdeaKotlinBinaryDependency {
val binaryType: String
val binaryFile: File
}
val IdeaKotlinResolvedBinaryDependency.isSourcesType get() = binaryType == SOURCES_BINARY_TYPE
val IdeaKotlinResolvedBinaryDependency.isDocumentationType get() = binaryType == DOCUMENTATION_BINARY_TYPE
val IdeaKotlinResolvedBinaryDependency.isClasspathType get() = binaryType == CLASSPATH_BINARY_TYPE
@InternalKotlinGradlePluginApi
data class IdeaKotlinSourceDependencyImpl(
override val buildId: String,
override val projectPath: String,
override val projectName: String,
override val kotlinModuleName: String,
override val kotlinModuleClassifier: String?,
override val kotlinFragmentName: String,
override val external: KotlinExternalModelContainer = KotlinExternalModelContainer.Empty
) : IdeaKotlinSourceDependency {
override fun toString(): String {
return "project://$buildId:$projectPath:$kotlinModuleName:$kotlinFragmentName"
}
@InternalKotlinGradlePluginApi
companion object {
private const val serialVersionUID = 0L
}
}
@InternalKotlinGradlePluginApi
data class IdeaKotlinBinaryCoordinatesImpl(
override val group: String,
override val module: String,
override val version: String,
override val kotlinModuleName: String? = null,
override val kotlinFragmentName: String? = null
) : IdeaKotlinBinaryCoordinates {
override fun toString(): String {
return "$group:$module:$version" +
(if (kotlinModuleName != null) ":$kotlinModuleName" else "") +
(if (kotlinFragmentName != null) ":$kotlinFragmentName" else "")
}
companion object {
private const val serialVersionUID = 0L
}
}
@InternalKotlinGradlePluginApi
data class IdeaKotlinResolvedBinaryDependencyImpl(
override val coordinates: IdeaKotlinBinaryCoordinates?,
override val binaryType: String,
override val binaryFile: File,
override val external: KotlinExternalModelContainer = KotlinExternalModelContainer.Empty
) : IdeaKotlinResolvedBinaryDependency {
override fun toString(): String {
return "${binaryType.split(".").last()}://$coordinates/${binaryFile.name}"
}
@InternalKotlinGradlePluginApi
companion object {
private const val serialVersionUID = 0L
}
}
@InternalKotlinGradlePluginApi
data class IdeaKotlinUnresolvedBinaryDependencyImpl(
override val cause: String?,
override val coordinates: IdeaKotlinBinaryCoordinates?,
override val external: KotlinExternalModelContainer = KotlinExternalModelContainer.Empty
) : IdeaKotlinUnresolvedBinaryDependency {
@InternalKotlinGradlePluginApi
companion object {
private const val serialVersionUID = 0L
}
}
@@ -11,8 +11,9 @@ import java.io.Serializable
interface IdeaKotlinFragment : Serializable {
val name: String
val moduleIdentifier: IdeaKotlinModuleIdentifier
val platforms: Set<IdeaKotlinPlatform>
val languageSettings: IdeaKotlinLanguageSettings?
val dependencies: List<IdeaKotlinFragmentDependency>
val dependencies: List<IdeaKotlinDependency>
val directRefinesDependencies: List<IdeaKotlinFragment>
val sourceDirectories: List<IdeaKotlinSourceDirectory>
val resourceDirectories: List<IdeaKotlinResourceDirectory>
@@ -23,8 +24,9 @@ interface IdeaKotlinFragment : Serializable {
data class IdeaKotlinFragmentImpl(
override val name: String,
override val moduleIdentifier: IdeaKotlinModuleIdentifier,
override val platforms: Set<IdeaKotlinPlatform>,
override val languageSettings: IdeaKotlinLanguageSettings?,
override val dependencies: List<IdeaKotlinFragmentDependency>,
override val dependencies: List<IdeaKotlinDependency>,
override val directRefinesDependencies: List<IdeaKotlinFragment>,
override val sourceDirectories: List<IdeaKotlinSourceDirectory>,
override val resourceDirectories: List<IdeaKotlinResourceDirectory>,
@@ -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.gradle.kpm.idea
import java.io.Serializable
interface IdeaKotlinFragmentDependency : Serializable
@Suppress("unused")
@InternalKotlinGradlePluginApi
class IdeaKotlinFragmentDependencyImpl: IdeaKotlinFragmentDependency {
@InternalKotlinGradlePluginApi
companion object {
const val serialVersionUID = 0L
}
}
@@ -8,12 +8,14 @@ package org.jetbrains.kotlin.gradle.kpm.idea
import java.io.Serializable
interface IdeaKotlinModule : Serializable {
val name: String
val moduleIdentifier: IdeaKotlinModuleIdentifier
val fragments: List<IdeaKotlinFragment>
}
@InternalKotlinGradlePluginApi
data class IdeaKotlinModuleImpl(
override val name: String,
override val moduleIdentifier: IdeaKotlinModuleIdentifier,
override val fragments: List<IdeaKotlinFragment>
) : IdeaKotlinModule {
@@ -0,0 +1,100 @@
/*
* 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.gradle.kpm.idea
import java.io.Serializable
sealed interface IdeaKotlinPlatform : Serializable {
val platformType: String
val platformDetails: IdeaKotlinPlatformDetails?
companion object {
val unknown: IdeaKotlinPlatform = IdeaKotlinPlatformImpl("unknown", null)
const val wasmPlatformType = "wasm"
const val nativePlatformType = "native"
const val jvmPlatformType = "jvm"
const val jsPlatformType = "js"
}
}
sealed interface IdeaKotlinPlatformDetails : Serializable
sealed interface IdeaKotlinWasmPlatformDetails : IdeaKotlinPlatformDetails
sealed interface IdeaKotlinNativePlatformDetails : IdeaKotlinPlatformDetails {
val konanTarget: String
}
sealed interface IdeaKotlinJvmPlatformDetails : IdeaKotlinPlatformDetails {
val jvmTarget: String
}
sealed interface IdeaKotlinJsPlatformDetails : IdeaKotlinPlatformDetails {
val isIr: Boolean
}
@InternalKotlinGradlePluginApi
fun IdeaKotlinPlatform.Companion.wasm(): IdeaKotlinPlatform {
return IdeaKotlinPlatformImpl(wasmPlatformType, IdeaKotlinWasmPlatformDetailsImpl)
}
@InternalKotlinGradlePluginApi
fun IdeaKotlinPlatform.Companion.native(konanTarget: String): IdeaKotlinPlatform {
return IdeaKotlinPlatformImpl(nativePlatformType, IdeaKotlinNativePlatformDetailsImpl(konanTarget))
}
@InternalKotlinGradlePluginApi
fun IdeaKotlinPlatform.Companion.jvm(jvmTarget: String): IdeaKotlinPlatform {
return IdeaKotlinPlatformImpl(jvmPlatformType, IdeaKotlinJvmPlatformDetailsImpl(jvmTarget))
}
@InternalKotlinGradlePluginApi
fun IdeaKotlinPlatform.Companion.js(isIr: Boolean): IdeaKotlinPlatform {
return IdeaKotlinPlatformImpl(jsPlatformType, IdeaKotlinJsPlatformDetailsImpl(isIr))
}
val IdeaKotlinPlatform.isWasm get() = platformType == IdeaKotlinPlatform.wasmPlatformType
val IdeaKotlinPlatform.isNative get() = platformType == IdeaKotlinPlatform.nativePlatformType
val IdeaKotlinPlatform.isJvm get() = platformType == IdeaKotlinPlatform.jvmPlatformType
val IdeaKotlinPlatform.isJs get() = platformType == IdeaKotlinPlatform.jsPlatformType
val IdeaKotlinPlatform.nativeOrNull get() = (platformDetails as? IdeaKotlinNativePlatformDetails)
val IdeaKotlinPlatform.jvmOrNull get() = (platformDetails as? IdeaKotlinJvmPlatformDetails)
val IdeaKotlinPlatform.jsOrNull get() = (platformDetails as? IdeaKotlinJsPlatformDetails)
private data class IdeaKotlinPlatformImpl(
override val platformType: String, override val platformDetails: IdeaKotlinPlatformDetails?
) : IdeaKotlinPlatform {
companion object {
const val serialVersionUID = 0L
}
}
private object IdeaKotlinWasmPlatformDetailsImpl : IdeaKotlinWasmPlatformDetails {
private const val serialVersionUID = 0L
}
private data class IdeaKotlinJvmPlatformDetailsImpl(override val jvmTarget: String) : IdeaKotlinJvmPlatformDetails {
private companion object {
const val serialVersionUID = 0L
}
}
private data class IdeaKotlinNativePlatformDetailsImpl(override val konanTarget: String) : IdeaKotlinNativePlatformDetails {
private companion object {
const val serialVersionUID = 0L
}
}
private data class IdeaKotlinJsPlatformDetailsImpl(override val isIr: Boolean) : IdeaKotlinJsPlatformDetails {
private companion object {
const val serialVersionUID = 0L
}
}
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.gradle.kpm.idea
import java.io.Serializable
interface IdeaKotlinVariant : IdeaKotlinFragment, Serializable {
val platform: IdeaKotlinPlatform
val variantAttributes: Map<String, String>
val compilationOutputs: IdeaKotlinCompilationOutput
}
@@ -15,6 +16,7 @@ interface IdeaKotlinVariant : IdeaKotlinFragment, Serializable {
@InternalKotlinGradlePluginApi
data class IdeaKotlinVariantImpl(
internal val fragment: IdeaKotlinFragment,
override val platform: IdeaKotlinPlatform,
override val variantAttributes: Map<String, String>,
override val compilationOutputs: IdeaKotlinCompilationOutput,
) : IdeaKotlinVariant, IdeaKotlinFragment by fragment {
@@ -1,4 +1,5 @@
import org.gradle.api.Project
import org.gradle.api.artifacts.verification.DependencyVerificationMode
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.configurationcache.extensions.serviceOf
import org.gradle.testfixtures.ProjectBuilder
@@ -20,5 +21,8 @@ fun Project.buildIdeaKotlinProjectModel(): IdeaKotlinProjectModel {
fun createKpmProject(): Pair<ProjectInternal, KotlinPm20ProjectExtension> {
val project = ProjectBuilder.builder().build() as ProjectInternal
project.plugins.apply(KotlinPm20PluginWrapper::class.java)
project.gradle.startParameter.dependencyVerificationMode = DependencyVerificationMode.OFF
project.repositories.mavenLocal()
project.repositories.maven { it.setUrl("https://cache-redirector.jetbrains.com/maven-central") }
return project to project.extensions.getByType(KotlinPm20ProjectExtension::class.java)
}
@@ -5,8 +5,8 @@
package org.jetbrains.kotlin.gradle.kpm
import deserialize
import serialize
import org.jetbrains.kotlin.gradle.kpm.idea.testFixtures.deserialize
import org.jetbrains.kotlin.gradle.kpm.idea.testFixtures.serialize
import java.io.Serializable
import kotlin.test.Test
import kotlin.test.assertEquals
@@ -10,23 +10,20 @@ package org.jetbrains.kotlin.gradle.kpm.idea
import buildIdeaKotlinProjectModel
import createKpmProject
import createProxyInstance
import deserialize
import org.gradle.api.Project
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.configurationcache.extensions.serviceOf
import org.gradle.kotlin.dsl.create
import org.gradle.testfixtures.ProjectBuilder
import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
import org.jetbrains.kotlin.gradle.kpm.KotlinExternalModelKey
import org.jetbrains.kotlin.gradle.kpm.KotlinExternalModelSerializer.Companion.serializable
import org.jetbrains.kotlin.gradle.kpm.external.ExternalVariantApi
import org.jetbrains.kotlin.gradle.kpm.external.external
import org.jetbrains.kotlin.gradle.kpm.idea.testFixtures.deserialize
import org.jetbrains.kotlin.gradle.kpm.idea.testFixtures.serialize
import org.jetbrains.kotlin.gradle.plugin.KotlinPm20PluginWrapper
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinIosX64Variant
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinLinuxX64Variant
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinPm20ProjectExtension
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.jvm
import serialize
import unwrapProxyInstance
import java.io.File
import java.io.Serializable
@@ -0,0 +1,75 @@
/*
* 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.gradle.kpm.idea.testFixtures
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinBinaryCoordinates
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinBinaryDependency
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinDependency
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinResolvedBinaryDependency
import java.io.File
fun buildIdeaKotlinDependencyMatchers(any: Any?): List<IdeaKotlinDependencyMatcher> {
return when (any) {
null -> emptyList()
is IdeaKotlinDependencyMatcher -> listOf(any)
is String -> listOf(IdeaKotlinDependencyMatcher.Coordinates(parseIdeaKotlinBinaryCoordinates(any)))
is Regex -> listOf(IdeaKotlinDependencyMatcher.CoordinatesRegex(any))
is File -> listOf(IdeaKotlinDependencyMatcher.BinaryFile(any))
is Iterable<*> -> any.flatMap { child -> buildIdeaKotlinDependencyMatchers(child) }
else -> error("Can't build ${IdeaKotlinDependencyMatcher::class.simpleName} from $any")
}
}
interface IdeaKotlinDependencyMatcher {
val description: String
fun matches(dependency: IdeaKotlinDependency): Boolean
class Coordinates(
private val coordinates: IdeaKotlinBinaryCoordinates
) : IdeaKotlinDependencyMatcher {
override val description: String = coordinates.toString()
override fun matches(dependency: IdeaKotlinDependency): Boolean {
return dependency is IdeaKotlinBinaryDependency && coordinates == dependency.coordinates
}
}
class CoordinatesRegex(
private val regex: Regex
) : IdeaKotlinDependencyMatcher {
override val description: String = regex.pattern
override fun matches(dependency: IdeaKotlinDependency): Boolean {
return dependency is IdeaKotlinBinaryDependency && regex.matches(dependency.coordinates.toString())
}
}
class BinaryFile(
private val binaryFile: File
) : IdeaKotlinDependencyMatcher {
override val description: String = binaryFile.path
override fun matches(dependency: IdeaKotlinDependency): Boolean {
return dependency is IdeaKotlinResolvedBinaryDependency && dependency.binaryFile == binaryFile
}
}
class InDirectory(
private val parentFile: File
) : IdeaKotlinDependencyMatcher {
constructor(parentFilePath: String) : this(File(parentFilePath))
override val description: String = "$parentFile/**"
override fun matches(dependency: IdeaKotlinDependency): Boolean {
return dependency is IdeaKotlinResolvedBinaryDependency &&
dependency.binaryFile.absoluteFile.normalize().canonicalPath.startsWith(
parentFile.absoluteFile.normalize().canonicalPath
)
}
}
}
@@ -0,0 +1,90 @@
/*
* 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.gradle.kpm.idea.testFixtures
import org.jetbrains.kotlin.gradle.kpm.idea.*
import kotlin.test.fail
fun IdeaKotlinProjectModel.assertIsNotEmpty(): IdeaKotlinProjectModel = apply {
if (this.modules.isEmpty()) fail("Expected at least one module in model")
}
fun IdeaKotlinProjectModel.assertContainsModule(name: String): IdeaKotlinModule {
return modules.find { it.name == name }
?: fail("Missing module with name '$name'. Found: ${modules.map { it.name }}")
}
fun IdeaKotlinModule.assertContainsFragment(name: String): IdeaKotlinFragment {
return fragments.find { it.name == name }
?: fail("Missing fragment with name '$name'. Found: ${fragments.map { it.name }}")
}
fun IdeaKotlinFragment.assertResolvedBinaryDependencies(
binaryType: String,
matchers: Set<IdeaKotlinDependencyMatcher>
): Set<IdeaKotlinResolvedBinaryDependency> {
val resolvedBinaryDependencies = dependencies
.mapNotNull { dependency ->
when (dependency) {
is IdeaKotlinResolvedBinaryDependencyImpl -> dependency
is IdeaKotlinUnresolvedBinaryDependencyImpl -> fail("Unexpected unresolved dependency: $dependency")
is IdeaKotlinSourceDependencyImpl -> null
}
}
.filter { it.binaryType == binaryType }
.toSet()
val unexpectedResolvedBinaryDependencies = resolvedBinaryDependencies
.filter { dependency -> matchers.none { matcher -> matcher.matches(dependency) } }
val missingDependencies = matchers.filter { matcher ->
resolvedBinaryDependencies.none { dependency -> matcher.matches(dependency) }
}
if (unexpectedResolvedBinaryDependencies.isEmpty() && missingDependencies.isEmpty()) {
return resolvedBinaryDependencies
}
fail(
buildString {
if (unexpectedResolvedBinaryDependencies.isNotEmpty()) {
appendLine("${name}: Unexpected dependencies found:")
unexpectedResolvedBinaryDependencies.forEach { unexpectedDependency ->
appendLine(unexpectedDependency)
}
appendLine()
appendLine("Unexpected dependency coordinates:")
unexpectedResolvedBinaryDependencies.forEach { unexpectedDependency ->
appendLine("\"${unexpectedDependency.coordinates}\",")
}
}
if (missingDependencies.isNotEmpty()) {
appendLine()
appendLine("Missing dependencies:")
missingDependencies.forEach { missingDependency ->
appendLine(missingDependency.description)
}
}
appendLine()
appendLine("Resolved Dependency Coordinates:")
resolvedBinaryDependencies.mapNotNull { it.coordinates }.forEach { coordinates ->
appendLine("\"$coordinates\",")
}
}
)
}
@JvmName("assertResolvedBinaryDependenciesByAnyMatcher")
fun IdeaKotlinFragment.assertResolvedBinaryDependencies(
binaryType: String, matchers: Set<Any?>,
) = assertResolvedBinaryDependencies(binaryType, matchers.flatMap { buildIdeaKotlinDependencyMatchers(it) }.toSet())
fun IdeaKotlinFragment.assertResolvedBinaryDependencies(
binaryType: String, vararg matchers: Any?
) = assertResolvedBinaryDependencies(binaryType, matchers.toSet())
@@ -0,0 +1,34 @@
/*
* 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.gradle.kpm.idea.testFixtures
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinBinaryCoordinates
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinBinaryCoordinatesImpl
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinBinaryDependency
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinFragment
fun parseIdeaKotlinBinaryCoordinates(coordinates: String): IdeaKotlinBinaryCoordinates {
val parts = coordinates.split(":")
if (parts.size == 3) {
return IdeaKotlinBinaryCoordinatesImpl(parts[0], parts[1], parts[2])
}
if (parts.size == 5) {
return IdeaKotlinBinaryCoordinatesImpl(parts[0], parts[1], parts[2], parts[3], parts[4])
}
throw IllegalArgumentException("Cannot parse $coordinates into ${IdeaKotlinBinaryCoordinates::class.java.simpleName}")
}
fun Iterable<IdeaKotlinBinaryCoordinates>.parsableString() =
joinToString("," + System.lineSeparator(), "", "") { "\"$it\"" }
@Suppress("unused") // Debugging API
fun IdeaKotlinFragment.parsableDependencyCoordinatesString(): String {
return dependencies.filterIsInstance<IdeaKotlinBinaryDependency>()
.mapNotNull { it.coordinates }.toSet()
.parsableString()
}
@@ -1,3 +1,5 @@
package org.jetbrains.kotlin.gradle.kpm.idea.testFixtures
import org.gradle.internal.io.ClassLoaderObjectInputStream
import java.io.*
@@ -6,20 +8,20 @@ import java.io.*
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
internal fun Any.serialize(): ByteArray {
fun Any.serialize(): ByteArray {
return ByteArrayOutputStream().use { byteArrayOutputStream ->
ObjectOutputStream(byteArrayOutputStream).writeObject(this)
byteArrayOutputStream.toByteArray()
}
}
internal inline fun <reified T : Serializable> ByteArray.deserialize(): T {
inline fun <reified T : Serializable> ByteArray.deserialize(): T {
val inputStream = ByteArrayInputStream(this)
val objectInputStream = ObjectInputStream(inputStream)
return objectInputStream.use { it.readObject() } as T
}
internal fun ByteArray.deserialize(classLoader: ClassLoader): Any {
fun ByteArray.deserialize(classLoader: ClassLoader): Any {
val inputStream = ByteArrayInputStream(this)
val objectInputStream = ClassLoaderObjectInputStream(inputStream, classLoader)
return objectInputStream.use { it.readObject() }
@@ -15,7 +15,7 @@ import org.jetbrains.kotlin.gradle.kpm.external.ExternalVariantApi
import org.jetbrains.kotlin.gradle.kpm.external.project
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinPm20ProjectExtension
fun KotlinPm20ProjectExtension.android() {
fun KotlinPm20ProjectExtension.androidPrototype() {
project.extensions.findByType<AppExtension>()?.applicationVariants?.all { androidVariant ->
main { createKotlinAndroidVariant(androidVariant) }
test { createKotlinAndroidVariant(androidVariant.unitTestVariant ?: return@test) }
@@ -27,4 +27,6 @@ fun KotlinPm20ProjectExtension.android() {
test { createKotlinAndroidVariant(androidVariant.unitTestVariant ?: return@test) }
instrumentedTest { createKotlinAndroidVariant(androidVariant.testVariant ?: return@instrumentedTest) }
}
setupIdeaKotlinFragmentDependencyResolver()
}
@@ -0,0 +1,94 @@
/*
* 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.gradle.android
import com.android.build.gradle.internal.publishing.AndroidArtifacts
import org.gradle.api.attributes.Usage
import org.jetbrains.kotlin.gradle.kpm.external.ExternalVariantApi
import org.jetbrains.kotlin.gradle.kpm.external.external
import org.jetbrains.kotlin.gradle.kpm.external.project
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinDependency.Companion.CLASSPATH_BINARY_TYPE
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinProjectModelBuilder.FragmentConstraint
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinResolvedBinaryDependencyImpl
import org.jetbrains.kotlin.gradle.kpm.idea.InternalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.kpm.idea.configureIdeaKotlinSpecialPlatformDependencyResolution
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinPm20ProjectExtension
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.containingVariants
@OptIn(ExternalVariantApi::class)
val isAndroidFragment = FragmentConstraint { fragment ->
fragment.containingVariants.all { variant -> androidDslKey in variant.external }
}
@OptIn(ExternalVariantApi::class)
val isAndroidAndJvmSharedFragment = FragmentConstraint constraint@{ fragment ->
val variants = fragment.containingVariants
if (variants.any { it.platformType != KotlinPlatformType.jvm }) return@constraint false
variants.any { androidDslKey in it.external } && variants.any { androidDslKey !in it.external }
}
@OptIn(ExternalVariantApi::class, InternalKotlinGradlePluginApi::class)
internal fun KotlinPm20ProjectExtension.setupIdeaKotlinFragmentDependencyResolver() {
configureIdeaKotlinSpecialPlatformDependencyResolution {
/*
Handle android + jvm use cases:
We do not yet support jvm based metadata compilations, therefore we do not
expect any reasonable results coming from metadata resolution.
We default to a 'KotlinPlatformType.jvm' resolution
*/
withConstraint(isAndroidAndJvmSharedFragment) {
withPlatformResolutionAttributes {
namedAttribute(Usage.USAGE_ATTRIBUTE, Usage.JAVA_API)
attribute(KotlinPlatformType.attribute, KotlinPlatformType.jvm)
}
artifactView(CLASSPATH_BINARY_TYPE)
}
withConstraint(isAndroidFragment) {
withPlatformResolutionAttributes {
namedAttribute(Usage.USAGE_ATTRIBUTE, Usage.JAVA_API)
attribute(KotlinPlatformType.attribute, KotlinPlatformType.androidJvm)
}
artifactView(CLASSPATH_BINARY_TYPE) {
attributes {
attribute(AndroidArtifacts.ARTIFACT_TYPE, AndroidArtifacts.ArtifactType.CLASSES_JAR.type)
}
}
artifactView("manifest") {
attributes {
attribute(AndroidArtifacts.ARTIFACT_TYPE, AndroidArtifacts.ArtifactType.MANIFEST.type)
}
}
artifactView("resources") {
attributes {
attribute(AndroidArtifacts.ARTIFACT_TYPE, AndroidArtifacts.ArtifactType.ANDROID_RES.type)
}
}
artifactView("android-symbol") {
attributes {
attribute(AndroidArtifacts.ARTIFACT_TYPE, AndroidArtifacts.ArtifactType.COMPILE_SYMBOL_LIST.type)
}
}
additionalDependencies {
project.getAndroidRuntimeJars().map { androidRuntimeJar ->
IdeaKotlinResolvedBinaryDependencyImpl(
binaryType = CLASSPATH_BINARY_TYPE,
binaryFile = androidRuntimeJar,
coordinates = null
)
}
}
}
}
}
@@ -18,6 +18,7 @@ import org.gradle.kotlin.dsl.named
import org.jetbrains.kotlin.gradle.kpm.external.ExternalVariantApi
import org.jetbrains.kotlin.gradle.kpm.external.createExternalJvmVariant
import org.jetbrains.kotlin.gradle.kpm.external.external
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.*
fun KotlinGradleModule.createKotlinAndroidVariant(androidVariant: BaseVariant) {
@@ -45,7 +46,11 @@ fun KotlinGradleModule.createKotlinAndroidVariant(androidVariant: BaseVariant) {
val kotlinVariant = createExternalJvmVariant(
"android${androidVariant.buildType.name.capitalize()}", KotlinJvmVariantConfig(
/* Only swap out configuration that is used. Default setup shall still be applied */
compileDependencies = DefaultKotlinCompileDependenciesDefinition
compileDependencies = (DefaultKotlinCompileDependenciesDefinition +
FragmentAttributes<KotlinGradleFragment> {
namedAttribute(TARGET_JVM_ENVIRONMENT_ATTRIBUTE, TargetJvmEnvironment.ANDROID)
attribute(KotlinPlatformType.attribute, KotlinPlatformType.androidJvm)
})
.withConfigurationProvider { androidVariant.compileConfiguration },
/* Only swap out configuration that is used. Default setup shall still be applied */
@@ -84,4 +89,5 @@ fun KotlinGradleModule.createKotlinAndroidVariant(androidVariant: BaseVariant) {
androidDsl.androidManifest = project.file("AndroidManifest.xml")
androidDsl.compileSdk = 23
androidCommon.external[androidDslKey] = androidDsl
kotlinVariant.external[androidDslKey] = androidDsl
}
@@ -5,11 +5,10 @@
package org.jetbrains.kotlin.gradle.android
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinGradleFragment
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinGradleFragmentInternal
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinGradleModule
val KotlinGradleModule.androidCommon: KotlinGradleFragmentInternal
get() = (fragments.findByName("android") ?: fragments.create("android") { fragment ->
get() = (fragments.findByName("androidCommon") ?: fragments.create("androidCommon") { fragment ->
fragment.refines(common)
}) as KotlinGradleFragmentInternal
@@ -95,6 +95,8 @@ dependencies {
because("Functional tests are using APIs from Android. Latest Version is used to avoid NoClassDefFoundError")
}
"functionalTestImplementation"(gradleKotlinDsl())
"functionalTestImplementation"(project(":kotlin-gradle-plugin-kpm-android"))
"functionalTestImplementation"(testFixtures(project(":kotlin-gradle-plugin-idea")))
}
testImplementation(commonDependency("org.jetbrains.teamcity:serviceMessages"))
@@ -16,6 +16,7 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_ENABLE_CINTEROP_COMMONIZATION
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_HIERARCHICAL_STRUCTURE_BY_DEFAULT
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_STDLIB_DEFAULT_DEPENDENCY
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinSharedNativeCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinCompilationData
@@ -101,3 +102,7 @@ fun Project.enableCInteropCommonization(enabled: Boolean = true) {
fun Project.enableHierarchicalStructureByDefault(enabled: Boolean = true) {
propertiesExtension.set(KOTLIN_MPP_HIERARCHICAL_STRUCTURE_BY_DEFAULT, enabled.toString())
}
fun Project.enableDefaultStdlibDependency(enabled: Boolean = true) {
project.propertiesExtension.set(KOTLIN_STDLIB_DEFAULT_DEPENDENCY, enabled.toString())
}
@@ -6,27 +6,27 @@
package org.jetbrains.kotlin.gradle.kpm
import org.gradle.api.Project
import org.gradle.api.artifacts.verification.DependencyVerificationMode
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.testfixtures.ProjectBuilder
import org.jetbrains.kotlin.gradle.addBuildEventsListenerRegistryMock
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinProjectModel
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinPm20ProjectExtension
import kotlin.test.BeforeTest
abstract class AbstractKpmExtensionTest {
protected val project: ProjectInternal = ProjectBuilder.builder().build() as ProjectInternal
protected lateinit var kotlin: KotlinPm20ProjectExtension
private set
@BeforeTest
open fun setup() {
kotlin = project.applyKpmPlugin()
protected val project: ProjectInternal = (ProjectBuilder.builder().build() as ProjectInternal).also { project ->
project.gradle.startParameter.dependencyVerificationMode = DependencyVerificationMode.OFF
}
protected val kotlin: KotlinPm20ProjectExtension by lazy { project.applyKpmPlugin() }
}
fun Project.applyKpmPlugin(): KotlinPm20ProjectExtension {
fun Project.applyKpmPlugin(configure: KotlinPm20ProjectExtension.() -> Unit = {}): KotlinPm20ProjectExtension {
addBuildEventsListenerRegistryMock(project)
plugins.apply("org.jetbrains.kotlin.multiplatform.pm20")
return extensions.getByName("kotlin") as KotlinPm20ProjectExtension
return (extensions.getByName("kotlin") as KotlinPm20ProjectExtension).also(configure)
}
fun KotlinPm20ProjectExtension.buildIdeaKotlinProjectModel(): IdeaKotlinProjectModel {
return ideaKotlinProjectModelBuilder.buildIdeaKotlinProjectModel()
}
@@ -0,0 +1,30 @@
/*
* 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("FunctionName")
package org.jetbrains.kotlin.gradle.kpm.idea
import org.gradle.api.Project
import org.gradle.api.artifacts.verification.DependencyVerificationMode
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.testfixtures.ProjectBuilder
import org.jetbrains.kotlin.commonizer.KonanDistribution
import org.jetbrains.kotlin.compilerRunner.konanHome
import org.jetbrains.kotlin.gradle.enableDefaultStdlibDependency
abstract class AbstractLightweightIdeaDependencyResolutionTest {
fun buildProject(builder: ProjectBuilder.() -> Unit = {}): ProjectInternal {
return (ProjectBuilder.builder().also(builder).build()).also { project ->
project.gradle.startParameter.dependencyVerificationMode = DependencyVerificationMode.OFF
project.enableDefaultStdlibDependency(false)
project.repositories.mavenLocal()
project.repositories.maven { it.setUrl("https://cache-redirector.jetbrains.com/maven-central") }
} as ProjectInternal
}
val Project.konanDistribution get() = KonanDistribution(project.konanHome)
}
@@ -9,6 +9,9 @@ package org.jetbrains.kotlin.gradle.kpm.idea
import org.gradle.kotlin.dsl.create
import org.jetbrains.kotlin.gradle.kpm.AbstractKpmExtensionTest
import org.jetbrains.kotlin.gradle.kpm.buildIdeaKotlinProjectModel
import org.jetbrains.kotlin.gradle.kpm.idea.testFixtures.deserialize
import org.jetbrains.kotlin.gradle.kpm.idea.testFixtures.serialize
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinIosX64Variant
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinLinuxX64Variant
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinMacosX64Variant
@@ -26,7 +29,7 @@ class IdeaKotlinProjectModelSerializableTest : AbstractKpmExtensionTest() {
@Test
fun `test - serialize and deserialize - empty project`() {
project.evaluate()
assertSerializeAndDeserializeEquals(kotlin.toIdeaKotlinProjectModel())
assertSerializeAndDeserializeEquals(kotlin.buildIdeaKotlinProjectModel())
}
@Test
@@ -47,18 +50,11 @@ class IdeaKotlinProjectModelSerializableTest : AbstractKpmExtensionTest() {
}
project.evaluate()
assertSerializeAndDeserializeEquals(kotlin.toIdeaKotlinProjectModel())
assertSerializeAndDeserializeEquals(kotlin.buildIdeaKotlinProjectModel())
}
private fun assertSerializeAndDeserializeEquals(model: IdeaKotlinProjectModel) {
val byteStream = ByteArrayOutputStream()
ObjectOutputStream(byteStream).use { stream -> stream.writeObject(model) }
val serializedModel = byteStream.toByteArray()
val deserializedModel = ObjectInputStream(ByteArrayInputStream(serializedModel)).use { stream -> stream.readObject() }
if (deserializedModel !is IdeaKotlinProjectModelImpl) {
fail("Expected 'deserializedModel' to implement ${IdeaKotlinProjectModelImpl::class.java}. Found $deserializedModel")
}
val deserializedModel = model.serialize().deserialize<IdeaKotlinProjectModel>()
assertEquals(
model.toString(), deserializedModel.toString(),
@@ -0,0 +1,197 @@
/*
* 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("FunctionName", "DuplicatedCode")
package org.jetbrains.kotlin.gradle.kpm.idea
import com.android.build.gradle.LibraryExtension
import com.android.build.gradle.LibraryPlugin
import org.jetbrains.kotlin.commonizer.platformLibsDir
import org.jetbrains.kotlin.commonizer.stdlib
import org.jetbrains.kotlin.gradle.android.androidPrototype
import org.jetbrains.kotlin.gradle.kpm.applyKpmPlugin
import org.jetbrains.kotlin.gradle.kpm.buildIdeaKotlinProjectModel
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinDependency.Companion.CLASSPATH_BINARY_TYPE
import org.jetbrains.kotlin.gradle.kpm.idea.testFixtures.*
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinIosArm64Variant
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinIosX64Variant
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinJvmVariant
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.jvm
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.junit.Test
class MviKotlinIdeaDependencyResolutionTest : AbstractLightweightIdeaDependencyResolutionTest() {
@Test
fun `test - simple ios and jvm project`() {
val project = buildProject()
val kotlin = project.applyKpmPlugin {
mainAndTest {
fragments.create("jvm", KotlinJvmVariant::class.java)
val iosX64Variant = fragments.create("iosX64", KotlinIosX64Variant::class.java)
val iosArm64Variant = fragments.create("iosArm64", KotlinIosArm64Variant::class.java)
val iosCommon = fragments.create("iosCommon")
iosCommon.refines(common)
iosX64Variant.refines(iosCommon)
iosArm64Variant.refines(iosCommon)
dependencies {
implementation("com.arkivanov.mvikotlin:mvikotlin:3.0.0-beta01")
}
}
}
kotlin.buildIdeaKotlinProjectModel().assertIsNotEmpty().modules.forEach { module ->
module.assertContainsFragment("common").assertResolvedBinaryDependencies(
CLASSPATH_BINARY_TYPE,
"com.arkivanov.mvikotlin:mvikotlin:3.0.0-beta01:main:commonMain",
"com.arkivanov.essenty:lifecycle:0.2.2:main:commonMain",
"com.arkivanov.essenty:instance-keeper:0.2.2:main:commonMain",
"org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10"
)
module.assertContainsFragment("jvm").assertResolvedBinaryDependencies(
CLASSPATH_BINARY_TYPE,
"com.arkivanov.mvikotlin:mvikotlin-jvm:3.0.0-beta01",
"com.arkivanov.essenty:lifecycle-jvm:0.2.2",
"com.arkivanov.essenty:instance-keeper-jvm:0.2.2",
"org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10",
"org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10",
"org.jetbrains.kotlin:kotlin-stdlib:1.6.10",
"org.jetbrains:annotations:13.0",
)
module.assertContainsFragment("iosCommon").assertResolvedBinaryDependencies(
CLASSPATH_BINARY_TYPE,
project.konanDistribution.stdlib,
"com.arkivanov.mvikotlin:mvikotlin:3.0.0-beta01:main:commonMain",
"com.arkivanov.mvikotlin:mvikotlin:3.0.0-beta01:main:jsNativeMain",
"com.arkivanov.essenty:lifecycle:0.2.2:main:commonMain",
"com.arkivanov.essenty:instance-keeper:0.2.2:main:commonMain",
"org.jetbrains.kotlin:kotlin-stdlib:1.6.10",
"org.jetbrains:annotations:13.0",
/*
The following dependencies are actually marked as 'implementation' on mvikotlin.
We still resolve those dependencies, because this source set is shared native and
the shared native compiler expects those dependencies as well.
Including those dependencies for IDE analysis is up for discussion.
*/
"com.arkivanov.mvikotlin:utils-internal:3.0.0-beta01:main:commonMain",
"com.arkivanov.mvikotlin:utils-internal:3.0.0-beta01:main:darwinMain",
"com.arkivanov.mvikotlin:utils-internal:3.0.0-beta01:main:nativeMain",
"com.arkivanov.mvikotlin:rx:3.0.0-beta01:main:commonMain",
"com.arkivanov.mvikotlin:rx-internal:3.0.0-beta01:main:commonMain",
"com.arkivanov.mvikotlin:rx-internal:3.0.0-beta01:main:darwinMain",
"com.arkivanov.mvikotlin:rx-internal:3.0.0-beta01:main:nativeMain",
"com.arkivanov.essenty:utils-internal:0.2.2:main:commonMain",
"com.arkivanov.essenty:utils-internal:0.2.2:main:nativeMain",
)
module.assertContainsFragment("iosX64").assertResolvedBinaryDependencies(
CLASSPATH_BINARY_TYPE,
project.konanDistribution.stdlib,
IdeaKotlinDependencyMatcher.InDirectory(project.konanDistribution.platformLibsDir.resolve(KonanTarget.IOS_X64.name)),
"org.jetbrains.kotlin:kotlin-stdlib:1.6.10",
"org.jetbrains:annotations:13.0",
"com.arkivanov.mvikotlin:mvikotlin-iosx64:3.0.0-beta01",
"com.arkivanov.essenty:lifecycle-iosx64:0.2.2",
"com.arkivanov.essenty:instance-keeper-iosx64:0.2.2",
/* Internals are listed here as well. See comment above */
"com.arkivanov.mvikotlin:rx-internal-iosx64:3.0.0-beta01",
"com.arkivanov.mvikotlin:utils-internal-iosx64:3.0.0-beta01",
"com.arkivanov.mvikotlin:rx-iosx64:3.0.0-beta01",
"com.arkivanov.essenty:utils-internal-iosx64:0.2.2",
)
module.assertContainsFragment("iosArm64").assertResolvedBinaryDependencies(
CLASSPATH_BINARY_TYPE,
project.konanDistribution.stdlib,
IdeaKotlinDependencyMatcher.InDirectory(project.konanDistribution.platformLibsDir.resolve(KonanTarget.IOS_ARM64.name)),
"com.arkivanov.mvikotlin:mvikotlin-iosarm64:3.0.0-beta01",
"com.arkivanov.essenty:lifecycle-iosarm64:0.2.2",
"com.arkivanov.essenty:instance-keeper-iosarm64:0.2.2",
/* Internals are listed here as well. See comment above */
"com.arkivanov.mvikotlin:rx-internal-iosarm64:3.0.0-beta01",
"com.arkivanov.mvikotlin:utils-internal-iosarm64:3.0.0-beta01",
"com.arkivanov.mvikotlin:rx-iosarm64:3.0.0-beta01",
"com.arkivanov.essenty:utils-internal-iosarm64:0.2.2",
"org.jetbrains.kotlin:kotlin-stdlib:1.6.10",
"org.jetbrains:annotations:13.0",
)
}
}
@Test
fun `test - android and jvm`() {
val project = buildProject()
/* Setup Android */
project.plugins.apply(LibraryPlugin::class.java)
val android = project.extensions.getByType(LibraryExtension::class.java)
android.compileSdkVersion(30)
val kotlin = project.applyKpmPlugin {
androidPrototype()
jvm {}
mainAndTest {
dependencies {
implementation("com.arkivanov.mvikotlin:mvikotlin:3.0.0-beta01")
}
}
}
/* Android requires project to evaluate */
project.evaluate()
kotlin.buildIdeaKotlinProjectModel().assertIsNotEmpty().assertContainsModule("main").let { module ->
listOf("common", "jvm").forEach { fragmentName ->
module.assertContainsFragment(fragmentName).assertResolvedBinaryDependencies(
CLASSPATH_BINARY_TYPE,
"com.arkivanov.mvikotlin:mvikotlin-jvm:3.0.0-beta01",
"com.arkivanov.essenty:lifecycle-jvm:0.2.2",
"com.arkivanov.essenty:instance-keeper-jvm:0.2.2",
"org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10",
"org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10",
"org.jetbrains.kotlin:kotlin-stdlib:1.6.10",
"org.jetbrains:annotations:13.0",
)
}
listOf("androidCommon", "androidRelease").forEach { fragmentName ->
module.assertContainsFragment(fragmentName).assertResolvedBinaryDependencies(
CLASSPATH_BINARY_TYPE,
"com.arkivanov.mvikotlin:mvikotlin-android:3.0.0-beta01",
"com.arkivanov.essenty:lifecycle-android:0.2.2",
"com.arkivanov.essenty:instance-keeper-android:0.2.2",
"org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10",
"org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10",
"org.jetbrains.kotlin:kotlin-stdlib:1.6.10",
"org.jetbrains:annotations:13.0",
android.bootClasspath
)
}
module.assertContainsFragment("androidDebug").assertResolvedBinaryDependencies(
CLASSPATH_BINARY_TYPE,
"com.arkivanov.mvikotlin:mvikotlin-android-debug:3.0.0-beta01",
"com.arkivanov.essenty:lifecycle-android-debug:0.2.2",
"com.arkivanov.essenty:instance-keeper-android-debug:0.2.2",
"org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10",
"org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10",
"org.jetbrains.kotlin:kotlin-stdlib:1.6.10",
"org.jetbrains:annotations:13.0",
android.bootClasspath
)
}
}
}
@@ -0,0 +1,163 @@
/*
* 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("FunctionName", "DuplicatedCode")
package org.jetbrains.kotlin.gradle.kpm.idea
import org.jetbrains.kotlin.gradle.enableDefaultStdlibDependency
import org.jetbrains.kotlin.gradle.kpm.applyKpmPlugin
import org.jetbrains.kotlin.gradle.kpm.buildIdeaKotlinProjectModel
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinDependency.Companion.CLASSPATH_BINARY_TYPE
import org.jetbrains.kotlin.gradle.kpm.idea.testFixtures.IdeaKotlinDependencyMatcher
import org.jetbrains.kotlin.gradle.kpm.idea.testFixtures.assertContainsFragment
import org.jetbrains.kotlin.gradle.kpm.idea.testFixtures.assertIsNotEmpty
import org.jetbrains.kotlin.gradle.kpm.idea.testFixtures.assertResolvedBinaryDependencies
import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinIosArm64Variant
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinIosX64Variant
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinJvmVariant
import org.junit.Test
class StdlibKotlinIdeaDependencyResolutionTest : AbstractLightweightIdeaDependencyResolutionTest() {
@Test
fun `test - simple ios and jvm project`() {
val project = buildProject()
val kotlin = project.applyKpmPlugin {
mainAndTest {
fragments.create("jvm", KotlinJvmVariant::class.java)
val iosX64Variant = fragments.create("iosX64", KotlinIosX64Variant::class.java)
val iosArm64Variant = fragments.create("iosArm64", KotlinIosArm64Variant::class.java)
val iosCommon = fragments.create("iosCommon")
iosCommon.refines(common)
iosX64Variant.refines(iosCommon)
iosArm64Variant.refines(iosCommon)
dependencies {
implementation(kotlin("stdlib-common", "1.6.10"))
}
}
}
kotlin.buildIdeaKotlinProjectModel().assertIsNotEmpty().modules.forEach { module ->
module.assertContainsFragment("common").assertResolvedBinaryDependencies(
CLASSPATH_BINARY_TYPE,
"org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10"
)
/* stdlib-common does not automatically add platform dependencies */
module.assertContainsFragment("jvm").assertResolvedBinaryDependencies(
CLASSPATH_BINARY_TYPE,
)
/* native fragments only references dependencies from the native distribution */
listOf("iosCommon", "iosX64", "iosArm64").forEach { fragmentName ->
module.assertContainsFragment(fragmentName).assertResolvedBinaryDependencies(
CLASSPATH_BINARY_TYPE, IdeaKotlinDependencyMatcher.InDirectory(project.konanDistribution.root)
)
}
}
}
/**
* Testing common misconfiguration of depending directly to the regular stdlib in common
*/
@Test
fun `test - simple ios and jvm project - dependency to 'stdlib' in common`() {
val project = buildProject()
val kotlin = project.applyKpmPlugin {
mainAndTest {
fragments.create("jvm", KotlinJvmVariant::class.java)
val iosX64Variant = fragments.create("iosX64", KotlinIosX64Variant::class.java)
val iosArm64Variant = fragments.create("iosArm64", KotlinIosArm64Variant::class.java)
val iosCommon = fragments.create("iosCommon")
iosCommon.refines(common)
iosX64Variant.refines(iosCommon)
iosArm64Variant.refines(iosCommon)
dependencies {
implementation(kotlin("stdlib", "1.6.10"))
}
}
}
kotlin.buildIdeaKotlinProjectModel().assertIsNotEmpty().modules.forEach { module ->
module.assertContainsFragment("common").assertResolvedBinaryDependencies(
CLASSPATH_BINARY_TYPE,
"org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10",
/*
Actually not a desired dependency, however a general filtering mechanism cannot be implemented,
since this variant does not contain attributes that would enable such filter.
A special filter for the stdlib is not implemented yet.
*/
"org.jetbrains.kotlin:kotlin-stdlib:1.6.10",
"org.jetbrains:annotations:13.0",
)
module.assertContainsFragment("jvm").assertResolvedBinaryDependencies(
CLASSPATH_BINARY_TYPE,
"org.jetbrains.kotlin:kotlin-stdlib:1.6.10",
"org.jetbrains:annotations:13.0",
)
/* native fragments only references dependencie from the native distribution */
listOf("iosCommon", "iosX64", "iosArm64").forEach { fragmentName ->
module.assertContainsFragment(fragmentName).assertResolvedBinaryDependencies(
CLASSPATH_BINARY_TYPE,
IdeaKotlinDependencyMatcher.InDirectory(project.konanDistribution.root),
/* Actually not correct as well, since those are jvm dependencies. Filtering is not easily possible here, as well */
"org.jetbrains.kotlin:kotlin-stdlib:1.6.10",
"org.jetbrains:annotations:13.0",
)
}
}
}
@Test
fun `test simple ios and jvm project - with default stdlib dependency`() {
val project = buildProject()
project.enableDefaultStdlibDependency(true)
val kotlin = project.applyKpmPlugin {
mainAndTest {
fragments.create("jvm", KotlinJvmVariant::class.java)
val iosX64Variant = fragments.create("iosX64", KotlinIosX64Variant::class.java)
val iosArm64Variant = fragments.create("iosArm64", KotlinIosArm64Variant::class.java)
val iosCommon = fragments.create("iosCommon")
iosCommon.refines(common)
iosX64Variant.refines(iosCommon)
iosArm64Variant.refines(iosCommon)
}
}
kotlin.buildIdeaKotlinProjectModel().assertIsNotEmpty().modules.forEach { module ->
module.assertContainsFragment("common").assertResolvedBinaryDependencies(
CLASSPATH_BINARY_TYPE,
"org.jetbrains.kotlin:kotlin-stdlib-common:${project.getKotlinPluginVersion()}"
)
module.assertContainsFragment("jvm").assertResolvedBinaryDependencies(
CLASSPATH_BINARY_TYPE,
"org.jetbrains.kotlin:kotlin-stdlib:${project.getKotlinPluginVersion()}",
Regex("""org\.jetbrains:annotations:.*"""),
)
listOf("iosCommon", "iosX64", "iosArm64").forEach { fragmentName ->
module.assertContainsFragment(fragmentName).assertResolvedBinaryDependencies(
CLASSPATH_BINARY_TYPE,
IdeaKotlinDependencyMatcher.InDirectory(project.konanDistribution.root),
)
}
}
}
}
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.gradle.kpm.external
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinTopLevelExtension
import org.jetbrains.kotlin.gradle.kpm.KotlinMutableExternalModelContainer
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinProjectModelBuilder
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.*
@RequiresOptIn("API is intended to build external Kotlin Targets.")
@@ -17,6 +18,10 @@ annotation class ExternalVariantApi
val KotlinTopLevelExtension.project: Project
get() = this.project
@ExternalVariantApi
val KotlinPm20ProjectExtension.ideaKotlinProjectModelBuilder: IdeaKotlinProjectModelBuilder
get() = this.ideaKotlinProjectModelBuilder
@ExternalVariantApi
fun KotlinGradleModule.createExternalJvmVariant(
name: String,
@@ -0,0 +1,54 @@
/*
* 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.gradle.kpm.idea
import com.google.gson.GsonBuilder
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.TaskProvider
import org.jetbrains.kotlin.gradle.dsl.pm20Extension
import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask
import java.io.ByteArrayOutputStream
import java.io.ObjectOutputStream
internal fun Project.locateOrRegisterBuildIdeaKotlinProjectModelTask(): TaskProvider<BuildIdeaKotlinProjectModelTask> {
return locateOrRegisterTask(BuildIdeaKotlinProjectModelTask.defaultTaskName)
}
internal open class BuildIdeaKotlinProjectModelTask : DefaultTask() {
@OutputDirectory
val outputDirectory = project.buildDir.resolve("ideaKotlinProjectModel")
private val builder = project.pm20Extension.ideaKotlinProjectModelBuilder
init {
outputs.upToDateWhen { false }
}
@TaskAction
protected fun buildIdeaKotlinProjectModel() {
outputDirectory.mkdirs()
val model = builder.buildIdeaKotlinProjectModel()
val textFile = outputDirectory.resolve("model.txt")
textFile.writeText(model.toString())
val binaryFile = outputDirectory.resolve("model.bin")
binaryFile.writeBytes(ByteArrayOutputStream().use { byteArrayOutputStream ->
ObjectOutputStream(byteArrayOutputStream).use { objectOutputStream -> objectOutputStream.writeObject(model) }
byteArrayOutputStream.toByteArray()
})
val jsonFile = outputDirectory.resolve("model.json")
jsonFile.writeText(GsonBuilder().setLenient().setPrettyPrinting().create().toJson(model))
}
companion object {
const val defaultTaskName = "buildIdeaKotlinProjectModel"
}
}
@@ -0,0 +1,137 @@
/*
* 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:OptIn(ExternalVariantApi::class)
package org.jetbrains.kotlin.gradle.kpm.idea
import org.jetbrains.kotlin.gradle.kpm.external.ExternalVariantApi
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinPlatformDependencyResolver.ArtifactResolution
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinProjectModelBuilder.*
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.*
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.FragmentAttributes
@DslMarker
@ExternalVariantApi
annotation class ExternalVariantPlatformDependencyResolutionDsl
@ExternalVariantApi
fun KotlinPm20ProjectExtension.configureIdeaKotlinSpecialPlatformDependencyResolution(
configure: IdeaKotlinPlatformDependencyResolutionDslHandle.() -> Unit
) {
IdeaKotlinPlatformDependencyResolutionDslHandle(ideaKotlinProjectModelBuilder).also(configure).setup()
}
@ExternalVariantPlatformDependencyResolutionDsl
class IdeaKotlinPlatformDependencyResolutionDslHandle internal constructor(
private val toolingModelBuilder: IdeaKotlinProjectModelBuilder,
private val constraint: FragmentConstraint = FragmentConstraint.unconstrained,
private val parent: IdeaKotlinPlatformDependencyResolutionDslHandle? = null
) {
private val artifactViewDslHandles = mutableListOf<ArtifactViewDslHandle>()
private val children = mutableListOf<IdeaKotlinPlatformDependencyResolutionDslHandle>()
private val additionalDependencies = mutableListOf<IdeaKotlinDependencyResolver>()
@ExternalVariantPlatformDependencyResolutionDsl
var platformResolutionAttributes: FragmentAttributes<KotlinGradleFragment>? = null
@ExternalVariantPlatformDependencyResolutionDsl
class ArtifactViewDslHandle(
@property:ExternalVariantPlatformDependencyResolutionDsl
var binaryType: String
) {
@ExternalVariantPlatformDependencyResolutionDsl
var attributes: FragmentAttributes<KotlinGradleFragment> = FragmentAttributes { }
@ExternalVariantPlatformDependencyResolutionDsl
fun attributes(setAttributes: KotlinGradleFragmentConfigurationAttributesContext<KotlinGradleFragment>.() -> Unit) {
attributes += FragmentAttributes(setAttributes)
}
}
@ExternalVariantPlatformDependencyResolutionDsl
fun withConstraint(
constraint: FragmentConstraint,
configure: IdeaKotlinPlatformDependencyResolutionDslHandle.() -> Unit
) {
children += IdeaKotlinPlatformDependencyResolutionDslHandle(
toolingModelBuilder, constraint, this,
).apply(configure)
}
@ExternalVariantPlatformDependencyResolutionDsl
fun withPlatformResolutionAttributes(
setAttributes: KotlinGradleFragmentConfigurationAttributesContext<KotlinGradleFragment>.() -> Unit
) {
val additionalAttributes = FragmentAttributes(setAttributes)
this.platformResolutionAttributes = platformResolutionAttributes?.plus(additionalAttributes) ?: additionalAttributes
}
@ExternalVariantPlatformDependencyResolutionDsl
fun artifactView(binaryType: String, configure: ArtifactViewDslHandle.() -> Unit = {}) {
artifactViewDslHandles += ArtifactViewDslHandle(binaryType).apply(configure)
}
@ExternalVariantPlatformDependencyResolutionDsl
fun additionalDependencies(dependencyProvider: (KotlinGradleFragment) -> List<IdeaKotlinDependency>) {
this.additionalDependencies += IdeaKotlinDependencyResolver { fragment -> dependencyProvider(fragment).toSet() }
}
internal fun setup() {
children.forEach { child -> child.setup() }
val constraint = buildConstraint()
val platformResolutionAttributes = buildPlatformResolutionAttributes()
/* Setup artifact views */
artifactViewDslHandles.toList().forEach { artifactViewDslHandle ->
toolingModelBuilder.registerDependencyResolver(
IdeaKotlinPlatformDependencyResolver(
binaryType = artifactViewDslHandle.binaryType,
artifactResolution = ArtifactResolution.Variant(artifactViewDslHandle.attributes)
),
constraint = FragmentConstraint.isVariant and constraint,
phase = DependencyResolutionPhase.BinaryDependencyResolution,
level = DependencyResolutionLevel.Overwrite
)
if (platformResolutionAttributes != null) {
toolingModelBuilder.registerDependencyResolver(
IdeaKotlinPlatformDependencyResolver(
binaryType = artifactViewDslHandle.binaryType,
artifactResolution = ArtifactResolution.PlatformFragment(
artifactViewAttributes = artifactViewDslHandle.attributes,
platformResolutionAttributes = platformResolutionAttributes
)
),
constraint = !FragmentConstraint.isVariant and constraint,
phase = DependencyResolutionPhase.BinaryDependencyResolution,
level = DependencyResolutionLevel.Overwrite
)
}
}
/* Setup plain additional resolvers */
additionalDependencies.toList().forEach { additionalDependencyResolver ->
toolingModelBuilder.registerDependencyResolver(
resolver = additionalDependencyResolver,
constraint = constraint,
phase = DependencyResolutionPhase.BinaryDependencyResolution,
level = DependencyResolutionLevel.Overwrite
)
}
}
private fun buildConstraint(): FragmentConstraint {
val parentConstraint = parent?.buildConstraint() ?: return constraint
return parentConstraint and this.constraint
}
private fun buildPlatformResolutionAttributes(): FragmentAttributes<KotlinGradleFragment>? {
val parentAttributes = parent?.buildPlatformResolutionAttributes() ?: return this.platformResolutionAttributes
return parentAttributes + (this.platformResolutionAttributes ?: return parentAttributes)
}
}
@@ -0,0 +1,16 @@
/*
* 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.gradle.kpm.idea
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinGradleFragment
fun interface IdeaKotlinDependencyEffect {
operator fun invoke(fragment: KotlinGradleFragment, dependencies: Set<IdeaKotlinDependency>)
}
fun IdeaKotlinDependencyResolver.withEffect(effect: IdeaKotlinDependencyEffect) = IdeaKotlinDependencyResolver { fragment ->
this@withEffect.resolve(fragment).also { dependencies -> effect(fragment, dependencies) }
}
@@ -0,0 +1,26 @@
/*
* 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.gradle.kpm.idea
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinGradleFragment
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.path
internal object IdeaKotlinDependencyLogger : IdeaKotlinDependencyEffect {
override fun invoke(
fragment: KotlinGradleFragment, dependencies: Set<IdeaKotlinDependency>
) {
val fragmentPathRegex = fragment.project.properties["idea.kotlin.log.dependencies"]?.toString() ?: return
if (!fragment.path.matches(Regex(fragmentPathRegex))) return
val message = buildString {
appendLine("Resolved dependencies for ${fragment.path}")
dependencies.forEach { dependency -> appendLine("> $dependency") }
appendLine()
}
fragment.project.logger.quiet(message)
}
}
@@ -0,0 +1,54 @@
/*
* 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.gradle.kpm.idea
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinGradleFragment
fun interface IdeaKotlinDependencyResolver {
fun resolve(fragment: KotlinGradleFragment): Set<IdeaKotlinDependency>
object Empty : IdeaKotlinDependencyResolver {
override fun resolve(fragment: KotlinGradleFragment): Set<IdeaKotlinDependency> = emptySet()
}
}
fun IdeaKotlinDependencyResolver(
resolvers: Iterable<IdeaKotlinDependencyResolver>
): IdeaKotlinDependencyResolver {
val resolversList = resolvers.toList()
if (resolversList.isEmpty()) return IdeaKotlinDependencyResolver.Empty
return CompositeIdeaKotlinDependencyResolver(resolversList)
}
fun IdeaKotlinDependencyResolver(
vararg resolvers: IdeaKotlinDependencyResolver
): IdeaKotlinDependencyResolver = IdeaKotlinDependencyResolver(resolvers.toList())
operator fun IdeaKotlinDependencyResolver.plus(
other: IdeaKotlinDependencyResolver
): IdeaKotlinDependencyResolver {
if (this is CompositeIdeaKotlinDependencyResolver && other is CompositeIdeaKotlinDependencyResolver) {
return CompositeIdeaKotlinDependencyResolver(this.children + other.children)
}
if (this is CompositeIdeaKotlinDependencyResolver) {
return CompositeIdeaKotlinDependencyResolver(this.children + other)
}
if (other is CompositeIdeaKotlinDependencyResolver) {
return CompositeIdeaKotlinDependencyResolver(listOf(this) + other.children)
}
return CompositeIdeaKotlinDependencyResolver(listOf(this, other))
}
private class CompositeIdeaKotlinDependencyResolver(
val children: List<IdeaKotlinDependencyResolver>
) : IdeaKotlinDependencyResolver {
override fun resolve(fragment: KotlinGradleFragment): Set<IdeaKotlinDependency> {
return children.flatMap { child -> child.resolve(fragment) }.toSet()
}
}
@@ -0,0 +1,53 @@
/*
* 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.gradle.kpm.idea
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinGradleFragment
fun interface IdeaKotlinDependencyTransformer {
fun transform(
fragment: KotlinGradleFragment, dependencies: Set<IdeaKotlinDependency>
): Set<IdeaKotlinDependency>
}
fun IdeaKotlinDependencyResolver.withTransformer(transformer: IdeaKotlinDependencyTransformer) = IdeaKotlinDependencyResolver { fragment ->
transformer.transform(fragment, this@withTransformer.resolve(fragment))
}
fun IdeaKotlinDependencyTransformer(
transformers: List<IdeaKotlinDependencyTransformer>
): IdeaKotlinDependencyTransformer = CompositeIdeaKotlinDependencyTransformer(transformers)
fun IdeaKotlinDependencyTransformer(
vararg transformers: IdeaKotlinDependencyTransformer
): IdeaKotlinDependencyTransformer = IdeaKotlinDependencyTransformer(transformers.toList())
operator fun IdeaKotlinDependencyTransformer.plus(other: IdeaKotlinDependencyTransformer):
IdeaKotlinDependencyTransformer {
if (this is CompositeIdeaKotlinDependencyTransformer && other is CompositeIdeaKotlinDependencyTransformer) {
return CompositeIdeaKotlinDependencyTransformer(this.transformers + other.transformers)
}
if (this is CompositeIdeaKotlinDependencyTransformer) {
return CompositeIdeaKotlinDependencyTransformer(this.transformers + other)
}
if (other is CompositeIdeaKotlinDependencyTransformer) {
return CompositeIdeaKotlinDependencyTransformer(listOf(this) + other.transformers)
}
return CompositeIdeaKotlinDependencyTransformer(listOf(this, other))
}
private class CompositeIdeaKotlinDependencyTransformer(
val transformers: List<IdeaKotlinDependencyTransformer>
) : IdeaKotlinDependencyTransformer {
override fun transform(
fragment: KotlinGradleFragment, dependencies: Set<IdeaKotlinDependency>
): Set<IdeaKotlinDependency> {
return transformers.fold(dependencies) { currentDependencies, transformer -> transformer.transform(fragment, currentDependencies) }
}
}
@@ -9,39 +9,42 @@ import org.jetbrains.kotlin.gradle.kpm.KotlinExternalModelContainer
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinGradleFragment
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinGradleFragmentInternal
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinGradleVariant
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.variantsContainingFragment
private typealias IdeaKotlinFragmentBuilderCache = MutableMap<KotlinGradleFragment, IdeaKotlinFragment>
internal class IdeaKotlinFragmentBuildingContext(
private val parent: IdeaKotlinProjectModelBuildingContext
) : IdeaKotlinProjectModelBuildingContext by parent {
private val fragmentCache = mutableMapOf<KotlinGradleFragment, IdeaKotlinFragment>()
fun getOrPut(source: KotlinGradleFragment, builder: () -> IdeaKotlinFragment) = fragmentCache.getOrPut(source, builder)
}
internal fun KotlinGradleFragment.toIdeaKotlinFragment(
cache: IdeaKotlinFragmentBuilderCache = mutableMapOf()
): IdeaKotlinFragment {
return cache.getOrPut(this) {
if (this is KotlinGradleVariant) buildIdeaKotlinVariant(cache)
else buildIdeaKotlinFragment(cache)
internal fun IdeaKotlinFragmentBuildingContext.toIdeaKotlinFragment(fragment: KotlinGradleFragment): IdeaKotlinFragment {
return getOrPut(fragment) {
if (fragment is KotlinGradleVariant) buildIdeaKotlinVariant(fragment)
else buildIdeaKotlinFragment(fragment)
}
}
private fun KotlinGradleFragment.buildIdeaKotlinFragment(cache: IdeaKotlinFragmentBuilderCache): IdeaKotlinFragment {
private fun IdeaKotlinFragmentBuildingContext.buildIdeaKotlinFragment(fragment: KotlinGradleFragment): IdeaKotlinFragment {
return IdeaKotlinFragmentImpl(
name = name,
moduleIdentifier = containingModule.moduleIdentifier.toIdeaKotlinModuleIdentifier(),
languageSettings = languageSettings.toIdeaKotlinLanguageSettings(),
dependencies = emptyList(),
directRefinesDependencies = directRefinesDependencies.map { refinesFragment ->
refinesFragment.toIdeaKotlinFragment(cache)
},
sourceDirectories = kotlinSourceRoots.sourceDirectories.files.toList().map { file ->
IdeaKotlinSourceDirectoryImpl(file)
},
name = fragment.name,
moduleIdentifier = fragment.containingModule.moduleIdentifier.toIdeaKotlinModuleIdentifier(),
platforms = fragment.containingModule.variantsContainingFragment(fragment)
.map { variant -> buildIdeaKotlinPlatform(variant) }.toSet(),
languageSettings = fragment.languageSettings.toIdeaKotlinLanguageSettings(),
dependencies = dependencyResolver.resolve(fragment).toList(),
directRefinesDependencies = fragment.directRefinesDependencies.map { refinesFragment -> toIdeaKotlinFragment(refinesFragment) },
sourceDirectories = fragment.kotlinSourceRoots.sourceDirectories.files.toList().map { file -> IdeaKotlinSourceDirectoryImpl(file) },
resourceDirectories = emptyList(),
external = (this as? KotlinGradleFragmentInternal)?.external ?: KotlinExternalModelContainer.Empty
external = (fragment as? KotlinGradleFragmentInternal)?.external ?: KotlinExternalModelContainer.Empty
)
}
private fun KotlinGradleVariant.buildIdeaKotlinVariant(cache: IdeaKotlinFragmentBuilderCache): IdeaKotlinVariant {
private fun IdeaKotlinFragmentBuildingContext.buildIdeaKotlinVariant(variant: KotlinGradleVariant): IdeaKotlinVariant {
return IdeaKotlinVariantImpl(
fragment = buildIdeaKotlinFragment(cache),
variantAttributes = variantAttributes.mapKeys { (key, _) -> key.uniqueName },
compilationOutputs = compilationOutputs.toIdeaKotlinCompilationOutputs()
fragment = buildIdeaKotlinFragment(variant),
platform = buildIdeaKotlinPlatform(variant),
variantAttributes = variant.variantAttributes.mapKeys { (key, _) -> key.uniqueName },
compilationOutputs = variant.compilationOutputs.toIdeaKotlinCompilationOutputs()
)
}
@@ -0,0 +1,72 @@
/*
* 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.gradle.kpm.idea
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets
import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.JarMetadataProvider
import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.ProjectMetadataProvider
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.FragmentGranularMetadataResolverFactory
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinGradleFragment
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinGradleModule.Companion.moduleName
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.toModuleDependency
import org.jetbrains.kotlin.gradle.utils.withTemporaryDirectory
internal class IdeaKotlinMetadataBinaryDependencyResolver(
private val fragmentGranularMetadataResolverFactory: FragmentGranularMetadataResolverFactory
) : IdeaKotlinDependencyResolver {
override fun resolve(fragment: KotlinGradleFragment): Set<IdeaKotlinDependency> {
return fragmentGranularMetadataResolverFactory.getOrCreate(fragment).resolutions
.filterIsInstance<ChooseVisibleSourceSets>()
.flatMap { resolution -> resolve(fragment, resolution) }
.toSet()
}
private fun resolve(fragment: KotlinGradleFragment, resolution: ChooseVisibleSourceSets): Iterable<IdeaKotlinDependency> {
val gradleModuleIdentifier = resolution.dependency.id as? ModuleComponentIdentifier ?: return emptySet()
val kotlinModuleIdentifier = resolution.dependency.toModuleDependency().moduleIdentifier
/* Project to project metadata dependencies shall be resolved as source dependencies, somewhere else */
val metadataProvider = when (resolution.metadataProvider) {
is ProjectMetadataProvider -> return emptySet()
is JarMetadataProvider -> resolution.metadataProvider
}
return resolution.allVisibleSourceSetNames.mapNotNull { visibleFragmentName ->
val binaryFile = withTemporaryDirectory("metadataBinaryDependencyResolver") { temporaryDirectory ->
val sourceBinaryFile = metadataProvider.getSourceSetCompiledMetadata(
sourceSetName = visibleFragmentName,
outputDirectory = temporaryDirectory,
materializeFile = true
)
if (!sourceBinaryFile.isFile)
return@mapNotNull null
fragment.project.rootDir
.resolve(".gradle").resolve("kotlin").resolve("transformedKotlinMetadata")
.resolve(gradleModuleIdentifier.group)
.resolve(gradleModuleIdentifier.module)
.resolve(gradleModuleIdentifier.version)
.resolve(kotlinModuleIdentifier.moduleName)
.resolve(visibleFragmentName)
.resolve(sourceBinaryFile.name)
.apply { if (!isFile) sourceBinaryFile.copyTo(this) }
}
IdeaKotlinResolvedBinaryDependencyImpl(
binaryType = IdeaKotlinDependency.CLASSPATH_BINARY_TYPE,
binaryFile = binaryFile,
coordinates = IdeaKotlinBinaryCoordinatesImpl(
group = gradleModuleIdentifier.group,
module = gradleModuleIdentifier.module,
version = gradleModuleIdentifier.version,
kotlinModuleName = kotlinModuleIdentifier.moduleName,
kotlinFragmentName = visibleFragmentName
)
)
}
}
}
@@ -0,0 +1,25 @@
/*
* 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.gradle.kpm.idea
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinGradleFragment
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.path
import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
internal object IdeaKotlinMissingFileDependencyLogger : IdeaKotlinDependencyEffect {
override fun invoke(fragment: KotlinGradleFragment, dependencies: Set<IdeaKotlinDependency>) {
dependencies.filterIsInstance<IdeaKotlinResolvedBinaryDependency>()
.filter { !it.binaryFile.exists() }
.ifNotEmpty { fragment.project.logger.warn(buildWarningMessage(fragment, this)) }
}
private fun buildWarningMessage(fragment: KotlinGradleFragment, dependencies: List<IdeaKotlinResolvedBinaryDependency>) = buildString {
append("[WARNING] ${fragment.path}: Missing dependency files")
dependencies.forEach { dependency ->
appendLine(dependency.binaryFile)
}
}
}
@@ -5,13 +5,13 @@
package org.jetbrains.kotlin.gradle.kpm.idea
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinGradleFragment
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinGradleModule
internal fun KotlinGradleModule.toIdeaKotlinModule(): IdeaKotlinModule {
val fragmentsCache = mutableMapOf<KotlinGradleFragment, IdeaKotlinFragment>()
internal fun IdeaKotlinProjectModelBuildingContext.toIdeaKotlinModule(module: KotlinGradleModule): IdeaKotlinModule {
val fragmentBuildingContext = IdeaKotlinFragmentBuildingContext(this)
return IdeaKotlinModuleImpl(
moduleIdentifier = moduleIdentifier.toIdeaKotlinModuleIdentifier(),
fragments = fragments.toList().map { fragment -> fragment.toIdeaKotlinFragment(fragmentsCache) }
name = module.name,
moduleIdentifier = module.moduleIdentifier.toIdeaKotlinModuleIdentifier(),
fragments = module.fragments.toList().map { fragment -> fragmentBuildingContext.toIdeaKotlinFragment(fragment) }
)
}
@@ -0,0 +1,66 @@
/*
* 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.gradle.kpm.idea
import org.gradle.api.Project
import org.jetbrains.kotlin.commonizer.KonanDistribution
import org.jetbrains.kotlin.commonizer.platformLibsDir
import org.jetbrains.kotlin.compilerRunner.konanHome
import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinGradleFragment
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinNativeVariantInternal
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.containingVariants
import org.jetbrains.kotlin.library.ToolingSingleFileKlibResolveStrategy
import org.jetbrains.kotlin.library.resolveSingleFileKlib
import org.jetbrains.kotlin.library.shortName
import org.jetbrains.kotlin.library.uniqueName
import java.io.File
internal class IdeaKotlinNativePlatformDependencyResolver : IdeaKotlinDependencyResolver {
override fun resolve(fragment: KotlinGradleFragment): Set<IdeaKotlinDependency> {
val konanTargets = fragment.containingVariants
.map { it as? KotlinNativeVariantInternal ?: return emptySet() }
.map { it.konanTarget }
.toSet()
/* Fragments with multiple konan targets will receive commonized klibs */
val konanTarget = konanTargets.singleOrNull() ?: return emptySet()
return fragment.project.konanDistribution.platformLibsDir.resolve(konanTarget.name)
.listLibraryFiles()
.mapNotNull { libraryFile -> fragment.project.resolveKlib(libraryFile) }
.toSet()
}
}
private fun Project.resolveKlib(file: File): IdeaKotlinResolvedBinaryDependency? {
try {
val kotlinLibrary = resolveSingleFileKlib(
org.jetbrains.kotlin.konan.file.File(file.absolutePath),
strategy = ToolingSingleFileKlibResolveStrategy
)
return IdeaKotlinResolvedBinaryDependencyImpl(
binaryType = IdeaKotlinDependency.CLASSPATH_BINARY_TYPE,
binaryFile = file,
coordinates = IdeaKotlinBinaryCoordinatesImpl(
group = "org.jetbrains.kotlin.native",
module = kotlinLibrary.shortName ?: kotlinLibrary.uniqueName,
version = project.getKotlinPluginVersion()
)
)
} catch (t: Throwable) {
logger.error("Failed resolving library ${file.path}", t)
return null
}
}
private val Project.konanDistribution: KonanDistribution
get() = KonanDistribution(project.file(konanHome))
private fun File.listLibraryFiles(): Set<File> = listFiles().orEmpty()
.filter { it.isDirectory || it.extension == "klib" }
.toSet()
@@ -0,0 +1,26 @@
/*
* 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.gradle.kpm.idea
import org.jetbrains.kotlin.commonizer.KonanDistribution
import org.jetbrains.kotlin.commonizer.stdlib
import org.jetbrains.kotlin.compilerRunner.konanHome
import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinGradleFragment
internal object IdeaKotlinNativeStdlibDependencyResolver : IdeaKotlinDependencyResolver {
override fun resolve(fragment: KotlinGradleFragment): Set<IdeaKotlinDependency> {
return setOf(
IdeaKotlinResolvedBinaryDependencyImpl(
binaryType = IdeaKotlinDependency.CLASSPATH_BINARY_TYPE,
binaryFile = KonanDistribution(fragment.project.konanHome).stdlib,
coordinates = IdeaKotlinBinaryCoordinatesImpl(
"org.jetbrains.kotlin", "stdlib-native", fragment.project.getKotlinPluginVersion()
)
)
)
}
}
@@ -0,0 +1,40 @@
/*
* 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.gradle.kpm.idea
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.KeepOriginalDependency
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.FragmentGranularMetadataResolverFactory
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinGradleFragment
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.resolvableMetadataConfigurationName
internal class IdeaKotlinOriginalMetadataDependencyResolver(
private val fragmentGranularMetadataResolverFactory: FragmentGranularMetadataResolverFactory
) : IdeaKotlinDependencyResolver {
override fun resolve(fragment: KotlinGradleFragment): Set<IdeaKotlinDependency> {
val dependencyIdentifiers = fragmentGranularMetadataResolverFactory.getOrCreate(fragment).resolutions
.filterIsInstance<KeepOriginalDependency>()
.mapNotNull { resolution -> resolution.dependency.id as? ModuleComponentIdentifier }
.toSet()
val allModuleCompileDependenciesConfiguration = fragment.project.configurations
.getByName(fragment.containingModule.resolvableMetadataConfigurationName)
return allModuleCompileDependenciesConfiguration.incoming.artifactView { view ->
view.componentFilter { id -> id in dependencyIdentifiers }
view.isLenient = true
}.artifacts
.map { artifact ->
val artifactId = artifact.variant.owner as ModuleComponentIdentifier
IdeaKotlinResolvedBinaryDependencyImpl(
binaryType = IdeaKotlinDependency.CLASSPATH_BINARY_TYPE,
binaryFile = artifact.file,
coordinates = IdeaKotlinBinaryCoordinatesImpl(artifactId.group, artifactId.module, artifactId.version)
)
}.toSet()
}
}
@@ -0,0 +1,41 @@
/*
* 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.gradle.kpm.idea
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.*
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrCompilation
import org.jetbrains.kotlin.konan.target.HostManager
@Suppress("unused")
/* Receiver acts as scope, or key to that function */
internal fun IdeaKotlinFragmentBuildingContext.buildIdeaKotlinPlatform(variant: KotlinGradleVariant): IdeaKotlinPlatform {
when (variant) {
is KotlinJvmVariant -> return IdeaKotlinPlatform.jvm(variant.compilationData.kotlinOptions.jvmTarget ?: JvmTarget.DEFAULT.name)
is KotlinNativeVariantInternal -> return IdeaKotlinPlatform.native(variant.konanTarget.name)
is LegacyMappedVariant -> when (val compilation = variant.compilation) {
is KotlinJvmCompilation -> return IdeaKotlinPlatform.jvm(compilation.kotlinOptions.jvmTarget ?: JvmTarget.DEFAULT.name)
is KotlinJvmAndroidCompilation -> return IdeaKotlinPlatform.jvm(compilation.kotlinOptions.jvmTarget ?: JvmTarget.DEFAULT.name)
is KotlinNativeCompilation -> return IdeaKotlinPlatform.native(compilation.konanTarget.name)
is KotlinJsIrCompilation -> return IdeaKotlinPlatform.js(isIr = true)
is KotlinJsCompilation -> return IdeaKotlinPlatform.js(isIr = false)
}
}
/* Fallback calculation based on 'platformType' alone */
/* This is a last line of defence, not expected to be actually executed */
assert(false) { "Unable to build 'IdeaKotlinPlatform' from variant ${variant.path}" }
return when (variant.platformType) {
KotlinPlatformType.common -> throw IllegalArgumentException("Unexpected platformType 'common' for variant ${variant.name}")
KotlinPlatformType.jvm -> IdeaKotlinPlatform.jvm(JvmTarget.DEFAULT.name)
KotlinPlatformType.androidJvm -> IdeaKotlinPlatform.jvm(JvmTarget.DEFAULT.name)
KotlinPlatformType.js -> IdeaKotlinPlatform.js(false)
KotlinPlatformType.native -> IdeaKotlinPlatform.native(HostManager.host.name)
KotlinPlatformType.wasm -> IdeaKotlinPlatform.wasm()
}
}
@@ -0,0 +1,112 @@
/*
* 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.gradle.kpm.idea
import org.gradle.api.artifacts.ArtifactView
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.artifacts.component.ComponentIdentifier
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import org.gradle.api.artifacts.component.ModuleComponentSelector
import org.gradle.api.artifacts.component.ProjectComponentIdentifier
import org.gradle.internal.resolve.ModuleVersionResolveException
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinPlatformDependencyResolver.*
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.*
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.FragmentAttributes
class IdeaKotlinPlatformDependencyResolver(
private val binaryType: String = IdeaKotlinDependency.CLASSPATH_BINARY_TYPE,
private val artifactResolution: ArtifactResolution = ArtifactResolution.Variant()
) : IdeaKotlinDependencyResolver {
sealed class ArtifactResolution {
data class Variant(
internal val artifactViewAttributes: FragmentAttributes<KotlinGradleFragment> = FragmentAttributes { }
) : ArtifactResolution()
data class PlatformFragment(
internal val platformResolutionAttributes: FragmentAttributes<KotlinGradleFragment>,
internal val artifactViewAttributes: FragmentAttributes<KotlinGradleFragment> = FragmentAttributes { },
) : ArtifactResolution()
}
override fun resolve(fragment: KotlinGradleFragment): Set<IdeaKotlinBinaryDependency> {
val artifacts = artifactResolution.createArtifactView(fragment)?.artifacts ?: return emptySet()
val unresolvedDependencies = artifacts.failures
.onEach { reason -> fragment.project.logger.error("Failed to resolve dependency", reason) }
.map { reason ->
val selector = (reason as? ModuleVersionResolveException)?.selector as? ModuleComponentSelector
/* Can't figure out the dependency here :( */
?: return@map IdeaKotlinUnresolvedBinaryDependencyImpl(
coordinates = null, cause = reason.message?.takeIf { it.isNotBlank() }
)
IdeaKotlinUnresolvedBinaryDependencyImpl(
coordinates = IdeaKotlinBinaryCoordinatesImpl(selector.group, selector.module, selector.version),
cause = reason.message?.takeIf { it.isNotBlank() }
)
}.toSet()
val resolvedDependencies = artifacts.artifacts.mapNotNull { artifact ->
IdeaKotlinResolvedBinaryDependencyImpl(
coordinates = artifact.variant.owner.ideaKotlinBinaryCoordinates,
binaryType = binaryType,
binaryFile = artifact.file
)
}.toSet()
return resolvedDependencies + unresolvedDependencies
}
}
private val ComponentIdentifier.ideaKotlinBinaryCoordinates: IdeaKotlinBinaryCoordinates?
get() = when (this) {
is ModuleComponentIdentifier -> IdeaKotlinBinaryCoordinatesImpl(group, module, version)
else -> null
}
private fun ArtifactResolution.createArtifactView(fragment: KotlinGradleFragment): ArtifactView? {
return when (this) {
is ArtifactResolution.Variant -> createVariantArtifactView(fragment)
is ArtifactResolution.PlatformFragment -> createPlatformFragmentArtifactView(fragment)
}
}
private fun ArtifactResolution.Variant.createVariantArtifactView(fragment: KotlinGradleFragment): ArtifactView? {
if (fragment !is KotlinGradleVariant) return null
return fragment.compileDependenciesConfiguration.incoming.artifactView { view ->
view.isLenient = true
view.componentFilter { id -> id !is ProjectComponentIdentifier }
view.attributes.apply(artifactViewAttributes, fragment)
}
}
private fun ArtifactResolution.PlatformFragment.createPlatformFragmentArtifactView(fragment: KotlinGradleFragment): ArtifactView {
val fragmentCompileDependencies = fragment.project.configurations.detachedConfiguration()
fragmentCompileDependencies.dependencies.addAll(
fragment.transitiveApiConfiguration.allDependencies.matching { it !is ProjectDependency }
)
fragmentCompileDependencies.dependencies.addAll(
fragment.transitiveImplementationConfiguration.allDependencies.matching { it !is ProjectDependency }
)
fragmentCompileDependencies.attributes.apply(platformResolutionAttributes, fragment)
/* Ensure consistent dependency resolution result within the whole module */
val allModuleCompileDependencies = fragment.project.configurations.getByName(
fragment.containingModule.resolvableMetadataConfigurationName
)
fragmentCompileDependencies.shouldResolveConsistentlyWith(allModuleCompileDependencies)
return fragmentCompileDependencies.incoming.artifactView { view ->
view.isLenient = true
view.componentFilter { id -> id !is ProjectComponentIdentifier }
view.attributes.apply(artifactViewAttributes, fragment)
}
}
@@ -1,26 +1,104 @@
@file:OptIn(ExternalVariantApi::class)
package org.jetbrains.kotlin.gradle.kpm.idea
import org.gradle.api.Project
import org.gradle.tooling.provider.model.ToolingModelBuilder
import org.jetbrains.kotlin.compilerRunner.konanHome
import org.jetbrains.kotlin.gradle.dsl.pm20Extension
import org.jetbrains.kotlin.gradle.kpm.external.ExternalVariantApi
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinProjectModelBuilder.FragmentConstraint
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.native
import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinGradleFragment
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinPm20ProjectExtension
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.containingVariants
import org.jetbrains.kotlin.project.model.KotlinModuleVariant
import java.io.File
class IdeaKotlinProjectModelBuilder : ToolingModelBuilder {
override fun canBuild(modelName: String): Boolean = modelName == IdeaKotlinProjectModel::class.java.name
override fun buildAll(modelName: String, project: Project): IdeaKotlinProjectModel {
return project.pm20Extension.toIdeaKotlinProjectModel()
internal interface IdeaKotlinProjectModelBuildingContext {
val dependencyResolver: IdeaKotlinDependencyResolver
companion object Empty : IdeaKotlinProjectModelBuildingContext {
override val dependencyResolver: IdeaKotlinDependencyResolver = IdeaKotlinDependencyResolver.Empty
}
}
internal fun KotlinPm20ProjectExtension.toIdeaKotlinProjectModel(): IdeaKotlinProjectModel {
interface IdeaKotlinProjectModelBuilder {
enum class DependencyResolutionPhase {
PreDependencyResolution,
SourceDependencyResolution,
BinaryDependencyResolution,
PostDependencyResolution
}
enum class DependencyResolutionLevel {
Default, Overwrite
}
enum class DependencyTransformationPhase {
PreDependencyTransformationPhase,
FreeDependencyTransformationPhase,
DependencyFilteringPhase,
PostDependencyTransformationPhase
}
fun interface FragmentConstraint {
operator fun invoke(fragment: KotlinGradleFragment): Boolean
companion object {
val unconstrained = FragmentConstraint { true }
val isVariant = FragmentConstraint { fragment -> fragment is KotlinModuleVariant }
val isNative = FragmentConstraint { fragment -> fragment.containingVariants.run { any() && all { it.platformType == native } } }
}
}
@ExternalVariantApi
fun registerDependencyResolver(
resolver: IdeaKotlinDependencyResolver,
constraint: FragmentConstraint,
phase: DependencyResolutionPhase,
level: DependencyResolutionLevel = DependencyResolutionLevel.Default,
)
@ExternalVariantApi
fun registerDependencyTransformer(
transformer: IdeaKotlinDependencyTransformer,
constraint: FragmentConstraint,
phase: DependencyTransformationPhase
)
@ExternalVariantApi
fun registerDependencyEffect(
effect: IdeaKotlinDependencyEffect,
constraint: FragmentConstraint
)
fun buildIdeaKotlinProjectModel(): IdeaKotlinProjectModel
companion object
}
internal fun IdeaKotlinProjectModelBuildingContext.toIdeaKotlinProjectModel(extension: KotlinPm20ProjectExtension): IdeaKotlinProjectModel {
return IdeaKotlinProjectModelImpl(
gradlePluginVersion = project.getKotlinPluginVersion(),
coreLibrariesVersion = coreLibrariesVersion,
explicitApiModeCliOption = explicitApi?.cliOption,
kotlinNativeHome = File(project.konanHome).absoluteFile,
modules = modules.map { module -> module.toIdeaKotlinModule() }
gradlePluginVersion = extension.project.getKotlinPluginVersion(),
coreLibrariesVersion = extension.coreLibrariesVersion,
explicitApiModeCliOption = extension.explicitApi?.cliOption,
kotlinNativeHome = File(extension.project.konanHome).absoluteFile,
modules = extension.modules.map { module -> toIdeaKotlinModule(module) }
)
}
infix fun FragmentConstraint.or(
other: FragmentConstraint
) = FragmentConstraint { fragment ->
this@or(fragment) || other(fragment)
}
infix fun FragmentConstraint.and(
other: FragmentConstraint
): FragmentConstraint = FragmentConstraint { fragment ->
this@and(fragment) && other(fragment)
}
operator fun FragmentConstraint.not() = FragmentConstraint { fragment ->
this@not(fragment).not()
}
@@ -0,0 +1,91 @@
/*
* 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:OptIn(ExternalVariantApi::class)
package org.jetbrains.kotlin.gradle.kpm.idea
import org.jetbrains.kotlin.gradle.kpm.external.ExternalVariantApi
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.FragmentGranularMetadataResolverFactory
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinPm20ProjectExtension
import org.jetbrains.kotlin.gradle.utils.UnsafeApi
@OptIn(UnsafeApi::class)
internal fun IdeaKotlinProjectModelBuilder.Companion.default(
extension: KotlinPm20ProjectExtension
) = IdeaKotlinProjectModelBuilderImpl(extension).apply {
val fragmentMetadataResolverFactory = FragmentGranularMetadataResolverFactory()
registerDependencyResolver(
resolver = IdeaKotlinSourceDependencyResolver(fragmentMetadataResolverFactory),
constraint = IdeaKotlinProjectModelBuilder.FragmentConstraint.unconstrained,
phase = IdeaKotlinProjectModelBuilder.DependencyResolutionPhase.SourceDependencyResolution,
level = IdeaKotlinProjectModelBuilder.DependencyResolutionLevel.Default
)
registerDependencyResolver(
resolver = IdeaKotlinMetadataBinaryDependencyResolver(fragmentMetadataResolverFactory),
constraint = !IdeaKotlinProjectModelBuilder.FragmentConstraint.isVariant,
phase = IdeaKotlinProjectModelBuilder.DependencyResolutionPhase.BinaryDependencyResolution,
level = IdeaKotlinProjectModelBuilder.DependencyResolutionLevel.Default
)
registerDependencyResolver(
resolver = IdeaKotlinOriginalMetadataDependencyResolver(fragmentMetadataResolverFactory),
constraint = !IdeaKotlinProjectModelBuilder.FragmentConstraint.isVariant,
phase = IdeaKotlinProjectModelBuilder.DependencyResolutionPhase.BinaryDependencyResolution,
level = IdeaKotlinProjectModelBuilder.DependencyResolutionLevel.Default
)
registerDependencyResolver(
resolver = IdeaKotlinPlatformDependencyResolver(),
constraint = IdeaKotlinProjectModelBuilder.FragmentConstraint.isVariant,
phase = IdeaKotlinProjectModelBuilder.DependencyResolutionPhase.BinaryDependencyResolution,
level = IdeaKotlinProjectModelBuilder.DependencyResolutionLevel.Default
)
registerDependencyResolver(
resolver = IdeaKotlinNativeStdlibDependencyResolver,
constraint = IdeaKotlinProjectModelBuilder.FragmentConstraint.isNative,
phase = IdeaKotlinProjectModelBuilder.DependencyResolutionPhase.BinaryDependencyResolution,
level = IdeaKotlinProjectModelBuilder.DependencyResolutionLevel.Default
)
registerDependencyResolver(
resolver = IdeaKotlinNativePlatformDependencyResolver(),
constraint = IdeaKotlinProjectModelBuilder.FragmentConstraint.isNative,
phase = IdeaKotlinProjectModelBuilder.DependencyResolutionPhase.BinaryDependencyResolution,
level = IdeaKotlinProjectModelBuilder.DependencyResolutionLevel.Default
)
registerDependencyResolver(
resolver = IdeaKotlinSourcesAndDocumentationResolver(),
constraint = IdeaKotlinProjectModelBuilder.FragmentConstraint.unconstrained,
phase = IdeaKotlinProjectModelBuilder.DependencyResolutionPhase.PostDependencyResolution,
level = IdeaKotlinProjectModelBuilder.DependencyResolutionLevel.Default
)
registerDependencyTransformer(
transformer = IdeaKotlinSinglePlatformStdlibCommonFilter,
phase = IdeaKotlinProjectModelBuilder.DependencyTransformationPhase.DependencyFilteringPhase,
constraint = IdeaKotlinProjectModelBuilder.FragmentConstraint.unconstrained
)
registerDependencyTransformer(
transformer = IdeaKotlinUnusedSourcesAndDocumentationFilter,
phase = IdeaKotlinProjectModelBuilder.DependencyTransformationPhase.DependencyFilteringPhase,
constraint = IdeaKotlinProjectModelBuilder.FragmentConstraint.unconstrained
)
registerDependencyEffect(
effect = IdeaKotlinDependencyLogger,
constraint = IdeaKotlinProjectModelBuilder.FragmentConstraint.unconstrained
)
registerDependencyEffect(
effect = IdeaKotlinMissingFileDependencyLogger,
constraint = IdeaKotlinProjectModelBuilder.FragmentConstraint.unconstrained
)
}
@@ -0,0 +1,136 @@
/*
* 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:OptIn(ExternalVariantApi::class)
package org.jetbrains.kotlin.gradle.kpm.idea
import org.gradle.api.Project
import org.gradle.tooling.provider.model.ToolingModelBuilder
import org.jetbrains.kotlin.gradle.kpm.external.ExternalVariantApi
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinProjectModelBuilder.*
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinPm20ProjectExtension
import org.jetbrains.kotlin.gradle.utils.UnsafeApi
internal class IdeaKotlinProjectModelBuilderImpl @UnsafeApi("Use factory methods instead") constructor(
private val extension: KotlinPm20ProjectExtension,
) : ToolingModelBuilder, IdeaKotlinProjectModelBuilder {
private data class RegisteredDependencyResolver(
val resolver: IdeaKotlinDependencyResolver,
val constraint: FragmentConstraint,
val phase: DependencyResolutionPhase,
val level: DependencyResolutionLevel,
)
private data class RegisteredDependencyTransformer(
val transformer: IdeaKotlinDependencyTransformer,
val constraint: FragmentConstraint,
val phase: DependencyTransformationPhase
)
private data class RegisteredDependencyEffect(
val effect: IdeaKotlinDependencyEffect,
val constraint: FragmentConstraint,
)
private val registeredDependencyResolvers = mutableListOf<RegisteredDependencyResolver>()
private val registeredDependencyTransformers = mutableListOf<RegisteredDependencyTransformer>()
private val registeredDependencyEffects = mutableListOf<RegisteredDependencyEffect>()
override fun registerDependencyResolver(
resolver: IdeaKotlinDependencyResolver,
constraint: FragmentConstraint,
phase: DependencyResolutionPhase,
level: DependencyResolutionLevel
) {
registeredDependencyResolvers.add(
RegisteredDependencyResolver(resolver, constraint, phase, level)
)
}
override fun registerDependencyTransformer(
transformer: IdeaKotlinDependencyTransformer,
constraint: FragmentConstraint,
phase: DependencyTransformationPhase
) {
registeredDependencyTransformers.add(
RegisteredDependencyTransformer(transformer, constraint, phase)
)
}
override fun registerDependencyEffect(
effect: IdeaKotlinDependencyEffect,
constraint: FragmentConstraint
) {
registeredDependencyEffects.add(
RegisteredDependencyEffect(effect, constraint)
)
}
override fun buildIdeaKotlinProjectModel(): IdeaKotlinProjectModel {
return Context().toIdeaKotlinProjectModel(extension)
}
override fun canBuild(modelName: String): Boolean =
modelName == IdeaKotlinProjectModel::class.java.name
override fun buildAll(modelName: String, project: Project): IdeaKotlinProjectModel {
check(project === extension.project) { "Expected project ${extension.project.path}, found ${project.path}" }
return buildIdeaKotlinProjectModel()
}
private inner class Context : IdeaKotlinProjectModelBuildingContext {
override val dependencyResolver = createDependencyResolver()
}
private fun createDependencyResolver(): IdeaKotlinDependencyResolver {
return IdeaKotlinDependencyResolver(DependencyResolutionPhase.values().map { phase ->
createDependencyResolver(phase)
}).withTransformer(createDependencyTransformer())
.withEffect(createDependencyEffect())
}
private fun createDependencyResolver(phase: DependencyResolutionPhase) = IdeaKotlinDependencyResolver resolve@{ fragment ->
val applicableResolvers = registeredDependencyResolvers
.filter { it.phase == phase }
.filter { it.constraint(fragment) }
.groupBy { it.level }
/* Find resolvers in the highest resolution level and only consider those */
DependencyResolutionLevel.values().reversed().forEach { level ->
val resolvers = applicableResolvers[level].orEmpty().map { it.resolver }
if (resolvers.isNotEmpty()) {
return@resolve IdeaKotlinDependencyResolver(resolvers).resolve(fragment)
}
}
/* No resolvers found */
emptySet()
}
private fun createDependencyTransformer(): IdeaKotlinDependencyTransformer {
return IdeaKotlinDependencyTransformer(DependencyTransformationPhase.values().map { phase ->
createDependencyTransformer(phase)
})
}
private fun createDependencyTransformer(phase: DependencyTransformationPhase): IdeaKotlinDependencyTransformer {
return IdeaKotlinDependencyTransformer { fragment, dependencies ->
IdeaKotlinDependencyTransformer(
registeredDependencyTransformers
.filter { it.phase == phase }
.filter { it.constraint(fragment) }
.map { it.transformer }
).transform(fragment, dependencies)
}
}
private fun createDependencyEffect(): IdeaKotlinDependencyEffect = IdeaKotlinDependencyEffect { fragment, dependencies ->
registeredDependencyEffects
.filter { it.constraint(fragment) }
.forEach { it.effect(fragment, dependencies) }
}
}
@@ -0,0 +1,28 @@
/*
* 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.gradle.kpm.idea
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinGradleFragment
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.containingVariants
internal object IdeaKotlinSinglePlatformStdlibCommonFilter : IdeaKotlinDependencyTransformer {
private const val stdlibCoordinatesGroup = "org.jetbrains.kotlin"
private val stdlibCoordinatesModules = setOf("kotlin-stdlib-common", "kotlin-test-common", "kotlin-test-annotations-common")
override fun transform(
fragment: KotlinGradleFragment,
dependencies: Set<IdeaKotlinDependency>
): Set<IdeaKotlinDependency> {
val platforms = fragment.containingVariants.map { it.platformType }.toSet()
if (platforms.size != 1) return dependencies
return dependencies.filter { dependency ->
val binaryDependency = dependency as? IdeaKotlinBinaryDependency ?: return@filter true
val coordinates = binaryDependency.coordinates ?: return@filter true
coordinates.group != stdlibCoordinatesGroup || coordinates.module !in stdlibCoordinatesModules
}.toSet()
}
}
@@ -0,0 +1,39 @@
/*
* 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.gradle.kpm.idea
import org.gradle.api.artifacts.component.ProjectComponentIdentifier
import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.FragmentGranularMetadataResolverFactory
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinGradleFragment
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinGradleModule.Companion.moduleName
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.toModuleDependency
internal class IdeaKotlinSourceDependencyResolver(
private val fragmentGranularMetadataResolverFactory: FragmentGranularMetadataResolverFactory
) : IdeaKotlinDependencyResolver {
override fun resolve(fragment: KotlinGradleFragment): Set<IdeaKotlinDependency> {
return fragmentGranularMetadataResolverFactory.getOrCreate(fragment).resolutions
.filterIsInstance<ChooseVisibleSourceSets>()
.flatMap { resolution -> resolve(resolution) }
.toSet()
}
private fun resolve(resolution: ChooseVisibleSourceSets): Iterable<IdeaKotlinDependency> {
val gradleProjectIdentifier = resolution.dependency.id as? ProjectComponentIdentifier ?: return emptyList()
val kotlinModuleIdentifier = resolution.dependency.toModuleDependency().moduleIdentifier
return resolution.allVisibleSourceSetNames.map { visibleFragmentName ->
IdeaKotlinSourceDependencyImpl(
buildId = gradleProjectIdentifier.build.name,
projectPath = gradleProjectIdentifier.projectPath,
projectName = gradleProjectIdentifier.projectName,
kotlinModuleName = kotlinModuleIdentifier.moduleName,
kotlinModuleClassifier = kotlinModuleIdentifier.moduleClassifier,
kotlinFragmentName = visibleFragmentName
)
}
}
}
@@ -0,0 +1,57 @@
/*
* 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.gradle.kpm.idea
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import org.gradle.api.artifacts.result.ArtifactResolutionResult
import org.gradle.api.artifacts.result.ResolvedArtifactResult
import org.gradle.api.component.Artifact
import org.gradle.jvm.JvmLibrary
import org.gradle.language.base.artifact.SourcesArtifact
import org.gradle.language.java.artifact.JavadocArtifact
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinGradleFragment
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinGradleVariant
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.resolvableMetadataConfigurationName
internal class IdeaKotlinSourcesAndDocumentationResolver : IdeaKotlinDependencyResolver {
override fun resolve(fragment: KotlinGradleFragment): Set<IdeaKotlinDependency> {
if (fragment is KotlinGradleVariant) {
return resolve(fragment.project, fragment.compileDependenciesConfiguration)
}
val metadataDependencies = fragment.project.configurations.getByName(fragment.containingModule.resolvableMetadataConfigurationName)
return resolve(fragment.project, metadataDependencies)
}
private fun resolve(project: Project, configuration: Configuration): Set<IdeaKotlinDependency> {
val resolutionResult = project.dependencies.createArtifactResolutionQuery()
.forComponents(configuration.incoming.resolutionResult.allComponents.map { it.id })
.withArtifacts(JvmLibrary::class.java, SourcesArtifact::class.java, JavadocArtifact::class.java)
.execute()
return resolve(resolutionResult, SourcesArtifact::class.java, IdeaKotlinDependency.SOURCES_BINARY_TYPE) +
resolve(resolutionResult, JavadocArtifact::class.java, IdeaKotlinDependency.DOCUMENTATION_BINARY_TYPE)
}
fun resolve(
resolutionResult: ArtifactResolutionResult, artifactType: Class<out Artifact>, binaryType: String
): Set<IdeaKotlinDependency> {
return resolutionResult.resolvedComponents.flatMap { resolved ->
resolved.getArtifacts(artifactType)
.filterIsInstance<ResolvedArtifactResult>()
.mapNotNull { artifact ->
val id = artifact.id.componentIdentifier as? ModuleComponentIdentifier ?: return@mapNotNull null
IdeaKotlinResolvedBinaryDependencyImpl(
coordinates = IdeaKotlinBinaryCoordinatesImpl(group = id.group, module = id.module, version = id.version),
binaryType = binaryType,
binaryFile = artifact.file
)
}
}.toSet()
}
}
@@ -0,0 +1,30 @@
/*
* 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.gradle.kpm.idea
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinGradleFragment
internal object IdeaKotlinUnusedSourcesAndDocumentationFilter : IdeaKotlinDependencyTransformer {
override fun transform(
fragment: KotlinGradleFragment, dependencies: Set<IdeaKotlinDependency>
): Set<IdeaKotlinDependency> {
val sourcesAndDocumentationDependencies = dependencies
.filterIsInstance<IdeaKotlinResolvedBinaryDependency>()
.filter { dependency -> dependency.isSourcesType || dependency.isDocumentationType }
.toSet()
val classpathCoordinates = dependencies.filterIsInstance<IdeaKotlinResolvedBinaryDependency>()
.filter { dependency -> dependency.isClasspathType }
.mapNotNull { it.coordinates }
.toSet()
val unusedSourcesAndDocumentationDependencies = sourcesAndDocumentationDependencies
.filter { it.coordinates !in classpathCoordinates }
.toSet()
return dependencies - unusedSourcesAndDocumentationDependencies
}
}
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLI
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_HIERARCHICAL_STRUCTURE_BY_DEFAULT
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_HIERARCHICAL_STRUCTURE_SUPPORT
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_NATIVE_DEPENDENCY_PROPAGATION
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_STDLIB_DEFAULT_DEPENDENCY
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMultiplatformPlugin
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService
import org.jetbrains.kotlin.gradle.targets.js.dukat.ExternalsOutputFormat
@@ -437,7 +438,7 @@ internal class PropertiesProvider private constructor(private val project: Proje
get() = booleanProperty(NOWARN_2JS_FLAG) ?: false
val stdlibDefaultDependency: Boolean
get() = booleanProperty("kotlin.stdlib.default.dependency") ?: true
get() = booleanProperty(KOTLIN_STDLIB_DEFAULT_DEPENDENCY) ?: true
val kotlinTestInferJvmVariant: Boolean
get() = booleanProperty("kotlin.test.infer.jvm.variant") ?: true
@@ -500,6 +501,7 @@ internal class PropertiesProvider private constructor(private val project: Proje
}
object PropertyNames {
const val KOTLIN_STDLIB_DEFAULT_DEPENDENCY = "kotlin.stdlib.default.dependency"
const val KOTLIN_MPP_ENABLE_GRANULAR_SOURCE_SETS_METADATA = "kotlin.mpp.enableGranularSourceSetsMetadata"
const val KOTLIN_MPP_ENABLE_CINTEROP_COMMONIZATION = "kotlin.mpp.enableCInteropCommonization"
const val KOTLIN_MPP_HIERARCHICAL_STRUCTURE_BY_DEFAULT = "kotlin.internal.mpp.hierarchicalStructureByDefault"
@@ -529,6 +531,6 @@ internal class PropertiesProvider private constructor(private val project: Proje
?: PropertiesProvider(project) // Fallback if multiple class loaders are involved
}
internal val Project.kotlinPropertiesProvider get() = PropertiesProvider(this)
internal val Project.kotlinPropertiesProvider get() = invoke(this)
}
}
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinGradleModule
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.hasKpmModel
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.refinesClosure
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.withRefinesClosure
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope
import org.jetbrains.kotlin.gradle.plugin.sources.sourceSetDependencyConfigurationByScope
import org.jetbrains.kotlin.gradle.targets.metadata.dependsOnClosureWithInterCompilationDependencies
@@ -200,7 +201,7 @@ internal fun buildProjectStructureMetadata(module: KotlinGradleModule): KotlinPr
}.toMap()
val kotlinFragmentsPerKotlinVariant =
module.variants.associate { variant -> variant.name to variant.refinesClosure.map { it.name }.toSet() }
module.variants.associate { variant -> variant.name to variant.withRefinesClosure.map { it.name }.toSet() }
val fragmentRefinesRelation =
module.fragments.associate { it.name to it.directRefinesDependencies.map { it.fragmentName }.toSet() }
@@ -48,7 +48,7 @@ internal class FragmentGranularMetadataResolver(
error("unexpected failure in dependency graph resolution for $requestingFragment in $project")
dependencyGraph as GradleDependencyGraph // refactor the type hierarchy to avoid this downcast? FIXME?
val fragmentsToInclude = requestingFragment.refinesClosure
val fragmentsToInclude = requestingFragment.withRefinesClosure
val requestedDependencies = dependencyGraph.root.dependenciesByFragment.filterKeys { it in fragmentsToInclude }.values.flatten()
val visited = mutableSetOf<GradleDependencyGraphNode>()
@@ -15,14 +15,13 @@ import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinTopLevelExtension
import org.jetbrains.kotlin.gradle.dsl.pm20Extension
import org.jetbrains.kotlin.gradle.dsl.topLevelExtensionOrNull
import org.jetbrains.kotlin.gradle.internal.customizeKotlinDependencies
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinProjectModelBuilder
import org.jetbrains.kotlin.gradle.kpm.idea.default
import org.jetbrains.kotlin.gradle.kpm.idea.locateOrRegisterBuildIdeaKotlinProjectModelTask
import org.jetbrains.kotlin.gradle.utils.checkGradleCompatibility
import org.jetbrains.kotlin.project.model.KotlinModuleIdentifier
import javax.inject.Inject
abstract class KotlinPm20GradlePlugin @Inject constructor(
@@ -40,7 +39,7 @@ abstract class KotlinPm20GradlePlugin @Inject constructor(
registerDefaultVariantFactories(project)
setupFragmentsMetadataForKpmModules(project)
setupKpmModulesPublication(project)
setupToolingModelBuilder()
setupToolingModelBuilder(project)
}
private fun createDefaultModules(project: Project) {
@@ -82,8 +81,9 @@ abstract class KotlinPm20GradlePlugin @Inject constructor(
}
}
private fun setupToolingModelBuilder() {
toolingModelBuilderRegistry.register(IdeaKotlinProjectModelBuilder())
private fun setupToolingModelBuilder(project: Project) {
toolingModelBuilderRegistry.register(project.pm20Extension.ideaKotlinProjectModelBuilder)
project.locateOrRegisterBuildIdeaKotlinProjectModelTask()
}
}
@@ -94,6 +94,8 @@ open class KotlinPm20ProjectExtension(project: Project) : KotlinTopLevelExtensio
internal val kpmModelContainer = DefaultKpmGradleProjectModelContainer.create(project)
internal val ideaKotlinProjectModelBuilder by lazy { IdeaKotlinProjectModelBuilder.default(this) }
val modules: NamedDomainObjectContainer<KotlinGradleModule>
get() = project.kpmModules
@@ -49,6 +49,12 @@ fun <T : KotlinGradleFragment> FragmentAttributes(
}
}
fun <T : KotlinGradleFragment> AttributeContainer.apply(
attributes: KotlinGradleFragmentConfigurationAttributes<T>, fragment: T
) {
attributes.setAttributes(this, fragment)
}
operator fun <T : KotlinGradleFragment> FragmentAttributes<T>.plus(other: FragmentAttributes<T>): FragmentAttributes<T> {
if (this === KotlinGradleFragmentConfigurationAttributes.None) return other
if (other === KotlinGradleFragmentConfigurationAttributes.None) return this
@@ -59,16 +59,16 @@ internal abstract class AbstractKotlinFragmentMetadataCompilationData<T : Kotlin
override var compileDependencyFiles: FileCollection by project.newProperty {
createMetadataDependencyTransformationClasspath(
project,
resolvableMetadataConfiguration(fragment.containingModule),
lazy {
project = project,
fromFiles = resolvableMetadataConfiguration(fragment.containingModule),
parentCompiledMetadataFiles = lazy {
fragment.refinesClosure.minus(fragment).map {
val compilation = metadataCompilationRegistry.getForFragmentOrNull(it)
?: return@map project.files()
compilation.output.classesDirs
}
},
resolvedMetadataFiles
metadataResolutionProviders = resolvedMetadataFiles
)
}
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.gradle.plugin.mpp.pm20
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.attributes.Category
import org.gradle.api.attributes.Usage
import org.gradle.api.tasks.TaskProvider
import org.gradle.jvm.tasks.Jar
@@ -109,7 +110,9 @@ private fun createResolvableMetadataConfigurationForModule(module: KotlinGradleM
project.configurations.create(module.resolvableMetadataConfigurationName).apply {
isCanBeConsumed = false
isCanBeResolved = true
attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.common)
attributes.attribute(Usage.USAGE_ATTRIBUTE, project.usageByName(KotlinUsages.KOTLIN_METADATA))
attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.categoryByName(Category.LIBRARY))
module.fragments.all { fragment ->
project.addExtendsFromRelation(name, fragment.apiConfigurationName)
project.addExtendsFromRelation(name, fragment.implementationConfigurationName)
@@ -71,7 +71,7 @@ abstract class KotlinCompileCommon @Inject constructor(
is AbstractKotlinFragmentMetadataCompilationData -> {
val fragment = compilation.fragment
project.files(
fragment.refinesClosure.minus(fragment).map {
fragment.refinesClosure.map {
val compilation = compilation.metadataCompilationRegistry.getForFragmentOrNull(it)
?: return@map project.files()
compilation.output.classesDirs
@@ -59,4 +59,13 @@ internal fun File.isParentOf(childCandidate: File, strict: Boolean = false): Boo
internal fun File.canonicalPathWithoutExtension(): String =
canonicalPath.substringBeforeLast(".")
internal fun File.listFilesOrEmpty() = (if (exists()) listFiles() else null).orEmpty()
internal fun File.listFilesOrEmpty() = (if (exists()) listFiles() else null).orEmpty()
internal inline fun <T> withTemporaryDirectory(prefix: String, action: (directory: File) -> T): T {
val directory = Files.createTempDirectory(prefix).toFile()
return try {
action(directory)
} finally {
directory.deleteRecursively()
}
}
@@ -11,7 +11,9 @@ import org.jetbrains.kotlin.konan.library.KONAN_DISTRIBUTION_PLATFORM_LIBS_DIR
import org.jetbrains.kotlin.konan.library.KONAN_STDLIB_NAME
import java.io.File
public data class KonanDistribution(val root: File)
public data class KonanDistribution(val root: File) {
public constructor(rootPath: String) : this(File(rootPath))
}
public val KonanDistribution.konanCommonLibraries: File
get() = root.resolve(KONAN_DISTRIBUTION_KLIB_DIR).resolve(KONAN_DISTRIBUTION_COMMON_LIBS_DIR)