1 /*
2  * Copyright 2023 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.baselineprofile.gradle.consumer
18 
19 import androidx.baselineprofile.gradle.utils.WarningsExtension
20 import javax.inject.Inject
21 import org.gradle.api.Action
22 import org.gradle.api.NamedDomainObjectContainer
23 import org.gradle.api.Project
24 import org.gradle.api.model.ObjectFactory
25 import org.gradle.api.plugins.ExtensionAware
26 
27 /** Allows specifying settings for the Baseline Profile Consumer Plugin. */
28 abstract class BaselineProfileConsumerExtension @Inject constructor(objectFactory: ObjectFactory) :
29     BaselineProfileVariantConfiguration, ExtensionAware {
30 
31     companion object {
32         private const val EXTENSION_NAME = "baselineProfile"
33 
registernull34         internal fun register(project: Project): BaselineProfileConsumerExtension {
35             val ext = project.extensions.findByType(BaselineProfileConsumerExtension::class.java)
36             if (ext != null) {
37                 return ext
38             }
39             return project.extensions.create(
40                 EXTENSION_NAME,
41                 BaselineProfileConsumerExtension::class.java
42             )
43         }
44     }
45 
46     val warnings = WarningsExtension.register(this.extensions)
47 
48     val variants: NamedDomainObjectContainer<BaselineProfileVariantConfigurationImpl> =
49         objectFactory.domainObjectContainer(BaselineProfileVariantConfigurationImpl::class.java)
50 
51     // Shortcut to access the "main" variant.
52     private val main: BaselineProfileVariantConfiguration =
<lambda>null53         variants.create("main") {
54 
55             // These are the default global settings.
56             it.mergeIntoMain = null
57             it.baselineProfileOutputDir = "generated/baselineProfiles"
58             it.baselineProfileRulesRewrite = null
59             it.dexLayoutOptimization = null
60             it.saveInSrc = true
61             it.automaticGenerationDuringBuild = false
62         }
63 
64     /**
65      * Controls whether Android Studio should hide synthetic build types created to generate
66      * baseline profiles and run benchmarks. These build types are copied from the existing release
67      * ones, adding as prefix `nonMinified` and `benchmark`. For example, if the build type is
68      * `release` the new build type will be `nonMinifiedRelease` and `benchmarkRelease`. Note that
69      * in case of defined product flavors, these are normally merged in the variant name. For
70      * example with flavor `free` the variant name will be `freeNonMinifiedRelease` and
71      * `freeBenchmarkRelease`.
72      */
73     var hideSyntheticBuildTypesInAndroidStudio: Boolean = true
74 
75     /**
76      * Controls the global [BaselineProfileVariantConfiguration.baselineProfileRulesRewrite]. Note
77      * that this value is overridden by per variant configurations.
78      */
79     override var baselineProfileRulesRewrite: Boolean?
80         get() = main.baselineProfileRulesRewrite
81         set(value) {
82             main.baselineProfileRulesRewrite = value
83         }
84 
85     /**
86      * Controls the global [BaselineProfileVariantConfiguration.dexLayoutOptimization]. Note that
87      * this value is overridden by per variant configurations.
88      */
89     override var dexLayoutOptimization: Boolean?
90         get() = main.dexLayoutOptimization
91         set(value) {
92             main.dexLayoutOptimization = value
93         }
94 
95     /**
96      * Controls the global [BaselineProfileVariantConfiguration.saveInSrc]. Note that this value is
97      * overridden by per variant configurations.
98      */
99     override var saveInSrc: Boolean?
100         get() = main.saveInSrc
101         set(value) {
102             main.saveInSrc = value
103         }
104 
105     /**
106      * Controls the global [BaselineProfileVariantConfiguration.automaticGenerationDuringBuild].
107      * Note that this value is overridden by per variant configurations.
108      */
109     override var automaticGenerationDuringBuild: Boolean?
110         get() = main.automaticGenerationDuringBuild
111         set(value) {
112             main.automaticGenerationDuringBuild = value
113         }
114 
115     /**
116      * Controls the global [BaselineProfileVariantConfiguration.baselineProfileOutputDir]. Note that
117      * this value is overridden by per variant configurations.
118      */
119     override var baselineProfileOutputDir: String?
120         get() = main.baselineProfileOutputDir
121         set(value) {
122             main.baselineProfileOutputDir = value
123         }
124 
125     /**
126      * Controls the global [BaselineProfileVariantConfiguration.mergeIntoMain]. Note that this value
127      * is overridden by per variant configurations.
128      */
129     override var mergeIntoMain: Boolean?
130         get() = main.mergeIntoMain
131         set(value) {
132             main.mergeIntoMain = value
133         }
134 
135     /**
136      * Applies the global [BaselineProfileVariantConfiguration.filter]. This function is just a
137      * shortcut for `baselineProfiles.variants.main.filters { }`
138      */
filternull139     override fun filter(action: FilterRules.() -> (Unit)) = main.filter(action)
140 
141     /**
142      * Applies the global [BaselineProfileVariantConfiguration.filter]. This function is just a
143      * shortcut for `baselineProfiles.variants.main.filters { }`
144      */
145     override fun filter(action: Action<FilterRules>) = main.filter(action)
146 
147     /**
148      * Applies global dependencies for baseline profiles. This has the same effect of defining a
149      * baseline profile dependency in the dependency block. For example:
150      * ```
151      * dependencies {
152      *     baselineProfile(project(":baseline-profile"))
153      * }
154      * ```
155      */
156     override fun from(project: Project) = main.from(project)
157 
158     fun variants(
159         action: Action<NamedDomainObjectContainer<BaselineProfileVariantConfigurationImpl>>
160     ) {
161         action.execute(variants)
162     }
163 
variantsnull164     fun variants(
165         action: NamedDomainObjectContainer<out BaselineProfileVariantConfigurationImpl>.() -> Unit
166     ) {
167         action.invoke(variants)
168     }
169 }
170 
171 abstract class BaselineProfileVariantConfigurationImpl(val name: String) :
172     BaselineProfileVariantConfiguration {
173 
174     internal val filters = FilterRules()
175     internal val dependencies = mutableListOf<Project>()
176 
177     /** @inheritDoc */
filternull178     override fun filter(action: FilterRules.() -> (Unit)) = action.invoke(filters)
179 
180     /** @inheritDoc */
181     override fun filter(action: Action<FilterRules>) = action.execute(filters)
182 
183     /** @inheritDoc */
184     override fun from(project: Project) {
185         dependencies.add(project)
186     }
187 }
188 
189 /**
190  * Defines the configuration properties that each variant of a consumer module offers. Note that
191  * also [BaselineProfileConsumerExtension] is an implementation of this interface and it's simply a
192  * proxy to the `main` variant.
193  */
194 interface BaselineProfileVariantConfiguration {
195 
196     /**
197      * Enables R8 to rewrite the incoming human readable baseline profile rules to account for
198      * synthetics, so they are preserved after optimizations by R8.
199      *
200      * TODO: This feature is experimental and currently not working properly.
201      *   https://issuetracker.google.com/issue?id=271172067.
202      */
203     var baselineProfileRulesRewrite: Boolean?
204 
205     /**
206      * Enables R8 to optimize the primary dex file used to contain only classes utilized for
207      * startup.
208      */
209     var dexLayoutOptimization: Boolean?
210 
211     /**
212      * Specifies whether generated baseline profiles should be stored in the src folder. When this
213      * flag is set to true, the generated baseline profiles are stored in
214      * `src/<variant>/generated/baselineProfiles`.
215      */
216     var saveInSrc: Boolean?
217 
218     /**
219      * Specifies whether baseline profiles should be regenerated when building, for example, during
220      * a full release build for distribution. When set to true a new profile is generated as part of
221      * building the release build. This including rebuilding the non minified release, running the
222      * baseline profile tests and ultimately building the release build.
223      */
224     var automaticGenerationDuringBuild: Boolean?
225 
226     /**
227      * Specifies the output directory for generated baseline profiles when
228      * [BaselineProfileVariantConfiguration.saveInSrc] is `true`. Note that the dir specified here
229      * is created in the `src/<variant>/` folder.
230      */
231     var baselineProfileOutputDir: String?
232 
233     /**
234      * Specifies if baseline profile files should be merged into a single one when generating for
235      * multiple variants:
236      * - When `true` all the generated baseline profile for each variant are merged into
237      *   `src/main/generated/baselineProfiles`'.
238      * - When `false` each variant will have its own baseline profile in
239      *   `src/<variant>/generated/baselineProfiles`'. If this is not specified, by default it will
240      *   be true for library modules and false for application modules. Note that when `saveInSrc`
241      *   is false the output folder is in the build output folder but this setting still determines
242      *   whether the profile included in the built apk or aar includes all the variant profiles.
243      */
244     var mergeIntoMain: Boolean?
245 
246     /**
247      * Specifies a filtering rule to decide which profiles rules should be included in this consumer
248      * baseline profile. This is useful especially for libraries, in order to exclude profile rules
249      * for class and methods for dependencies of the sample app. The filter supports:
250      * - Double wildcards, to match specified package and subpackages. Example: `com.example.**`
251      * - Wildcards, to match specified package only. Example: `com.example.*`
252      * - Class names, to match the specified class. Example: `com.example.MyClass`
253      *
254      * Note that when only excludes are specified, if there are no matches with any rule the profile
255      * rule is selected.
256      *
257      * Example to include a package and all the subpackages:
258      * ```
259      *     filter { include "com.somelibrary.**" }
260      * ```
261      *
262      * Example to exclude some packages and include all the rest:
263      * ```
264      *     filter { exclude "com.somelibrary.debug" }
265      * ```
266      *
267      * Example to include and exclude specific packages:
268      * ```
269      *     filter {
270      *          include "com.somelibrary.widget.grid.**"
271      *          exclude "com.somelibrary.widget.grid.debug.**"
272      *          include "com.somelibrary.widget.list.**"
273      *          exclude "com.somelibrary.widget.grid.debug.**"
274      *          include "com.somelibrary.widget.text.**"
275      *          exclude "com.somelibrary.widget.grid.debug.**"
276      *     }
277      * ```
278      */
filternull279     fun filter(action: FilterRules.() -> (Unit))
280 
281     /**
282      * Specifies a filtering rule to decide which profiles rules should be included in this consumer
283      * baseline profile. This is useful especially for libraries, in order to exclude profile rules
284      * for class and methods for dependencies of the sample app. The filter supports:
285      * - Double wildcards, to match specified package and subpackages. Example: `com.example.**`
286      * - Wildcards, to match specified package only. Example: `com.example.*`
287      * - Class names, to match the specified class. Example: `com.example.MyClass`
288      *
289      * Note that when only excludes are specified, if there are no matches with any rule the profile
290      * rule is selected.
291      *
292      * Example to include a package and all the subpackages:
293      * ```
294      *     filter { include "com.somelibrary.**" }
295      * ```
296      *
297      * Example to exclude some packages and include all the rest:
298      * ```
299      *     filter { exclude "com.somelibrary.debug" }
300      * ```
301      *
302      * Example to include and exclude specific packages:
303      * ```
304      *     filter {
305      *          include "com.somelibrary.widget.grid.**"
306      *          exclude "com.somelibrary.widget.grid.debug.**"
307      *          include "com.somelibrary.widget.list.**"
308      *          exclude "com.somelibrary.widget.list.debug.**"
309      *          include "com.somelibrary.widget.text.**"
310      *          exclude "com.somelibrary.widget.text.debug.**"
311      *     }
312      * ```
313      */
314     fun filter(action: Action<FilterRules>)
315 
316     /**
317      * Allows to specify a target `com.android.test` module that has the `androidx.baselineprofile`
318      * plugin, and that can provide a baseline profile for this module. For example
319      *
320      * ```
321      * baselineProfile {
322      *     variants {
323      *         freeRelease {
324      *             from(project(":baseline-profile"))
325      *         }
326      *     }
327      * }
328      * ```
329      */
330     fun from(project: Project)
331 }
332 
333 class FilterRules {
334 
335     internal val rules = mutableListOf<Pair<RuleType, String>>()
336 
337     fun include(pkg: String) = rules.add(Pair(RuleType.INCLUDE, pkg))
338 
339     fun exclude(pkg: String) = rules.add(Pair(RuleType.EXCLUDE, pkg))
340 }
341 
342 enum class RuleType {
343     INCLUDE,
344     EXCLUDE
345 }
346