1 /* <lambda>null2 * 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.utils 18 19 import androidx.baselineprofile.gradle.utils.TestAgpVersion.TEST_AGP_VERSION_8_3_1 20 import androidx.testutils.gradle.ProjectSetupRule 21 import com.google.testing.platform.proto.api.core.LabelProto 22 import com.google.testing.platform.proto.api.core.PathProto 23 import com.google.testing.platform.proto.api.core.TestArtifactProto 24 import com.google.testing.platform.proto.api.core.TestResultProto 25 import com.google.testing.platform.proto.api.core.TestStatusProto 26 import com.google.testing.platform.proto.api.core.TestSuiteResultProto 27 import java.io.File 28 import java.util.Properties 29 import org.gradle.testkit.runner.GradleRunner 30 import org.junit.rules.ExternalResource 31 import org.junit.rules.RuleChain 32 import org.junit.rules.TemporaryFolder 33 import org.junit.runner.Description 34 import org.junit.runners.model.Statement 35 36 internal const val ANDROID_APPLICATION_PLUGIN = "com.android.application" 37 internal const val ANDROID_LIBRARY_PLUGIN = "com.android.library" 38 internal const val ANDROID_TEST_PLUGIN = "com.android.test" 39 internal const val EXPECTED_PROFILE_FOLDER = "generated/baselineProfiles" 40 41 class BaselineProfileProjectSetupRule( 42 private val forceAgpVersion: String? = null, 43 private val addKotlinGradlePluginToClasspath: Boolean = false 44 ) : ExternalResource() { 45 46 private val forcedTestAgpVersion = TestAgpVersion.fromVersionString(forceAgpVersion) 47 48 /** Root folder for the project setup that contains 3 modules. */ 49 val rootFolder = TemporaryFolder().also { it.create() } 50 51 /** Represents a module with the app target plugin applied. */ 52 val appTarget by lazy { 53 AppTargetModule( 54 rule = appTargetSetupRule, 55 name = appTargetName, 56 ) 57 } 58 59 /** Represents a module with the consumer plugin applied. */ 60 val consumer by lazy { 61 ConsumerModule( 62 rule = consumerSetupRule, 63 name = consumerName, 64 producerName = producerName, 65 dependencyName = dependencyName 66 ) 67 } 68 69 /** Represents a module with the producer plugin applied. */ 70 val producer by lazy { 71 ProducerModule( 72 rule = producerSetupRule, 73 name = producerName, 74 tempFolder = tempFolder, 75 consumer = consumer, 76 managedDeviceContainerName = managedDeviceContainerName, 77 ) 78 } 79 80 /** Represents a simple java library dependency module. */ 81 val dependency by lazy { DependencyModule(name = dependencyName) } 82 83 /** The managed device container name to use in the build.gradle file. */ 84 val managedDeviceContainerName: String 85 get() = "allDevices" 86 87 // Temp folder for temp generated files that need to be referenced by a module. 88 private val tempFolder by lazy { File(rootFolder.root, "temp").apply { mkdirs() } } 89 90 // Project setup rules 91 private val appTargetSetupRule by lazy { ProjectSetupRule(rootFolder.root) } 92 private val consumerSetupRule by lazy { ProjectSetupRule(rootFolder.root) } 93 private val producerSetupRule by lazy { ProjectSetupRule(rootFolder.root) } 94 private val dependencySetupRule by lazy { ProjectSetupRule(rootFolder.root) } 95 96 // Module names (generated automatically) 97 private val appTargetName: String by lazy { 98 appTargetSetupRule.rootDir.relativeTo(rootFolder.root).name 99 } 100 private val consumerName: String by lazy { 101 consumerSetupRule.rootDir.relativeTo(rootFolder.root).name 102 } 103 private val producerName: String by lazy { 104 producerSetupRule.rootDir.relativeTo(rootFolder.root).name 105 } 106 private val dependencyName: String by lazy { 107 dependencySetupRule.rootDir.relativeTo(rootFolder.root).name 108 } 109 110 override fun apply(base: Statement, description: Description): Statement { 111 return RuleChain.outerRule(appTargetSetupRule) 112 .around(producerSetupRule) 113 .around(dependencySetupRule) 114 .around(consumerSetupRule) 115 .around { b, _ -> applyInternal(b) } 116 .apply(base, description) 117 } 118 119 private fun applyInternal(base: Statement) = 120 object : Statement() { 121 override fun evaluate() { 122 123 // Creates the main gradle.properties 124 rootFolder.newFile("gradle.properties").writer().use { 125 val props = Properties() 126 props.setProperty( 127 "org.gradle.jvmargs", 128 "-Xmx4g -XX:+UseParallelGC -XX:MaxMetaspaceSize=1g" 129 ) 130 props.setProperty("android.useAndroidX", "true") 131 props.store(it, null) 132 } 133 134 // Creates the main settings.gradle 135 rootFolder 136 .newFile("settings.gradle") 137 .writeText( 138 """ 139 include '$appTargetName' 140 include '$producerName' 141 include '$dependencyName' 142 include '$consumerName' 143 """ 144 .trimIndent() 145 ) 146 147 val repositoriesBlock = 148 """ 149 repositories { 150 ${producerSetupRule.allRepositoryPaths.joinToString("\n") { """ maven { url "$it" } """ }} 151 } 152 """ 153 .trimIndent() 154 155 val agpDependency = 156 if (forceAgpVersion == null) { 157 """"${appTargetSetupRule.props.agpDependency}"""" 158 } else { 159 """ 160 ("com.android.tools.build:gradle") { version { strictly "$forceAgpVersion" } } 161 """ 162 .trimIndent() 163 } 164 165 val kotlinGradlePluginDependency = 166 if (addKotlinGradlePluginToClasspath) { 167 """ "${appTargetSetupRule.props.kgpDependency}" """ 168 } else { 169 null 170 } 171 172 rootFolder 173 .newFile("build.gradle") 174 .writeText( 175 """ 176 buildscript { 177 $repositoriesBlock 178 dependencies { 179 180 // Specifies agp dependency 181 ${ 182 listOfNotNull( 183 agpDependency, 184 kotlinGradlePluginDependency, 185 ).joinToString("\n") { "classpath $it".trim() } 186 } 187 188 // Specifies plugin dependency 189 ${ 190 listOf( 191 "consumer", 192 "producer", 193 "apptarget", 194 ).joinToString(separator = System.lineSeparator()) { 195 """ 196 classpath "androidx.baselineprofile.$it:androidx.baselineprofile.$it.gradle.plugin:+" 197 """.trimIndent() 198 } 199 } 200 } 201 } 202 203 allprojects { 204 $repositoriesBlock 205 } 206 207 """ 208 .trimIndent() 209 ) 210 211 // Copies test project data 212 mapOf( 213 "app-target" to appTargetSetupRule, 214 "consumer" to consumerSetupRule, 215 "producer" to producerSetupRule, 216 "dependency" to dependencySetupRule, 217 ) 218 .forEach { (folder, project) -> 219 File("src/test/test-data", folder) 220 .apply { deleteOnExit() } 221 .copyRecursively(project.rootDir, overwrite = true) 222 } 223 224 base.evaluate() 225 } 226 } 227 228 fun baselineProfileFile(variantName: String): File { 229 // Warning: support for baseline profile source sets in library module was added with 230 // agp 8.3.0 alpha 15 (b/309858620). Therefore, before then, we can only always merge into 231 // main and always output only in src/main/baseline-prof.txt. 232 return if ( 233 consumer.isLibraryModule == false || 234 (consumer.isLibraryModule == true && 235 forcedTestAgpVersion.isAtLeast(TEST_AGP_VERSION_8_3_1)) 236 ) { 237 File(consumer.rootDir, "src/$variantName/$EXPECTED_PROFILE_FOLDER/baseline-prof.txt") 238 } else if (consumer.isLibraryModule == true /* and version is not at least AGP 8.3.0 */) { 239 if (variantName != "main") { 240 throw IllegalArgumentException( 241 """ 242 Invalid variant name `$variantName` for library pre-agp 8.3.0. Only main is supported. 243 """ 244 .trimIndent() 245 ) 246 } 247 File(consumer.rootDir, "src/main/baseline-prof.txt") 248 } else { 249 // This happens only when trying to read the baseline profile file before defining 250 // the consumer type (library or app). 251 throw IllegalStateException("Consumer is nether a library or app.") 252 } 253 } 254 255 fun startupProfileFile(variantName: String) = 256 File(consumer.rootDir, "src/$variantName/$EXPECTED_PROFILE_FOLDER/startup-prof.txt") 257 258 fun mergedArtProfile(variantName: String): File { 259 // Task name folder in path was first observed in the update to AGP 8.3.0-alpha10. 260 // Before that, the folder was omitted in path. 261 val taskNameFolder = 262 if (forcedTestAgpVersion.isAtLeast(TEST_AGP_VERSION_8_3_1)) { 263 camelCase("merge", variantName, "artProfile") 264 } else { 265 "" 266 } 267 return File( 268 consumer.rootDir, 269 "build/intermediates/merged_art_profile/$variantName/$taskNameFolder/baseline-prof.txt" 270 ) 271 } 272 273 fun readBaselineProfileFileContent(variantName: String): List<String> = 274 baselineProfileFile(variantName).readLines() 275 276 fun readStartupProfileFileContent(variantName: String): List<String> = 277 startupProfileFile(variantName).readLines() 278 } 279 280 data class VariantProfile( 281 val flavorDimensions: Map<String, String>, 282 val buildType: String, 283 val profileFileLines: Map<String, List<String>>, 284 val startupFileLines: Map<String, List<String>>, 285 val ftlFileLines: Map<String, List<String>> = mapOf(), 286 val useGsSchema: Boolean = false, 287 ) { 288 289 companion object { 290 291 fun release( 292 baselineProfileLines: List<String> = listOf(), 293 startupProfileLines: List<String> = listOf(), 294 ftlFileLines: List<String> = listOf(), 295 useGsSchema: Boolean = false, 296 ) = 297 listOf( 298 VariantProfile( 299 flavorDimensions = mapOf(), 300 buildType = "release", 301 profileFileLines = mapOf("myTest" to baselineProfileLines), 302 startupFileLines = mapOf("myStartupTest" to startupProfileLines), 303 ftlFileLines = mapOf("anotherTest" to ftlFileLines), 304 useGsSchema = useGsSchema, 305 ) 306 ) 307 } 308 309 val nonMinifiedVariant = 310 camelCase(*flavorDimensions.map { it.value }.toTypedArray(), "nonMinified", buildType) 311 312 constructor( 313 flavor: String?, 314 buildType: String = "release", 315 profileFileLines: Map<String, List<String>> = mapOf(), 316 startupFileLines: Map<String, List<String>> = mapOf(), 317 ftlFileLines: Map<String, List<String>> = mapOf(), 318 ) : this( 319 flavorDimensions = if (flavor != null) mapOf("version" to flavor) else mapOf(), 320 buildType = buildType, 321 profileFileLines = profileFileLines, 322 startupFileLines = startupFileLines, 323 ftlFileLines = ftlFileLines 324 ) 325 } 326 327 interface Module { 328 329 val name: String 330 val rule: ProjectSetupRule 331 val rootDir: File 332 get() = rule.rootDir 333 334 val gradleRunner: GradleRunner 335 get() = GradleRunner.create().withProjectDir(rule.rootDir) 336 337 fun setBuildGradle(buildGradleContent: String) = 338 rule.writeDefaultBuildGradle( 339 prefix = buildGradleContent, 340 suffix = 341 """ 342 $GRADLE_CODE_PRINT_TASK 343 """ 344 .trimIndent() 345 ) 346 } 347 348 class DependencyModule( 349 val name: String, 350 ) 351 352 class AppTargetModule( 353 override val rule: ProjectSetupRule, 354 override val name: String, 355 ) : Module { 356 357 fun setup( 358 buildGradleContent: String = 359 """ 360 plugins { 361 id("com.android.application") 362 id("androidx.baselineprofile.apptarget") 363 } 364 android { 365 namespace 'com.example.namespace' 366 } 367 """ 368 .trimIndent() 369 ) { 370 setBuildGradle(buildGradleContent) 371 } 372 } 373 374 class ProducerModule( 375 override val rule: ProjectSetupRule, 376 override val name: String, 377 private val tempFolder: File, 378 private val consumer: Module, 379 private val managedDeviceContainerName: String, 380 ) : Module { 381 382 fun setupWithFreeAndPaidFlavors( 383 freeReleaseProfileLines: List<String>? = null, 384 paidReleaseProfileLines: List<String>? = null, 385 freeAnotherReleaseProfileLines: List<String>? = null, 386 paidAnotherReleaseProfileLines: List<String>? = null, 387 freeReleaseStartupProfileLines: List<String> = listOf(), 388 paidReleaseStartupProfileLines: List<String> = listOf(), 389 freeAnotherReleaseStartupProfileLines: List<String> = listOf(), 390 paidAnotherReleaseStartupProfileLines: List<String> = listOf(), 391 otherPluginsBlock: String = "", 392 ) { 393 val variantProfiles = mutableListOf<VariantProfile>() 394 395 fun addProfile( 396 flavor: String, 397 buildType: String, 398 profile: List<String>?, 399 startupProfile: List<String>, 400 ) { 401 if (profile != null) { 402 variantProfiles.add( 403 VariantProfile( 404 flavor = flavor, 405 buildType = buildType, 406 profileFileLines = mapOf("my-$flavor-$buildType-profile" to profile), 407 startupFileLines = 408 mapOf("my-$flavor-$buildType-startup=profile" to startupProfile) 409 ) 410 ) 411 } 412 } 413 414 addProfile( 415 flavor = "free", 416 buildType = "release", 417 profile = freeReleaseProfileLines, 418 startupProfile = freeReleaseStartupProfileLines 419 ) 420 addProfile( 421 flavor = "free", 422 buildType = "anotherRelease", 423 profile = freeAnotherReleaseProfileLines, 424 startupProfile = freeAnotherReleaseStartupProfileLines 425 ) 426 addProfile( 427 flavor = "paid", 428 buildType = "release", 429 profile = paidReleaseProfileLines, 430 startupProfile = paidReleaseStartupProfileLines 431 ) 432 addProfile( 433 flavor = "paid", 434 buildType = "anotherRelease", 435 profile = paidAnotherReleaseProfileLines, 436 startupProfile = paidAnotherReleaseStartupProfileLines 437 ) 438 439 setup( 440 variantProfiles = variantProfiles, 441 otherPluginsBlock = otherPluginsBlock, 442 ) 443 } 444 445 fun setupWithoutFlavors( 446 releaseProfileLines: List<String> = listOf(), 447 releaseStartupProfileLines: List<String> = listOf(), 448 otherPluginsBlock: String = "", 449 ) { 450 setup( 451 variantProfiles = 452 listOf( 453 VariantProfile( 454 flavor = null, 455 buildType = "release", 456 profileFileLines = mapOf("myTest" to releaseProfileLines), 457 startupFileLines = mapOf("myStartupTest" to releaseStartupProfileLines) 458 ) 459 ), 460 otherPluginsBlock = otherPluginsBlock, 461 ) 462 } 463 464 fun setup( 465 variantProfiles: List<VariantProfile> = 466 listOf( 467 VariantProfile( 468 flavor = null, 469 buildType = "release", 470 profileFileLines = 471 mapOf( 472 "myTest" to 473 listOf( 474 Fixtures.CLASS_1_METHOD_1, 475 Fixtures.CLASS_2_METHOD_2, 476 Fixtures.CLASS_2, 477 Fixtures.CLASS_1 478 ) 479 ), 480 startupFileLines = 481 mapOf( 482 "myStartupTest" to 483 listOf( 484 Fixtures.CLASS_3_METHOD_1, 485 Fixtures.CLASS_4_METHOD_1, 486 Fixtures.CLASS_3, 487 Fixtures.CLASS_4 488 ) 489 ), 490 ) 491 ), 492 otherPluginsBlock: String = "", 493 baselineProfileBlock: String = "", 494 additionalGradleCodeBlock: String = "", 495 targetProject: Module = consumer, 496 managedDevices: List<String> = listOf(), 497 namespace: String = "com.example.namespace.test", 498 ) { 499 val managedDevicesBlock = 500 if (managedDevices.isEmpty()) "" 501 else 502 """ 503 testOptions.managedDevices.$managedDeviceContainerName { 504 ${ 505 managedDevices.joinToString("\n") { 506 """ 507 $it(ManagedVirtualDevice) { 508 device = "Pixel 6" 509 apiLevel = 31 510 systemImageSource = "aosp" 511 } 512 513 """.trimIndent() 514 } 515 } 516 } 517 """ 518 .trimIndent() 519 520 val flavors = variantProfiles.flatMap { it.flavorDimensions.toList() } 521 val flavorDimensionNames = flavors.map { it.first }.toSet().joinToString { """ "$it"""" } 522 val flavorBlocks = 523 flavors 524 .groupBy { it.second } 525 .toList() 526 .map { it.second } 527 .flatten() 528 .joinToString("\n") { """ ${it.second} { dimension "${it.first}" } """ } 529 val flavorsBlock = 530 """ 531 productFlavors { 532 flavorDimensions = [$flavorDimensionNames] 533 $flavorBlocks 534 } 535 """ 536 .trimIndent() 537 538 val buildTypesBlock = 539 """ 540 buildTypes { 541 ${ 542 variantProfiles 543 .filter { it.buildType.isNotBlank() && it.buildType != "release" } 544 .joinToString("\n") { " ${it.buildType} { initWith(debug) } " } 545 } 546 } 547 """ 548 .trimIndent() 549 550 val disableConnectedAndroidTestsBlock = 551 variantProfiles.joinToString("\n") { 552 553 // Creates a folder to use as results dir 554 val variantOutputDir = File(tempFolder, it.nonMinifiedVariant) 555 val testResultsOutputDir = 556 File(variantOutputDir, "testResultsOutDir").apply { mkdirs() } 557 val profilesOutputDir = 558 File(variantOutputDir, "profilesOutputDir").apply { mkdirs() } 559 560 // Writes the fake test result proto in it, with the given lines 561 writeFakeTestResultsProto( 562 testResultsOutputDir = testResultsOutputDir, 563 profilesOutputDir = profilesOutputDir, 564 profileFileLines = it.profileFileLines, 565 startupFileLines = it.startupFileLines, 566 ftlProfileLines = it.ftlFileLines, 567 useGsSchema = it.useGsSchema, 568 ) 569 570 // Gradle script to injects a fake and disable the actual task execution for 571 // android test 572 """ 573 afterEvaluate { 574 project.tasks.named("connected${it.nonMinifiedVariant.capitalized()}AndroidTest") { 575 it.resultsDir.set(new File("${testResultsOutputDir.absolutePath}")) 576 onlyIf { false } 577 } 578 } 579 580 """ 581 .trimIndent() 582 } 583 584 setBuildGradle( 585 """ 586 import com.android.build.api.dsl.ManagedVirtualDevice 587 588 plugins { 589 id("com.android.test") 590 id("androidx.baselineprofile.producer") 591 $otherPluginsBlock 592 } 593 594 android { 595 $flavorsBlock 596 597 $buildTypesBlock 598 599 $managedDevicesBlock 600 601 namespace "${namespace.trim()}" 602 targetProjectPath = ":${targetProject.name}" 603 } 604 605 dependencies { 606 } 607 608 baselineProfile { 609 $baselineProfileBlock 610 } 611 612 $disableConnectedAndroidTestsBlock 613 614 $additionalGradleCodeBlock 615 616 """ 617 .trimIndent() 618 ) 619 } 620 621 private fun writeFakeTestResultsProto( 622 testResultsOutputDir: File, 623 profilesOutputDir: File, 624 profileFileLines: Map<String, List<String>>, 625 startupFileLines: Map<String, List<String>>, 626 ftlProfileLines: Map<String, List<String>>, 627 useGsSchema: Boolean, 628 ) { 629 // This function writes a profile file for each key of the map, containing for lines 630 // the strings in the list in the value. 631 fun buildProfileArtifact( 632 testNameToProfileLines: Map<String, List<String>>, 633 fileNamePart: String, 634 label: String, 635 useGsSchema: Boolean, 636 ) = 637 testNameToProfileLines.map { 638 639 // Write the fake profile with the given list of profile rules. 640 val profileFileName = "fake-$fileNamePart-${it.key}.txt" 641 val fakeProfileFile = 642 File(profilesOutputDir, profileFileName).apply { 643 writeText(it.value.joinToString(System.lineSeparator())) 644 } 645 646 // Creates an artifact for the test result proto. Note that this can be used 647 // both as a test result artifact and a global artifact. 648 val path = (if (useGsSchema) "gs://" else "") + fakeProfileFile.absolutePath 649 TestArtifactProto.Artifact.newBuilder() 650 .setLabel(LabelProto.Label.newBuilder().setLabel(label).build()) 651 .setSourcePath(PathProto.Path.newBuilder().setPath(path).build()) 652 .build() 653 } 654 655 // Baseline and startup profiles are added as test results artifacts. 656 // For testing with FTL instead, we add the profile as global artifact. 657 val testSuiteResultProto = 658 TestSuiteResultProto.TestSuiteResult.newBuilder() 659 .setTestStatus(TestStatusProto.TestStatus.PASSED) 660 .addTestResult( 661 TestResultProto.TestResult.newBuilder() 662 .addAllOutputArtifact( 663 buildProfileArtifact( 664 testNameToProfileLines = profileFileLines, 665 fileNamePart = "baseline-prof", 666 label = "additionaltestoutput.benchmark.trace", 667 useGsSchema = useGsSchema, 668 ) 669 ) 670 .addAllOutputArtifact( 671 buildProfileArtifact( 672 testNameToProfileLines = startupFileLines, 673 fileNamePart = "startup-prof", 674 label = "additionaltestoutput.benchmark.trace", 675 useGsSchema = useGsSchema, 676 ) 677 ) 678 .build() 679 ) 680 .addAllOutputArtifact( 681 buildProfileArtifact( 682 testNameToProfileLines = ftlProfileLines, 683 fileNamePart = "baseline-prof", 684 label = "firebase.toolOutput", 685 useGsSchema = useGsSchema, 686 ) 687 ) 688 .build() 689 690 File(testResultsOutputDir, "test-result.pb").apply { 691 outputStream().use { testSuiteResultProto.writeTo(it) } 692 } 693 } 694 } 695 696 class ConsumerModule( 697 override val rule: ProjectSetupRule, 698 override val name: String, 699 private val producerName: String, 700 private val dependencyName: String, 701 ) : Module { 702 703 var isLibraryModule: Boolean? = null 704 705 fun setup( 706 androidPlugin: String, 707 flavors: Boolean = false, 708 dependenciesBlock: String = 709 """ 710 implementation(project(":$dependencyName")) 711 """ 712 .trimIndent(), 713 dependencyOnProducerProject: Boolean = true, 714 buildTypeAnotherRelease: Boolean = false, 715 addAppTargetPlugin: Boolean = androidPlugin == ANDROID_APPLICATION_PLUGIN, 716 baselineProfileBlock: String = "", 717 additionalGradleCodeBlock: String = "", 718 ) = 719 setupWithBlocks( 720 androidPlugin = androidPlugin, 721 otherPluginsBlock = "", 722 flavorsBlock = 723 if (flavors) 724 """ 725 flavorDimensions = ["version"] 726 free { dimension "version" } 727 paid { dimension "version" } 728 """ 729 .trimIndent() 730 else "", 731 dependencyOnProducerProject = dependencyOnProducerProject, 732 dependenciesBlock = dependenciesBlock, 733 buildTypesBlock = 734 if (buildTypeAnotherRelease) 735 """ 736 anotherRelease { initWith(release) } 737 """ 738 .trimIndent() 739 else "", 740 addAppTargetPlugin = addAppTargetPlugin, 741 baselineProfileBlock = baselineProfileBlock, 742 additionalGradleCodeBlock = additionalGradleCodeBlock 743 ) 744 745 fun setupWithBlocks( 746 androidPlugin: String, 747 otherPluginsBlock: String = "", 748 flavorsBlock: String = "", 749 buildTypesBlock: String = "", 750 dependenciesBlock: String = "", 751 dependencyOnProducerProject: Boolean = true, 752 addAppTargetPlugin: Boolean = androidPlugin == ANDROID_APPLICATION_PLUGIN, 753 baselineProfileBlock: String = "", 754 additionalGradleCodeBlock: String = "", 755 ) { 756 isLibraryModule = androidPlugin == ANDROID_LIBRARY_PLUGIN 757 setBuildGradle( 758 """ 759 plugins { 760 id("$androidPlugin") 761 ${if (addAppTargetPlugin) "id(\"androidx.baselineprofile.apptarget\")" else ""} 762 id("androidx.baselineprofile.consumer") 763 $otherPluginsBlock 764 } 765 android { 766 namespace 'com.example.namespace' 767 ${ 768 """ 769 productFlavors { 770 $flavorsBlock 771 } 772 """.trimIndent() 773 } 774 ${ 775 """ 776 buildTypes { 777 $buildTypesBlock 778 } 779 """.trimIndent() 780 } 781 } 782 783 dependencies { 784 ${if (dependencyOnProducerProject) """baselineProfile(project(":$producerName"))""" else ""} 785 $dependenciesBlock 786 787 } 788 789 baselineProfile { 790 $baselineProfileBlock 791 } 792 793 $additionalGradleCodeBlock 794 795 """ 796 .trimIndent() 797 ) 798 } 799 } 800