From d1fc6d0793e42e83da2645ad18436b64c4021c24 Mon Sep 17 00:00:00 2001 From: Jake Wharton Date: Tue, 12 Dec 2023 22:59:05 -0500 Subject: [PATCH] Add DSL helpers to enable metrics and reports (#1627) --- .../redwood/gradle/RedwoodComposeExtension.kt | 53 +++++++++++++-- .../gradle/RedwoodComposeExtensionImpl.kt | 65 +++++++++++++++++++ .../redwood/gradle/RedwoodComposePlugin.kt | 7 +- .../redwood/gradle/RedwoodGeneratorPlugin.kt | 2 +- .../redwood/gradle/RedwoodSchemaPlugin.kt | 2 +- .../kotlin/app/cash/redwood/gradle/util.kt | 11 ++++ .../compose-compiler-metrics/build.gradle | 31 +++++++++ .../compose-compiler-metrics/settings.gradle | 7 ++ .../src/main/kotlin/com/example/double.kt | 23 +++++++ .../compose-compiler-reports/build.gradle | 31 +++++++++ .../compose-compiler-reports/settings.gradle | 7 ++ .../src/main/kotlin/com/example/double.kt | 23 +++++++ .../app/cash/redwood/gradle/FixtureTest.kt | 32 +++++++++ 13 files changed, 284 insertions(+), 10 deletions(-) create mode 100644 redwood-gradle-plugin/src/main/kotlin/app/cash/redwood/gradle/RedwoodComposeExtensionImpl.kt create mode 100644 redwood-gradle-plugin/src/test/fixture/compose-compiler-metrics/build.gradle create mode 100644 redwood-gradle-plugin/src/test/fixture/compose-compiler-metrics/settings.gradle create mode 100644 redwood-gradle-plugin/src/test/fixture/compose-compiler-metrics/src/main/kotlin/com/example/double.kt create mode 100644 redwood-gradle-plugin/src/test/fixture/compose-compiler-reports/build.gradle create mode 100644 redwood-gradle-plugin/src/test/fixture/compose-compiler-reports/settings.gradle create mode 100644 redwood-gradle-plugin/src/test/fixture/compose-compiler-reports/src/main/kotlin/com/example/double.kt diff --git a/redwood-gradle-plugin/src/main/kotlin/app/cash/redwood/gradle/RedwoodComposeExtension.kt b/redwood-gradle-plugin/src/main/kotlin/app/cash/redwood/gradle/RedwoodComposeExtension.kt index 4d3ec3d53b..ddc12a6eec 100644 --- a/redwood-gradle-plugin/src/main/kotlin/app/cash/redwood/gradle/RedwoodComposeExtension.kt +++ b/redwood-gradle-plugin/src/main/kotlin/app/cash/redwood/gradle/RedwoodComposeExtension.kt @@ -15,12 +15,10 @@ */ package app.cash.redwood.gradle -import javax.inject.Inject -import org.gradle.api.model.ObjectFactory +import org.gradle.api.Action import org.gradle.api.provider.Property -public abstract class RedwoodComposeExtension -@Inject constructor(objectFactory: ObjectFactory) { +public interface RedwoodComposeExtension { /** * The version of the JetBrains Compose compiler to use, or a Maven coordinate triple of * the custom Compose compiler to use. @@ -39,7 +37,48 @@ public abstract class RedwoodComposeExtension * } * ``` */ - public val kotlinCompilerPlugin: Property = - objectFactory.property(String::class.java) - .convention(composeCompilerVersion) + public val kotlinCompilerPlugin: Property + + /** + * Configuration options that require extra care when used. + * + * @see DangerZone + */ + public fun dangerZone(body: Action) + + /** + * Configuration options that require extra care when used. Please read the documentation of + * each member carefully to understand how it affects your build. + */ + public interface DangerZone { + /** + * Enable the output of metrics from the Compose compiler. + * + * Text files will be written to `generated/redwood/compose-metrics/` in the project's build + * directory. See + * [the compiler documentation](https://github.com/androidx/androidx/blob/androidx-main/compose/compiler/design/compiler-metrics.md#reports-breakdown) + * for more information about the contents. + * + * **NOTE:** This should only be enabled during investigation as it breaks the use of + * Gradle's build cache for this project's Kotlin compilation tasks. + * + * @see enableReports + */ + public fun enableMetrics() + + /** + * Enable the output of reports from the Compose compiler. + * + * Text files will be written to `generated/redwood/compose-reports/` in the project's build + * directory. See + * [the compiler documentation](https://github.com/androidx/androidx/blob/androidx-main/compose/compiler/design/compiler-metrics.md#reports-breakdown) + * for more information about the contents. + * + * **NOTE:** This should only be enabled during investigation as it breaks the use of + * Gradle's build cache for this project's Kotlin compilation tasks. + * + * @see enableMetrics + */ + public fun enableReports() + } } diff --git a/redwood-gradle-plugin/src/main/kotlin/app/cash/redwood/gradle/RedwoodComposeExtensionImpl.kt b/redwood-gradle-plugin/src/main/kotlin/app/cash/redwood/gradle/RedwoodComposeExtensionImpl.kt new file mode 100644 index 0000000000..59afab41cc --- /dev/null +++ b/redwood-gradle-plugin/src/main/kotlin/app/cash/redwood/gradle/RedwoodComposeExtensionImpl.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2023 Square, Inc. + * + * 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 app.cash.redwood.gradle + +import app.cash.redwood.gradle.RedwoodComposeExtension.DangerZone +import org.gradle.api.Action +import org.gradle.api.Project +import org.gradle.api.provider.Property +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +internal class RedwoodComposeExtensionImpl( + private val project: Project, +) : RedwoodComposeExtension, DangerZone { + private var metricsEnabled = false + private var reportsEnabled = false + + // Explicit backing property avoids Gradle attempting to reflect on an implicit backing field. + override val kotlinCompilerPlugin: Property get() = _kotlinCompilerPlugin + + private val _kotlinCompilerPlugin = project.objects.property(String::class.java) + .convention(composeCompilerVersion) + + override fun dangerZone(body: Action) { + body.execute(this) + } + + override fun enableMetrics() { + if (metricsEnabled) return + metricsEnabled = true + + project.tasks.withType(KotlinCompile::class.java).configureEach { + val dir = project.redwoodReportDir("compose-metrics/${it.name}").get().asFile.absolutePath + it.compilerOptions.freeCompilerArgs.addAll( + "-P", + "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=$dir", + ) + } + } + + override fun enableReports() { + if (reportsEnabled) return + reportsEnabled = true + + project.tasks.withType(KotlinCompile::class.java).configureEach { + val dir = project.redwoodReportDir("compose-reports/${it.name}").get().asFile.absolutePath + it.compilerOptions.freeCompilerArgs.addAll( + "-P", + "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=$dir", + ) + } + } +} diff --git a/redwood-gradle-plugin/src/main/kotlin/app/cash/redwood/gradle/RedwoodComposePlugin.kt b/redwood-gradle-plugin/src/main/kotlin/app/cash/redwood/gradle/RedwoodComposePlugin.kt index 3721578224..438c347c14 100644 --- a/redwood-gradle-plugin/src/main/kotlin/app/cash/redwood/gradle/RedwoodComposePlugin.kt +++ b/redwood-gradle-plugin/src/main/kotlin/app/cash/redwood/gradle/RedwoodComposePlugin.kt @@ -38,7 +38,12 @@ public class RedwoodComposePlugin : KotlinCompilerPluginSupportPlugin { override fun apply(target: Project) { super.apply(target) - extension = target.extensions.create(EXTENSION_NAME, RedwoodComposeExtension::class.java) + extension = RedwoodComposeExtensionImpl(target) + target.extensions.add( + RedwoodComposeExtension::class.java, + EXTENSION_NAME, + extension, + ) target.plugins.withId("org.jetbrains.compose") { throw IllegalStateException( diff --git a/redwood-gradle-plugin/src/main/kotlin/app/cash/redwood/gradle/RedwoodGeneratorPlugin.kt b/redwood-gradle-plugin/src/main/kotlin/app/cash/redwood/gradle/RedwoodGeneratorPlugin.kt index d2ffe75839..10d02b4162 100644 --- a/redwood-gradle-plugin/src/main/kotlin/app/cash/redwood/gradle/RedwoodGeneratorPlugin.kt +++ b/redwood-gradle-plugin/src/main/kotlin/app/cash/redwood/gradle/RedwoodGeneratorPlugin.kt @@ -88,7 +88,7 @@ public abstract class RedwoodGeneratorPlugin( it.description = "Generate Redwood Kotlin sources" it.toolClasspath.from(toolingConfiguration) - it.outputDir.set(project.layout.buildDirectory.dir("generated/redwood")) + it.outputDir.set(project.redwoodGeneratedDir("sources")) it.generatorFlag.set(strategy.generatorFlag) it.schemaType.set(extension.type) it.classpath.from(schemaConfiguration) diff --git a/redwood-gradle-plugin/src/main/kotlin/app/cash/redwood/gradle/RedwoodSchemaPlugin.kt b/redwood-gradle-plugin/src/main/kotlin/app/cash/redwood/gradle/RedwoodSchemaPlugin.kt index 990081c33c..6b5560a9b2 100644 --- a/redwood-gradle-plugin/src/main/kotlin/app/cash/redwood/gradle/RedwoodSchemaPlugin.kt +++ b/redwood-gradle-plugin/src/main/kotlin/app/cash/redwood/gradle/RedwoodSchemaPlugin.kt @@ -62,7 +62,7 @@ public class RedwoodSchemaPlugin : Plugin { it.description = "Generate parsed schema JSON" it.toolClasspath.from(toolingConfiguration) - it.outputDir.set(project.layout.buildDirectory.dir("generated/redwood")) + it.outputDir.set(project.redwoodGeneratedDir("schema-json")) it.schemaType.set(extension.type) it.classpath.from(classpath, compilation.output.classesDirs) } diff --git a/redwood-gradle-plugin/src/main/kotlin/app/cash/redwood/gradle/util.kt b/redwood-gradle-plugin/src/main/kotlin/app/cash/redwood/gradle/util.kt index 22b146557a..3ec06ac579 100644 --- a/redwood-gradle-plugin/src/main/kotlin/app/cash/redwood/gradle/util.kt +++ b/redwood-gradle-plugin/src/main/kotlin/app/cash/redwood/gradle/util.kt @@ -16,6 +16,9 @@ package app.cash.redwood.gradle import org.gradle.api.Project +import org.gradle.api.file.Directory +import org.gradle.api.provider.Provider +import org.gradle.api.reporting.ReportingExtension internal fun Project.redwoodDependency(artifactId: String): Any { // Indicates when the plugin is applied inside the Redwood repo to Redwood's own modules. This @@ -28,3 +31,11 @@ internal fun Project.redwoodDependency(artifactId: String): Any { "app.cash.redwood:$artifactId:$redwoodVersion" } } + +internal fun Project.redwoodGeneratedDir(name: String): Provider { + return layout.buildDirectory.dir("generated/redwood/$name") +} + +internal fun Project.redwoodReportDir(name: String): Provider { + return extensions.getByType(ReportingExtension::class.java).baseDirectory.dir("redwood/$name") +} diff --git a/redwood-gradle-plugin/src/test/fixture/compose-compiler-metrics/build.gradle b/redwood-gradle-plugin/src/test/fixture/compose-compiler-metrics/build.gradle new file mode 100644 index 0000000000..e0e0ff4f9a --- /dev/null +++ b/redwood-gradle-plugin/src/test/fixture/compose-compiler-metrics/build.gradle @@ -0,0 +1,31 @@ +buildscript { + dependencies { + classpath "app.cash.redwood:redwood-gradle-plugin:$redwoodVersion" + classpath libs.kotlin.gradlePlugin + } + + repositories { + maven { + url "file://${rootDir.absolutePath}/../../../../../build/localMaven" + } + mavenCentral() + google() + } +} + +apply plugin: 'org.jetbrains.kotlin.jvm' +apply plugin: 'app.cash.redwood' + +redwood { + dangerZone { + enableMetrics() + } +} + +repositories { + maven { + url "file://${rootDir.absolutePath}/../../../../../build/localMaven" + } + mavenCentral() + google() +} diff --git a/redwood-gradle-plugin/src/test/fixture/compose-compiler-metrics/settings.gradle b/redwood-gradle-plugin/src/test/fixture/compose-compiler-metrics/settings.gradle new file mode 100644 index 0000000000..f348f3831c --- /dev/null +++ b/redwood-gradle-plugin/src/test/fixture/compose-compiler-metrics/settings.gradle @@ -0,0 +1,7 @@ +dependencyResolutionManagement { + versionCatalogs { + libs { + from(files('../../../../../gradle/libs.versions.toml')) + } + } +} diff --git a/redwood-gradle-plugin/src/test/fixture/compose-compiler-metrics/src/main/kotlin/com/example/double.kt b/redwood-gradle-plugin/src/test/fixture/compose-compiler-metrics/src/main/kotlin/com/example/double.kt new file mode 100644 index 0000000000..bd0f61f9d6 --- /dev/null +++ b/redwood-gradle-plugin/src/test/fixture/compose-compiler-metrics/src/main/kotlin/com/example/double.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 Square, Inc. + * + * 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 com.example + +import androidx.compose.runtime.Composable + +@Composable +fun double(value: Int): Int { + return value * 2 +} diff --git a/redwood-gradle-plugin/src/test/fixture/compose-compiler-reports/build.gradle b/redwood-gradle-plugin/src/test/fixture/compose-compiler-reports/build.gradle new file mode 100644 index 0000000000..12d91a48c6 --- /dev/null +++ b/redwood-gradle-plugin/src/test/fixture/compose-compiler-reports/build.gradle @@ -0,0 +1,31 @@ +buildscript { + dependencies { + classpath "app.cash.redwood:redwood-gradle-plugin:$redwoodVersion" + classpath libs.kotlin.gradlePlugin + } + + repositories { + maven { + url "file://${rootDir.absolutePath}/../../../../../build/localMaven" + } + mavenCentral() + google() + } +} + +apply plugin: 'org.jetbrains.kotlin.jvm' +apply plugin: 'app.cash.redwood' + +redwood { + dangerZone { + enableReports() + } +} + +repositories { + maven { + url "file://${rootDir.absolutePath}/../../../../../build/localMaven" + } + mavenCentral() + google() +} diff --git a/redwood-gradle-plugin/src/test/fixture/compose-compiler-reports/settings.gradle b/redwood-gradle-plugin/src/test/fixture/compose-compiler-reports/settings.gradle new file mode 100644 index 0000000000..f348f3831c --- /dev/null +++ b/redwood-gradle-plugin/src/test/fixture/compose-compiler-reports/settings.gradle @@ -0,0 +1,7 @@ +dependencyResolutionManagement { + versionCatalogs { + libs { + from(files('../../../../../gradle/libs.versions.toml')) + } + } +} diff --git a/redwood-gradle-plugin/src/test/fixture/compose-compiler-reports/src/main/kotlin/com/example/double.kt b/redwood-gradle-plugin/src/test/fixture/compose-compiler-reports/src/main/kotlin/com/example/double.kt new file mode 100644 index 0000000000..bd0f61f9d6 --- /dev/null +++ b/redwood-gradle-plugin/src/test/fixture/compose-compiler-reports/src/main/kotlin/com/example/double.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 Square, Inc. + * + * 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 com.example + +import androidx.compose.runtime.Composable + +@Composable +fun double(value: Int): Int { + return value * 2 +} diff --git a/redwood-gradle-plugin/src/test/kotlin/app/cash/redwood/gradle/FixtureTest.kt b/redwood-gradle-plugin/src/test/kotlin/app/cash/redwood/gradle/FixtureTest.kt index 4ac55fb256..d0c8fcf595 100644 --- a/redwood-gradle-plugin/src/test/kotlin/app/cash/redwood/gradle/FixtureTest.kt +++ b/redwood-gradle-plugin/src/test/kotlin/app/cash/redwood/gradle/FixtureTest.kt @@ -15,11 +15,15 @@ */ package app.cash.redwood.gradle +import assertk.all import assertk.assertThat import assertk.assertions.contains import assertk.assertions.containsExactly +import assertk.assertions.containsOnly +import assertk.assertions.exists import assertk.assertions.isEqualTo import assertk.assertions.isNotEmpty +import assertk.assertions.isNotNull import assertk.assertions.prop import java.io.File import org.gradle.testkit.runner.BuildTask @@ -208,6 +212,34 @@ class FixtureTest { ) } + @Test fun composeCompilerMetrics() { + val fixtureDir = File("src/test/fixture/compose-compiler-metrics") + fixtureGradleRunner(fixtureDir).build() + assertThat(fixtureDir.resolve("build/reports/redwood/compose-metrics/compileKotlin")).all { + exists() + prop("children", File::list) + .isNotNull() + .containsOnly( + "compose-compiler-metrics-module.json", + ) + } + } + + @Test fun composeCompilerReports() { + val fixtureDir = File("src/test/fixture/compose-compiler-reports") + fixtureGradleRunner(fixtureDir).build() + assertThat(fixtureDir.resolve("build/reports/redwood/compose-reports/compileKotlin")).all { + exists() + prop("children", File::list) + .isNotNull() + .containsOnly( + "compose-compiler-reports-classes.txt", + "compose-compiler-reports-composables.csv", + "compose-compiler-reports-composables.txt", + ) + } + } + private fun fixtureGradleRunner( fixtureDir: File, vararg tasks: String = arrayOf("clean", "build"),