Implement script filename extensions discovery via artifact transforms
to avoid using never-up-to-date task and make discovery cacheable and checkable for up-to-dateness #KT-28469 fixed
This commit is contained in:
+2
@@ -41,4 +41,6 @@ interface KotlinSourceSet : Named, HasKotlinDependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val customSourceFilesExtensions: Iterable<String> // lazy iterable expected
|
val customSourceFilesExtensions: Iterable<String> // lazy iterable expected
|
||||||
|
|
||||||
|
fun addCustomSourceFilesExtensions(extensions: List<String>) {}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -167,7 +167,7 @@ internal class Kotlin2JvmSourceSetProcessor(
|
|||||||
Kapt3KotlinGradleSubplugin.createAptConfigurationIfNeeded(project, kotlinCompilation.compilationName)
|
Kapt3KotlinGradleSubplugin.createAptConfigurationIfNeeded(project, kotlinCompilation.compilationName)
|
||||||
}
|
}
|
||||||
|
|
||||||
ScriptingGradleSubplugin.createDiscoveryConfigurationIfNeeded(project, kotlinCompilation.compilationName)
|
ScriptingGradleSubplugin.configureForSourceSet(project, kotlinCompilation.compilationName)
|
||||||
|
|
||||||
project.afterEvaluate { project ->
|
project.afterEvaluate { project ->
|
||||||
val javaTask = javaSourceSet?.let { project.tasks.findByName(it.compileJavaTaskName) as JavaCompile }
|
val javaTask = javaSourceSet?.let { project.tasks.findByName(it.compileJavaTaskName) as JavaCompile }
|
||||||
|
|||||||
+14
-6
@@ -95,15 +95,23 @@ class DefaultKotlinSourceSet(
|
|||||||
|
|
||||||
override fun toString(): String = "source set $name"
|
override fun toString(): String = "source set $name"
|
||||||
|
|
||||||
|
private val explicitlyAddedCustomSourceFilesExtensions = ArrayList<String>()
|
||||||
|
|
||||||
override val customSourceFilesExtensions: Iterable<String>
|
override val customSourceFilesExtensions: Iterable<String>
|
||||||
get() = Iterable {
|
get() = Iterable {
|
||||||
kotlin.filter.includes.mapNotNull { pattern ->
|
val fromExplicitFilters = kotlin.filter.includes.mapNotNull { pattern ->
|
||||||
pattern.substringAfterLast('.').takeUnless { extension ->
|
pattern.substringAfterLast('.')
|
||||||
DEFAULT_KOTLIN_SOURCE_FILES_EXTENSIONS.any { extension.equals(it, ignoreCase = true) }
|
}
|
||||||
|| extension.any { it == '\\' || it == '/' }
|
val merged = (fromExplicitFilters + explicitlyAddedCustomSourceFilesExtensions).filterNot { extension ->
|
||||||
}
|
DEFAULT_KOTLIN_SOURCE_FILES_EXTENSIONS.any { extension.equals(it, ignoreCase = true) }
|
||||||
}.iterator()
|
|| extension.any { it == '\\' || it == '/' }
|
||||||
|
}.distinct()
|
||||||
|
merged.iterator()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun addCustomSourceFilesExtensions(extensions: List<String>) {
|
||||||
|
explicitlyAddedCustomSourceFilesExtensions.addAll(extensions)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun KotlinSourceSet.checkForCircularDependencies(): Unit {
|
private fun KotlinSourceSet.checkForCircularDependencies(): Unit {
|
||||||
|
|||||||
+147
-58
@@ -3,107 +3,196 @@
|
|||||||
* that can be found in the license/LICENSE.txt file.
|
* that can be found in the license/LICENSE.txt file.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:Suppress("UnstableApiUsage")
|
||||||
|
|
||||||
package org.jetbrains.kotlin.gradle.scripting.internal
|
package org.jetbrains.kotlin.gradle.scripting.internal
|
||||||
|
|
||||||
import org.gradle.api.DefaultTask
|
|
||||||
import org.gradle.api.Plugin
|
import org.gradle.api.Plugin
|
||||||
import org.gradle.api.Project
|
import org.gradle.api.Project
|
||||||
import org.gradle.api.artifacts.Configuration
|
import org.gradle.api.artifacts.Configuration
|
||||||
|
import org.gradle.api.artifacts.transform.ArtifactTransform
|
||||||
|
import org.gradle.api.attributes.Attribute
|
||||||
import org.gradle.api.plugins.JavaPluginConvention
|
import org.gradle.api.plugins.JavaPluginConvention
|
||||||
import org.gradle.api.tasks.*
|
|
||||||
import org.gradle.api.tasks.compile.AbstractCompile
|
import org.gradle.api.tasks.compile.AbstractCompile
|
||||||
|
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
|
||||||
|
import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector
|
||||||
import org.jetbrains.kotlin.gradle.internal.KaptGenerateStubsTask
|
import org.jetbrains.kotlin.gradle.internal.KaptGenerateStubsTask
|
||||||
import org.jetbrains.kotlin.gradle.plugin.*
|
import org.jetbrains.kotlin.gradle.plugin.*
|
||||||
import org.jetbrains.kotlin.gradle.scripting.ScriptingExtension
|
import org.jetbrains.kotlin.gradle.scripting.ScriptingExtension
|
||||||
import org.jetbrains.kotlin.gradle.logging.GradlePrintingMessageCollector
|
|
||||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
|
||||||
import org.jetbrains.kotlin.scripting.compiler.plugin.ScriptDefinitionsFromClasspathDiscoverySource
|
import org.jetbrains.kotlin.scripting.compiler.plugin.ScriptDefinitionsFromClasspathDiscoverySource
|
||||||
import kotlin.properties.Delegates
|
import java.io.File
|
||||||
|
|
||||||
|
private const val MISCONFIGURATION_MESSAGE_SUFFIX = "the plugin is probably applied by a mistake"
|
||||||
|
|
||||||
class ScriptingGradleSubplugin : Plugin<Project> {
|
class ScriptingGradleSubplugin : Plugin<Project> {
|
||||||
companion object {
|
companion object {
|
||||||
fun isEnabled(project: Project) = project.plugins.findPlugin(ScriptingGradleSubplugin::class.java) != null
|
fun isEnabled(project: Project) = project.plugins.findPlugin(ScriptingGradleSubplugin::class.java) != null
|
||||||
|
|
||||||
val MAIN_CONFIGURATION_NAME = "kotlinScriptDef"
|
fun configureForSourceSet(project: Project, sourceSetName: String) {
|
||||||
|
|
||||||
fun getConfigurationName(sourceSetName: String): String = when (sourceSetName) {
|
if (!org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast(4, 0)) return
|
||||||
"main" -> MAIN_CONFIGURATION_NAME
|
|
||||||
else -> "$sourceSetName${MAIN_CONFIGURATION_NAME.capitalize()}"
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createDiscoveryConfigurationIfNeeded(project: Project, sourceSetName: String) {
|
val discoveryConfiguration = project.configurations.maybeCreate(getDiscoveryClasspathConfigurationName(sourceSetName)).apply {
|
||||||
project.configurations.maybeCreate(getConfigurationName(sourceSetName))
|
isVisible = false
|
||||||
|
isCanBeConsumed = false
|
||||||
|
description = "Script filename extensions discovery classpath configuration"
|
||||||
|
}
|
||||||
|
|
||||||
|
configureDiscoveryTransformation(project, discoveryConfiguration, getDiscoveryResultsConfigurationName(sourceSetName))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun apply(project: Project) {
|
override fun apply(project: Project) {
|
||||||
|
|
||||||
|
if (!org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast(4, 0)) return
|
||||||
|
|
||||||
project.afterEvaluate {
|
project.afterEvaluate {
|
||||||
|
|
||||||
val javaPluginConvention = project.convention.findPlugin(JavaPluginConvention::class.java)
|
val javaPluginConvention = project.convention.findPlugin(JavaPluginConvention::class.java)
|
||||||
if (javaPluginConvention?.sourceSets?.isEmpty() == false) {
|
if (javaPluginConvention?.sourceSets?.isEmpty() == false) {
|
||||||
|
|
||||||
project.tasks.withType(KotlinCompile::class.java) { task ->
|
project.tasks.withType(KotlinCompile::class.java) { task ->
|
||||||
|
|
||||||
if (task !is KaptGenerateStubsTask) {
|
if (task !is KaptGenerateStubsTask) {
|
||||||
javaPluginConvention.sourceSets.findByName(task.sourceSetName)?.let { sourceSet ->
|
|
||||||
project.tasks.create(
|
val discoveryClasspathConfigurationName = getDiscoveryClasspathConfigurationName(task.sourceSetName)
|
||||||
"discover${task.sourceSetName.capitalize()}ScriptsExtensions",
|
project.configurations.findByName(discoveryClasspathConfigurationName)?.let { _ ->
|
||||||
DiscoverScriptExtensionsTask::class.java
|
configureScriptsExtensions(project, javaPluginConvention, task.sourceSetName)
|
||||||
) { extensionsTask ->
|
|
||||||
extensionsTask.sourceSet = sourceSet
|
|
||||||
extensionsTask.discoveryClasspathConfiguration =
|
|
||||||
project.configurations.findByName(getConfigurationName(task.sourceSetName))
|
|
||||||
extensionsTask.kotlinCompile = task
|
|
||||||
task.dependsOn(extensionsTask)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
?: project.logger.warn("kotlin scripting plugin: $project.${task.name} - configuration not found: $discoveryClasspathConfigurationName, $MISCONFIGURATION_MESSAGE_SUFFIX")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
project.logger.warn("kotlin scripting plugin: applied to a non-JVM project $project")
|
project.logger.warn("kotlin scripting plugin: applied to a non-JVM project $project, $MISCONFIGURATION_MESSAGE_SUFFIX")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun configureScriptsExtensions(
|
||||||
|
project: Project,
|
||||||
|
javaPluginConvention: JavaPluginConvention,
|
||||||
|
sourceSetName: String
|
||||||
|
) {
|
||||||
|
javaPluginConvention.sourceSets.findByName(sourceSetName)?.let { sourceSet ->
|
||||||
|
|
||||||
|
val discoveryResultsConfigurationName = getDiscoveryResultsConfigurationName(sourceSetName)
|
||||||
|
|
||||||
|
val kotlinSourceSet = sourceSet.getConvention(KOTLIN_DSL_NAME) as? KotlinSourceSet
|
||||||
|
if (kotlinSourceSet == null) {
|
||||||
|
project.logger.warn("kotlin scripting plugin: kotlin source set not found: $project.$sourceSet, $MISCONFIGURATION_MESSAGE_SUFFIX")
|
||||||
|
} else {
|
||||||
|
val extensions by lazy {
|
||||||
|
val discoveryResultsConfiguration = project.configurations.findByName(discoveryResultsConfigurationName)
|
||||||
|
if (discoveryResultsConfiguration == null) {
|
||||||
|
project.logger.warn("kotlin scripting plugin: discovery results not found: $project.$discoveryResultsConfigurationName, $MISCONFIGURATION_MESSAGE_SUFFIX")
|
||||||
|
emptySet<String>()
|
||||||
|
} else {
|
||||||
|
discoveryResultsConfiguration.files.flatMapTo(HashSet()) {
|
||||||
|
it.readLines().filter(String::isNotBlank)
|
||||||
|
}.also {
|
||||||
|
kotlinSourceSet.addCustomSourceFilesExtensions(it.toList())
|
||||||
|
project.logger.debug("kotlin scripting plugin: $project.$sourceSet: discovered script extensions: $it")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
kotlinSourceSet.kotlin.filter.include { it.file.extension in extensions }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val MAIN_CONFIGURATION_NAME = "kotlinScriptDef"
|
||||||
|
private const val RESULTS_CONFIGURATION_SUFFIX = "Extensions"
|
||||||
|
|
||||||
|
private fun getDiscoveryClasspathConfigurationName(sourceSetName: String): String = when (sourceSetName) {
|
||||||
|
"main" -> MAIN_CONFIGURATION_NAME
|
||||||
|
else -> "$sourceSetName${MAIN_CONFIGURATION_NAME.capitalize()}"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getDiscoveryResultsConfigurationName(sourceSetName: String): String =
|
||||||
|
getDiscoveryClasspathConfigurationName(sourceSetName) + RESULTS_CONFIGURATION_SUFFIX
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private fun configureDiscoveryTransformation(
|
||||||
|
project: Project,
|
||||||
|
discoveryConfiguration: Configuration,
|
||||||
|
discoveryResultsConfigurationName: String
|
||||||
|
) {
|
||||||
|
project.configurations.maybeCreate(discoveryResultsConfigurationName).apply {
|
||||||
|
isCanBeConsumed = false
|
||||||
|
}
|
||||||
|
project.dependencies.apply {
|
||||||
|
add(
|
||||||
|
discoveryResultsConfigurationName,
|
||||||
|
project.withRegisteredDiscoverScriptExtensionsTransform {
|
||||||
|
discoveryConfiguration.discoverScriptExtensionsFiles()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DiscoverScriptExtensionsTransform : ArtifactTransform() {
|
||||||
|
|
||||||
|
override fun transform(input: File): List<File> {
|
||||||
|
val definitions =
|
||||||
|
ScriptDefinitionsFromClasspathDiscoverySource(
|
||||||
|
listOf(input), emptyMap(),
|
||||||
|
PrintingMessageCollector(System.out, MessageRenderer.WITHOUT_PATHS, false)
|
||||||
|
).definitions
|
||||||
|
val extensions = definitions.mapTo(arrayListOf()) { it.fileExtension }
|
||||||
|
return if (extensions.isNotEmpty()) {
|
||||||
|
val outputFile = outputDirectory.resolve("${input.nameWithoutExtension}.discoveredScriptsExtensions.txt")
|
||||||
|
outputFile.writeText(extensions.joinToString("\n"))
|
||||||
|
listOf(outputFile)
|
||||||
|
} else emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
fun Project.registerDiscoverScriptExtensionsTransform() {
|
||||||
|
dependencies.apply {
|
||||||
|
registerTransform {
|
||||||
|
with(it) {
|
||||||
|
from.attribute(artifactType, "jar")
|
||||||
|
to.attribute(artifactType, scriptFilesExtensions)
|
||||||
|
artifactTransform(DiscoverScriptExtensionsTransform::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
registerTransform {
|
||||||
|
with(it) {
|
||||||
|
from.attribute(artifactType, "classes")
|
||||||
|
to.attribute(artifactType, scriptFilesExtensions)
|
||||||
|
artifactTransform(DiscoverScriptExtensionsTransform::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open class DiscoverScriptExtensionsTask : DefaultTask() {
|
private
|
||||||
|
fun <T> Project.withRegisteredDiscoverScriptExtensionsTransform(block: () -> T): T {
|
||||||
@get:Internal
|
if (!project.extensions.extraProperties.has("DiscoverScriptExtensionsTransform")) {
|
||||||
internal var sourceSet: SourceSet by Delegates.notNull()
|
registerDiscoverScriptExtensionsTransform()
|
||||||
|
project.extensions.extraProperties["DiscoverScriptExtensionsTransform"] = true
|
||||||
@get:InputFiles
|
|
||||||
@get:Classpath
|
|
||||||
@get:Optional
|
|
||||||
internal var discoveryClasspathConfiguration: Configuration? = null
|
|
||||||
|
|
||||||
@get:Internal
|
|
||||||
internal var kotlinCompile: KotlinCompile by Delegates.notNull()
|
|
||||||
|
|
||||||
@Input
|
|
||||||
override fun getDependsOn(): MutableSet<Any> = kotlinCompile.dependsOn
|
|
||||||
|
|
||||||
@TaskAction
|
|
||||||
@Suppress("unused")
|
|
||||||
fun findKnownScriptExtensions() {
|
|
||||||
val scriptingClasspath = discoveryClasspathConfiguration?.files?.takeIf { it.isNotEmpty() } ?: return
|
|
||||||
|
|
||||||
val definitions =
|
|
||||||
ScriptDefinitionsFromClasspathDiscoverySource(
|
|
||||||
scriptingClasspath.toList(), emptyMap(),
|
|
||||||
GradlePrintingMessageCollector(logger)
|
|
||||||
).definitions
|
|
||||||
val extensions = definitions.mapTo(arrayListOf(), KotlinScriptDefinition::fileExtension)
|
|
||||||
val kotlinSourceSet = sourceSet.getConvention(KOTLIN_DSL_NAME) as? KotlinSourceSet
|
|
||||||
if (kotlinSourceSet == null) {
|
|
||||||
logger.warn("kotlin scripting plugin: kotlin source set not found: $project.$sourceSet")
|
|
||||||
} else if (extensions.isNotEmpty()) {
|
|
||||||
logger.info("kotlin scripting plugin: Add new extensions to the sourceset $project.$sourceSet: $extensions")
|
|
||||||
kotlinSourceSet.kotlin.filter.include(extensions.map { "**/*.$it" })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return block()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val artifactType = Attribute.of("artifactType", String::class.java)
|
||||||
|
|
||||||
|
private val scriptFilesExtensions = "script-files-extensions"
|
||||||
|
|
||||||
|
private
|
||||||
|
fun Configuration.discoverScriptExtensionsFiles() =
|
||||||
|
incoming.artifactView {
|
||||||
|
attributes {
|
||||||
|
it.attribute(artifactType, scriptFilesExtensions)
|
||||||
|
}
|
||||||
|
}.artifacts.artifactFiles
|
||||||
|
|
||||||
|
|
||||||
class ScriptingKotlinGradleSubplugin : KotlinGradleSubplugin<AbstractCompile> {
|
class ScriptingKotlinGradleSubplugin : KotlinGradleSubplugin<AbstractCompile> {
|
||||||
companion object {
|
companion object {
|
||||||
const val SCRIPTING_ARTIFACT_NAME = "kotlin-scripting-compiler-embeddable"
|
const val SCRIPTING_ARTIFACT_NAME = "kotlin-scripting-compiler-embeddable"
|
||||||
|
|||||||
Reference in New Issue
Block a user