Gradle, native: Report passing binary-specific args to compilation

Earlier we propagated all free args specified for a compilation into
a link task. It was required because the link task actually compiled
the same source as the compile task but with another output kind.

But currently a link task produces a final binary from a klib instead
of sources. It means that such a propagation becomes incorrect. Now
options related to the compiler frontend like -Xexperimental must
be specified per compilation while options related to the compiler
backend (e.g. -opt, -g, -Xstatic-framework etc) - per binary.

This path shows a special warning if some of "binary-specific"
arguments are passed to a compilation.
This commit is contained in:
Ilya Matveev
2019-08-26 19:59:59 +07:00
parent 8d495c4b45
commit f07e0594e2
9 changed files with 232 additions and 49 deletions
@@ -1117,6 +1117,41 @@ class NewMultiplatformIT : BaseGradleIT() {
}
}
@Test
fun testNativeFreeArgsWarning() = with(transformProjectWithPluginsDsl("kotlin-dsl", gradleVersion, "new-mpp-native-binaries")) {
gradleBuildScript().appendText(
"""kotlin.targets["macos64"].compilations["main"].kotlinOptions.freeCompilerArgs += "-opt""""
)
gradleBuildScript("exported").appendText(
"""
kotlin.targets["macos64"].compilations["main"].kotlinOptions.freeCompilerArgs += "-opt"
kotlin.targets["macos64"].compilations["test"].kotlinOptions.freeCompilerArgs += "-g"
kotlin.targets["linux64"].compilations["main"].kotlinOptions.freeCompilerArgs +=
listOf("-g", "-Xdisable-phases=Devirtualization,BuildDFG")
""".trimIndent()
)
build("tasks") {
assertSuccessful()
assertContains(
"""
The following free compiler arguments must be specified for a binary instead of a compilation:
* In project ':':
* In target 'macos64':
* Compilation: 'main', arguments: [-opt]
* In project ':exported':
* In target 'linux64':
* Compilation: 'main', arguments: [-g, -Xdisable-phases=Devirtualization,BuildDFG]
* In target 'macos64':
* Compilation: 'main', arguments: [-opt]
* Compilation: 'test', arguments: [-g]
Please move them into final binary declarations. E.g. binaries.executable { freeCompilerArgs += "..." }
See more about final binaries: https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#building-final-native-binaries.
""".trimIndent()
)
}
}
@Test
fun testSourceJars() = with(Project("sample-lib", gradleVersion, "new-mpp-lib-and-app")) {
setupWorkingDir()
@@ -54,7 +54,7 @@ kotlin {
executable("test2", [RELEASE]) {
compilation = compilations["test"]
freeCompilerArgs.add("-tr")
freeCompilerArgs += "-tr"
linkTask.kotlinOptions {
freeCompilerArgs += "-Xtime"
}
@@ -44,7 +44,7 @@ kotlin {
executable("test2") {
compilation = compilations["test"]
freeCompilerArgs.add("-tr")
freeCompilerArgs += "-tr"
linkTask.kotlinOptions {
freeCompilerArgs += "-Xtime"
}
@@ -18,7 +18,7 @@ package org.jetbrains.kotlin.gradle.plugin
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.Coroutines
import org.jetbrains.kotlin.gradle.plugin.mpp.DisabledNativeTargetsReporter
import org.jetbrains.kotlin.gradle.targets.native.DisabledNativeTargetsReporter
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
@@ -0,0 +1,136 @@
/*
* 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.targets.native
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
internal object CompilationFreeArgsValidator : AggregateReporter() {
private const val EXTRA_PROPERTY_NAME = "org.jetbrains.kotlin.native.incorrectFreeArgs"
private data class IncorrectArgumentsReport(val compilation: KotlinNativeCompilation, val incorrectArgs: List<String>) {
val target: KotlinNativeTarget
get() = compilation.target
val project: Project
get() = target.project
}
private fun String.startsWithIncorrectPrefix() = incorrectArgPrefixes.any { startsWith(it) }
private fun String.disablesDevirtualization() = startsWith("-Xdisable-phases=") && contains("Devirtualization")
private fun Project.getOrRegisterIncorrectArguments(): MutableList<IncorrectArgumentsReport> =
getOrRegisterData(this, EXTRA_PROPERTY_NAME)
private fun String.withIndent(indent: Int) = prependIndent(" ".repeat(indent))
fun validate(compilation: KotlinNativeCompilation) {
val incorrectArgs = compilation.kotlinOptions.freeCompilerArgs.filter { arg ->
arg.startsWithIncorrectPrefix() || arg.disablesDevirtualization()
}
if (incorrectArgs.isNotEmpty()) {
compilation.target.project
.getOrRegisterIncorrectArguments()
.add(IncorrectArgumentsReport(compilation, incorrectArgs))
}
}
/**
* The message format is the following:
*
* The following free compiler arguments must be specified for a binary instead of a compilation:
* * In project ':':
* * In target 'macos':
* Compilation: 'main', arguments: [-opt]
* * In project ':test':
* * In target 'macos':
* Compilation: 'main', arguments: [-opt, -g]
* Compilation: 'test', arguments: [-opt, -g, -Xdisable-phases=Devirtualization,BuildDFG]
*/
override fun printWarning(project: Project) {
// filterIsInstance helps against potential class loaders conflict or misconfiguration.
@Suppress("UselessCallOnCollection")
val incorrectArgs = project
.getOrRegisterIncorrectArguments()
.filterIsInstance<IncorrectArgumentsReport>()
.groupBy { it.project }
.toSortedMap(compareBy { it.path })
if (incorrectArgs.isEmpty()) {
return
}
val message = buildString {
appendln()
appendln("The following free compiler arguments must be specified for a binary instead of a compilation:")
incorrectArgs.forEach { (project, reports) ->
appendln("* In project '${project.path}':".withIndent(1))
val groupedReports = reports
.groupBy { it.target }
.toSortedMap(compareBy { it.name })
groupedReports.forEach { (target, reports) ->
appendln("* In target '${target.name}':".withIndent(2))
reports.forEach {
appendln(
"* Compilation: '${it.compilation.name}', arguments: [${it.incorrectArgs.joinToString()}]".withIndent(3)
)
}
}
}
appendln()
appendln(
"""
Please move them into final binary declarations. E.g. binaries.executable { freeCompilerArgs += "..." }
See more about final binaries: https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#building-final-native-binaries.
""".trimIndent()
)
}
project.logger.warn(message)
}
private val incorrectArgPrefixes = listOf(
// Optimizations/debug info.
"-opt",
"-g",
"-ea",
"-enable-assertions",
// Test runners.
"-trn",
"-generate-no-exit-test-runner",
"-tr",
"-generate-test-runner",
"-trw",
"-generate-worker-test-runner",
// Linker parameters and entry point.
"-linker-option",
"-linker-options",
"-e",
"-entry",
"-nomain",
// Runtime settings.
"-memory-model",
// Coverage.
"-Xcoverage",
"-Xcoverage-file",
"-Xlibrary-to-cover",
// Reverse ObjC interop and framework producing.
"-Xembed-bitcode",
"-Xembed-bitcode-marker",
"-Xexport-library",
"-Xframework-import-header",
"-Xobjc-generics",
"-Xstatic-framework",
// Advanced debug info.
"-Xdebug-info-version",
"-Xg0",
// Other.
"-Xinclude",
"-Xruntime"
)
}
@@ -3,15 +3,30 @@
* 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.mpp
package org.jetbrains.kotlin.gradle.targets.native
import org.gradle.api.Project
import org.gradle.api.plugins.ExtraPropertiesExtension
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget
internal object DisabledNativeTargetsReporter {
internal abstract class AggregateReporter {
@Suppress("UNCHECKED_CAST")
protected fun <T> getOrRegisterData(project: Project, propertyName: String): MutableList<T> =
project.rootProject.extensions.getByType(ExtraPropertiesExtension::class.java).run {
if (!has(propertyName)) {
set(propertyName, mutableListOf<T>())
project.gradle.taskGraph.whenReady { printWarning(project) }
}
get(propertyName)
} as MutableList<T>
protected abstract fun printWarning(project: Project)
}
internal object DisabledNativeTargetsReporter : AggregateReporter() {
private const val EXTRA_PROPERTY_NAME = "org.jetbrains.kotlin.native.disabledTargets"
internal const val WARNING_PREFIX = "Some Kotlin/Native targets cannot be built on this "
@@ -25,51 +40,42 @@ internal object DisabledNativeTargetsReporter {
private data class DisabledTarget(val project: Project, val target: KotlinNativeTarget, val supportedHosts: Collection<KonanTarget>)
@Suppress("UNCHECKED_CAST")
private fun getOrRegisterDisabledTargets(project: Project) =
project.rootProject.extensions.getByType(ExtraPropertiesExtension::class.java).run {
if (!has(EXTRA_PROPERTY_NAME)) {
set(EXTRA_PROPERTY_NAME, mutableListOf<DisabledTarget>())
printWarningWhenTaskGraphIsReady(project)
}
get(EXTRA_PROPERTY_NAME)
} as MutableList<DisabledTarget>
getOrRegisterData<DisabledTarget>(project, EXTRA_PROPERTY_NAME)
private fun printWarningWhenTaskGraphIsReady(project: Project) {
project.gradle.taskGraph.whenReady {
if (PropertiesProvider(project).ignoreDisabledNativeTargets == true) {
return@whenReady
}
val disabledTargetsList = getOrRegisterDisabledTargets(project)
@Suppress("UselessCallOnCollection") // filterIsInstance helps against potential class loaders conflict or misconfiguration
val disabledTargetGroups = disabledTargetsList
.filterIsInstance<DisabledTarget>()
.groupBy { it.project }
.mapValues { (_, disabledTargetsInProject) -> disabledTargetsInProject.groupBy { it.supportedHosts } }
.toSortedMap(compareBy { it.path })
project.logger.warn(buildString {
appendln("\n$WARNING_PREFIX${HostManager.host} machine and are disabled:")
disabledTargetGroups.forEach { (targetProject, targetsBySupportedHosts) ->
appendln(
" * In project '${targetProject.path}':"
)
targetsBySupportedHosts.forEach { (supportedHosts, disabledTargets) ->
append(" * target" + "s".takeIf { disabledTargets.size > 1 }.orEmpty() + " ")
append(disabledTargets.joinToString { "'${it.target.name}'" })
val supportedHostsString = when (supportedHosts.size) {
1 -> "a ${supportedHosts.single()} host"
else -> "one of the hosts: ${supportedHosts.joinToString(", ")}"
}
appendln(" (can be built with $supportedHostsString)")
}
}
appendln("To hide this message, add '$DISABLE_WARNING_PROPERTY_NAME=true' to the Gradle properties.")
})
override fun printWarning(project: Project) {
if (PropertiesProvider(project).ignoreDisabledNativeTargets == true) {
return
}
val disabledTargetsList = getOrRegisterDisabledTargets(project)
@Suppress("UselessCallOnCollection") // filterIsInstance helps against potential class loaders conflict or misconfiguration.
val disabledTargetGroups = disabledTargetsList
.filterIsInstance<DisabledTarget>()
.groupBy { it.project }
.mapValues { (_, disabledTargetsInProject) -> disabledTargetsInProject.groupBy { it.supportedHosts } }
.toSortedMap(compareBy { it.path })
project.logger.warn(buildString {
appendln("\n$WARNING_PREFIX${HostManager.host} machine and are disabled:")
disabledTargetGroups.forEach { (targetProject, targetsBySupportedHosts) ->
appendln(
" * In project '${targetProject.path}':"
)
targetsBySupportedHosts.forEach { (supportedHosts, disabledTargets) ->
append(" * target" + "s".takeIf { disabledTargets.size > 1 }.orEmpty() + " ")
append(disabledTargets.joinToString { "'${it.target.name}'" })
val supportedHostsString = when (supportedHosts.size) {
1 -> "a ${supportedHosts.single()} host"
else -> "one of the hosts: ${supportedHosts.joinToString(", ")}"
}
appendln(" (can be built with $supportedHostsString)")
}
}
appendln("To hide this message, add '$DISABLE_WARNING_PROPERTY_NAME=true' to the Gradle properties.")
})
}
}
@@ -8,6 +8,8 @@ package org.jetbrains.kotlin.gradle.plugin.mpp
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.whenEvaluated
import org.jetbrains.kotlin.gradle.targets.native.CompilationFreeArgsValidator
class KotlinNativeCompilationFactory(
val project: Project,
@@ -22,5 +24,8 @@ class KotlinNativeCompilationFactory(
if (name == KotlinCompilation.TEST_COMPILATION_NAME) {
friendCompilationName = KotlinCompilation.MAIN_COMPILATION_NAME
}
project.whenEvaluated {
CompilationFreeArgsValidator.validate(this@apply)
}
}
}
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinNativeTargetConfigurator
import org.jetbrains.kotlin.gradle.plugin.KotlinTargetPreset
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
import org.jetbrains.kotlin.gradle.targets.native.DisabledNativeTargetsReporter
import org.jetbrains.kotlin.gradle.utils.NativeCompilerDownloader
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget
@@ -5,7 +5,7 @@
package org.jetbrains.kotlin.gradle.internals
import org.jetbrains.kotlin.gradle.plugin.mpp.DisabledNativeTargetsReporter
import org.jetbrains.kotlin.gradle.targets.native.DisabledNativeTargetsReporter
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinProjectStructureMetadata
import org.w3c.dom.Document