Add workaround for external plugin modifying options on task execution

Currently, known plugins doing that are 'kotlin-dsl' and AGP with
compose enabled. They are setting freeCompilerArgs in KotlinCompile
.doFirst task action. At this point compilerOptions.freeCompilerArgs are
 finalized and Gradle doesn't allow further modification.

^KT-27301 In Progress
This commit is contained in:
Yahor Berdnikau
2022-09-16 11:15:52 +02:00
parent 6a6bcda298
commit 732ba96667
8 changed files with 134 additions and 4 deletions
@@ -0,0 +1,29 @@
/*
* Copyright 2010-2022 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.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.testbase.*
import org.junit.jupiter.api.DisplayName
internal class CompilerOptionsIT : KGPBaseTest() {
// In Gradle 7.3-7.5 'kotlin-dsl' plugin tries to set up freeCompilerArgs in doFirst task action
@DisplayName("Allows to set kotlinOptions.freeCompilerArgs on task execution with warning")
@JvmGradlePluginTests
@GradleTestVersions(
minVersion = TestVersions.Gradle.G_7_3,
maxVersion = TestVersions.Gradle.G_7_5
)
@GradleTest
internal fun compatibleWithKotlinDsl(gradleVersion: GradleVersion) {
project("buildSrcWithKotlinDslAndKgp", gradleVersion) {
build("tasks") {
assertOutputContains("kotlinOptions.freeCompilerArgs were changed on task execution phase:")
}
}
}
}
@@ -0,0 +1,8 @@
plugins {
kotlin("jvm")
}
repositories {
mavenLocal()
mavenCentral()
}
@@ -0,0 +1,20 @@
//buildscript {
// dependencies {
// classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${property("kotlin_version")}")
// classpath("org.jetbrains.kotlin:kotlin-sam-with-receiver:${property("kotlin_version")}")
// }
//}
plugins {
id("org.jetbrains.kotlin.jvm")
`kotlin-dsl`
}
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:${property("kotlin_version")}")
}
@@ -0,0 +1,8 @@
/*
* Copyright 2010-2022 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 org.gradle.api.Project
fun Project.nameLength() = name.length
@@ -0,0 +1,3 @@
dependencies {
"implementation"("org.jetbrains.kotlin:kotlin-symbol-processing-api")
}
@@ -0,0 +1,8 @@
/*
* Copyright 2010-2022 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() {
println("Hello World!")
}
@@ -56,6 +56,7 @@ import org.jetbrains.kotlin.gradle.report.BuildMetricsService
import org.jetbrains.kotlin.gradle.report.BuildReportMode
import org.jetbrains.kotlin.gradle.report.BuildReportsService
import org.jetbrains.kotlin.gradle.report.ReportingSettings
import org.jetbrains.kotlin.gradle.tasks.internal.KotlinJvmOptionsCompat
import org.jetbrains.kotlin.gradle.utils.*
import org.jetbrains.kotlin.incremental.*
import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotDisabled
@@ -535,10 +536,10 @@ abstract class KotlinCompile @Inject constructor(
@Suppress("DEPRECATION")
@Deprecated("Replaced by compilerOptions input", replaceWith = ReplaceWith("compilerOptions"))
final override val kotlinOptions: KotlinJvmOptions = object : KotlinJvmOptions {
override val options: CompilerJvmOptions
get() = compilerOptions
}
final override val kotlinOptions: KotlinJvmOptions = KotlinJvmOptionsCompat(
{ this },
compilerOptions
)
@Suppress("DEPRECATION")
@Deprecated("Configure compilerOptions directly", replaceWith = ReplaceWith("compilerOptions"))
@@ -677,6 +678,16 @@ abstract class KotlinCompile @Inject constructor(
override fun createCompilerArgs(): K2JVMCompilerArguments =
K2JVMCompilerArguments()
/**
* Workaround for those "nasty" plugins that are adding 'freeCompilerArgs' on task execution phase.
* With properties api it is not possible to update property value after task configuration is finished.
*
* Marking it as `@Internal` as anyway on the configuration phase, when Gradle does task inputs snapshot,
* this input will always be empty.
*/
@get:Internal
internal var additionalFreeCompilerArgs: List<String> = listOf()
override fun setupCompilerArgs(args: K2JVMCompilerArguments, defaultsOnly: Boolean, ignoreClasspathResolutionErrors: Boolean) {
compilerArgumentsContributor.contributeArguments(
args, compilerArgumentsConfigurationFlags(
@@ -690,6 +701,10 @@ abstract class KotlinCompile @Inject constructor(
if (reportingSettings().buildReportMode == BuildReportMode.VERBOSE) {
args.reportPerf = true
}
if (additionalFreeCompilerArgs.isNotEmpty()) {
args.freeArgs = compilerOptions.freeCompilerArgs.get().union(additionalFreeCompilerArgs).toList()
}
}
@get:Internal
@@ -0,0 +1,39 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.tasks.internal
import org.jetbrains.kotlin.gradle.dsl.*
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
/**
* Temporary workaround for external plugins that tries to set up freeCompilerArgs
* in task execution phase.
*/
@Suppress("DEPRECATION")
class KotlinJvmOptionsCompat(
private val task: () -> KotlinCompile,
override val options: CompilerJvmOptions
) : KotlinJvmOptions {
override var freeCompilerArgs: List<String>
get() = if (isTaskExecuting) {
options.freeCompilerArgs.get().union(task().additionalFreeCompilerArgs).toList()
} else {
options.freeCompilerArgs.get()
}
set(value) = if (isTaskExecuting) {
task().logger.warn(
"kotlinOptions.freeCompilerArgs were changed on task execution phase: ${value.joinToString()}\n" +
"This behaviour is deprecated and become an error in future releases!"
)
task().additionalFreeCompilerArgs = value
} else {
options.freeCompilerArgs.set(value)
}
private val isTaskExecuting: Boolean
get() = task().state.executing
}