diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt index 167aa9cd53c..062213d886a 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt @@ -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() diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-native-binaries/groovy-dsl/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-native-binaries/groovy-dsl/build.gradle index 7129216fcbc..d2c24e9ada6 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-native-binaries/groovy-dsl/build.gradle +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-native-binaries/groovy-dsl/build.gradle @@ -54,7 +54,7 @@ kotlin { executable("test2", [RELEASE]) { compilation = compilations["test"] - freeCompilerArgs.add("-tr") + freeCompilerArgs += "-tr" linkTask.kotlinOptions { freeCompilerArgs += "-Xtime" } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-native-binaries/kotlin-dsl/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-native-binaries/kotlin-dsl/build.gradle.kts index 268b7aed056..108e034de68 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-native-binaries/kotlin-dsl/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-native-binaries/kotlin-dsl/build.gradle.kts @@ -44,7 +44,7 @@ kotlin { executable("test2") { compilation = compilations["test"] - freeCompilerArgs.add("-tr") + freeCompilerArgs += "-tr" linkTask.kotlinOptions { freeCompilerArgs += "-Xtime" } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinProperties.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinProperties.kt index e7e106500ce..06d37797248 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinProperties.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinProperties.kt @@ -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 diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/CompilationFreeArgsValidator.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/CompilationFreeArgsValidator.kt new file mode 100644 index 00000000000..e31dcefb71b --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/CompilationFreeArgsValidator.kt @@ -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) { + 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 = + 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() + .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" + ) +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/DisabledNativeTargetsReporter.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/DisabledNativeTargetsReporter.kt index fd0fa3fd12a..a45155a440f 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/DisabledNativeTargetsReporter.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/DisabledNativeTargetsReporter.kt @@ -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 getOrRegisterData(project: Project, propertyName: String): MutableList = + project.rootProject.extensions.getByType(ExtraPropertiesExtension::class.java).run { + if (!has(propertyName)) { + set(propertyName, mutableListOf()) + project.gradle.taskGraph.whenReady { printWarning(project) } + } + get(propertyName) + } as MutableList + + 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) - @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()) - printWarningWhenTaskGraphIsReady(project) - } - get(EXTRA_PROPERTY_NAME) - } as MutableList + getOrRegisterData(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() - .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() + .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.") + }) } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeCompilationFactory.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeCompilationFactory.kt index 2fdac4cf874..e08f6fbd2a3 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeCompilationFactory.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeCompilationFactory.kt @@ -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) + } } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetPreset.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetPreset.kt index c530208ef6f..0ded1f1eccb 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetPreset.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetPreset.kt @@ -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 diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internals/ExposedForIntegrationTests.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internals/ExposedForIntegrationTests.kt index 06625097a9e..60cd8af3a71 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internals/ExposedForIntegrationTests.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internals/ExposedForIntegrationTests.kt @@ -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