-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix reporting configuration problems with configuration cache (#3596)
Sometimes we need to report warnings during the configuration phase. For example, when Androidx Compose Compiler is used with non-JVM targets (e.g. iOS/js), we want to warn users that using non-JB compiler with non-JVM targets is not supported. The default way of reporting warnings in Gradle is using a logger. For example we could write something like: ``` abstract class ComposePlugin : Plugin<Project> { override fun apply(project: Project) { if (project.hasNonJvmTargets() && project.usesNonJBComposeCompiler()) { project.logger.warn("...") } } } ``` This approach has a few issues: 1. When the Configuration Cache is enabled, project's configuration might get skipped altogether, and the warning won't be printed. 2. If a project contains multiple Gradle modules (subprojects), the warning might be printed multiple times. That might be OK for some warnings. But repeating exactly the same warning 10s or 100s is unnecessary. The only way to share the state between Gradle modules, while preserving compatibility with the Configuration Cache, is to define Gradle Build Service. In 1.5.0 we used Gradle Build Service mechanism for both warnings. However, I did not know that: * only the service's parameters are persisted in the Configuration Cache. The service itself is not persisted. * if a service instance is materialized during the configuration phase, then all changes made to its parameters will not be visible to that particular instance (but they will be visible to the next instance). So the only way to report diagnostics with configuration cache without repetition is to define a service that is not materialized during the configuration phase (i.e. serviceProvider.get() is not called), add to add warnings to a set property of the service. This change implements that. Resolves #3595
- Loading branch information
1 parent
f81f6fa
commit eb124fe
Showing
16 changed files
with
332 additions
and
167 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
119 changes: 0 additions & 119 deletions
119
...plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeMultiplatformBuildService.kt
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
81 changes: 81 additions & 0 deletions
81
...kotlin/org/jetbrains/compose/internal/service/AbstractComposeMultiplatformBuildService.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
/* | ||
* 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
66 changes: 66 additions & 0 deletions
66
...main/kotlin/org/jetbrains/compose/internal/service/ConfigurationProblemReporterService.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
/* | ||
* 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) | ||
} | ||
|
||
private inline fun configureParameters(project: Project, fn: Parameters.() -> Unit) { | ||
getExistingServiceRegistration<ConfigurationProblemReporterService, Parameters>(project) | ||
.parameters.fn() | ||
} | ||
|
||
fun reportProblem(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) } | ||
}) | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.