[Gradle] Split Tasks.kt into several source files (per class) (2/2)

^KT-57142 Verification Pending
This commit is contained in:
Sebastian Sellmair
2023-03-06 14:56:43 +01:00
committed by Space Team
parent 02ac54fe5f
commit d23a4f46cc
8 changed files with 129 additions and 7323 deletions
@@ -1,226 +1,58 @@
/* /*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. * Copyright 2010-2023 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. * 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.tasks package org.jetbrains.kotlin.gradle.tasks
import groovy.lang.Closure import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.* import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.* import org.gradle.api.file.FileCollection
import org.gradle.api.invocation.Gradle import org.gradle.api.file.FileSystemOperations
import org.gradle.api.logging.Logger
import org.gradle.api.model.ObjectFactory import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider import org.gradle.api.provider.Provider
import org.gradle.api.provider.SetProperty import org.gradle.api.provider.SetProperty
import org.gradle.api.specs.Spec
import org.gradle.api.tasks.* import org.gradle.api.tasks.*
import org.gradle.api.tasks.compile.AbstractCompile
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.api.tasks.util.PatternFilterable
import org.gradle.api.tasks.util.PatternSet
import org.gradle.util.GradleVersion
import org.gradle.work.ChangeType import org.gradle.work.ChangeType
import org.gradle.work.Incremental import org.gradle.work.Incremental
import org.gradle.work.InputChanges import org.gradle.work.InputChanges
import org.gradle.work.NormalizeLineEndings import org.gradle.work.NormalizeLineEndings
import org.gradle.workers.WorkerExecutor import org.gradle.workers.WorkerExecutor
import org.jetbrains.kotlin.build.DEFAULT_KOTLIN_SOURCE_FILES_EXTENSIONS import org.jetbrains.kotlin.build.report.metrics.BuildPerformanceMetric
import org.jetbrains.kotlin.build.report.metrics.* import org.jetbrains.kotlin.build.report.metrics.BuildTime
import org.jetbrains.kotlin.build.report.metrics.measure
import org.jetbrains.kotlin.cli.common.CompilerSystemProperties import org.jetbrains.kotlin.cli.common.CompilerSystemProperties
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
import org.jetbrains.kotlin.cli.common.arguments.CommonToolArguments import org.jetbrains.kotlin.compilerRunner.CompilerExecutionSettings
import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments import org.jetbrains.kotlin.compilerRunner.GradleCompilerRunner
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments import org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers
import org.jetbrains.kotlin.compilerRunner.* import org.jetbrains.kotlin.compilerRunner.UsesCompilerSystemPropertiesService
import org.jetbrains.kotlin.compilerRunner.GradleCompilerRunner.Companion.normalizeForFlagFile
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.daemon.common.MultiModuleICSettings import org.jetbrains.kotlin.daemon.common.MultiModuleICSettings
import org.jetbrains.kotlin.gradle.dsl.* import org.jetbrains.kotlin.gradle.incremental.UsesIncrementalModuleInfoBuildService
import org.jetbrains.kotlin.gradle.incremental.* import org.jetbrains.kotlin.gradle.internal.AbstractKotlinCompileArgumentsContributor
import org.jetbrains.kotlin.gradle.internal.* import org.jetbrains.kotlin.gradle.internal.compilerArgumentsConfigurationFlags
import org.jetbrains.kotlin.gradle.internal.tasks.TaskWithLocalState import org.jetbrains.kotlin.gradle.internal.prepareCompilerArguments
import org.jetbrains.kotlin.gradle.internal.tasks.allOutputFiles import org.jetbrains.kotlin.gradle.internal.tasks.allOutputFiles
import org.jetbrains.kotlin.gradle.logging.GradleErrorMessageCollector
import org.jetbrains.kotlin.gradle.logging.GradleKotlinLogger import org.jetbrains.kotlin.gradle.logging.GradleKotlinLogger
import org.jetbrains.kotlin.gradle.logging.GradlePrintingMessageCollector
import org.jetbrains.kotlin.gradle.logging.kotlinDebug import org.jetbrains.kotlin.gradle.logging.kotlinDebug
import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.UsesVariantImplementationFactories
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService
import org.jetbrains.kotlin.gradle.report.* import org.jetbrains.kotlin.gradle.report.*
import org.jetbrains.kotlin.gradle.targets.js.internal.LibraryFilterCachingService import org.jetbrains.kotlin.gradle.report.UsesBuildMetricsService
import org.jetbrains.kotlin.gradle.targets.js.internal.UsesLibraryFilterCachingService import org.jetbrains.kotlin.gradle.report.UsesBuildReportsService
import org.jetbrains.kotlin.gradle.targets.js.ir.*
import org.jetbrains.kotlin.gradle.tasks.internal.KotlinJsOptionsCompat
import org.jetbrains.kotlin.gradle.tasks.internal.KotlinJvmOptionsCompat
import org.jetbrains.kotlin.gradle.utils.* import org.jetbrains.kotlin.gradle.utils.*
import org.jetbrains.kotlin.incremental.* import org.jetbrains.kotlin.gradle.utils.newInstance
import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotDisabled import org.jetbrains.kotlin.gradle.utils.property
import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotEnabled.* import org.jetbrains.kotlin.gradle.utils.propertyWithConvention
import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotEnabled.IncrementalRun.* import org.jetbrains.kotlin.gradle.utils.propertyWithNewInstance
import org.jetbrains.kotlin.library.impl.isKotlinLibrary import org.jetbrains.kotlin.incremental.ChangedFiles
import org.jetbrains.kotlin.incremental.IncrementalCompilerRunner
import org.jetbrains.kotlin.statistics.metrics.BooleanMetrics import org.jetbrains.kotlin.statistics.metrics.BooleanMetrics
import org.jetbrains.kotlin.utils.JsLibraryUtils
import org.jetbrains.kotlin.utils.addToStdlib.cast
import java.io.File import java.io.File
import java.nio.file.Files.*
import javax.inject.Inject import javax.inject.Inject
import org.jetbrains.kotlin.gradle.tasks.cleanOutputsAndLocalState as cleanOutputsAndLocalStateUtil import org.jetbrains.kotlin.gradle.tasks.cleanOutputsAndLocalState as cleanOutputsAndLocalStateUtil
const val KOTLIN_BUILD_DIR_NAME = "kotlin"
const val USING_JVM_INCREMENTAL_COMPILATION_MESSAGE = "Using Kotlin/JVM incremental compilation"
const val USING_JS_INCREMENTAL_COMPILATION_MESSAGE = "Using Kotlin/JS incremental compilation"
const val USING_JS_IR_BACKEND_MESSAGE = "Using Kotlin/JS IR backend"
abstract class AbstractKotlinCompileTool<T : CommonToolArguments> @Inject constructor(
objectFactory: ObjectFactory
) : DefaultTask(),
KotlinCompileTool,
CompilerArgumentAware<T>,
TaskWithLocalState {
private val patternFilterable = PatternSet()
init {
patternFilterable.include(
DEFAULT_KOTLIN_SOURCE_FILES_EXTENSIONS.flatMap { ext -> ext.fileExtensionCasePermutations().map { "**/*.$it" } }
)
}
private val sourceFiles = objectFactory.fileCollection()
override val sources: FileCollection = objectFactory.fileCollection()
.from(
{ sourceFiles.asFileTree.matching(patternFilterable) }
)
override fun source(vararg sources: Any) {
sourceFiles.from(sources)
}
override fun setSource(vararg sources: Any) {
sourceFiles.from(sources)
}
fun disallowSourceChanges() {
sourceFiles.disallowChanges()
}
@Internal
final override fun getIncludes(): MutableSet<String> = patternFilterable.includes
@Internal
final override fun getExcludes(): MutableSet<String> = patternFilterable.excludes
final override fun setIncludes(includes: Iterable<String>): PatternFilterable = also {
patternFilterable.setIncludes(includes)
}
final override fun setExcludes(excludes: Iterable<String>): PatternFilterable = also {
patternFilterable.setExcludes(excludes)
}
final override fun include(vararg includes: String?): PatternFilterable = also {
patternFilterable.include(*includes)
}
final override fun include(includes: Iterable<String>): PatternFilterable = also {
patternFilterable.include(includes)
}
final override fun include(includeSpec: Spec<FileTreeElement>): PatternFilterable = also {
patternFilterable.include(includeSpec)
}
final override fun include(includeSpec: Closure<*>): PatternFilterable = also {
patternFilterable.include(includeSpec)
}
final override fun exclude(vararg excludes: String?): PatternFilterable = also {
patternFilterable.exclude(*excludes)
}
final override fun exclude(excludes: Iterable<String>): PatternFilterable = also {
patternFilterable.exclude(excludes)
}
final override fun exclude(excludeSpec: Spec<FileTreeElement>): PatternFilterable = also {
patternFilterable.exclude(excludeSpec)
}
final override fun exclude(excludeSpec: Closure<*>): PatternFilterable = also {
patternFilterable.exclude(excludeSpec)
}
@get:Internal
final override val metrics: Property<BuildMetricsReporter> = project.objects
.property(BuildMetricsReporterImpl())
/**
* By default, should be set by plugin from [COMPILER_CLASSPATH_CONFIGURATION_NAME] configuration.
*
* Empty classpath will fail the build.
*/
@get:Classpath
internal val defaultCompilerClasspath: ConfigurableFileCollection =
project.objects.fileCollection()
protected fun validateCompilerClasspath() {
// Note that the check triggers configuration resolution
require(!defaultCompilerClasspath.isEmpty) {
"Default Kotlin compiler classpath is empty! Task: $path (${this::class.qualifiedName})"
}
}
}
abstract class GradleCompileTaskProvider @Inject constructor(
objectFactory: ObjectFactory,
projectLayout: ProjectLayout,
gradle: Gradle,
task: Task,
project: Project,
incrementalModuleInfoProvider: Provider<IncrementalModuleInfoProvider>
) {
@get:Internal
val path: Provider<String> = objectFactory.property(task.path)
@get:Internal
val logger: Provider<Logger> = objectFactory.property(task.logger)
@get:Internal
val buildDir: DirectoryProperty = projectLayout.buildDirectory
@get:Internal
val projectDir: Provider<File> = objectFactory
.property(project.rootProject.projectDir)
@get:Internal
val projectCacheDir: Provider<File> = objectFactory
.property(gradle.projectCacheDir)
@get:Internal
val sessionsDir: Provider<File> = objectFactory
.property(GradleCompilerRunner.sessionsDir(gradle.projectCacheDir))
@get:Internal
val projectName: Provider<String> = objectFactory
.property(project.rootProject.name.normalizeForFlagFile())
@get:Internal
val buildModulesInfo: Provider<out IncrementalModuleInfoProvider> = objectFactory
.property(incrementalModuleInfoProvider)
@get:Internal
val errorsFile: Provider<File?> = objectFactory
.property(
gradle.rootProject.rootDir.resolve(".gradle/kotlin/errors/").also { it.mkdirs() }
.resolve("errors-${System.currentTimeMillis()}.log"))
}
abstract class AbstractKotlinCompile<T : CommonCompilerArguments> @Inject constructor( abstract class AbstractKotlinCompile<T : CommonCompilerArguments> @Inject constructor(
objectFactory: ObjectFactory, objectFactory: ObjectFactory,
workerExecutor: WorkerExecutor workerExecutor: WorkerExecutor
@@ -497,733 +329,3 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments> @Inject constr
} }
} }
} }
open class KotlinCompileArgumentsProvider<T : AbstractKotlinCompile<out CommonCompilerArguments>>(taskProvider: T) {
val logger: Logger = taskProvider.logger
val isMultiplatform: Boolean = taskProvider.multiPlatformEnabled.get()
private val pluginData = taskProvider.kotlinPluginData?.orNull
val pluginClasspath: FileCollection = listOfNotNull(taskProvider.pluginClasspath, pluginData?.classpath).reduce(FileCollection::plus)
val pluginOptions: CompilerPluginOptions = taskProvider.pluginOptions.toSingleCompilerPluginOptions() + pluginData?.options
}
class KotlinJvmCompilerArgumentsProvider
(taskProvider: KotlinCompile) : KotlinCompileArgumentsProvider<KotlinCompile>(taskProvider) {
val taskName: String = taskProvider.name
val moduleName: String = taskProvider.moduleName.get()
val friendPaths: FileCollection = taskProvider.friendPaths
val compileClasspath: Iterable<File> = taskProvider.libraries
val destinationDir: File = taskProvider.destinationDirectory.get().asFile
internal val compilerOptions: KotlinJvmCompilerOptions = taskProvider.compilerOptions
}
internal inline val <reified T : Task> T.thisTaskProvider: TaskProvider<out T>
get() = checkNotNull(project.locateTask<T>(name))
@CacheableTask
abstract class KotlinCompile @Inject constructor(
final override val compilerOptions: KotlinJvmCompilerOptions,
workerExecutor: WorkerExecutor,
objectFactory: ObjectFactory
) : AbstractKotlinCompile<K2JVMCompilerArguments>(objectFactory, workerExecutor),
K2MultiplatformCompilationTask,
@Suppress("TYPEALIAS_EXPANSION_DEPRECATION") KotlinJvmCompileDsl,
KotlinCompilationTask<KotlinJvmCompilerOptions>,
UsesKotlinJavaToolchain {
init {
compilerOptions.moduleName.convention(moduleName)
compilerOptions.verbose.convention(logger.isDebugEnabled)
}
final override val kotlinOptions: KotlinJvmOptions = KotlinJvmOptionsCompat(
{ this },
compilerOptions
)
@Suppress("DEPRECATION")
@Deprecated("Configure compilerOptions directly", replaceWith = ReplaceWith("compilerOptions"))
override val parentKotlinOptions: Property<KotlinJvmOptions> = objectFactory
.property(kotlinOptions)
.chainedDisallowChanges()
@get:Nested
override val multiplatformStructure: K2MultiplatformStructure = objectFactory.newInstance()
/** A package prefix that is used for locating Java sources in a directory structure with non-full-depth packages.
*
* Example: a Java source file with `package com.example.my.package` is located in directory `src/main/java/my/package`.
* Then, for the Kotlin compilation to locate the source file, use package prefix `"com.example"` */
@get:Input
@get:Optional
var javaPackagePrefix: String? = null
@get:Input
var usePreciseJavaTracking: Boolean = true
set(value) {
field = value
logger.kotlinDebug { "Set $this.usePreciseJavaTracking=$value" }
}
@get:Internal // To support compile avoidance (ClasspathSnapshotProperties.classpathSnapshot will be used as input instead)
abstract override val libraries: ConfigurableFileCollection
@Deprecated(
"Replaced with 'libraries' input",
replaceWith = ReplaceWith("libraries"),
level = DeprecationLevel.ERROR
)
@get:Internal
var classpath: FileCollection
set(value) = libraries.setFrom(value)
get() = libraries
@get:Input
abstract val useKotlinAbiSnapshot: Property<Boolean>
@get:Nested
abstract val classpathSnapshotProperties: ClasspathSnapshotProperties
/** Properties related to the `kotlin.incremental.useClasspathSnapshot` feature. */
abstract class ClasspathSnapshotProperties {
@get:Input
abstract val useClasspathSnapshot: Property<Boolean>
@get:Classpath
@get:Incremental
@get:Optional // Set if useClasspathSnapshot == true
abstract val classpathSnapshot: ConfigurableFileCollection
@get:Classpath
@get:Incremental
@get:Optional // Set if useClasspathSnapshot == false (to restore the existing classpath annotations when the feature is disabled)
abstract val classpath: ConfigurableFileCollection
@get:OutputDirectory
@get:Optional // Set if useClasspathSnapshot == true
abstract val classpathSnapshotDir: DirectoryProperty
}
override val incrementalProps: List<FileCollection>
get() = listOf(
sources,
javaSources,
scriptSources,
androidLayoutResources,
commonSourceSet,
classpathSnapshotProperties.classpath,
classpathSnapshotProperties.classpathSnapshot
)
@get:Internal
final override val defaultKotlinJavaToolchain: Provider<DefaultKotlinJavaToolchain> = objectFactory
.propertyWithNewInstance({ this })
final override val kotlinJavaToolchainProvider: Provider<KotlinJavaToolchain> = defaultKotlinJavaToolchain.cast()
@get:Internal
internal abstract val associatedJavaCompileTaskTargetCompatibility: Property<String>
@get:Internal
internal abstract val associatedJavaCompileTaskName: Property<String>
@get:Input
internal abstract val jvmTargetValidationMode: Property<PropertiesProvider.JvmTargetValidationMode>
@get:Internal
internal val scriptDefinitions: ConfigurableFileCollection = objectFactory.fileCollection()
@get:Input
@get:Optional
internal val scriptExtensions: SetProperty<String> = objectFactory.setPropertyWithLazyValue {
scriptDefinitions
.map { definitionFile ->
definitionFile.readLines().filter(String::isNotBlank)
}
.flatten()
}
private class ScriptFilterSpec(
private val scriptExtensions: SetProperty<String>
) : Spec<FileTreeElement> {
override fun isSatisfiedBy(element: FileTreeElement): Boolean {
val extensions = scriptExtensions.get()
return extensions.isNotEmpty() &&
(element.isDirectory || extensions.contains(element.file.extension))
}
}
private val scriptSourceFiles = objectFactory.fileCollection()
@get:InputFiles
@get:SkipWhenEmpty
@get:IgnoreEmptyDirectories
@get:NormalizeLineEndings
@get:PathSensitive(PathSensitivity.RELATIVE)
internal open val scriptSources: FileCollection = scriptSourceFiles
.asFileTree
.matching { patternFilterable ->
patternFilterable.include(ScriptFilterSpec(scriptExtensions))
}
init {
incremental = true
}
override fun skipCondition(): Boolean = sources.isEmpty && scriptSources.isEmpty
override fun createCompilerArgs(): K2JVMCompilerArguments =
K2JVMCompilerArguments()
/**
* Workaround for those "nasty" plugins that are adding 'freeCompilerArgs' on task execution phase.
* With properties api it is not possible to update property value after task configuration is finished.
*
* Marking it as `@Internal` as anyway on the configuration phase, when Gradle does task inputs snapshot,
* this input will always be empty.
*/
@get:Internal
internal var executionTimeFreeCompilerArgs: List<String>? = null
override fun setupCompilerArgs(args: K2JVMCompilerArguments, defaultsOnly: Boolean, ignoreClasspathResolutionErrors: Boolean) {
compilerArgumentsContributor.contributeArguments(
args, compilerArgumentsConfigurationFlags(
defaultsOnly,
ignoreClasspathResolutionErrors
)
)
args.configureMultiplatform(
compilerOptions,
k1CommonSources = commonSourceSet.asFileTree,
k2MultiplatformFragments = multiplatformStructure
)
if (reportingSettings().buildReportMode == BuildReportMode.VERBOSE) {
args.reportPerf = true
}
val localExecutionTimeFreeCompilerArgs = executionTimeFreeCompilerArgs
if (localExecutionTimeFreeCompilerArgs != null) {
args.freeArgs = localExecutionTimeFreeCompilerArgs
}
}
@get:Internal
internal val compilerArgumentsContributor: CompilerArgumentsContributor<K2JVMCompilerArguments> by lazy {
KotlinJvmCompilerArgumentsContributor(KotlinJvmCompilerArgumentsProvider(this))
}
override fun callCompilerAsync(
args: K2JVMCompilerArguments,
kotlinSources: Set<File>,
inputChanges: InputChanges,
taskOutputsBackup: TaskOutputsBackup?
) {
validateKotlinAndJavaHasSameTargetCompatibility(args)
val scriptSources = scriptSources.asFileTree.files
val javaSources = javaSources.files
val gradlePrintingMessageCollector = GradlePrintingMessageCollector(logger, args.allWarningsAsErrors)
val gradleMessageCollector = GradleErrorMessageCollector(gradlePrintingMessageCollector, kotlinPluginVersion = getKotlinPluginVersion(logger))
val outputItemCollector = OutputItemsCollectorImpl()
val compilerRunner = compilerRunner.get()
val icEnv = if (isIncrementalCompilationEnabled()) {
logger.info(USING_JVM_INCREMENTAL_COMPILATION_MESSAGE)
IncrementalCompilationEnvironment(
changedFiles = getChangedFiles(inputChanges, incrementalProps),
classpathChanges = getClasspathChanges(inputChanges),
workingDir = taskBuildCacheableOutputDirectory.get().asFile,
usePreciseJavaTracking = usePreciseJavaTracking,
disableMultiModuleIC = disableMultiModuleIC,
multiModuleICSettings = multiModuleICSettings,
withAbiSnapshot = useKotlinAbiSnapshot.get(),
preciseCompilationResultsBackup = preciseCompilationResultsBackup.get(),
)
} else null
@Suppress("ConvertArgumentToSet")
val environment = GradleCompilerEnvironment(
defaultCompilerClasspath, gradleMessageCollector, outputItemCollector,
// In the incremental compiler, outputFiles will be cleaned on rebuild. However, because classpathSnapshotDir is not included in
// TaskOutputsBackup, we don't want classpathSnapshotDir to be cleaned immediately on rebuild, and therefore we exclude it from
// outputFiles here. (See TaskOutputsBackup's kdoc for more info.)
outputFiles = allOutputFiles()
- (classpathSnapshotProperties.classpathSnapshotDir.orNull?.asFile?.let { setOf(it) } ?: emptySet()),
reportingSettings = reportingSettings(),
incrementalCompilationEnvironment = icEnv,
kotlinScriptExtensions = scriptExtensions.get().toTypedArray()
)
logger.info("Kotlin source files: ${kotlinSources.joinToString()}")
logger.info("Java source files: ${javaSources.joinToString()}")
logger.info("Script source files: ${scriptSources.joinToString()}")
logger.info("Script file extensions: ${scriptExtensions.get().joinToString()}")
compilerRunner.runJvmCompilerAsync(
(kotlinSources + scriptSources + javaSources).toList(),
javaPackagePrefix,
args,
environment,
defaultKotlinJavaToolchain.get().buildJvm.get().javaHome,
taskOutputsBackup
)
compilerRunner.errorsFile?.also { gradleMessageCollector.flush(it) }
}
private fun validateKotlinAndJavaHasSameTargetCompatibility(
args: K2JVMCompilerArguments,
) {
associatedJavaCompileTaskTargetCompatibility.orNull?.let { targetCompatibility ->
val normalizedJavaTarget = when (targetCompatibility) {
"6" -> "1.6"
"7" -> "1.7"
"8" -> "1.8"
"1.9" -> "9"
else -> targetCompatibility
}
val jvmTarget = args.jvmTarget ?: JvmTarget.DEFAULT.toString()
if (normalizedJavaTarget != jvmTarget) {
val javaTaskName = associatedJavaCompileTaskName.get()
val errorMessage = buildString {
append("'$javaTaskName' task (current target is $targetCompatibility) and ")
append("'$name' task (current target is $jvmTarget) ")
appendLine("jvm target compatibility should be set to the same Java version.")
if (GradleVersion.current().baseVersion < GradleVersion.version("8.0")) {
append("By default will become an error since Gradle 8.0+! ")
appendLine("Read more: https://kotl.in/gradle/jvm/target-validation")
}
appendLine("Consider using JVM toolchain: https://kotl.in/gradle/jvm/toolchain")
}
when (jvmTargetValidationMode.get()) {
PropertiesProvider.JvmTargetValidationMode.ERROR -> throw GradleException(errorMessage)
PropertiesProvider.JvmTargetValidationMode.WARNING -> logger.warn(errorMessage)
else -> Unit
}
}
}
}
@get:Input
val disableMultiModuleIC: Boolean by lazy {
if (!isIncrementalCompilationEnabled() || !javaOutputDir.isPresent) {
false
} else {
var illegalTaskOrNull: AbstractCompile? = null
project.tasks.configureEach {
if (it is AbstractCompile &&
it !is JavaCompile &&
it !is AbstractKotlinCompile<*> &&
javaOutputDir.get().asFile.isParentOf(it.destinationDirectory.get().asFile)
) {
illegalTaskOrNull = illegalTaskOrNull ?: it
}
}
if (illegalTaskOrNull != null) {
val illegalTask = illegalTaskOrNull!!
logger.info(
"Kotlin inter-project IC is disabled: " +
"unknown task '$illegalTask' destination dir ${illegalTask.destinationDirectory.get().asFile} " +
"intersects with java destination dir $javaOutputDir"
)
}
illegalTaskOrNull != null
}
}
private val javaSourceFiles = objectFactory.fileCollection()
private fun javaFilesPatternFilter(patternFilterable: PatternFilterable) {
patternFilterable.include(
"java".fileExtensionCasePermutations().map { "**/*.$it" }
)
}
@get:Incremental
@get:InputFiles
@get:IgnoreEmptyDirectories
@get:NormalizeLineEndings
@get:PathSensitive(PathSensitivity.RELATIVE)
open val javaSources: FileCollection = objectFactory.fileCollection()
.from(
javaSourceFiles
.asFileTree
.matching(::javaFilesPatternFilter)
)
@get:Internal
internal val androidLayoutResourceFiles = objectFactory.fileCollection()
/**
* This input is used by android-extensions plugin
*/
@get:Incremental
@get:InputFiles
@get:IgnoreEmptyDirectories
@get:NormalizeLineEndings
@get:PathSensitive(PathSensitivity.RELATIVE)
internal open val androidLayoutResources: FileCollection = androidLayoutResourceFiles
.asFileTree
.matching { patternFilterable ->
patternFilterable.include("xml".fileExtensionCasePermutations().map { "**/*.$it" })
}
// override setSource to track Java and script sources as well
override fun source(vararg sources: Any) {
javaSourceFiles.from(sources)
scriptSourceFiles.from(sources)
super.setSource(sources)
}
// override source to track Java and script sources as well
override fun setSource(vararg sources: Any) {
javaSourceFiles.from(*sources)
scriptSourceFiles.from(*sources)
super.setSource(*sources)
}
private fun getClasspathChanges(inputChanges: InputChanges): ClasspathChanges = when {
!classpathSnapshotProperties.useClasspathSnapshot.get() -> ClasspathSnapshotDisabled
else -> {
val classpathSnapshotFiles = ClasspathSnapshotFiles(
classpathSnapshotProperties.classpathSnapshot.files.toList(),
classpathSnapshotProperties.classpathSnapshotDir.get().asFile
)
when {
!inputChanges.isIncremental -> NotAvailableForNonIncrementalRun(classpathSnapshotFiles)
// When `inputChanges.isIncremental == true`, we want to compile incrementally. However, if the incremental state (e.g.
// lookup caches or previous classpath snapshot) is missing, we need to compile non-incrementally. There are a few cases:
// 1. Previous compilation happened using Kotlin daemon and with IC enabled (the usual case) => Incremental state
// including classpath snapshot must have been produced (we have that guarantee in `IncrementalCompilerRunner`).
// 2. Previous compilation happened using Kotlin daemon but with IC disabled (e.g., by setting `kotlin.incremental=false`)
// => This run will have `inputChanges.isIncremental = false` as `isIncrementalCompilationEnabled` is a task input.
// 3. Previous compilation happened without using Kotlin daemon (set by the user or caused by a fallback).
// 4. Previous compilation was skipped because there were no sources to compile (see `AbstractKotlinCompile.executeImpl`).
// In case 3 and 4, it is possible that `inputChanges.isIncremental == true` in this run and incremental state is missing.
!classpathSnapshotFiles.shrunkPreviousClasspathSnapshotFile.exists() -> {
NotAvailableDueToMissingClasspathSnapshot(classpathSnapshotFiles)
}
inputChanges.getFileChanges(classpathSnapshotProperties.classpathSnapshot).none() -> NoChanges(classpathSnapshotFiles)
else -> ToBeComputedByIncrementalCompiler(classpathSnapshotFiles)
}
}
}
}
@CacheableTask
abstract class Kotlin2JsCompile @Inject constructor(
override val compilerOptions: KotlinJsCompilerOptions,
objectFactory: ObjectFactory,
workerExecutor: WorkerExecutor
) : AbstractKotlinCompile<K2JSCompilerArguments>(objectFactory, workerExecutor),
KotlinCompilationTask<KotlinJsCompilerOptions>,
UsesLibraryFilterCachingService,
KotlinJsCompile,
K2MultiplatformCompilationTask {
init {
incremental = true
compilerOptions.verbose.convention(logger.isDebugEnabled)
}
override val kotlinOptions: KotlinJsOptions = KotlinJsOptionsCompat(
{ this },
compilerOptions
)
@get:Input
internal var incrementalJsKlib: Boolean = true
override fun isIncrementalCompilationEnabled(): Boolean {
val freeArgs = enhancedFreeCompilerArgs.get()
return when {
PRODUCE_JS in freeArgs -> false
PRODUCE_UNZIPPED_KLIB in freeArgs -> {
KotlinBuildStatsService.applyIfInitialised {
it.report(BooleanMetrics.JS_KLIB_INCREMENTAL, incrementalJsKlib)
}
incrementalJsKlib
}
PRODUCE_ZIPPED_KLIB in freeArgs -> {
KotlinBuildStatsService.applyIfInitialised {
it.report(BooleanMetrics.JS_KLIB_INCREMENTAL, incrementalJsKlib)
}
incrementalJsKlib
}
else -> incremental
}
}
// Workaround to be able to use default value and change it later based on external input
@get:Internal
internal abstract val defaultDestinationDirectory: DirectoryProperty
@Deprecated("Use destinationDirectory and moduleName instead")
@get:Internal
abstract val outputFileProperty: Property<File>
// Workaround to add additional compiler args based on the exising one
// Currently there is a logic to add additional compiler arguments based on already existing one.
// And it is not possible to update compilerOptions.freeCompilerArgs using some kind of .map
// or .flatMap call - this will cause StackOverlowException as upstream source will be updated
// and .map will be called again.
@get:Input
internal abstract val enhancedFreeCompilerArgs: ListProperty<String>
/**
* Workaround for those "nasty" plugins that are adding 'freeCompilerArgs' on task execution phase.
* With properties api it is not possible to update property value after task configuration is finished.
*
* Marking it as `@Internal` as anyway on the configuration phase, when Gradle does task inputs snapshot,
* this input will always be empty.
*/
@get:Internal
internal var executionTimeFreeCompilerArgs: List<String>? = null
@get:Nested
override val multiplatformStructure: K2MultiplatformStructure = objectFactory.newInstance()
override fun createCompilerArgs(): K2JSCompilerArguments =
K2JSCompilerArguments()
override fun setupCompilerArgs(args: K2JSCompilerArguments, defaultsOnly: Boolean, ignoreClasspathResolutionErrors: Boolean) {
(compilerOptions as KotlinJsCompilerOptionsDefault).fillDefaultValues(args)
super.setupCompilerArgs(args, defaultsOnly = defaultsOnly, ignoreClasspathResolutionErrors = ignoreClasspathResolutionErrors)
if (defaultsOnly) return
(compilerOptions as KotlinJsCompilerOptionsDefault).fillCompilerArguments(args)
if (!args.sourceMapPrefix.isNullOrEmpty()) {
args.sourceMapBaseDirs = sourceMapBaseDir.get().asFile.absolutePath
}
if (isIrBackendEnabled()) {
val outputFilePath: String? = compilerOptions.outputFile.orNull
if (outputFilePath != null) {
val outputFile = File(outputFilePath)
args.outputDir = (if (outputFile.extension == "") outputFile else outputFile.parentFile).normalize().absolutePath
args.moduleName = outputFile.nameWithoutExtension
} else {
args.outputDir = destinationDirectory.get().asFile.normalize().absolutePath
args.moduleName = compilerOptions.moduleName.get()
}
} else {
args.outputFile = outputFileProperty.get().absoluteFile.normalize().absolutePath
}
args.configureMultiplatform(
compilerOptions,
k1CommonSources = commonSourceSet.asFileTree,
k2MultiplatformFragments = multiplatformStructure
)
// Overriding freeArgs from compilerOptions with enhanced one + additional one set on execution phase
// containing additional arguments based on the js compilation configuration
val localExecutionTimeFreeCompilerArgs = executionTimeFreeCompilerArgs
args.freeArgs = if (localExecutionTimeFreeCompilerArgs != null) localExecutionTimeFreeCompilerArgs else enhancedFreeCompilerArgs.get()
}
@get:InputFiles
@get:IgnoreEmptyDirectories
@get:Incremental
@get:Optional
@get:NormalizeLineEndings
@get:PathSensitive(PathSensitivity.RELATIVE)
internal val friendDependencies: FileCollection = objectFactory
.fileCollection()
.from(friendPaths)
.filter {
// .jar files are not required for js compilation as friend modules
// and, because of `@InputFiles` and different normalization strategy from `@Classpath`,
// they produce build cache misses
it.exists() && !it.name.endsWith(".jar") && libraryFilter(it)
}
@get:Internal
internal val sourceMapBaseDir: Property<Directory> = objectFactory
.directoryProperty()
.value(project.layout.projectDirectory)
private fun isHybridKotlinJsLibrary(file: File): Boolean =
JsLibraryUtils.isKotlinJavascriptLibrary(file) && isKotlinLibrary(file)
private val preIrBackendCompilerFlags = listOf(
DISABLE_PRE_IR,
PRODUCE_JS,
PRODUCE_ZIPPED_KLIB
)
private fun isPreIrBackendDisabled(): Boolean = enhancedFreeCompilerArgs
.get()
.any { preIrBackendCompilerFlags.contains(it) }
// see also isIncrementalCompilationEnabled
private val irBackendCompilerFlags = listOf(
PRODUCE_UNZIPPED_KLIB,
PRODUCE_JS,
PRODUCE_ZIPPED_KLIB
)
private fun isIrBackendEnabled(): Boolean = enhancedFreeCompilerArgs
.get()
.any { irBackendCompilerFlags.contains(it) }
private val File.asLibraryFilterCacheKey: LibraryFilterCachingService.LibraryFilterCacheKey
get() = LibraryFilterCachingService.LibraryFilterCacheKey(
this,
irEnabled = isIrBackendEnabled(),
preIrDisabled = isPreIrBackendDisabled()
)
// Kotlin/JS can operate in 3 modes:
// 1) purely pre-IR backend
// 2) purely IR backend
// 3) hybrid pre-IR and IR backend. Can only accept libraries with both JS and IR parts.
private val libraryFilterBody: (File) -> Boolean
get() = if (isIrBackendEnabled()) {
if (isPreIrBackendDisabled()) {
//::isKotlinLibrary
// Workaround for KT-47797
{ isKotlinLibrary(it) }
} else {
::isHybridKotlinJsLibrary
}
} else {
JsLibraryUtils::isKotlinJavascriptLibrary
}
@get:Input
internal val jsLegacyNoWarn: Provider<Boolean> = objectFactory.property(
PropertiesProvider(project).jsCompilerNoWarn
)
@get:Internal
protected val libraryFilter: (File) -> Boolean
get() = { file ->
libraryFilterCacheService.get().getOrCompute(file.asLibraryFilterCacheKey, libraryFilterBody)
}
override val incrementalProps: List<FileCollection>
get() = super.incrementalProps + listOf(friendDependencies)
open fun processArgs(
args: K2JSCompilerArguments
) {
}
override fun callCompilerAsync(
args: K2JSCompilerArguments,
kotlinSources: Set<File>,
inputChanges: InputChanges,
taskOutputsBackup: TaskOutputsBackup?
) {
logger.debug("Calling compiler")
validateOutputDirectory()
if (isIrBackendEnabled()) {
logger.info(USING_JS_IR_BACKEND_MESSAGE)
}
val dependencies = libraries
.filter { it.exists() && libraryFilter(it) }
.map { it.normalize().absolutePath }
args.libraries = dependencies.distinct().let {
if (it.isNotEmpty())
it.joinToString(File.pathSeparator) else
null
}
args.friendModules = friendDependencies.files.joinToString(File.pathSeparator) { it.absolutePath }
if (!isIrBackendEnabled()) {
args.legacyDeprecatedNoWarn = true
args.useDeprecatedLegacyCompiler = true
}
logger.kotlinDebug("compiling with args ${ArgumentUtils.convertArgumentsToStringList(args)}")
val gradlePrintingMessageCollector = GradlePrintingMessageCollector(logger, args.allWarningsAsErrors)
val gradleMessageCollector = GradleErrorMessageCollector(gradlePrintingMessageCollector, kotlinPluginVersion = getKotlinPluginVersion(logger))
val outputItemCollector = OutputItemsCollectorImpl()
val compilerRunner = compilerRunner.get()
val icEnv = if (isIncrementalCompilationEnabled()) {
logger.info(USING_JS_INCREMENTAL_COMPILATION_MESSAGE)
IncrementalCompilationEnvironment(
getChangedFiles(inputChanges, incrementalProps),
ClasspathChanges.NotAvailableForJSCompiler,
taskBuildCacheableOutputDirectory.get().asFile,
multiModuleICSettings = multiModuleICSettings,
preciseCompilationResultsBackup = preciseCompilationResultsBackup.get(),
)
} else null
val environment = GradleCompilerEnvironment(
defaultCompilerClasspath, gradleMessageCollector, outputItemCollector,
outputFiles = allOutputFiles(),
reportingSettings = reportingSettings(),
incrementalCompilationEnvironment = icEnv
)
processArgs(args)
compilerRunner.runJsCompilerAsync(
kotlinSources.toList(),
args,
environment,
taskOutputsBackup
)
compilerRunner.errorsFile?.also { gradleMessageCollector.flush(it) }
}
private val projectRootDir = project.rootDir
private fun validateOutputDirectory() {
val outputFile = outputFileProperty.get()
val outputDir = outputFile.parentFile
if (outputDir.isParentOf(projectRootDir)) {
throw InvalidUserDataException(
"The output directory '$outputDir' (defined by outputFile of ':$name') contains or " +
"matches the project root directory '${projectRootDir}'.\n" +
"Gradle will not be able to build the project because of the root directory lock.\n" +
"To fix this, consider using the default outputFile location instead of providing it explicitly."
)
}
}
}
data class KotlinCompilerPluginData(
@get:Classpath
val classpath: FileCollection,
@get:Internal
val options: CompilerPluginOptions,
/**
* Used only for Up-to-date checks
*/
@get:Nested
val inputsOutputsState: InputsOutputsState
) {
data class InputsOutputsState(
@get:Input
val inputs: Map<String, String>,
@get:InputFiles
@get:IgnoreEmptyDirectories
@get:NormalizeLineEndings
@get:PathSensitive(PathSensitivity.RELATIVE)
val inputFiles: Set<File>,
@get:OutputFiles
val outputFiles: Set<File>
)
}
@@ -1,918 +1,55 @@
/* /*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. * Copyright 2010-2023 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. * 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.tasks package org.jetbrains.kotlin.gradle.tasks
import groovy.lang.Closure import org.gradle.api.InvalidUserDataException
import org.gradle.api.* import org.gradle.api.file.Directory
import org.gradle.api.file.* import org.gradle.api.file.DirectoryProperty
import org.gradle.api.invocation.Gradle import org.gradle.api.file.FileCollection
import org.gradle.api.logging.Logger
import org.gradle.api.model.ObjectFactory import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.ListProperty import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider import org.gradle.api.provider.Provider
import org.gradle.api.provider.SetProperty
import org.gradle.api.specs.Spec
import org.gradle.api.tasks.* import org.gradle.api.tasks.*
import org.gradle.api.tasks.compile.AbstractCompile
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.api.tasks.util.PatternFilterable
import org.gradle.api.tasks.util.PatternSet
import org.gradle.util.GradleVersion
import org.gradle.work.ChangeType
import org.gradle.work.Incremental import org.gradle.work.Incremental
import org.gradle.work.InputChanges import org.gradle.work.InputChanges
import org.gradle.work.NormalizeLineEndings import org.gradle.work.NormalizeLineEndings
import org.gradle.workers.WorkerExecutor import org.gradle.workers.WorkerExecutor
import org.jetbrains.kotlin.build.DEFAULT_KOTLIN_SOURCE_FILES_EXTENSIONS
import org.jetbrains.kotlin.build.report.metrics.*
import org.jetbrains.kotlin.cli.common.CompilerSystemProperties
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
import org.jetbrains.kotlin.cli.common.arguments.CommonToolArguments
import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments import org.jetbrains.kotlin.compilerRunner.ArgumentUtils
import org.jetbrains.kotlin.compilerRunner.* import org.jetbrains.kotlin.compilerRunner.GradleCompilerEnvironment
import org.jetbrains.kotlin.compilerRunner.GradleCompilerRunner.Companion.normalizeForFlagFile import org.jetbrains.kotlin.compilerRunner.IncrementalCompilationEnvironment
import org.jetbrains.kotlin.config.JvmTarget import org.jetbrains.kotlin.compilerRunner.OutputItemsCollectorImpl
import org.jetbrains.kotlin.daemon.common.MultiModuleICSettings import org.jetbrains.kotlin.gradle.dsl.KotlinJsCompile
import org.jetbrains.kotlin.gradle.dsl.* import org.jetbrains.kotlin.gradle.dsl.KotlinJsCompilerOptions
import org.jetbrains.kotlin.gradle.incremental.* import org.jetbrains.kotlin.gradle.dsl.KotlinJsCompilerOptionsDefault
import org.jetbrains.kotlin.gradle.internal.* import org.jetbrains.kotlin.gradle.dsl.KotlinJsOptions
import org.jetbrains.kotlin.gradle.internal.tasks.TaskWithLocalState
import org.jetbrains.kotlin.gradle.internal.tasks.allOutputFiles import org.jetbrains.kotlin.gradle.internal.tasks.allOutputFiles
import org.jetbrains.kotlin.gradle.logging.GradleErrorMessageCollector import org.jetbrains.kotlin.gradle.logging.GradleErrorMessageCollector
import org.jetbrains.kotlin.gradle.logging.GradleKotlinLogger
import org.jetbrains.kotlin.gradle.logging.GradlePrintingMessageCollector import org.jetbrains.kotlin.gradle.logging.GradlePrintingMessageCollector
import org.jetbrains.kotlin.gradle.logging.kotlinDebug import org.jetbrains.kotlin.gradle.logging.kotlinDebug
import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.mpp.* import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService
import org.jetbrains.kotlin.gradle.report.*
import org.jetbrains.kotlin.gradle.targets.js.internal.LibraryFilterCachingService import org.jetbrains.kotlin.gradle.targets.js.internal.LibraryFilterCachingService
import org.jetbrains.kotlin.gradle.targets.js.internal.UsesLibraryFilterCachingService import org.jetbrains.kotlin.gradle.targets.js.internal.UsesLibraryFilterCachingService
import org.jetbrains.kotlin.gradle.targets.js.ir.* import org.jetbrains.kotlin.gradle.targets.js.ir.DISABLE_PRE_IR
import org.jetbrains.kotlin.gradle.targets.js.ir.PRODUCE_JS
import org.jetbrains.kotlin.gradle.targets.js.ir.PRODUCE_UNZIPPED_KLIB
import org.jetbrains.kotlin.gradle.targets.js.ir.PRODUCE_ZIPPED_KLIB
import org.jetbrains.kotlin.gradle.tasks.internal.KotlinJsOptionsCompat import org.jetbrains.kotlin.gradle.tasks.internal.KotlinJsOptionsCompat
import org.jetbrains.kotlin.gradle.tasks.internal.KotlinJvmOptionsCompat import org.jetbrains.kotlin.gradle.utils.isParentOf
import org.jetbrains.kotlin.gradle.utils.* import org.jetbrains.kotlin.gradle.utils.newInstance
import org.jetbrains.kotlin.incremental.* import org.jetbrains.kotlin.gradle.utils.property
import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotDisabled import org.jetbrains.kotlin.incremental.ClasspathChanges
import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotEnabled.*
import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotEnabled.IncrementalRun.*
import org.jetbrains.kotlin.library.impl.isKotlinLibrary import org.jetbrains.kotlin.library.impl.isKotlinLibrary
import org.jetbrains.kotlin.statistics.metrics.BooleanMetrics import org.jetbrains.kotlin.statistics.metrics.BooleanMetrics
import org.jetbrains.kotlin.utils.JsLibraryUtils import org.jetbrains.kotlin.utils.JsLibraryUtils
import org.jetbrains.kotlin.utils.addToStdlib.cast
import java.io.File import java.io.File
import java.nio.file.Files.*
import javax.inject.Inject import javax.inject.Inject
import org.jetbrains.kotlin.gradle.tasks.cleanOutputsAndLocalState as cleanOutputsAndLocalStateUtil
const val KOTLIN_BUILD_DIR_NAME = "kotlin"
const val USING_JVM_INCREMENTAL_COMPILATION_MESSAGE = "Using Kotlin/JVM incremental compilation"
const val USING_JS_INCREMENTAL_COMPILATION_MESSAGE = "Using Kotlin/JS incremental compilation"
const val USING_JS_IR_BACKEND_MESSAGE = "Using Kotlin/JS IR backend"
abstract class AbstractKotlinCompileTool<T : CommonToolArguments> @Inject constructor(
objectFactory: ObjectFactory
) : DefaultTask(),
KotlinCompileTool,
CompilerArgumentAware<T>,
TaskWithLocalState {
private val patternFilterable = PatternSet()
init {
patternFilterable.include(
DEFAULT_KOTLIN_SOURCE_FILES_EXTENSIONS.flatMap { ext -> ext.fileExtensionCasePermutations().map { "**/*.$it" } }
)
}
private val sourceFiles = objectFactory.fileCollection()
override val sources: FileCollection = objectFactory.fileCollection()
.from(
{ sourceFiles.asFileTree.matching(patternFilterable) }
)
override fun source(vararg sources: Any) {
sourceFiles.from(sources)
}
override fun setSource(vararg sources: Any) {
sourceFiles.from(sources)
}
fun disallowSourceChanges() {
sourceFiles.disallowChanges()
}
@Internal
final override fun getIncludes(): MutableSet<String> = patternFilterable.includes
@Internal
final override fun getExcludes(): MutableSet<String> = patternFilterable.excludes
final override fun setIncludes(includes: Iterable<String>): PatternFilterable = also {
patternFilterable.setIncludes(includes)
}
final override fun setExcludes(excludes: Iterable<String>): PatternFilterable = also {
patternFilterable.setExcludes(excludes)
}
final override fun include(vararg includes: String?): PatternFilterable = also {
patternFilterable.include(*includes)
}
final override fun include(includes: Iterable<String>): PatternFilterable = also {
patternFilterable.include(includes)
}
final override fun include(includeSpec: Spec<FileTreeElement>): PatternFilterable = also {
patternFilterable.include(includeSpec)
}
final override fun include(includeSpec: Closure<*>): PatternFilterable = also {
patternFilterable.include(includeSpec)
}
final override fun exclude(vararg excludes: String?): PatternFilterable = also {
patternFilterable.exclude(*excludes)
}
final override fun exclude(excludes: Iterable<String>): PatternFilterable = also {
patternFilterable.exclude(excludes)
}
final override fun exclude(excludeSpec: Spec<FileTreeElement>): PatternFilterable = also {
patternFilterable.exclude(excludeSpec)
}
final override fun exclude(excludeSpec: Closure<*>): PatternFilterable = also {
patternFilterable.exclude(excludeSpec)
}
@get:Internal
final override val metrics: Property<BuildMetricsReporter> = project.objects
.property(BuildMetricsReporterImpl())
/**
* By default, should be set by plugin from [COMPILER_CLASSPATH_CONFIGURATION_NAME] configuration.
*
* Empty classpath will fail the build.
*/
@get:Classpath
internal val defaultCompilerClasspath: ConfigurableFileCollection =
project.objects.fileCollection()
protected fun validateCompilerClasspath() {
// Note that the check triggers configuration resolution
require(!defaultCompilerClasspath.isEmpty) {
"Default Kotlin compiler classpath is empty! Task: $path (${this::class.qualifiedName})"
}
}
}
abstract class GradleCompileTaskProvider @Inject constructor(
objectFactory: ObjectFactory,
projectLayout: ProjectLayout,
gradle: Gradle,
task: Task,
project: Project,
incrementalModuleInfoProvider: Provider<IncrementalModuleInfoProvider>
) {
@get:Internal
val path: Provider<String> = objectFactory.property(task.path)
@get:Internal
val logger: Provider<Logger> = objectFactory.property(task.logger)
@get:Internal
val buildDir: DirectoryProperty = projectLayout.buildDirectory
@get:Internal
val projectDir: Provider<File> = objectFactory
.property(project.rootProject.projectDir)
@get:Internal
val projectCacheDir: Provider<File> = objectFactory
.property(gradle.projectCacheDir)
@get:Internal
val sessionsDir: Provider<File> = objectFactory
.property(GradleCompilerRunner.sessionsDir(gradle.projectCacheDir))
@get:Internal
val projectName: Provider<String> = objectFactory
.property(project.rootProject.name.normalizeForFlagFile())
@get:Internal
val buildModulesInfo: Provider<out IncrementalModuleInfoProvider> = objectFactory
.property(incrementalModuleInfoProvider)
@get:Internal
val errorsFile: Provider<File?> = objectFactory
.property(
gradle.rootProject.rootDir.resolve(".gradle/kotlin/errors/").also { it.mkdirs() }
.resolve("errors-${System.currentTimeMillis()}.log"))
}
abstract class AbstractKotlinCompile<T : CommonCompilerArguments> @Inject constructor(
objectFactory: ObjectFactory,
workerExecutor: WorkerExecutor
) : AbstractKotlinCompileTool<T>(objectFactory),
CompileUsingKotlinDaemonWithNormalization,
UsesBuildMetricsService,
UsesBuildReportsService,
UsesIncrementalModuleInfoBuildService,
UsesCompilerSystemPropertiesService,
UsesVariantImplementationFactories,
BaseKotlinCompile {
init {
cacheOnlyIfEnabledForKotlin()
}
private val layout = project.layout
@get:Inject
internal abstract val fileSystemOperations: FileSystemOperations
// avoid creating directory in getter: this can lead to failure in parallel build
@get:OutputDirectory
internal open val taskBuildCacheableOutputDirectory: DirectoryProperty = objectFactory.directoryProperty()
// avoid creating directory in getter: this can lead to failure in parallel build
@get:LocalState
internal abstract val taskBuildLocalStateDirectory: DirectoryProperty
@get:Internal
internal val buildHistoryFile
get() = taskBuildLocalStateDirectory.file("build-history.bin")
// indicates that task should compile kotlin incrementally if possible
// it's not possible when IncrementalTaskInputs#isIncremental returns false (i.e first build)
// todo: deprecate and remove (we may need to design api for configuring IC)
// don't rely on it to check if IC is enabled, use isIncrementalCompilationEnabled instead
@get:Internal
var incremental: Boolean = false
set(value) {
field = value
logger.kotlinDebug { "Set $this.incremental=$value" }
}
@Input
internal open fun isIncrementalCompilationEnabled(): Boolean =
incremental
@Deprecated("Scheduled for removal with Kotlin 1.9", ReplaceWith("moduleName"))
@get:Input
abstract val ownModuleName: Property<String>
@get:Internal
val startParameters = BuildReportsService.getStartParameters(project)
@get:Internal
internal abstract val suppressKotlinOptionsFreeArgsModificationWarning: Property<Boolean>
internal fun reportingSettings() = buildReportsService.orNull?.parameters?.reportingSettings?.orNull ?: ReportingSettings()
@get:Internal
protected val multiModuleICSettings: MultiModuleICSettings
get() = MultiModuleICSettings(buildHistoryFile.get().asFile, useModuleDetection.get())
/**
* Plugin Data provided by [KpmCompilerPlugin]
*/
@get:Optional
@get:Nested
// TODO: replace with objects.property and introduce task configurator
internal var kotlinPluginData: Provider<KotlinCompilerPluginData>? = null
@get:Internal
internal val javaOutputDir: DirectoryProperty = objectFactory.directoryProperty()
@get:InputFiles
@get:IgnoreEmptyDirectories
@get:Incremental
@get:NormalizeLineEndings
@get:PathSensitive(PathSensitivity.RELATIVE)
internal val commonSourceSet: ConfigurableFileCollection = objectFactory.fileCollection()
@get:Internal
val abiSnapshotFile
get() = taskBuildCacheableOutputDirectory.file(IncrementalCompilerRunner.ABI_SNAPSHOT_FILE_NAME)
@get:Input
val abiSnapshotRelativePath: Property<String> = objectFactory.property(String::class.java).value(
//TODO update to support any jar changes
"$name/${IncrementalCompilerRunner.ABI_SNAPSHOT_FILE_NAME}"
)
@get:Internal
internal val friendSourceSets = objectFactory.listProperty(String::class.java)
private val kotlinLogger by lazy { GradleKotlinLogger(logger) }
@get:Internal
protected val gradleCompileTaskProvider: Provider<GradleCompileTaskProvider> = objectFactory
.property(
objectFactory.newInstance<GradleCompileTaskProvider>(project.gradle, this, project, incrementalModuleInfoProvider)
)
@get:Internal
internal open val defaultKotlinJavaToolchain: Provider<DefaultKotlinJavaToolchain> = objectFactory
.propertyWithNewInstance({ null })
@get:Internal
internal val compilerRunner: Provider<GradleCompilerRunner> =
objectFactory.propertyWithConvention(
gradleCompileTaskProvider.flatMap { taskProvider ->
compilerExecutionStrategy
.zip(metrics) { executionStrategy, metrics ->
metrics to executionStrategy
}
.flatMap { params ->
defaultKotlinJavaToolchain
.map {
val toolsJar = it.currentJvmJdkToolsJar.orNull
GradleCompilerRunnerWithWorkers(
taskProvider,
toolsJar,
CompilerExecutionSettings(
normalizedKotlinDaemonJvmArguments.orNull,
params.second,
useDaemonFallbackStrategy.get()
),
params.first,
workerExecutor
)
}
}
}
)
@get:Internal
internal abstract val preciseCompilationResultsBackup: Property<Boolean>
/** Task outputs that we don't want to include in [TaskOutputsBackup] (see [TaskOutputsBackup.outputsToRestore] for more info). */
@get:Internal
internal abstract val taskOutputsBackupExcludes: SetProperty<File>
@TaskAction
fun execute(inputChanges: InputChanges) {
val buildMetrics = metrics.get()
buildMetrics.addTimeMetric(BuildPerformanceMetric.START_TASK_ACTION_EXECUTION)
buildMetrics.measure(BuildTime.OUT_OF_WORKER_TASK_ACTION) {
KotlinBuildStatsService.applyIfInitialised {
if (name.contains("Test"))
it.report(BooleanMetrics.TESTS_EXECUTED, true)
else
it.report(BooleanMetrics.COMPILATION_STARTED, true)
}
validateCompilerClasspath()
systemPropertiesService.get().startIntercept()
CompilerSystemProperties.KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY.value = "true"
// If task throws exception, but its outputs are changed during execution,
// then Gradle forces next build to be non-incremental (see Gradle's DefaultTaskArtifactStateRepository#persistNewOutputs)
// To prevent this, we backup outputs before incremental build and restore when exception is thrown
val outputsBackup: TaskOutputsBackup? =
if (isIncrementalCompilationEnabled() && inputChanges.isIncremental)
buildMetrics.measure(BuildTime.BACKUP_OUTPUT) {
TaskOutputsBackup(
fileSystemOperations,
layout.buildDirectory,
layout.buildDirectory.dir("snapshot/kotlin/$name"),
outputsToRestore = allOutputFiles() - taskOutputsBackupExcludes.get(),
logger
).also {
it.createSnapshot()
}
}
else null
if (!isIncrementalCompilationEnabled()) {
cleanOutputsAndLocalState("IC is disabled")
} else if (!inputChanges.isIncremental) {
cleanOutputsAndLocalState("Task cannot run incrementally")
}
executeImpl(inputChanges, outputsBackup)
}
buildMetricsService.orNull?.also { it.addTask(path, this.javaClass, buildMetrics) }
}
protected open fun cleanOutputsAndLocalState(reason: String?) {
cleanOutputsAndLocalStateUtil(reason)
}
protected open fun skipCondition(): Boolean = sources.isEmpty
@get:Internal
protected open val incrementalProps: List<FileCollection>
get() = listOfNotNull(
sources,
libraries,
commonSourceSet
)
private fun executeImpl(
inputChanges: InputChanges,
taskOutputsBackup: TaskOutputsBackup?
) {
val allKotlinSources = sources.asFileTree.files
logger.kotlinDebug { "All kotlin sources: ${allKotlinSources.pathsAsStringRelativeTo(gradleCompileTaskProvider.get().projectDir.get())}" }
if (!inputChanges.isIncremental && skipCondition()) {
// Skip running only if non-incremental run. Otherwise, we may need to do some cleanup.
logger.kotlinDebug { "No Kotlin files found, skipping Kotlin compiler task" }
return
}
val args = prepareCompilerArguments()
taskBuildCacheableOutputDirectory.get().asFile.mkdirs()
taskBuildLocalStateDirectory.get().asFile.mkdirs()
callCompilerAsync(
args,
allKotlinSources,
inputChanges,
taskOutputsBackup
)
}
protected fun getChangedFiles(
inputChanges: InputChanges,
incrementalProps: List<FileCollection>
) = if (!inputChanges.isIncremental) {
ChangedFiles.Unknown()
} else {
incrementalProps
.fold(mutableListOf<File>() to mutableListOf<File>()) { (modified, removed), prop ->
inputChanges.getFileChanges(prop).forEach {
when (it.changeType) {
ChangeType.ADDED, ChangeType.MODIFIED -> modified.add(it.file)
ChangeType.REMOVED -> removed.add(it.file)
else -> Unit
}
}
modified to removed
}
.run {
ChangedFiles.Known(first, second)
}
}
/**
* Compiler might be executed asynchronously. Do not do anything requiring end of compilation after this function is called.
* @see [GradleKotlinCompilerWork]
*/
internal abstract fun callCompilerAsync(
args: T,
kotlinSources: Set<File>,
inputChanges: InputChanges,
taskOutputsBackup: TaskOutputsBackup?
)
@get:Internal
internal val abstractKotlinCompileArgumentsContributor by lazy {
AbstractKotlinCompileArgumentsContributor(
KotlinCompileArgumentsProvider(this)
)
}
override fun setupCompilerArgs(args: T, defaultsOnly: Boolean, ignoreClasspathResolutionErrors: Boolean) {
abstractKotlinCompileArgumentsContributor.contributeArguments(
args,
compilerArgumentsConfigurationFlags(defaultsOnly, ignoreClasspathResolutionErrors)
)
if (reportingSettings().buildReportMode == BuildReportMode.VERBOSE) {
args.reportPerf = true
}
}
}
open class KotlinCompileArgumentsProvider<T : AbstractKotlinCompile<out CommonCompilerArguments>>(taskProvider: T) {
val logger: Logger = taskProvider.logger
val isMultiplatform: Boolean = taskProvider.multiPlatformEnabled.get()
private val pluginData = taskProvider.kotlinPluginData?.orNull
val pluginClasspath: FileCollection = listOfNotNull(taskProvider.pluginClasspath, pluginData?.classpath).reduce(FileCollection::plus)
val pluginOptions: CompilerPluginOptions = taskProvider.pluginOptions.toSingleCompilerPluginOptions() + pluginData?.options
}
class KotlinJvmCompilerArgumentsProvider
(taskProvider: KotlinCompile) : KotlinCompileArgumentsProvider<KotlinCompile>(taskProvider) {
val taskName: String = taskProvider.name
val moduleName: String = taskProvider.moduleName.get()
val friendPaths: FileCollection = taskProvider.friendPaths
val compileClasspath: Iterable<File> = taskProvider.libraries
val destinationDir: File = taskProvider.destinationDirectory.get().asFile
internal val compilerOptions: KotlinJvmCompilerOptions = taskProvider.compilerOptions
}
internal inline val <reified T : Task> T.thisTaskProvider: TaskProvider<out T>
get() = checkNotNull(project.locateTask<T>(name))
@CacheableTask
abstract class KotlinCompile @Inject constructor(
final override val compilerOptions: KotlinJvmCompilerOptions,
workerExecutor: WorkerExecutor,
objectFactory: ObjectFactory
) : AbstractKotlinCompile<K2JVMCompilerArguments>(objectFactory, workerExecutor),
K2MultiplatformCompilationTask,
@Suppress("TYPEALIAS_EXPANSION_DEPRECATION") KotlinJvmCompileDsl,
KotlinCompilationTask<KotlinJvmCompilerOptions>,
UsesKotlinJavaToolchain {
init {
compilerOptions.moduleName.convention(moduleName)
compilerOptions.verbose.convention(logger.isDebugEnabled)
}
final override val kotlinOptions: KotlinJvmOptions = KotlinJvmOptionsCompat(
{ this },
compilerOptions
)
@Suppress("DEPRECATION")
@Deprecated("Configure compilerOptions directly", replaceWith = ReplaceWith("compilerOptions"))
override val parentKotlinOptions: Property<KotlinJvmOptions> = objectFactory
.property(kotlinOptions)
.chainedDisallowChanges()
@get:Nested
override val multiplatformStructure: K2MultiplatformStructure = objectFactory.newInstance()
/** A package prefix that is used for locating Java sources in a directory structure with non-full-depth packages.
*
* Example: a Java source file with `package com.example.my.package` is located in directory `src/main/java/my/package`.
* Then, for the Kotlin compilation to locate the source file, use package prefix `"com.example"` */
@get:Input
@get:Optional
var javaPackagePrefix: String? = null
@get:Input
var usePreciseJavaTracking: Boolean = true
set(value) {
field = value
logger.kotlinDebug { "Set $this.usePreciseJavaTracking=$value" }
}
@get:Internal // To support compile avoidance (ClasspathSnapshotProperties.classpathSnapshot will be used as input instead)
abstract override val libraries: ConfigurableFileCollection
@Deprecated(
"Replaced with 'libraries' input",
replaceWith = ReplaceWith("libraries"),
level = DeprecationLevel.ERROR
)
@get:Internal
var classpath: FileCollection
set(value) = libraries.setFrom(value)
get() = libraries
@get:Input
abstract val useKotlinAbiSnapshot: Property<Boolean>
@get:Nested
abstract val classpathSnapshotProperties: ClasspathSnapshotProperties
/** Properties related to the `kotlin.incremental.useClasspathSnapshot` feature. */
abstract class ClasspathSnapshotProperties {
@get:Input
abstract val useClasspathSnapshot: Property<Boolean>
@get:Classpath
@get:Incremental
@get:Optional // Set if useClasspathSnapshot == true
abstract val classpathSnapshot: ConfigurableFileCollection
@get:Classpath
@get:Incremental
@get:Optional // Set if useClasspathSnapshot == false (to restore the existing classpath annotations when the feature is disabled)
abstract val classpath: ConfigurableFileCollection
@get:OutputDirectory
@get:Optional // Set if useClasspathSnapshot == true
abstract val classpathSnapshotDir: DirectoryProperty
}
override val incrementalProps: List<FileCollection>
get() = listOf(
sources,
javaSources,
scriptSources,
androidLayoutResources,
commonSourceSet,
classpathSnapshotProperties.classpath,
classpathSnapshotProperties.classpathSnapshot
)
@get:Internal
final override val defaultKotlinJavaToolchain: Provider<DefaultKotlinJavaToolchain> = objectFactory
.propertyWithNewInstance({ this })
final override val kotlinJavaToolchainProvider: Provider<KotlinJavaToolchain> = defaultKotlinJavaToolchain.cast()
@get:Internal
internal abstract val associatedJavaCompileTaskTargetCompatibility: Property<String>
@get:Internal
internal abstract val associatedJavaCompileTaskName: Property<String>
@get:Input
internal abstract val jvmTargetValidationMode: Property<PropertiesProvider.JvmTargetValidationMode>
@get:Internal
internal val scriptDefinitions: ConfigurableFileCollection = objectFactory.fileCollection()
@get:Input
@get:Optional
internal val scriptExtensions: SetProperty<String> = objectFactory.setPropertyWithLazyValue {
scriptDefinitions
.map { definitionFile ->
definitionFile.readLines().filter(String::isNotBlank)
}
.flatten()
}
private class ScriptFilterSpec(
private val scriptExtensions: SetProperty<String>
) : Spec<FileTreeElement> {
override fun isSatisfiedBy(element: FileTreeElement): Boolean {
val extensions = scriptExtensions.get()
return extensions.isNotEmpty() &&
(element.isDirectory || extensions.contains(element.file.extension))
}
}
private val scriptSourceFiles = objectFactory.fileCollection()
@get:InputFiles
@get:SkipWhenEmpty
@get:IgnoreEmptyDirectories
@get:NormalizeLineEndings
@get:PathSensitive(PathSensitivity.RELATIVE)
internal open val scriptSources: FileCollection = scriptSourceFiles
.asFileTree
.matching { patternFilterable ->
patternFilterable.include(ScriptFilterSpec(scriptExtensions))
}
init {
incremental = true
}
override fun skipCondition(): Boolean = sources.isEmpty && scriptSources.isEmpty
override fun createCompilerArgs(): K2JVMCompilerArguments =
K2JVMCompilerArguments()
/**
* Workaround for those "nasty" plugins that are adding 'freeCompilerArgs' on task execution phase.
* With properties api it is not possible to update property value after task configuration is finished.
*
* Marking it as `@Internal` as anyway on the configuration phase, when Gradle does task inputs snapshot,
* this input will always be empty.
*/
@get:Internal
internal var executionTimeFreeCompilerArgs: List<String>? = null
override fun setupCompilerArgs(args: K2JVMCompilerArguments, defaultsOnly: Boolean, ignoreClasspathResolutionErrors: Boolean) {
compilerArgumentsContributor.contributeArguments(
args, compilerArgumentsConfigurationFlags(
defaultsOnly,
ignoreClasspathResolutionErrors
)
)
args.configureMultiplatform(
compilerOptions,
k1CommonSources = commonSourceSet.asFileTree,
k2MultiplatformFragments = multiplatformStructure
)
if (reportingSettings().buildReportMode == BuildReportMode.VERBOSE) {
args.reportPerf = true
}
val localExecutionTimeFreeCompilerArgs = executionTimeFreeCompilerArgs
if (localExecutionTimeFreeCompilerArgs != null) {
args.freeArgs = localExecutionTimeFreeCompilerArgs
}
}
@get:Internal
internal val compilerArgumentsContributor: CompilerArgumentsContributor<K2JVMCompilerArguments> by lazy {
KotlinJvmCompilerArgumentsContributor(KotlinJvmCompilerArgumentsProvider(this))
}
override fun callCompilerAsync(
args: K2JVMCompilerArguments,
kotlinSources: Set<File>,
inputChanges: InputChanges,
taskOutputsBackup: TaskOutputsBackup?
) {
validateKotlinAndJavaHasSameTargetCompatibility(args)
val scriptSources = scriptSources.asFileTree.files
val javaSources = javaSources.files
val gradlePrintingMessageCollector = GradlePrintingMessageCollector(logger, args.allWarningsAsErrors)
val gradleMessageCollector = GradleErrorMessageCollector(gradlePrintingMessageCollector, kotlinPluginVersion = getKotlinPluginVersion(logger))
val outputItemCollector = OutputItemsCollectorImpl()
val compilerRunner = compilerRunner.get()
val icEnv = if (isIncrementalCompilationEnabled()) {
logger.info(USING_JVM_INCREMENTAL_COMPILATION_MESSAGE)
IncrementalCompilationEnvironment(
changedFiles = getChangedFiles(inputChanges, incrementalProps),
classpathChanges = getClasspathChanges(inputChanges),
workingDir = taskBuildCacheableOutputDirectory.get().asFile,
usePreciseJavaTracking = usePreciseJavaTracking,
disableMultiModuleIC = disableMultiModuleIC,
multiModuleICSettings = multiModuleICSettings,
withAbiSnapshot = useKotlinAbiSnapshot.get(),
preciseCompilationResultsBackup = preciseCompilationResultsBackup.get(),
)
} else null
@Suppress("ConvertArgumentToSet")
val environment = GradleCompilerEnvironment(
defaultCompilerClasspath, gradleMessageCollector, outputItemCollector,
// In the incremental compiler, outputFiles will be cleaned on rebuild. However, because classpathSnapshotDir is not included in
// TaskOutputsBackup, we don't want classpathSnapshotDir to be cleaned immediately on rebuild, and therefore we exclude it from
// outputFiles here. (See TaskOutputsBackup's kdoc for more info.)
outputFiles = allOutputFiles()
- (classpathSnapshotProperties.classpathSnapshotDir.orNull?.asFile?.let { setOf(it) } ?: emptySet()),
reportingSettings = reportingSettings(),
incrementalCompilationEnvironment = icEnv,
kotlinScriptExtensions = scriptExtensions.get().toTypedArray()
)
logger.info("Kotlin source files: ${kotlinSources.joinToString()}")
logger.info("Java source files: ${javaSources.joinToString()}")
logger.info("Script source files: ${scriptSources.joinToString()}")
logger.info("Script file extensions: ${scriptExtensions.get().joinToString()}")
compilerRunner.runJvmCompilerAsync(
(kotlinSources + scriptSources + javaSources).toList(),
javaPackagePrefix,
args,
environment,
defaultKotlinJavaToolchain.get().buildJvm.get().javaHome,
taskOutputsBackup
)
compilerRunner.errorsFile?.also { gradleMessageCollector.flush(it) }
}
private fun validateKotlinAndJavaHasSameTargetCompatibility(
args: K2JVMCompilerArguments,
) {
associatedJavaCompileTaskTargetCompatibility.orNull?.let { targetCompatibility ->
val normalizedJavaTarget = when (targetCompatibility) {
"6" -> "1.6"
"7" -> "1.7"
"8" -> "1.8"
"1.9" -> "9"
else -> targetCompatibility
}
val jvmTarget = args.jvmTarget ?: JvmTarget.DEFAULT.toString()
if (normalizedJavaTarget != jvmTarget) {
val javaTaskName = associatedJavaCompileTaskName.get()
val errorMessage = buildString {
append("'$javaTaskName' task (current target is $targetCompatibility) and ")
append("'$name' task (current target is $jvmTarget) ")
appendLine("jvm target compatibility should be set to the same Java version.")
if (GradleVersion.current().baseVersion < GradleVersion.version("8.0")) {
append("By default will become an error since Gradle 8.0+! ")
appendLine("Read more: https://kotl.in/gradle/jvm/target-validation")
}
appendLine("Consider using JVM toolchain: https://kotl.in/gradle/jvm/toolchain")
}
when (jvmTargetValidationMode.get()) {
PropertiesProvider.JvmTargetValidationMode.ERROR -> throw GradleException(errorMessage)
PropertiesProvider.JvmTargetValidationMode.WARNING -> logger.warn(errorMessage)
else -> Unit
}
}
}
}
@get:Input
val disableMultiModuleIC: Boolean by lazy {
if (!isIncrementalCompilationEnabled() || !javaOutputDir.isPresent) {
false
} else {
var illegalTaskOrNull: AbstractCompile? = null
project.tasks.configureEach {
if (it is AbstractCompile &&
it !is JavaCompile &&
it !is AbstractKotlinCompile<*> &&
javaOutputDir.get().asFile.isParentOf(it.destinationDirectory.get().asFile)
) {
illegalTaskOrNull = illegalTaskOrNull ?: it
}
}
if (illegalTaskOrNull != null) {
val illegalTask = illegalTaskOrNull!!
logger.info(
"Kotlin inter-project IC is disabled: " +
"unknown task '$illegalTask' destination dir ${illegalTask.destinationDirectory.get().asFile} " +
"intersects with java destination dir $javaOutputDir"
)
}
illegalTaskOrNull != null
}
}
private val javaSourceFiles = objectFactory.fileCollection()
private fun javaFilesPatternFilter(patternFilterable: PatternFilterable) {
patternFilterable.include(
"java".fileExtensionCasePermutations().map { "**/*.$it" }
)
}
@get:Incremental
@get:InputFiles
@get:IgnoreEmptyDirectories
@get:NormalizeLineEndings
@get:PathSensitive(PathSensitivity.RELATIVE)
open val javaSources: FileCollection = objectFactory.fileCollection()
.from(
javaSourceFiles
.asFileTree
.matching(::javaFilesPatternFilter)
)
@get:Internal
internal val androidLayoutResourceFiles = objectFactory.fileCollection()
/**
* This input is used by android-extensions plugin
*/
@get:Incremental
@get:InputFiles
@get:IgnoreEmptyDirectories
@get:NormalizeLineEndings
@get:PathSensitive(PathSensitivity.RELATIVE)
internal open val androidLayoutResources: FileCollection = androidLayoutResourceFiles
.asFileTree
.matching { patternFilterable ->
patternFilterable.include("xml".fileExtensionCasePermutations().map { "**/*.$it" })
}
// override setSource to track Java and script sources as well
override fun source(vararg sources: Any) {
javaSourceFiles.from(sources)
scriptSourceFiles.from(sources)
super.setSource(sources)
}
// override source to track Java and script sources as well
override fun setSource(vararg sources: Any) {
javaSourceFiles.from(*sources)
scriptSourceFiles.from(*sources)
super.setSource(*sources)
}
private fun getClasspathChanges(inputChanges: InputChanges): ClasspathChanges = when {
!classpathSnapshotProperties.useClasspathSnapshot.get() -> ClasspathSnapshotDisabled
else -> {
val classpathSnapshotFiles = ClasspathSnapshotFiles(
classpathSnapshotProperties.classpathSnapshot.files.toList(),
classpathSnapshotProperties.classpathSnapshotDir.get().asFile
)
when {
!inputChanges.isIncremental -> NotAvailableForNonIncrementalRun(classpathSnapshotFiles)
// When `inputChanges.isIncremental == true`, we want to compile incrementally. However, if the incremental state (e.g.
// lookup caches or previous classpath snapshot) is missing, we need to compile non-incrementally. There are a few cases:
// 1. Previous compilation happened using Kotlin daemon and with IC enabled (the usual case) => Incremental state
// including classpath snapshot must have been produced (we have that guarantee in `IncrementalCompilerRunner`).
// 2. Previous compilation happened using Kotlin daemon but with IC disabled (e.g., by setting `kotlin.incremental=false`)
// => This run will have `inputChanges.isIncremental = false` as `isIncrementalCompilationEnabled` is a task input.
// 3. Previous compilation happened without using Kotlin daemon (set by the user or caused by a fallback).
// 4. Previous compilation was skipped because there were no sources to compile (see `AbstractKotlinCompile.executeImpl`).
// In case 3 and 4, it is possible that `inputChanges.isIncremental == true` in this run and incremental state is missing.
!classpathSnapshotFiles.shrunkPreviousClasspathSnapshotFile.exists() -> {
NotAvailableDueToMissingClasspathSnapshot(classpathSnapshotFiles)
}
inputChanges.getFileChanges(classpathSnapshotProperties.classpathSnapshot).none() -> NoChanges(classpathSnapshotFiles)
else -> ToBeComputedByIncrementalCompiler(classpathSnapshotFiles)
}
}
}
}
@CacheableTask @CacheableTask
abstract class Kotlin2JsCompile @Inject constructor( abstract class Kotlin2JsCompile @Inject constructor(
@@ -1199,31 +336,3 @@ abstract class Kotlin2JsCompile @Inject constructor(
} }
} }
} }
data class KotlinCompilerPluginData(
@get:Classpath
val classpath: FileCollection,
@get:Internal
val options: CompilerPluginOptions,
/**
* Used only for Up-to-date checks
*/
@get:Nested
val inputsOutputsState: InputsOutputsState
) {
data class InputsOutputsState(
@get:Input
val inputs: Map<String, String>,
@get:InputFiles
@get:IgnoreEmptyDirectories
@get:NormalizeLineEndings
@get:PathSensitive(PathSensitivity.RELATIVE)
val inputFiles: Set<File>,
@get:OutputFiles
val outputFiles: Set<File>
)
}
@@ -1,17 +1,16 @@
/* /*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. * Copyright 2010-2023 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. * 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.tasks package org.jetbrains.kotlin.gradle.tasks
import groovy.lang.Closure import org.gradle.api.GradleException
import org.gradle.api.* import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.* import org.gradle.api.file.DirectoryProperty
import org.gradle.api.invocation.Gradle import org.gradle.api.file.FileCollection
import org.gradle.api.logging.Logger import org.gradle.api.file.FileTreeElement
import org.gradle.api.model.ObjectFactory import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider import org.gradle.api.provider.Provider
import org.gradle.api.provider.SetProperty import org.gradle.api.provider.SetProperty
@@ -20,505 +19,44 @@ import org.gradle.api.tasks.*
import org.gradle.api.tasks.compile.AbstractCompile import org.gradle.api.tasks.compile.AbstractCompile
import org.gradle.api.tasks.compile.JavaCompile import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.api.tasks.util.PatternFilterable import org.gradle.api.tasks.util.PatternFilterable
import org.gradle.api.tasks.util.PatternSet
import org.gradle.util.GradleVersion import org.gradle.util.GradleVersion
import org.gradle.work.ChangeType
import org.gradle.work.Incremental import org.gradle.work.Incremental
import org.gradle.work.InputChanges import org.gradle.work.InputChanges
import org.gradle.work.NormalizeLineEndings import org.gradle.work.NormalizeLineEndings
import org.gradle.workers.WorkerExecutor import org.gradle.workers.WorkerExecutor
import org.jetbrains.kotlin.build.DEFAULT_KOTLIN_SOURCE_FILES_EXTENSIONS
import org.jetbrains.kotlin.build.report.metrics.*
import org.jetbrains.kotlin.cli.common.CompilerSystemProperties
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
import org.jetbrains.kotlin.cli.common.arguments.CommonToolArguments
import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
import org.jetbrains.kotlin.compilerRunner.* import org.jetbrains.kotlin.compilerRunner.GradleCompilerEnvironment
import org.jetbrains.kotlin.compilerRunner.GradleCompilerRunner.Companion.normalizeForFlagFile import org.jetbrains.kotlin.compilerRunner.IncrementalCompilationEnvironment
import org.jetbrains.kotlin.compilerRunner.OutputItemsCollectorImpl
import org.jetbrains.kotlin.config.JvmTarget import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.daemon.common.MultiModuleICSettings import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions
import org.jetbrains.kotlin.gradle.dsl.* import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions
import org.jetbrains.kotlin.gradle.incremental.* import org.jetbrains.kotlin.gradle.internal.CompilerArgumentsContributor
import org.jetbrains.kotlin.gradle.internal.* import org.jetbrains.kotlin.gradle.internal.KotlinJvmCompilerArgumentsContributor
import org.jetbrains.kotlin.gradle.internal.tasks.TaskWithLocalState import org.jetbrains.kotlin.gradle.internal.compilerArgumentsConfigurationFlags
import org.jetbrains.kotlin.gradle.internal.tasks.allOutputFiles import org.jetbrains.kotlin.gradle.internal.tasks.allOutputFiles
import org.jetbrains.kotlin.gradle.logging.GradleErrorMessageCollector import org.jetbrains.kotlin.gradle.logging.GradleErrorMessageCollector
import org.jetbrains.kotlin.gradle.logging.GradleKotlinLogger
import org.jetbrains.kotlin.gradle.logging.GradlePrintingMessageCollector import org.jetbrains.kotlin.gradle.logging.GradlePrintingMessageCollector
import org.jetbrains.kotlin.gradle.logging.kotlinDebug import org.jetbrains.kotlin.gradle.logging.kotlinDebug
import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.mpp.* import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService import org.jetbrains.kotlin.gradle.report.BuildReportMode
import org.jetbrains.kotlin.gradle.report.*
import org.jetbrains.kotlin.gradle.targets.js.internal.LibraryFilterCachingService
import org.jetbrains.kotlin.gradle.targets.js.internal.UsesLibraryFilterCachingService
import org.jetbrains.kotlin.gradle.targets.js.ir.*
import org.jetbrains.kotlin.gradle.tasks.internal.KotlinJsOptionsCompat
import org.jetbrains.kotlin.gradle.tasks.internal.KotlinJvmOptionsCompat import org.jetbrains.kotlin.gradle.tasks.internal.KotlinJvmOptionsCompat
import org.jetbrains.kotlin.gradle.utils.* import org.jetbrains.kotlin.gradle.utils.*
import org.jetbrains.kotlin.incremental.* import org.jetbrains.kotlin.gradle.utils.chainedDisallowChanges
import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotDisabled import org.jetbrains.kotlin.gradle.utils.newInstance
import org.jetbrains.kotlin.gradle.utils.property
import org.jetbrains.kotlin.gradle.utils.propertyWithNewInstance
import org.jetbrains.kotlin.incremental.ClasspathChanges
import org.jetbrains.kotlin.incremental.ClasspathChanges.*
import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotEnabled.* import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotEnabled.*
import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotEnabled.IncrementalRun.* import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotEnabled.IncrementalRun.NoChanges
import org.jetbrains.kotlin.library.impl.isKotlinLibrary import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotEnabled.IncrementalRun.ToBeComputedByIncrementalCompiler
import org.jetbrains.kotlin.statistics.metrics.BooleanMetrics import org.jetbrains.kotlin.incremental.ClasspathSnapshotFiles
import org.jetbrains.kotlin.utils.JsLibraryUtils
import org.jetbrains.kotlin.utils.addToStdlib.cast import org.jetbrains.kotlin.utils.addToStdlib.cast
import java.io.File import java.io.File
import java.nio.file.Files.*
import javax.inject.Inject import javax.inject.Inject
import org.jetbrains.kotlin.gradle.tasks.cleanOutputsAndLocalState as cleanOutputsAndLocalStateUtil import kotlin.text.appendLine
const val KOTLIN_BUILD_DIR_NAME = "kotlin"
const val USING_JVM_INCREMENTAL_COMPILATION_MESSAGE = "Using Kotlin/JVM incremental compilation"
const val USING_JS_INCREMENTAL_COMPILATION_MESSAGE = "Using Kotlin/JS incremental compilation"
const val USING_JS_IR_BACKEND_MESSAGE = "Using Kotlin/JS IR backend"
abstract class AbstractKotlinCompileTool<T : CommonToolArguments> @Inject constructor(
objectFactory: ObjectFactory
) : DefaultTask(),
KotlinCompileTool,
CompilerArgumentAware<T>,
TaskWithLocalState {
private val patternFilterable = PatternSet()
init {
patternFilterable.include(
DEFAULT_KOTLIN_SOURCE_FILES_EXTENSIONS.flatMap { ext -> ext.fileExtensionCasePermutations().map { "**/*.$it" } }
)
}
private val sourceFiles = objectFactory.fileCollection()
override val sources: FileCollection = objectFactory.fileCollection()
.from(
{ sourceFiles.asFileTree.matching(patternFilterable) }
)
override fun source(vararg sources: Any) {
sourceFiles.from(sources)
}
override fun setSource(vararg sources: Any) {
sourceFiles.from(sources)
}
fun disallowSourceChanges() {
sourceFiles.disallowChanges()
}
@Internal
final override fun getIncludes(): MutableSet<String> = patternFilterable.includes
@Internal
final override fun getExcludes(): MutableSet<String> = patternFilterable.excludes
final override fun setIncludes(includes: Iterable<String>): PatternFilterable = also {
patternFilterable.setIncludes(includes)
}
final override fun setExcludes(excludes: Iterable<String>): PatternFilterable = also {
patternFilterable.setExcludes(excludes)
}
final override fun include(vararg includes: String?): PatternFilterable = also {
patternFilterable.include(*includes)
}
final override fun include(includes: Iterable<String>): PatternFilterable = also {
patternFilterable.include(includes)
}
final override fun include(includeSpec: Spec<FileTreeElement>): PatternFilterable = also {
patternFilterable.include(includeSpec)
}
final override fun include(includeSpec: Closure<*>): PatternFilterable = also {
patternFilterable.include(includeSpec)
}
final override fun exclude(vararg excludes: String?): PatternFilterable = also {
patternFilterable.exclude(*excludes)
}
final override fun exclude(excludes: Iterable<String>): PatternFilterable = also {
patternFilterable.exclude(excludes)
}
final override fun exclude(excludeSpec: Spec<FileTreeElement>): PatternFilterable = also {
patternFilterable.exclude(excludeSpec)
}
final override fun exclude(excludeSpec: Closure<*>): PatternFilterable = also {
patternFilterable.exclude(excludeSpec)
}
@get:Internal
final override val metrics: Property<BuildMetricsReporter> = project.objects
.property(BuildMetricsReporterImpl())
/**
* By default, should be set by plugin from [COMPILER_CLASSPATH_CONFIGURATION_NAME] configuration.
*
* Empty classpath will fail the build.
*/
@get:Classpath
internal val defaultCompilerClasspath: ConfigurableFileCollection =
project.objects.fileCollection()
protected fun validateCompilerClasspath() {
// Note that the check triggers configuration resolution
require(!defaultCompilerClasspath.isEmpty) {
"Default Kotlin compiler classpath is empty! Task: $path (${this::class.qualifiedName})"
}
}
}
abstract class GradleCompileTaskProvider @Inject constructor(
objectFactory: ObjectFactory,
projectLayout: ProjectLayout,
gradle: Gradle,
task: Task,
project: Project,
incrementalModuleInfoProvider: Provider<IncrementalModuleInfoProvider>
) {
@get:Internal
val path: Provider<String> = objectFactory.property(task.path)
@get:Internal
val logger: Provider<Logger> = objectFactory.property(task.logger)
@get:Internal
val buildDir: DirectoryProperty = projectLayout.buildDirectory
@get:Internal
val projectDir: Provider<File> = objectFactory
.property(project.rootProject.projectDir)
@get:Internal
val projectCacheDir: Provider<File> = objectFactory
.property(gradle.projectCacheDir)
@get:Internal
val sessionsDir: Provider<File> = objectFactory
.property(GradleCompilerRunner.sessionsDir(gradle.projectCacheDir))
@get:Internal
val projectName: Provider<String> = objectFactory
.property(project.rootProject.name.normalizeForFlagFile())
@get:Internal
val buildModulesInfo: Provider<out IncrementalModuleInfoProvider> = objectFactory
.property(incrementalModuleInfoProvider)
@get:Internal
val errorsFile: Provider<File?> = objectFactory
.property(
gradle.rootProject.rootDir.resolve(".gradle/kotlin/errors/").also { it.mkdirs() }
.resolve("errors-${System.currentTimeMillis()}.log"))
}
abstract class AbstractKotlinCompile<T : CommonCompilerArguments> @Inject constructor(
objectFactory: ObjectFactory,
workerExecutor: WorkerExecutor
) : AbstractKotlinCompileTool<T>(objectFactory),
CompileUsingKotlinDaemonWithNormalization,
UsesBuildMetricsService,
UsesBuildReportsService,
UsesIncrementalModuleInfoBuildService,
UsesCompilerSystemPropertiesService,
UsesVariantImplementationFactories,
BaseKotlinCompile {
init {
cacheOnlyIfEnabledForKotlin()
}
private val layout = project.layout
@get:Inject
internal abstract val fileSystemOperations: FileSystemOperations
// avoid creating directory in getter: this can lead to failure in parallel build
@get:OutputDirectory
internal open val taskBuildCacheableOutputDirectory: DirectoryProperty = objectFactory.directoryProperty()
// avoid creating directory in getter: this can lead to failure in parallel build
@get:LocalState
internal abstract val taskBuildLocalStateDirectory: DirectoryProperty
@get:Internal
internal val buildHistoryFile
get() = taskBuildLocalStateDirectory.file("build-history.bin")
// indicates that task should compile kotlin incrementally if possible
// it's not possible when IncrementalTaskInputs#isIncremental returns false (i.e first build)
// todo: deprecate and remove (we may need to design api for configuring IC)
// don't rely on it to check if IC is enabled, use isIncrementalCompilationEnabled instead
@get:Internal
var incremental: Boolean = false
set(value) {
field = value
logger.kotlinDebug { "Set $this.incremental=$value" }
}
@Input
internal open fun isIncrementalCompilationEnabled(): Boolean =
incremental
@Deprecated("Scheduled for removal with Kotlin 1.9", ReplaceWith("moduleName"))
@get:Input
abstract val ownModuleName: Property<String>
@get:Internal
val startParameters = BuildReportsService.getStartParameters(project)
@get:Internal
internal abstract val suppressKotlinOptionsFreeArgsModificationWarning: Property<Boolean>
internal fun reportingSettings() = buildReportsService.orNull?.parameters?.reportingSettings?.orNull ?: ReportingSettings()
@get:Internal
protected val multiModuleICSettings: MultiModuleICSettings
get() = MultiModuleICSettings(buildHistoryFile.get().asFile, useModuleDetection.get())
/**
* Plugin Data provided by [KpmCompilerPlugin]
*/
@get:Optional
@get:Nested
// TODO: replace with objects.property and introduce task configurator
internal var kotlinPluginData: Provider<KotlinCompilerPluginData>? = null
@get:Internal
internal val javaOutputDir: DirectoryProperty = objectFactory.directoryProperty()
@get:InputFiles
@get:IgnoreEmptyDirectories
@get:Incremental
@get:NormalizeLineEndings
@get:PathSensitive(PathSensitivity.RELATIVE)
internal val commonSourceSet: ConfigurableFileCollection = objectFactory.fileCollection()
@get:Internal
val abiSnapshotFile
get() = taskBuildCacheableOutputDirectory.file(IncrementalCompilerRunner.ABI_SNAPSHOT_FILE_NAME)
@get:Input
val abiSnapshotRelativePath: Property<String> = objectFactory.property(String::class.java).value(
//TODO update to support any jar changes
"$name/${IncrementalCompilerRunner.ABI_SNAPSHOT_FILE_NAME}"
)
@get:Internal
internal val friendSourceSets = objectFactory.listProperty(String::class.java)
private val kotlinLogger by lazy { GradleKotlinLogger(logger) }
@get:Internal
protected val gradleCompileTaskProvider: Provider<GradleCompileTaskProvider> = objectFactory
.property(
objectFactory.newInstance<GradleCompileTaskProvider>(project.gradle, this, project, incrementalModuleInfoProvider)
)
@get:Internal
internal open val defaultKotlinJavaToolchain: Provider<DefaultKotlinJavaToolchain> = objectFactory
.propertyWithNewInstance({ null })
@get:Internal
internal val compilerRunner: Provider<GradleCompilerRunner> =
objectFactory.propertyWithConvention(
gradleCompileTaskProvider.flatMap { taskProvider ->
compilerExecutionStrategy
.zip(metrics) { executionStrategy, metrics ->
metrics to executionStrategy
}
.flatMap { params ->
defaultKotlinJavaToolchain
.map {
val toolsJar = it.currentJvmJdkToolsJar.orNull
GradleCompilerRunnerWithWorkers(
taskProvider,
toolsJar,
CompilerExecutionSettings(
normalizedKotlinDaemonJvmArguments.orNull,
params.second,
useDaemonFallbackStrategy.get()
),
params.first,
workerExecutor
)
}
}
}
)
@get:Internal
internal abstract val preciseCompilationResultsBackup: Property<Boolean>
/** Task outputs that we don't want to include in [TaskOutputsBackup] (see [TaskOutputsBackup.outputsToRestore] for more info). */
@get:Internal
internal abstract val taskOutputsBackupExcludes: SetProperty<File>
@TaskAction
fun execute(inputChanges: InputChanges) {
val buildMetrics = metrics.get()
buildMetrics.addTimeMetric(BuildPerformanceMetric.START_TASK_ACTION_EXECUTION)
buildMetrics.measure(BuildTime.OUT_OF_WORKER_TASK_ACTION) {
KotlinBuildStatsService.applyIfInitialised {
if (name.contains("Test"))
it.report(BooleanMetrics.TESTS_EXECUTED, true)
else
it.report(BooleanMetrics.COMPILATION_STARTED, true)
}
validateCompilerClasspath()
systemPropertiesService.get().startIntercept()
CompilerSystemProperties.KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY.value = "true"
// If task throws exception, but its outputs are changed during execution,
// then Gradle forces next build to be non-incremental (see Gradle's DefaultTaskArtifactStateRepository#persistNewOutputs)
// To prevent this, we backup outputs before incremental build and restore when exception is thrown
val outputsBackup: TaskOutputsBackup? =
if (isIncrementalCompilationEnabled() && inputChanges.isIncremental)
buildMetrics.measure(BuildTime.BACKUP_OUTPUT) {
TaskOutputsBackup(
fileSystemOperations,
layout.buildDirectory,
layout.buildDirectory.dir("snapshot/kotlin/$name"),
outputsToRestore = allOutputFiles() - taskOutputsBackupExcludes.get(),
logger
).also {
it.createSnapshot()
}
}
else null
if (!isIncrementalCompilationEnabled()) {
cleanOutputsAndLocalState("IC is disabled")
} else if (!inputChanges.isIncremental) {
cleanOutputsAndLocalState("Task cannot run incrementally")
}
executeImpl(inputChanges, outputsBackup)
}
buildMetricsService.orNull?.also { it.addTask(path, this.javaClass, buildMetrics) }
}
protected open fun cleanOutputsAndLocalState(reason: String?) {
cleanOutputsAndLocalStateUtil(reason)
}
protected open fun skipCondition(): Boolean = sources.isEmpty
@get:Internal
protected open val incrementalProps: List<FileCollection>
get() = listOfNotNull(
sources,
libraries,
commonSourceSet
)
private fun executeImpl(
inputChanges: InputChanges,
taskOutputsBackup: TaskOutputsBackup?
) {
val allKotlinSources = sources.asFileTree.files
logger.kotlinDebug { "All kotlin sources: ${allKotlinSources.pathsAsStringRelativeTo(gradleCompileTaskProvider.get().projectDir.get())}" }
if (!inputChanges.isIncremental && skipCondition()) {
// Skip running only if non-incremental run. Otherwise, we may need to do some cleanup.
logger.kotlinDebug { "No Kotlin files found, skipping Kotlin compiler task" }
return
}
val args = prepareCompilerArguments()
taskBuildCacheableOutputDirectory.get().asFile.mkdirs()
taskBuildLocalStateDirectory.get().asFile.mkdirs()
callCompilerAsync(
args,
allKotlinSources,
inputChanges,
taskOutputsBackup
)
}
protected fun getChangedFiles(
inputChanges: InputChanges,
incrementalProps: List<FileCollection>
) = if (!inputChanges.isIncremental) {
ChangedFiles.Unknown()
} else {
incrementalProps
.fold(mutableListOf<File>() to mutableListOf<File>()) { (modified, removed), prop ->
inputChanges.getFileChanges(prop).forEach {
when (it.changeType) {
ChangeType.ADDED, ChangeType.MODIFIED -> modified.add(it.file)
ChangeType.REMOVED -> removed.add(it.file)
else -> Unit
}
}
modified to removed
}
.run {
ChangedFiles.Known(first, second)
}
}
/**
* Compiler might be executed asynchronously. Do not do anything requiring end of compilation after this function is called.
* @see [GradleKotlinCompilerWork]
*/
internal abstract fun callCompilerAsync(
args: T,
kotlinSources: Set<File>,
inputChanges: InputChanges,
taskOutputsBackup: TaskOutputsBackup?
)
@get:Internal
internal val abstractKotlinCompileArgumentsContributor by lazy {
AbstractKotlinCompileArgumentsContributor(
KotlinCompileArgumentsProvider(this)
)
}
override fun setupCompilerArgs(args: T, defaultsOnly: Boolean, ignoreClasspathResolutionErrors: Boolean) {
abstractKotlinCompileArgumentsContributor.contributeArguments(
args,
compilerArgumentsConfigurationFlags(defaultsOnly, ignoreClasspathResolutionErrors)
)
if (reportingSettings().buildReportMode == BuildReportMode.VERBOSE) {
args.reportPerf = true
}
}
}
open class KotlinCompileArgumentsProvider<T : AbstractKotlinCompile<out CommonCompilerArguments>>(taskProvider: T) {
val logger: Logger = taskProvider.logger
val isMultiplatform: Boolean = taskProvider.multiPlatformEnabled.get()
private val pluginData = taskProvider.kotlinPluginData?.orNull
val pluginClasspath: FileCollection = listOfNotNull(taskProvider.pluginClasspath, pluginData?.classpath).reduce(FileCollection::plus)
val pluginOptions: CompilerPluginOptions = taskProvider.pluginOptions.toSingleCompilerPluginOptions() + pluginData?.options
}
class KotlinJvmCompilerArgumentsProvider
(taskProvider: KotlinCompile) : KotlinCompileArgumentsProvider<KotlinCompile>(taskProvider) {
val taskName: String = taskProvider.name
val moduleName: String = taskProvider.moduleName.get()
val friendPaths: FileCollection = taskProvider.friendPaths
val compileClasspath: Iterable<File> = taskProvider.libraries
val destinationDir: File = taskProvider.destinationDirectory.get().asFile
internal val compilerOptions: KotlinJvmCompilerOptions = taskProvider.compilerOptions
}
internal inline val <reified T : Task> T.thisTaskProvider: TaskProvider<out T>
get() = checkNotNull(project.locateTask<T>(name))
@CacheableTask @CacheableTask
abstract class KotlinCompile @Inject constructor( abstract class KotlinCompile @Inject constructor(
@@ -913,317 +451,3 @@ abstract class KotlinCompile @Inject constructor(
} }
} }
} }
@CacheableTask
abstract class Kotlin2JsCompile @Inject constructor(
override val compilerOptions: KotlinJsCompilerOptions,
objectFactory: ObjectFactory,
workerExecutor: WorkerExecutor
) : AbstractKotlinCompile<K2JSCompilerArguments>(objectFactory, workerExecutor),
KotlinCompilationTask<KotlinJsCompilerOptions>,
UsesLibraryFilterCachingService,
KotlinJsCompile,
K2MultiplatformCompilationTask {
init {
incremental = true
compilerOptions.verbose.convention(logger.isDebugEnabled)
}
override val kotlinOptions: KotlinJsOptions = KotlinJsOptionsCompat(
{ this },
compilerOptions
)
@get:Input
internal var incrementalJsKlib: Boolean = true
override fun isIncrementalCompilationEnabled(): Boolean {
val freeArgs = enhancedFreeCompilerArgs.get()
return when {
PRODUCE_JS in freeArgs -> false
PRODUCE_UNZIPPED_KLIB in freeArgs -> {
KotlinBuildStatsService.applyIfInitialised {
it.report(BooleanMetrics.JS_KLIB_INCREMENTAL, incrementalJsKlib)
}
incrementalJsKlib
}
PRODUCE_ZIPPED_KLIB in freeArgs -> {
KotlinBuildStatsService.applyIfInitialised {
it.report(BooleanMetrics.JS_KLIB_INCREMENTAL, incrementalJsKlib)
}
incrementalJsKlib
}
else -> incremental
}
}
// Workaround to be able to use default value and change it later based on external input
@get:Internal
internal abstract val defaultDestinationDirectory: DirectoryProperty
@Deprecated("Use destinationDirectory and moduleName instead")
@get:Internal
abstract val outputFileProperty: Property<File>
// Workaround to add additional compiler args based on the exising one
// Currently there is a logic to add additional compiler arguments based on already existing one.
// And it is not possible to update compilerOptions.freeCompilerArgs using some kind of .map
// or .flatMap call - this will cause StackOverlowException as upstream source will be updated
// and .map will be called again.
@get:Input
internal abstract val enhancedFreeCompilerArgs: ListProperty<String>
/**
* Workaround for those "nasty" plugins that are adding 'freeCompilerArgs' on task execution phase.
* With properties api it is not possible to update property value after task configuration is finished.
*
* Marking it as `@Internal` as anyway on the configuration phase, when Gradle does task inputs snapshot,
* this input will always be empty.
*/
@get:Internal
internal var executionTimeFreeCompilerArgs: List<String>? = null
@get:Nested
override val multiplatformStructure: K2MultiplatformStructure = objectFactory.newInstance()
override fun createCompilerArgs(): K2JSCompilerArguments =
K2JSCompilerArguments()
override fun setupCompilerArgs(args: K2JSCompilerArguments, defaultsOnly: Boolean, ignoreClasspathResolutionErrors: Boolean) {
(compilerOptions as KotlinJsCompilerOptionsDefault).fillDefaultValues(args)
super.setupCompilerArgs(args, defaultsOnly = defaultsOnly, ignoreClasspathResolutionErrors = ignoreClasspathResolutionErrors)
if (defaultsOnly) return
(compilerOptions as KotlinJsCompilerOptionsDefault).fillCompilerArguments(args)
if (!args.sourceMapPrefix.isNullOrEmpty()) {
args.sourceMapBaseDirs = sourceMapBaseDir.get().asFile.absolutePath
}
if (isIrBackendEnabled()) {
val outputFilePath: String? = compilerOptions.outputFile.orNull
if (outputFilePath != null) {
val outputFile = File(outputFilePath)
args.outputDir = (if (outputFile.extension == "") outputFile else outputFile.parentFile).normalize().absolutePath
args.moduleName = outputFile.nameWithoutExtension
} else {
args.outputDir = destinationDirectory.get().asFile.normalize().absolutePath
args.moduleName = compilerOptions.moduleName.get()
}
} else {
args.outputFile = outputFileProperty.get().absoluteFile.normalize().absolutePath
}
args.configureMultiplatform(
compilerOptions,
k1CommonSources = commonSourceSet.asFileTree,
k2MultiplatformFragments = multiplatformStructure
)
// Overriding freeArgs from compilerOptions with enhanced one + additional one set on execution phase
// containing additional arguments based on the js compilation configuration
val localExecutionTimeFreeCompilerArgs = executionTimeFreeCompilerArgs
args.freeArgs = if (localExecutionTimeFreeCompilerArgs != null) localExecutionTimeFreeCompilerArgs else enhancedFreeCompilerArgs.get()
}
@get:InputFiles
@get:IgnoreEmptyDirectories
@get:Incremental
@get:Optional
@get:NormalizeLineEndings
@get:PathSensitive(PathSensitivity.RELATIVE)
internal val friendDependencies: FileCollection = objectFactory
.fileCollection()
.from(friendPaths)
.filter {
// .jar files are not required for js compilation as friend modules
// and, because of `@InputFiles` and different normalization strategy from `@Classpath`,
// they produce build cache misses
it.exists() && !it.name.endsWith(".jar") && libraryFilter(it)
}
@get:Internal
internal val sourceMapBaseDir: Property<Directory> = objectFactory
.directoryProperty()
.value(project.layout.projectDirectory)
private fun isHybridKotlinJsLibrary(file: File): Boolean =
JsLibraryUtils.isKotlinJavascriptLibrary(file) && isKotlinLibrary(file)
private val preIrBackendCompilerFlags = listOf(
DISABLE_PRE_IR,
PRODUCE_JS,
PRODUCE_ZIPPED_KLIB
)
private fun isPreIrBackendDisabled(): Boolean = enhancedFreeCompilerArgs
.get()
.any { preIrBackendCompilerFlags.contains(it) }
// see also isIncrementalCompilationEnabled
private val irBackendCompilerFlags = listOf(
PRODUCE_UNZIPPED_KLIB,
PRODUCE_JS,
PRODUCE_ZIPPED_KLIB
)
private fun isIrBackendEnabled(): Boolean = enhancedFreeCompilerArgs
.get()
.any { irBackendCompilerFlags.contains(it) }
private val File.asLibraryFilterCacheKey: LibraryFilterCachingService.LibraryFilterCacheKey
get() = LibraryFilterCachingService.LibraryFilterCacheKey(
this,
irEnabled = isIrBackendEnabled(),
preIrDisabled = isPreIrBackendDisabled()
)
// Kotlin/JS can operate in 3 modes:
// 1) purely pre-IR backend
// 2) purely IR backend
// 3) hybrid pre-IR and IR backend. Can only accept libraries with both JS and IR parts.
private val libraryFilterBody: (File) -> Boolean
get() = if (isIrBackendEnabled()) {
if (isPreIrBackendDisabled()) {
//::isKotlinLibrary
// Workaround for KT-47797
{ isKotlinLibrary(it) }
} else {
::isHybridKotlinJsLibrary
}
} else {
JsLibraryUtils::isKotlinJavascriptLibrary
}
@get:Input
internal val jsLegacyNoWarn: Provider<Boolean> = objectFactory.property(
PropertiesProvider(project).jsCompilerNoWarn
)
@get:Internal
protected val libraryFilter: (File) -> Boolean
get() = { file ->
libraryFilterCacheService.get().getOrCompute(file.asLibraryFilterCacheKey, libraryFilterBody)
}
override val incrementalProps: List<FileCollection>
get() = super.incrementalProps + listOf(friendDependencies)
open fun processArgs(
args: K2JSCompilerArguments
) {
}
override fun callCompilerAsync(
args: K2JSCompilerArguments,
kotlinSources: Set<File>,
inputChanges: InputChanges,
taskOutputsBackup: TaskOutputsBackup?
) {
logger.debug("Calling compiler")
validateOutputDirectory()
if (isIrBackendEnabled()) {
logger.info(USING_JS_IR_BACKEND_MESSAGE)
}
val dependencies = libraries
.filter { it.exists() && libraryFilter(it) }
.map { it.normalize().absolutePath }
args.libraries = dependencies.distinct().let {
if (it.isNotEmpty())
it.joinToString(File.pathSeparator) else
null
}
args.friendModules = friendDependencies.files.joinToString(File.pathSeparator) { it.absolutePath }
if (!isIrBackendEnabled()) {
args.legacyDeprecatedNoWarn = true
args.useDeprecatedLegacyCompiler = true
}
logger.kotlinDebug("compiling with args ${ArgumentUtils.convertArgumentsToStringList(args)}")
val gradlePrintingMessageCollector = GradlePrintingMessageCollector(logger, args.allWarningsAsErrors)
val gradleMessageCollector = GradleErrorMessageCollector(gradlePrintingMessageCollector, kotlinPluginVersion = getKotlinPluginVersion(logger))
val outputItemCollector = OutputItemsCollectorImpl()
val compilerRunner = compilerRunner.get()
val icEnv = if (isIncrementalCompilationEnabled()) {
logger.info(USING_JS_INCREMENTAL_COMPILATION_MESSAGE)
IncrementalCompilationEnvironment(
getChangedFiles(inputChanges, incrementalProps),
ClasspathChanges.NotAvailableForJSCompiler,
taskBuildCacheableOutputDirectory.get().asFile,
multiModuleICSettings = multiModuleICSettings,
preciseCompilationResultsBackup = preciseCompilationResultsBackup.get(),
)
} else null
val environment = GradleCompilerEnvironment(
defaultCompilerClasspath, gradleMessageCollector, outputItemCollector,
outputFiles = allOutputFiles(),
reportingSettings = reportingSettings(),
incrementalCompilationEnvironment = icEnv
)
processArgs(args)
compilerRunner.runJsCompilerAsync(
kotlinSources.toList(),
args,
environment,
taskOutputsBackup
)
compilerRunner.errorsFile?.also { gradleMessageCollector.flush(it) }
}
private val projectRootDir = project.rootDir
private fun validateOutputDirectory() {
val outputFile = outputFileProperty.get()
val outputDir = outputFile.parentFile
if (outputDir.isParentOf(projectRootDir)) {
throw InvalidUserDataException(
"The output directory '$outputDir' (defined by outputFile of ':$name') contains or " +
"matches the project root directory '${projectRootDir}'.\n" +
"Gradle will not be able to build the project because of the root directory lock.\n" +
"To fix this, consider using the default outputFile location instead of providing it explicitly."
)
}
}
}
data class KotlinCompilerPluginData(
@get:Classpath
val classpath: FileCollection,
@get:Internal
val options: CompilerPluginOptions,
/**
* Used only for Up-to-date checks
*/
@get:Nested
val inputsOutputsState: InputsOutputsState
) {
data class InputsOutputsState(
@get:Input
val inputs: Map<String, String>,
@get:InputFiles
@get:IgnoreEmptyDirectories
@get:NormalizeLineEndings
@get:PathSensitive(PathSensitivity.RELATIVE)
val inputFiles: Set<File>,
@get:OutputFiles
val outputFiles: Set<File>
)
}
@@ -0,0 +1,18 @@
/*
* Copyright 2010-2020 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.tasks
import org.gradle.api.Task
import org.gradle.api.tasks.TaskProvider
const val KOTLIN_BUILD_DIR_NAME = "kotlin"
const val USING_JVM_INCREMENTAL_COMPILATION_MESSAGE = "Using Kotlin/JVM incremental compilation"
const val USING_JS_INCREMENTAL_COMPILATION_MESSAGE = "Using Kotlin/JS incremental compilation"
const val USING_JS_IR_BACKEND_MESSAGE = "Using Kotlin/JS IR backend"
internal inline val <reified T : Task> T.thisTaskProvider: TaskProvider<out T>
get() = checkNotNull(project.locateTask<T>(name))