[KPM] Implement initial IdeaKotlinProjectModelBuilder

- Introduce `kotlin-gradle-plugin-idea` module that allows
to share models between the IDE and KGP

- Add `kotlin-gradle-plugin-idea` to the RuntimePublicAPITest to
ensure binary compatibility

^KT-51262
This commit is contained in:
sebastian.sellmair
2022-02-11 15:54:26 +01:00
committed by Space
parent faf0cbea5d
commit e91785ba39
29 changed files with 353 additions and 7 deletions
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinScriptingSettings">
<option name="suppressDefinitionsCheck" value="true" />
</component>
</project>
+1
View File
@@ -362,6 +362,7 @@ val projectsWithDisabledFirBootstrap = coreLibProjects + listOf(
val gradlePluginProjects = listOf(
":kotlin-gradle-plugin",
":kotlin-gradle-plugin-api",
":kotlin-gradle-plugin-idea",
":kotlin-gradle-plugin-kpm-android",
":kotlin-allopen",
":kotlin-annotation-processing-gradle",
+1
View File
@@ -40,6 +40,7 @@ val kotlinGradlePluginAndItsRequired = arrayOf(
":kotlin-daemon-client",
":kotlin-project-model",
":kotlin-gradle-plugin-api",
":kotlin-gradle-plugin-idea",
":kotlin-gradle-plugin",
":kotlin-gradle-plugin-model",
":kotlin-tooling-metadata",
@@ -7,7 +7,7 @@ and ensure that the public binary API wasn't changed in a way that make this cha
Compile and run tests. `CasesPublicAPITest` verifies the tool itself,
and `RuntimePublicAPITest` dumps the public API of `kotlin-stdlib`,
`kotlin-stdlib-jdk7/8`, and `kotlin-reflect` jars,
`kotlin-stdlib-jdk7/8`, `kotlin-reflect` and `kotlin-gradle-plugin-idea` jars,
which must be built beforehand with gradle. Use `clean assemble` tasks,
since the incremental compilation currently doesn't produce all the required output.
@@ -14,6 +14,7 @@ dependencies {
testArtifacts project(':kotlin-stdlib-jdk7')
testArtifacts project(':kotlin-stdlib-jdk8')
testArtifacts project(':kotlin-reflect')
testArtifacts project(':kotlin-gradle-plugin-idea')
}
pill {
@@ -33,6 +33,10 @@ class RuntimePublicAPITest {
snapshotAPIAndCompare("../../reflect/api/build/libs", "kotlin-reflect-api(?!-[-a-z]+)", nonPublicPackages = listOf("kotlin.reflect.jvm.internal"))
}
@Test fun kotlinGradlePluginIdea() {
snapshotAPIAndCompare("../kotlin-gradle-plugin-idea/build/libs", "kotlin-gradle-plugin-idea(?!-[-a-z]+)")
}
private fun snapshotAPIAndCompare(
basePath: String,
jarPattern: String,
@@ -40,7 +40,7 @@ interface ExperimentalExtensionConfig {
/**
* Different modes that can be used to set the level of issue reporting for [KotlinTopLevelExtensionConfig.explicitApi] option.
*/
enum class ExplicitApiMode(private val cliOption: String) {
enum class ExplicitApiMode(val cliOption: String) {
/** Report issues as errors. */
Strict("strict"),
@@ -31,6 +31,8 @@ interface KotlinGradleFragment : KotlinModuleFragment, HasKotlinDependencies, Ko
fun refines(other: NamedDomainObjectProvider<KotlinGradleFragment>)
override val directRefinesDependencies: Iterable<KotlinGradleFragment>
override fun dependencies(configureClosure: Closure<Any?>) =
dependencies f@{ project.configure(this@f, configureClosure) }
@@ -0,0 +1,13 @@
# Kotlin Gradle Plugin Models shared between IDEA import and KGP
The classes defined in this module will be available inside the Kotlin Gradle Plugin (KGP)
as well as in the IDE import (GradleProject).
## Binary Compatibility:
Binary compatibility can be tested by
`./gradlew :tools:binary-compatibility-validator:clean :tools:binary-compatibility-validator:test`
Binary incompatible changes have to go through a proper deprecation cycle after releases
### Unstable APIs / KotlinGradlePluginApi
Some APIs are marked with 'KotlinGradlePluginApi' which means, that those are not kept binary compatible and are considered 'unstable'
from the IDEA perspective. Typically constructors for models are marked like this
@@ -0,0 +1,9 @@
plugins {
kotlin("jvm")
}
dependencies {
implementation(project(":kotlin-stdlib"))
}
publish()
@@ -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 java.io.Serializable
interface IdeaKotlinFragment : Serializable {
val name: String
val languageSettings: IdeaKotlinLanguageSettings?
val dependencies: Collection<IdeaKotlinFragmentDependency>
val directRefinesDependencies: Collection<IdeaKotlinFragment>
val sourceDirectories: Collection<IdeaKotlinSourceDirectory>
val resourceDirectories: Collection<IdeaKotlinResourceDirectory>
}
class IdeaKotlinFragmentImpl @KotlinGradlePluginApi constructor(
override val name: String,
override val languageSettings: IdeaKotlinLanguageSettings?,
override val dependencies: Collection<IdeaKotlinFragmentDependency>,
override val directRefinesDependencies: Collection<IdeaKotlinFragment>,
override val sourceDirectories: Collection<IdeaKotlinSourceDirectory>,
override val resourceDirectories: Collection<IdeaKotlinResourceDirectory>
) : IdeaKotlinFragment
@@ -0,0 +1,8 @@
/*
* 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
interface IdeaKotlinFragmentDependency
@@ -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 java.io.File
interface IdeaKotlinLanguageSettings {
val languageVersion: String?
val apiVersion: String?
val isProgressiveMode: Boolean
val enabledLanguageFeatures: Collection<String>
val optInAnnotationsInUse: Collection<String>
val compilerPluginArguments: Collection<String>
val compilerPluginClasspath: Collection<File>
val freeCompilerArgs: Collection<String>
}
class IdeaKotlinLanguageSettingsImpl @KotlinGradlePluginApi constructor(
override val languageVersion: String?,
override val apiVersion: String?,
override val isProgressiveMode: Boolean,
override val enabledLanguageFeatures: Collection<String>,
override val optInAnnotationsInUse: Collection<String>,
override val compilerPluginArguments: Collection<String>,
override val compilerPluginClasspath: Collection<File>,
override val freeCompilerArgs: Collection<String>
) : IdeaKotlinLanguageSettings
@@ -0,0 +1,18 @@
/*
* 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 IdeaKotlinModule : Serializable {
val moduleIdentifier: IdeaKotlinModuleIdentifier
val fragments: Collection<IdeaKotlinFragment>
}
class IdeaKotlinModuleImpl @KotlinGradlePluginApi constructor(
override val moduleIdentifier: IdeaKotlinModuleIdentifier,
override val fragments: Collection<IdeaKotlinFragment>
) : IdeaKotlinModule
@@ -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
import java.io.Serializable
sealed interface IdeaKotlinModuleIdentifier : Serializable {
val moduleClassifier: String?
}
interface IdeaKotlinLocalModuleIdentifier : IdeaKotlinModuleIdentifier {
val buildId: String
val projectId: String
}
interface IdeaKotlinMavenModuleIdentifier : IdeaKotlinModuleIdentifier {
val group: String
val name: String
}
class IdeaKotlinLocalModuleIdentifierImpl @KotlinGradlePluginApi constructor(
override val moduleClassifier: String?,
override val buildId: String,
override val projectId: String
) : IdeaKotlinLocalModuleIdentifier
class IdeaKotlinMavenModuleIdentifierImpl @KotlinGradlePluginApi constructor(
override val moduleClassifier: String?,
override val group: String,
override val name: String
) : IdeaKotlinMavenModuleIdentifier
@@ -0,0 +1,23 @@
/*
* 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.File
import java.io.Serializable
interface IdeaKotlinProjectModel : Serializable {
val coreLibrariesVersion: String
val explicitApiModeCliOption: String?
val kotlinNativeHome: File
val modules: List<IdeaKotlinModule>
}
class IdeaKotlinProjectModelImpl @KotlinGradlePluginApi constructor(
override val coreLibrariesVersion: String,
override val explicitApiModeCliOption: String?,
override val kotlinNativeHome: File,
override val modules: List<IdeaKotlinModule>
) : IdeaKotlinProjectModel
@@ -0,0 +1,17 @@
/*
* 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.File
import java.io.Serializable
interface IdeaKotlinResourceDirectory : Serializable {
val file: File
}
class IdeaKotlinResourceDirectoryImpl @KotlinGradlePluginApi constructor(
override val file: File
) : IdeaKotlinResourceDirectory
@@ -0,0 +1,17 @@
/*
* 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.File
import java.io.Serializable
interface IdeaKotlinSourceDirectory : Serializable {
val file: File
}
class IdeaKotlinSourceDirectoryImpl @KotlinGradlePluginApi constructor(
override val file: File,
) : IdeaKotlinSourceDirectory
@@ -0,0 +1,12 @@
/*
* 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
@RequiresOptIn(
level = RequiresOptIn.Level.ERROR,
message = "This API can only be used by the Kotlin Gradle Plugin and is not kept stable for access inside the IDE process"
)
annotation class KotlinGradlePluginApi
@@ -30,6 +30,7 @@ kotlin.sourceSets.all {
languageSettings.optIn("kotlin.RequiresOptIn")
languageSettings.optIn("org.jetbrains.kotlin.gradle.plugin.mpp.external.ExternalVariantApi")
languageSettings.optIn("org.jetbrains.kotlin.gradle.plugin.mpp.external.AdvancedExternalVariantApi")
languageSettings.optIn("org.jetbrains.kotlin.gradle.kpm.idea.KotlinGradlePluginApi")
}
dependencies {
@@ -56,6 +57,7 @@ dependencies {
embedded(project(":kotlin-gradle-statistics"))
compileOnly(project(":kotlin-gradle-build-metrics"))
embedded(project(":kotlin-gradle-build-metrics"))
implementation(project(":kotlin-gradle-plugin-idea"))
implementation(commonDependency("com.google.code.gson:gson"))
implementation(commonDependency("com.google.guava:guava"))
@@ -0,0 +1,27 @@
/*
* 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 fun KotlinGradleFragment.toIdeaKotlinFragment(
cache: MutableMap<KotlinGradleFragment, IdeaKotlinFragment> = mutableMapOf()
): IdeaKotlinFragment {
return cache.getOrPut(this) {
IdeaKotlinFragmentImpl(
name = name,
languageSettings = languageSettings.toIdeaKotlinLanguageSettings(),
dependencies = emptyList(),
directRefinesDependencies = directRefinesDependencies.map { refinesFragment ->
refinesFragment.toIdeaKotlinFragment(cache)
},
sourceDirectories = kotlinSourceRoots.sourceDirectories.files.toList().map { file ->
IdeaKotlinSourceDirectoryImpl(file)
},
resourceDirectories = emptyList()
)
}
}
@@ -0,0 +1,22 @@
/*
* 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.sources.DefaultLanguageSettingsBuilder
import org.jetbrains.kotlin.project.model.LanguageSettings
internal fun LanguageSettings.toIdeaKotlinLanguageSettings(): IdeaKotlinLanguageSettings {
return IdeaKotlinLanguageSettingsImpl(
languageVersion = languageVersion,
apiVersion = apiVersion,
isProgressiveMode = progressiveMode,
enabledLanguageFeatures = enabledLanguageFeatures.toSet(),
optInAnnotationsInUse = optInAnnotationsInUse.toSet(),
compilerPluginArguments = (this as? DefaultLanguageSettingsBuilder)?.compilerPluginArguments?.toList().orEmpty(),
compilerPluginClasspath = (this as? DefaultLanguageSettingsBuilder)?.compilerPluginClasspath?.toList().orEmpty(),
freeCompilerArgs = (this as? DefaultLanguageSettingsBuilder)?.freeCompilerArgs?.toList().orEmpty()
)
}
@@ -0,0 +1,17 @@
/*
* 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.KotlinGradleModule
internal fun KotlinGradleModule.toIdeaKotlinModule(): IdeaKotlinModule {
val fragmentsCache = mutableMapOf<KotlinGradleFragment, IdeaKotlinFragment>()
return IdeaKotlinModuleImpl(
moduleIdentifier = moduleIdentifier.toIdeaKotlinModuleIdentifier(),
fragments = fragments.toList().map { fragment -> fragment.toIdeaKotlinFragment(fragmentsCache) }
)
}
@@ -0,0 +1,21 @@
/*
* 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.project.model.KotlinModuleIdentifier
import org.jetbrains.kotlin.project.model.LocalModuleIdentifier
import org.jetbrains.kotlin.project.model.MavenModuleIdentifier
internal fun KotlinModuleIdentifier.toIdeaKotlinModuleIdentifier(): IdeaKotlinModuleIdentifier {
return when (this) {
is LocalModuleIdentifier -> IdeaKotlinLocalModuleIdentifierImpl(
moduleClassifier, buildId = buildId, projectId = projectId
)
is MavenModuleIdentifier -> IdeaKotlinMavenModuleIdentifierImpl(
moduleClassifier, group = group, name = name
)
}
}
@@ -0,0 +1,24 @@
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.plugin.mpp.pm20.KotlinPm20ProjectExtension
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 fun KotlinPm20ProjectExtension.toIdeaKotlinProjectModel(): IdeaKotlinProjectModel {
return IdeaKotlinProjectModelImpl(
coreLibrariesVersion = coreLibrariesVersion,
explicitApiModeCliOption = explicitApi?.cliOption,
kotlinNativeHome = File(project.konanHome).absoluteFile,
modules = modules.map { module -> module.toIdeaKotlinModule() }
)
}
@@ -13,18 +13,21 @@ import org.gradle.api.component.SoftwareComponentFactory
import org.gradle.api.plugins.JavaBasePlugin
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.utils.checkGradleCompatibility
import org.jetbrains.kotlin.project.model.KotlinModuleIdentifier
import javax.inject.Inject
abstract class KotlinPm20GradlePlugin @Inject constructor(
@Inject private val softwareComponentFactory: SoftwareComponentFactory
@Inject private val softwareComponentFactory: SoftwareComponentFactory,
@Inject private val toolingModelBuilderRegistry: ToolingModelBuilderRegistry
) : Plugin<Project> {
override fun apply(project: Project) {
checkGradleCompatibility("the Kotlin Multiplatform plugin", GradleVersion.version("6.1"))
@@ -37,6 +40,7 @@ abstract class KotlinPm20GradlePlugin @Inject constructor(
registerDefaultVariantFactories(project)
setupFragmentsMetadataForKpmModules(project)
setupKpmModulesPublication(project)
setupToolingModelBuilder()
}
private fun createDefaultModules(project: Project) {
@@ -77,6 +81,10 @@ abstract class KotlinPm20GradlePlugin @Inject constructor(
}
}
}
private fun setupToolingModelBuilder() {
toolingModelBuilderRegistry.register(IdeaKotlinProjectModelBuilder())
}
}
fun rootPublicationComponentName(module: KotlinGradleModule) =
@@ -102,7 +102,7 @@ internal open class LegacyMappedVariant(
override val fragmentName: String
get() = fragmentForDefaultSourceSet.fragmentName + "Variant"
override val directRefinesDependencies: Iterable<KotlinModuleFragment>
override val directRefinesDependencies: Iterable<KotlinGradleFragment>
get() = fragmentForDefaultSourceSet.directRefinesDependencies
override val declaredModuleDependencies: Iterable<KotlinModuleDependency>
@@ -239,4 +239,4 @@ internal fun mapTargetCompilationsToKpmVariants(target: AbstractKotlinTarget, pu
VariantPublishingConfigurator.get(target.project).configurePublishing(request)
}
}
}
}
@@ -7,8 +7,7 @@ package org.jetbrains.kotlin.project.model
import java.io.Serializable
// TODO sealed with an abstract subclass? this will make exhaustive checks work
open class KotlinModuleIdentifier(open val moduleClassifier: String?): Serializable
sealed class KotlinModuleIdentifier(open val moduleClassifier: String?): Serializable
// TODO consider id: Any, to allow IDs with custom equality?
data class LocalModuleIdentifier(
+2
View File
@@ -227,6 +227,7 @@ include ":benchmarks",
":tools:kotlinp",
":kotlin-project-model",
":kotlin-gradle-plugin-api",
":kotlin-gradle-plugin-idea",
":kotlin-gradle-plugin-dsl-codegen",
":kotlin-gradle-plugin-npm-versions-codegen",
":kotlin-gradle-statistics",
@@ -631,6 +632,7 @@ project(':kotlin-sam-with-receiver-compiler-plugin').projectDir = "$rootDir/plug
project(':tools:kotlinp').projectDir = "$rootDir/libraries/tools/kotlinp" as File
project(':kotlin-project-model').projectDir = "$rootDir/libraries/tools/kotlin-project-model" as File
project(':kotlin-gradle-plugin-api').projectDir = "$rootDir/libraries/tools/kotlin-gradle-plugin-api" as File
project(':kotlin-gradle-plugin-idea').projectDir = "$rootDir/libraries/tools/kotlin-gradle-plugin-idea" as File
project(':kotlin-gradle-plugin-dsl-codegen').projectDir = "$rootDir/libraries/tools/kotlin-gradle-plugin-dsl-codegen" as File
project(':kotlin-gradle-plugin-npm-versions-codegen').projectDir = "$rootDir/libraries/tools/kotlin-gradle-plugin-npm-versions-codegen" as File
project(':kotlin-gradle-statistics').projectDir = "$rootDir/libraries/tools/kotlin-gradle-statistics" as File