KT-33908 Make Kotlin Gradle plugin compatible with configuration cache

#KT-33908 Fixed
This commit is contained in:
Nataliya Valtman
2019-10-30 13:22:59 +03:00
committed by nataliya.valtman
parent ea57b4cccb
commit a766369e72
62 changed files with 1629 additions and 824 deletions
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6-20200603220033+0000-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
@@ -726,7 +726,7 @@ fun getSomething() = 10
project.build("assemble", options = options) {
assertFailed()
assertContains("Class 'User' is not abstract and does not implement abstract member public abstract fun writeToParcel")
assertContainsRegex("Class 'User' is not abstract and does not implement abstract member public abstract fun (writeToParcel|describeContents)".toRegex())
}
File(project.projectDir, "app/build.gradle").modify { it.replace("[\"views\"]", "[\"parcelize\", \"views\"]") }
@@ -13,7 +13,6 @@ import org.jetbrains.kotlin.gradle.model.ModelContainer
import org.jetbrains.kotlin.gradle.model.ModelFetcherBuildAction
import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType
import org.jetbrains.kotlin.gradle.util.*
import org.jetbrains.kotlin.test.util.trimTrailingWhitespaces
import org.junit.After
import org.junit.AfterClass
import org.junit.Assert
@@ -22,6 +21,8 @@ import java.io.File
import java.util.regex.Pattern
import kotlin.test.*
import org.jetbrains.kotlin.test.util.trimTrailingWhitespaces
val SYSTEM_LINE_SEPARATOR: String = System.getProperty("line.separator")
abstract class BaseGradleIT {
@@ -205,9 +206,15 @@ abstract class BaseGradleIT {
val withBuildCache: Boolean = false,
val kaptOptions: KaptOptions? = null,
val parallelTasksInProject: Boolean? = null,
val jsCompilerType: KotlinJsCompilerType? = null
val jsCompilerType: KotlinJsCompilerType? = null,
val configurationCache: Boolean = false,
val configurationCacheProblems: ConfigurationCacheProblems = ConfigurationCacheProblems.FAIL
)
enum class ConfigurationCacheProblems {
FAIL, WARN
}
data class KaptOptions(
val verbose: Boolean,
val useWorkers: Boolean,
@@ -707,9 +714,9 @@ Finished executing task ':$taskName'|
val xmlString = buildString {
appendln("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
appendln("<results>")
files.forEach {
files.forEach { file ->
appendln(
it.readText()
file.readText()
.trimTrailingWhitespaces()
.replace(projectDir.absolutePath, "/\$PROJECT_DIR$")
.replace(projectDir.name, "\$PROJECT_NAME$")
@@ -801,6 +808,9 @@ Finished executing task ':$taskName'|
add("-Pkotlin.js.compiler=$it")
}
add("-Dorg.gradle.unsafe.configuration-cache=${options.configurationCache}")
add("-Dorg.gradle.unsafe.configuration-cache-problems=${options.configurationCacheProblems.name.toLowerCase()}")
// Workaround: override a console type set in the user machine gradle.properties (since Gradle 4.3):
add("--console=plain")
addAll(options.freeCommandLineArgs)
@@ -0,0 +1,73 @@
/*
* 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
import org.jetbrains.kotlin.gradle.util.AGPVersion
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.junit.Test
class ConfigurationCacheForAndroidIT : AbstractConfigurationCacheIT() {
private val androidGradlePluginVersion: AGPVersion
get() = AGPVersion.v4_2_0
override fun defaultBuildOptions() =
super.defaultBuildOptions().copy(
androidHome = KotlinTestUtils.findAndroidSdk(),
androidGradlePluginVersion = androidGradlePluginVersion,
configurationCache = true,
/* AGP causes a configuration cache problem:
- plugin 'com.android.internal.application': registration of listener on 'TaskExecutionGraph.addTaskExecutionListener' is unsupported
which causes tests to fail when configuration-cache-problems=fail is used.
However, everything works fine with WARN level reporting.
TODO: switch to FAIL when AGP no longer causes the cache problem
*/
configurationCacheProblems = ConfigurationCacheProblems.WARN
)
@Test
fun testAndroidKaptProject() = with(Project("android-dagger", directoryPrefix = "kapt2")) {
applyAndroid40Alpha4KotlinVersionWorkaround()
projectDir.resolve("gradle.properties").appendText("\nkapt.incremental.apt=false")
testConfigurationCacheOf(":app:compileDebugKotlin", ":app:kaptDebugKotlin", ":app:kaptGenerateStubsDebugKotlin")
}
@Test
fun testKotlinAndroidProject() = with(Project("AndroidProject")) {
applyAndroid40Alpha4KotlinVersionWorkaround()
testConfigurationCacheOf(":Lib:compileFlavor1DebugKotlin", ":Android:compileFlavor1DebugKotlin")
}
/**
* Android Gradle plugin 4.0-alpha4 depends on the EAP versions of some o.j.k modules.
* Force the current Kotlin version, so the EAP versions are not queried from the
* test project's repositories, where there's no 'kotlin-eap' repo.
* TODO remove this workaround once an Android Gradle plugin version is used that depends on the stable Kotlin version
*/
private fun Project.applyAndroid40Alpha4KotlinVersionWorkaround() {
setupWorkingDir()
val resolutionStrategyHack = """
configurations.all {
resolutionStrategy.dependencySubstitution.all { dependency ->
def requested = dependency.requested
if (requested instanceof ModuleComponentSelector && requested.group == 'org.jetbrains.kotlin') {
dependency.useTarget requested.group + ':' + requested.module + ':' + '${defaultBuildOptions().kotlinVersion}'
}
}
}
""".trimIndent()
gradleBuildScript().appendText(
"\n" + """
buildscript {
$resolutionStrategyHack
}
$resolutionStrategyHack
""".trimIndent()
)
}
}
@@ -0,0 +1,118 @@
/*
* 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
import org.jetbrains.kotlin.gradle.util.findFileByName
import org.junit.Test
import java.io.File
import java.net.URI
import java.util.Arrays.asList
import kotlin.test.fail
class ConfigurationCacheIT : AbstractConfigurationCacheIT() {
@Test
fun testSimpleKotlinJvmProject() = with(Project("kotlinProject")) {
testConfigurationCacheOf(":compileKotlin")
}
@Test
fun testIncrementalKaptProject() = with(Project("kaptIncrementalCompilationProject")) {
setupIncrementalAptProject("AGGREGATING")
testConfigurationCacheOf(
":compileKotlin",
":kaptKotlin",
buildOptions = defaultBuildOptions().copy(
incremental = true,
kaptOptions = KaptOptions(
verbose = true,
useWorkers = true,
incrementalKapt = true,
includeCompileClasspath = false
)
)
)
}
@Test
fun testInstantExecution() = with(Project("instantExecution")) {
testConfigurationCacheOf("assemble", executedTaskNames = asList(":lib-project:compileKotlin"))
}
@Test
fun testInstantExecutionForJs() = with(Project("instantExecutionToJs")) {
testConfigurationCacheOf("assemble", executedTaskNames = asList(":compileKotlin2Js"))
}
}
abstract class AbstractConfigurationCacheIT : BaseGradleIT() {
override fun defaultBuildOptions() =
super.defaultBuildOptions().copy(configurationCache = true)
override val defaultGradleVersion: GradleVersionRequired = GradleVersionRequired.AtLeast("6.6-rc-3")
protected fun Project.testConfigurationCacheOf(
vararg taskNames: String,
executedTaskNames: List<String>? = null,
buildOptions: BuildOptions = defaultBuildOptions()
) {
// First, run a build that serializes the tasks state for instant execution in further builds
val executedTask: List<String> = executedTaskNames ?: taskNames.toList()
build(*taskNames, options = buildOptions) {
assertSuccessful()
assertTasksExecuted(executedTask)
assertContains("Calculating task graph as no configuration cache is available for tasks: ${taskNames.joinToString(separator = " ")}")
checkInstantExecutionSucceeded()
}
build("clean") {
assertSuccessful()
}
// Then run a build where tasks states are deserialized to check that they work correctly in this mode
build(*taskNames, options = buildOptions) {
assertSuccessful()
assertTasksExecuted(executedTask)
assertContains("Reusing configuration cache.")
}
build(*taskNames, options = buildOptions) {
assertSuccessful()
assertTasksUpToDate(executedTask)
}
}
private fun Project.checkInstantExecutionSucceeded() {
instantExecutionReportFile()?.let { htmlReportFile ->
fail("Instant execution problems were found, check ${htmlReportFile.asClickableFileUrl()} for details.")
}
}
/**
* Copies all files from the directory containing the given [htmlReportFile] to a
* fresh temp dir and returns a reference to the copied [htmlReportFile] in the new
* directory.
*/
private fun copyReportToTempDir(htmlReportFile: File): File =
createTempDir().let { tempDir ->
htmlReportFile.parentFile.copyRecursively(tempDir)
tempDir.resolve(htmlReportFile.name)
}
/**
* The instant execution report file, if exists, indicates problems were
* found while caching the task graph.
*/
private fun Project.instantExecutionReportFile() = projectDir
.resolve("configuration-cache")
.findFileByName("configuration-cache-report.html")
?.let { copyReportToTempDir(it) }
private fun File.asClickableFileUrl(): String =
URI("file", "", toURI().path, null, null).toString()
}
@@ -1,149 +0,0 @@
/*
* Copyright 2010-2019 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
import org.jetbrains.kotlin.gradle.util.AGPVersion
import org.jetbrains.kotlin.gradle.util.findFileByName
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.junit.Test
import java.io.File
import java.net.URI
import kotlin.test.fail
class InstantExecutionIT : BaseGradleIT() {
private val androidGradlePluginVersion: AGPVersion
get() = AGPVersion.v4_0_ALPHA_8
override fun defaultBuildOptions() =
super.defaultBuildOptions().copy(
androidHome = KotlinTestUtils.findAndroidSdk(),
androidGradlePluginVersion = androidGradlePluginVersion
)
private val minimumGradleVersion = GradleVersionRequired.AtLeast("6.1-rc-3")
@Test
fun testSimpleKotlinJvmProject() = with(Project("kotlinProject", minimumGradleVersion)) {
testInstantExecutionOf(":compileKotlin")
}
@Test
fun testSimpleKotlinAndroidProject() = with(Project("android-dagger", minimumGradleVersion, "kapt2")) {
applyAndroid40Alpha4KotlinVersionWorkaround()
projectDir.resolve("gradle.properties").appendText("\nkapt.incremental.apt=false")
testInstantExecutionOf(":app:compileDebugKotlin", ":app:kaptDebugKotlin", ":app:kaptGenerateStubsDebugKotlin")
}
@Test
fun testIncrementalKaptProject() = with(getIncrementalKaptProject()) {
testInstantExecutionOf(
":compileKotlin",
":kaptKotlin",
buildOptions = defaultBuildOptions().copy(
incremental = true,
kaptOptions = KaptOptions(
verbose = true,
useWorkers = true,
incrementalKapt = true,
includeCompileClasspath = false
)
)
)
}
private fun getIncrementalKaptProject() =
Project("kaptIncrementalCompilationProject", minimumGradleVersion).apply {
setupIncrementalAptProject("AGGREGATING")
}
private fun Project.testInstantExecutionOf(vararg taskNames: String, buildOptions: BuildOptions = defaultBuildOptions()) {
// First, run a build that serializes the tasks state for instant execution in further builds
instantExecutionOf(*taskNames, buildOptions = buildOptions) {
assertSuccessful()
assertTasksExecuted(*taskNames)
checkInstantExecutionSucceeded()
}
build("clean") {
assertSuccessful()
}
// Then run a build where tasks states are deserialized to check that they work correctly in this mode
instantExecutionOf(*taskNames, buildOptions = buildOptions) {
assertSuccessful()
assertTasksExecuted(*taskNames)
}
instantExecutionOf(*taskNames, buildOptions = buildOptions) {
assertSuccessful()
assertTasksUpToDate(*taskNames)
}
}
private fun Project.checkInstantExecutionSucceeded() {
instantExecutionReportFile()?.let { htmlReportFile ->
fail("Instant execution problems were found, check ${htmlReportFile.asClickableFileUrl()} for details.")
}
}
private fun Project.instantExecutionOf(
vararg tasks: String,
buildOptions: BuildOptions = defaultBuildOptions(),
check: CompiledProject.() -> Unit
) =
build("-Dorg.gradle.unsafe.instant-execution=true", *tasks, options = buildOptions, check = check)
/**
* Copies all files from the directory containing the given [htmlReportFile] to a
* fresh temp dir and returns a reference to the copied [htmlReportFile] in the new
* directory.
*/
private fun copyReportToTempDir(htmlReportFile: File): File =
createTempDir().let { tempDir ->
htmlReportFile.parentFile.copyRecursively(tempDir)
tempDir.resolve(htmlReportFile.name)
}
/**
* The instant execution report file, if exists, indicates problems were
* found while caching the task graph.
*/
private fun Project.instantExecutionReportFile() = projectDir
.resolve(".instant-execution-state")
.findFileByName("instant-execution-report.html")
?.let { copyReportToTempDir(it) }
private fun File.asClickableFileUrl(): String =
URI("file", "", toURI().path, null, null).toString()
/**
* Android Gradle plugin 4.0-alpha4 depends on the EAP versions of some o.j.k modules.
* Force the current Kotlin version, so the EAP versions are not queried from the
* test project's repositories, where there's no 'kotlin-eap' repo.
* TODO remove this workaround once an Android Gradle plugin version is used that depends on the stable Kotlin version
*/
private fun Project.applyAndroid40Alpha4KotlinVersionWorkaround() {
setupWorkingDir()
val resolutionStrategyHack = """
configurations.all {
resolutionStrategy.dependencySubstitution.all { dependency ->
def requested = dependency.requested
if (requested instanceof ModuleComponentSelector && requested.group == 'org.jetbrains.kotlin') {
dependency.useTarget requested.group + ':' + requested.module + ':' + '${defaultBuildOptions().kotlinVersion}'
}
}
}
""".trimIndent()
gradleBuildScript().appendText("\n" + """
buildscript {
$resolutionStrategyHack
}
$resolutionStrategyHack
""".trimIndent())
}
}
@@ -365,7 +365,7 @@ class KotlinGradleIT : BaseGradleIT() {
}
@Test
fun testDowngradeTo106() {
fun testDowngradePluginVersion() {
val project = Project("kotlinProject")
val options = defaultBuildOptions().copy(incremental = true, withDaemon = false)
@@ -373,7 +373,7 @@ class KotlinGradleIT : BaseGradleIT() {
assertSuccessful()
}
project.build("clean", "assemble", options = options.copy(kotlinVersion = "1.0.6")) {
project.build("clean", "assemble", options = options.copy(kotlinVersion = "1.3.41")) {
assertSuccessful()
}
}
@@ -78,7 +78,9 @@ class UpToDateIT : BaseGradleIT() {
val originalPaths get() = originalCompilerCp.map { it.replace("\\", "/") }.joinToString(", ") { "'$it'" }
override fun initProject(project: Project) = with(project) {
buildGradle.appendText("\nprintln 'compiler_cp=' + compileKotlin.getComputedCompilerClasspath\$kotlin_gradle_plugin()")
buildGradle.appendText(
"\nafterEvaluate { println 'compiler_cp=' + compileKotlin.getComputedCompilerClasspath\$kotlin_gradle_plugin() }"
)
build("clean") { originalCompilerCp = "compiler_cp=\\[(.*)]".toRegex().find(output)!!.groupValues[1].split(", ") }
buildGradle.appendText("""${'\n'}
// Add Kapt to the project to test its input checks as well:
@@ -24,6 +24,7 @@ class AGPVersion private constructor(private val versionNumber: VersionNumber) {
val v3_3_2 = fromString("3.3.2")
val v3_4_1 = fromString("3.4.1")
val v3_6_0 = fromString("3.6.0")
val v4_0_ALPHA_8 = fromString("4.0.0-alpha08")
val v4_1_0 = fromString("4.1.0-beta02")
val v4_2_0 = fromString("4.2.0-alpha07")
}
}
@@ -0,0 +1,16 @@
buildscript {
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
subprojects {
repositories {
mavenLocal()
mavenCentral()
}
}
@@ -0,0 +1,5 @@
apply plugin: 'kotlin'
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}
@@ -0,0 +1,10 @@
/*
* 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 libproject
class Util {
val projectName: String = "lib-project"
}
@@ -0,0 +1,6 @@
apply plugin: 'kotlin'
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile project(':lib-project')
}
@@ -0,0 +1,10 @@
import libProject.Util
/*
* 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.
*/
fun main(args: Array<String>) {
println(Util.projectName)
}
@@ -0,0 +1 @@
include ':main-project', ':lib-project'
@@ -0,0 +1,37 @@
buildscript {
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'kotlin2js'
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version"
testCompile "org.jetbrains.kotlin:kotlin-test-js:$kotlin_version"
}
task jarSources(type: Jar) {
from sourceSets.main.allSource
classifier = 'source'
}
artifacts {
compile jarSources
}
compileKotlin2Js.kotlinOptions.outputFile = "${buildDir}/kotlin2js/main/module.js"
compileTestKotlin2Js.kotlinOptions.outputFile = "${buildDir}/kotlin2js/test/module-tests.js"
if (project.findProperty("kotlin.js.useIrBackend")?.toBoolean() == true) {
compileKotlin2Js.kotlinOptions.freeCompilerArgs += ["-Xir-produce-klib-dir", "-Xir-only"]
compileTestKotlin2Js.kotlinOptions.freeCompilerArgs += ["-Xir-produce-js"]
}
@@ -0,0 +1,10 @@
/*
* 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 project
class Main {
fun value(): String = "instant"
}
@@ -0,0 +1,13 @@
/*
* 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.
*/
import project.Main
import kotlin.test.*
class MainTest {
@Test
fun mySimpleTest() {
assertEquals("instant", Main().value())
}
}
@@ -2,12 +2,12 @@ apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
compileSdkVersion 26
buildToolsVersion "28.0.3"
defaultConfig {
minSdkVersion 21
targetSdkVersion 29
minSdkVersion 26
targetSdkVersion 26
}
}
@@ -2,12 +2,12 @@ apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
compileSdkVersion 26
buildToolsVersion "28.0.3"
defaultConfig {
minSdkVersion 21
targetSdkVersion 29
minSdkVersion 26
targetSdkVersion 26
}
}
@@ -5,17 +5,18 @@
package org.jetbrains.kotlin.compilerRunner
import org.gradle.api.Task
import org.gradle.workers.IsolationMode
import org.gradle.workers.WorkerExecutor
import org.jetbrains.kotlin.gradle.logging.kotlinDebug
import org.jetbrains.kotlin.gradle.tasks.GradleCompileTaskProvider
internal class GradleCompilerRunnerWithWorkers(
task: Task,
taskProvider: GradleCompileTaskProvider,
private val workersExecutor: WorkerExecutor
) : GradleCompilerRunner(task) {
) : GradleCompilerRunner(taskProvider) {
override fun runCompilerAsync(workArgs: GradleKotlinCompilerWorkArguments) {
task.logger.kotlinDebug { "Starting Kotlin compiler work from task '${task.path}'" }
loggerProvider.kotlinDebug { "Starting Kotlin compiler work from task '${pathProvider}'" }
// todo: write tests with Workers enabled;
workersExecutor.submit(GradleKotlinCompilerWork::class.java) { config ->
config.isolationMode = IsolationMode.NONE
@@ -6,8 +6,8 @@
package org.jetbrains.kotlin.compilerRunner
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.invocation.Gradle
import org.gradle.api.logging.Logger
import org.gradle.api.plugins.JavaPluginConvention
import org.gradle.api.tasks.bundling.AbstractArchiveTask
import org.gradle.jvm.tasks.Jar
@@ -24,11 +24,12 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinWithJavaTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.isMain
import org.jetbrains.kotlin.gradle.plugin.mpp.ownModuleName
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService
import org.jetbrains.kotlin.gradle.tasks.GradleCompileTaskProvider
import org.jetbrains.kotlin.gradle.tasks.KotlinCompileTaskData
import org.jetbrains.kotlin.gradle.tasks.locateTask
import org.jetbrains.kotlin.gradle.utils.archivePathCompatible
import org.jetbrains.kotlin.gradle.utils.newTmpFile
import org.jetbrains.kotlin.gradle.utils.relativeToRoot
import org.jetbrains.kotlin.gradle.utils.relativeOrCanonical
import org.jetbrains.kotlin.incremental.IncrementalModuleEntry
import org.jetbrains.kotlin.incremental.IncrementalModuleInfo
import org.jetbrains.kotlin.statistics.metrics.BooleanMetrics
@@ -50,9 +51,21 @@ const val COULD_NOT_CONNECT_TO_DAEMON_MESSAGE = "Could not connect to Kotlin com
internal fun kotlinCompilerExecutionStrategy(): String =
System.getProperty(KOTLIN_COMPILER_EXECUTION_STRATEGY_PROPERTY) ?: DAEMON_EXECUTION_STRATEGY
internal open class GradleCompilerRunner(protected val task: Task) {
protected val project: Project
get() = task.project
/*
Using real taskProvider cause "field 'taskProvider' from type 'org.jetbrains.kotlin.compilerRunner.GradleCompilerRunner':
value 'fixed(class org.jetbrains.kotlin.gradle.tasks.KotlinCompile_Decorated, task ':compileKotlin')'
is not assignable to 'org.gradle.api.tasks.TaskProvider'" exception
*/
internal open class GradleCompilerRunner(protected val taskProvider: GradleCompileTaskProvider) {
internal val pathProvider = taskProvider.path
internal val loggerProvider = taskProvider.logger
internal val buildDirProvider = taskProvider.buildDir
internal val projectDirProvider = taskProvider.projectDir
internal val projectRootDirProvider = taskProvider.rootDir
internal val sessionDirProvider = taskProvider.sessionsDir
internal val projectNameProvider = taskProvider.projectName
internal val incrementalModuleInfoProvider = taskProvider.buildModulesInfo
/**
* Compiler might be executed asynchronously. Do not do anything requiring end of compilation after this function is called.
@@ -107,7 +120,7 @@ internal open class GradleCompilerRunner(protected val task: Task) {
environment: GradleCompilerEnvironment
) {
if (compilerArgs.version) {
task.logger.lifecycle(
loggerProvider.lifecycle(
"Kotlin version " + loadCompilerVersion(environment.compilerClasspath) +
" (JRE " + System.getProperty("java.runtime.version") + ")"
)
@@ -128,9 +141,16 @@ internal open class GradleCompilerRunner(protected val task: Task) {
}
val incrementalCompilationEnvironment = environment.incrementalCompilationEnvironment
val modulesInfo = incrementalCompilationEnvironment?.let { buildModulesInfo(project.gradle) }
val modulesInfo = incrementalCompilationEnvironment?.let { incrementalModuleInfoProvider }
val workArgs = GradleKotlinCompilerWorkArguments(
projectFiles = ProjectFilesForCompilation(project),
projectFiles = ProjectFilesForCompilation(
loggerProvider,
projectDirProvider,
buildDirProvider,
projectNameProvider,
projectRootDirProvider,
sessionDirProvider
),
compilerFullClasspath = environment.compilerFullClasspath,
compilerClassName = compilerClassName,
compilerArgs = argsArray,
@@ -138,12 +158,12 @@ internal open class GradleCompilerRunner(protected val task: Task) {
incrementalCompilationEnvironment = incrementalCompilationEnvironment,
incrementalModuleInfo = modulesInfo,
outputFiles = environment.outputFiles.toList(),
taskPath = task.path,
taskPath = pathProvider,
buildReportMode = environment.buildReportMode,
kotlinScriptExtensions = environment.kotlinScriptExtensions,
allWarningsAsErrors = compilerArgs.allWarningsAsErrors
)
TaskLoggers.put(task.path, task.logger)
TaskLoggers.put(pathProvider, loggerProvider)
runCompilerAsync(workArgs)
}
@@ -221,7 +241,7 @@ internal open class GradleCompilerRunner(protected val task: Task) {
} else {
if (target is KotlinWithJavaTarget<*>) {
val jar = project.tasks.getByName(target.artifactsTaskName) as Jar
jarToClassListFile[jar.archivePathCompatible.canonicalFile] = target.defaultArtifactClassesListFile
jarToClassListFile[jar.archivePathCompatible.canonicalFile] = target.defaultArtifactClassesListFile.get()
}
}
}
@@ -261,11 +281,9 @@ internal open class GradleCompilerRunner(protected val task: Task) {
private var clientIsAliveFlagFile: File? = null
@Synchronized
internal fun getOrCreateClientFlagFile(project: Project): File {
val log = project.logger
internal fun getOrCreateClientFlagFile(log: Logger, projectName: String): File {
if (clientIsAliveFlagFile == null || !clientIsAliveFlagFile!!.exists()) {
val projectName = project.rootProject.name.normalizeForFlagFile()
clientIsAliveFlagFile = newTmpFile(prefix = "kotlin-compiler-in-$projectName-", suffix = ".alive")
clientIsAliveFlagFile = newTmpFile(prefix = "kotlin-compiler-in-${projectName}-", suffix = ".alive")
log.kotlinDebug { CREATED_CLIENT_FILE_PREFIX + clientIsAliveFlagFile!!.canonicalPath }
} else {
log.kotlinDebug { EXISTING_CLIENT_FILE_PREFIX + clientIsAliveFlagFile!!.canonicalPath }
@@ -274,7 +292,7 @@ internal open class GradleCompilerRunner(protected val task: Task) {
return clientIsAliveFlagFile!!
}
private fun String.normalizeForFlagFile(): String {
internal fun String.normalizeForFlagFile(): String {
val validChars = ('a'..'z') + ('0'..'9') + "-_"
return filter { it in validChars }
}
@@ -285,14 +303,13 @@ internal open class GradleCompilerRunner(protected val task: Task) {
// session files are deleted at org.jetbrains.kotlin.gradle.plugin.KotlinGradleBuildServices.buildFinished
@Synchronized
internal fun getOrCreateSessionFlagFile(project: Project): File {
val log = project.logger
internal fun getOrCreateSessionFlagFile(log: Logger, sessionsDir: File, projectRootDir: File): File {
if (sessionFlagFile == null || !sessionFlagFile!!.exists()) {
val sessionFilesDir = sessionsDir(project).apply { mkdirs() }
val sessionFilesDir = sessionsDir.apply { mkdirs() }
sessionFlagFile = newTmpFile(prefix = "kotlin-compiler-", suffix = ".salive", directory = sessionFilesDir)
log.kotlinDebug { CREATED_SESSION_FILE_PREFIX + sessionFlagFile!!.relativeToRoot(project) }
log.kotlinDebug { CREATED_SESSION_FILE_PREFIX + sessionFlagFile!!.relativeOrCanonical(projectRootDir) }
} else {
log.kotlinDebug { EXISTING_SESSION_FILE_PREFIX + sessionFlagFile!!.relativeToRoot(project) }
log.kotlinDebug { EXISTING_SESSION_FILE_PREFIX + sessionFlagFile!!.relativeOrCanonical(projectRootDir) }
}
return sessionFlagFile!!
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.compilerRunner
import org.gradle.api.Project
import org.gradle.api.logging.Logger
import org.jetbrains.kotlin.cli.common.ExitCode
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.config.Services
@@ -35,11 +36,12 @@ internal class ProjectFilesForCompilation(
val sessionFlagFile: File,
val buildDir: File
) : Serializable {
constructor(project: Project) : this(
projectRootFile = project.rootProject.projectDir,
clientIsAliveFlagFile = GradleCompilerRunner.getOrCreateClientFlagFile(project),
sessionFlagFile = GradleCompilerRunner.getOrCreateSessionFlagFile(project),
buildDir = project.buildDir
//TODO
constructor(logger: Logger, projectDir:File, buildDir: File, prjectName: String, projectRootDir: File, sessionDir: File) : this(
projectRootFile = projectDir,
clientIsAliveFlagFile = GradleCompilerRunner.getOrCreateClientFlagFile(logger, prjectName),
sessionFlagFile = GradleCompilerRunner.getOrCreateSessionFlagFile(logger, sessionDir, projectRootDir),
buildDir = buildDir
)
companion object {
@@ -5,13 +5,15 @@
package org.jetbrains.kotlin.gradle.internal
import org.gradle.api.tasks.TaskProvider
import org.jetbrains.kotlin.cli.common.arguments.*
import org.jetbrains.kotlin.gradle.dsl.*
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
import org.jetbrains.kotlin.cli.common.arguments.CommonToolArguments
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
import org.jetbrains.kotlin.gradle.dsl.Coroutines
import org.jetbrains.kotlin.gradle.dsl.fillDefaultValues
import org.jetbrains.kotlin.gradle.logging.kotlinDebug
import org.jetbrains.kotlin.gradle.tasks.*
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.gradle.utils.getValue
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinCompileArgumentsProvider
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompilerArgumentsProvider
import org.jetbrains.kotlin.gradle.utils.toSortedPathsArray
import org.jetbrains.kotlin.incremental.classpathAsList
import org.jetbrains.kotlin.incremental.destinationAsFile
@@ -38,22 +40,20 @@ internal fun compilerArgumentsConfigurationFlags(defaultsOnly: Boolean, ignoreCl
* but outside the tasks, so that this state & logic can be reused without referencing the task directly. */
internal open class AbstractKotlinCompileArgumentsContributor<T : CommonCompilerArguments>(
// Don't save this reference into a property! That would be hostile to Gradle instant execution
taskProvider: TaskProvider<out AbstractKotlinCompile<T>>
taskProvider: KotlinCompileArgumentsProvider<out AbstractKotlinCompile<T>>
) : CompilerArgumentsContributor<T> {
private val coroutines by taskProvider.map { it.coroutines }
protected val logger by taskProvider.map { it.logger }
private val isMultiplatform by taskProvider.map { it.isMultiplatform }
private val pluginClasspath by taskProvider.map { it.pluginClasspath }
private val pluginOptions by taskProvider.map { it.pluginOptions }
private val coroutines = taskProvider.coroutines
protected val logger = taskProvider.logger
private val isMultiplatform = taskProvider.isMultiplatform
private val pluginClasspath = taskProvider.pluginClasspath
private val pluginOptions = taskProvider.pluginOptions
override fun contributeArguments(
args: T,
flags: Collection<CompilerArgumentsConfigurationFlag>
) {
args.coroutinesState = when (coroutines) {
args.coroutinesState = when (coroutines.get()) {
Coroutines.ENABLE -> CommonCompilerArguments.ENABLE
Coroutines.WARN -> CommonCompilerArguments.WARN
Coroutines.ERROR -> CommonCompilerArguments.ERROR
@@ -79,23 +79,14 @@ internal open class AbstractKotlinCompileArgumentsContributor<T : CommonCompiler
internal open class KotlinJvmCompilerArgumentsContributor(
// Don't save this reference into a property! That would be hostile to Gradle instant execution. Only map it to the task properties.
taskProvider: TaskProvider<out KotlinCompile>
taskProvider: KotlinJvmCompilerArgumentsProvider
) : AbstractKotlinCompileArgumentsContributor<K2JVMCompilerArguments>(taskProvider) {
private val moduleName by taskProvider.map { it.moduleName }
private val friendPaths by taskProvider.map { it.friendPaths }
private val compileClasspath by taskProvider.map { it.compileClasspath }
private val destinationDir by taskProvider.map { it.destinationDir }
private val kotlinOptions by taskProvider.map {
listOfNotNull(
it.parentKotlinOptionsImpl as KotlinJvmOptionsImpl?,
it.kotlinOptions as KotlinJvmOptionsImpl
)
}
private val moduleName = taskProvider.moduleName
private val friendPaths = taskProvider.friendPaths
private val compileClasspath = taskProvider.compileClasspath
private val destinationDir = taskProvider.destinationDir
private val kotlinOptions = taskProvider.kotlinOptions
override fun contributeArguments(
args: K2JVMCompilerArguments,
@@ -121,6 +112,6 @@ internal open class KotlinJvmCompilerArgumentsContributor(
}
args.destinationAsFile = destinationDir
kotlinOptions.forEach { it.updateArguments(args) }
kotlinOptions.forEach { it?.updateArguments(args) }
}
}
@@ -10,6 +10,7 @@ import com.android.build.gradle.api.AndroidSourceSet
import com.android.build.gradle.api.BaseVariant
import com.android.build.gradle.api.SourceKind
import com.intellij.openapi.util.SystemInfo
import com.intellij.util.lang.JavaVersion
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
@@ -22,7 +23,6 @@ import org.gradle.api.tasks.*
import org.gradle.api.tasks.compile.AbstractCompile
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.process.CommandLineArgumentProvider
import org.gradle.tooling.provider.model.ToolingModelBuilder
import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions
import org.jetbrains.kotlin.gradle.internal.kapt.incremental.CLASS_STRUCTURE_ARTIFACT_TYPE
@@ -34,6 +34,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinCompileTaskData
import org.jetbrains.kotlin.gradle.tasks.locateTask
import org.jetbrains.kotlin.gradle.tasks.registerTask
import org.jetbrains.kotlin.gradle.utils.isConfigurationCacheAvailable
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.ObjectOutputStream
@@ -474,7 +475,14 @@ class Kapt3GradleSubplugin @Inject internal constructor(private val registry: To
val dslJavacOptions: Provider<Map<String, String>> = project.provider {
kaptExtension.getJavacOptions().toMutableMap().also { result ->
if (javaCompile != null && "-source" !in result && "--source" !in result && "--release" !in result) {
val sourceOptionKey = if (SystemInfo.isJavaVersionAtLeast(12, 0, 0)) {
val atLeast12Java =
if (isConfigurationCacheAvailable(project.gradle)) {
val currentJavaVersion = JavaVersion.parse(project.providers.systemProperty("java.version").forUseAtConfigurationTime().get())
currentJavaVersion.feature >= 12
} else {
SystemInfo.isJavaVersionAtLeast(12, 0, 0)
}
val sourceOptionKey = if (atLeast12Java) {
"--source"
} else {
"-source"
@@ -20,7 +20,7 @@ import org.gradle.api.artifacts.Configuration
import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.*
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
import org.jetbrains.kotlin.gradle.dsl.*
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptionsImpl
import org.jetbrains.kotlin.gradle.tasks.FilteringSourceRootsContainer
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
@@ -50,7 +50,7 @@ open class KaptGenerateStubsTask : KotlinCompile() {
@get:Classpath
@get:InputFiles
val kaptClasspath: FileCollection
get() = project.files(kaptClasspathConfigurations)
get() = objects.fileCollection().from(kaptClasspathConfigurations)
@get:Internal
internal lateinit var kaptClasspathConfigurations: List<Configuration>
@@ -69,6 +69,9 @@ open class KaptGenerateStubsTask : KotlinCompile() {
error("KaptGenerateStubsTask.useModuleDetection setter should not be called!")
}
@get:Input
val verbose = (project.hasProperty("kapt.verbose") && project.property("kapt.verbose").toString().toBoolean() == true)
override fun source(vararg sources: Any): SourceTask {
return super.source(sourceRootsContainer.add(sources))
}
@@ -95,7 +98,7 @@ open class KaptGenerateStubsTask : KotlinCompile() {
val pluginOptionsWithKapt = pluginOptions.withWrappedKaptOptions(withApClasspath = kaptClasspath)
args.pluginOptions = (pluginOptionsWithKapt.arguments + args.pluginOptions!!).toTypedArray()
args.verbose = project.hasProperty("kapt.verbose") && project.property("kapt.verbose").toString().toBoolean() == true
args.verbose = verbose
args.classpathAsList = this.compileClasspath.filter { it.exists() }.toList()
args.destinationAsFile = this.destinationDir
}
@@ -1,6 +1,5 @@
package org.jetbrains.kotlin.gradle.internal
import com.intellij.openapi.util.io.FileUtil
import org.gradle.api.artifacts.Configuration
import org.gradle.api.file.FileCollection
import org.gradle.api.internal.ConventionTask
@@ -28,7 +27,7 @@ abstract class KaptTask : ConventionTask(), TaskWithLocalState {
outputs.cacheIf(reason) { useBuildCache }
}
override fun localStateDirectories(): FileCollection = project.files()
override fun localStateDirectories(): FileCollection = objects.fileCollection()
@get:Internal
@field:Transient
@@ -37,10 +36,13 @@ abstract class KaptTask : ConventionTask(), TaskWithLocalState {
@get:Internal
internal lateinit var stubsDir: File
@get:Internal
internal val objects = project.objects
@get:Classpath
@get:InputFiles
val kaptClasspath: FileCollection
get() = project.files(kaptClasspathConfigurations)
get() = objects.fileCollection().from(kaptClasspathConfigurations)
@get:Classpath
@get:InputFiles
@@ -137,8 +139,36 @@ abstract class KaptTask : ConventionTask(), TaskWithLocalState {
private fun isRootAllowed(file: File): Boolean =
file.exists() &&
!FileUtil.isAncestor(destinationDir, file, /* strict = */ false) &&
!FileUtil.isAncestor(classesDir, file, /* strict = */ false)
!isAncestor(destinationDir, file) &&
!isAncestor(classesDir, file)
//Have to avoid using FileUtil because it is required system property reading that is not allowed for configuration cache
private fun isAncestor(dir: File, file: File): Boolean {
val path = file.canonicalPath
val prefix = dir.canonicalPath
val pathLength = path.length
val prefixLength = prefix.length
//TODO
val caseSensitive = true
return if (prefixLength == 0) {
true
} else if (prefixLength > pathLength) {
false
} else if (!path.regionMatches(0, prefix, 0, prefixLength, ignoreCase = !caseSensitive)) {
return false
} else if (pathLength == prefixLength) {
return true
} else {
val lastPrefixChar: Char = prefix.get(prefixLength - 1)
var slashOrSeparatorIdx = prefixLength
if (lastPrefixChar == '/' || lastPrefixChar == File.separatorChar) {
slashOrSeparatorIdx = prefixLength - 1
}
val next1 = path[slashOrSeparatorIdx]
return !(next1 != '/' && next1 != File.separatorChar)
}
}
private fun FileCollection?.orEmpty(): FileCollection =
this ?: project.files()
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.gradle.logging.GradleKotlinLogger
import org.jetbrains.kotlin.gradle.logging.GradlePrintingMessageCollector
import org.jetbrains.kotlin.gradle.plugin.PLUGIN_CLASSPATH_CONFIGURATION_NAME
import org.jetbrains.kotlin.gradle.tasks.CompilerPluginOptions
import org.jetbrains.kotlin.gradle.tasks.GradleCompileTaskProvider
import org.jetbrains.kotlin.gradle.utils.getValue
import org.jetbrains.kotlin.gradle.utils.optionalProvider
import org.jetbrains.kotlin.gradle.utils.toSortedPathsArray
@@ -43,6 +44,9 @@ open class KaptWithKotlincTask : KaptTask(), CompilerArgumentAwareWithInput<K2JV
val pluginClasspath: FileCollection
get() = project.configurations.getByName(PLUGIN_CLASSPATH_CONFIGURATION_NAME)
@get:Internal
val taskProvider = GradleCompileTaskProvider(this)
override fun createCompilerArgs(): K2JVMCompilerArguments = K2JVMCompilerArguments()
private val compileKotlinArgumentsContributor by project.provider {
@@ -50,8 +54,9 @@ open class KaptWithKotlincTask : KaptTask(), CompilerArgumentAwareWithInput<K2JV
}
override fun setupCompilerArgs(args: K2JVMCompilerArguments, defaultsOnly: Boolean, ignoreClasspathResolutionErrors: Boolean) {
compileKotlinArgumentsContributor.contributeArguments(args, compilerArgumentsConfigurationFlags(
defaultsOnly,
compileKotlinArgumentsContributor.contributeArguments(
args, compilerArgumentsConfigurationFlags(
defaultsOnly,
ignoreClasspathResolutionErrors
))
@@ -106,7 +111,7 @@ open class KaptWithKotlincTask : KaptTask(), CompilerArgumentAwareWithInput<K2JV
throw GradleException("Could not find tools.jar in system classpath, which is required for kapt to work")
}
val compilerRunner = GradleCompilerRunner(this)
val compilerRunner = GradleCompilerRunner(taskProvider)
compilerRunner.runJvmCompilerAsync(
sourcesToCompile = emptyList(),
commonSources = emptyList(),
@@ -11,13 +11,12 @@ import org.gradle.workers.IsolationMode
import org.gradle.workers.WorkerExecutor
import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin.Companion.KAPT_WORKER_DEPENDENCIES_CONFIGURATION_NAME
import org.jetbrains.kotlin.gradle.internal.kapt.incremental.KaptIncrementalChanges
import org.jetbrains.kotlin.gradle.plugin.CompositeSubpluginOption
import org.jetbrains.kotlin.gradle.plugin.KotlinAndroidPluginWrapper
import org.jetbrains.kotlin.gradle.plugin.SubpluginOption
import org.jetbrains.kotlin.gradle.tasks.CompilerPluginOptions
import org.jetbrains.kotlin.gradle.tasks.findKotlinStdlibClasspath
import org.jetbrains.kotlin.gradle.tasks.findToolsJar
import org.jetbrains.kotlin.gradle.utils.getValue
import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast
import org.jetbrains.kotlin.utils.PathUtil
import java.io.File
import java.io.Serializable
@@ -29,7 +28,7 @@ open class KaptWithoutKotlincTask @Inject constructor(private val workerExecutor
@get:InputFiles
@get:Classpath
@Suppress("unused")
val kaptJars: Collection<File> by project.provider {
val kaptJars: Collection<File> by lazy {
project.configurations.getByName(KAPT_WORKER_DEPENDENCIES_CONFIGURATION_NAME).resolve()
}
@@ -48,6 +47,18 @@ open class KaptWithoutKotlincTask @Inject constructor(private val workerExecutor
@get:Input
lateinit var javacOptions: Map<String, String>
@get:Input
internal val kotlinAndroidPluginWrapperPluginDoesNotExist = project.plugins.none { it is KotlinAndroidPluginWrapper }
@get:Input
internal val kotlinStdlibClasspath = findKotlinStdlibClasspath(project)
@get:Internal
internal val projectDir = project.projectDir
@get:Internal
internal val providers = project.providers
private fun getAnnotationProcessorOptions(): Map<String, String> {
val options = processorOptions.subpluginOptionsByPluginId[Kapt3GradleSubplugin.KAPT_SUBPLUGIN_ID] ?: return emptyMap()
@@ -70,7 +81,7 @@ open class KaptWithoutKotlincTask @Inject constructor(private val workerExecutor
}
val compileClasspath = classpath.files.toMutableList()
if (project.plugins.none { it is KotlinAndroidPluginWrapper }) {
if (kotlinAndroidPluginWrapperPluginDoesNotExist) {
compileClasspath.addAll(0, PathUtil.getJdkClassesRootsFromCurrentJre())
}
@@ -82,7 +93,7 @@ open class KaptWithoutKotlincTask @Inject constructor(private val workerExecutor
}
val optionsForWorker = KaptOptionsForWorker(
project.projectDir,
projectDir,
compileClasspath,
javaSourceRoots.toList(),
@@ -108,25 +119,35 @@ open class KaptWithoutKotlincTask @Inject constructor(private val workerExecutor
if (annotationProcessorFqNames.isEmpty() && kaptClasspath.isEmpty())
return
val kaptClasspath = kaptJars + findKotlinStdlibClasspath(project)
val kaptClasspath = kaptJars + kotlinStdlibClasspath
workerExecutor.submit(KaptExecution::class.java) { config ->
val isolationModeStr = project.findProperty("kapt.workers.isolation") as String? ?: "none"
//TODO for gradle < 6.5
val isolationModeStr = getValue("kapt.workers.isolation") ?: "none"
config.isolationMode = when (isolationModeStr.toLowerCase()) {
"process" -> IsolationMode.PROCESS
"none" -> IsolationMode.NONE
else -> IsolationMode.NONE
}
config.params(optionsForWorker, findToolsJar()?.toURI()?.toURL()?.toString().orEmpty(), kaptClasspath)
if (project.findProperty("kapt.workers.log.classloading") == "true") {
if (getValue("kapt.workers.log.classloading") == "true") {
// for tests
config.forkOptions.jvmArgs("-verbose:class")
}
logger.info("Kapt worker classpath: ${config.classpath}")
}
}
internal fun getValue(propertyName: String): String? =
if (isGradleVersionAtLeast(6, 5)) {
providers.systemProperty(propertyName).forUseAtConfigurationTime().orNull
} else {
project.findProperty(propertyName) as String?
}
}
private class KaptExecution @Inject constructor(
val optionsForWorker: KaptOptionsForWorker,
val toolsJarURLSpec: String,
@@ -144,7 +144,7 @@ class KotlinModelBuilder(private val kotlinPluginVersion: String, private val an
}
private fun AbstractKotlinCompile<*>.createExperimentalFeatures(): ExperimentalFeatures {
return ExperimentalFeaturesImpl(coroutinesStr)
return ExperimentalFeaturesImpl(coroutinesStr.get())
}
}
}
@@ -0,0 +1,23 @@
/*
* 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.plugin
import org.gradle.api.Project
import org.gradle.build.event.BuildEventsListenerRegistry
import org.jetbrains.kotlin.gradle.utils.isConfigurationCacheAvailable
import javax.inject.Inject
open class BuildEventsListenerRegistryHolder @Inject constructor(val listenerRegistry: BuildEventsListenerRegistry?) {
companion object {
fun getInstance(project: Project) = run {
if (isConfigurationCacheAvailable(project.gradle)) {
project.objects.newInstance(BuildEventsListenerRegistryHolder::class.java)
} else {
BuildEventsListenerRegistryHolder(null)
}
}
}
}
@@ -0,0 +1,21 @@
/*
* 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.plugin
import org.gradle.tooling.events.FinishEvent
import org.gradle.tooling.events.OperationCompletionListener
//Available since Gradle 6.1
class KotlinGradleBuildListener(
// val gradle: Gradle,
val services: KotlinGradleFinishBuildHandler
) : OperationCompletionListener {
override fun onFinish(event: FinishEvent) {
// services.buildFinished(gradle)
}
}
@@ -10,20 +10,18 @@ import org.gradle.BuildResult
import org.gradle.api.Project
import org.gradle.api.invocation.Gradle
import org.gradle.api.logging.Logging
import org.jetbrains.kotlin.compilerRunner.DELETED_SESSION_FILE_PREFIX
import org.jetbrains.kotlin.compilerRunner.GradleCompilerRunner
import org.jetbrains.kotlin.gradle.logging.kotlinDebug
import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskExecutionResults
import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskLoggers
import org.jetbrains.kotlin.gradle.report.configureBuildReporter
import org.jetbrains.kotlin.gradle.utils.relativeToRoot
import org.jetbrains.kotlin.utils.addToStdlib.sumByLong
import java.lang.management.ManagementFactory
import kotlin.math.max
import org.jetbrains.kotlin.gradle.utils.isConfigurationCacheAvailable
//Support Gradle 6 and less. Move to
internal class KotlinGradleBuildServices private constructor(
private val gradle: Gradle
) : BuildAdapter() {
companion object {
private val CLASS_NAME = KotlinGradleBuildServices::class.java.simpleName
const val FORCE_SYSTEM_GC_MESSAGE = "Forcing System.gc()"
@@ -34,36 +32,69 @@ internal class KotlinGradleBuildServices private constructor(
val ALREADY_INITIALIZED_MESSAGE = "$CLASS_NAME is already initialized"
@field:Volatile
private var instance: KotlinGradleBuildServices? = null
internal var instance: KotlinGradleBuildServices? = null
// @JvmStatic
// @Synchronized
// fun getInstance(gradle: Gradle): KotlinGradleBuildServices {
// val log = Logging.getLogger(KotlinGradleBuildServices::class.java)
//
// if (instance != null) {
// log.kotlinDebug(ALREADY_INITIALIZED_MESSAGE)
// return instance!!
// }
//
// val services = KotlinGradleBuildServices(gradle)
// instance = services
// if (!isGradleVersionAtLeast(6,1)) {
// gradle.addBuildListener(services)
// log.kotlinDebug(INIT_MESSAGE)
// } else {
// BuildEventsListenerRegistry.
// }
//
// services.buildStarted()
// return services
// }
@JvmStatic
@Synchronized
fun getInstance(gradle: Gradle): KotlinGradleBuildServices {
fun getInstance(project: Project, listenerRegistryHolder: BuildEventsListenerRegistryHolder): KotlinGradleBuildServices {
val log = Logging.getLogger(KotlinGradleBuildServices::class.java)
val kotlinGradleListenerProvider: org.gradle.api.provider.Provider<KotlinGradleBuildListener> = project.provider {
KotlinGradleBuildListener(KotlinGradleFinishBuildHandler())
}
if (instance != null) {
log.kotlinDebug(ALREADY_INITIALIZED_MESSAGE)
return instance!!
}
val gradle = project.gradle
val services = KotlinGradleBuildServices(gradle)
gradle.addBuildListener(services)
instance = services
log.kotlinDebug(INIT_MESSAGE)
if (isConfigurationCacheAvailable(gradle)) {
listenerRegistryHolder.listenerRegistry!!.onTaskCompletion(kotlinGradleListenerProvider)
} else {
gradle.addBuildListener(services)
instance = services
log.kotlinDebug(INIT_MESSAGE)
}
services.buildStarted()
return services
}
}
private val log = Logging.getLogger(this.javaClass)
private var startMemory: Long? = null
private val shouldReportMemoryUsage = System.getProperty(SHOULD_REPORT_MEMORY_USAGE_PROPERTY) != null
private var buildHandler: KotlinGradleFinishBuildHandler? = null
// There is function with the same name in BuildAdapter,
// but it is called before any plugin can attach build listener
fun buildStarted() {
startMemory = getUsedMemoryKb()
buildHandler = KotlinGradleFinishBuildHandler()
buildHandler!!.buildStart()
TaskLoggers.clear()
TaskExecutionResults.clear()
@@ -72,57 +103,11 @@ internal class KotlinGradleBuildServices private constructor(
}
override fun buildFinished(result: BuildResult) {
TaskLoggers.clear()
TaskExecutionResults.clear()
val gradle = result.gradle!!
GradleCompilerRunner.clearBuildModulesInfo()
val rootProject = gradle.rootProject
val sessionsDir = GradleCompilerRunner.sessionsDir(rootProject)
if (sessionsDir.exists()) {
val sessionFiles = sessionsDir.listFiles()
// it is expected that only one session file per build exists
// afaik is is not possible to run multiple gradle builds in one project since gradle locks some dirs
if (sessionFiles.size > 1) {
log.warn("w: Detected multiple Kotlin daemon sessions at ${sessionsDir.relativeToRoot(rootProject)}")
}
for (file in sessionFiles) {
file.delete()
log.kotlinDebug { DELETED_SESSION_FILE_PREFIX + file.relativeToRoot(rootProject) }
}
}
if (shouldReportMemoryUsage) {
val startMem = startMemory!!
val endMem = getUsedMemoryKb()!!
// the value reported here is not necessarily a leak, since it is calculated before collecting the plugin classes
// but on subsequent runs in the daemon it should be rather small, then the classes are actually reused by the daemon (see above)
log.lifecycle("[KOTLIN][PERF] Used memory after build: $endMem kb (difference since build start: ${"%+d".format(endMem - startMem)} kb)")
}
gradle.removeListener(this)
buildHandler!!.buildFinished(result.gradle!!)
instance = null
log.kotlinDebug(DISPOSE_MESSAGE)
}
private fun getUsedMemoryKb(): Long? {
if (!shouldReportMemoryUsage) return null
log.lifecycle(FORCE_SYSTEM_GC_MESSAGE)
val gcCountBefore = getGcCount()
System.gc()
while (getGcCount() == gcCountBefore) {
}
val rt = Runtime.getRuntime()
return (rt.totalMemory() - rt.freeMemory()) / 1024
}
private fun getGcCount(): Long =
ManagementFactory.getGarbageCollectorMXBeans().sumByLong { max(0, it.collectionCount) }
private val multipleProjectsHolder = KotlinPluginInMultipleProjectsHolder(
trackPluginVersionsSeparately = true
@@ -0,0 +1,81 @@
/*
* 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.plugin
import org.gradle.api.invocation.Gradle
import org.gradle.api.logging.Logging
import org.jetbrains.kotlin.compilerRunner.DELETED_SESSION_FILE_PREFIX
import org.jetbrains.kotlin.compilerRunner.GradleCompilerRunner
import org.jetbrains.kotlin.gradle.logging.kotlinDebug
import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskExecutionResults
import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskLoggers
import org.jetbrains.kotlin.gradle.utils.relativeToRoot
import org.jetbrains.kotlin.utils.addToStdlib.sumByLong
import java.lang.management.ManagementFactory
import kotlin.math.max
// move to KotlinGradleBuildListener when min supported version is 6.1
class KotlinGradleFinishBuildHandler {
private val log = Logging.getLogger(this.javaClass)
private var startMemory: Long? = null
private val shouldReportMemoryUsage = System.getProperty(KotlinGradleBuildServices.SHOULD_REPORT_MEMORY_USAGE_PROPERTY) != null
fun buildStart() {
startMemory = getUsedMemoryKb()
}
fun buildFinished(gradle: Gradle) {
TaskLoggers.clear()
TaskExecutionResults.clear()
GradleCompilerRunner.clearBuildModulesInfo()
val rootProject = gradle.rootProject
val sessionsDir = GradleCompilerRunner.sessionsDir(rootProject)
if (sessionsDir.exists()) {
val sessionFiles = sessionsDir.listFiles()
// it is expected that only one session file per build exists
// afaik is is not possible to run multiple gradle builds in one project since gradle locks some dirs
if (sessionFiles.size > 1) {
log.warn("w: Detected multiple Kotlin daemon sessions at ${sessionsDir.relativeToRoot(rootProject)}")
}
for (file in sessionFiles) {
file.delete()
log.kotlinDebug { DELETED_SESSION_FILE_PREFIX + file.relativeToRoot(rootProject) }
}
}
if (shouldReportMemoryUsage) {
val startMem = startMemory!!
val endMem = getUsedMemoryKb()!!
// the value reported here is not necessarily a leak, since it is calculated before collecting the plugin classes
// but on subsequent runs in the daemon it should be rather small, then the classes are actually reused by the daemon (see above)
log.lifecycle("[KOTLIN][PERF] Used memory after build: $endMem kb (difference since build start: ${"%+d".format(endMem - startMem)} kb)")
}
gradle.removeListener(this)
}
internal fun getUsedMemoryKb(): Long? {
if (!shouldReportMemoryUsage) return null
log.lifecycle(KotlinGradleBuildServices.FORCE_SYSTEM_GC_MESSAGE)
val gcCountBefore = getGcCount()
System.gc()
while (getGcCount() == gcCountBefore) {
}
val rt = Runtime.getRuntime()
return (rt.totalMemory() - rt.freeMemory()) / 1024
}
private fun getGcCount(): Long =
ManagementFactory.getGarbageCollectorMXBeans().sumByLong { max(0, it.collectionCount) }
}
@@ -1,3 +1,4 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
@@ -103,7 +103,7 @@ internal abstract class KotlinSourceSetProcessor<T : AbstractKotlinCompile<*>>(
private fun prepareKotlinCompileTask(): TaskProvider<out T> =
registerKotlinCompileTask(register = ::doRegisterTask).also { task ->
kotlinCompilation.output.addClassesDir { project.files(task.map { it.destinationDir }) }
kotlinCompilation.output.addClassesDir { project.files(task.map { it.destinationDir }).builtBy(task) }
}
protected fun registerKotlinCompileTask(
@@ -24,6 +24,7 @@ import org.gradle.api.internal.FeaturePreviews
import org.gradle.api.internal.file.FileResolver
import org.gradle.api.logging.Logger
import org.gradle.api.logging.Logging
import org.gradle.build.event.BuildEventsListenerRegistry
import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
import org.jetbrains.kotlin.gradle.dsl.*
import org.jetbrains.kotlin.gradle.logging.kotlinDebug
@@ -49,6 +50,7 @@ import kotlin.reflect.KClass
abstract class KotlinBasePluginWrapper(
protected val fileResolver: FileResolver
) : Plugin<Project> {
private val log = Logging.getLogger(this.javaClass)
val kotlinPluginVersion = loadKotlinVersionFromResource(log)
@@ -58,7 +60,8 @@ abstract class KotlinBasePluginWrapper(
DefaultKotlinSourceSetFactory(project, fileResolver)
override fun apply(project: Project) {
val statisticsReporter = KotlinBuildStatsService.getOrCreateInstance(project.gradle)
val listenerRegistryHolder = BuildEventsListenerRegistryHolder.getInstance(project)
val statisticsReporter = KotlinBuildStatsService.getOrCreateInstance(project, listenerRegistryHolder)
statisticsReporter?.report(StringMetrics.KOTLIN_COMPILER_VERSION, kotlinPluginVersion)
checkGradleCompatibility()
@@ -80,7 +83,8 @@ abstract class KotlinBasePluginWrapper(
// TODO: consider only set if if daemon or parallel compilation are enabled, though this way it should be safe too
System.setProperty(org.jetbrains.kotlin.cli.common.KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY, "true")
val kotlinGradleBuildServices = KotlinGradleBuildServices.getInstance(project.gradle)
val kotlinGradleBuildServices = KotlinGradleBuildServices.getInstance(project, listenerRegistryHolder)
kotlinGradleBuildServices.detectKotlinPluginLoadedInMultipleProjects(project, kotlinPluginVersion)
@@ -27,16 +27,16 @@ import java.io.File
internal fun AbstractCompile.appendClasspathDynamically(file: File) {
var added = false
val objects = project.objects
doFirst {
if (file !in classpath) {
classpath += project.files(file)
classpath += objects.fileCollection().from(file)
added = true
}
}
doLast {
if (added) {
classpath -= project.files(file)
classpath -= objects.fileCollection().from(file)
}
}
}
@@ -24,9 +24,14 @@ import java.util.zip.ZipOutputStream
import javax.xml.parsers.DocumentBuilderFactory
internal sealed class MetadataDependencyResolution(
@field:Transient // can't be used with Gradle Instant Execution, but fortunately not needed when deserialized
val dependency: ResolvedComponentResult,
@field:Transient
val projectDependency: Project?
) {
/** Evaluate and store the value, as the [dependency] will be lost during Gradle instant execution */
// val originalArtifactFiles: List<File> = dependency.dependents.flatMap { it.allModuleArtifacts } .map { it.file }
override fun toString(): String {
val verb = when (this) {
is KeepOriginalDependency -> "keep"
@@ -59,7 +64,10 @@ internal sealed class MetadataDependencyResolution(
* If [doProcessFiles] is true, these temporary files are actually re-created during the call,
* otherwise only their paths are returned, while the files might be missing.
*/
abstract fun getMetadataFilesBySourceSet(baseDir: File, doProcessFiles: Boolean): Map<String, FileCollection>
fun getMetadataFilesBySourceSet(baseDir: File, doProcessFiles: Boolean): Map<String, FileCollection> =
getExtractableMetadataFiles(baseDir).getMetadataFilesPerSourceSet(doProcessFiles)
abstract fun getExtractableMetadataFiles(baseDir: File): ExtractableMetadataFiles
override fun toString(): String =
super.toString() + ", sourceSets = " + allVisibleSourceSetNames.joinToString(", ", "[", "]") {
@@ -183,6 +191,7 @@ internal class GranularMetadataTransformation(
allModuleDependencies.forEach { resolvedDependency ->
if (resolvedDependency.selected !in visitedDependencies) {
// val files = resolvedDependency.moduleArtifacts.map { it.file }
result.add(
MetadataDependencyResolution.ExcludeAsUnrequested(
resolvedDependency.selected,
@@ -273,6 +282,20 @@ internal class GranularMetadataTransformation(
module, resolvedToProject, projectStructureMetadata, allVisibleSourceSets, visibleSourceSetsExcludingDependsOn,
transitiveDependenciesToVisit, mppDependencyMetadataExtractor
)
/*
return object : MetadataDependencyResolution.ChooseVisibleSourceSets(
module,
projectDependency,
projectStructureMetadata,
allVisibleSourceSets,
visibleSourceSetsExcludingDependsOn,
transitiveDependenciesToVisit
) {
override fun getExtractableMetadataFiles(baseDir: File): ExtractableMetadataFiles =
mppDependencyMetadataExtractor.getExtractableMetadataFiles(visibleSourceSetsExcludingDependsOn, baseDir)
}
*/
}
private class ChooseVisibleSourceSetsImpl(
@@ -291,18 +314,24 @@ internal class GranularMetadataTransformation(
visibleSourceSetNamesExcludingDependsOn,
visibleTransitiveDependencies
) {
override fun getMetadataFilesBySourceSet(baseDir: File, doProcessFiles: Boolean): Map<String, FileCollection> =
metadataExtractor.getVisibleSourceSetsMetadata(visibleSourceSetNamesExcludingDependsOn, baseDir, doProcessFiles)
override fun getExtractableMetadataFiles(baseDir: File): ExtractableMetadataFiles =
object : ExtractableMetadataFiles() {
override fun getMetadataFilesPerSourceSet(doProcessFiles: Boolean) : Map<String, FileCollection>
= metadataExtractor.getExtractableMetadataFiles(visibleSourceSetNamesExcludingDependsOn, baseDir, doProcessFiles)
.getMetadataFilesPerSourceSet(doProcessFiles)
}
}
}
private abstract class MppDependencyMetadataExtractor(val project: Project, val dependency: ResolvedComponentResult) {
abstract fun getProjectStructureMetadata(): KotlinProjectStructureMetadata?
abstract fun getVisibleSourceSetsMetadata(
abstract fun getExtractableMetadataFiles(
visibleSourceSetNames: Set<String>,
baseDir: File,
doProcessFiles: Boolean
): Map<String, FileCollection>
): ExtractableMetadataFiles
}
private class ProjectMppDependencyMetadataExtractor(
@@ -313,14 +342,19 @@ private class ProjectMppDependencyMetadataExtractor(
override fun getProjectStructureMetadata(): KotlinProjectStructureMetadata? =
buildKotlinProjectStructureMetadata(dependencyProject)
override fun getVisibleSourceSetsMetadata(
override fun getExtractableMetadataFiles(
visibleSourceSetNames: Set<String>,
baseDir: File,
doProcessFiles: Boolean
): Map<String, FileCollection> =
dependencyProject.multiplatformExtension.targets.getByName(KotlinMultiplatformPlugin.METADATA_TARGET_NAME).compilations
): ExtractableMetadataFiles {
val result = dependencyProject.multiplatformExtension.targets.getByName(KotlinMultiplatformPlugin.METADATA_TARGET_NAME).compilations
.filter { it.name in visibleSourceSetNames }
.associate { it.defaultSourceSet.name to it.output.classesDirs }
return object : ExtractableMetadataFiles() {
override fun getMetadataFilesPerSourceSet(doProcessFiles: Boolean): Map<String, FileCollection> = result
}
}
}
private class JarArtifactMppDependencyMetadataExtractor(
@@ -344,72 +378,106 @@ private class JarArtifactMppDependencyMetadataExtractor(
}
}
override fun getVisibleSourceSetsMetadata(
override fun getExtractableMetadataFiles(
visibleSourceSetNames: Set<String>,
baseDir: File,
doProcessFiles: Boolean
): Map<String, FileCollection> {
): ExtractableMetadataFiles {
// val artifactFile = artifact.file
val primaryArtifact = primaryArtifact
val moduleId = ModuleIds.fromComponent(project, dependency)
return extractSourceSetMetadataFromArtifacts(
// val jarArtifact = artifact
// ?: return object : ExtractableMetadataFiles() {
// override fun getMetadataFilesPerSourceSet(doProcessFiles: Boolean): Map<String, FileCollection> = emptyMap()
// }
return JarExtractableMetadataFiles(
moduleId,
project,
primaryArtifact,
// artifactJar,
// visibleSourceSetNames,
baseDir,
doProcessFiles,
visibleSourceSetNames.associate { it to (metadataArtifactBySourceSet[it] ?: primaryArtifact) }
)
}
private fun extractSourceSetMetadataFromArtifacts(
module: ModuleDependencyIdentifier,
baseDir: File,
private class JarExtractableMetadataFiles(
private val module: ModuleDependencyIdentifier,
private val project: Project,
private val primaryArtifact: File,
private val baseDir: File,
doProcessFiles: Boolean,
artifactBySourceSet: Map<String, File>
): Map<String, FileCollection> {
val moduleString = "${module.groupId}-${module.moduleId}"
val transformedModuleRoot = run { baseDir.resolve(moduleString).also { it.mkdirs() } }
private val artifactBySourceSet: Map<String, File>
) : ExtractableMetadataFiles() {
val resultFiles = mutableMapOf<String, FileCollection>()
//TODO its part of extractor. Move it
fun getProjectStructureMetadata(): KotlinProjectStructureMetadata? {
return ZipFile(primaryArtifact).use { zip ->
val metadata = zip.getEntry("META-INF/$MULTIPLATFORM_PROJECT_METADATA_FILE_NAME")
?: return null
val projectStructureMetadata = checkNotNull(getProjectStructureMetadata()) {
"can't extract metadata from a module without project structure metadata"
val metadataXmlDocument = zip.getInputStream(metadata).use { inputStream ->
DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream)
}
parseKotlinSourceSetMetadataFromXml(metadataXmlDocument)
}
}
artifactBySourceSet.forEach { (sourceSetName, artifact) ->
ZipFile(artifact).use { zip ->
val entries = zip.entries().asSequence().filter { it.name.startsWith("$sourceSetName/") }.toList()
override fun getMetadataFilesPerSourceSet(doProcessFiles: Boolean): Map<String, FileCollection> {
val moduleString = "${module.groupId}-${module.moduleId}"
val transformedModuleRoot = run { baseDir.resolve(moduleString).also { it.mkdirs() } }
// TODO: once IJ supports non-JAR metadata dependencies, extract to a directory, not a JAR
// Also, if both IJ and the CLI compiler can read metadata from a path inside a JAR, then no operations will be needed
val resultFiles = mutableMapOf<String, FileCollection>()
val projectStructureMetadata = checkNotNull(getProjectStructureMetadata()) {
"can't extract metadata from a module without project structure metadata"
}
// val artifactJar :File= artifactBySourceSet.values().first()
artifactBySourceSet.forEach { (sourceSetName, artifact) ->
ZipFile(artifact).use { zip ->
val entries = zip.entries().asSequence().filter { it.name.startsWith("$sourceSetName/") }.toList()
if (entries.any()) {
val extension = projectStructureMetadata.sourceSetBinaryLayout[sourceSetName]?.archiveExtension
?: SourceSetMetadataLayout.METADATA.archiveExtension
// TODO: once IJ supports non-JAR metadata dependencies, extract to a directory, not a JAR
// Also, if both IJ and the CLI compiler can read metadata from a path inside a JAR, then no operations will be needed
val extractToJarFile = transformedModuleRoot.resolve("$moduleString-$sourceSetName.$extension")
resultFiles[sourceSetName] = project.files(extractToJarFile)
if (entries.any()) {
val extension = projectStructureMetadata.sourceSetBinaryLayout[sourceSetName]?.archiveExtension
?: SourceSetMetadataLayout.METADATA.archiveExtension
if (doProcessFiles) {
ZipOutputStream(extractToJarFile.outputStream()).use { resultZipOutput ->
for (entry in entries) {
if (entry.isDirectory)
continue
val extractToJarFile = transformedModuleRoot.resolve("$moduleString-$sourceSetName.$extension")
resultFiles[sourceSetName] = project.files(extractToJarFile)
// Drop the source set name from the entry path
val resultEntry = ZipEntry(entry.name.substringAfter("/"))
if (doProcessFiles) {
ZipOutputStream(extractToJarFile.outputStream()).use { resultZipOutput ->
for (entry in entries) {
if (entry.isDirectory)
continue
zip.getInputStream(entry).use { inputStream ->
resultZipOutput.putNextEntry(resultEntry)
inputStream.copyTo(resultZipOutput)
resultZipOutput.closeEntry()
// Drop the source set name from the entry path
val resultEntry = ZipEntry(entry.name.substringAfter("/"))
zip.getInputStream(entry).use { inputStream ->
resultZipOutput.putNextEntry(resultEntry)
inputStream.copyTo(resultZipOutput)
resultZipOutput.closeEntry()
}
}
}
}
}
}
}
}
return resultFiles
return resultFiles
}
}
}
// This class is needed to encapsulate how we extract the files and point to them in a way that doesn't capture the Gradle project state
internal abstract class ExtractableMetadataFiles {
abstract fun getMetadataFilesPerSourceSet(doProcessFiles: Boolean): Map<String, FileCollection>
}
@@ -15,22 +15,28 @@ import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope.*
import org.jetbrains.kotlin.gradle.plugin.sources.getSourceSetHierarchy
import org.jetbrains.kotlin.gradle.targets.metadata.ALL_COMPILE_METADATA_CONFIGURATION_NAME
import org.jetbrains.kotlin.gradle.targets.metadata.KotlinMetadataTargetConfigurator
import org.jetbrains.kotlin.gradle.utils.getValue
import java.io.File
import javax.inject.Inject
open class TransformKotlinGranularMetadata
@Inject constructor(
@get:Internal
@field:Transient
val kotlinSourceSet: KotlinSourceSet
) : DefaultTask() {
@get:OutputDirectory
val outputsDir: File = project.buildDir.resolve("kotlinSourceSetMetadata/${kotlinSourceSet.name}")
val outputsDir: File by project.provider {
project.buildDir.resolve("kotlinSourceSetMetadata/${kotlinSourceSet.name}")
}
@Suppress("unused") // Gradle input
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
internal val allSourceSetsMetadataConfiguration = project.configurations.getByName(ALL_COMPILE_METADATA_CONFIGURATION_NAME)
internal val allSourceSetsMetadataConfiguration: FileCollection by lazy {
project.files(project.configurations.getByName(ALL_COMPILE_METADATA_CONFIGURATION_NAME))
}
private val participatingSourceSets: Set<KotlinSourceSet>
get() = transformation.kotlinSourceSet.getSourceSetHierarchy().toMutableSet().apply {
@@ -40,15 +46,14 @@ open class TransformKotlinGranularMetadata
@Suppress("unused") // Gradle input
@get:Input
internal val inputSourceSetsAndCompilations: Map<String, Iterable<String>>
get() {
val sourceSets = participatingSourceSets
return CompilationSourceSetUtil.compilationsBySourceSets(project)
.filterKeys { it in sourceSets }
.entries.associate { (sourceSet, compilations) ->
sourceSet.name to compilations.map { it.name }.sorted()
}
internal val inputSourceSetsAndCompilations: Map<String, Iterable<String>> by project.provider {
val sourceSets = participatingSourceSets
CompilationSourceSetUtil.compilationsBySourceSets(project)
.filterKeys { it in sourceSets }
.entries.associate { (sourceSet, compilations) ->
sourceSet.name to compilations.map { it.name }.sorted()
}
}
private val participatingCompilations: Iterable<KotlinCompilation<*>>
get() {
@@ -58,13 +63,15 @@ open class TransformKotlinGranularMetadata
@Suppress("unused") // Gradle input
@get:Input
internal val inputCompilationDependencies: Map<String, Set<List<String?>>>
get() = participatingCompilations.associate {
internal val inputCompilationDependencies: Map<String, Set<List<String?>>> by project.provider {
participatingCompilations.associate {
it.name to project.configurations.getByName(it.compileDependencyConfigurationName)
.allDependencies.map { listOf(it.group, it.name, it.version) }.toSet()
}
}
@get:Internal
@delegate:Transient
internal val transformation: GranularMetadataTransformation by lazy {
GranularMetadataTransformation(
project,
@@ -82,14 +89,39 @@ open class TransformKotlinGranularMetadata
}
@get:Internal
internal val metadataDependencyResolutions: Iterable<MetadataDependencyResolution>
get() = transformation.metadataDependencyResolutions
@delegate:Transient // exclude from Gradle instant execution state
internal val metadataDependencyResolutions: Iterable<MetadataDependencyResolution> by project.provider {
transformation.metadataDependencyResolutions
}
//TODO avoid using it
@get:Internal
internal val filesByResolution: Map<out MetadataDependencyResolution, FileCollection>
get() = metadataDependencyResolutions.filterIsInstance<MetadataDependencyResolution.ChooseVisibleSourceSets>()
.associate { it to project.files(it.getMetadataFilesBySourceSet(outputsDir, doProcessFiles = false).values) }
// @get:Internal
// internal val filesByOriginalFiles: Map<out Iterable<File>, FileCollection> by project.provider {
// metadataDependencyResolutions.associate {
// it.originalArtifactFiles to project.files(
// when (it) {
// is MetadataDependencyResolution.ChooseVisibleSourceSets ->
// it.getMetadataFilesBySourceSet(outputsDir, doProcessFiles = false).values
// is MetadataDependencyResolution.ExcludeAsUnrequested ->
// emptyList()
// is MetadataDependencyResolution.KeepOriginalDependency ->
// it.originalArtifactFiles
// }
// )
// }
// }
private val extractableFiles by project.provider {
transformation.metadataDependencyResolutions
.filterIsInstance<MetadataDependencyResolution.ChooseVisibleSourceSets>()
.map { it.getExtractableMetadataFiles(outputsDir) }
}
@TaskAction
fun transformMetadata() {
if (outputsDir.isDirectory) {
@@ -97,8 +129,6 @@ open class TransformKotlinGranularMetadata
}
outputsDir.mkdirs()
metadataDependencyResolutions
.filterIsInstance<MetadataDependencyResolution.ChooseVisibleSourceSets>()
.forEach { it.getMetadataFilesBySourceSet(outputsDir, doProcessFiles = true) }
extractableFiles.forEach { it.getMetadataFilesPerSourceSet(doProcessFiles = true) }
}
}
@@ -69,6 +69,7 @@ internal class DefaultLanguageSettingsBuilder : LanguageSettingsBuilder {
}
/* A Kotlin task that is responsible for code analysis of the owner of this language settings builder. */
@Transient // not needed during Gradle Instant Execution
var compilerPluginOptionsTask: Lazy<AbstractCompile?> = lazyOf(null)
val compilerPluginArguments: List<String>?
@@ -76,7 +77,7 @@ internal class DefaultLanguageSettingsBuilder : LanguageSettingsBuilder {
val pluginOptionsTask = compilerPluginOptionsTask.value ?: return null
return when (pluginOptionsTask) {
is AbstractKotlinCompile<*> -> pluginOptionsTask.pluginOptions
is AbstractKotlinNativeCompile<*> -> pluginOptionsTask.compilerPluginOptions
is AbstractKotlinNativeCompile<*, *> -> pluginOptionsTask.compilerPluginOptions
else -> error("Unexpected task: $pluginOptionsTask")
}.arguments
}
@@ -86,7 +87,7 @@ internal class DefaultLanguageSettingsBuilder : LanguageSettingsBuilder {
val pluginClasspathTask = compilerPluginOptionsTask.value ?: return null
return when (pluginClasspathTask) {
is AbstractKotlinCompile<*> -> pluginClasspathTask.pluginClasspath
is AbstractKotlinNativeCompile<*> -> pluginClasspathTask.compilerPluginClasspath ?: pluginClasspathTask.project.files()
is AbstractKotlinNativeCompile<*, *> -> pluginClasspathTask.compilerPluginClasspath ?: pluginClasspathTask.project.files()
else -> error("Unexpected task: $pluginClasspathTask")
}
}
@@ -0,0 +1,215 @@
/*
* 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.plugin.statistics
import org.gradle.api.Project
import org.gradle.api.artifacts.DependencySet
import org.gradle.api.invocation.Gradle
import org.gradle.api.logging.Logging
import org.jetbrains.kotlin.gradle.utils.API
import org.jetbrains.kotlin.gradle.utils.COMPILE
import org.jetbrains.kotlin.gradle.utils.IMPLEMENTATION
import org.jetbrains.kotlin.gradle.utils.RUNTIME
import org.jetbrains.kotlin.statistics.BuildSessionLogger
import org.jetbrains.kotlin.statistics.metrics.BooleanMetrics
import org.jetbrains.kotlin.statistics.metrics.NumericalMetrics
import org.jetbrains.kotlin.statistics.metrics.StringMetrics
import java.io.File
import java.lang.management.ManagementFactory
import javax.management.MBeanServer
import javax.management.ObjectName
import kotlin.system.measureTimeMillis
class KotlinBuildStatHandler {
companion object {
@JvmStatic
internal fun getLogger() = Logging.getLogger(KotlinBuildStatHandler::class.java)
internal fun <T> runSafe(methodName: String, action: () -> T?): T? {
return try {
getLogger().debug("Executing [$methodName]")
action.invoke()
} catch (e: Throwable) {
logException("Could not execute [$methodName]", e)
null
}
}
internal fun logException(description: String, e: Throwable) {
getLogger().info(description)
getLogger().debug(e.message, e)
}
}
fun buildFinished(
gradle: Gradle?,
beanName: ObjectName,
sessionLogger: BuildSessionLogger,
action: String?,
failure: Throwable?
) {
runSafe("${KotlinBuildStatHandler::class.java}.buildFinished") {
try {
try {
if (gradle != null) reportGlobalMetrics(gradle, sessionLogger)
} finally {
sessionLogger.finishBuildSession(action, failure)
}
} finally {
val mbs: MBeanServer = ManagementFactory.getPlatformMBeanServer()
if (mbs.isRegistered(beanName)) {
mbs.unregisterMBean(beanName)
}
}
}
}
internal fun reportGlobalMetrics(gradle: Gradle, sessionLogger: BuildSessionLogger) {
System.getProperty("os.name")?.also {
sessionLogger.report(StringMetrics.OS_TYPE, System.getProperty("os.name"))
}
sessionLogger.report(NumericalMetrics.CPU_NUMBER_OF_CORES, Runtime.getRuntime().availableProcessors().toLong())
sessionLogger.report(StringMetrics.GRADLE_VERSION, gradle.gradleVersion)
sessionLogger.report(BooleanMetrics.EXECUTED_FROM_IDEA, System.getProperty("idea.active") != null)
sessionLogger.report(NumericalMetrics.GRADLE_DAEMON_HEAP_SIZE, Runtime.getRuntime().maxMemory())
sessionLogger.report(
BooleanMetrics.KOTLIN_OFFICIAL_CODESTYLE,
gradle.rootProject.properties["kotlin.code.style"] == "official"
) // constants are saved in IDEA plugin and could not be accessed directly
gradle.taskGraph.whenReady() { taskExecutionGraph ->
val executedTaskNames = taskExecutionGraph.allTasks.map { it.name }.distinct()
report(sessionLogger, BooleanMetrics.COMPILATION_STARTED, executedTaskNames.contains("compileKotlin"), null)
report(sessionLogger, BooleanMetrics.TESTS_EXECUTED, executedTaskNames.contains("compileTestKotlin"), null)
report(sessionLogger, BooleanMetrics.MAVEN_PUBLISH_EXECUTED, executedTaskNames.contains("install"), null)
}
fun buildSrcExists(project: Project) = File(project.projectDir, "buildSrc").exists()
if (buildSrcExists(gradle.rootProject)) {
sessionLogger.report(BooleanMetrics.BUILD_SRC_EXISTS, true)
}
val statisticOverhead = measureTimeMillis {
gradle.allprojects { project ->
for (configuration in project.configurations) {
val configurationName = configuration.name
val dependencies = configuration.dependencies
when (configurationName) {
"kapt" -> {
sessionLogger.report(BooleanMetrics.ENABLED_KAPT, true)
dependencies?.forEach { dependency ->
when (dependency.group) {
"com.google.dagger" -> sessionLogger.report(BooleanMetrics.ENABLED_DAGGER, true)
"com.android.databinding" -> sessionLogger.report(BooleanMetrics.ENABLED_DATABINDING, true)
}
}
}
API -> {
sessionLogger.report(NumericalMetrics.CONFIGURATION_API_COUNT, 1)
reportLibrariesVersions(sessionLogger, dependencies)
}
IMPLEMENTATION -> {
sessionLogger.report(NumericalMetrics.CONFIGURATION_IMPLEMENTATION_COUNT, 1)
reportLibrariesVersions(sessionLogger, dependencies)
}
COMPILE -> {
sessionLogger.report(NumericalMetrics.CONFIGURATION_COMPILE_COUNT, 1)
reportLibrariesVersions(sessionLogger, dependencies)
}
RUNTIME -> {
sessionLogger.report(NumericalMetrics.CONFIGURATION_RUNTIME_COUNT, 1)
reportLibrariesVersions(sessionLogger, dependencies)
}
}
}
sessionLogger.report(NumericalMetrics.NUMBER_OF_SUBPROJECTS, 1)
sessionLogger.report(BooleanMetrics.KOTLIN_KTS_USED, project.buildscript.sourceFile?.name?.endsWith(".kts") ?: false)
sessionLogger.report(NumericalMetrics.GRADLE_NUMBER_OF_TASKS, project.tasks.names.size.toLong())
sessionLogger.report(
NumericalMetrics.GRADLE_NUMBER_OF_UNCONFIGURED_TASKS,
project.tasks.names.count { name ->
try {
project.tasks.named(name).javaClass.name.contains("TaskCreatingProvider")
} catch (_: Exception) {
true
}
}.toLong()
)
if (buildSrcExists(project)) {
sessionLogger.report(NumericalMetrics.BUILD_SRC_COUNT, 1)
sessionLogger.report(BooleanMetrics.BUILD_SRC_EXISTS, true)
}
}
}
sessionLogger.report(NumericalMetrics.STATISTICS_VISIT_ALL_PROJECTS_OVERHEAD, statisticOverhead)
}
private fun reportLibrariesVersions(sessionLogger: BuildSessionLogger, dependencies: DependencySet?) {
dependencies?.forEach { dependency ->
when {
dependency.group?.startsWith("org.springframework") ?: false -> sessionLogger.report(
StringMetrics.LIBRARY_SPRING_VERSION,
dependency.version ?: "0.0.0"
)
dependency.group?.startsWith("com.vaadin") ?: false -> sessionLogger.report(
StringMetrics.LIBRARY_VAADIN_VERSION,
dependency.version ?: "0.0.0"
)
dependency.group?.startsWith("com.google.gwt") ?: false -> sessionLogger.report(
StringMetrics.LIBRARY_GWT_VERSION,
dependency.version ?: "0.0.0"
)
dependency.group?.startsWith("org.hibernate") ?: false -> sessionLogger.report(
StringMetrics.LIBRARY_HIBERNATE_VERSION,
dependency.version ?: "0.0.0"
)
dependency.group == "org.jetbrains.kotlin" && dependency.name.startsWith("kotlin-stdlib") -> sessionLogger.report(
StringMetrics.KOTLIN_STDLIB_VERSION,
dependency.version ?: "0.0.0"
)
dependency.group == "org.jetbrains.kotlinx" && dependency.name == "kotlinx-coroutines" -> sessionLogger.report(
StringMetrics.KOTLIN_COROUTINES_VERSION,
dependency.version ?: "0.0.0"
)
dependency.group == "org.jetbrains.kotlin" && dependency.name == "kotlin-reflect" -> sessionLogger.report(
StringMetrics.KOTLIN_REFLECT_VERSION,
dependency.version ?: "0.0.0"
)
dependency.group == "org.jetbrains.kotlinx" && dependency.name
.startsWith("kotlinx-serialization-runtime") -> sessionLogger.report(
StringMetrics.KOTLIN_SERIALIZATION_VERSION,
dependency.version ?: "0.0.0"
)
dependency.group == "com.android.tools.build" && dependency.name.startsWith("gradle") -> sessionLogger.report(
StringMetrics.ANDROID_GRADLE_PLUGIN_VERSION,
dependency.version ?: "0.0.0"
)
}
}
}
internal fun report(sessionLogger: BuildSessionLogger, metric: BooleanMetrics, value: Boolean, subprojectName: String?) {
runSafe("report metric ${metric.name}") {
sessionLogger.report(metric, value, subprojectName)
}
}
internal fun report(sessionLogger: BuildSessionLogger, metric: NumericalMetrics, value: Long, subprojectName: String?) {
runSafe("report metric ${metric.name}") {
sessionLogger.report(metric, value, subprojectName)
}
}
internal fun report(sessionLogger: BuildSessionLogger, metric: StringMetrics, value: String, subprojectName: String?) {
runSafe("report metric ${metric.name}") {
sessionLogger.report(metric, value, subprojectName)
}
}
}
@@ -0,0 +1,66 @@
/*
* 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.plugin.statistics
import org.gradle.api.invocation.Gradle
import org.gradle.tooling.events.FinishEvent
import org.gradle.tooling.events.OperationCompletionListener
import org.gradle.tooling.events.configuration.ProjectConfigurationFinishEvent
import org.jetbrains.kotlin.statistics.BuildSessionLogger
import java.lang.management.ManagementFactory
import javax.management.MBeanServer
import javax.management.ObjectName
open class KotlinBuildStatListener(val beanName: ObjectName/*, val gradle: Gradle*/) : OperationCompletionListener, AutoCloseable {
private var projectEvaluatedTime: Long? = null
override fun onFinish(event: FinishEvent?) {
if (event is ProjectConfigurationFinishEvent) {
projectEvaluatedTime = event.eventTime
}
//todo is it any chance to get failure exception?
//todo nothing to do?
// KotlinBuildStatHandler.runSafe("${KotlinBuildStatListener::class.java}.onFinish") {
//
//
// try {
// val finishTime = event?.result?.endTime
// val startTime = event?.result?.startTime
// report(NumericalMetrics.GRADLE_BUILD_DURATION, finishTime - it.buildStartedTime)
// report(NumericalMetrics.GRADLE_EXECUTION_DURATION, finishTime - it.projectEvaluatedTime)
// report(NumericalMetrics.BUILD_FINISH_TIME, finishTime)
// } finally {
// val mbs: MBeanServer = ManagementFactory.getPlatformMBeanServer()
// if (mbs.isRegistered(beanName)) {
// mbs.unregisterMBean(beanName)
// }
// }
// }
}
override fun close() {
// val sessionLogger = BuildSessionLogger(gradle.gradleUserHomeDir)
// KotlinBuildStatHandler.runSafe("${KotlinBuildStatListener::class.java}.close()") {
// try {
// try {
// KotlinBuildStatHandler().reportGlobalMetrics(gradle, sessionLogger)
// } finally {
//// report(NumericalMetrics.GRADLE_BUILD_DURATION, finishTime - it.buildStartedTime)
//// report(NumericalMetrics.GRADLE_EXECUTION_DURATION, finishTime - it.projectEvaluatedTime)
//// report(NumericalMetrics.BUILD_FINISH_TIME, finishTime)
// }
//
// } finally {
// val mbs: MBeanServer = ManagementFactory.getPlatformMBeanServer()
// if (mbs.isRegistered(beanName)) {
// mbs.unregisterMBean(beanName)
// }
// }
// }
}
}
@@ -8,15 +8,13 @@ package org.jetbrains.kotlin.gradle.plugin.statistics
import org.gradle.BuildAdapter
import org.gradle.BuildResult
import org.gradle.api.Project
import org.gradle.api.artifacts.DependencySet
import org.gradle.api.invocation.Gradle
import org.gradle.api.logging.Logging
import org.gradle.initialization.BuildRequestMetaData
import org.gradle.invocation.DefaultGradle
import org.jetbrains.kotlin.gradle.utils.API
import org.jetbrains.kotlin.gradle.utils.COMPILE
import org.jetbrains.kotlin.gradle.utils.IMPLEMENTATION
import org.jetbrains.kotlin.gradle.utils.RUNTIME
import org.jetbrains.kotlin.gradle.plugin.BuildEventsListenerRegistryHolder
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatHandler.Companion.runSafe
import org.jetbrains.kotlin.gradle.utils.isConfigurationCacheAvailable
import org.jetbrains.kotlin.statistics.BuildSessionLogger
import org.jetbrains.kotlin.statistics.BuildSessionLogger.Companion.STATISTICS_FOLDER_NAME
import org.jetbrains.kotlin.statistics.metrics.BooleanMetrics
@@ -87,8 +85,10 @@ internal abstract class KotlinBuildStatsService internal constructor() : BuildAd
*/
@JvmStatic
@Synchronized
internal fun getOrCreateInstance(gradle: Gradle): IStatisticsValuesConsumer? {
internal fun getOrCreateInstance(project: Project, listenerRegistryHolder: BuildEventsListenerRegistryHolder): IStatisticsValuesConsumer? {
return runSafe("${KotlinBuildStatsService::class.java}.getOrCreateInstance") {
val gradle = project.gradle
statisticsIsEnabled = statisticsIsEnabled ?: checkStatisticsEnabled(gradle)
if (statisticsIsEnabled != true) {
null
@@ -106,13 +106,17 @@ internal abstract class KotlinBuildStatsService internal constructor() : BuildAd
)
instance = JMXKotlinBuildStatsService(mbs, beanName)
} else {
val kotlinBuildStatProvider = project.provider{ KotlinBuildStatListener(beanName) }
val newInstance = DefaultKotlinBuildStatsService(gradle, beanName)
instance = newInstance
log.debug("Instantiated ${KotlinBuildStatsService::class.java}: new instance $instance")
mbs.registerMBean(StandardMBean(newInstance, KotlinBuildStatsMXBean::class.java), beanName)
}
}
gradle.addBuildListener(instance)
if (!isConfigurationCacheAvailable(gradle)) {
gradle.addBuildListener(instance)
}
instance
}
}
@@ -130,7 +134,7 @@ internal abstract class KotlinBuildStatsService internal constructor() : BuildAd
}
this.report(NumericalMetrics.STATISTICS_COLLECT_METRICS_OVERHEAD, duration)
} catch (e: Throwable) {
logException("Could collect statistics metrics", e)
KotlinBuildStatHandler.logException("Could collect statistics metrics", e)
}
}
}
@@ -149,21 +153,6 @@ internal abstract class KotlinBuildStatsService internal constructor() : BuildAd
gradle.rootProject.properties[ENABLE_STATISTICS_PROPERTY_NAME]?.toString()?.toBoolean() ?: DEFAULT_STATISTICS_STATE
}
}
private fun logException(description: String, e: Throwable) {
getLogger().info(description)
getLogger().debug(e.message, e)
}
internal fun <T> runSafe(methodName: String, action: () -> T?): T? {
return try {
getLogger().debug("Executing [$methodName]")
action.invoke()
} catch (e: Throwable) {
logException("Could not execute [$methodName]", e)
null
}
}
}
}
@@ -212,132 +201,6 @@ internal class DefaultKotlinBuildStatsService internal constructor(
return (gradle as? DefaultGradle)?.services?.get(BuildRequestMetaData::class.java)?.startTime
}
private fun reportLibrariesVersions(dependencies: DependencySet?) {
dependencies?.forEach { dependency ->
when {
dependency.group?.startsWith("org.springframework") ?: false -> sessionLogger.report(
StringMetrics.LIBRARY_SPRING_VERSION,
dependency.version ?: "0.0.0"
)
dependency.group?.startsWith("com.vaadin") ?: false -> sessionLogger.report(
StringMetrics.LIBRARY_VAADIN_VERSION,
dependency.version ?: "0.0.0"
)
dependency.group?.startsWith("com.google.gwt") ?: false -> sessionLogger.report(
StringMetrics.LIBRARY_GWT_VERSION,
dependency.version ?: "0.0.0"
)
dependency.group?.startsWith("org.hibernate") ?: false -> sessionLogger.report(
StringMetrics.LIBRARY_HIBERNATE_VERSION,
dependency.version ?: "0.0.0"
)
dependency.group == "org.jetbrains.kotlin" && dependency.name.startsWith("kotlin-stdlib") -> sessionLogger.report(
StringMetrics.KOTLIN_STDLIB_VERSION,
dependency.version ?: "0.0.0"
)
dependency.group == "org.jetbrains.kotlinx" && dependency.name == "kotlinx-coroutines" -> sessionLogger.report(
StringMetrics.KOTLIN_COROUTINES_VERSION,
dependency.version ?: "0.0.0"
)
dependency.group == "org.jetbrains.kotlin" && dependency.name == "kotlin-reflect" -> sessionLogger.report(
StringMetrics.KOTLIN_REFLECT_VERSION,
dependency.version ?: "0.0.0"
)
dependency.group == "org.jetbrains.kotlinx" && dependency.name
.startsWith("kotlinx-serialization-runtime") -> sessionLogger.report(
StringMetrics.KOTLIN_SERIALIZATION_VERSION,
dependency.version ?: "0.0.0"
)
dependency.group == "com.android.tools.build" && dependency.name.startsWith("gradle") -> sessionLogger.report(
StringMetrics.ANDROID_GRADLE_PLUGIN_VERSION,
dependency.version ?: "0.0.0"
)
}
}
}
private fun reportGlobalMetrics(gradle: Gradle) {
System.getProperty("os.name")?.also {
sessionLogger.report(StringMetrics.OS_TYPE, System.getProperty("os.name"))
}
sessionLogger.report(NumericalMetrics.CPU_NUMBER_OF_CORES, Runtime.getRuntime().availableProcessors().toLong())
sessionLogger.report(StringMetrics.GRADLE_VERSION, gradle.gradleVersion)
sessionLogger.report(BooleanMetrics.EXECUTED_FROM_IDEA, System.getProperty("idea.active") != null)
sessionLogger.report(NumericalMetrics.GRADLE_DAEMON_HEAP_SIZE, Runtime.getRuntime().maxMemory())
sessionLogger.report(
BooleanMetrics.KOTLIN_OFFICIAL_CODESTYLE,
gradle.rootProject.properties["kotlin.code.style"] == "official"
) // constants are saved in IDEA plugin and could not be accessed directly
gradle.taskGraph.whenReady() { taskExecutionGraph ->
val executedTaskNames = taskExecutionGraph.allTasks.map { it.name }.distinct()
report(BooleanMetrics.COMPILATION_STARTED, executedTaskNames.contains("compileKotlin"))
report(BooleanMetrics.TESTS_EXECUTED, executedTaskNames.contains("compileTestKotlin"))
report(BooleanMetrics.MAVEN_PUBLISH_EXECUTED, executedTaskNames.contains("install"))
}
fun buildSrcExists(project: Project) = File(project.projectDir, "buildSrc").exists()
if (buildSrcExists(gradle.rootProject)) {
sessionLogger.report(BooleanMetrics.BUILD_SRC_EXISTS, true)
}
val statisticOverhead = measureTimeMillis {
gradle.allprojects { project ->
for (configuration in project.configurations) {
val configurationName = configuration.name
val dependencies = configuration.dependencies
when (configurationName) {
"kapt" -> {
sessionLogger.report(BooleanMetrics.ENABLED_KAPT, true)
dependencies?.forEach { dependency ->
when (dependency.group) {
"com.google.dagger" -> sessionLogger.report(BooleanMetrics.ENABLED_DAGGER, true)
"com.android.databinding" -> sessionLogger.report(BooleanMetrics.ENABLED_DATABINDING, true)
}
}
}
API -> {
sessionLogger.report(NumericalMetrics.CONFIGURATION_API_COUNT, 1)
reportLibrariesVersions(dependencies)
}
IMPLEMENTATION -> {
sessionLogger.report(NumericalMetrics.CONFIGURATION_IMPLEMENTATION_COUNT, 1)
reportLibrariesVersions(dependencies)
}
COMPILE -> {
sessionLogger.report(NumericalMetrics.CONFIGURATION_COMPILE_COUNT, 1)
reportLibrariesVersions(dependencies)
}
RUNTIME -> {
sessionLogger.report(NumericalMetrics.CONFIGURATION_RUNTIME_COUNT, 1)
reportLibrariesVersions(dependencies)
}
}
}
sessionLogger.report(NumericalMetrics.NUMBER_OF_SUBPROJECTS, 1)
sessionLogger.report(BooleanMetrics.KOTLIN_KTS_USED, project.buildscript.sourceFile?.name?.endsWith(".kts") ?: false)
sessionLogger.report(NumericalMetrics.GRADLE_NUMBER_OF_TASKS, project.tasks.names.size.toLong())
sessionLogger.report(
NumericalMetrics.GRADLE_NUMBER_OF_UNCONFIGURED_TASKS,
project.tasks.names.count { name ->
try {
project.tasks.named(name).javaClass.name.contains("TaskCreatingProvider")
} catch (_: Exception) {
true
}
}.toLong()
)
if (buildSrcExists(project)) {
sessionLogger.report(NumericalMetrics.BUILD_SRC_COUNT, 1)
sessionLogger.report(BooleanMetrics.BUILD_SRC_EXISTS, true)
}
}
}
sessionLogger.report(NumericalMetrics.STATISTICS_VISIT_ALL_PROJECTS_OVERHEAD, statisticOverhead)
}
override fun projectsEvaluated(gradle: Gradle) {
runSafe("${DefaultKotlinBuildStatsService::class.java}.projectEvaluated") {
if (!sessionLogger.isBuildSessionStarted()) {
@@ -351,41 +214,20 @@ internal class DefaultKotlinBuildStatsService internal constructor(
@Synchronized
override fun buildFinished(result: BuildResult) {
runSafe("${DefaultKotlinBuildStatsService::class.java}.buildFinished") {
try {
try {
val gradle = result.gradle
if (gradle != null) reportGlobalMetrics(gradle)
} finally {
sessionLogger.finishBuildSession(result.action, result.failure)
}
} finally {
val mbs: MBeanServer = ManagementFactory.getPlatformMBeanServer()
if (mbs.isRegistered(beanName)) {
mbs.unregisterMBean(beanName)
}
instance = null
}
}
KotlinBuildStatHandler().buildFinished(result.gradle, beanName, sessionLogger, result.action, result.failure)
instance = null
}
override fun report(metric: BooleanMetrics, value: Boolean, subprojectName: String?) {
runSafe("report metric ${metric.name}") {
sessionLogger.report(metric, value, subprojectName)
}
KotlinBuildStatHandler().report(sessionLogger, metric, value, subprojectName)
}
override fun report(metric: NumericalMetrics, value: Long, subprojectName: String?) {
runSafe("report metric ${metric.name}") {
sessionLogger.report(metric, value, subprojectName)
}
KotlinBuildStatHandler().report(sessionLogger, metric, value, subprojectName)
}
override fun report(metric: StringMetrics, value: String, subprojectName: String?) {
runSafe("report metric ${metric.name}") {
sessionLogger.report(metric, value, subprojectName)
}
KotlinBuildStatHandler().report(sessionLogger, metric, value, subprojectName)
}
override fun reportBoolean(name: String, value: Boolean, subprojectName: String?) {
@@ -16,11 +16,10 @@ import org.jetbrains.kotlin.gradle.logging.kotlinDebug
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskExecutionResults
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
import org.jetbrains.kotlin.gradle.utils.isConfigurationCacheAvailable
import java.io.File
import java.lang.StringBuilder
import java.text.SimpleDateFormat
import java.util.*
import kotlin.math.max
internal fun configureBuildReporter(gradle: Gradle, log: Logger) {
val rootProject = gradle.rootProject
@@ -40,8 +39,11 @@ internal fun configureBuildReporter(gradle: Gradle, log: Logger) {
val ts = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(Calendar.getInstance().time)
val perfReportFile = perfLogDir.resolve("${gradle.rootProject.name}-build-$ts.txt")
val reporter = KotlinBuildReporter(gradle, perfReportFile)
gradle.addBuildListener(reporter)
if (!isConfigurationCacheAvailable(gradle)) {
val reporter = KotlinBuildReporter(gradle, perfReportFile)
gradle.addBuildListener(reporter)
}
val buildReportMode = if (properties.buildReportVerbose) BuildReportMode.VERBOSE else BuildReportMode.SIMPLE
@@ -112,94 +114,7 @@ internal class KotlinBuildReporter(
@Synchronized
override fun buildFinished(result: BuildResult) {
val logger = result.gradle?.rootProject?.logger
try {
perfReportFile.writeText(buildInfo(result) + taskOverview() + tasksSb.toString())
logger?.lifecycle("Kotlin build report is written to ${perfReportFile.canonicalPath}")
} catch (e: Throwable) {
logger?.error("Could not write Kotlin build report to ${perfReportFile.canonicalPath}", e)
}
}
private fun buildInfo(result: BuildResult): String {
val startParams = arrayListOf<String>()
gradle.startParameter.apply {
startParams.add("tasks = ${taskRequests.joinToString { it.args.toString() }}")
startParams.add("excluded tasks = $excludedTaskNames")
startParams.add("current dir = $currentDir")
startParams.add("project properties args = $projectProperties")
startParams.add("system properties args = $systemPropertiesArgs")
}
return buildString {
appendln("Gradle start parameters:")
startParams.forEach {
appendln(" $it")
}
if (result.failure != null) {
appendln("Build failed: ${result.failure}")
}
appendln()
}
}
private fun taskOverview(): String {
if (kotlinTaskTimeNs.isEmpty()) return buildString { appendln("No Kotlin task was run") }
val sb = StringBuilder()
val kotlinTotalTimeNs = kotlinTaskTimeNs.values.sum()
val ktTaskPercent = (kotlinTotalTimeNs.toDouble() / allTasksTimeNs * 100).asString(1)
sb.appendln("Total time for Kotlin tasks: ${formatTime(kotlinTotalTimeNs)} ($ktTaskPercent % of all tasks time)")
val table = TextTable("Time", "% of Kotlin time", "Task")
kotlinTaskTimeNs.entries
.sortedByDescending { (_, timeNs) -> timeNs }
.forEach { (task, timeNs) ->
val percent = (timeNs.toDouble() / kotlinTotalTimeNs * 100).asString(1)
table.addRow(formatTime(timeNs), "$percent %", task.path)
}
table.printTo(sb)
sb.appendln()
return sb.toString()
}
private fun formatTime(ns: Long): String {
val seconds = ns.toDouble() / 1_000_000_000
return seconds.asString(2) + " s"
}
private fun Double.asString(decPoints: Int): String =
String.format("%.${decPoints}f", this)
private class TextTable(vararg columnNames: String) {
private val rows = ArrayList<List<String>>()
private val columnsCount = columnNames.size
private val maxLengths = IntArray(columnsCount) { columnNames[it].length }
init {
rows.add(columnNames.toList())
}
fun addRow(vararg row: String) {
check(row.size == columnsCount) { "Row size ${row.size} differs from columns count $columnsCount" }
rows.add(row.toList())
for ((i, col) in row.withIndex()) {
maxLengths[i] = max(maxLengths[i], col.length)
}
}
fun printTo(sb: StringBuilder) {
for (row in rows) {
sb.appendln()
for ((i, col) in row.withIndex()) {
if (i > 0) sb.append("|")
sb.append(col.padEnd(maxLengths[i], ' '))
}
}
}
KotlinBuildReporterHandler().buildFinished(gradle, perfReportFile, kotlinTaskTimeNs.mapKeys{it.key.path}, allTasksTimeNs, result.failure)
}
}
@@ -0,0 +1,110 @@
/*
* 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.report
import org.gradle.api.invocation.Gradle
import java.io.File
import java.util.*
import kotlin.math.max
class KotlinBuildReporterHandler {
fun buildFinished(
gradle: Gradle,
perfReportFile: File,
kotlinTaskTimeNs: Map<String, Long>,
allTasksTimeNs: Long,
failure: Throwable? = null
) {
val logger = gradle.rootProject.logger
try {
perfReportFile.writeText(buildInfo(gradle, failure) + taskOverview(kotlinTaskTimeNs, allTasksTimeNs))
logger.lifecycle("Kotlin build report is written to ${perfReportFile.canonicalPath}")
} catch (e: Throwable) {
logger.error("Could not write Kotlin build report to ${perfReportFile.canonicalPath}", e)
}
}
private fun buildInfo(gradle: Gradle, failure: Throwable?): String {
val startParams = arrayListOf<String>()
gradle.startParameter.apply {
startParams.add("tasks = ${taskRequests.joinToString { it.args.toString() }}")
startParams.add("excluded tasks = $excludedTaskNames")
startParams.add("current dir = $currentDir")
startParams.add("project properties args = $projectProperties")
startParams.add("system properties args = $systemPropertiesArgs")
}
return buildString {
appendln("Gradle start parameters:")
startParams.forEach {
appendln(" $it")
}
if (failure != null) {
appendln("Build failed: ${failure}")
}
appendln()
}
}
internal fun taskOverview(kotlinTaskTimeNs: Map<String, Long>, allTasksTimeNs: Long): String {
if (kotlinTaskTimeNs.isEmpty()) return buildString { appendln("No Kotlin task was run") }
val sb = StringBuilder()
val kotlinTotalTimeNs = kotlinTaskTimeNs.values.sum()
val ktTaskPercent = (kotlinTotalTimeNs.toDouble() / allTasksTimeNs * 100).asString(1)
sb.appendln("Total time for Kotlin tasks: ${formatTime(kotlinTotalTimeNs)} ($ktTaskPercent % of all tasks time)")
val table = TextTable("Time", "% of Kotlin time", "Task")
kotlinTaskTimeNs.entries
.sortedByDescending { (_, timeNs) -> timeNs }
.forEach { (taskPath, timeNs) ->
val percent = (timeNs.toDouble() / kotlinTotalTimeNs * 100).asString(1)
table.addRow(formatTime(timeNs), "$percent %", taskPath)
}
table.printTo(sb)
sb.appendln()
return sb.toString()
}
internal class TextTable(vararg columnNames: String) {
private val rows = ArrayList<List<String>>()
private val columnsCount = columnNames.size
private val maxLengths = IntArray(columnsCount) { columnNames[it].length }
init {
rows.add(columnNames.toList())
}
fun addRow(vararg row: String) {
check(row.size == columnsCount) { "Row size ${row.size} differs from columns count $columnsCount" }
rows.add(row.toList())
for ((i, col) in row.withIndex()) {
maxLengths[i] = max(maxLengths[i], col.length)
}
}
fun printTo(sb: StringBuilder) {
for (row in rows) {
sb.appendln()
for ((i, col) in row.withIndex()) {
if (i > 0) sb.append("|")
sb.append(col.padEnd(maxLengths[i], ' '))
}
}
}
}
}
internal fun formatTime(ns: Long): String {
val seconds = ns.toDouble() / 1_000_000_000
return seconds.asString(2) + " s"
}
internal fun Double.asString(decPoints: Int): String =
String.format("%.${decPoints}f", this)
@@ -0,0 +1,34 @@
/*
* 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.report
import com.intellij.util.text.DateFormatUtil.formatTime
import org.gradle.api.invocation.Gradle
import org.gradle.tooling.events.FinishEvent
import org.gradle.tooling.events.OperationCompletionListener
import org.gradle.tooling.events.task.TaskOperationDescriptor
import java.io.File
import java.util.*
class KotlinBuildReporterListener(val gradle: Gradle, val perfReportFile: File) : OperationCompletionListener, AutoCloseable {
private val kotlinTaskTimeNs = HashMap<String, Long>()
internal var allTasksTimeNs: Long = 0L
override fun onFinish(event: FinishEvent?) {
val descriptor = event?.descriptor
if (descriptor is TaskOperationDescriptor) {
val taskPath = descriptor.taskPath
val executionTime = event.result.endTime - event.result.startTime
allTasksTimeNs += executionTime
kotlinTaskTimeNs[taskPath] = executionTime
}
}
override fun close() {
KotlinBuildReporterHandler().buildFinished(gradle, perfReportFile, kotlinTaskTimeNs, allTasksTimeNs)
}
}
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.gradle.targets.js.ir
import org.gradle.api.tasks.CacheableTask
import org.gradle.workers.WorkerExecutor
import org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers
import org.jetbrains.kotlin.gradle.tasks.GradleCompileTaskProvider
import javax.inject.Inject
@CacheableTask
@@ -16,5 +17,5 @@ internal open class KotlinJsIrLinkWithWorkers
constructor(
private val workerExecutor: WorkerExecutor
) : KotlinJsIrLink() {
override fun compilerRunner() = GradleCompilerRunnerWithWorkers(this, workerExecutor)
override fun compilerRunner() = GradleCompilerRunnerWithWorkers(GradleCompileTaskProvider(this), workerExecutor)
}
@@ -10,6 +10,7 @@ import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.Project
import org.gradle.api.artifacts.Dependency
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.provider.Provider
import org.gradle.jvm.tasks.Jar
import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
@@ -44,10 +45,12 @@ open class KotlinWithJavaTarget<KotlinOptionsType : KotlinCommonOptions>(
KotlinWithJavaCompilationFactory(project, this, kotlinOptionsFactory)
)
internal val defaultArtifactClassesListFile: File
get() {
private val layout = project.layout
internal val defaultArtifactClassesListFile: Provider<File> =
layout.buildDirectory.dir(KOTLIN_BUILD_DIR_NAME).map {
val jarTask = project.tasks.getByName(artifactsTaskName) as Jar
return File(File(project.buildDir, KOTLIN_BUILD_DIR_NAME), "${sanitizeFileName(jarTask.archiveFileName.get())}-classes.txt")
it.file("${sanitizeFileName(jarTask.archiveFileName.get())}-classes.txt").asFile
}
}
@@ -457,6 +457,31 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) :
}
}
)
/*
val transformedFilesByOriginalFiles = project.provider {
transformationTaskHolders
.flatMap { it.get().filesByOriginalFiles.toList() }
.groupBy({ it.first }, valueTransform = { it.second })
}
val builtBySet = mutableSetOf<FileCollection>()
val resultFiles = project.files(Callable {
val originalFiles = fromFiles.toSet()
val filesToAdd = mutableSetOf<File>()
val filesToExclude = mutableSetOf<File>()
transformedFilesByOriginalFiles.get().forEach { (original, replacement) ->
if (original.all { it in originalFiles }) {
builtBySet.addAll(replacement)
filesToAdd += replacement.flatMap { it.files }
filesToExclude += original
}
}
originalFiles - filesToExclude + filesToAdd
})
return resultFiles.builtBy(builtBySet)
*/
}
private fun createCommonMainElementsConfiguration(target: KotlinMetadataTarget) {
@@ -10,9 +10,7 @@ import groovy.lang.Closure
import org.gradle.api.Action
import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.Project
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.FileCollection
import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.tasks.TaskProvider
import org.gradle.util.ConfigureUtil
import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions
@@ -156,7 +156,7 @@ open class KotlinNativeTargetConfigurator<T : KotlinNativeTarget>(
val compileTaskProvider = registerTask<KotlinNativeCompile>(
compilation.compileKotlinTaskName
) {
it.compilation = compilation
it.compilation.set(compilation)
it.group = BasePlugin.BUILD_GROUP
it.description = "Compiles a klibrary from the '${compilation.name}' " +
"compilation for target '${compilation.platformType.name}'."
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.gradle.targets.native.internal.PlatformLibrariesGene
import org.jetbrains.kotlin.gradle.targets.native.internal.setUpKotlinNativePlatformDependencies
import org.jetbrains.kotlin.gradle.utils.NativeCompilerDownloader
import org.jetbrains.kotlin.gradle.utils.SingleActionPerProject
import org.jetbrains.kotlin.gradle.utils.isConfigurationCacheAvailable
import org.jetbrains.kotlin.konan.CompilerVersion
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget
@@ -89,11 +90,9 @@ abstract class AbstractKotlinNativeTargetPreset<T : KotlinNativeTarget>(
createTargetConfigurator().configureTarget(result)
SingleActionPerProject.run(project, "setUpKotlinNativePlatformDependencies") {
project.gradle.addListener(object : BuildAdapter() {
override fun projectsEvaluated(gradle: Gradle) {
project.setUpKotlinNativePlatformDependencies()
}
})
project.gradle.projectsEvaluated {
project.setUpKotlinNativePlatformDependencies()
}
}
if (!konanTarget.enabledOnCurrentHost) {
@@ -8,11 +8,9 @@ package org.jetbrains.kotlin.gradle.plugin.cocoapods
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Sync
import org.gradle.api.tasks.TaskProvider
import org.jetbrains.kotlin.gradle.tasks.registerTask
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
@@ -20,10 +18,7 @@ import org.jetbrains.kotlin.gradle.plugin.addExtension
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.plugin.whenEvaluated
import org.jetbrains.kotlin.gradle.targets.native.tasks.*
import org.jetbrains.kotlin.gradle.tasks.DefFileTask
import org.jetbrains.kotlin.gradle.tasks.DummyFrameworkTask
import org.jetbrains.kotlin.gradle.tasks.FatFrameworkTask
import org.jetbrains.kotlin.gradle.tasks.PodspecTask
import org.jetbrains.kotlin.gradle.tasks.*
import org.jetbrains.kotlin.gradle.utils.asValidTaskName
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import org.jetbrains.kotlin.konan.target.HostManager
@@ -16,8 +16,8 @@ import org.jetbrains.kotlin.gradle.plugin.addExtension
import org.jetbrains.kotlin.gradle.plugin.mpp.disambiguateName
import org.jetbrains.kotlin.gradle.targets.native.tasks.NativePerformanceReport
import org.jetbrains.kotlin.gradle.tasks.registerTask
import org.jetbrains.kotlin.gradle.utils.isConfigurationCacheAvailable
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import java.io.File
import java.util.concurrent.ConcurrentHashMap
class TaskTime(val startTime: Long) {
@@ -66,17 +66,26 @@ open class KotlinPerformancePlugin : Plugin<Project> {
private fun configureTasks(project: Project, performanceExtension: PerformanceExtension) {
// Add time listener.
val timeListener = TaskTimerListener(project)
project.gradle.addListener(timeListener)
performanceExtension.trackedBinaries.forEach { binary ->
val perfReport = project.registerTask<NativePerformanceReport>(binary.target.disambiguateName(lowerCamelCaseName("perfReport", binary.name))) {
it.binary = binary
it.settings = performanceExtension
it.timeListener = timeListener
it.group = TASK_GROUP
it.description = "Report performance measurement results for binary '${binary.name}' of target '${binary.target.name}'."
if (!isConfigurationCacheAvailable(project.gradle)) {
val timeListener = TaskTimerListener(project)
project.gradle.addListener(timeListener)
performanceExtension.trackedBinaries.forEach { binary ->
val perfReport = project.registerTask<NativePerformanceReport>(
binary.target.disambiguateName(
lowerCamelCaseName(
"perfReport",
binary.name
)
)
) {
it.binary = binary
it.settings = performanceExtension
it.timeListener = timeListener
it.group = TASK_GROUP
it.description = "Report performance measurement results for binary '${binary.name}' of target '${binary.target.name}'."
}
binary.linkTaskProvider.configure { linkTask -> linkTask.finalizedBy(perfReport) }
}
binary.linkTaskProvider.configure { linkTask -> linkTask.finalizedBy(perfReport) }
}
}
@@ -70,15 +70,15 @@ open class NativePerformanceReport : DefaultTask() {
}
private fun getPerformanceCompilerOptions() =
(compilerFlagsFromBinary() + binary.linkTask.compilation.kotlinOptions.freeCompilerArgs)
(compilerFlagsFromBinary() + binary.linkTask.compilation.get().kotlinOptions.freeCompilerArgs)
.filter { it in listOf("-g", "-opt", "-Xg0") }.map { "\"$it\"" }
@TaskAction
fun generate() {
val compileTasks = if (settings.includeAssociatedTasks)
getAllExecutedTasks(binary.linkTask.compilation)
getAllExecutedTasks(binary.linkTask.compilation.get())
else
listOf(binary.linkTask.compilation.compileKotlinTask)
listOf(binary.linkTask.compilation.get().compileKotlinTask)
val allExecutedTasks = listOf(binary.linkTask) + compileTasks
val upToDateTasks = allExecutedTasks.filter { it.state.upToDate }.map { it.name }
if (upToDateTasks.isNotEmpty()) {
@@ -13,8 +13,8 @@ import org.gradle.api.artifacts.*
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.FileCollection
import org.gradle.api.file.FileTree
import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.logging.Logger
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*
import org.gradle.api.tasks.compile.AbstractCompile
@@ -26,12 +26,12 @@ import org.jetbrains.kotlin.gradle.dsl.NativeCacheKind
import org.jetbrains.kotlin.gradle.internal.ensureParentDirsCreated
import org.jetbrains.kotlin.gradle.plugin.LanguageSettingsBuilder
import org.jetbrains.kotlin.gradle.plugin.cocoapods.asValidFrameworkName
import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.sources.DefaultLanguageSettingsBuilder
import org.jetbrains.kotlin.gradle.targets.native.internal.isAllowCommonizer
import org.jetbrains.kotlin.gradle.utils.getValue
import org.jetbrains.kotlin.gradle.utils.klibModuleName
import org.jetbrains.kotlin.gradle.utils.newProperty
import org.jetbrains.kotlin.gradle.utils.listFilesOrEmpty
import org.jetbrains.kotlin.konan.library.KLIB_INTEROP_IR_PROVIDER_IDENTIFIER
import org.jetbrains.kotlin.konan.properties.saveToFile
@@ -112,7 +112,7 @@ private fun Collection<File>.filterKlibsPassedToCompiler(project: Project) = fil
}
// endregion
abstract class AbstractKotlinNativeCompile<T : KotlinCommonToolOptions> : AbstractCompile() {
abstract class AbstractKotlinNativeCompile<T : KotlinCommonToolOptions, K : AbstractKotlinNativeCompilation> : AbstractCompile() {
init {
sourceCompatibility = "1.6"
@@ -120,7 +120,7 @@ abstract class AbstractKotlinNativeCompile<T : KotlinCommonToolOptions> : Abstra
}
@get:Internal
abstract val compilation: AbstractKotlinNativeCompilation
abstract val compilation: Provider<K>
// region inputs/outputs
@get:Input
@@ -136,20 +136,21 @@ abstract class AbstractKotlinNativeCompile<T : KotlinCommonToolOptions> : Abstra
abstract val baseName: String
// Inputs and outputs
val libraries: FileCollection
@InputFiles get() =
// Avoid resolving these dependencies during task graph construction when we can't build the target:
if (compilation.konanTarget.enabledOnCurrentHost)
compilation.compileDependencyFiles.filterOutPublishableInteropLibs(project)
else project.files()
@get:InputFiles
val libraries: FileCollection by project.provider {
// Avoid resolving these dependencies during task graph construction when we can't build the target:
if (compilation.get().konanTarget.enabledOnCurrentHost)
compilation.get().compileDependencyFiles.filterOutPublishableInteropLibs(project)
else project.files()
}
override fun getClasspath(): FileCollection = libraries
override fun setClasspath(configuration: FileCollection?) {
throw UnsupportedOperationException("Setting classpath directly is unsupported.")
}
val target: String
@Input get() = compilation.konanTarget.name
@get:Input
val target: String by project.provider { compilation.get().konanTarget.name }
// region Compiler options.
@get:Internal
@@ -158,11 +159,12 @@ abstract class AbstractKotlinNativeCompile<T : KotlinCommonToolOptions> : Abstra
abstract fun kotlinOptions(fn: Closure<*>)
@get:Input
abstract val additionalCompilerOptions: Collection<String>
abstract val additionalCompilerOptions: Provider<Collection<String>>
@get:Internal
val languageSettings: LanguageSettingsBuilder
get() = compilation.defaultSourceSet.languageSettings
val languageSettings: LanguageSettingsBuilder by lazy {
compilation.get().defaultSourceSet.languageSettings
}
@get:Input
val progressiveMode: Boolean
@@ -170,8 +172,8 @@ abstract class AbstractKotlinNativeCompile<T : KotlinCommonToolOptions> : Abstra
// endregion.
@get:Input
val enableEndorsedLibs: Boolean
get() = compilation.enableEndorsedLibs
val enableEndorsedLibs by
project.provider { compilation.map { it.enableEndorsedLibs }.get() }
val kotlinNativeVersion: String
@Input get() = project.konanVersion.toString()
@@ -179,11 +181,11 @@ abstract class AbstractKotlinNativeCompile<T : KotlinCommonToolOptions> : Abstra
// OutputFile is located under the destinationDir, so there is no need to register it as a separate output.
@Internal
val outputFile: Provider<File> = project.provider {
val konanTarget = compilation.konanTarget
val konanTarget = compilation.get().konanTarget
val prefix = outputKind.prefix(konanTarget)
val suffix = outputKind.suffix(konanTarget)
val filename = "$prefix$baseName$suffix".let {
val filename = "$prefix${baseName}$suffix".let {
when {
outputKind == FRAMEWORK ->
it.asValidFrameworkName()
@@ -241,10 +243,10 @@ abstract class AbstractKotlinNativeCompile<T : KotlinCommonToolOptions> : Abstra
addKey("-progressive", progressiveMode)
if (!defaultsOnly) {
addAll(additionalCompilerOptions)
addAll(additionalCompilerOptions.get())
}
(compilation.defaultSourceSet.languageSettings as? DefaultLanguageSettingsBuilder)?.run {
(compilation.get().defaultSourceSet.languageSettings as? DefaultLanguageSettingsBuilder)?.run {
addAll(freeCompilerArgs)
}
}
@@ -274,7 +276,7 @@ abstract class AbstractKotlinNativeCompile<T : KotlinCommonToolOptions> : Abstra
addArg("-target", target)
addArg("-p", outputKind.name.toLowerCase())
if (compilation is KotlinSharedNativeCompilation) {
if (compilation.get() is KotlinSharedNativeCompilation) {
add("-Xexpect-actual-linker")
add("-Xmetadata-klib")
addArg("-manifest", manifestFile.get().absolutePath)
@@ -301,7 +303,7 @@ abstract class AbstractKotlinNativeCompile<T : KotlinCommonToolOptions> : Abstra
val output = outputFile.get()
output.parentFile.mkdirs()
if (compilation is KotlinSharedNativeCompilation) {
if (compilation.get() is KotlinSharedNativeCompilation) {
val manifestFile: File = manifestFile.get()
manifestFile.ensureParentDirsCreated()
val properties = java.util.Properties()
@@ -316,9 +318,12 @@ abstract class AbstractKotlinNativeCompile<T : KotlinCommonToolOptions> : Abstra
/**
* A task producing a klibrary from a compilation.
*/
open class KotlinNativeCompile : AbstractKotlinNativeCompile<KotlinCommonOptions>(), KotlinCompile<KotlinCommonOptions> {
open class KotlinNativeCompile : AbstractKotlinNativeCompile<KotlinCommonOptions, AbstractKotlinNativeCompilation>(),
KotlinCompile<KotlinCommonOptions> {
@Internal
final override lateinit var compilation: AbstractKotlinNativeCompilation
@Transient // can't be serialized for Gradle configuration avoidance
final override val compilation: Property<AbstractKotlinNativeCompilation> =
project.newProperty()
@get:Input
override val outputKind = LIBRARY
@@ -330,8 +335,11 @@ open class KotlinNativeCompile : AbstractKotlinNativeCompile<KotlinCommonOptions
override val debuggable = true
@get:Internal
override val baseName: String
get() = if (compilation.isMain()) project.name else "${project.name}_${compilation.name}"
override val baseName: String by
compilation.map { if (it.isMain()) project.name else "${project.name}_${it.name}" }
// Store as an explicit provider in order to allow Gradle Instant Execution to capture the state
// private val allSourceProvider = compilation.map { project.files(it.allSources).asFileTree }
@get:Input
val moduleName: String by project.provider {
@@ -347,13 +355,21 @@ open class KotlinNativeCompile : AbstractKotlinNativeCompile<KotlinCommonOptions
@get:Internal // these sources are normally a subset of `source` ones which are already tracked
val commonSources: ConfigurableFileCollection = project.files()
// private val commonSources: FileCollection by lazy {
// // Already taken into account in getSources method.
// project.files(compilation.map { it.commonSources }).asFileTree
// }
private val commonSourcesTree: FileTree
get() = commonSources.asFileTree
private val friendModule: FileCollection?
get() = project.files(
project.provider { compilation.associateWithTransitiveClosure.map { it.output.allOutputs } + compilation.friendArtifacts }
private val friendModule: FileCollection by compilation.map { compilationInstance ->
project.files(
compilationInstance.associateWithTransitiveClosure.map { it.output.allOutputs },
compilationInstance.friendArtifacts
)
}
// endregion.
// region Language settings imported from a SourceSet.
@@ -372,11 +388,12 @@ open class KotlinNativeCompile : AbstractKotlinNativeCompile<KotlinCommonOptions
// region Kotlin options.
override val kotlinOptions: KotlinCommonOptions
get() = compilation.kotlinOptions
get() = compilation.get().kotlinOptions
@get:Input
override val additionalCompilerOptions: Collection<String>
get() = kotlinOptions.freeCompilerArgs
override val additionalCompilerOptions: Provider<Collection<String>> = project.provider {
kotlinOptions.freeCompilerArgs
}
override fun kotlinOptions(fn: KotlinCommonOptions.() -> Unit) {
kotlinOptions.fn()
@@ -409,7 +426,7 @@ open class KotlinNativeCompile : AbstractKotlinNativeCompile<KotlinCommonOptions
// Configure FQ module name to avoid cyclic dependencies in klib manifests (see KT-36721).
addArg("-module-name", moduleName)
add("-Xshort-module-name=$shortModuleName")
val friends = friendModule?.files
val friends = friendModule.files
if (friends != null && friends.isNotEmpty()) {
addArg("-friend-modules", friends.map { it.absolutePath }.joinToString(File.pathSeparator))
}
@@ -427,22 +444,23 @@ open class KotlinNativeCompile : AbstractKotlinNativeCompile<KotlinCommonOptions
/**
* A task producing a final binary from a compilation.
*/
open class KotlinNativeLink : AbstractKotlinNativeCompile<KotlinCommonToolOptions>() {
open class KotlinNativeLink : AbstractKotlinNativeCompile<KotlinCommonToolOptions, KotlinNativeCompilation>() {
@get:Internal
@Transient // can't be serialized for Gradle configuration avoidance
final override val compilation: Property<KotlinNativeCompilation> = project.newProperty() { binary.compilation }
init {
dependsOn(project.provider { compilation.compileKotlinTask })
dependsOn(project.provider { compilation.get().compileKotlinTask })
}
@Internal
@Transient
lateinit var binary: NativeBinary
@get:Internal
override val compilation: KotlinNativeCompilation
get() = binary.compilation
@Internal // Taken into account by getSources().
val intermediateLibrary: Provider<File> = project.provider {
compilation.compileKotlinTask.outputFile.get()
val intermediateLibrary: Provider<File> = compilation.map { compilationInstance ->
compilationInstance.compileKotlinTask.outputFile.get()
}
@InputFiles
@@ -459,27 +477,23 @@ open class KotlinNativeLink : AbstractKotlinNativeCompile<KotlinCommonToolOption
binary.outputDirectory = destinationDir
}
@get:Input
override val outputKind: CompilerOutputKind
get() = binary.outputKind.compilerOutputKind
@Input get() = binary.outputKind.compilerOutputKind
@get:Input
override val optimized: Boolean
get() = binary.optimized
@Input get() = binary.optimized
@get:Input
override val debuggable: Boolean
get() = binary.debuggable
@Input get() = binary.debuggable
@get:Internal
override val baseName: String
get() = binary.baseName
@Input get() = binary.baseName
@get:Input
protected val konanCacheKind: NativeCacheKind
get() = project.konanCacheKind
inner class NativeLinkOptions: KotlinCommonToolOptions {
inner class NativeLinkOptions : KotlinCommonToolOptions {
override var allWarningsAsErrors: Boolean = false
override var suppressWarnings: Boolean = false
override var verbose: Boolean = false
@@ -488,8 +502,9 @@ open class KotlinNativeLink : AbstractKotlinNativeCompile<KotlinCommonToolOption
// We propagate compilation free args to the link task for now (see KT-33717).
@get:Input
override val additionalCompilerOptions: Collection<String>
get() = kotlinOptions.freeCompilerArgs + compilation.kotlinOptions.freeCompilerArgs
override val additionalCompilerOptions: Provider<Collection<String>> = compilation.map { compilationInstance ->
kotlinOptions.freeCompilerArgs + compilationInstance.kotlinOptions.freeCompilerArgs
}
override val kotlinOptions: KotlinCommonToolOptions = NativeLinkOptions()
@@ -503,18 +518,16 @@ open class KotlinNativeLink : AbstractKotlinNativeCompile<KotlinCommonToolOption
}
// Binary-specific options.
@get:Optional
@get:Input
val entryPoint: String?
@Input
@Optional
get() = (binary as? Executable)?.entryPoint
@get:Input
val linkerOpts: List<String>
get() = binary.linkerOpts
@Input get() = binary.linkerOpts
@get:Input
val processTests: Boolean
get() = binary is TestExecutable
@Input get() = binary is TestExecutable
@get:InputFiles
val exportLibraries: FileCollection
@@ -527,12 +540,14 @@ open class KotlinNativeLink : AbstractKotlinNativeCompile<KotlinCommonToolOption
}
@get:Input
val isStaticFramework: Boolean
get() = binary.let { it is Framework && it.isStatic }
val isStaticFramework: Boolean by project.provider {
binary.let { it is Framework && it.isStatic }
}
@get:Input
val embedBitcode: Framework.BitcodeEmbeddingMode
get() = (binary as? Framework)?.embedBitcode ?: Framework.BitcodeEmbeddingMode.DISABLE
val embedBitcode: Framework.BitcodeEmbeddingMode by project.provider {
(binary as? Framework)?.embedBitcode ?: Framework.BitcodeEmbeddingMode.DISABLE
}
override fun buildCompilerArgs(): List<String> = mutableListOf<String>().apply {
addAll(super.buildCompilerArgs())
@@ -569,9 +584,12 @@ open class KotlinNativeLink : AbstractKotlinNativeCompile<KotlinCommonToolOption
override fun buildSourceArgs(): List<String> =
listOf("-Xinclude=${intermediateLibrary.get().absolutePath}")
@get:Internal
val apiFilesProvider = compilation.map {project.configurations.getByName(it.apiConfigurationName).files.filterKlibsPassedToCompiler(project)}
private fun validatedExportedLibraries() {
val exportConfiguration = exportLibraries as? Configuration ?: return
val apiFiles = project.configurations.getByName(compilation.apiConfigurationName).files.filterKlibsPassedToCompiler(project)
val apiFiles = apiFilesProvider.get()
val failed = mutableSetOf<Dependency>()
exportConfiguration.allDependencies.forEach {
@@ -765,7 +783,11 @@ internal class CacheBuilder(val project: Project, val binary: NativeBinary) {
private val String.cachedName
get() = getCacheFileName(this, konanCacheKind)
private fun ensureCompilerProvidedLibPrecached(platformLibName: String, platformLibs: Map<String, File>, visitedLibs: MutableSet<String>) {
private fun ensureCompilerProvidedLibPrecached(
platformLibName: String,
platformLibs: Map<String, File>,
visitedLibs: MutableSet<String>
) {
if (platformLibName in visitedLibs)
return
visitedLibs += platformLibName
@@ -11,6 +11,7 @@ import org.gradle.api.plugins.JavaPluginConvention
import org.gradle.api.tasks.*
import org.jetbrains.kotlin.gradle.dsl.KotlinSingleJavaTargetExtension
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast
import org.jetbrains.kotlin.gradle.utils.newProperty
import java.io.File
@@ -27,18 +28,35 @@ internal open class InspectClassesForMultiModuleIC : DefaultTask() {
@Suppress("MemberVisibilityCanBePrivate")
@get:OutputFile
internal val classesListFile: File by lazy {
(project.kotlinExtension as KotlinSingleJavaTargetExtension).target.defaultArtifactClassesListFile
(project.kotlinExtension as KotlinSingleJavaTargetExtension).target.defaultArtifactClassesListFile.get()
}
@get:InputFiles
internal val sourceSetOutputClassesDir by lazy {
project.convention.findPlugin(JavaPluginConvention::class.java)?.sourceSets?.findByName(sourceSetName)?.output?.classesDirs
}
@get:Internal
internal val fileTrees
get() = sourceSetOutputClassesDir?.map {
if (isGradleVersionAtLeast(6, 0)) {
objects.fileTree().from(it).include("**/*.class")
} else {
project.fileTree(it).include("**/*.class")
}
}
@get:Internal
internal val objects = project.objects
@Suppress("MemberVisibilityCanBePrivate")
@get:InputFiles
internal val classFiles: FileCollection
get() {
val convention = project.convention.findPlugin(JavaPluginConvention::class.java)
val sourceSet = convention?.sourceSets?.findByName(sourceSetName) ?: return project.files()
val fileTrees = sourceSet.output.classesDirs.map { project.fileTree(it).include("**/*.class") }
return project.files(fileTrees)
if (sourceSetOutputClassesDir != null) {
return objects.fileCollection().from(fileTrees)
}
return objects.fileCollection()
}
@TaskAction
@@ -96,7 +96,7 @@ open class KotlinCompileCommon : AbstractKotlinCompile<K2MetadataCompilerArgumen
override fun callCompilerAsync(args: K2MetadataCompilerArguments, sourceRoots: SourceRoots, changedFiles: ChangedFiles) {
val messageCollector = GradlePrintingMessageCollector(logger, args.allWarningsAsErrors)
val outputItemCollector = OutputItemsCollectorImpl()
val compilerRunner = compilerRunner()
val compilerRunner = compilerRunner
val environment = GradleCompilerEnvironment(
computedCompilerClasspath, messageCollector, outputItemCollector,
buildReportMode = buildReportMode,
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.gradle.tasks
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.file.FileCollection
import org.gradle.api.logging.Logger
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*
import org.gradle.api.tasks.compile.AbstractCompile
@@ -20,6 +21,7 @@ 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.ChangedFiles
@@ -35,8 +37,12 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.AbstractKotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.associateWithTransitiveClosure
import org.jetbrains.kotlin.gradle.plugin.mpp.ownModuleName
import org.jetbrains.kotlin.gradle.report.BuildReportMode
import org.jetbrains.kotlin.gradle.utils.*
import org.jetbrains.kotlin.gradle.utils.getValue
import org.jetbrains.kotlin.gradle.utils.isParentOf
import org.jetbrains.kotlin.gradle.utils.pathsAsStringRelativeTo
import org.jetbrains.kotlin.gradle.utils.toSortedPathsArray
import org.jetbrains.kotlin.incremental.ChangedFiles
import org.jetbrains.kotlin.incremental.IncrementalModuleInfo
import org.jetbrains.kotlin.library.impl.isKotlinLibrary
import org.jetbrains.kotlin.utils.JsLibraryUtils
import java.io.File
@@ -87,7 +93,7 @@ abstract class AbstractKotlinCompileTool<T : CommonToolArguments>
@get:Classpath
@get:InputFiles
internal val computedCompilerClasspath: List<File> by project.provider {
internal val computedCompilerClasspath: List<File> by lazy {
compilerClasspath?.takeIf { it.isNotEmpty() }
?: compilerJarFile?.let {
// a hack to remove compiler jar from the cp, will be dropped when compilerJarFile will be removed
@@ -113,19 +119,51 @@ abstract class AbstractKotlinCompileTool<T : CommonToolArguments>
protected abstract fun findKotlinCompilerClasspath(project: Project): List<File>
}
public class GradleCompileTaskProvider {
constructor(task: Task) {
buildDir = task.project.buildDir
projectDir = task.project.rootProject.projectDir
rootDir = task.project.rootProject.rootDir
sessionsDir = GradleCompilerRunner.sessionsDir(task.project)
projectName = task.project.rootProject.name.normalizeForFlagFile()
buildModulesInfo = GradleCompilerRunner.buildModulesInfo(task.project.gradle)
path = task.path
logger = task.logger
}
val path: String
val logger: Logger
val buildDir: File /*= project.buildDir*/
val projectDir: File /*= project.rootProject.projectDir*/
val rootDir: File /*= project.rootProject.rootDir*/
val sessionsDir: File/* = GradleCompilerRunner.sessionsDir(project)*/
val projectName: String /*= project.rootProject.name.normalizeForFlagFile()*/
val buildModulesInfo: IncrementalModuleInfo /*= GradleCompilerRunner.buildModulesInfo(project.gradle)*/
}
abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractKotlinCompileTool<T>() {
init {
cacheOnlyIfEnabledForKotlin()
}
@get:Internal
private val layout = project.layout
// avoid creating directory in getter: this can lead to failure in parallel build
@get:LocalState
internal val taskBuildDirectory: File by project.provider {
File(File(project.buildDir, KOTLIN_BUILD_DIR_NAME), name)
}
internal val taskBuildDirectory: File by layout.buildDirectory.dir(KOTLIN_BUILD_DIR_NAME).map { it.file(name).asFile }
override fun localStateDirectories(): FileCollection = project.files(taskBuildDirectory)
@get:Internal
internal val projectObjects = project.objects
@get:LocalState
internal val localStateDirectoriesProvider: FileCollection = projectObjects.fileCollection().from(taskBuildDirectory)
override fun localStateDirectories(): FileCollection = localStateDirectoriesProvider
// indicates that task should compile kotlin incrementally if possible
// it's not possible when IncrementalTaskInputs#isIncremental returns false (i.e first build)
@@ -147,9 +185,7 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractKo
internal var buildReportMode: BuildReportMode? = null
@get:Internal
internal val taskData: KotlinCompileTaskData by project.provider {
KotlinCompileTaskData.get(project, name)
}
internal val taskData: KotlinCompileTaskData = KotlinCompileTaskData.get(project, name)
@get:Input
internal open var useModuleDetection: Boolean
@@ -162,11 +198,9 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractKo
protected val multiModuleICSettings: MultiModuleICSettings
get() = MultiModuleICSettings(taskData.buildHistoryFile, useModuleDetection)
@get:Classpath
@get:InputFiles
val pluginClasspath: FileCollection by project.provider {
project.configurations.getByName(PLUGIN_CLASSPATH_CONFIGURATION_NAME)
}
@get:Classpath
val pluginClasspath: FileCollection = project.configurations.getByName(PLUGIN_CLASSPATH_CONFIGURATION_NAME)
@get:Internal
internal val pluginOptions = CompilerPluginOptions()
@@ -175,10 +209,13 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractKo
@get:InputFiles
protected val additionalClasspath = arrayListOf<File>()
@get:Internal
internal val objects = project.objects
// Store this file collection before it is filtered by File::exists to ensure that Gradle Instant execution doesn't serialize the
// filtered files, losing those that don't exist yet and will only be created during build
private val compileClasspathImpl by project.provider {
classpath + project.files(additionalClasspath)
classpath + objects.fileCollection().from(additionalClasspath)
}
@get:Internal // classpath already participates in the checks
@@ -197,8 +234,9 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractKo
sourceFilesExtensionsSources.add(extensions)
}
private val kotlinExt: KotlinProjectExtension
get() = project.extensions.findByType(KotlinProjectExtension::class.java)!!
@get:Internal
@field:Transient
internal val kotlinExtProvider: KotlinProjectExtension = project.extensions.findByType(KotlinProjectExtension::class.java)!!
override fun getDestinationDir(): File =
taskData.destinationDir.get()
@@ -218,13 +256,13 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractKo
@get:Internal
internal var coroutinesFromGradleProperties: Coroutines? = null
// Input is needed to force rebuild even if source files are not changed
@get:Input
internal val coroutinesStr: String
get() = coroutines.name
@get:Internal
internal val coroutines: Coroutines by project.provider {
kotlinExt.experimental.coroutines
@get:Input
internal val coroutinesStr: Provider<String> = project.provider {coroutines.get().name}
@get:Input
internal val coroutines: Provider<Coroutines> = project.provider {
kotlinExtProvider.experimental.coroutines
?: coroutinesFromGradleProperties
?: Coroutines.DEFAULT
}
@@ -242,7 +280,7 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractKo
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
internal var commonSourceSet: FileCollection = project.files()
internal var commonSourceSet: FileCollection = project.files() //TODO
@get:Input
internal val moduleName: String by project.provider {
@@ -262,8 +300,10 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractKo
private val kotlinLogger by lazy { GradleKotlinLogger(logger) }
internal open fun compilerRunner(): GradleCompilerRunner =
GradleCompilerRunner(this)
@get:Internal
internal val compilerRunner by lazy { compilerRunner() }
internal open fun compilerRunner(): GradleCompilerRunner = GradleCompilerRunner(GradleCompileTaskProvider(this))
@TaskAction
fun execute(inputs: IncrementalTaskInputs) {
@@ -297,6 +337,9 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractKo
return !inputs.isIncremental && getSourceRoots().kotlinSourceFiles.isEmpty()
}
@get:Internal
private val projectDir = project.rootProject.projectDir
private fun executeImpl(inputs: IncrementalTaskInputs) {
// Check that the JDK tools are available in Gradle (fail-fast, instead of a fail during the compiler run):
findToolsJar()
@@ -304,7 +347,7 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractKo
val sourceRoots = getSourceRoots()
val allKotlinSources = sourceRoots.kotlinSourceFiles
logger.kotlinDebug { "All kotlin sources: ${allKotlinSources.pathsAsStringRelativeTo(project.rootProject.projectDir)}" }
logger.kotlinDebug { "All kotlin sources: ${allKotlinSources.pathsAsStringRelativeTo(projectDir)}" }
if (skipCondition(inputs)) {
// Skip running only if non-incremental run. Otherwise, we may need to do some cleanup.
@@ -327,13 +370,14 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractKo
*/
internal abstract fun callCompilerAsync(args: T, sourceRoots: SourceRoots, changedFiles: ChangedFiles)
@get:Input
internal val isMultiplatform: Boolean by lazy { project.plugins.any { it is KotlinPlatformPluginBase || it is KotlinMultiplatformPluginWrapper } }
@get:Internal
internal val isMultiplatform: Boolean by project.provider {
project.plugins.any { it is KotlinPlatformPluginBase || it is KotlinMultiplatformPluginWrapper }
}
internal val abstractKotlinCompileArgumentsContributor by lazy { AbstractKotlinCompileArgumentsContributor(KotlinCompileArgumentsProvider(this)) }
override fun setupCompilerArgs(args: T, defaultsOnly: Boolean, ignoreClasspathResolutionErrors: Boolean) {
AbstractKotlinCompileArgumentsContributor(thisTaskProvider).contributeArguments(
abstractKotlinCompileArgumentsContributor.contributeArguments(
args,
compilerArgumentsConfigurationFlags(defaultsOnly, ignoreClasspathResolutionErrors)
)
@@ -350,6 +394,45 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractKo
}
}
open class KotlinCompileArgumentsProvider<T : AbstractKotlinCompile<out CommonCompilerArguments>>(taskProvider: T) {
val coroutines: Provider<Coroutines>
val logger: Logger
val isMultiplatform: Boolean
val pluginClasspath: FileCollection
val pluginOptions: CompilerPluginOptions
init {
coroutines = taskProvider.coroutines
logger = taskProvider.logger
isMultiplatform = taskProvider.isMultiplatform
pluginClasspath = taskProvider.pluginClasspath
pluginOptions = taskProvider.pluginOptions
}
}
class KotlinJvmCompilerArgumentsProvider
(taskProvider: KotlinCompile) : KotlinCompileArgumentsProvider<KotlinCompile>(taskProvider) {
val moduleName: String
val friendPaths: Array<String>
val compileClasspath: Iterable<File>
val destinationDir: File
internal val kotlinOptions: List<KotlinJvmOptionsImpl?>
init {
// super(taskProvider)
moduleName = taskProvider.moduleName
friendPaths = taskProvider.friendPaths
compileClasspath = taskProvider.compileClasspath
destinationDir = taskProvider.destinationDir
kotlinOptions = listOfNotNull(
taskProvider.parentKotlinOptionsImpl as KotlinJvmOptionsImpl?,
taskProvider.kotlinOptions as KotlinJvmOptionsImpl
)
}
}
internal inline val <reified T : Task> T.thisTaskProvider: TaskProvider<out T>
get() = checkNotNull(project.locateTask<T>(name))
@@ -397,8 +480,8 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
}
@get:Internal
internal val compilerArgumentsContributor: CompilerArgumentsContributor<K2JVMCompilerArguments> by project.provider {
KotlinJvmCompilerArgumentsContributor(thisTaskProvider)
internal val compilerArgumentsContributor: CompilerArgumentsContributor<K2JVMCompilerArguments> by lazy {
KotlinJvmCompilerArgumentsContributor(KotlinJvmCompilerArgumentsProvider(this))
}
@Internal
@@ -409,7 +492,7 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
val messageCollector = GradlePrintingMessageCollector(logger, args.allWarningsAsErrors)
val outputItemCollector = OutputItemsCollectorImpl()
val compilerRunner = compilerRunner()
val compilerRunner = compilerRunner
val icEnv = if (isIncrementalCompilationEnabled()) {
logger.info(USING_JVM_INCREMENTAL_COMPILATION_MESSAGE)
@@ -417,7 +500,7 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
if (hasFilesInTaskBuildDirectory()) changedFiles else ChangedFiles.Unknown(),
taskBuildDirectory,
usePreciseJavaTracking = usePreciseJavaTracking,
disableMultiModuleIC = disableMultiModuleIC(),
disableMultiModuleIC = disableMultiModuleIC,
multiModuleICSettings = multiModuleICSettings
)
} else null
@@ -439,31 +522,32 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
)
}
private fun disableMultiModuleIC(): Boolean {
if (!isIncrementalCompilationEnabled() || javaOutputDir == null) return false
@get:Input
val disableMultiModuleIC: Boolean by lazy {
if (!isIncrementalCompilationEnabled() || javaOutputDir == null) {
false
} else {
var illegalTaskOrNull: AbstractCompile? = null
project.tasks.configureEach {
if (it is AbstractCompile &&
it !is JavaCompile &&
it !is AbstractKotlinCompile<*> &&
javaOutputDir!!.isParentOf(it.destinationDir)
) {
illegalTaskOrNull = illegalTaskOrNull ?: it
var illegalTaskOrNull: AbstractCompile? = null
project.tasks.configureEach {
if (it is AbstractCompile &&
it !is JavaCompile &&
it !is AbstractKotlinCompile<*> &&
javaOutputDir!!.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
}
illegalTaskOrNull?.let { illegalTask ->
logger.info(
"Kotlin inter-project IC is disabled: " +
"unknown task '$illegalTask' destination dir ${illegalTask.destinationDir} " +
"intersects with java destination dir $javaOutputDir"
)
return true
}
return false
}
// override setSource to track source directory sets and files (for generated android folders)
@@ -483,21 +567,26 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
internal open class KotlinCompileWithWorkers @Inject constructor(
private val workerExecutor: WorkerExecutor
) : KotlinCompile() {
override fun compilerRunner() = GradleCompilerRunnerWithWorkers(this, workerExecutor)
@get:Internal
val compilerRunnerValue = GradleCompilerRunnerWithWorkers(GradleCompileTaskProvider(this), workerExecutor)
override fun compilerRunner() = compilerRunnerValue
}
@CacheableTask
internal open class Kotlin2JsCompileWithWorkers @Inject constructor(
private val workerExecutor: WorkerExecutor
) : Kotlin2JsCompile() {
override fun compilerRunner() = GradleCompilerRunnerWithWorkers(this, workerExecutor)
override fun compilerRunner() = GradleCompilerRunnerWithWorkers(GradleCompileTaskProvider(this), workerExecutor)
}
@CacheableTask
internal open class KotlinCompileCommonWithWorkers @Inject constructor(
private val workerExecutor: WorkerExecutor
) : KotlinCompileCommon() {
override fun compilerRunner() = GradleCompilerRunnerWithWorkers(this, workerExecutor)
override fun compilerRunner() = GradleCompilerRunnerWithWorkers(GradleCompileTaskProvider(this), workerExecutor)
}
@CacheableTask
@@ -507,8 +596,7 @@ open class Kotlin2JsCompile : AbstractKotlinCompile<K2JSCompilerArguments>(), Ko
incremental = true
}
override val kotlinOptions: KotlinJsOptions
get() = taskData.compilation.kotlinOptions as KotlinJsOptions
override val kotlinOptions = taskData.compilation.kotlinOptions as KotlinJsOptions
@get:Internal
protected val defaultOutputFile: File
@@ -601,6 +689,9 @@ open class Kotlin2JsCompile : AbstractKotlinCompile<K2JSCompilerArguments>(), Ko
JsLibraryUtils::isKotlinJavascriptLibrary
}
@get:Internal
internal val absolutePathProvider = project.projectDir.absolutePath
override fun callCompilerAsync(args: K2JSCompilerArguments, sourceRoots: SourceRoots, changedFiles: ChangedFiles) {
sourceRoots as SourceRoots.KotlinOnly
@@ -624,14 +715,14 @@ open class Kotlin2JsCompile : AbstractKotlinCompile<K2JSCompilerArguments>(), Ko
args.friendModules = friendDependencies.joinToString(File.pathSeparator)
if (args.sourceMapBaseDirs == null && !args.sourceMapPrefix.isNullOrEmpty()) {
args.sourceMapBaseDirs = project.projectDir.absolutePath
args.sourceMapBaseDirs = absolutePathProvider
}
logger.kotlinDebug("compiling with args ${ArgumentUtils.convertArgumentsToStringList(args)}")
val messageCollector = GradlePrintingMessageCollector(logger, args.allWarningsAsErrors)
val outputItemCollector = OutputItemsCollectorImpl()
val compilerRunner = compilerRunner()
val compilerRunner = compilerRunner
val icEnv = if (isIncrementalCompilationEnabled()) {
logger.info(USING_JS_INCREMENTAL_COMPILATION_MESSAGE)
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.gradle.utils
import org.gradle.api.invocation.Gradle
import org.gradle.util.GradleVersion
internal data class ParsedGradleVersion(val major: Int, val minor: Int) : Comparable<ParsedGradleVersion> {
@@ -56,4 +57,12 @@ internal data class ParsedGradleVersion(val major: Int, val minor: Int) : Compar
fun isGradleVersionAtLeast(major: Int, minor: Int) =
ParsedGradleVersion.parse(GradleVersion.current().version)
?.let { it >= ParsedGradleVersion(major, minor) } ?: false
?.let { it >= ParsedGradleVersion(major, minor) } ?: false
fun isConfigurationCacheAvailable(gradle: Gradle) =
try {
val startParameters = gradle.startParameter
startParameters.javaClass.getMethod("isConfigurationCache").invoke(startParameters) as? Boolean
} catch (_: Exception) {
null
} ?: false
@@ -6,7 +6,6 @@
package org.jetbrains.kotlin.gradle.utils
import org.gradle.api.Project
import org.gradle.api.file.RegularFile
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
@@ -20,14 +19,6 @@ internal operator fun <T> Property<T>.setValue(thisRef: Any?, property: KPropert
set(value)
}
internal fun <T : Any> Project.newProperty(initialize: (() -> T)? = null): Property<T> =
@Suppress("UNCHECKED_CAST")
// use Any and not T::class to allow using lists and maps as the property type, which is otherwise not allowed
(project.objects.property(Any::class.java) as Property<T>).apply {
if (initialize != null)
set(provider(initialize))
}
private class OptionalProviderDelegate<T>(private val provider: Provider<T?>) : ReadOnlyProperty<Any?, T?> {
override fun getValue(thisRef: Any?, property: KProperty<*>): T? =
if (provider.isPresent)
@@ -38,6 +29,13 @@ private class OptionalProviderDelegate<T>(private val provider: Provider<T?>) :
internal fun <T> Project.optionalProvider(initialize: () -> T?): ReadOnlyProperty<Any?, T?> =
OptionalProviderDelegate(provider(initialize))
internal fun <T : Any> Project.newProperty(initialize: (() -> T)? = null): Property<T> =
@Suppress("UNCHECKED_CAST")
(project.objects.property(Any::class.java) as Property<T>).apply {
if (initialize != null)
set(provider(initialize))
}
// Before 5.0 fileProperty is created via ProjectLayout
// https://docs.gradle.org/current/javadoc/org/gradle/api/model/ObjectFactory.html#fileProperty--
internal fun Project.newFileProperty(initialize: (() -> File)? = null): RegularFileProperty {