Fix explicitApiMode could be overwritten by freeCompilerArgs

Now tasks has a separate task input for 'explicitApiMode' which is
passed to Kotlin compiler via arg, rather than free compiler args.

^KT-57653 Fixed
This commit is contained in:
Yahor Berdnikau
2023-05-04 22:38:04 +02:00
committed by Space Team
parent d3043b6f1c
commit 2ea65bd658
15 changed files with 198 additions and 53 deletions
@@ -34,7 +34,10 @@ interface KotlinTopLevelExtensionConfig {
/**
* Different modes that can be used to set the level of issue reporting for [KotlinTopLevelExtensionConfig.explicitApi] option.
*/
enum class ExplicitApiMode(val cliOption: String) {
enum class ExplicitApiMode(
@Deprecated("Should not be exposed in api", level = DeprecationLevel.ERROR)
val cliOption: String
) {
/** Report issues as errors. */
Strict("strict"),
@@ -44,5 +47,7 @@ enum class ExplicitApiMode(val cliOption: String) {
/** Disable issues reporting. */
Disabled("disable");
@Deprecated("Should not be exposed in api", level = DeprecationLevel.ERROR)
@Suppress("DEPRECATION_ERROR")
fun toCompilerArg() = "-Xexplicit-api=$cliOption"
}
@@ -0,0 +1,145 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle
import org.gradle.api.logging.LogLevel
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.testbase.*
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.junit.jupiter.api.DisplayName
import kotlin.io.path.appendText
@DisplayName("Explicit API DSL")
@JvmGradlePluginTests
class ExplicitApiIT : KGPBaseTest() {
@DisplayName("Explicit api warning mode produces warnings")
@GradleTest
fun explicitApiWarning(gradleVersion: GradleVersion) {
project(
"simpleProject",
gradleVersion,
buildOptions = defaultBuildOptions.copy(logLevel = LogLevel.DEBUG)
) {
buildGradle.appendText(
"""
|
|kotlin.explicitApiWarning()
""".trimMargin()
)
build("compileKotlin") {
assertTasksExecuted(":compileKotlin")
assertCompilerArgument(":compileKotlin", "-Xexplicit-api=warning")
}
}
}
@DisplayName("Explicit api strict mode produces errors on violation")
@GradleTest
fun explicitApiStrict(gradleVersion: GradleVersion) {
project(
"simpleProject",
gradleVersion,
buildOptions = defaultBuildOptions.copy(logLevel = LogLevel.DEBUG)
) {
buildGradle.appendText(
"""
|
|kotlin.explicitApi()
""".trimMargin()
)
buildAndFail("compileKotlin") {
assertTasksFailed(":compileKotlin")
assertCompilerArgument(":compileKotlin", "-Xexplicit-api=strict")
assertOutputContains("Visibility must be specified in explicit API mode")
}
}
}
@DisplayName("KT-57653: Explicit api warning mode is not overridden by freeCompilerArgs")
@GradleTest
fun explicitApiWarningFreeArgsOverride(gradleVersion: GradleVersion) {
project(
"simpleProject",
gradleVersion,
buildOptions = defaultBuildOptions.copy(logLevel = LogLevel.DEBUG)
) {
buildGradle.appendText(
//language=groovy
"""
|
|kotlin.explicitApiWarning()
|
|tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile.class).configureEach {
| compilerOptions.freeCompilerArgs.add("-Xcontext-receivers")
|}
|
""".trimMargin()
)
build("compileKotlin") {
assertTasksExecuted(":compileKotlin")
assertCompilerArgument(":compileKotlin", "-Xcontext-receivers")
assertCompilerArgument(":compileKotlin", "-Xexplicit-api=warning")
}
}
}
@DisplayName("MPP: explicit api warning works for MPP tasks")
@GradleTest
@MppGradlePluginTests
fun explicitApiMpp(gradleVersion: GradleVersion) {
project(
"new-mpp-lib-and-app/sample-lib",
gradleVersion,
buildOptions = defaultBuildOptions.copy(logLevel = LogLevel.DEBUG)
) {
buildGradle.appendText(
//language=groovy
"""
|
|kotlin.explicitApiWarning()
""".trimMargin()
)
build(":compileCommonMainKotlinMetadata") {
assertTasksExecuted(":compileCommonMainKotlinMetadata")
assertCompilerArgument(":compileCommonMainKotlinMetadata", "-Xexplicit-api=warning")
}
build(":compileKotlinJvm6") {
assertTasksExecuted(":compileKotlinJvm6")
assertCompilerArgument(":compileKotlinJvm6", "-Xexplicit-api=warning")
}
build(":compileKotlinNodeJs") {
assertTasksExecuted(":compileKotlinNodeJs")
assertCompilerArgument(":compileKotlinNodeJs", "-Xexplicit-api=warning")
}
val nativeTaskName = when (HostManager.host) {
KonanTarget.LINUX_X64 -> ":compileKotlinLinux64"
KonanTarget.MACOS_ARM64 -> ":compileKotlinMacos64"
KonanTarget.MINGW_X64 -> ":compileKotlinMingw64"
else -> null
}
if (nativeTaskName != null) {
build(nativeTaskName) {
assertTasksExecuted(nativeTaskName)
assertNativeTasksCommandLineArguments(nativeTaskName) {
assertCommandLineArgumentsContain("-Xexplicit-api=warning", commandLineArguments = it)
}
}
}
}
}
}
@@ -156,6 +156,18 @@ abstract class KotlinTopLevelExtension(internal val project: Project) : KotlinTo
}
}
internal fun ExplicitApiMode.toCompilerValue() = when (this) {
ExplicitApiMode.Strict -> "strict"
ExplicitApiMode.Warning -> "warning"
ExplicitApiMode.Disabled -> "disable"
}
internal fun KotlinTopLevelExtension.explicitApiModeAsCompilerArg(): String? {
val cliOption = explicitApi?.toCompilerValue()
return cliOption?.let { "-Xexplicit-api=$it" }
}
open class KotlinProjectExtension @Inject constructor(project: Project) : KotlinTopLevelExtension(project), KotlinSourceSetContainer {
override var sourceSets: NamedDomainObjectContainer<KotlinSourceSet>
@Suppress("UNCHECKED_CAST")
@@ -1,25 +0,0 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.gradle.dsl
import org.jetbrains.kotlin.cli.common.arguments.CommonToolArguments
fun KotlinCommonToolOptions.copyFreeCompilerArgsToArgs(args: CommonToolArguments) {
// cast to List<Any> is important because in Groovy a GString can be inside of a list
val freeArgs = (freeCompilerArgs as List<Any>).map(Any::toString)
args.freeArgs += freeArgs
}
@@ -3,6 +3,7 @@
package org.jetbrains.kotlin.gradle.kpm.idea
import org.jetbrains.kotlin.compilerRunner.konanHome
import org.jetbrains.kotlin.gradle.dsl.explicitApiModeAsCompilerArg
import org.jetbrains.kotlin.gradle.idea.kpm.IdeaKpmProject
import org.jetbrains.kotlin.gradle.idea.kpm.IdeaKpmProjectImpl
import org.jetbrains.kotlin.gradle.idea.serialize.IdeaKotlinExtrasSerializationExtension
@@ -147,7 +148,7 @@ internal fun IdeaKpmProjectBuildingContext.IdeaKpmProject(extension: KotlinPm20P
return IdeaKpmProjectImpl(
gradlePluginVersion = extension.project.getKotlinPluginVersion(),
coreLibrariesVersion = extension.coreLibrariesVersion,
explicitApiModeCliOption = extension.explicitApi?.cliOption,
explicitApiModeCliOption = extension.explicitApiModeAsCompilerArg(),
kotlinNativeHome = File(extension.project.konanHome).absoluteFile,
modules = extension.modules.map { module -> IdeaKpmModule(module) }
)
@@ -320,17 +320,6 @@ internal fun sourcesJarTaskNamed(
}
internal fun Project.setupGeneralKotlinExtensionParameters() {
val sourceSetsInMainCompilation by lazy {
kotlinExtension.sourceSets.filter { sourceSet ->
sourceSet.internal.compilations.any {
// kotlin main compilation
it.isMain()
// android compilation which is NOT in tested variant
|| (it as? KotlinJvmAndroidCompilation)?.let { getTestedVariantData(it.androidVariant) == null } == true
}
}
}
kotlinExtension.sourceSets.all { sourceSet ->
(sourceSet.languageSettings as? DefaultLanguageSettingsBuilder)?.run {
@@ -348,11 +337,6 @@ internal fun Project.setupGeneralKotlinExtensionParameters() {
is String -> add(propertyValue)
is Iterable<*> -> addAll(propertyValue.map { it.toString() })
}
val explicitApiState = project.kotlinExtension.explicitApi?.toCompilerArg()
// do not look into lazy set if explicitApiMode was not enabled
if (explicitApiState != null && sourceSet in sourceSetsInMainCompilation)
add(explicitApiState)
}
}
}
@@ -21,6 +21,7 @@ import org.gradle.api.tasks.Exec
import org.gradle.api.tasks.TaskProvider
import org.gradle.language.base.plugins.LifecycleBasePlugin
import org.jetbrains.kotlin.gradle.dsl.KotlinNativeCompilerOptions
import org.jetbrains.kotlin.gradle.dsl.topLevelExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.TEST_COMPILATION_NAME
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilationInfo.KPM
import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle.Stage.ReadyForExecution
@@ -380,6 +381,7 @@ open class KotlinNativeTargetConfigurator<T : KotlinNativeTarget> : AbstractKotl
konanTarget: KonanTarget
): TaskProvider<KotlinNativeCompile> {
val project = compilationInfo.project
val ext = project.topLevelExtension
val compileTaskProvider = project.registerTask<KotlinNativeCompile>(
compilationInfo.compileKotlinTaskName,
listOf(
@@ -400,6 +402,10 @@ open class KotlinNativeTargetConfigurator<T : KotlinNativeTarget> : AbstractKotl
}
it.compilerOptions.useK2.disallowChanges()
it.runViaBuildToolsApi.value(false).disallowChanges() // K/N is not yet supported
it.explicitApiMode
.value(project.providers.provider { ext.explicitApi })
.finalizeValueOnRead()
}
compilationInfo.classesDirs.from(compileTaskProvider.map { it.outputFile })
@@ -16,6 +16,7 @@ import org.gradle.api.artifacts.result.DependencyResult
import org.gradle.api.artifacts.result.ResolvedDependencyResult
import org.gradle.api.file.*
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.tasks.*
@@ -146,6 +147,10 @@ abstract class AbstractKotlinNativeCompile<
@get:Internal
abstract val baseName: String
@get:Input
@get:Optional
internal abstract val explicitApiMode: Property<ExplicitApiMode>
@get:Internal
protected val konanTarget by project.provider {
when (val compilation = compilation) {
@@ -457,6 +462,8 @@ internal constructor(
}
KotlinNativeCompilerOptionsHelper.fillCompilerArguments(compilerOptions, args)
explicitApiMode.orNull?.run { args.explicitApi = toCompilerValue() }
}
pluginClasspath { args ->
@@ -30,6 +30,7 @@ import org.jetbrains.kotlin.compilerRunner.GradleCompilerRunner
import org.jetbrains.kotlin.compilerRunner.UsesCompilerSystemPropertiesService
import org.jetbrains.kotlin.compilerRunner.createGradleCompilerRunner
import org.jetbrains.kotlin.daemon.common.MultiModuleICSettings
import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode
import org.jetbrains.kotlin.gradle.dsl.KotlinCommonCompilerOptions
import org.jetbrains.kotlin.gradle.incremental.UsesIncrementalModuleInfoBuildService
import org.jetbrains.kotlin.gradle.internal.UsesClassLoadersCachingBuildService
@@ -109,6 +110,10 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments> @Inject constr
@get:Internal
val startParameters = BuildReportsService.getStartParameters(project)
@get:Input
@get:Optional
internal abstract val explicitApiMode: Property<ExplicitApiMode>
@get:Internal
internal abstract val suppressKotlinOptionsFreeArgsModificationWarning: Property<Boolean>
@@ -165,6 +165,8 @@ abstract class Kotlin2JsCompile @Inject constructor(
args.fragmentRefines = multiplatformStructure.fragmentRefinesCompilerArgs
}
explicitApiMode.orNull?.run { args.explicitApi = toCompilerValue() }
// Overriding freeArgs from compilerOptions with enhanced one + additional one set on execution phase
// containing additional arguments based on the js compilation configuration
args.freeArgs = executionTimeFreeCompilerArgs ?: enhancedFreeCompilerArgs.get().toList()
@@ -29,9 +29,8 @@ import org.jetbrains.kotlin.compilerRunner.GradleCompilerEnvironment
import org.jetbrains.kotlin.compilerRunner.IncrementalCompilationEnvironment
import org.jetbrains.kotlin.compilerRunner.OutputItemsCollectorImpl
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions
import org.jetbrains.kotlin.gradle.dsl.*
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptionsHelper
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions
import org.jetbrains.kotlin.gradle.dsl.jvm.JvmTargetValidationMode
import org.jetbrains.kotlin.gradle.dsl.usesK2
import org.jetbrains.kotlin.gradle.internal.tasks.allOutputFiles
@@ -248,6 +247,8 @@ abstract class KotlinCompile @Inject constructor(
if (localExecutionTimeFreeCompilerArgs != null) {
args.freeArgs = localExecutionTimeFreeCompilerArgs
}
explicitApiMode.orNull?.run { args.explicitApi = toCompilerValue() }
}
pluginClasspath { args ->
@@ -90,6 +90,8 @@ abstract class KotlinCompileCommon @Inject constructor(
args.destination = destinationDirectory.get().asFile.normalize().absolutePath
explicitApiMode.orNull?.run { args.explicitApi = toCompilerValue() }
KotlinCommonCompilerOptionsHelper.fillCompilerArguments(compilerOptions, args)
val localExecutionTimeFreeCompilerArgs = executionTimeFreeCompilerArgs
@@ -121,6 +121,10 @@ internal abstract class AbstractKotlinCompileConfig<TASK : AbstractKotlinCompile
}
task.runViaBuildToolsApi.convention(propertiesProvider.runKotlinCompilerViaBuildToolsApi).finalizeValueOnRead()
task.classLoadersCachingService.value(cachedClassLoadersService).disallowChanges()
task.explicitApiMode
.value(project.providers.provider { ext.explicitApi })
.finalizeValueOnRead()
}
}
@@ -100,7 +100,7 @@ internal open class BaseKotlinCompileConfig<TASK : KotlinCompile> : AbstractKotl
constructor(project: Project, ext: KotlinTopLevelExtension) : super(
project, ext, languageSettings = getDefaultLangSetting(project, ext)
project, ext, languageSettings = getDefaultLangSetting(project)
)
companion object {
@@ -111,12 +111,8 @@ internal open class BaseKotlinCompileConfig<TASK : KotlinCompile> : AbstractKotl
private const val JAR_ARTIFACT_TYPE = "jar"
const val CLASSPATH_ENTRY_SNAPSHOT_ARTIFACT_TYPE = "classpath-entry-snapshot"
private fun getDefaultLangSetting(project: Project, ext: KotlinTopLevelExtension): Provider<LanguageSettings> {
return project.provider {
DefaultLanguageSettingsBuilder().also {
it.freeCompilerArgsProvider = project.provider { listOfNotNull(ext.explicitApi?.toCompilerArg()) }
}
}
private fun getDefaultLangSetting(project: Project): Provider<LanguageSettings> {
return project.provider { DefaultLanguageSettingsBuilder() }
}
}
@@ -140,6 +140,6 @@ class KotlinCompileApiTest {
fun testTopLevelExtension() {
plugin.kotlinExtension.explicitApi = ExplicitApiMode.Strict
project.evaluate()
assertTrue(ExplicitApiMode.Strict.toCompilerArg() in taskImpl.compilerOptions.freeCompilerArgs.get())
assertEquals(ExplicitApiMode.Strict, taskImpl.explicitApiMode.orNull)
}
}