Browse Source
We had two build services: 1) to check unsupported compose compiler plugins were applied 2) to check a native cache kind configuration Both of them are outdated and we may get rid of them. Fixes https://github.com/JetBrains/compose-multiplatform/issues/4815 ## Release Notes ### Fixes - Gradle Plugin - Delete outdated build servicespull/4963/head
Konstantin
5 months ago
committed by
GitHub
10 changed files with 6 additions and 588 deletions
@ -1,127 +0,0 @@
|
||||
/* |
||||
* Copyright 2020-2023 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
package org.jetbrains.compose.experimental.internal |
||||
|
||||
import org.gradle.api.Project |
||||
import org.jetbrains.compose.internal.KOTLIN_MPP_PLUGIN_ID |
||||
import org.jetbrains.compose.internal.mppExt |
||||
import org.jetbrains.compose.internal.utils.KGPPropertyProvider |
||||
import org.jetbrains.compose.internal.utils.configureEachWithType |
||||
import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion |
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget |
||||
import org.jetbrains.kotlin.konan.target.KonanTarget |
||||
import org.jetbrains.kotlin.konan.target.presetName |
||||
|
||||
private const val PROJECT_CACHE_KIND_PROPERTY_NAME = "kotlin.native.cacheKind" |
||||
private const val COMPOSE_NATIVE_MANAGE_CACHE_KIND = "compose.kotlin.native.manageCacheKind" |
||||
private const val NONE_VALUE = "none" |
||||
|
||||
// Compose runtime supports these k/native targets: |
||||
// https://github.com/JetBrains/compose-multiplatform-core/blob/jb-main/compose/runtime/runtime/build.gradle#L75 |
||||
private val SUPPORTED_NATIVE_TARGETS = setOf( |
||||
// ios |
||||
KonanTarget.IOS_X64, |
||||
KonanTarget.IOS_ARM64, |
||||
KonanTarget.IOS_SIMULATOR_ARM64, |
||||
// macos |
||||
KonanTarget.MACOS_X64, |
||||
KonanTarget.MACOS_ARM64, |
||||
// tvos |
||||
KonanTarget.TVOS_X64, |
||||
KonanTarget.TVOS_ARM64, |
||||
KonanTarget.TVOS_SIMULATOR_ARM64, |
||||
// watchOS |
||||
KonanTarget.WATCHOS_ARM64, |
||||
KonanTarget.WATCHOS_ARM32, |
||||
KonanTarget.WATCHOS_X64, |
||||
KonanTarget.WATCHOS_SIMULATOR_ARM64, |
||||
// mingw |
||||
KonanTarget.MINGW_X64, |
||||
// linux |
||||
KonanTarget.LINUX_X64, |
||||
) |
||||
|
||||
internal val SUPPORTED_NATIVE_CACHE_KIND_PROPERTIES = |
||||
SUPPORTED_NATIVE_TARGETS.map { it.targetCacheKindPropertyName } + |
||||
PROJECT_CACHE_KIND_PROPERTY_NAME |
||||
|
||||
internal fun Project.configureNativeCompilerCaching() { |
||||
if (findProperty(COMPOSE_NATIVE_MANAGE_CACHE_KIND) == "false") return |
||||
|
||||
plugins.withId(KOTLIN_MPP_PLUGIN_ID) { |
||||
val kotlinPluginVersion = kotlinVersionNumbers(project.getKotlinPluginVersion()) |
||||
mppExt.targets.configureEachWithType<KotlinNativeTarget> { |
||||
if (konanTarget in SUPPORTED_NATIVE_TARGETS) { |
||||
checkExplicitCacheKind() |
||||
if (kotlinPluginVersion < KotlinVersion(1, 9, 20)) { |
||||
// Pre-1.9.20 Kotlin compiler caches have known compatibility issues |
||||
// See KT-57329, KT-61270 |
||||
disableKotlinNativeCache() |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
private fun KotlinNativeTarget.checkExplicitCacheKind() { |
||||
// To determine cache kind KGP checks kotlin.native.cacheKind.<PRESET_NAME> first, then kotlin.native.cacheKind |
||||
// For each property it tries to read Project.property, then checks local.properties |
||||
// See https://github.com/JetBrains/kotlin/blob/d4d30dcfcf1afb083f09279c6f1ba05031efeabb/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt#L416 |
||||
val cacheKindProperties = listOf(targetCacheKindPropertyName, PROJECT_CACHE_KIND_PROPERTY_NAME) |
||||
val propertyProviders = listOf( |
||||
KGPPropertyProvider.GradleProperties(project), |
||||
KGPPropertyProvider.LocalProperties(project) |
||||
) |
||||
|
||||
for (cacheKindProperty in cacheKindProperties) { |
||||
for (provider in propertyProviders) { |
||||
val value = provider.valueOrNull(cacheKindProperty) |
||||
if (value != null) { |
||||
error(explicitCacheKindErrorMessage(cacheKindProperty, value, provider)) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
private fun explicitCacheKindErrorMessage( |
||||
cacheKindProperty: String, |
||||
value: String, |
||||
provider: KGPPropertyProvider |
||||
) = """ |
||||
|Error: '$cacheKindProperty' is explicitly set to '$value'. |
||||
|This option significantly slows the Kotlin/Native compiler. |
||||
|Compose Multiplatform Gradle plugin manages this property automatically based on a Kotlin compiler version being used. |
||||
| * Recommended action: remove explicit '$cacheKindProperty=$value' from ${provider.location}. |
||||
| * Alternative action: disable cache kind management by adding '$COMPOSE_NATIVE_MANAGE_CACHE_KIND=false' to your 'gradle.properties'. |
||||
""".trimMargin() |
||||
|
||||
|
||||
private val KotlinNativeTarget.targetCacheKindPropertyName: String |
||||
get() = konanTarget.targetCacheKindPropertyName |
||||
|
||||
private val KonanTarget.targetCacheKindPropertyName: String |
||||
get() = "$PROJECT_CACHE_KIND_PROPERTY_NAME.${presetName}" |
||||
|
||||
private fun KotlinNativeTarget.disableKotlinNativeCache() { |
||||
val existingValue = project.findProperty(targetCacheKindPropertyName)?.toString() |
||||
if (NONE_VALUE.equals(existingValue, ignoreCase = true)) return |
||||
|
||||
if (targetCacheKindPropertyName in project.properties) { |
||||
project.setProperty(targetCacheKindPropertyName, NONE_VALUE) |
||||
} else { |
||||
project.extensions.extraProperties.set(targetCacheKindPropertyName, NONE_VALUE) |
||||
} |
||||
} |
||||
|
||||
internal fun kotlinVersionNumbers(version: String): KotlinVersion { |
||||
val m = Regex("(\\d+)\\.(\\d+)\\.(\\d+)").find(version) ?: error("Kotlin version has unexpected format: '$version'") |
||||
val (_, majorPart, minorPart, patchPart) = m.groupValues |
||||
return KotlinVersion( |
||||
major = majorPart.toIntOrNull() ?: error("Could not parse major part '$majorPart' of Kotlin plugin version: '$version'"), |
||||
minor = minorPart.toIntOrNull() ?: error("Could not parse minor part '$minorPart' of Kotlin plugin version: '$version'"), |
||||
patch = patchPart.toIntOrNull() ?: error("Could not parse patch part '$patchPart' of Kotlin plugin version: '$version'"), |
||||
) |
||||
} |
@ -1,81 +0,0 @@
|
||||
/* |
||||
* Copyright 2020-2023 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
package org.jetbrains.compose.internal.service |
||||
|
||||
import org.gradle.api.Project |
||||
import org.gradle.api.services.BuildService |
||||
import org.gradle.api.services.BuildServiceParameters |
||||
import org.gradle.api.services.BuildServiceRegistration |
||||
import org.gradle.tooling.events.FinishEvent |
||||
import org.gradle.tooling.events.OperationCompletionListener |
||||
|
||||
// The service implements OperationCompletionListener just so Gradle would materialize the service even if the service is not used by any task or transformation |
||||
abstract class AbstractComposeMultiplatformBuildService<P : BuildServiceParameters> : BuildService<P> , OperationCompletionListener, AutoCloseable { |
||||
override fun onFinish(event: FinishEvent) {} |
||||
override fun close() {} |
||||
} |
||||
|
||||
internal inline fun <reified Service : BuildService<*>> serviceName(instance: Service? = null): String = |
||||
fqName(instance) |
||||
|
||||
internal inline fun <reified Service : AbstractComposeMultiplatformBuildService<Params>, reified Params : BuildServiceParameters> registerServiceIfAbsent( |
||||
project: Project, |
||||
crossinline initParams: Params.() -> Unit = {} |
||||
): BuildServiceRegistration<Service, Params> { |
||||
if (findRegistration<Service, Params>(project) == null) { |
||||
val newService = project.gradle.sharedServices.registerIfAbsent(fqName<Service>(), Service::class.java) { |
||||
it.parameters.initParams() |
||||
} |
||||
// Workaround to materialize a service even if it is not bound to a task |
||||
BuildEventsListenerRegistryProvider.getInstance(project).onTaskCompletion(newService) |
||||
} |
||||
|
||||
return getExistingServiceRegistration(project) |
||||
} |
||||
|
||||
internal inline fun <reified Service : BuildService<Params>, reified Params : BuildServiceParameters> getExistingServiceRegistration( |
||||
project: Project |
||||
): BuildServiceRegistration<Service, Params> { |
||||
val registration = findRegistration<Service, Params>(project) |
||||
?: error("Service '${serviceName<Service>()}' was not initialized") |
||||
return registration.verified(project) |
||||
} |
||||
|
||||
private inline fun <reified Service : BuildService<Params>, reified Params : BuildServiceParameters> BuildServiceRegistration<*, *>.verified( |
||||
project: Project |
||||
): BuildServiceRegistration<Service, Params> { |
||||
val parameters = parameters |
||||
// We are checking the type of parameters instead of the type of service |
||||
// to avoid materializing the service. |
||||
// After a service instance is created all changes made to its parameters won't be visible to |
||||
// that particular service instance. |
||||
// This is undesirable in some cases. For example, when reporting configuration problems, |
||||
// we want to collect all configuration issues from all projects first, then report issues all at once |
||||
// in execution phase. |
||||
if (parameters !is Params) { |
||||
// Compose Gradle plugin was probably loaded more than once |
||||
// See https://github.com/JetBrains/compose-multiplatform/issues/3459 |
||||
if (fqName(parameters) == fqName<Params>()) { |
||||
val rootScript = project.rootProject.buildFile |
||||
error(""" |
||||
Compose Multiplatform Gradle plugin has been loaded in multiple classloaders. |
||||
To avoid classloading issues, declare Compose Gradle Plugin in root build file $rootScript. |
||||
""".trimIndent()) |
||||
} else { |
||||
error("Shared build service '${serviceName<Service>()}' parameters have unexpected type: ${fqName(parameters)}") |
||||
} |
||||
} |
||||
|
||||
@Suppress("UNCHECKED_CAST") |
||||
return this as BuildServiceRegistration<Service, Params> |
||||
} |
||||
|
||||
private inline fun <reified S : BuildService<P>, reified P : BuildServiceParameters> findRegistration( |
||||
project: Project |
||||
): BuildServiceRegistration<*, *>? = |
||||
project.gradle.sharedServices.registrations.findByName(fqName<S>()) |
||||
|
||||
private inline fun <reified T : Any> fqName(instance: T? = null) = T::class.java.canonicalName |
@ -1,19 +0,0 @@
|
||||
/* |
||||
* Copyright 2020-2023 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
package org.jetbrains.compose.internal.service |
||||
|
||||
import org.gradle.api.Project |
||||
import org.gradle.build.event.BuildEventsListenerRegistry |
||||
import javax.inject.Inject |
||||
|
||||
// a hack to get BuildEventsListenerRegistry conveniently, which can only be injected by Gradle |
||||
@Suppress("UnstableApiUsage") |
||||
internal abstract class BuildEventsListenerRegistryProvider @Inject constructor(val registry: BuildEventsListenerRegistry) { |
||||
companion object { |
||||
fun getInstance(project: Project): BuildEventsListenerRegistry = |
||||
project.objects.newInstance(BuildEventsListenerRegistryProvider::class.java).registry |
||||
} |
||||
} |
@ -1,69 +0,0 @@
|
||||
/* |
||||
* Copyright 2020-2023 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
package org.jetbrains.compose.internal.service |
||||
|
||||
import org.gradle.api.Project |
||||
import org.gradle.api.logging.Logging |
||||
import org.gradle.api.provider.ListProperty |
||||
import org.gradle.api.provider.Provider |
||||
import org.gradle.api.provider.SetProperty |
||||
import org.gradle.api.services.BuildServiceParameters |
||||
import org.jetbrains.compose.createWarningAboutNonCompatibleCompiler |
||||
import org.jetbrains.kotlin.gradle.plugin.SubpluginArtifact |
||||
|
||||
abstract class ConfigurationProblemReporterService : AbstractComposeMultiplatformBuildService<ConfigurationProblemReporterService.Parameters>() { |
||||
interface Parameters : BuildServiceParameters { |
||||
val unsupportedPluginWarningProviders: ListProperty<Provider<String?>> |
||||
val warnings: SetProperty<String> |
||||
} |
||||
|
||||
private val log = Logging.getLogger(this.javaClass) |
||||
|
||||
override fun close() { |
||||
warnAboutUnsupportedCompilerPlugin() |
||||
logWarnings() |
||||
} |
||||
|
||||
private fun warnAboutUnsupportedCompilerPlugin() { |
||||
for (warningProvider in parameters.unsupportedPluginWarningProviders.get()) { |
||||
val warning = warningProvider.orNull |
||||
if (warning != null) { |
||||
log.warn(warning) |
||||
} |
||||
} |
||||
} |
||||
|
||||
private fun logWarnings() { |
||||
for (warning in parameters.warnings.get()) { |
||||
log.warn(warning) |
||||
} |
||||
} |
||||
companion object { |
||||
fun init(project: Project) { |
||||
registerServiceIfAbsent<ConfigurationProblemReporterService, Parameters>(project) { |
||||
// WORKAROUND! Call getter at least once, because of Issue: https://github.com/gradle/gradle/issues/27099 |
||||
warnings |
||||
} |
||||
} |
||||
|
||||
private inline fun configureParameters(project: Project, fn: Parameters.() -> Unit) { |
||||
getExistingServiceRegistration<ConfigurationProblemReporterService, Parameters>(project) |
||||
.parameters.fn() |
||||
} |
||||
|
||||
fun reportWarning(project: Project, message: String) { |
||||
configureParameters(project) { warnings.add(message) } |
||||
} |
||||
|
||||
fun registerUnsupportedPluginProvider(project: Project, unsupportedPlugin: Provider<SubpluginArtifact?>) { |
||||
configureParameters(project) { |
||||
unsupportedPluginWarningProviders.add(unsupportedPlugin.map { unsupportedCompiler -> |
||||
unsupportedCompiler?.groupId?.let { createWarningAboutNonCompatibleCompiler(it) } |
||||
}) |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,52 +0,0 @@
|
||||
/* |
||||
* Copyright 2020-2023 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
package org.jetbrains.compose.internal.service |
||||
|
||||
import org.gradle.api.Project |
||||
import org.gradle.api.provider.MapProperty |
||||
import org.gradle.api.services.BuildServiceParameters |
||||
import org.jetbrains.compose.experimental.internal.SUPPORTED_NATIVE_CACHE_KIND_PROPERTIES |
||||
import org.jetbrains.compose.internal.utils.loadProperties |
||||
import org.jetbrains.compose.internal.utils.localPropertiesFile |
||||
|
||||
internal abstract class GradlePropertySnapshotService : AbstractComposeMultiplatformBuildService<GradlePropertySnapshotService.Parameters>() { |
||||
interface Parameters : BuildServiceParameters { |
||||
val gradlePropertiesCacheKindSnapshot: MapProperty<String, String> |
||||
val localPropertiesCacheKindSnapshot: MapProperty<String, String> |
||||
} |
||||
|
||||
internal val gradleProperties: Map<String, String> = parameters.gradlePropertiesCacheKindSnapshot.get() |
||||
internal val localProperties: Map<String, String> = parameters.localPropertiesCacheKindSnapshot.get() |
||||
|
||||
companion object { |
||||
fun init(project: Project) { |
||||
registerServiceIfAbsent<GradlePropertySnapshotService, Parameters>(project) { |
||||
// WORKAROUND! Call getter at least once, because of Issue: https://github.com/gradle/gradle/issues/27099 |
||||
gradlePropertiesCacheKindSnapshot |
||||
localPropertiesCacheKindSnapshot |
||||
initParams(project) |
||||
} |
||||
} |
||||
|
||||
fun getInstance(project: Project): GradlePropertySnapshotService = |
||||
getExistingServiceRegistration<GradlePropertySnapshotService, Parameters>(project).service.get() |
||||
|
||||
private fun Parameters.initParams(project: Project) { |
||||
// we want to record original properties (explicitly set by a user) |
||||
// before we possibly change them in configureNativeCompilerCaching.kt |
||||
val rootProject = project.rootProject |
||||
val localProperties = loadProperties(rootProject.localPropertiesFile) |
||||
for (cacheKindProperty in SUPPORTED_NATIVE_CACHE_KIND_PROPERTIES) { |
||||
rootProject.findProperty(cacheKindProperty)?.toString()?.let { value -> |
||||
gradlePropertiesCacheKindSnapshot.put(cacheKindProperty, value) |
||||
} |
||||
localProperties[cacheKindProperty]?.toString()?.let { value -> |
||||
localPropertiesCacheKindSnapshot.put(cacheKindProperty, value) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,36 +0,0 @@
|
||||
/* |
||||
* Copyright 2020-2023 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
package org.jetbrains.compose.internal.utils |
||||
|
||||
import org.gradle.api.Project |
||||
import org.jetbrains.compose.internal.service.GradlePropertySnapshotService |
||||
|
||||
/** |
||||
* Reads Kotlin Gradle plugin properties. |
||||
* |
||||
* Kotlin Gradle plugin supports reading property from two sources: |
||||
* 1. Gradle properties. Normally located in gradle.properties file, |
||||
* but can also be provided via command-line, <GRADLE_HOME>/gradle.properties |
||||
* or can be set via Gradle API. |
||||
* 2. local.properties file. local.properties file is not supported by Gradle out-of-the-box. |
||||
* Nevertheless, it became a widespread convention. |
||||
*/ |
||||
internal abstract class KGPPropertyProvider { |
||||
abstract fun valueOrNull(propertyName: String): String? |
||||
abstract val location: String |
||||
|
||||
class GradleProperties(private val project: Project) : KGPPropertyProvider() { |
||||
override fun valueOrNull(propertyName: String): String? = |
||||
GradlePropertySnapshotService.getInstance(project).gradleProperties[propertyName] |
||||
override val location: String = "gradle.properties" |
||||
} |
||||
|
||||
class LocalProperties(private val project: Project) : KGPPropertyProvider() { |
||||
override fun valueOrNull(propertyName: String): String? = |
||||
GradlePropertySnapshotService.getInstance(project).localProperties[propertyName] |
||||
override val location: String = "local.properties" |
||||
} |
||||
} |
@ -1,52 +0,0 @@
|
||||
package org.jetbrains.compose.test.tests.integration |
||||
|
||||
import org.jetbrains.compose.createWarningAboutNonCompatibleCompiler |
||||
import org.jetbrains.compose.test.utils.GradlePluginTestBase |
||||
import org.jetbrains.compose.test.utils.TestProjects |
||||
import org.jetbrains.compose.test.utils.checks |
||||
import org.junit.jupiter.api.Test |
||||
|
||||
class UnsupportedCompilerPluginWarningTest : GradlePluginTestBase() { |
||||
|
||||
private val androidxComposeCompilerGroupId = "androidx.compose.compiler" |
||||
private val androidxComposeCompilerPlugin = "$androidxComposeCompilerGroupId:compiler:1.4.8" |
||||
|
||||
private fun testCustomCompilerUnsupportedPlatformsWarning( |
||||
platforms: String, |
||||
warningIsExpected: Boolean |
||||
) { |
||||
testProject( |
||||
TestProjects.customCompilerUnsupportedPlatformsWarning, defaultTestEnvironment.copy( |
||||
kotlinVersion = "1.8.22", |
||||
composeCompilerPlugin = "\"$androidxComposeCompilerPlugin\"", |
||||
) |
||||
).apply { |
||||
// repeat twice to check that configuration cache hit does not affect the result |
||||
repeat(2) { |
||||
gradle("-Pplatforms=$platforms").checks { |
||||
val warning = createWarningAboutNonCompatibleCompiler(androidxComposeCompilerGroupId) |
||||
if (warningIsExpected) { |
||||
check.logContainsOnce(warning) |
||||
} else { |
||||
check.logDoesntContain(warning) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun testJs() { |
||||
testCustomCompilerUnsupportedPlatformsWarning("js", warningIsExpected = true) |
||||
} |
||||
|
||||
@Test |
||||
fun testIos() { |
||||
testCustomCompilerUnsupportedPlatformsWarning("ios", warningIsExpected = true) |
||||
} |
||||
|
||||
@Test |
||||
fun testJvm() { |
||||
testCustomCompilerUnsupportedPlatformsWarning("jvm", warningIsExpected = false) |
||||
} |
||||
} |
Loading…
Reference in new issue