[Kotlin Project Model] Add compiler plugin support

This commit is contained in:
Anton Lakotka
2021-04-23 16:42:45 +02:00
committed by Space
parent 16e6fe0e08
commit 5a58c1c437
13 changed files with 292 additions and 25 deletions
@@ -0,0 +1,17 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.plugin.mpp.pm20
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.jetbrains.kotlin.project.model.*
/**
* Gradle plugin that provides [KpmCompilerPlugin] from applied [Project]
*/
interface GradleKpmCompilerPlugin : Plugin<Project> {
val kpmCompilerPlugin: KpmCompilerPlugin
}
@@ -13,6 +13,7 @@ import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.plugin.HasKotlinDependencies
import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler
import org.jetbrains.kotlin.project.model.KotlinModule
import org.jetbrains.kotlin.project.model.KpmCompilerPlugin
interface KotlinGradleModule : KotlinModule, Named, HasKotlinDependencies {
val project: Project
@@ -23,6 +24,8 @@ interface KotlinGradleModule : KotlinModule, Named, HasKotlinDependencies {
// TODO DSL & build script model: find a way to create a flexible typed view on fragments?
override val variants: NamedDomainObjectSet<KotlinGradleVariant>
override val plugins: Set<KpmCompilerPlugin>
val isPublic: Boolean
fun ifMadePublic(action: () -> Unit)
@@ -0,0 +1,63 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.plugin.mpp.pm20
import org.gradle.api.Project
import org.gradle.api.provider.Provider
import org.jetbrains.kotlin.gradle.plugin.SubpluginOption
import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
import org.jetbrains.kotlin.gradle.tasks.CompilerPluginOptions
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilerPluginData
import org.jetbrains.kotlin.gradle.utils.newProperty
import org.jetbrains.kotlin.project.model.*
internal fun Project.compilerPluginProviderForMetadata(
fragment: KotlinGradleFragment,
compilationData: KotlinCommonFragmentMetadataCompilationData
) = compilerPluginDataProvider(compilationData, fragment::metadataCompilationPluginData)
internal fun Project.compilerPluginProviderForNativeMetadata(
fragment: KotlinGradleFragment,
compilationData: KotlinNativeFragmentMetadataCompilationData
) = compilerPluginDataProvider(compilationData, fragment::nativeMetadataCompilationPluginData)
internal fun Project.compilerPluginProviderForPlatformCompilation(
variant: KotlinGradleVariant,
compilationData: KotlinCompilationData<*>
) = compilerPluginDataProvider(compilationData, variant::platformCompilationPluginData)
internal fun KotlinCompilationData<*>.pluginClasspathConfigurationName() = "${compileKotlinTaskName}PluginClasspath"
private fun Project.compilerPluginDataProvider(
compilationData: KotlinCompilationData<*>,
pluginDataList: () -> List<PluginData>
): Provider<KotlinCompilerPluginData> {
val configurationName = compilationData.pluginClasspathConfigurationName()
val pluginClasspathConfiguration =
configurations.maybeCreate(configurationName).apply {
isCanBeConsumed = false
isCanBeResolved = true
isVisible = false
}
val pluginOptions = CompilerPluginOptions()
return newProperty {
for (pluginData in pluginDataList()) {
dependencies.add(pluginClasspathConfiguration.name, pluginData.artifact.toGradleCoordinates(project))
pluginData.options.forEach { (key, value) ->
pluginOptions.addPluginArgument(pluginData.pluginId, SubpluginOption(key, value))
}
}
KotlinCompilerPluginData(
classpath = pluginClasspathConfiguration,
options = pluginOptions
)
}.apply { finalizeValueOnRead() }
}
private fun PluginData.ArtifactCoordinates.toGradleCoordinates(project: Project): String =
"$group:$artifact:${version ?: project.getKotlinPluginVersion()}"
@@ -9,13 +9,11 @@ import org.gradle.api.Project
import org.gradle.api.tasks.TaskProvider
import org.jetbrains.kotlin.gradle.plugin.Kotlin2JvmSourceSetProcessor
import org.jetbrains.kotlin.gradle.plugin.KotlinNativeTargetConfigurator
import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
import org.jetbrains.kotlin.gradle.plugin.mpp.addCommonSourcesToKotlinCompileTask
import org.jetbrains.kotlin.gradle.plugin.mpp.addSourcesToKotlinCompileTask
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider
import org.jetbrains.kotlin.gradle.tasks.withType
open class KotlinCompilationTaskConfigurator(
protected val project: Project
@@ -29,34 +27,37 @@ open class KotlinCompilationTaskConfigurator(
fragmentSourcesProvider.getCommonSourcesFromRefinesClosure(fragment)
fun createKotlinJvmCompilationTask(
fragment: KotlinGradleFragment,
variant: KotlinGradleVariant,
compilationData: KotlinCompilationData<*>
): TaskProvider<out KotlinCompile> {
Kotlin2JvmSourceSetProcessor(KotlinTasksProvider(), compilationData).run()
val allSources = getSourcesForFragmentCompilation(fragment)
val commonSources = getCommonSourcesForFragmentCompilation(fragment)
val allSources = getSourcesForFragmentCompilation(variant)
val commonSources = getCommonSourcesForFragmentCompilation(variant)
// FIXME support custom source file extensions in the two calls below
addSourcesToKotlinCompileTask(project, compilationData.compileKotlinTaskName, emptyList()) { allSources }
addCommonSourcesToKotlinCompileTask(project, compilationData.compileKotlinTaskName, emptyList()) { commonSources }
return project.tasks.withType<KotlinCompile>().named(compilationData.compileKotlinTaskName)
return project.tasks.named(compilationData.compileKotlinTaskName, KotlinCompile::class.java) {
it.kotlinPluginData = project.compilerPluginProviderForPlatformCompilation(variant, compilationData)
}
}
fun createKotlinNativeCompilationTask(
fragment: KotlinGradleFragment,
variant: KotlinGradleVariant,
compilationData: KotlinNativeCompilationData<*>
): TaskProvider<KotlinNativeCompile> {
val compileTask = KotlinNativeTargetConfigurator.createKlibCompilationTask(compilationData)
val allSources = getSourcesForFragmentCompilation(fragment)
val commonSources = getCommonSourcesForFragmentCompilation(fragment)
val allSources = getSourcesForFragmentCompilation(variant)
val commonSources = getCommonSourcesForFragmentCompilation(variant)
compileTask.configure {
it.source(allSources)
it.commonSources.from(commonSources)
}
it.kotlinPluginData = project.compilerPluginProviderForPlatformCompilation(variant, compilationData)
}
return compileTask
}
}
@@ -9,9 +9,7 @@ import org.gradle.api.ExtensiblePolymorphicDomainObjectContainer
import org.gradle.api.NamedDomainObjectSet
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import org.jetbrains.kotlin.project.model.KotlinModuleFragment
import org.jetbrains.kotlin.project.model.KotlinModuleIdentifier
import org.jetbrains.kotlin.project.model.LocalModuleIdentifier
import org.jetbrains.kotlin.project.model.*
import javax.inject.Inject
open class KotlinGradleModuleInternal(
@@ -36,6 +34,15 @@ open class KotlinGradleModuleInternal(
fragments.withType(KotlinGradleVariant::class.java)
}
override val plugins: Set<KpmCompilerPlugin> by lazy {
mutableSetOf<KpmCompilerPlugin>().also { set ->
project
.plugins
.withType(GradleKpmCompilerPlugin::class.java)
.mapTo(set, GradleKpmCompilerPlugin::kpmCompilerPlugin)
}
}
override var isPublic: Boolean = false
protected set
@@ -8,8 +8,6 @@ package org.jetbrains.kotlin.gradle.plugin.mpp.pm20
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.attributes.Usage
import org.gradle.api.file.FileCollection
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskProvider
import org.gradle.jvm.tasks.Jar
import org.jetbrains.kotlin.gradle.dsl.pm20Extension
@@ -17,9 +15,9 @@ import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.KotlinCommonSourceSetProcessor
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.ComputedCapability
import org.jetbrains.kotlin.gradle.targets.metadata.NativeSharedCompilationProcessor
import org.jetbrains.kotlin.gradle.targets.metadata.createGenerateProjectStructureMetadataTask
import org.jetbrains.kotlin.gradle.targets.metadata.filesWithUnpackedArchives
import org.jetbrains.kotlin.gradle.tasks.*
import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider
import org.jetbrains.kotlin.gradle.tasks.registerTask
import org.jetbrains.kotlin.gradle.tasks.withType
@@ -28,7 +26,6 @@ import org.jetbrains.kotlin.gradle.utils.dashSeparatedName
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import org.jetbrains.kotlin.library.KLIB_FILE_EXTENSION
import org.jetbrains.kotlin.project.model.KotlinModuleFragment
import java.io.File
import java.util.concurrent.Callable
internal fun configureMetadataResolutionAndBuild(module: KotlinGradleModule) {
@@ -204,7 +201,7 @@ private fun createNativeMetadataCompilation(
metadataCompilationRegistry,
lazy { resolvedMetadataProviders(fragment) }
)
MetadataCompilationTasksConfigurator(project).createKotlinNativeCompilationTask(fragment, metadataCompilationData)
MetadataCompilationTasksConfigurator(project).createKotlinNativeMetadataCompilationTask(fragment, metadataCompilationData)
metadataCompilationRegistry.registerNative(fragment, metadataCompilationData)
}
@@ -222,6 +219,29 @@ private class MetadataCompilationTasksConfigurator(project: Project) : KotlinCom
addSourcesToKotlinCompileTask(project, compilationData.compileKotlinTaskName, emptyList()) { allSources }
addCommonSourcesToKotlinCompileTask(project, compilationData.compileKotlinTaskName, emptyList()) { commonSources }
project.tasks.named(compilationData.compileKotlinTaskName, AbstractKotlinCompile::class.java).configure {
it.kotlinPluginData = project.compilerPluginProviderForMetadata(fragment, compilationData)
}
}
fun createKotlinNativeMetadataCompilationTask(
fragment: KotlinGradleFragment,
compilationData: KotlinNativeFragmentMetadataCompilationData
): TaskProvider<KotlinNativeCompile> {
val compileTask = KotlinNativeTargetConfigurator.createKlibCompilationTask(compilationData)
val allSources = getSourcesForFragmentCompilation(fragment)
val commonSources = getCommonSourcesForFragmentCompilation(fragment)
compileTask.configure {
it.source(allSources)
it.commonSources.from(commonSources)
it.kotlinPluginData = project.compilerPluginProviderForNativeMetadata(fragment, compilationData)
}
return compileTask
}
override fun getSourcesForFragmentCompilation(fragment: KotlinGradleFragment): MultipleSourceRootsProvider {
@@ -23,7 +23,6 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinCommonToolOptions
import org.jetbrains.kotlin.gradle.dsl.KotlinCompile
import org.jetbrains.kotlin.gradle.dsl.NativeCacheKind
import org.jetbrains.kotlin.gradle.internal.ensureParentDirsCreated
import org.jetbrains.kotlin.gradle.plugin.LanguageSettingsBuilder
import org.jetbrains.kotlin.gradle.plugin.cocoapods.asValidFrameworkName
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinNativeCompilationData
@@ -227,6 +226,13 @@ abstract class AbstractKotlinNativeCompile<T : KotlinCommonToolOptions, K : Kotl
@Classpath
var compilerPluginClasspath: FileCollection? = null
/**
* Plugin Data provided by [KpmCompilerPlugin]
*/
@get:Optional
@get:Nested
var kotlinPluginData: Provider<KotlinCompilerPluginData>? = null
// Used by IDE via reflection.
@get:Internal
val serializedCompilerArguments: List<String>
@@ -249,17 +255,20 @@ abstract class AbstractKotlinNativeCompile<T : KotlinCommonToolOptions, K : Kotl
add("-no-endorsed-libs")
}
// Compiler plugins.
compilerPluginClasspath?.let { pluginClasspath ->
pluginClasspath.map { it.canonicalPath }.sorted().forEach { path ->
fun addPluginOptions(classpath: FileCollection, options: CompilerPluginOptions) {
classpath.map { it.canonicalPath }.sorted().forEach { path ->
add("-Xplugin=$path")
}
compilerPluginOptions.arguments.forEach {
options.arguments.forEach {
add("-P")
add(it)
}
}
// Compiler plugins.
compilerPluginClasspath?.let { addPluginOptions(it, compilerPluginOptions) }
kotlinPluginData?.orNull?.let { addPluginOptions(it.classpath, it.options) }
// kotlin options
addKey("-Werror", kotlinOptions.allWarningsAsErrors)
addKey("-nowarn", kotlinOptions.suppressWarnings)
@@ -32,4 +32,13 @@ class CompilerPluginOptions {
fun addPluginArgument(pluginId: String, option: SubpluginOption) {
subpluginOptionsByPluginId.getOrPut(pluginId) { mutableListOf() }.add(option)
}
operator fun plus(options: CompilerPluginOptions?): CompilerPluginOptions {
if (options == null) return this
val newOptions = CompilerPluginOptions()
newOptions.subpluginOptionsByPluginId += subpluginOptionsByPluginId
newOptions.subpluginOptionsByPluginId += options.subpluginOptionsByPluginId
return newOptions
}
}
@@ -210,6 +210,15 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments> : AbstractKotl
protected val multiModuleICSettings: MultiModuleICSettings
get() = MultiModuleICSettings(buildHistoryFile.get().asFile, useModuleDetection.get())
/*
@get:InputFiles
@get:Classpath
open val pluginClasspath: FileCollection by project.provider {
(taskData.compilation as? KotlinCompilation<*>?)?.pluginConfigurationName?.let(project.configurations::getByName)
?: project.files()
}
*/
@get:InputFiles
@get:Classpath
open val pluginClasspath: ConfigurableFileCollection = objects.fileCollection()
@@ -220,6 +229,13 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments> : AbstractKotl
@get:Input
val sourceFilesExtensions: ListProperty<String> = objects.listProperty(String::class.java).value(DEFAULT_KOTLIN_SOURCE_FILES_EXTENSIONS)
/**
* Plugin Data provided by [KpmCompilerPlugin]
*/
@get:Optional
@get:Nested
internal var kotlinPluginData: Provider<KotlinCompilerPluginData>? = null
// Input is needed to force rebuild even if source files are not changed
@get:Input
internal val coroutines: Property<Coroutines> = objects.property(Coroutines::class.java)
@@ -369,8 +385,9 @@ open class KotlinCompileArgumentsProvider<T : AbstractKotlinCompile<out CommonCo
val coroutines: Provider<Coroutines> = taskProvider.coroutines
val logger: Logger = taskProvider.logger
val isMultiplatform: Boolean = taskProvider.multiPlatformEnabled.get()
val pluginClasspath: FileCollection = taskProvider.pluginClasspath
val pluginOptions: CompilerPluginOptions = taskProvider.pluginOptions
val pluginData = taskProvider.kotlinPluginData?.orNull
val pluginClasspath: FileCollection = taskProvider.pluginClasspath // TODO: Add pluginData
val pluginOptions: CompilerPluginOptions = taskProvider.pluginOptions // TODO: Add pluginData
}
class KotlinJvmCompilerArgumentsProvider
@@ -767,3 +784,12 @@ abstract class Kotlin2JsCompile @Inject constructor(
compilerRunner.runJsCompilerAsync(sourceRoots.kotlinSourceFiles, commonSourceSet.toList(), args, environment)
}
}
data class KotlinCompilerPluginData(
@get:InputFiles
@get:Classpath
val classpath: FileCollection,
@get:Internal
val options: CompilerPluginOptions
)
@@ -5,6 +5,10 @@
package org.jetbrains.kotlin.project.model
// TODO: Add better Kotlin Variant Attributes management
// Ideally we should respect the nature of Attributes and express values in form of Complete Lattice
// Moreover we can add support for default and most used Value Types (eg. Enums)
open class KotlinAttributeKey(
val uniqueName: String
) {
@@ -15,6 +19,7 @@ open class KotlinAttributeKey(
uniqueName.hashCode()
}
// TODO: Introduce ENUM
object KotlinPlatformTypeAttribute : KotlinAttributeKey("org.jetbrains.kotlin.platform.type") {
const val JVM = "jvm"
const val ANDROID_JVM = "androidJvm"
@@ -42,6 +42,8 @@ interface KotlinModule {
val variants: Iterable<KotlinModuleVariant>
get() = fragments.filterIsInstance<KotlinModuleVariant>()
val plugins: Iterable<KpmCompilerPlugin>
// TODO: isSynthetic?
}
@@ -50,5 +52,7 @@ open class BasicKotlinModule(
) : KotlinModule {
override val fragments = mutableListOf<BasicKotlinModuleFragment>()
override val plugins = mutableListOf<KpmCompilerPlugin>()
override fun toString(): String = "module $moduleIdentifier"
}
@@ -45,6 +45,7 @@ val KotlinModuleFragment.refinesClosure: Set<KotlinModuleFragment>
visit(this@refinesClosure)
}
val KotlinModuleVariant.platform get() = variantAttributes[KotlinPlatformTypeAttribute]
open class BasicKotlinModuleFragment(
override val containingModule: KotlinModule,
@@ -0,0 +1,102 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.project.model
/**
* Adapts Kotlin Compiler Plugin for Multiplatform Kotlin Project Model
* Build System uses this interface to identify applicable plugin artifacts and its options
* before executing actual Kotlin Compilation
*/
interface KpmCompilerPlugin {
/**
* Returns [PluginData] when applicable for [fragment] compilation
* Returns [null] if not applicable
*/
fun forMetadataCompilation(fragment: KotlinModuleFragment): PluginData?
/**
* Returns [PluginData] when applicable for [fragment] compilation
* Returns [null] if not applicable
*/
fun forNativeMetadataCompilation(fragment: KotlinModuleFragment): PluginData?
/**
* Returns [PluginData] when applicable for [variant] compilation
* Returns [null] if not applicable
*/
fun forPlatformCompilation(variant: KotlinModuleVariant): PluginData?
}
/**
* Plugin data can be used for changing some compilation request
*/
data class PluginData(
val pluginId: String,
val artifact: ArtifactCoordinates,
val options: List<PluginOption>
) {
// FIXME: (?) Is it common thing or gradle/maven centric?
data class ArtifactCoordinates(
val group: String,
val artifact: String,
val version: String? = null
)
data class PluginOption(
val key: String,
val value: String
)
}
// TODO: It should be part of "Compilation Process": KotlinModule.compilationRequestFor(METADATA | PLATFORM) -> CompilationRequest
// But there is no such thing at the moment :)
fun KotlinModuleFragment.metadataCompilationPluginData(): List<PluginData> =
containingModule
.plugins
.mapNotNull { plugin -> plugin.forMetadataCompilation(this) }
fun KotlinModuleFragment.nativeMetadataCompilationPluginData(): List<PluginData> =
containingModule
.plugins
.mapNotNull { plugin -> plugin.forNativeMetadataCompilation(this) }
fun KotlinModuleVariant.platformCompilationPluginData(): List<PluginData> =
containingModule
.plugins
.mapNotNull { plugin -> plugin.forPlatformCompilation(this) }
/**
* Represents trivial Compiler Plugin adapter for Kotlin Project Model
* Where Compiler Plugin can have common and native artifacts
*/
abstract class BasicKpmCompilerPlugin : KpmCompilerPlugin {
abstract val pluginId: String
protected abstract fun commonPluginArtifact(): PluginData.ArtifactCoordinates?
protected abstract fun nativePluginArtifact(): PluginData.ArtifactCoordinates?
protected abstract val pluginOptions: List<PluginData.PluginOption>
override fun forMetadataCompilation(fragment: KotlinModuleFragment) = pluginDataOrNull(commonPluginArtifact())
override fun forNativeMetadataCompilation(fragment: KotlinModuleFragment) = pluginDataOrNull(nativePluginArtifact())
override fun forPlatformCompilation(variant: KotlinModuleVariant) =
when (variant.platform) {
KotlinPlatformTypeAttribute.NATIVE -> nativePluginArtifact()
else -> commonPluginArtifact()
}.let(::pluginDataOrNull)
private fun pluginDataOrNull(artifact: PluginData.ArtifactCoordinates?) =
if (artifact != null) PluginData(pluginId, artifact, pluginOptions)
else null
}