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