1 /*
<lambda>null2 * Copyright 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package androidx.build
18
19 import com.android.build.api.variant.LintLifecycleExtension
20 import com.android.build.gradle.AppPlugin
21 import com.android.build.gradle.LibraryPlugin
22 import java.io.File
23 import org.gradle.api.Plugin
24 import org.gradle.api.Project
25 import org.gradle.api.artifacts.type.ArtifactTypeDefinition
26 import org.gradle.api.attributes.Attribute
27 import org.gradle.api.tasks.bundling.Zip
28 import org.gradle.kotlin.dsl.create
29 import org.jetbrains.kotlin.gradle.plugin.CompilerPluginConfig
30 import org.jetbrains.kotlin.gradle.plugin.KotlinBasePluginWrapper
31 import org.jetbrains.kotlin.gradle.plugin.SubpluginOption
32 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
33
34 const val zipComposeReportsTaskName = "zipComposeCompilerReports"
35 const val zipComposeMetricsTaskName = "zipComposeCompilerMetrics"
36
37 /** Plugin to apply common configuration for Compose projects. */
38 class AndroidXComposeImplPlugin : Plugin<Project> {
39 override fun apply(project: Project) {
40 val extension =
41 project.extensions.create<AndroidXComposeExtension>("androidxCompose", project)
42 project.plugins.configureEach { plugin ->
43 when (plugin) {
44 is AppPlugin,
45 is LibraryPlugin -> {
46 project.configureAndroidCommonOptions()
47 }
48 is KotlinBasePluginWrapper -> {
49 configureComposeCompilerPlugin(project, extension)
50 }
51 }
52 }
53 }
54
55 companion object {
56 private fun Project.configureAndroidCommonOptions() {
57 extensions.findByType(LintLifecycleExtension::class.java)!!.finalizeDsl { lint ->
58 val isPublished = androidXExtension.shouldPublish()
59
60 lint.run {
61 // These lint checks are normally a warning (or lower), but we ignore (in
62 // AndroidX)
63 // warnings in Lint, so we make it an error here so it will fail the build.
64 // Note that this causes 'UnknownIssueId' lint warnings in the build log when
65 // Lint tries to apply this rule to modules that do not have this lint check, so
66 // we disable that check too
67 disable.add("UnknownIssueId")
68 error.addAll(ComposeLintWarningIdsToTreatAsErrors)
69
70 // Paths we want to disable ListIteratorChecks for
71 val ignoreListIteratorFilter =
72 listOf(
73 // These are not runtime libraries and so Iterator allocation is not
74 // relevant.
75 "compose:ui:ui-test",
76 "compose:ui:ui-tooling",
77 "compose:ui:ui-inspection",
78 // Navigation libraries are not in performance critical paths, so we can
79 // ignore them.
80 "navigation:navigation-compose",
81 "wear:compose:compose-navigation"
82 )
83
84 // Disable ListIterator if we are not in a matching path, or we are in an
85 // unpublished project
86 if (ignoreListIteratorFilter.any { path.contains(it) } || !isPublished) {
87 disable.add("ListIterator")
88 }
89
90 // b/333784604 Disable ConfigurationScreenWidthHeight for wear libraries, it
91 // does not apply to wear
92 if (path.startsWith(":wear:")) {
93 disable.add("ConfigurationScreenWidthHeight")
94 }
95
96 // These checks are not required for samples projects.
97 if (androidXExtension.type == SoftwareType.SAMPLES) {
98 disable.add("ListIterator")
99 disable.add("PrimitiveInCollection")
100 }
101 }
102 }
103
104 if (!allowMissingLintProject()) {
105 // TODO: figure out how to apply this to multiplatform modules
106 dependencies.add(
107 "lintChecks",
108 project.dependencies.project(
109 mapOf(
110 "path" to ":compose:lint:internal-lint-checks",
111 // TODO(b/206617878) remove this shadow configuration
112 "configuration" to "shadow"
113 )
114 )
115 )
116 }
117 }
118 }
119 }
120
121 private const val COMPILER_PLUGIN_CONFIGURATION = "kotlinPlugin"
122
configureComposeCompilerPluginnull123 private fun configureComposeCompilerPlugin(project: Project, extension: AndroidXComposeExtension) {
124 project.afterEvaluate {
125 // If a project has opted-out of Compose compiler plugin, don't add it
126 if (!extension.composeCompilerPluginEnabled) return@afterEvaluate
127
128 // Create configuration that we'll use to load Compose compiler plugin
129 val configuration =
130 project.configurations.create(COMPILER_PLUGIN_CONFIGURATION) {
131 it.isCanBeConsumed = false
132 }
133 // Add Compose compiler plugin to kotlinPlugin configuration, making sure it works
134 // for Playground builds as well
135 val isPlayground = ProjectLayoutType.isPlayground(project)
136 val compilerPluginVersion = project.getVersionByName("kotlin")
137 project.dependencies.add(
138 COMPILER_PLUGIN_CONFIGURATION,
139 "org.jetbrains.kotlin:kotlin-compose-compiler-plugin-embeddable:$compilerPluginVersion"
140 )
141
142 if (
143 !isPlayground &&
144 // ksp is also a compiler plugin, updating Kotlin for it will likely break the build
145 !project.plugins.hasPlugin("com.google.devtools.ksp")
146 ) {
147 if (compilerPluginVersion.endsWith("-SNAPSHOT")) {
148 // use exact project path instead of subprojects.find, it is faster
149 val compilerProject = project.rootProject.resolveProject(":compose")
150 val compilerMavenDirectory =
151 File(
152 compilerProject.projectDir,
153 "compiler/compose-compiler-snapshot-repository"
154 )
155 project.repositories.maven { it.url = compilerMavenDirectory.toURI() }
156 project.configurations.configureEach {
157 it.resolutionStrategy.eachDependency { dep ->
158 val requested = dep.requested
159 if (
160 requested.group == "org.jetbrains.kotlin" &&
161 (requested.name == "kotlin-compiler-embeddable" ||
162 requested.name == "kotlin-compose-compiler-plugin-embeddable")
163 ) {
164 dep.useVersion(compilerPluginVersion)
165 }
166 }
167 }
168 }
169 }
170
171 val kotlinPluginProvider =
172 project.provider {
173 configuration.incoming
174 .artifactView { view ->
175 view.attributes { attributes ->
176 attributes.attribute(
177 Attribute.of("artifactType", String::class.java),
178 ArtifactTypeDefinition.JAR_TYPE
179 )
180 }
181 }
182 .files
183 }
184
185 val enableMetrics = project.enableComposeCompilerMetrics()
186 val enableReports = project.enableComposeCompilerReports()
187
188 val compileTasks = project.tasks.withType(KotlinCompile::class.java)
189
190 compileTasks.configureEach { compile ->
191 compile.inputs.property("composeMetricsEnabled", enableMetrics)
192 compile.inputs.property("composeReportsEnabled", enableReports)
193
194 compile.pluginClasspath.from(kotlinPluginProvider.get())
195
196 compile.enableFeatureFlag(ComposeFeatureFlag.OptimizeNonSkippingGroups)
197 compile.enableFeatureFlag(ComposeFeatureFlag.PausableComposition)
198
199 compile.addPluginOption(ComposeCompileOptions.SourceOption, "true")
200 }
201
202 if (enableMetrics) {
203 project.rootProject.tasks.named(zipComposeMetricsTaskName).configure { zipTask ->
204 zipTask.dependsOn(compileTasks)
205 }
206
207 val metricsIntermediateDir = project.compilerMetricsIntermediatesDir()
208 compileTasks.configureEach { compile ->
209 compile.addPluginOption(
210 ComposeCompileOptions.MetricsOption,
211 metricsIntermediateDir.path
212 )
213 }
214 }
215 if (enableReports) {
216 project.rootProject.tasks.named(zipComposeReportsTaskName).configure { zipTask ->
217 zipTask.dependsOn(compileTasks)
218 }
219
220 val reportsIntermediateDir = project.compilerReportsIntermediatesDir()
221 compileTasks.configureEach { compile ->
222 compile.addPluginOption(
223 ComposeCompileOptions.ReportsOption,
224 reportsIntermediateDir.path
225 )
226 }
227 }
228 }
229 }
230
KotlinCompilenull231 private fun KotlinCompile.addPluginOption(
232 composeCompileOptions: ComposeCompileOptions,
233 value: String
234 ) =
235 pluginOptions.add(
236 CompilerPluginConfig().apply {
237 addPluginArgument(
238 composeCompileOptions.pluginId,
239 SubpluginOption(composeCompileOptions.key, value)
240 )
241 }
242 )
243
KotlinCompilenull244 private fun KotlinCompile.enableFeatureFlag(featureFlag: ComposeFeatureFlag) {
245 addPluginOption(ComposeCompileOptions.FeatureFlagOption, featureFlag.featureName)
246 }
247
KotlinCompilenull248 private fun KotlinCompile.disableFeatureFlag(featureFlag: ComposeFeatureFlag) {
249 addPluginOption(ComposeCompileOptions.FeatureFlagOption, "-${featureFlag.featureName}")
250 }
251
zipComposeCompilerMetricsnull252 internal fun Project.zipComposeCompilerMetrics() {
253 if (project.enableComposeCompilerMetrics()) {
254 val zipComposeMetrics =
255 project.tasks.register(zipComposeMetricsTaskName, Zip::class.java) { zipTask ->
256 zipTask.from(project.compilerMetricsIntermediatesDir())
257 zipTask.destinationDirectory.set(project.composeCompilerDataDir())
258 zipTask.archiveBaseName.set("composemetrics")
259 }
260 project.addToBuildOnServer(zipComposeMetrics)
261 }
262 }
263
zipComposeCompilerReportsnull264 internal fun Project.zipComposeCompilerReports() {
265 if (project.enableComposeCompilerReports()) {
266 val zipComposeReports =
267 project.tasks.register(zipComposeReportsTaskName, Zip::class.java) { zipTask ->
268 zipTask.from(project.compilerReportsIntermediatesDir())
269 zipTask.destinationDirectory.set(project.composeCompilerDataDir())
270 zipTask.archiveBaseName.set("composereports")
271 }
272 project.addToBuildOnServer(zipComposeReports)
273 }
274 }
275
Projectnull276 private fun Project.compilerMetricsIntermediatesDir(): File {
277 return project.rootProject.layout.buildDirectory
278 .dir("libraryreports/composemetrics")
279 .get()
280 .asFile
281 }
282
Projectnull283 private fun Project.compilerReportsIntermediatesDir(): File {
284 return project.rootProject.layout.buildDirectory
285 .dir("libraryreports/composereports")
286 .get()
287 .asFile
288 }
289
Projectnull290 private fun Project.composeCompilerDataDir(): File {
291 return File(getDistributionDirectory(), "compose-compiler-data")
292 }
293
294 private const val ComposePluginId = "androidx.compose.compiler.plugins.kotlin"
295
296 private enum class ComposeCompileOptions(val pluginId: String, val key: String) {
297 SourceOption(ComposePluginId, "sourceInformation"),
298 StrongSkipping(ComposePluginId, "strongSkipping"),
299 NonSkippingGroupOptimization(ComposePluginId, "nonSkippingGroupOptimization"),
300 MetricsOption(ComposePluginId, "metricsDestination"),
301 ReportsOption(ComposePluginId, "reportsDestination"),
302 FeatureFlagOption(ComposePluginId, "featureFlag"),
303 }
304
305 private enum class ComposeFeatureFlag(val featureName: String) {
306 StrongSkipping("StrongSkipping"),
307 OptimizeNonSkippingGroups("OptimizeNonSkippingGroups"),
308 PausableComposition("PausableComposition"),
309 }
310