From 1a5fc84080d86116ead7d141450ae18b663a4caf Mon Sep 17 00:00:00 2001 From: "sebastian.sellmair" Date: Mon, 7 Mar 2022 11:12:22 +0100 Subject: [PATCH] [KPM] Implement initial kpm/idea dependency resolution ^KT-51386 Verification Pending --- .../kotlin-gradle-plugin-api/build.gradle.kts | 4 +- .../plugin/mpp/pm20/KotlinGradleFragment.kt | 15 +- .../build.gradle.kts | 24 ++- .../gradle/kpm/idea/IdeaKotlinDependency.kt | 131 ++++++++++++ .../gradle/kpm/idea/IdeaKotlinFragment.kt | 6 +- .../kpm/idea/IdeaKotlinFragmentDependency.kt | 20 -- .../gradle/kpm/idea/IdeaKotlinModule.kt | 2 + .../gradle/kpm/idea/IdeaKotlinPlatform.kt | 100 +++++++++ .../gradle/kpm/idea/IdeaKotlinVariant.kt | 2 + .../src/test/kotlin/GradleUtils.kt | 4 + .../kpm/KotlinExternalModelContainerTest.kt | 4 +- ...ckwardsCompatibilityDeserializationTest.kt | 7 +- .../IdeaKotlinDependencyMatcher.kt | 75 +++++++ .../kpm/idea/testFixtures/assertions.kt | 90 ++++++++ .../kpm/idea/testFixtures/modelUtils.kt | 34 +++ .../idea/testFixtures/serializationUtil.kt} | 8 +- .../gradle/android/AndroidTargetExtension.kt | 4 +- .../android/IdeaDependencyResolution.kt | 94 +++++++++ .../android/KotlinAndroidVariantFactory.kt | 8 +- .../gradle/android/androidCommonFragment.kt | 3 +- .../kotlin-gradle-plugin/build.gradle.kts | 2 + .../gradle/MultiplatformExtensionTest.kt | 5 + .../gradle/kpm/AbstractKpmExtensionTest.kt | 20 +- ...LightweightIdeaDependencyResolutionTest.kt | 30 +++ .../IdeaKotlinProjectModelSerializableTest.kt | 16 +- .../MviKotlinIdeaDependencyResolutionTest.kt | 197 ++++++++++++++++++ ...tdlibKotlinIdeaDependencyResolutionTest.kt | 163 +++++++++++++++ .../gradle/kpm/external/ExternalVariantApi.kt | 5 + .../idea/BuildIdeaKotlinProjectModelTask.kt | 54 +++++ ...lVariantPlatformDependencyResolutionDsl.kt | 137 ++++++++++++ .../kpm/idea/IdeaKotlinDependencyEffect.kt | 16 ++ .../kpm/idea/IdeaKotlinDependencyLogger.kt | 26 +++ .../kpm/idea/IdeaKotlinDependencyResolver.kt | 54 +++++ .../idea/IdeaKotlinDependencyTransformer.kt | 53 +++++ .../kpm/idea/IdeaKotlinFragmentBuilder.kt | 49 +++-- ...aKotlinMetadataBinaryDependencyResolver.kt | 72 +++++++ .../IdeaKotlinMissingFileDependencyLogger.kt | 25 +++ .../kpm/idea/IdeaKotlinModuleBuilder.kt | 10 +- ...aKotlinNativePlatformDependencyResolver.kt | 66 ++++++ ...deaKotlinNativeStdlibDependencyResolver.kt | 26 +++ ...otlinOriginalMetadataDependencyResolver.kt | 40 ++++ .../kpm/idea/IdeaKotlinPlatformBuilder.kt | 41 ++++ .../IdeaKotlinPlatformDependencyResolver.kt | 112 ++++++++++ .../kpm/idea/IdeaKotlinProjectModelBuilder.kt | 104 +++++++-- .../IdeaKotlinProjectModelBuilderFactory.kt | 91 ++++++++ .../idea/IdeaKotlinProjectModelBuilderImpl.kt | 136 ++++++++++++ ...aKotlinSinglePlatformStdlibCommonFilter.kt | 28 +++ .../IdeaKotlinSourceDependencyResolver.kt | 39 ++++ ...eaKotlinSourcesAndDocumentationResolver.kt | 57 +++++ ...tlinUnusedSourcesAndDocumentationFilter.kt | 30 +++ .../kotlin/gradle/plugin/KotlinProperties.kt | 6 +- .../mpp/KotlinProjectStructureMetadata.kt | 3 +- .../pm20/FragmentGranularMetadataResolver.kt | 2 +- .../gradle/plugin/mpp/pm20/GradlePlugin.kt | 14 +- ...inGradleFragmentConfigurationAttributes.kt | 6 + ...ent.kt => KotlinGradleFragmentInternal.kt} | 0 .../mpp/pm20/KotlinMetadataCompilationData.kt | 8 +- .../mpp/pm20/fragmentsMetadataCompilation.kt | 3 + .../gradle/tasks/KotlinCompileCommon.kt | 2 +- .../kotlin/gradle/utils/fileUtils.kt | 11 +- .../kotlin/commonizer/KonanDistribution.kt | 4 +- 61 files changed, 2278 insertions(+), 120 deletions(-) create mode 100644 libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinDependency.kt delete mode 100644 libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinFragmentDependency.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinPlatform.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-idea/src/testFixtures/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/testFixtures/IdeaKotlinDependencyMatcher.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-idea/src/testFixtures/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/testFixtures/assertions.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-idea/src/testFixtures/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/testFixtures/modelUtils.kt rename libraries/tools/kotlin-gradle-plugin-idea/src/{test/kotlin/SerializationUtil.kt => testFixtures/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/testFixtures/serializationUtil.kt} (79%) create mode 100644 libraries/tools/kotlin-gradle-plugin-kpm-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/IdeaDependencyResolution.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/AbstractLightweightIdeaDependencyResolutionTest.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/MviKotlinIdeaDependencyResolutionTest.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/StdlibKotlinIdeaDependencyResolutionTest.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/BuildIdeaKotlinProjectModelTask.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/ExternalVariantPlatformDependencyResolutionDsl.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinDependencyEffect.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinDependencyLogger.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinDependencyResolver.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinDependencyTransformer.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinMetadataBinaryDependencyResolver.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinMissingFileDependencyLogger.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinNativePlatformDependencyResolver.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinNativeStdlibDependencyResolver.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinOriginalMetadataDependencyResolver.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinPlatformBuilder.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinPlatformDependencyResolver.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinProjectModelBuilderFactory.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinProjectModelBuilderImpl.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinSinglePlatformStdlibCommonFilter.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinSourceDependencyResolver.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinSourcesAndDocumentationResolver.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinUnusedSourcesAndDocumentationFilter.kt rename libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/{KotlinGradleFragment.kt => KotlinGradleFragmentInternal.kt} (100%) diff --git a/libraries/tools/kotlin-gradle-plugin-api/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-api/build.gradle.kts index 64013a16b30..fc65068da9c 100644 --- a/libraries/tools/kotlin-gradle-plugin-api/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin-api/build.gradle.kts @@ -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") -} \ No newline at end of file +} diff --git a/libraries/tools/kotlin-gradle-plugin-api/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleFragment.kt b/libraries/tools/kotlin-gradle-plugin-api/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleFragment.kt index a87000e47e7..c4a577c5642 100644 --- a/libraries/tools/kotlin-gradle-plugin-api/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleFragment.kt +++ b/libraries/tools/kotlin-gradle-plugin-api/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleFragment.kt @@ -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 + get() = this.withClosure { it.directRefinesDependencies } + val KotlinGradleFragment.refinesClosure: Set - 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 + get() = containingModule.variantsContainingFragment(this).map { it as KotlinGradleVariant }.toSet() diff --git a/libraries/tools/kotlin-gradle-plugin-idea/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-idea/build.gradle.kts index 555e6029fb3..0151a19a3c3 100644 --- a/libraries/tools/kotlin-gradle-plugin-idea/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin-idea/build.gradle.kts @@ -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 } ) } } diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinDependency.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinDependency.kt new file mode 100644 index 00000000000..0010cd3f9e0 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinDependency.kt @@ -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 + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinFragment.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinFragment.kt index 89efeb2897b..9b2850c57b9 100644 --- a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinFragment.kt +++ b/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinFragment.kt @@ -11,8 +11,9 @@ import java.io.Serializable interface IdeaKotlinFragment : Serializable { val name: String val moduleIdentifier: IdeaKotlinModuleIdentifier + val platforms: Set val languageSettings: IdeaKotlinLanguageSettings? - val dependencies: List + val dependencies: List val directRefinesDependencies: List val sourceDirectories: List val resourceDirectories: List @@ -23,8 +24,9 @@ interface IdeaKotlinFragment : Serializable { data class IdeaKotlinFragmentImpl( override val name: String, override val moduleIdentifier: IdeaKotlinModuleIdentifier, + override val platforms: Set, override val languageSettings: IdeaKotlinLanguageSettings?, - override val dependencies: List, + override val dependencies: List, override val directRefinesDependencies: List, override val sourceDirectories: List, override val resourceDirectories: List, diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinFragmentDependency.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinFragmentDependency.kt deleted file mode 100644 index 21dd2bebd7a..00000000000 --- a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinFragmentDependency.kt +++ /dev/null @@ -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 - } -} diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinModule.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinModule.kt index d6f2c231cfc..4f5741686aa 100644 --- a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinModule.kt +++ b/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinModule.kt @@ -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 } @InternalKotlinGradlePluginApi data class IdeaKotlinModuleImpl( + override val name: String, override val moduleIdentifier: IdeaKotlinModuleIdentifier, override val fragments: List ) : IdeaKotlinModule { diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinPlatform.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinPlatform.kt new file mode 100644 index 00000000000..1b61f135205 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinPlatform.kt @@ -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 + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinVariant.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinVariant.kt index 777a81edacd..bb823a1e65c 100644 --- a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinVariant.kt +++ b/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinVariant.kt @@ -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 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, override val compilationOutputs: IdeaKotlinCompilationOutput, ) : IdeaKotlinVariant, IdeaKotlinFragment by fragment { diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/test/kotlin/GradleUtils.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/test/kotlin/GradleUtils.kt index 5b81f3b3ef7..0f1f7287ec8 100644 --- a/libraries/tools/kotlin-gradle-plugin-idea/src/test/kotlin/GradleUtils.kt +++ b/libraries/tools/kotlin-gradle-plugin-idea/src/test/kotlin/GradleUtils.kt @@ -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 { 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) } diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/test/kotlin/org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelContainerTest.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/test/kotlin/org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelContainerTest.kt index 1150180ddaa..9fc5a6d0348 100644 --- a/libraries/tools/kotlin-gradle-plugin-idea/src/test/kotlin/org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelContainerTest.kt +++ b/libraries/tools/kotlin-gradle-plugin-idea/src/test/kotlin/org/jetbrains/kotlin/gradle/kpm/KotlinExternalModelContainerTest.kt @@ -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 diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/test/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/BackwardsCompatibilityDeserializationTest.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/test/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/BackwardsCompatibilityDeserializationTest.kt index a9cc01fa354..91a6b9ccde4 100644 --- a/libraries/tools/kotlin-gradle-plugin-idea/src/test/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/BackwardsCompatibilityDeserializationTest.kt +++ b/libraries/tools/kotlin-gradle-plugin-idea/src/test/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/BackwardsCompatibilityDeserializationTest.kt @@ -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 diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/testFixtures/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/testFixtures/IdeaKotlinDependencyMatcher.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/testFixtures/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/testFixtures/IdeaKotlinDependencyMatcher.kt new file mode 100644 index 00000000000..c11bc4c72d5 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-idea/src/testFixtures/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/testFixtures/IdeaKotlinDependencyMatcher.kt @@ -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 { + 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 + ) + } + } +} + diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/testFixtures/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/testFixtures/assertions.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/testFixtures/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/testFixtures/assertions.kt new file mode 100644 index 00000000000..6752a17ffce --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-idea/src/testFixtures/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/testFixtures/assertions.kt @@ -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 +): Set { + 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, +) = assertResolvedBinaryDependencies(binaryType, matchers.flatMap { buildIdeaKotlinDependencyMatchers(it) }.toSet()) + +fun IdeaKotlinFragment.assertResolvedBinaryDependencies( + binaryType: String, vararg matchers: Any? +) = assertResolvedBinaryDependencies(binaryType, matchers.toSet()) diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/testFixtures/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/testFixtures/modelUtils.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/testFixtures/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/testFixtures/modelUtils.kt new file mode 100644 index 00000000000..5f91bdc8c83 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-idea/src/testFixtures/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/testFixtures/modelUtils.kt @@ -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.parsableString() = + joinToString("," + System.lineSeparator(), "", "") { "\"$it\"" } + +@Suppress("unused") // Debugging API +fun IdeaKotlinFragment.parsableDependencyCoordinatesString(): String { + return dependencies.filterIsInstance() + .mapNotNull { it.coordinates }.toSet() + .parsableString() +} diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/test/kotlin/SerializationUtil.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/testFixtures/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/testFixtures/serializationUtil.kt similarity index 79% rename from libraries/tools/kotlin-gradle-plugin-idea/src/test/kotlin/SerializationUtil.kt rename to libraries/tools/kotlin-gradle-plugin-idea/src/testFixtures/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/testFixtures/serializationUtil.kt index 4c75cad9c6d..a6e82087e29 100644 --- a/libraries/tools/kotlin-gradle-plugin-idea/src/test/kotlin/SerializationUtil.kt +++ b/libraries/tools/kotlin-gradle-plugin-idea/src/testFixtures/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/testFixtures/serializationUtil.kt @@ -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 ByteArray.deserialize(): T { +inline fun 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() } diff --git a/libraries/tools/kotlin-gradle-plugin-kpm-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/AndroidTargetExtension.kt b/libraries/tools/kotlin-gradle-plugin-kpm-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/AndroidTargetExtension.kt index c9f25cf9125..1bd9582923a 100644 --- a/libraries/tools/kotlin-gradle-plugin-kpm-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/AndroidTargetExtension.kt +++ b/libraries/tools/kotlin-gradle-plugin-kpm-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/AndroidTargetExtension.kt @@ -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()?.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() } diff --git a/libraries/tools/kotlin-gradle-plugin-kpm-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/IdeaDependencyResolution.kt b/libraries/tools/kotlin-gradle-plugin-kpm-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/IdeaDependencyResolution.kt new file mode 100644 index 00000000000..e9e0e2295c8 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-kpm-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/IdeaDependencyResolution.kt @@ -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 + ) + } + } + } + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-kpm-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/KotlinAndroidVariantFactory.kt b/libraries/tools/kotlin-gradle-plugin-kpm-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/KotlinAndroidVariantFactory.kt index 2c46a8f1b27..0ab7c257f63 100644 --- a/libraries/tools/kotlin-gradle-plugin-kpm-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/KotlinAndroidVariantFactory.kt +++ b/libraries/tools/kotlin-gradle-plugin-kpm-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/KotlinAndroidVariantFactory.kt @@ -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 { + 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 } diff --git a/libraries/tools/kotlin-gradle-plugin-kpm-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/androidCommonFragment.kt b/libraries/tools/kotlin-gradle-plugin-kpm-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/androidCommonFragment.kt index d6051d4b502..0ed5dee6b6d 100644 --- a/libraries/tools/kotlin-gradle-plugin-kpm-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/androidCommonFragment.kt +++ b/libraries/tools/kotlin-gradle-plugin-kpm-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/androidCommonFragment.kt @@ -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 diff --git a/libraries/tools/kotlin-gradle-plugin/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin/build.gradle.kts index 2ad07cedb50..c8bf6be025d 100644 --- a/libraries/tools/kotlin-gradle-plugin/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin/build.gradle.kts @@ -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")) diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/MultiplatformExtensionTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/MultiplatformExtensionTest.kt index ee0ef40fa5d..2e00f71bada 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/MultiplatformExtensionTest.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/MultiplatformExtensionTest.kt @@ -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()) +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/kpm/AbstractKpmExtensionTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/kpm/AbstractKpmExtensionTest.kt index f419e8a1c62..4a86623ecd7 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/kpm/AbstractKpmExtensionTest.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/kpm/AbstractKpmExtensionTest.kt @@ -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() +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/AbstractLightweightIdeaDependencyResolutionTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/AbstractLightweightIdeaDependencyResolutionTest.kt new file mode 100644 index 00000000000..06b08777f5c --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/AbstractLightweightIdeaDependencyResolutionTest.kt @@ -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) +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinProjectModelSerializableTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinProjectModelSerializableTest.kt index 2e5892185fa..02a7165e7cf 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinProjectModelSerializableTest.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinProjectModelSerializableTest.kt @@ -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() assertEquals( model.toString(), deserializedModel.toString(), diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/MviKotlinIdeaDependencyResolutionTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/MviKotlinIdeaDependencyResolutionTest.kt new file mode 100644 index 00000000000..891430d1d4a --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/MviKotlinIdeaDependencyResolutionTest.kt @@ -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 + ) + } + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/StdlibKotlinIdeaDependencyResolutionTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/StdlibKotlinIdeaDependencyResolutionTest.kt new file mode 100644 index 00000000000..51e7a1fa0d4 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/StdlibKotlinIdeaDependencyResolutionTest.kt @@ -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), + ) + } + } + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/external/ExternalVariantApi.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/external/ExternalVariantApi.kt index 80ada0fe5d5..12bf564b5fe 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/external/ExternalVariantApi.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/external/ExternalVariantApi.kt @@ -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, diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/BuildIdeaKotlinProjectModelTask.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/BuildIdeaKotlinProjectModelTask.kt new file mode 100644 index 00000000000..60e3d311743 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/BuildIdeaKotlinProjectModelTask.kt @@ -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 { + 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" + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/ExternalVariantPlatformDependencyResolutionDsl.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/ExternalVariantPlatformDependencyResolutionDsl.kt new file mode 100644 index 00000000000..c4a785db4b7 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/ExternalVariantPlatformDependencyResolutionDsl.kt @@ -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() + private val children = mutableListOf() + private val additionalDependencies = mutableListOf() + + @ExternalVariantPlatformDependencyResolutionDsl + var platformResolutionAttributes: FragmentAttributes? = null + + @ExternalVariantPlatformDependencyResolutionDsl + class ArtifactViewDslHandle( + @property:ExternalVariantPlatformDependencyResolutionDsl + var binaryType: String + ) { + @ExternalVariantPlatformDependencyResolutionDsl + var attributes: FragmentAttributes = FragmentAttributes { } + + @ExternalVariantPlatformDependencyResolutionDsl + fun attributes(setAttributes: KotlinGradleFragmentConfigurationAttributesContext.() -> Unit) { + attributes += FragmentAttributes(setAttributes) + } + } + + @ExternalVariantPlatformDependencyResolutionDsl + fun withConstraint( + constraint: FragmentConstraint, + configure: IdeaKotlinPlatformDependencyResolutionDslHandle.() -> Unit + ) { + children += IdeaKotlinPlatformDependencyResolutionDslHandle( + toolingModelBuilder, constraint, this, + ).apply(configure) + } + + + @ExternalVariantPlatformDependencyResolutionDsl + fun withPlatformResolutionAttributes( + setAttributes: KotlinGradleFragmentConfigurationAttributesContext.() -> 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) { + 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? { + val parentAttributes = parent?.buildPlatformResolutionAttributes() ?: return this.platformResolutionAttributes + return parentAttributes + (this.platformResolutionAttributes ?: return parentAttributes) + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinDependencyEffect.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinDependencyEffect.kt new file mode 100644 index 00000000000..3e8cc2a18c9 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinDependencyEffect.kt @@ -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) +} + +fun IdeaKotlinDependencyResolver.withEffect(effect: IdeaKotlinDependencyEffect) = IdeaKotlinDependencyResolver { fragment -> + this@withEffect.resolve(fragment).also { dependencies -> effect(fragment, dependencies) } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinDependencyLogger.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinDependencyLogger.kt new file mode 100644 index 00000000000..d1de8874088 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinDependencyLogger.kt @@ -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 + ) { + 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) + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinDependencyResolver.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinDependencyResolver.kt new file mode 100644 index 00000000000..4779c59172d --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinDependencyResolver.kt @@ -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 + + object Empty : IdeaKotlinDependencyResolver { + override fun resolve(fragment: KotlinGradleFragment): Set = emptySet() + } +} + +fun IdeaKotlinDependencyResolver( + resolvers: Iterable +): 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 { + override fun resolve(fragment: KotlinGradleFragment): Set { + return children.flatMap { child -> child.resolve(fragment) }.toSet() + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinDependencyTransformer.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinDependencyTransformer.kt new file mode 100644 index 00000000000..3299d71da2b --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinDependencyTransformer.kt @@ -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 + ): Set +} + +fun IdeaKotlinDependencyResolver.withTransformer(transformer: IdeaKotlinDependencyTransformer) = IdeaKotlinDependencyResolver { fragment -> + transformer.transform(fragment, this@withTransformer.resolve(fragment)) +} + +fun IdeaKotlinDependencyTransformer( + transformers: List +): 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 { + override fun transform( + fragment: KotlinGradleFragment, dependencies: Set + ): Set { + return transformers.fold(dependencies) { currentDependencies, transformer -> transformer.transform(fragment, currentDependencies) } + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinFragmentBuilder.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinFragmentBuilder.kt index cf52f5d5715..589c5e86845 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinFragmentBuilder.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinFragmentBuilder.kt @@ -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 +internal class IdeaKotlinFragmentBuildingContext( + private val parent: IdeaKotlinProjectModelBuildingContext +) : IdeaKotlinProjectModelBuildingContext by parent { + private val fragmentCache = mutableMapOf() + 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() ) } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinMetadataBinaryDependencyResolver.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinMetadataBinaryDependencyResolver.kt new file mode 100644 index 00000000000..02bd55a431c --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinMetadataBinaryDependencyResolver.kt @@ -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 { + return fragmentGranularMetadataResolverFactory.getOrCreate(fragment).resolutions + .filterIsInstance() + .flatMap { resolution -> resolve(fragment, resolution) } + .toSet() + } + + private fun resolve(fragment: KotlinGradleFragment, resolution: ChooseVisibleSourceSets): Iterable { + 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 + ) + ) + } + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinMissingFileDependencyLogger.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinMissingFileDependencyLogger.kt new file mode 100644 index 00000000000..3cee4fb9185 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinMissingFileDependencyLogger.kt @@ -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) { + dependencies.filterIsInstance() + .filter { !it.binaryFile.exists() } + .ifNotEmpty { fragment.project.logger.warn(buildWarningMessage(fragment, this)) } + } + + private fun buildWarningMessage(fragment: KotlinGradleFragment, dependencies: List) = buildString { + append("[WARNING] ${fragment.path}: Missing dependency files") + dependencies.forEach { dependency -> + appendLine(dependency.binaryFile) + } + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinModuleBuilder.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinModuleBuilder.kt index 8453f9f9d71..d4632ede5c5 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinModuleBuilder.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinModuleBuilder.kt @@ -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() +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) } ) } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinNativePlatformDependencyResolver.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinNativePlatformDependencyResolver.kt new file mode 100644 index 00000000000..a7743f03e56 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinNativePlatformDependencyResolver.kt @@ -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 { + 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 = listFiles().orEmpty() + .filter { it.isDirectory || it.extension == "klib" } + .toSet() diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinNativeStdlibDependencyResolver.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinNativeStdlibDependencyResolver.kt new file mode 100644 index 00000000000..cc5ca5e1e9e --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinNativeStdlibDependencyResolver.kt @@ -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 { + return setOf( + IdeaKotlinResolvedBinaryDependencyImpl( + binaryType = IdeaKotlinDependency.CLASSPATH_BINARY_TYPE, + binaryFile = KonanDistribution(fragment.project.konanHome).stdlib, + coordinates = IdeaKotlinBinaryCoordinatesImpl( + "org.jetbrains.kotlin", "stdlib-native", fragment.project.getKotlinPluginVersion() + ) + ) + ) + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinOriginalMetadataDependencyResolver.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinOriginalMetadataDependencyResolver.kt new file mode 100644 index 00000000000..65c2680fe65 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinOriginalMetadataDependencyResolver.kt @@ -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 { + val dependencyIdentifiers = fragmentGranularMetadataResolverFactory.getOrCreate(fragment).resolutions + .filterIsInstance() + .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() + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinPlatformBuilder.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinPlatformBuilder.kt new file mode 100644 index 00000000000..bc7c7589a3a --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinPlatformBuilder.kt @@ -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() + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinPlatformDependencyResolver.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinPlatformDependencyResolver.kt new file mode 100644 index 00000000000..8633cea6c1d --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinPlatformDependencyResolver.kt @@ -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 = FragmentAttributes { } + ) : ArtifactResolution() + + data class PlatformFragment( + internal val platformResolutionAttributes: FragmentAttributes, + internal val artifactViewAttributes: FragmentAttributes = FragmentAttributes { }, + ) : ArtifactResolution() + } + + override fun resolve(fragment: KotlinGradleFragment): Set { + 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) + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinProjectModelBuilder.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinProjectModelBuilder.kt index 40e3321cb0e..d712628e87a 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinProjectModelBuilder.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinProjectModelBuilder.kt @@ -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() +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinProjectModelBuilderFactory.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinProjectModelBuilderFactory.kt new file mode 100644 index 00000000000..0ac33ea4855 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinProjectModelBuilderFactory.kt @@ -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 + ) +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinProjectModelBuilderImpl.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinProjectModelBuilderImpl.kt new file mode 100644 index 00000000000..4ef94cae816 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinProjectModelBuilderImpl.kt @@ -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() + private val registeredDependencyTransformers = mutableListOf() + private val registeredDependencyEffects = mutableListOf() + + 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) } + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinSinglePlatformStdlibCommonFilter.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinSinglePlatformStdlibCommonFilter.kt new file mode 100644 index 00000000000..89064364d47 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinSinglePlatformStdlibCommonFilter.kt @@ -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 + ): Set { + 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() + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinSourceDependencyResolver.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinSourceDependencyResolver.kt new file mode 100644 index 00000000000..44a9ef9d92a --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinSourceDependencyResolver.kt @@ -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 { + return fragmentGranularMetadataResolverFactory.getOrCreate(fragment).resolutions + .filterIsInstance() + .flatMap { resolution -> resolve(resolution) } + .toSet() + } + + private fun resolve(resolution: ChooseVisibleSourceSets): Iterable { + 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 + ) + } + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinSourcesAndDocumentationResolver.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinSourcesAndDocumentationResolver.kt new file mode 100644 index 00000000000..7e00ede1c32 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinSourcesAndDocumentationResolver.kt @@ -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 { + 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 { + 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, binaryType: String + ): Set { + return resolutionResult.resolvedComponents.flatMap { resolved -> + resolved.getArtifacts(artifactType) + .filterIsInstance() + .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() + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinUnusedSourcesAndDocumentationFilter.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinUnusedSourcesAndDocumentationFilter.kt new file mode 100644 index 00000000000..372a9a6056c --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinUnusedSourcesAndDocumentationFilter.kt @@ -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 + ): Set { + val sourcesAndDocumentationDependencies = dependencies + .filterIsInstance() + .filter { dependency -> dependency.isSourcesType || dependency.isDocumentationType } + .toSet() + + val classpathCoordinates = dependencies.filterIsInstance() + .filter { dependency -> dependency.isClasspathType } + .mapNotNull { it.coordinates } + .toSet() + + val unusedSourcesAndDocumentationDependencies = sourcesAndDocumentationDependencies + .filter { it.coordinates !in classpathCoordinates } + .toSet() + + return dependencies - unusedSourcesAndDocumentationDependencies + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinProperties.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinProperties.kt index 630d558fb56..0d2425ca75f 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinProperties.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinProperties.kt @@ -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) } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinProjectStructureMetadata.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinProjectStructureMetadata.kt index f1f74bfc483..26b9bd8b54f 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinProjectStructureMetadata.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinProjectStructureMetadata.kt @@ -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() } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/FragmentGranularMetadataResolver.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/FragmentGranularMetadataResolver.kt index c443c6a76d7..9f8a0ce74cd 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/FragmentGranularMetadataResolver.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/FragmentGranularMetadataResolver.kt @@ -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() diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/GradlePlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/GradlePlugin.kt index 31aec3e5f12..f7148b2a705 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/GradlePlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/GradlePlugin.kt @@ -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 get() = project.kpmModules diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleFragmentConfigurationAttributes.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleFragmentConfigurationAttributes.kt index c3f6ba80d7b..9c987838115 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleFragmentConfigurationAttributes.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleFragmentConfigurationAttributes.kt @@ -49,6 +49,12 @@ fun FragmentAttributes( } } +fun AttributeContainer.apply( + attributes: KotlinGradleFragmentConfigurationAttributes, fragment: T +) { + attributes.setAttributes(this, fragment) +} + operator fun FragmentAttributes.plus(other: FragmentAttributes): FragmentAttributes { if (this === KotlinGradleFragmentConfigurationAttributes.None) return other if (other === KotlinGradleFragmentConfigurationAttributes.None) return this diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleFragment.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleFragmentInternal.kt similarity index 100% rename from libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleFragment.kt rename to libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleFragmentInternal.kt diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinMetadataCompilationData.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinMetadataCompilationData.kt index 46b59b15181..4ba2a0925e4 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinMetadataCompilationData.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinMetadataCompilationData.kt @@ -59,16 +59,16 @@ internal abstract class AbstractKotlinFragmentMetadataCompilationData project.addExtendsFromRelation(name, fragment.apiConfigurationName) project.addExtendsFromRelation(name, fragment.implementationConfigurationName) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinCompileCommon.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinCompileCommon.kt index 357e843e6d4..b9084e1992f 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinCompileCommon.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinCompileCommon.kt @@ -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 diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/fileUtils.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/fileUtils.kt index 6c90cfb5291..1fc7eb20448 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/fileUtils.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/fileUtils.kt @@ -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() \ No newline at end of file +internal fun File.listFilesOrEmpty() = (if (exists()) listFiles() else null).orEmpty() + +internal inline fun withTemporaryDirectory(prefix: String, action: (directory: File) -> T): T { + val directory = Files.createTempDirectory(prefix).toFile() + return try { + action(directory) + } finally { + directory.deleteRecursively() + } +} diff --git a/native/commonizer-api/src/org/jetbrains/kotlin/commonizer/KonanDistribution.kt b/native/commonizer-api/src/org/jetbrains/kotlin/commonizer/KonanDistribution.kt index 9eb10c4a8bb..2fb8d881760 100644 --- a/native/commonizer-api/src/org/jetbrains/kotlin/commonizer/KonanDistribution.kt +++ b/native/commonizer-api/src/org/jetbrains/kotlin/commonizer/KonanDistribution.kt @@ -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)