Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.jetbrains.kotlin.gradle.tasks.Tasks.kt Maven / Gradle / Ivy
/*
* 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.GradleException
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.file.*
import org.gradle.api.invocation.Gradle
import org.gradle.api.attributes.Attribute
import org.gradle.api.logging.Logger
import org.gradle.api.model.ObjectFactory
import org.gradle.api.model.ReplacedBy
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.services.BuildService
import org.gradle.api.services.BuildServiceParameters
import org.gradle.api.tasks.*
import org.gradle.api.tasks.compile.AbstractCompile
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.work.ChangeType
import org.gradle.work.Incremental
import org.gradle.work.InputChanges
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.compilerRunner.*
import org.jetbrains.kotlin.compilerRunner.GradleCompilerRunner.Companion.normalizeForFlagFile
import org.jetbrains.kotlin.daemon.common.MultiModuleICSettings
import org.jetbrains.kotlin.gradle.dsl.*
import org.jetbrains.kotlin.gradle.incremental.*
import org.jetbrains.kotlin.gradle.internal.*
import org.jetbrains.kotlin.gradle.internal.tasks.TaskConfigurator
import org.jetbrains.kotlin.gradle.internal.tasks.TaskWithLocalState
import org.jetbrains.kotlin.gradle.internal.tasks.allOutputFiles
import org.jetbrains.kotlin.gradle.internal.transforms.CLASSPATH_ENTRY_SNAPSHOT_FILE_NAME
import org.jetbrains.kotlin.gradle.internal.transforms.ClasspathEntrySnapshotTransform
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.plugin.*
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinCompilationData
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService
import org.jetbrains.kotlin.gradle.report.ReportingSettings
import org.jetbrains.kotlin.gradle.targets.js.ir.isProduceUnzippedKlib
import org.jetbrains.kotlin.gradle.utils.*
import org.jetbrains.kotlin.incremental.ClasspathChanges
import org.jetbrains.kotlin.incremental.ChangedFiles
import org.jetbrains.kotlin.incremental.IncrementalCompilerRunner
import org.jetbrains.kotlin.library.impl.isKotlinLibrary
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.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
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
: AbstractCompile(),
CompilerArgumentAwareWithInput,
TaskWithLocalState {
@ReplacedBy("stableSources")
override fun getSource() = super.getSource()
@get:InputFiles
@get:SkipWhenEmpty
@get:IgnoreEmptyDirectories
@get:PathSensitive(PathSensitivity.RELATIVE)
internal val stableSources: FileCollection = project.files(
{ source }
)
@Incremental
override fun getClasspath(): FileCollection {
return super.getClasspath()
}
@get:Internal
override val metrics: BuildMetricsReporter =
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()
init {
this.doFirst {
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
) {
@get:Internal
val path: Provider = objectFactory.property(task.path)
@get:Internal
val logger: Provider = objectFactory.property(task.logger)
@get:Internal
val buildDir: DirectoryProperty = projectLayout.buildDirectory
@get:Internal
val projectDir: Provider = objectFactory
.property(project.rootProject.projectDir)
@get:Internal
val rootDir: Provider = objectFactory
.property(project.rootProject.rootDir)
@get:Internal
val sessionsDir: Provider = objectFactory
.property(GradleCompilerRunner.sessionsDir(project.rootProject.buildDir))
@get:Internal
val projectName: Provider = objectFactory
.property(project.rootProject.name.normalizeForFlagFile())
@get:Internal
val buildModulesInfo: Provider = objectFactory.property(
/**
* See https://youtrack.jetbrains.com/issue/KT-46820. Build service that holds the incremental info may
* be instantiated during execution phase and there could be multiple threads trying to do that. Because the
* underlying mechanism does not support multi-threaded access, we need to add external synchronization.
*/
synchronized(gradle.sharedServices) {
gradle.sharedServices.registerIfAbsent(
IncrementalModuleInfoBuildService.getServiceName(), IncrementalModuleInfoBuildService::class.java
) {
it.parameters.info.set(
objectFactory.providerWithLazyConvention {
GradleCompilerRunner.buildModulesInfo(gradle)
}
)
}
}
)
}
abstract class AbstractKotlinCompile : AbstractKotlinCompileTool(),
CompileUsingKotlinDaemonWithNormalization {
open class Configurator>(protected val compilation: KotlinCompilationData<*>) : TaskConfigurator {
override fun configure(task: T) {
val project = task.project
task.friendPaths.from(project.provider { compilation.friendPaths })
if (compilation is KotlinCompilation<*>) {
task.friendSourceSets.set(project.provider { compilation.associateWithTransitiveClosure.map { it.name } })
// FIXME support compiler plugins with PM20
task.pluginClasspath.from(project.configurations.getByName(compilation.pluginConfigurationName))
}
task.moduleName.set(project.provider { compilation.moduleName })
task.sourceSetName.set(project.provider { compilation.compilationPurpose })
task.coroutines.value(
project.provider {
project.extensions.findByType(KotlinTopLevelExtension::class.java)!!.experimental.coroutines
?: PropertiesProvider(project).coroutines
?: Coroutines.DEFAULT
}
).disallowChanges()
task.multiPlatformEnabled.value(
project.provider {
project.plugins.any { it is KotlinPlatformPluginBase || it is KotlinMultiplatformPluginWrapper || it is KotlinPm20PluginWrapper }
}
).disallowChanges()
task.taskBuildDirectory.value(getKotlinBuildDir(task)).disallowChanges()
task.localStateDirectories.from(task.taskBuildDirectory).disallowChanges()
PropertiesProvider(task.project).mapKotlinDaemonProperties(task)
}
private fun getKotlinBuildDir(task: T): Provider =
task.project.layout.buildDirectory.dir("$KOTLIN_BUILD_DIR_NAME/${task.name}")
protected open fun getClasspathSnapshotDir(task: T): Provider =
task.project.layout.buildDirectory.dir("$KOTLIN_BUILD_DIR_NAME/classpath-snapshot/${task.name}")
}
init {
cacheOnlyIfEnabledForKotlin()
}
private val layout = project.layout
@get:Internal
protected val objects: ObjectFactory = project.objects
// avoid creating directory in getter: this can lead to failure in parallel build
@get:LocalState
internal val taskBuildDirectory: DirectoryProperty = objects.directoryProperty()
@get:Internal
internal val buildHistoryFile
get() = taskBuildDirectory.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
@get:Internal
internal var reportingSettings = ReportingSettings()
@get:Input
internal val useModuleDetection: Property = objects.property(Boolean::class.java).value(false)
@get:Internal
protected val multiModuleICSettings: MultiModuleICSettings
get() = MultiModuleICSettings(buildHistoryFile.get().asFile, useModuleDetection.get())
@get:Classpath
open val pluginClasspath: ConfigurableFileCollection = objects.fileCollection()
@get:Internal
internal val pluginOptions = CompilerPluginOptions()
@get:Input
val sourceFilesExtensions: ListProperty = objects.listProperty(String::class.java).value(DEFAULT_KOTLIN_SOURCE_FILES_EXTENSIONS)
/**
* Plugin Data provided by [KpmCompilerPlugin]
*/
@get:Optional
@get:Nested
// TODO: replace with objects.property and introduce task configurator
internal var kotlinPluginData: Provider? = null
// Input is needed to force rebuild even if source files are not changed
@get:Input
internal val coroutines: Property = objects.property(Coroutines::class.java)
@get:Internal
internal val javaOutputDir: DirectoryProperty = objects.directoryProperty()
@get:Internal
internal val sourceSetName: Property = objects.property(String::class.java)
@get:InputFiles
@get:IgnoreEmptyDirectories
@get:Incremental
@get:PathSensitive(PathSensitivity.RELATIVE)
internal val commonSourceSet: ConfigurableFileCollection = objects.fileCollection()
@get:Input
internal val moduleName: Property = objects.property(String::class.java)
@get:Internal
val abiSnapshotFile
get() = taskBuildDirectory.file(IncrementalCompilerRunner.ABI_SNAPSHOT_FILE_NAME)
@get:Input
val abiSnapshotRelativePath: Property = objects.property(String::class.java).value(
//TODO update to support any jar changes
"$name/${IncrementalCompilerRunner.ABI_SNAPSHOT_FILE_NAME}"
)
@get:Internal
internal val friendSourceSets = objects.listProperty(String::class.java)
@get:Internal // takes part in the compiler arguments
val friendPaths: ConfigurableFileCollection = objects.fileCollection()
private val kotlinLogger by lazy { GradleKotlinLogger(logger) }
abstract override val kotlinDaemonJvmArguments: ListProperty
@get:Internal
protected val gradleCompileTaskProvider: Provider = objects
.property(
objects.newInstance(project.gradle, this, project)
)
@get:Internal
internal open val compilerRunner: Provider =
objects.propertyWithConvention(
gradleCompileTaskProvider.map {
GradleCompilerRunner(it, null, normalizedKotlinDaemonJvmArguments.orNull)
}
)
private val systemPropertiesService = CompilerSystemPropertiesService.registerIfAbsent(project.gradle)
@TaskAction
fun execute(inputChanges: InputChanges) {
metrics.measure(BuildTime.GRADLE_TASK_ACTION) {
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)
metrics.measure(BuildTime.BACKUP_OUTPUT) {
TaskOutputsBackup(allOutputFiles())
}
else null
if (!isIncrementalCompilationEnabled()) {
clearLocalState("IC is disabled")
} else if (!inputChanges.isIncremental) {
clearLocalState("Task cannot run incrementally")
}
try {
executeImpl(inputChanges)
metrics.measure(BuildTime.CALCULATE_OUTPUT_SIZE) {
metrics.addMetric(
BuildPerformanceMetric.SNAPSHOT_SIZE,
taskBuildDirectory.file("build-history.bin").get().asFile.length() +
taskBuildDirectory.file("last-build.bin").get().asFile.length() +
taskBuildDirectory.file("abi-snapshot.bin").get().asFile.length()
)
metrics.addMetric(BuildPerformanceMetric.OUTPUT_SIZE,
taskBuildDirectory.dir("caches-jvm").get().asFileTree.files.filter { it.isFile }.map { it.length() }
.sum()
)
}
} catch (t: Throwable) {
if (outputsBackup != null) {
metrics.measure(BuildTime.RESTORE_OUTPUT_FROM_BACKUP) {
outputsBackup.restoreOutputs()
}
}
throw t
}
}
}
protected open fun skipCondition(): Boolean =
getSourceRoots().kotlinSourceFiles.isEmpty()
private val projectDir = project.rootProject.projectDir
@get:Internal
protected open val incrementalProps: List
get() = listOfNotNull(
stableSources,
classpath,
commonSourceSet
)
private fun executeImpl(inputChanges: InputChanges) {
val sourceRoots = getSourceRoots()
val allKotlinSources = sourceRoots.kotlinSourceFiles
logger.kotlinDebug { "All kotlin sources: ${allKotlinSources.pathsAsStringRelativeTo(projectDir)}" }
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
}
sourceRoots.log(this.name, logger)
val args = prepareCompilerArguments()
taskBuildDirectory.get().asFile.mkdirs()
callCompilerAsync(
args,
sourceRoots,
getChangedFiles(inputChanges, incrementalProps)
)
}
protected fun getChangedFiles(
inputChanges: InputChanges,
incrementalProps: List
) = if (!inputChanges.isIncremental) {
ChangedFiles.Unknown()
} else {
incrementalProps
.fold(mutableListOf() to mutableListOf()) { (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)
}
}
@Internal
internal abstract fun getSourceRoots(): SourceRoots
/**
* 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, sourceRoots: SourceRoots, changedFiles: ChangedFiles)
@get:Input
internal val multiPlatformEnabled: Property = objects.property(Boolean::class.java)
@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)
)
}
}
open class KotlinCompileArgumentsProvider>(taskProvider: T) {
val coroutines: Provider = taskProvider.coroutines
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 = listOfNotNull(taskProvider.pluginOptions, pluginData?.options).reduce(CompilerPluginOptions::plus)
}
class KotlinJvmCompilerArgumentsProvider
(taskProvider: KotlinCompile) : KotlinCompileArgumentsProvider(taskProvider) {
val moduleName: String = taskProvider.moduleName.get()
val friendPaths: FileCollection = taskProvider.friendPaths
val compileClasspath: Iterable = taskProvider.classpath
val destinationDir: File = taskProvider.destinationDir
internal val kotlinOptions: List = listOfNotNull(
taskProvider.parentKotlinOptionsImpl.orNull as? KotlinJvmOptionsImpl,
taskProvider.kotlinOptions as KotlinJvmOptionsImpl
)
}
internal inline val T.thisTaskProvider: TaskProvider
get() = checkNotNull(project.locateTask(name))
@CacheableTask
abstract class KotlinCompile @Inject constructor(
override val kotlinOptions: KotlinJvmOptions
) : AbstractKotlinCompile(),
KotlinJvmCompile,
UsesKotlinJavaToolchain {
internal open class Configurator(
kotlinCompilation: KotlinCompilationData<*>,
private val properties: PropertiesProvider
) : AbstractKotlinCompile.Configurator(kotlinCompilation) {
companion object {
private const val TRANSFORMS_REGISTERED = "_kgp_internal_kotlin_compile_transforms_registered"
val ARTIFACT_TYPE_ATTRIBUTE: Attribute = Attribute.of("artifactType", String::class.java)
private const val DIRECTORY_ARTIFACT_TYPE = "directory"
private const val JAR_ARTIFACT_TYPE = "jar"
const val CLASSPATH_ENTRY_SNAPSHOT_ARTIFACT_TYPE = "classpath-entry-snapshot"
}
/**
* Prepares for configuration of the task. This method must be called during build configuration, not during task configuration
* (which typically happens after build configuration). The reason is that some actions must be performed early (e.g., creating
* configurations should be done early to avoid issues with composite builds (https://issuetracker.google.com/183952598)).
*/
fun runAtConfigurationTime(taskProvider: TaskProvider, project: Project) {
if (properties.useClasspathSnapshot) {
registerTransformsOnce(project)
project.configurations.create(classpathSnapshotConfigurationName(taskProvider.name)).apply {
project.dependencies.add(name, project.files(project.provider { taskProvider.get().classpath }))
}
}
}
private fun registerTransformsOnce(project: Project) {
if (project.extensions.extraProperties.has(TRANSFORMS_REGISTERED)) {
return
}
project.extensions.extraProperties[TRANSFORMS_REGISTERED] = true
project.dependencies.registerTransform(ClasspathEntrySnapshotTransform::class.java) {
it.from.attribute(ARTIFACT_TYPE_ATTRIBUTE, JAR_ARTIFACT_TYPE)
it.to.attribute(ARTIFACT_TYPE_ATTRIBUTE, CLASSPATH_ENTRY_SNAPSHOT_ARTIFACT_TYPE)
}
project.dependencies.registerTransform(ClasspathEntrySnapshotTransform::class.java) {
it.from.attribute(ARTIFACT_TYPE_ATTRIBUTE, DIRECTORY_ARTIFACT_TYPE)
it.to.attribute(ARTIFACT_TYPE_ATTRIBUTE, CLASSPATH_ENTRY_SNAPSHOT_ARTIFACT_TYPE)
}
}
private fun classpathSnapshotConfigurationName(taskName: String) = "_kgp_internal_${taskName}_classpath_snapshot"
override fun configure(task: T) {
super.configure(task)
val compileJavaTaskProvider = when (compilation) {
is KotlinJvmCompilation -> compilation.compileJavaTaskProvider
is KotlinJvmAndroidCompilation -> compilation.compileJavaTaskProvider
is KotlinWithJavaCompilation -> compilation.compileJavaTaskProvider
else -> null
}
if (compileJavaTaskProvider != null) {
task.associatedJavaCompileTaskTargetCompatibility.set(
compileJavaTaskProvider.map { it.targetCompatibility }
)
task.associatedJavaCompileTaskSources.from(
compileJavaTaskProvider.map { javaTask ->
javaTask.source
}
)
task.associatedJavaCompileTaskName.set(
compileJavaTaskProvider.map { it.name }
)
}
task.moduleName.set(task.project.provider {
task.kotlinOptions.moduleName ?: task.parentKotlinOptionsImpl.orNull?.moduleName ?: compilation.moduleName
})
if (properties.useClasspathSnapshot) {
val classpathSnapshot = task.project.configurations.getByName(classpathSnapshotConfigurationName(task.name))
task.classpathSnapshotProperties.classpathSnapshot.from(
classpathSnapshot.incoming.artifactView {
it.attributes.attribute(ARTIFACT_TYPE_ATTRIBUTE, CLASSPATH_ENTRY_SNAPSHOT_ARTIFACT_TYPE)
}.files
)
task.classpathSnapshotProperties.classpathSnapshotDir.value(getClasspathSnapshotDir(task)).disallowChanges()
}
}
}
@get:Internal
internal val parentKotlinOptionsImpl: Property = objects.property(KotlinJvmOptions::class.java)
@get:Internal
@field:Transient
internal open val sourceRootsContainer = FilteringSourceRootsContainer(objects)
private val jvmSourceRoots by project.provider {
// serialize in the task state for configuration caching; avoid building anew in task execution, as it may access the project model
SourceRoots.ForJvm.create(source, sourceRootsContainer, sourceFilesExtensions.get())
}
/** 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:Nested
abstract val classpathSnapshotProperties: ClasspathSnapshotProperties
/** Properties related to the `kotlin.incremental.useClasspathSnapshot` feature. */
abstract class ClasspathSnapshotProperties {
@get:Input
abstract val useClasspathSnapshot: Property
@get:Classpath
@get:Incremental
@get:Optional // Set if useClasspathSnapshot == true
abstract val classpathSnapshot: ConfigurableFileCollection
@get:OutputDirectory
@get:Optional // Set if useClasspathSnapshot == true
abstract val classpathSnapshotDir: DirectoryProperty
}
@get:Internal
internal val defaultKotlinJavaToolchain: Provider = objects
.propertyWithNewInstance(
project.gradle,
{ this }
)
final override val kotlinJavaToolchainProvider: Provider = defaultKotlinJavaToolchain.cast()
@get:Internal
override val compilerRunner: Provider = objects.propertyWithConvention(
// From Gradle 6.6 better to replace flatMap with provider.zip()
defaultKotlinJavaToolchain.flatMap { toolchain ->
objects.property(gradleCompileTaskProvider.map {
GradleCompilerRunner(
it,
toolchain.currentJvmJdkToolsJar.orNull,
normalizedKotlinDaemonJvmArguments.orNull
)
})
}
)
@get:Internal
internal abstract val associatedJavaCompileTaskTargetCompatibility: Property
@get:Internal
internal abstract val associatedJavaCompileTaskSources: ConfigurableFileCollection
@get:Internal
internal abstract val associatedJavaCompileTaskName: Property
@get:Internal
internal abstract val jvmTargetValidationMode: Property
init {
incremental = true
}
override fun createCompilerArgs(): K2JVMCompilerArguments =
K2JVMCompilerArguments()
override fun setupCompilerArgs(args: K2JVMCompilerArguments, defaultsOnly: Boolean, ignoreClasspathResolutionErrors: Boolean) {
compilerArgumentsContributor.contributeArguments(
args, compilerArgumentsConfigurationFlags(
defaultsOnly,
ignoreClasspathResolutionErrors
)
)
// This method could be called on configuration phase to calculate `filteredArgumentsMap` property
if (state.executing) {
defaultKotlinJavaToolchain.get().updateJvmTarget(this, args)
}
}
@get:Internal
internal val compilerArgumentsContributor: CompilerArgumentsContributor by lazy {
KotlinJvmCompilerArgumentsContributor(KotlinJvmCompilerArgumentsProvider(this))
}
override val incrementalProps: List
get() = super.incrementalProps + listOf(classpathSnapshotProperties.classpathSnapshot)
override fun getSourceRoots(): SourceRoots.ForJvm = jvmSourceRoots
override fun callCompilerAsync(args: K2JVMCompilerArguments, sourceRoots: SourceRoots, changedFiles: ChangedFiles) {
sourceRoots as SourceRoots.ForJvm
validateKotlinAndJavaHasSameTargetCompatibility(args)
val messageCollector = GradlePrintingMessageCollector(logger, args.allWarningsAsErrors)
val outputItemCollector = OutputItemsCollectorImpl()
val compilerRunner = compilerRunner.get()
val icEnv = if (isIncrementalCompilationEnabled()) {
val classpathChanges = when {
!classpathSnapshotProperties.useClasspathSnapshot.get() -> ClasspathChanges.NotAvailable.ClasspathSnapshotIsDisabled
else -> when (changedFiles) {
is ChangedFiles.Known -> getClasspathChanges()
is ChangedFiles.Unknown -> ClasspathChanges.NotAvailable.ForNonIncrementalRun
is ChangedFiles.Dependencies -> error("Unexpected type: ${changedFiles.javaClass.name}")
}
}
logger.info(USING_JVM_INCREMENTAL_COMPILATION_MESSAGE)
IncrementalCompilationEnvironment(
changedFiles = changedFiles,
classpathChanges = classpathChanges,
workingDir = taskBuildDirectory.get().asFile,
usePreciseJavaTracking = usePreciseJavaTracking,
disableMultiModuleIC = disableMultiModuleIC,
multiModuleICSettings = multiModuleICSettings
)
} else null
val environment = GradleCompilerEnvironment(
defaultCompilerClasspath, messageCollector, outputItemCollector,
outputFiles = allOutputFiles(),
reportingSettings = reportingSettings,
incrementalCompilationEnvironment = icEnv,
kotlinScriptExtensions = sourceFilesExtensions.get().toTypedArray()
)
compilerRunner.runJvmCompilerAsync(
sourceRoots.kotlinSourceFiles.files.toList(),
commonSourceSet.toList(),
sourceRoots.javaSourceRoots,
javaPackagePrefix,
args,
environment,
defaultKotlinJavaToolchain.get().providedJvm.get().javaHome
)
with(classpathSnapshotProperties) {
if (isIncrementalCompilationEnabled() && useClasspathSnapshot.get()) {
copyClasspathSnapshotFilesToDir(classpathSnapshot.files.toList(), classpathSnapshotDir.get().asFile)
}
}
}
private fun validateKotlinAndJavaHasSameTargetCompatibility(args: K2JVMCompilerArguments) {
if (!associatedJavaCompileTaskSources.isEmpty) {
associatedJavaCompileTaskTargetCompatibility.orNull?.let { targetCompatibility ->
val normalizedJavaTarget = when (targetCompatibility) {
"6" -> "1.6"
"7" -> "1.7"
"8" -> "1.8"
"1.9" -> "9"
else -> targetCompatibility
}
if (normalizedJavaTarget != args.jvmTarget) {
val javaTaskName = associatedJavaCompileTaskName.get()
val errorMessage = "'$javaTaskName' task (current target is $targetCompatibility) and " +
"'$name' task (current target is ${args.jvmTarget}) " +
"jvm target compatibility should be set to the same Java version."
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.destinationDir)
) {
illegalTaskOrNull = illegalTaskOrNull ?: it
}
}
if (illegalTaskOrNull != null) {
val illegalTask = illegalTaskOrNull!!
logger.info(
"Kotlin inter-project IC is disabled: " +
"unknown task '$illegalTask' destination dir ${illegalTask.destinationDir} " +
"intersects with java destination dir $javaOutputDir"
)
}
illegalTaskOrNull != null
}
}
// override setSource to track source directory sets and files (for generated android folders)
override fun setSource(sources: Any) {
sourceRootsContainer.set(sources)
super.setSource(sources)
}
// override source to track source directory sets and files (for generated android folders)
override fun source(vararg sources: Any): SourceTask {
sourceRootsContainer.add(*sources)
return super.source(*sources)
}
private fun getClasspathChanges(): ClasspathChanges {
val currentSnapshotFiles = classpathSnapshotProperties.classpathSnapshot.files.toList()
val previousSnapshotFiles = getClasspathSnapshotFilesInDir(classpathSnapshotProperties.classpathSnapshotDir.get().asFile)
val currentSnapshot = ClasspathSnapshotSerializer.load(currentSnapshotFiles)
val previousSnapshot = ClasspathSnapshotSerializer.load(previousSnapshotFiles)
return ClasspathChangesComputer.getChanges(currentSnapshot, previousSnapshot)
}
/**
* Copies classpath snapshot files to the given directory.
*
* To preserve their order, we put them in subdirectories with names being their indices in the original list, as shown below:
* classpathSnapshotDir/0/snapshotFileName
* classpathSnapshotDir/1/snapshotFileName
* ...
* classpathSnapshotDir/N-1/snapshotFileName
*/
private fun copyClasspathSnapshotFilesToDir(classpathSnapshotFiles: List, classpathSnapshotDir: File) {
classpathSnapshotDir.deleteRecursively()
classpathSnapshotDir.mkdirs()
for ((index, file) in classpathSnapshotFiles.withIndex()) {
file.copyTo(File("$classpathSnapshotDir/$index/$CLASSPATH_ENTRY_SNAPSHOT_FILE_NAME"), overwrite = true)
}
}
/**
* Returns all classpath snapshot files in the given directory, sorted by their original indices (the subdirectories' names).
*
* See [copyClasspathSnapshotFilesToDir] for the structure of the classpath snapshot directory.
*/
private fun getClasspathSnapshotFilesInDir(classpathSnapshotDir: File): List {
val subDirs = classpathSnapshotDir.listFiles() ?: return emptyList()
return subDirs.toList().sortedBy { it.name.toInt() }.map { File(it, CLASSPATH_ENTRY_SNAPSHOT_FILE_NAME) }
}
}
@CacheableTask
internal abstract class KotlinCompileWithWorkers @Inject constructor(
kotlinOptions: KotlinJvmOptions,
workerExecutor: WorkerExecutor
) : KotlinCompile(kotlinOptions) {
override val compilerRunner: Provider =
defaultKotlinJavaToolchain.flatMap { toolchain ->
objects.property(gradleCompileTaskProvider.map {
GradleCompilerRunnerWithWorkers(
it,
toolchain.currentJvmJdkToolsJar.orNull,
normalizedKotlinDaemonJvmArguments.orNull,
workerExecutor
) as GradleCompilerRunner
})
}
}
@CacheableTask
internal abstract class Kotlin2JsCompileWithWorkers @Inject constructor(
kotlinOptions: KotlinJsOptions,
objectFactory: ObjectFactory,
workerExecutor: WorkerExecutor
) : Kotlin2JsCompile(kotlinOptions, objectFactory) {
override val compilerRunner: Provider =
objects.propertyWithConvention(
gradleCompileTaskProvider.map {
GradleCompilerRunnerWithWorkers(
it,
null,
normalizedKotlinDaemonJvmArguments.orNull,
workerExecutor
) as GradleCompilerRunner
}
)
}
@CacheableTask
internal abstract class KotlinCompileCommonWithWorkers @Inject constructor(
kotlinOptions: KotlinMultiplatformCommonOptions,
workerExecutor: WorkerExecutor
) : KotlinCompileCommon(kotlinOptions) {
override val compilerRunner: Provider =
objects.propertyWithConvention(
gradleCompileTaskProvider.map {
GradleCompilerRunnerWithWorkers(
it,
null,
normalizedKotlinDaemonJvmArguments.orNull,
workerExecutor
) as GradleCompilerRunner
}
)
}
@CacheableTask
abstract class Kotlin2JsCompile @Inject constructor(
override val kotlinOptions: KotlinJsOptions,
objectFactory: ObjectFactory
) : AbstractKotlinCompile(), KotlinJsCompile {
init {
incremental = true
}
open class Configurator(compilation: KotlinCompilationData<*>) :
AbstractKotlinCompile.Configurator(compilation) {
override fun configure(task: T) {
super.configure(task)
task.outputFileProperty.value(
task.project.provider {
task.kotlinOptions.outputFile?.let(::File)
?: task.destinationDirectory.locationOnly.get().asFile.resolve("${compilation.ownModuleName}.js")
}
).disallowChanges()
task.optionalOutputFile.fileProvider(
task.outputFileProperty.flatMap { outputFile ->
task.project.provider {
outputFile.takeUnless { task.kotlinOptions.isProduceUnzippedKlib() }
}
}
).disallowChanges()
val libraryCacheService = task.project.rootProject.gradle.sharedServices.registerIfAbsent(
"${LibraryFilterCachingService::class.java.canonicalName}_${LibraryFilterCachingService::class.java.classLoader.hashCode()}",
LibraryFilterCachingService::class.java
) {}
task.libraryCache.set(libraryCacheService).also { task.libraryCache.disallowChanges() }
}
}
internal abstract class LibraryFilterCachingService : BuildService, AutoCloseable {
internal data class LibraryFilterCacheKey(val dependency: File, val irEnabled: Boolean, val preIrDisabled: Boolean)
private val cache = ConcurrentHashMap()
fun getOrCompute(key: LibraryFilterCacheKey, compute: (File) -> Boolean) = cache.computeIfAbsent(key) {
compute(it.dependency)
}
override fun close() {
cache.clear()
}
}
@get:Input
internal var incrementalJsKlib: Boolean = true
override fun isIncrementalCompilationEnabled(): Boolean =
when {
"-Xir-produce-js" in kotlinOptions.freeCompilerArgs -> {
false
}
"-Xir-produce-klib-dir" in kotlinOptions.freeCompilerArgs -> {
KotlinBuildStatsService.applyIfInitialised {
it.report(BooleanMetrics.JS_KLIB_INCREMENTAL, incrementalJsKlib)
}
incrementalJsKlib
}
"-Xir-produce-klib-file" in kotlinOptions.freeCompilerArgs -> {
KotlinBuildStatsService.applyIfInitialised {
it.report(BooleanMetrics.JS_KLIB_INCREMENTAL, incrementalJsKlib)
}
incrementalJsKlib
}
else -> incremental
}
// This can be file or directory
@get:Internal
abstract val outputFileProperty: Property
@Deprecated("Please use outputFileProperty, this is kept for backwards compatibility.", replaceWith = ReplaceWith("outputFileProperty"))
@get:Internal
val outputFile: File
get() = outputFileProperty.get()
@get:OutputFile
@get:Optional
abstract val optionalOutputFile: RegularFileProperty
override fun createCompilerArgs(): K2JSCompilerArguments =
K2JSCompilerArguments()
override fun setupCompilerArgs(args: K2JSCompilerArguments, defaultsOnly: Boolean, ignoreClasspathResolutionErrors: Boolean) {
args.apply { fillDefaultValues() }
super.setupCompilerArgs(args, defaultsOnly = defaultsOnly, ignoreClasspathResolutionErrors = ignoreClasspathResolutionErrors)
try {
outputFileProperty.get().canonicalPath
} catch (ex: Throwable) {
logger.warn("IO EXCEPTION: outputFile: ${outputFileProperty.get().path}")
throw ex
}
args.outputFile = outputFileProperty.get().absoluteFile.normalize().absolutePath
if (defaultsOnly) return
(kotlinOptions as KotlinJsOptionsImpl).updateArguments(args)
}
override fun getSourceRoots() = SourceRoots.KotlinOnly.create(getSource(), sourceFilesExtensions.get())
@get:InputFiles
@get:IgnoreEmptyDirectories
@get:Incremental
@get:Optional
@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)
}
@Suppress("unused")
@get:InputFiles
@get:IgnoreEmptyDirectories
@get:Optional
@get:PathSensitive(PathSensitivity.RELATIVE)
internal val sourceMapBaseDirs: FileCollection?
get() = (kotlinOptions as KotlinJsOptionsImpl).sourceMapBaseDirs
private fun isHybridKotlinJsLibrary(file: File): Boolean =
JsLibraryUtils.isKotlinJavascriptLibrary(file) && isKotlinLibrary(file)
private fun KotlinJsOptions.isPreIrBackendDisabled(): Boolean =
listOf(
"-Xir-only",
"-Xir-produce-js",
"-Xir-produce-klib-file"
).any(freeCompilerArgs::contains)
// see also isIncrementalCompilationEnabled
private fun KotlinJsOptions.isIrBackendEnabled(): Boolean =
listOf(
"-Xir-produce-klib-dir",
"-Xir-produce-js",
"-Xir-produce-klib-file"
).any(freeCompilerArgs::contains)
private val File.asLibraryFilterCacheKey: LibraryFilterCachingService.LibraryFilterCacheKey
get() = LibraryFilterCachingService.LibraryFilterCacheKey(
this,
irEnabled = kotlinOptions.isIrBackendEnabled(),
preIrDisabled = kotlinOptions.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 (kotlinOptions.isIrBackendEnabled()) {
if (kotlinOptions.isPreIrBackendDisabled()) {
//::isKotlinLibrary
// Workaround for KT-47797
{ isKotlinLibrary(it) }
} else {
::isHybridKotlinJsLibrary
}
} else {
JsLibraryUtils::isKotlinJavascriptLibrary
}
@get:Internal
internal abstract val libraryCache: Property
@get:Internal
protected val libraryFilter: (File) -> Boolean
get() = { file ->
libraryCache.get().getOrCompute(file.asLibraryFilterCacheKey, libraryFilterBody)
}
@get:Internal
internal val absolutePathProvider = project.projectDir.absolutePath
override val incrementalProps: List
get() = super.incrementalProps + listOf(friendDependencies)
override fun callCompilerAsync(args: K2JSCompilerArguments, sourceRoots: SourceRoots, changedFiles: ChangedFiles) {
sourceRoots as SourceRoots.KotlinOnly
logger.debug("Calling compiler")
destinationDir.mkdirs()
if (kotlinOptions.isIrBackendEnabled()) {
logger.info(USING_JS_IR_BACKEND_MESSAGE)
}
val dependencies = classpath
.filter { it.exists() && libraryFilter(it) }
.map { it.canonicalPath }
args.libraries = dependencies.distinct().let {
if (it.isNotEmpty())
it.joinToString(File.pathSeparator) else
null
}
args.friendModules = friendDependencies.files.joinToString(File.pathSeparator) { it.absolutePath }
if (args.sourceMapBaseDirs == null && !args.sourceMapPrefix.isNullOrEmpty()) {
args.sourceMapBaseDirs = absolutePathProvider
}
logger.kotlinDebug("compiling with args ${ArgumentUtils.convertArgumentsToStringList(args)}")
val messageCollector = GradlePrintingMessageCollector(logger, args.allWarningsAsErrors)
val outputItemCollector = OutputItemsCollectorImpl()
val compilerRunner = compilerRunner.get()
val icEnv = if (isIncrementalCompilationEnabled()) {
logger.info(USING_JS_INCREMENTAL_COMPILATION_MESSAGE)
IncrementalCompilationEnvironment(
changedFiles,
ClasspathChanges.NotAvailable.ForJSCompiler,
taskBuildDirectory.get().asFile,
multiModuleICSettings = multiModuleICSettings
)
} else null
val environment = GradleCompilerEnvironment(
defaultCompilerClasspath, messageCollector, outputItemCollector,
outputFiles = allOutputFiles(),
reportingSettings = reportingSettings,
incrementalCompilationEnvironment = icEnv
)
compilerRunner.runJsCompilerAsync(
sourceRoots.kotlinSourceFiles.files.toList(),
commonSourceSet.toList(),
args,
environment
)
}
}
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,
@get:InputFiles
@get:IgnoreEmptyDirectories
@get:PathSensitive(PathSensitivity.RELATIVE)
val inputFiles: Set,
@get:OutputFiles
val outputFiles: Set
)
}