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