├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts └── src │ └── main │ └── kotlin │ ├── Constants.kt │ └── declarative-plugin-module-conventions.gradle.kts ├── common ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── com │ └── android │ └── declarative │ └── common │ ├── AbstractDeclarativePlugin.kt │ ├── DeclarativeFileParser.kt │ ├── DeclarativeFileValueSource.kt │ ├── DslTypeResult.kt │ ├── DslTypesCache.kt │ ├── GradleIssuesWorkarounds.kt │ ├── JdkLoggerWrapper.kt │ ├── LoggerWrapper.kt │ └── cache │ ├── IncludedBuildPluginCache.kt │ └── VersionCatalogs.kt ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── project-api ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── com │ └── android │ └── declarative │ └── project │ └── api │ └── DeclarativePlugin.kt ├── project-plugin ├── build.gradle.kts └── src │ ├── main │ └── kotlin │ │ └── com │ │ └── android │ │ └── declarative │ │ └── project │ │ ├── DeclarativePlugin.kt │ │ ├── DependencyProcessor.kt │ │ ├── GenericDeclarativeParser.kt │ │ ├── tasks │ │ └── DeclarativeBuildSerializerTask.kt │ │ └── variantApi │ │ └── AndroidComponentsParser.kt │ └── test │ └── kotlin │ └── com │ └── android │ └── declarative │ └── project │ └── agp │ ├── DependencyProcessorTest.kt │ ├── ProductFlavorTest.kt │ ├── agp │ ├── AgpDslTest.kt │ ├── BuildTypeTest.kt │ ├── BundleTest.kt │ └── variant │ │ ├── ApplicationVariantApiTest.kt │ │ └── LibraryVariantApiTest.kt │ ├── fixtures │ ├── FakeAndroidComponentsExtension.kt │ └── FakeSelector.kt │ └── java │ └── JavaPluginExtensionTest.kt ├── settings-api ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── com │ └── android │ └── declarative │ └── settings │ └── api │ └── SettingsDeclarativePlugin.kt ├── settings-plugin ├── build.gradle.kts └── src │ ├── main │ └── kotlin │ │ └── com │ │ └── android │ │ └── declarative │ │ └── internal │ │ ├── SettingsDeclarativePlugin.kt │ │ └── configurators │ │ └── RepositoriesConfigurator.kt │ └── test │ └── kotlin │ └── com │ └── android │ └── declarative │ └── internal │ └── configurators │ └── RepositoriesConfiguratorTest.kt ├── settings.gradle.kts └── tests ├── build.gradle.kts └── src ├── test-projects ├── nestedBuildScriptSingleLibApplication │ ├── app │ │ ├── build.gradle.toml │ │ └── src │ │ │ ├── androidTest │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── app │ │ │ │ └── HelloWorldTest.java │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── app │ │ │ │ └── HelloWorld.java │ │ │ └── res │ │ │ ├── layout │ │ │ └── main.xml │ │ │ └── values │ │ │ └── strings.xml │ ├── lib │ │ ├── build.gradle.toml │ │ └── src │ │ │ ├── androidTest │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── helloworld │ │ │ │ └── HelloWorldTest.java │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── helloworld │ │ │ │ ├── HelloWorld.java │ │ │ │ └── Lib1Utils.java │ │ │ └── res │ │ │ ├── layout │ │ │ └── main.xml │ │ │ └── values │ │ │ └── strings.xml │ ├── settings.gradle.toml │ └── toml │ │ ├── common.toml │ │ ├── demo.toml │ │ └── paid.toml ├── simpleApplication │ ├── app │ │ ├── build.gradle.toml │ │ └── src │ │ │ ├── androidTest │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── app │ │ │ │ └── HelloWorldTest.java │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── app │ │ │ │ └── HelloWorld.java │ │ │ └── res │ │ │ ├── layout │ │ │ └── main.xml │ │ │ └── values │ │ │ └── strings.xml │ └── settings.gradle.toml └── singleLibApplication │ ├── app │ ├── build.gradle.toml │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ └── app │ │ │ └── HelloWorldTest.java │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── app │ │ │ └── HelloWorld.java │ │ └── res │ │ ├── layout │ │ └── main.xml │ │ └── values │ │ └── strings.xml │ ├── lib │ ├── build.gradle.toml │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ └── helloworld │ │ │ └── HelloWorldTest.java │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── helloworld │ │ │ ├── HelloWorld.java │ │ │ └── Lib1Utils.java │ │ └── res │ │ ├── layout │ │ └── main.xml │ │ └── values │ │ └── strings.xml │ └── settings.gradle.toml └── test └── kotlin └── com └── android └── declarative ├── infra ├── DeclarativeTest.kt ├── DeclarativeTestProject.kt └── DeclarativeTestProjectBuilder.kt └── tests ├── AllDslMethodsTest.kt ├── DeclarativeTest.kt ├── JavaLibraryDeclarativeTest.kt ├── LargeDeclarativeTest.kt ├── SettingsDeclaredPluginTest.kt ├── SingleLibraryDeclarativeTest.kt └── samples ├── NestedBuildScriptSingleLibraryApplication.kt ├── SimpleApplication.kt └── SingleLibraryApplication.kt /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | build 4 | out 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | We'd love to accept your patches and contributions to this project. 4 | 5 | ## Before you begin 6 | 7 | ### Sign our Contributor License Agreement 8 | 9 | Contributions to this project must be accompanied by a 10 | [Contributor License Agreement](https://cla.developers.google.com/about) (CLA). 11 | You (or your employer) retain the copyright to your contribution; this simply 12 | gives us permission to use and redistribute your contributions as part of the 13 | project. 14 | 15 | If you or your current employer have already signed the Google CLA (even if it 16 | was for a different project), you probably don't need to do it again. 17 | 18 | Visit to see your current agreements or to 19 | sign a new one. 20 | 21 | ### Review our community guidelines 22 | 23 | This project follows 24 | [Google's Open Source Community Guidelines](https://opensource.google/conduct/). 25 | 26 | ## Contribution process 27 | 28 | ### Code reviews 29 | 30 | All submissions, including submissions by project members, require review. We 31 | use GitHub pull requests for this purpose. Consult 32 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 33 | information on using pull requests. 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Declarative Plugin 2 | 3 | ## Building 4 | 5 | To build the agp-experimental/declarative, you need to also have Android Studio codebase checked out 6 | and pointed to by the AGP_WORKSPACE_LOCATION environment variable. It is needed to access some of the testing 7 | infrastructure like accessing SDK, NDK and services like exploring APKs, AARs, etc... 8 | You also need to have the CUSTOM_REPO environment variable set. 9 | 10 | So for example this is what I use on my mac 11 | ``` 12 | AGP_WORKSPACE_LOCATION=/Users/jedo/src/studio-main 13 | CUSTOM_REPO=$AGP_WORKSPACE_LOCATION/out/repo:$AGP_WORKSPACE_LOCATION/prebuilts/tools/common/m2/repository 14 | ``` 15 | 16 | To build : `gw publish` 17 | 18 | ### Running tests 19 | 20 | To run the test in Gradle : ```gw tests:test``` or click on the Play in the gutter. 21 | 22 | ### Design docs 23 | 24 | There is a high level design doc available [here](go/gradle-declarative), a deeper design doc 25 | will be written once the project is founded. 26 | 27 | ### Code organization 28 | 29 | There are 4 modules in the current workspace 30 | 31 | | module | content | 32 | |--------|---------| 33 | | buildSrc | convention plugins and Constants used to build the project | 34 | | api | public APIs of the plugin | 35 | | impl | implementation of the plugins | 36 | | tests | integration tests | 37 | 38 | There are 2 plugins, a `Project` one and a `Settings` one. The `Project` plugin has the core of 39 | the declarative functionality : 40 | 1. Reads the build.gradle.toml 41 | 2. Applies the configured plugins 42 | 3. Reflectively populate the extension objects 43 | 4. Configure the dependencies/ 44 | 45 | The `Settings` plugin will do a similar job on the `Settings` object : 46 | 1. Reads the settings.gradle.toml 47 | 2. Configure the declarative plugins on each project 48 | 3. Provide basic plugin management capability. 49 | 50 | contact: `jedo@google.com` for questions. -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.kotlin.jvm) 3 | } 4 | 5 | tasks.test { 6 | this.environment("CUSTOM_REPO", System.getenv("CUSTOM_REPO") + File.pathSeparatorChar + "${project.rootDir}/out/repo") 7 | this.environment("TEST_TMPDIR", project.buildDir) 8 | this.environment("AGP_WORKSPACE_LOCATION", providers.gradleProperty("com.android.workspace.location").get()) 9 | this.environment("PLUGIN_VERSION", Constants.PLUGIN_VERSION) 10 | } 11 | 12 | dependencies { 13 | testImplementation(gradleApi()) 14 | testImplementation(libs.tomlj) 15 | testImplementation(libs.junit) 16 | testImplementation("com.android.build.integration-test:framework") 17 | } 18 | -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | repositories { 6 | gradlePluginPortal() // so that external plugins can be resolved in dependencies section 7 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/Constants.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 | object Constants { 17 | const val PLUGIN_VERSION = "0.0.1" 18 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/declarative-plugin-module-conventions.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | // due to https://github.com/gradle/gradle/issues/15383, I cannot put version catalog referenced plugins here 3 | // therefore I only declare plugins that are shipping with gradle. 4 | id("java-gradle-plugin") 5 | id("maven-publish") 6 | } 7 | 8 | publishing { 9 | repositories { 10 | maven { 11 | name = "localPluginRepository" 12 | url = uri("../out/repo") 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /common/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("declarative-plugin-module-conventions") 3 | alias(libs.plugins.kotlin.jvm) 4 | //alias(libs.plugins.plugin.publish) 5 | } 6 | 7 | dependencies { 8 | implementation(gradleApi()) 9 | implementation(libs.tomlj) 10 | implementation(libs.coroutines) 11 | implementation(libs.toolsCommon) 12 | implementation(libs.declarativeModel) 13 | implementation(libs.agpApi) 14 | 15 | testImplementation(libs.junit) 16 | testImplementation(libs.mockito) 17 | testImplementation(libs.truth) 18 | } 19 | -------------------------------------------------------------------------------- /common/src/main/kotlin/com/android/declarative/common/AbstractDeclarativePlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 com.android.declarative.common 18 | 19 | import com.android.declarative.internal.IssueLogger 20 | import com.android.declarative.internal.parsers.DeclarativeFileParser 21 | import org.tomlj.TomlParseResult 22 | import java.io.File 23 | import java.nio.file.Path 24 | import java.nio.file.Paths 25 | 26 | /** 27 | * Common behaviors for the declarative plugins, the [Settings] plugin and the [Project] plugin. 28 | */ 29 | abstract class AbstractDeclarativePlugin { 30 | 31 | abstract val buildFileName: String 32 | 33 | fun parseDeclarativeFile( 34 | location: String, 35 | declarativeFileContent: String, 36 | issueLogger: IssueLogger, 37 | ): TomlParseResult = 38 | DeclarativeFileParser(issueLogger).parseDeclarativeFile( 39 | location, 40 | declarativeFileContent, 41 | ) 42 | 43 | open fun parseDeclarativeInFolder(folder: File, issueLogger: IssueLogger): TomlParseResult = 44 | parseDeclarativeFile(Paths.get(folder.absolutePath, buildFileName), issueLogger) 45 | 46 | protected fun parseDeclarativeFile(buildFile: Path, issueLogger: IssueLogger): TomlParseResult = 47 | DeclarativeFileParser(issueLogger).parseDeclarativeFile(buildFile) 48 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/com/android/declarative/common/DeclarativeFileParser.kt: -------------------------------------------------------------------------------- 1 | package com.android.declarative.common 2 | 3 | import org.tomlj.TomlTable 4 | import kotlin.reflect.KClass 5 | 6 | interface DeclarativeFileParser { 7 | 8 | /** 9 | * parse a [TomlTable] into an extension object of type [T] 10 | */ 11 | fun parse(table: TomlTable, type: KClass, extension: T) 12 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/com/android/declarative/common/DeclarativeFileValueSource.kt: -------------------------------------------------------------------------------- 1 | package com.android.declarative.common 2 | 3 | import org.gradle.api.file.RegularFile 4 | import org.gradle.api.file.RegularFileProperty 5 | import org.gradle.api.initialization.Settings 6 | import org.gradle.api.model.ObjectFactory 7 | import org.gradle.api.provider.Provider 8 | import org.gradle.api.provider.ProviderFactory 9 | import org.gradle.api.provider.ValueSource 10 | import org.gradle.api.provider.ValueSourceParameters 11 | 12 | /** 13 | * Implementation of [ValueSource] for declarative files that are accessed/read during configuration 14 | * time. 15 | */ 16 | abstract class DeclarativeFileValueSource : ValueSource { 17 | 18 | companion object { 19 | 20 | /** 21 | * Enlists a new file to be monitored for configuration cache invalidation. 22 | * @param providers [ProviderFactory] to create instances of [DeclarativeFileValueSource] 23 | * @param file the [File] to monitor. 24 | */ 25 | fun enlist( 26 | providers: ProviderFactory, 27 | file: RegularFile, 28 | ): Provider = 29 | providers.of(DeclarativeFileValueSource::class.java) { 30 | it.parameters.configFile.set(file) 31 | } 32 | 33 | /** 34 | * Enlists a new file to be monitored for configuration cache invalidation. 35 | * @param providers [ProviderFactory] to create instances of [DeclarativeFileValueSource] 36 | * @param file the [Provider] of [RegularFile] to monitor. 37 | */ 38 | fun enlist( 39 | providers: ProviderFactory, 40 | file: Provider, 41 | ): Provider = 42 | providers.of(DeclarativeFileValueSource::class.java) { 43 | it.parameters.configFile.set(file) 44 | } 45 | 46 | /** 47 | * Enlists a new file to be monitored for configuration cache invalidation. 48 | * @param objects [ObjectFactory] to create instances of [DirectoryProprty] 49 | * @param settings [Settings] instance 50 | * @param fileName file name for a file present or not in [Settings.getSettingsDir] 51 | */ 52 | fun enlist( 53 | objects: ObjectFactory, 54 | settings: Settings, 55 | fileName: String, 56 | ): Provider = 57 | enlist(settings.providers, 58 | objects.directoryProperty().also { 59 | it.set(settings.settingsDir) 60 | }.file(fileName) 61 | ) 62 | } 63 | 64 | interface Params : ValueSourceParameters { 65 | val configFile: RegularFileProperty 66 | } 67 | 68 | /** 69 | * Return the file content or null if the file does not exists. 70 | */ 71 | override fun obtain(): String? { 72 | return parameters.configFile.asFile.get().takeIf { it.exists() }?.bufferedReader()?.readText() 73 | } 74 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/com/android/declarative/common/DslTypeResult.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 | * Copyright (C) 2019 The Android Open Source Project 18 | * 19 | * Licensed under the Apache License, Version 2.0 (the "License"); 20 | * you may not use this file except in compliance with the License. 21 | * You may obtain a copy of the License at 22 | * 23 | * http://www.apache.org/licenses/LICENSE-2.0 24 | * 25 | * Unless required by applicable law or agreed to in writing, software 26 | * distributed under the License is distributed on an "AS IS" BASIS, 27 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 28 | * See the License for the specific language governing permissions and 29 | * limitations under the License. 30 | */ 31 | package com.android.declarative.common 32 | 33 | import kotlin.reflect.KCallable 34 | import kotlin.reflect.KClass 35 | import kotlin.reflect.KMutableProperty1 36 | import kotlin.reflect.KProperty1 37 | import kotlin.reflect.full.memberProperties 38 | 39 | /** 40 | * Cache object for a dsl type. This will cache all properties, mutable properties 41 | * and methods that can b e called on this type. 42 | */ 43 | class DslTypeResult( 44 | val dslType: KClass, 45 | val properties: Map>, 46 | val mutableProperties: Map>, 47 | val members: Map>>, 48 | ) { 49 | 50 | companion object { 51 | 52 | fun parseType(type: KClass): DslTypeResult { 53 | val properties = mutableMapOf>() 54 | val mutableProperties = mutableMapOf>() 55 | parseMembers(type, properties, mutableProperties) 56 | val members = mutableMapOf>>() 57 | type.members.forEach { callable -> 58 | val values = members.getOrPut(callable.name) { mutableListOf() } 59 | values.add(callable) 60 | } 61 | return DslTypeResult( 62 | type, 63 | properties.toMap(), 64 | mutableProperties.toMap(), 65 | members.toMap(), 66 | ) 67 | 68 | } 69 | 70 | private fun parseMembers( 71 | type: KClass, 72 | properties: MutableMap>, 73 | mutableProperties: MutableMap> 74 | ) { 75 | type.memberProperties.forEach { property -> 76 | if (property is KMutableProperty1) { 77 | mutableProperties[property.name] = property as KMutableProperty1 78 | } else { 79 | properties[property.name] = property as KProperty1 80 | } 81 | } 82 | } 83 | } 84 | 85 | override fun toString(): String { 86 | val stringBuilder = StringBuilder(super.toString()).append("\n") 87 | stringBuilder.append("type = $dslType\n") 88 | properties.keys.forEach { key -> stringBuilder.append("P : ").append(key).append("\n") } 89 | mutableProperties.forEach { key -> stringBuilder.append("M : ").append(key).append("\n")} 90 | return stringBuilder.toString() 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /common/src/main/kotlin/com/android/declarative/common/DslTypesCache.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 | 18 | package com.android.declarative.common 19 | 20 | import kotlin.reflect.KClass 21 | 22 | /** 23 | * Maintains all the [DslTypeResult] for already visited DSL types. 24 | */ 25 | class DslTypesCache() { 26 | 27 | val cache = mutableListOf>() 28 | 29 | fun getCache( 30 | dslType: KClass 31 | ): DslTypeResult { 32 | val cached: DslTypeResult? = cache.firstOrNull { it.dslType == dslType } as DslTypeResult? 33 | return cached ?: DslTypeResult.parseType(dslType).also { 34 | cache.add(it) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /common/src/main/kotlin/com/android/declarative/common/GradleIssuesWorkarounds.kt: -------------------------------------------------------------------------------- 1 | package com.android.declarative.common 2 | 3 | import org.gradle.api.Project 4 | import org.gradle.api.artifacts.VersionCatalogsExtension 5 | 6 | class GradleIssuesWorkarounds { 7 | companion object { 8 | 9 | /** 10 | * This is a hack until https://github.com/gradle/gradle/issues/22468 is fixed. 11 | * we basically steal the versionCatalogs extension from the root project and store them 12 | * in the sub project's extensions. In turn the Project's DeclarativePlugin will remove those 13 | * so it can be reinserted by Gradle when it is ready to do so. 14 | */ 15 | fun installVersionCatalogSupport(project: Project) { 16 | 17 | project.rootProject.extensions.findByName("libs")?.let { 18 | project.extensions.add("libs", it) 19 | } 20 | project.rootProject.extensions.findByType(VersionCatalogsExtension::class.java)?.let { 21 | project.extensions.add("versionCatalogs", it) 22 | } 23 | } 24 | 25 | /** 26 | * this is a hack until https://github.com/gradle/gradle/issues/22468 is fixed. 27 | * 28 | * Counterpart to the [installVersionCatalogSupport] method, where all extensions added are removed. This is 29 | * necessary as Gradle will eventually try to register those extensions and would fail if they are already 30 | * present in the extensions contains. 31 | */ 32 | fun removeVersionCatalogSupport(project: Project) { 33 | 34 | val mapOfExtensions = project.extensions.javaClass.getDeclaredField("extensionsStorage").let { 35 | it.isAccessible = true 36 | it.get(project.extensions) 37 | }.let { extensions -> 38 | extensions.javaClass.getDeclaredField("extensions").let { 39 | it.isAccessible = true 40 | it.get(extensions) 41 | } 42 | } as MutableMap<*, *> 43 | mapOfExtensions.remove("libs") 44 | mapOfExtensions.remove("versionCatalogs") 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/com/android/declarative/common/JdkLoggerWrapper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 | package com.android.declarative.common 17 | 18 | import com.android.utils.ILogger 19 | import java.util.logging.Level 20 | import java.util.logging.Logger 21 | 22 | class JdkLoggerWrapper( 23 | val logger: Logger 24 | ): ILogger { 25 | override fun error(t: Throwable?, msgFormat: String?, vararg args: Any?) = 26 | logger.log(Level.SEVERE, t) { 27 | if (msgFormat == null) { 28 | "[no message defined]" 29 | } else 30 | String.format(msgFormat, *args) 31 | } 32 | 33 | 34 | override fun warning(msgFormat: String, vararg args: Any?) = 35 | logger.warning { 36 | String.format(msgFormat, args) 37 | } 38 | 39 | override fun info(msgFormat: String, vararg args: Any?) = 40 | logger.info { 41 | String.format(msgFormat, args) 42 | } 43 | 44 | 45 | override fun verbose(msgFormat: String, vararg args: Any?) = 46 | logger.fine { 47 | String.format(msgFormat, args) 48 | } 49 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/com/android/declarative/common/LoggerWrapper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 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 | package com.android.declarative.common 17 | 18 | import com.android.utils.ILogger 19 | import java.io.Serializable 20 | import java.util.function.Supplier 21 | import org.gradle.api.logging.LogLevel 22 | import org.gradle.api.logging.Logger 23 | import org.gradle.api.logging.Logging 24 | 25 | 26 | /** 27 | * Implementation of Android's [ILogger] over Gradle's [Logger]. 28 | * 29 | * Note that this maps info to the default user-visible lifecycle. 30 | */ 31 | class LoggerWrapper(private val logger: Logger) : ILogger { 32 | override fun error(throwable: Throwable?, s: String?, vararg objects: Any) { 33 | var s = s 34 | if (!logger.isEnabled(ILOGGER_ERROR)) { 35 | return 36 | } 37 | if (s == null) { 38 | s = "[no message defined]" 39 | } else if (objects != null && objects.size > 0) { 40 | s = String.format(s, *objects) 41 | } 42 | if (throwable == null) { 43 | logger.log(ILOGGER_ERROR, s) 44 | } else { 45 | logger.log( 46 | ILOGGER_ERROR, 47 | s, 48 | throwable 49 | ) 50 | } 51 | } 52 | 53 | override fun warning(s: String, vararg objects: Any) { 54 | log(ILOGGER_WARNING, s, objects) 55 | } 56 | 57 | override fun quiet(s: String, vararg objects: Any) { 58 | log(ILOGGER_QUIET, s, objects) 59 | } 60 | 61 | override fun lifecycle(s: String, vararg objects: Any) { 62 | log(ILOGGER_LIFECYCLE, s, objects) 63 | } 64 | 65 | override fun info(s: String, vararg objects: Any) { 66 | log(ILOGGER_INFO, s, objects) 67 | } 68 | 69 | override fun verbose(s: String, vararg objects: Any) { 70 | log(ILOGGER_VERBOSE, s, objects) 71 | } 72 | 73 | private fun log(logLevel: LogLevel, s: String, vararg objects: Any) { 74 | if (!logger.isEnabled(logLevel)) { 75 | return 76 | } 77 | if (objects == null || objects.size == 0) { 78 | logger.log(logLevel, s) 79 | } else { 80 | logger.log(logLevel, String.format(s, *objects)) 81 | } 82 | } 83 | 84 | private class LoggerSupplier private constructor(private val clazz: Class<*>) : 85 | Supplier, Serializable { 86 | private var logger: com.android.utils.ILogger? = null 87 | 88 | @Synchronized 89 | override fun get(): com.android.utils.ILogger? { 90 | if (logger == null) { 91 | logger = LoggerWrapper( 92 | Logging.getLogger( 93 | clazz 94 | ) 95 | ) 96 | } 97 | return logger 98 | } 99 | } 100 | 101 | companion object { 102 | // Mapping from ILogger method call to gradle log level. 103 | private val ILOGGER_ERROR = LogLevel.ERROR 104 | private val ILOGGER_WARNING = LogLevel.WARN 105 | private val ILOGGER_QUIET = LogLevel.QUIET 106 | private val ILOGGER_LIFECYCLE = LogLevel.LIFECYCLE 107 | private val ILOGGER_INFO = LogLevel.INFO 108 | private val ILOGGER_VERBOSE = LogLevel.INFO 109 | fun getLogger(klass: Class<*>): LoggerWrapper { 110 | return LoggerWrapper(Logging.getLogger(klass)) 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/com/android/declarative/common/cache/IncludedBuildPluginCache.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 com.android.declarative.common.cache 18 | 19 | import org.gradle.api.Project 20 | import java.io.File 21 | import java.util.Properties 22 | 23 | class IncludedBuildPluginInfo( 24 | val classesDir: File, 25 | val resourcesDir: File, 26 | ) 27 | 28 | class IncludedBuildPluginCache( 29 | private val project: Project, 30 | private val includedBuildDir: File, 31 | ) { 32 | private val markerFileLookup = "**/META-INF/gradle-plugins/*.properties" 33 | 34 | private val markerFiles: Set = 35 | project.fileTree(includedBuildDir) { 36 | it.include(markerFileLookup) 37 | }.files 38 | 39 | 40 | fun getPluginClassPath(): Set = 41 | mutableSetOf().also { allFolders -> 42 | markerFiles.forEach { markerFile -> 43 | getBinaryFolders(markerFile).let { 44 | allFolders.add(it.first) 45 | allFolders.add(it.second) 46 | } 47 | } 48 | } 49 | 50 | 51 | fun resolvePluginById(id:String): IncludedBuildPluginInfo? { 52 | markerFiles.firstOrNull { 53 | it.name == "$id.properties" 54 | }?.let { markerFile -> 55 | loadPluginMarkerFile(markerFile).let { implementationClassName -> 56 | 57 | val classFile = project.fileTree(includedBuildDir) { 58 | it.include("**/$implementationClassName.class") 59 | } 60 | 61 | val resourcesDir = markerFile.parentFile.parentFile.parentFile 62 | 63 | val classesDir = classFile.singleFile.absolutePath.substring(0, 64 | classFile.singleFile.absolutePath.length - 65 | (implementationClassName.length + ".class".length + File.separator.length) 66 | ) 67 | 68 | return IncludedBuildPluginInfo(File(classesDir), resourcesDir) 69 | } 70 | } 71 | return null 72 | } 73 | 74 | private fun getBinaryFolders(markerFile: File): Pair { 75 | loadPluginMarkerFile(markerFile).let { implementationClassName -> 76 | 77 | val classFile = project.fileTree(includedBuildDir) { 78 | it.include("**/$implementationClassName.class") 79 | } 80 | 81 | val resourcesDir = markerFile.parentFile.parentFile.parentFile 82 | 83 | val classesDir = classFile.singleFile.absolutePath.substring( 84 | 0, 85 | classFile.singleFile.absolutePath.length - 86 | (implementationClassName.length + ".class".length + File.separator.length) 87 | ) 88 | return File(classesDir) to resourcesDir 89 | } 90 | 91 | } 92 | private fun loadPluginMarkerFile(markerFile: File): String = 93 | Properties().also { 94 | it.load(markerFile.reader()) 95 | }.getProperty("implementation-class") 96 | } 97 | -------------------------------------------------------------------------------- /common/src/main/kotlin/com/android/declarative/common/cache/VersionCatalogs.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 com.android.declarative.common.cache 18 | 19 | import com.android.declarative.internal.IssueLogger 20 | import org.gradle.api.Project 21 | import org.gradle.api.artifacts.VersionCatalog 22 | import org.gradle.api.artifacts.VersionCatalogsExtension 23 | 24 | /** 25 | * Cache for version catalogs instances. 26 | */ 27 | class VersionCatalogs( 28 | val issueLogger: IssueLogger, 29 | ) { 30 | val cache = mutableMapOf() 31 | 32 | fun getVersionCatalog(project: Project, versionCatalogName: String) = 33 | synchronized(cache) { 34 | cache.getOrPut(versionCatalogName) { 35 | val versionCatalogs = project.extensions.findByType(VersionCatalogsExtension::class.java) 36 | if (versionCatalogs == null) { 37 | throw RuntimeException("Version catalog support not enabled") 38 | } 39 | val versionCatalog = versionCatalogs.find(versionCatalogName) 40 | if (!versionCatalog.isPresent) { 41 | throw RuntimeException("Version catalog $versionCatalogName not found !") 42 | } 43 | versionCatalog.get() 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.parallel=true 2 | org.gradle.jvmargs=-Xmx4096m 3 | org.gradle.daemon=true 4 | 5 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | kotlin = "1.9.20-Beta" 3 | tomlj = "1.1.0" 4 | junit = "4.13.2" 5 | plugin-publish = "1.0.0" 6 | mockito = "4.8.0" 7 | truth = "0.44" 8 | agp-version = "8.3.0-dev" 9 | sdk-version = "31.3.0-dev" 10 | coroutines = "1.6.4" 11 | intellij = "1.13.2" 12 | changelog = "1.3.1" 13 | 14 | [libraries] 15 | tomlj = { module = "org.tomlj:tomlj", version.ref = "tomlj" } 16 | junit = { module = "junit:junit", version.ref="junit" } 17 | mockito = { module = "org.mockito:mockito-core", version.ref = "mockito" } 18 | truth = { module = "com.google.truth:truth", version.ref = "truth" } 19 | testutils = { module = "com.android.tools:testutils", version.ref = "sdk-version" } 20 | coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm", version.ref = "coroutines" } 21 | toolsCommon = { module = "com.android.tools:common", version.ref = "sdk-version"} 22 | declarativeModel = { module = "com.android.tools.build.declarative:model", version.ref = "agp-version" } 23 | agpApi = { module = "com.android.tools.build:gradle-api", version.ref = "agp-version" } 24 | testFramework = { module = "com.android.build.integration-test:framework", version.ref = "agp-version" } 25 | 26 | [plugins] 27 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } 28 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } 29 | plugin-publish = { id = "com.gradle.plugin-publish", version.ref = "plugin-publish" } 30 | gradle-intellij = { id = "org.jetbrains.intellij", version.ref = "intellij"} 31 | gradle-intellij-changelog = { id = "org.jetbrains.changelog", version.ref = "changelog"} 32 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/android/gradle-declarative/df3822d41252658aa4427cfba8a0295eeb40da23/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Feb 27 16:35:22 PST 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-rc-2-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /project-api/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 | 18 | plugins { 19 | id("declarative-plugin-module-conventions") 20 | alias(libs.plugins.kotlin.jvm) 21 | //alias(libs.plugins.plugin.publish) 22 | } 23 | 24 | gradlePlugin { 25 | plugins { 26 | create("comAndroidDeclarative") { 27 | id = "com.android.experiments.declarative.project" 28 | version = Constants.PLUGIN_VERSION 29 | implementationClass = "com.android.declarative.project.api.DeclarativePlugin" 30 | } 31 | } 32 | } 33 | 34 | dependencies { 35 | implementation(project(":project-plugin")) 36 | } 37 | 38 | -------------------------------------------------------------------------------- /project-api/src/main/kotlin/com/android/declarative/project/api/DeclarativePlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 com.android.declarative.project.api 18 | 19 | import org.gradle.api.Plugin 20 | import org.gradle.api.Project 21 | 22 | class DeclarativePlugin: Plugin { 23 | override fun apply(project: Project) { 24 | project.apply(mapOf("plugin" to "com.android.internal.declarative.project")) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /project-plugin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("declarative-plugin-module-conventions") 3 | alias(libs.plugins.kotlin.jvm) 4 | //alias(libs.plugins.plugin.publish) 5 | } 6 | 7 | gradlePlugin { 8 | plugins { 9 | create("comAndroidInternalDeclarative") { 10 | id = "com.android.internal.declarative.project" 11 | version = Constants.PLUGIN_VERSION 12 | implementationClass = "com.android.declarative.project.DeclarativePlugin" 13 | } 14 | } 15 | } 16 | 17 | dependencies { 18 | implementation(project(":common")) 19 | implementation(gradleApi()) 20 | implementation(libs.tomlj) 21 | implementation(libs.coroutines) 22 | implementation(libs.toolsCommon) 23 | implementation(libs.declarativeModel) 24 | implementation(libs.agpApi) 25 | 26 | testImplementation(libs.junit) 27 | testImplementation(libs.mockito) 28 | testImplementation(libs.truth) 29 | } 30 | -------------------------------------------------------------------------------- /project-plugin/src/main/kotlin/com/android/declarative/project/DeclarativePlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 com.android.declarative.project 18 | 19 | import com.android.declarative.common.AbstractDeclarativePlugin 20 | import com.android.declarative.common.DeclarativeFileValueSource 21 | import com.android.declarative.common.DeclarativeFileParser 22 | import com.android.declarative.common.DslTypesCache 23 | import com.android.declarative.common.GradleIssuesWorkarounds 24 | import com.android.declarative.common.LoggerWrapper 25 | import com.android.declarative.internal.IssueLogger 26 | import com.android.declarative.internal.LOG 27 | import com.android.declarative.internal.parsers.DependencyParser 28 | import com.android.declarative.internal.parsers.PluginParser 29 | import com.android.declarative.project.tasks.DeclarativeBuildSerializerTask 30 | import com.android.declarative.project.variantApi.AndroidComponentsParser 31 | import org.gradle.api.Plugin 32 | import org.gradle.api.Project 33 | import org.gradle.api.file.RegularFile 34 | import org.tomlj.TomlParseResult 35 | import org.gradle.internal.time.Time 36 | import org.tomlj.TomlTable 37 | import java.io.File 38 | import javax.inject.Inject 39 | 40 | class DeclarativePlugin @Inject constructor( 41 | 42 | ): AbstractDeclarativePlugin(), Plugin { 43 | 44 | override val buildFileName: String 45 | get() = "build.gradle.toml" 46 | 47 | override fun apply(project: Project) { 48 | 49 | val issueLogger = IssueLogger(lenient = false, logger = LoggerWrapper(project.logger)) 50 | issueLogger.logger.LOG { 51 | "Project declarative plugin applied in ClassLoader : \n${this.javaClass.classLoader.name}" 52 | } 53 | val cache = DslTypesCache() 54 | try { 55 | parseDeclarativeBuildFile(project, issueLogger, project.layout.projectDirectory.file(buildFileName), cache) 56 | } catch (e: Exception) { 57 | issueLogger.logger.error(e, "Error while parsing the ${project.projectDir}${File.separatorChar}build.gradle.toml") 58 | throw e 59 | } finally { 60 | GradleIssuesWorkarounds.removeVersionCatalogSupport(project) 61 | } 62 | 63 | project.afterEvaluate { 64 | createTasks(it) 65 | } 66 | } 67 | 68 | private fun parseDeclarativeBuildFile(project: Project, issueLogger: IssueLogger, buildFile: RegularFile, cache: DslTypesCache) { 69 | val declarativeProvider = DeclarativeFileValueSource.enlist( 70 | project.providers, 71 | buildFile 72 | ) 73 | val declarativeFileContent = if (declarativeProvider.isPresent) { 74 | declarativeProvider.get() 75 | } else null 76 | 77 | if (declarativeFileContent.isNullOrEmpty()) return 78 | 79 | val parsedDecl: TomlParseResult = Time.currentTimeMillis().run { 80 | val parseResult = super.parseDeclarativeFile( 81 | "${project.projectDir}${File.separatorChar}$buildFileName", 82 | declarativeFileContent, 83 | issueLogger) 84 | // println("$buildFileName parsing finished in ${Time.currentTimeMillis() - this} ms") 85 | parseResult 86 | } 87 | 88 | // plugins must be applied first so extensions are correctly registered. 89 | parsedDecl.getArray("plugins")?.also { plugins -> 90 | PluginParser().parse(plugins).forEach { pluginInfo -> 91 | issueLogger.logger.LOG { "In ${project.path} , applying ${pluginInfo.id}" } 92 | project.plugins.apply(pluginInfo.id) 93 | } 94 | } 95 | 96 | parsedDecl.getArray("includeBuildFiles")?.let { 97 | val buildFileCount = it.size() 98 | for (index in 0 until buildFileCount) { 99 | val includedBuildFile = project.layout.projectDirectory.file(it.getString(index)) 100 | parseDeclarativeBuildFile(project, issueLogger, includedBuildFile, cache) 101 | } 102 | } 103 | 104 | parsedDecl.keySet().forEach { topLevelDeclaration -> 105 | when (topLevelDeclaration) { 106 | "includeBuildFiles" -> { 107 | // skip includes processing again. 108 | } 109 | 110 | "plugins" -> { 111 | // already applied above. 112 | } 113 | 114 | "dependencies" -> { 115 | // handled below, so all DSL driven configurations are created before dependencies are added. 116 | } 117 | 118 | "androidComponents" -> { 119 | // androidComponents is handled separately with a dedicated parser since it is very 120 | // heavy on callbacks which cannot be generically parsed. 121 | 122 | parseTomlTable( 123 | AndroidComponentsParser(project, cache, issueLogger), 124 | topLevelDeclaration, 125 | parsedDecl, 126 | project, 127 | issueLogger 128 | ) 129 | } 130 | 131 | else -> { 132 | parseTomlTable( 133 | GenericDeclarativeParser(project, cache, issueLogger), 134 | topLevelDeclaration, 135 | parsedDecl, 136 | project, 137 | issueLogger 138 | ) 139 | } 140 | } 141 | } 142 | 143 | parsedDecl.getTable("dependencies")?.also { 144 | @Suppress("UnstableApiUsage") 145 | DependencyProcessor( 146 | this, 147 | { projectPath: String -> project.rootProject.project(projectPath) }, 148 | project, 149 | issueLogger, 150 | ).process(DependencyParser(issueLogger).parseToml(it)) 151 | } 152 | } 153 | 154 | private fun createTasks(project: Project) { 155 | project.extensions.findByName("android")?.let { android -> 156 | project.tasks.register( 157 | "serializeBuildDeclarations", 158 | DeclarativeBuildSerializerTask::class.java, 159 | ) { 160 | println("Configuring serializeBuildDeclaration") 161 | it.extension.set(android) 162 | } 163 | } 164 | } 165 | 166 | /** 167 | * Parse a [TomlTable] using a dedicated [DeclarativeFileParser]. 168 | * 169 | * @param parser the Toml parser for this Toml declaration 170 | * @param topLevelDeclaration name of the top level extension block, like 'android' or 'androidComponents` 171 | * @param parsedToml the [TomlTable] to parse 172 | * @param project the project being configured 173 | * @param issueLogger to log issues and traces 174 | */ 175 | private fun parseTomlTable( 176 | parser: DeclarativeFileParser, 177 | topLevelDeclaration: String, 178 | parsedToml: TomlTable, 179 | project: Project, 180 | issueLogger: IssueLogger 181 | ) { 182 | if (!parsedToml.isTable(topLevelDeclaration)) { 183 | throw Error("Invalid declaration, $topLevelDeclaration must be a TOML table") 184 | } 185 | // find the extension registered under the name 186 | val publicExtensionType = project.extensions.extensionsSchema.firstOrNull() { 187 | it.name == topLevelDeclaration 188 | }?.publicType 189 | ?: throw Error("Cannot find top level key $topLevelDeclaration in ") 190 | 191 | issueLogger.logger.LOG { "Extension type is $publicExtensionType" } 192 | project.extensions.findByName(topLevelDeclaration)?.also { extension -> 193 | parser.parse( 194 | parsedToml.getTable(topLevelDeclaration) 195 | ?: throw Error("Internal error : please file a bug providing your TOML file"), 196 | publicExtensionType.concreteClass.kotlin, 197 | publicExtensionType.concreteClass.cast(extension) 198 | ) 199 | } ?: throw Error("Cannot find extension $topLevelDeclaration, " + 200 | "has the plugin registering the extension been applied ?") 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /project-plugin/src/main/kotlin/com/android/declarative/project/DependencyProcessor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 | 18 | package com.android.declarative.project 19 | 20 | import com.android.declarative.common.AbstractDeclarativePlugin 21 | import com.android.declarative.common.cache.VersionCatalogs 22 | import com.android.declarative.internal.IssueLogger 23 | import com.android.declarative.internal.LOG 24 | import com.android.declarative.internal.model.DependencyInfo 25 | import com.android.declarative.internal.model.DependencyInfo.Alias 26 | import com.android.declarative.internal.model.DependencyInfo.ExtensionFunction 27 | import com.android.declarative.internal.model.DependencyInfo.Files 28 | import com.android.declarative.internal.model.DependencyInfo.Maven 29 | import com.android.declarative.internal.model.DependencyInfo.Notation 30 | import com.android.declarative.internal.model.DependencyInfo.Platform 31 | import com.android.declarative.internal.model.DependencyType 32 | import org.gradle.api.Project 33 | import org.gradle.api.artifacts.dsl.DependencyHandler 34 | 35 | /** 36 | * A processor class that process [List] of [DependencyInfo] for a module and adds such 37 | * dependency to the provided [dependencyHandler] using Gradle APIs. 38 | */ 39 | @Suppress("UnstableApiUsage") 40 | class DependencyProcessor( 41 | private val plugin: AbstractDeclarativePlugin, 42 | private val projectResolver: (id: String) -> Project, 43 | private val project: Project, 44 | private val issueLogger: IssueLogger, 45 | ) { 46 | 47 | private val dependencyHandler = project.dependencies 48 | private val dependencyFactory = project.dependencyFactory 49 | private val versionCatalogs = VersionCatalogs(issueLogger) 50 | 51 | fun process(dependencies: List) { 52 | dependencies.forEach { dependency -> 53 | when(dependency) { 54 | is Notation -> { 55 | if (dependency.type == DependencyType.PROJECT) { 56 | addProjectDependency( 57 | dependency.configuration, 58 | dependency.notation 59 | ) 60 | } else if (dependency.type == DependencyType.NOTATION) { 61 | addNotationDependency( 62 | dependency 63 | ) 64 | } 65 | } 66 | is Files -> { 67 | addFilesDependency(dependency) 68 | } 69 | is Maven -> { 70 | addLibraryDependency(dependency) 71 | } 72 | is ExtensionFunction -> { 73 | addExtensionDependency( 74 | plugin, 75 | dependency, 76 | ) 77 | } 78 | is Alias -> { 79 | addVersionCatalogDependency(dependency) 80 | } 81 | is Platform -> { 82 | addPlatformDependency(dependency) 83 | } 84 | } 85 | } 86 | } 87 | 88 | private fun addVersionCatalogDependency(dependency: Alias) { 89 | 90 | val versionCatalogIdentifier = dependency.alias.substringBefore('.') 91 | val libraryName = dependency.alias.substringAfter('.') 92 | val versionCatalog = versionCatalogs.getVersionCatalog(project, versionCatalogIdentifier) 93 | val lib = versionCatalog.findLibrary(libraryName) 94 | lib.ifPresentOrElse({ 95 | issueLogger.logger.LOG { 96 | "Adding Version catalog ${dependency.alias} to ${dependency.configuration}" 97 | } 98 | dependencyHandler.add( 99 | dependency.configuration, 100 | it 101 | ) 102 | }) { 103 | issueLogger.logger.warning( 104 | "$libraryName library not found in version catalog $versionCatalogIdentifier" 105 | ) 106 | } 107 | } 108 | 109 | private fun addPlatformDependency(dependency: Platform) { 110 | issueLogger.logger.LOG {"Adding platform ${dependency.name} to ${dependency.configuration}" } 111 | if (dependency.name.contains(".")) { 112 | val versionCatalogIdentifier = dependency.name.substringBefore('.') 113 | val platformName = dependency.name.substringAfter('.') 114 | val versionCatalog = versionCatalogs.getVersionCatalog(project, versionCatalogIdentifier) 115 | val lib = versionCatalog.findLibrary(platformName) 116 | lib.ifPresentOrElse(dependencyHandler::platform) { 117 | issueLogger.raiseError("Cannot find ${dependency.name} in version catalog") 118 | } 119 | } else { 120 | dependencyHandler.platform( 121 | dependency.name 122 | ) 123 | } 124 | } 125 | 126 | private fun addFilesDependency(dependency: Files) { 127 | val fileCollection = project.files() 128 | dependency.files.forEach(fileCollection::from) 129 | println("adding files dependency ${dependency.files} to ${dependency.configuration}") 130 | dependencyHandler.add( 131 | dependency.configuration, 132 | dependencyFactory.create( 133 | fileCollection 134 | ) 135 | ) 136 | } 137 | 138 | private fun addLibraryDependency(dependency: Maven) { 139 | println("Adding maven ${dependency.name} to ${dependency.configuration}") 140 | dependencyHandler.add( 141 | dependency.configuration, 142 | dependencyFactory.create( 143 | dependency.group, 144 | dependency.name, 145 | dependency.version 146 | ) 147 | ) 148 | } 149 | 150 | private fun addProjectDependency(configurationName: String, notation: String) { 151 | val dependencyTarget = projectResolver(notation) 152 | //println("Adding project $dependencyTarget to $configurationName") 153 | dependencyHandler.add(configurationName, dependencyFactory.create(dependencyTarget)) 154 | } 155 | 156 | private fun addNotationDependency(dependencyInfo: Notation) { 157 | val dependency = when(dependencyInfo.notation) { 158 | "localGroovy" -> dependencyFactory.localGroovy() 159 | "gradleApi" -> dependencyFactory.gradleApi() 160 | "gradleTestKit" -> dependencyFactory.gradleTestKit() 161 | else -> dependencyFactory.create(dependencyInfo.notation) 162 | } 163 | issueLogger.logger.LOG {"Adding $dependency to ${dependencyInfo.configuration}" } 164 | dependencyHandler.add(dependencyInfo.configuration, dependency) 165 | } 166 | 167 | 168 | private fun addExtensionDependency( 169 | plugin: AbstractDeclarativePlugin, 170 | dependency: DependencyInfo.ExtensionFunction 171 | ) { 172 | if (dependency.extension == "kotlin") { 173 | if (dependency.parameters.size == 0 || dependency.parameters.size > 2) { 174 | issueLogger.raiseError("Unsupported number of parameters for kotlin() extension, needed one" + 175 | " or two parameters but got ${dependency.parameters.size}") 176 | } 177 | val kotlinDependencyExtensions = plugin.javaClass.classLoader.loadClass( 178 | "org.gradle.kotlin.dsl.KotlinDependencyExtensionsKt" 179 | ) 180 | 181 | val method = kotlinDependencyExtensions.getMethod("kotlin", DependencyHandler::class.java, String::class.java, String::class.java) 182 | val gradleDependency = dependencyFactory.create( 183 | method.invoke( 184 | null, // extension function in kotlin are static methods in Java. 185 | dependencyHandler, 186 | dependency.parameters["module"], 187 | dependency.parameters["version"], 188 | ) as String 189 | ) 190 | issueLogger.logger.LOG { "Adding ${gradleDependency.name} to ${dependency.configuration}" } 191 | dependencyHandler.add(dependency.configuration, gradleDependency) 192 | } 193 | } 194 | } -------------------------------------------------------------------------------- /project-plugin/src/main/kotlin/com/android/declarative/project/tasks/DeclarativeBuildSerializerTask.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 com.android.declarative.project.tasks 18 | 19 | import com.android.declarative.common.DslTypesCache 20 | import org.gradle.api.DefaultTask 21 | import org.gradle.api.provider.Property 22 | import org.gradle.api.tasks.Input 23 | import org.gradle.api.tasks.TaskAction 24 | import kotlin.reflect.full.isSubclassOf 25 | import kotlin.reflect.jvm.javaType 26 | import kotlin.reflect.jvm.jvmErasure 27 | 28 | /** 29 | * Unfinished task to experiment with serializing an extension to a build.gradle.toml file. 30 | */ 31 | abstract class DeclarativeBuildSerializerTask: DefaultTask() { 32 | 33 | @get:Input 34 | abstract val extension: Property 35 | 36 | @TaskAction 37 | fun taskAction() { 38 | val typesCache = DslTypesCache() 39 | println("I am running ${extension.get()}!") 40 | val dslTypeCache = typesCache.getCache(extension.get()::class) 41 | println(dslTypeCache) 42 | 43 | dslTypeCache.properties.forEach { key, property -> 44 | println("K: ${key}") 45 | println("T: -------") 46 | if (property.returnType.jvmErasure.isSubclassOf(Collection::class)) { 47 | println("Collection of ${property.returnType.arguments[0]}") 48 | println("Parsing ${property.returnType.arguments[0].type}") 49 | println("Parsing ${property.returnType.arguments[0].type?.javaType?.typeName}") 50 | val className = property.returnType.arguments[0].type.toString() 51 | if (!className.startsWith("kotlin")) { 52 | val argumentClass = this.javaClass.classLoader.loadClass(className).kotlin 53 | println("${typesCache.getCache(argumentClass)}") 54 | } 55 | } else { 56 | val subTypeCache = DslTypesCache().getCache(property.returnType.jvmErasure) 57 | 58 | println(subTypeCache) 59 | } 60 | } 61 | 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /project-plugin/src/main/kotlin/com/android/declarative/project/variantApi/AndroidComponentsParser.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 | package com.android.declarative.project.variantApi 17 | 18 | import com.android.declarative.common.DeclarativeFileParser 19 | import com.android.build.api.variant.AndroidComponentsExtension 20 | import com.android.build.api.variant.Variant 21 | import com.android.build.api.variant.VariantBuilder 22 | import com.android.build.api.variant.VariantSelector 23 | import com.android.declarative.common.DslTypesCache 24 | import com.android.declarative.internal.IssueLogger 25 | import com.android.declarative.common.LoggerWrapper 26 | import com.android.declarative.project.GenericDeclarativeParser 27 | import org.gradle.api.Project 28 | import org.tomlj.TomlTable 29 | import kotlin.reflect.KClass 30 | import kotlin.reflect.KType 31 | import kotlin.reflect.jvm.jvmErasure 32 | 33 | /** 34 | * Specialized parser responsible for parsing the 'androidComponents' elements 35 | * in declarative file. 36 | */ 37 | class AndroidComponentsParser( 38 | private val project: Project, 39 | private val cache: DslTypesCache = DslTypesCache(), 40 | private val issueLogger: IssueLogger = IssueLogger(false, LoggerWrapper(project.logger)), 41 | ): DeclarativeFileParser { 42 | 43 | override fun parse(table: TomlTable, type: KClass, extension: T) { 44 | // the extension is a subclass of AndroidComponentsExtension, its type parameters will tell me 45 | // type types of the VariantBuilder and Variant object I am dealing with. 46 | val superType = type.supertypes.first { superType -> 47 | superType.jvmErasure.simpleName == "AndroidComponentsExtension" 48 | } 49 | 50 | // now the superType arguments are as follows 51 | // superType.arguments[0] is the DSL common extension type 52 | // superType.arguments[1] is the VariantBuilder type 53 | // superType.arguments[2] is the Variant type. 54 | val variantBuilderType = superType.arguments[1].type 55 | ?: throw RuntimeException("Cannot determine the VariantBuilder type for extension $type") 56 | 57 | val variantType = superType.arguments[2].type 58 | ?: throw RuntimeException("Cannot determine the Variant type for extension $type") 59 | 60 | // It is not necessary to cast to the AndroidComponentsExtension subtype as I am only 61 | // interested in invoking methods that are all defined in the AndroidComponentsExtension interface itself. 62 | val typedExtension = AndroidComponentsExtension::class.java.cast(extension) 63 | 64 | // order does not matter, it's just registering a callback at this point which will be called by AGP 65 | // in the right order. 66 | handleCallback(table, typedExtension, "beforeVariants", variantBuilderType) 67 | { tomlDeclaration, variantApiType, selector -> 68 | if (selector == null) { 69 | typedExtension.beforeVariants { variantBuilder -> 70 | parse(tomlDeclaration, variantBuilder, variantApiType) 71 | } 72 | } else { 73 | typedExtension.beforeVariants(selector) { variantBuilder -> 74 | parse(tomlDeclaration, variantBuilder, variantApiType) 75 | } 76 | } 77 | } 78 | handleCallback(table, typedExtension, "onVariants", variantType) { 79 | tomlDeclaration, variantApiType, selector -> 80 | if (selector == null) { 81 | typedExtension.onVariants { variant -> 82 | parse(tomlDeclaration, variant, variantApiType) 83 | } 84 | } else { 85 | typedExtension.onVariants(selector) { variant -> 86 | parse(tomlDeclaration, variant, variantApiType) 87 | } 88 | } 89 | } 90 | } 91 | 92 | /** 93 | * Handle a Variant API callback style pf declarations. 94 | * 95 | * @param table the toml declarations 96 | * @param typedExtension the type of the Variant API we are dealing with (Application, Library, etc...) 97 | * @param variantApiName callback name like 'onVariants' or 'beforeVariants' 98 | * @param variantApiType type of instance passed to the Variant API callback so for 'onVariants', it's 99 | * ApplicationVariant (if dealing with an application) while for 'beforeVariants`, it's ApplicationVariantBuilder. 100 | * @param action lambda function to register callbacks on [typedExtension] for each target variants. 101 | */ 102 | private fun handleCallback( 103 | table: TomlTable, 104 | typedExtension: AndroidComponentsExtension<*, *, *>, 105 | variantApiName: String, 106 | variantApiType: KType, 107 | action: (tomlDeclaration: TomlTable, variantApiType: KType, selector: VariantSelector?) -> Unit 108 | ) { 109 | // let's look at setting that applies to all variants. 110 | table.getTable(variantApiName)?.let { variantNames -> 111 | variantNames.keySet().forEach { variantName -> 112 | if (!variantNames.isTable(variantName)) { 113 | throw RuntimeException( 114 | """ 115 | When invoking the beforeVariants/onVariants API, you must always provide a 116 | variant name, or `all` to target all variants. 117 | 118 | For example, instead of 119 | [androidComponents.beforeVariants] 120 | enable = false 121 | 122 | To target all variants, you must do 123 | [androidComponents.beforeVariants.all] 124 | enable = false 125 | To target a variant by its VARIANT_NAME name, you must do 126 | [androidComponents.beforeVariants.VARIANT_NAME] 127 | enable = false 128 | """.trimIndent() 129 | ) 130 | } 131 | variantNames.getTable(variantName)?.let { 132 | action( 133 | it, 134 | variantApiType, 135 | if (variantName.equals("all")) { 136 | typedExtension.selector().all() 137 | } else { 138 | typedExtension.selector().withName(variantName) 139 | } 140 | ) 141 | } 142 | } 143 | } 144 | } 145 | 146 | private fun parse(tomlDeclaration: TomlTable, variantBuilder: VariantBuilder, variantBuilderType: KType) { 147 | GenericDeclarativeParser(project, cache, issueLogger) 148 | .parse(tomlDeclaration, variantBuilderType.jvmErasure, variantBuilder) 149 | } 150 | 151 | private fun parse(tomlDeclaration: TomlTable, variant: Variant, variantType: KType) { 152 | GenericDeclarativeParser(project, cache, issueLogger) 153 | .parse(tomlDeclaration, variantType.jvmErasure, variant) 154 | } 155 | } -------------------------------------------------------------------------------- /project-plugin/src/test/kotlin/com/android/declarative/project/agp/DependencyProcessorTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 com.android.declarative.project.agp 18 | 19 | import com.android.declarative.common.AbstractDeclarativePlugin 20 | import com.android.declarative.internal.IssueLogger 21 | import com.android.declarative.internal.model.DependencyInfo.* 22 | import com.android.declarative.internal.model.DependencyType 23 | import com.android.declarative.project.DependencyProcessor 24 | import com.android.utils.ILogger 25 | import org.gradle.api.Project 26 | import org.gradle.api.artifacts.ExternalModuleDependency 27 | import org.gradle.api.artifacts.FileCollectionDependency 28 | import org.gradle.api.artifacts.MinimalExternalModuleDependency 29 | import org.gradle.api.artifacts.ProjectDependency 30 | import org.gradle.api.artifacts.VersionCatalog 31 | import org.gradle.api.artifacts.VersionCatalogsExtension 32 | import org.gradle.api.artifacts.dsl.DependencyFactory 33 | import org.gradle.api.artifacts.dsl.DependencyHandler 34 | import org.gradle.api.file.ConfigurableFileCollection 35 | import org.gradle.api.plugins.ExtensionContainer 36 | import org.gradle.api.provider.Provider 37 | import org.junit.Before 38 | import org.junit.Rule 39 | import org.junit.Test 40 | import org.mockito.Mock 41 | import org.mockito.Mockito 42 | import org.mockito.Mockito.times 43 | import org.mockito.junit.MockitoJUnit 44 | import org.mockito.junit.MockitoRule 45 | import org.mockito.quality.Strictness 46 | import java.util.* 47 | 48 | @Suppress("UnstableApiUsage") 49 | class DependencyProcessorTest { 50 | 51 | @get:Rule 52 | val rule: MockitoRule = MockitoJUnit.rule().strictness(Strictness.LENIENT) 53 | 54 | @Mock 55 | lateinit var dependencyHandler: DependencyHandler 56 | 57 | @Mock 58 | lateinit var dependencyFactory: DependencyFactory 59 | 60 | @Mock 61 | lateinit var fileCollection: ConfigurableFileCollection 62 | 63 | @Mock 64 | lateinit var project: Project 65 | 66 | @Before 67 | fun setup() { 68 | Mockito.`when`(project.rootProject).thenReturn(project) 69 | Mockito.`when`(project.files()).thenReturn(fileCollection) 70 | Mockito.`when`(project.dependencies).thenReturn(dependencyHandler) 71 | Mockito.`when`(project.dependencyFactory).thenReturn(dependencyFactory) 72 | } 73 | 74 | @Test 75 | fun testProjectDependency() { 76 | val parser = createDependenciesParser() 77 | val dependency = createSubProjectAndWireDependency(":lib1") 78 | val dependencies = listOf( 79 | Notation( 80 | DependencyType.PROJECT, 81 | "implementation", 82 | ":lib1" 83 | ) 84 | ) 85 | parser.process(dependencies) 86 | 87 | Mockito.verify(dependencyHandler).add("implementation", dependency) 88 | Mockito.verifyNoMoreInteractions(dependencyHandler) 89 | } 90 | 91 | @Test 92 | fun testMultipleShortFormProjectDependency() { 93 | val parser = createDependenciesParser() 94 | val dependency1 = createSubProjectAndWireDependency(":lib1") 95 | val dependency2 = createSubProjectAndWireDependency(":lib2") 96 | val dependencies = listOf( 97 | Notation( 98 | DependencyType.PROJECT, 99 | "implementation", 100 | ":lib1" 101 | ), 102 | Notation( 103 | DependencyType.PROJECT, 104 | "testImplementation", 105 | ":lib2" 106 | ) 107 | ) 108 | parser.process(dependencies) 109 | Mockito.verify(dependencyHandler).add("implementation", dependency1) 110 | Mockito.verify(dependencyHandler).add("testImplementation", dependency2) 111 | Mockito.verifyNoMoreInteractions(dependencyHandler) 112 | } 113 | 114 | @Test 115 | fun testSimpleExternalDependency() { 116 | val parser = createDependenciesParser() 117 | val dependency = createExternalDependency("org.mockito:mockito-core:4.8.0") 118 | val dependencies = listOf( 119 | Notation( 120 | DependencyType.NOTATION, 121 | configuration = "implementation", 122 | notation = "org.mockito:mockito-core:4.8.0", 123 | ) 124 | ) 125 | parser.process(dependencies) 126 | Mockito.verify(dependencyHandler).add("implementation", dependency) 127 | Mockito.verifyNoMoreInteractions(dependencyHandler) 128 | } 129 | 130 | @Test 131 | fun testPartialExternalDependency() { 132 | val parser = createDependenciesParser() 133 | val dependency = createExternalDependency("org.mockito", "mockito-core", "4.8.0") 134 | val dependencies = listOf( 135 | Maven( 136 | configuration = "implementation", 137 | group = "org.mockito", 138 | name = "mockito-core", 139 | version = "4.8.0", 140 | ) 141 | ) 142 | parser.process(dependencies) 143 | Mockito.verify(dependencyHandler).add("implementation", dependency) 144 | Mockito.verifyNoMoreInteractions(dependencyHandler) 145 | } 146 | 147 | @Test 148 | fun testPartialExternalDependencyInDottedNames() { 149 | val parser = createDependenciesParser() 150 | val dependency1 = createExternalDependency("org.mockito", "mockito-core", "4.8.0") 151 | val dependency2 = createExternalDependency("org.junit", "junit", "5.7.0") 152 | val dependencies = listOf( 153 | Maven( 154 | configuration = "testImplementation", 155 | group = "org.mockito", 156 | name = "mockito-core", 157 | version = "4.8.0", 158 | ), 159 | Maven( 160 | configuration = "testImplementation", 161 | group = "org.junit", 162 | name = "junit", 163 | version = "5.7.0", 164 | ) 165 | ) 166 | parser.process(dependencies) 167 | Mockito.verify(dependencyHandler).add("testImplementation", dependency1) 168 | Mockito.verify(dependencyHandler).add("testImplementation", dependency2) 169 | Mockito.verifyNoMoreInteractions(dependencyHandler) 170 | } 171 | 172 | @Test 173 | fun testVersionCatalogDependency() { 174 | val parser = createDependenciesParser() 175 | val extensions = Mockito.mock(ExtensionContainer::class.java) 176 | Mockito.`when`(project.extensions).thenReturn(extensions) 177 | val versionCatalogs = Mockito.mock(VersionCatalogsExtension::class.java) 178 | Mockito.`when`(extensions.findByType(VersionCatalogsExtension::class.java)).thenReturn(versionCatalogs) 179 | val libs = Mockito.mock(VersionCatalog::class.java) 180 | Mockito.`when`(versionCatalogs.find("libs")).thenReturn(Optional.of(libs)) 181 | val provider = Mockito.mock(Provider::class.java) as Provider 182 | val providerOptional = Optional.of(provider) 183 | Mockito.`when`(libs.findLibrary("junit")).thenReturn(providerOptional) 184 | 185 | val dependencies = listOf( 186 | Alias( 187 | "implementation", 188 | "libs.junit" 189 | ) 190 | ) 191 | parser.process(dependencies) 192 | Mockito.verify(dependencyHandler).add("implementation", provider) 193 | Mockito.verifyNoMoreInteractions(dependencyHandler) 194 | } 195 | 196 | @Test 197 | fun testFilesNotationDependency() { 198 | val parser = DependencyProcessor( 199 | Mockito.mock(AbstractDeclarativePlugin::class.java), 200 | { id -> project.rootProject.project(id)}, 201 | project, 202 | IssueLogger(lenient = true, logger = Mockito.mock(ILogger::class.java)) 203 | ) 204 | val dependency = Mockito.mock(FileCollectionDependency::class.java) 205 | Mockito.doReturn(dependency).`when`(dependencyFactory).create(fileCollection) 206 | 207 | val dependencies = listOf( 208 | Files( 209 | "implementation", 210 | listOf("local.jar") 211 | ), 212 | Files( 213 | "implementation", 214 | listOf("some.jar", "something.else", "final.one") 215 | ) 216 | ) 217 | parser.process(dependencies) 218 | Mockito.verify(dependencyHandler, times(2)).add("implementation", dependency) 219 | Mockito.verifyNoMoreInteractions(dependencyHandler) 220 | } 221 | 222 | @Test 223 | fun testMultipleProjectDependency() { 224 | val dependency1 = createSubProjectAndWireDependency(":lib1") 225 | val dependency2 = createSubProjectAndWireDependency(":lib2") 226 | val dependency3 = createSubProjectAndWireDependency(":lib3") 227 | 228 | val parser = createDependenciesParser() 229 | 230 | val dependencies = listOf( 231 | Notation( 232 | type = DependencyType.PROJECT, 233 | configuration = "implementation", 234 | notation = ":lib1" 235 | ), 236 | Notation( 237 | type = DependencyType.PROJECT, 238 | configuration = "implementation", 239 | notation = ":lib2" 240 | ), 241 | Notation( 242 | type = DependencyType.PROJECT, 243 | configuration = "implementation", 244 | notation = ":lib3" 245 | ), 246 | ) 247 | parser.process(dependencies) 248 | Mockito.verify(dependencyHandler).add("implementation", dependency1) 249 | Mockito.verify(dependencyHandler).add("implementation", dependency2) 250 | Mockito.verify(dependencyHandler).add("implementation", dependency3) 251 | Mockito.verifyNoMoreInteractions(dependencyHandler) 252 | } 253 | 254 | private fun createDependenciesParser() = 255 | DependencyProcessor( 256 | Mockito.mock(AbstractDeclarativePlugin::class.java), 257 | { id -> project.rootProject.project(id)}, 258 | project, 259 | IssueLogger(lenient = true, logger = Mockito.mock(ILogger::class.java)) 260 | ) 261 | private fun createSubProject(projectPath: String): Project = 262 | Mockito.mock(Project::class.java).also { 263 | Mockito.`when`(project.project(projectPath)).thenReturn(it) 264 | } 265 | 266 | private fun createSubProjectAndWireDependency(projectPath: String): ProjectDependency = 267 | Mockito.mock(ProjectDependency::class.java).also { 268 | Mockito.`when`(dependencyFactory.create(createSubProject(projectPath))).thenReturn(it) 269 | } 270 | 271 | private fun createExternalDependency(group: String?, name: String, version: String?) = 272 | Mockito.mock(ExternalModuleDependency::class.java).also { 273 | Mockito.`when`(dependencyFactory.create(group, name, version)).thenReturn( 274 | it 275 | ) 276 | } 277 | 278 | private fun createExternalDependency(notation: String) = 279 | Mockito.mock(ExternalModuleDependency::class.java).also { 280 | Mockito.`when`(dependencyFactory.create(notation)).thenReturn( 281 | it 282 | ) 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /project-plugin/src/test/kotlin/com/android/declarative/project/agp/ProductFlavorTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 com.android.declarative.project.agp 18 | 19 | import com.android.build.api.dsl.ApplicationExtension 20 | import com.android.build.api.dsl.ApplicationProductFlavor 21 | import com.android.declarative.project.GenericDeclarativeParser 22 | import com.android.declarative.project.agp.agp.AgpDslTest 23 | import com.google.common.truth.Truth 24 | import org.gradle.api.NamedDomainObjectContainer 25 | import org.junit.Test 26 | import org.mockito.Mock 27 | import org.mockito.Mockito 28 | import org.mockito.Mockito.times 29 | import org.tomlj.Toml 30 | 31 | class ProductFlavorTest: AgpDslTest() { 32 | 33 | @Mock(strictness = Mock.Strictness.STRICT_STUBS) 34 | lateinit var extension: ApplicationExtension 35 | 36 | @Mock(strictness = Mock.Strictness.STRICT_STUBS) 37 | lateinit var demoFlavor: ApplicationProductFlavor 38 | 39 | @Mock(strictness = Mock.Strictness.STRICT_STUBS) 40 | lateinit var fullFlavor: ApplicationProductFlavor 41 | 42 | @Test 43 | fun testSetDimension() { 44 | val flavorDimensions = mutableListOf() 45 | Mockito.`when`(extension.flavorDimensions).thenReturn(flavorDimensions) 46 | val toml = Toml.parse( 47 | """ 48 | [android] 49 | flavorDimensions = [ "version" ] 50 | """.trimIndent() 51 | ) 52 | GenericDeclarativeParser(project = project).parse( 53 | toml.getTable("android")!!, 54 | ApplicationExtension::class, 55 | extension 56 | ) 57 | Mockito.verify(extension, times(1)).flavorDimensions 58 | Truth.assertThat(flavorDimensions).containsExactly("version") 59 | } 60 | 61 | @Test 62 | fun testProductFlavors() { 63 | val flavorDimensions = mutableListOf() 64 | val flavorContainer = Mockito.mock(NamedDomainObjectContainer::class.java) 65 | as NamedDomainObjectContainer 66 | Mockito.`when`(extension.flavorDimensions).thenReturn(flavorDimensions) 67 | Mockito.`when`(extension.productFlavors).thenReturn(flavorContainer) 68 | Mockito.`when`(flavorContainer.maybeCreate("demo")).thenReturn(demoFlavor) 69 | Mockito.`when`(flavorContainer.maybeCreate("full")).thenReturn(fullFlavor) 70 | val toml = Toml.parse( 71 | """ 72 | [android] 73 | flavorDimensions = [ "version" ] 74 | 75 | [android.productFlavors.demo] 76 | dimension="version" 77 | applicationIdSuffix=".demo" 78 | versionNameSuffix="-demo" 79 | 80 | [android.productFlavors.full] 81 | dimension="version" 82 | applicationIdSuffix=".full" 83 | versionNameSuffix="-full" 84 | """.trimIndent() 85 | ) 86 | GenericDeclarativeParser(project = project).parse( 87 | toml.getTable("android")!!, 88 | ApplicationExtension::class, 89 | extension 90 | ) 91 | Mockito.verify(extension, times(1)).flavorDimensions 92 | Mockito.verify(extension, times(1)).productFlavors 93 | Mockito.verify(flavorContainer).maybeCreate("demo") 94 | Mockito.verify(flavorContainer).maybeCreate("full") 95 | Mockito.verify(demoFlavor).dimension = "version" 96 | Mockito.verify(demoFlavor).applicationIdSuffix = ".demo" 97 | Mockito.verify(demoFlavor).versionNameSuffix = "-demo" 98 | Mockito.verify(fullFlavor).dimension = "version" 99 | Mockito.verify(fullFlavor).applicationIdSuffix = ".full" 100 | Mockito.verify(fullFlavor).versionNameSuffix = "-full" 101 | Mockito.verifyNoMoreInteractions(extension, flavorContainer, demoFlavor, fullFlavor) 102 | Truth.assertThat(flavorDimensions).containsExactly("version") 103 | } 104 | } -------------------------------------------------------------------------------- /project-plugin/src/test/kotlin/com/android/declarative/project/agp/agp/AgpDslTest.kt: -------------------------------------------------------------------------------- 1 | package com.android.declarative.project.agp.agp 2 | 3 | import org.gradle.api.Project 4 | import org.gradle.testfixtures.ProjectBuilder 5 | import org.junit.Before 6 | import org.junit.Rule 7 | import org.junit.rules.TemporaryFolder 8 | import org.mockito.junit.MockitoJUnit 9 | import org.mockito.junit.MockitoRule 10 | import org.mockito.quality.Strictness 11 | import java.io.File 12 | 13 | abstract class AgpDslTest { 14 | @get:Rule 15 | val rule: MockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS) 16 | 17 | @get:Rule 18 | val temporaryFolder= TemporaryFolder() 19 | 20 | lateinit var project: Project 21 | @Before 22 | fun setup() { 23 | File(temporaryFolder.root, "gradle.properties").writeText( 24 | """ 25 | org.gradle.logging.level=debug 26 | """.trimIndent() 27 | ) 28 | project = ProjectBuilder.builder() 29 | .withProjectDir(temporaryFolder.root) 30 | .build() 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /project-plugin/src/test/kotlin/com/android/declarative/project/agp/agp/BuildTypeTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 com.android.declarative.project.agp.agp 18 | 19 | import com.android.build.api.dsl.ApplicationBuildType 20 | import com.android.build.api.dsl.ApplicationExtension 21 | import com.android.declarative.common.AbstractDeclarativePlugin 22 | import com.android.declarative.internal.IssueLogger 23 | import com.android.declarative.common.JdkLoggerWrapper 24 | import com.android.declarative.project.GenericDeclarativeParser 25 | import com.google.common.truth.Truth 26 | import org.gradle.api.NamedDomainObjectContainer 27 | import org.junit.Assert.assertThrows 28 | import org.junit.Test 29 | import org.mockito.Mock 30 | import org.mockito.Mockito 31 | import org.tomlj.Toml 32 | import java.io.File 33 | import java.lang.RuntimeException 34 | import java.util.logging.Logger 35 | 36 | class BuildTypeTest: AgpDslTest() { 37 | 38 | @Mock 39 | lateinit var extension: ApplicationExtension 40 | 41 | @Mock 42 | lateinit var debug: ApplicationBuildType 43 | 44 | @Mock 45 | lateinit var release: ApplicationBuildType 46 | 47 | @Mock 48 | lateinit var staging: ApplicationBuildType 49 | 50 | @Test 51 | fun testInvalidDeclaration() { 52 | File(temporaryFolder.root, "build.gradle.toml").writeText( 53 | """ 54 | [android.buildTypes] 55 | debug.minifyEnabled = true 56 | [android.buildTypes.debug.minifyEnabled] 57 | """.trimIndent() 58 | ) 59 | val plugin = object : AbstractDeclarativePlugin() { 60 | override val buildFileName: String 61 | get() = "build.gradle.toml" 62 | } 63 | 64 | val exception: RuntimeException = assertThrows(RuntimeException::class.java) { 65 | plugin.parseDeclarativeInFolder( 66 | temporaryFolder.root, IssueLogger( 67 | lenient = false, 68 | JdkLoggerWrapper(Logger.getLogger(BuildTypeTest::class.java.name)) 69 | ) 70 | ) 71 | } 72 | Truth.assertThat(exception.message).contains( 73 | "3:1 android.buildTypes.debug.minifyEnabled previously defined at line 2, column 1" 74 | ) 75 | } 76 | 77 | @Test 78 | fun testBuildTypes() { 79 | val buildTypeContainer = Mockito.mock(NamedDomainObjectContainer::class.java) 80 | as NamedDomainObjectContainer 81 | 82 | Mockito.`when`(extension.buildTypes).thenReturn(buildTypeContainer) 83 | Mockito.`when`(buildTypeContainer.maybeCreate("debug")).thenReturn(debug) 84 | val proguardFiles = mutableListOf() 85 | Mockito.`when`(debug.proguardFiles).thenReturn(proguardFiles) 86 | Mockito.`when`(buildTypeContainer.maybeCreate("release")).thenReturn(release) 87 | Mockito.`when`(buildTypeContainer.maybeCreate("staging")).thenReturn(staging) 88 | Mockito.`when`(buildTypeContainer.getByName("debug")).thenReturn(debug) 89 | val placeHolders = mutableMapOf() 90 | Mockito.`when`(staging.manifestPlaceholders).thenReturn(placeHolders) 91 | Mockito.`when`(extension.getDefaultProguardFile("proguard-android.txt")).thenReturn( 92 | File(temporaryFolder.newFolder(), "proguard-android.txt") 93 | ) 94 | 95 | val toml = Toml.parse( 96 | """ 97 | [android.buildTypes.debug] 98 | minifyEnabled = true 99 | proguardFiles = [ "proguard-rules.pro" ] 100 | 101 | [android.buildTypes.debug._dispatch_] 102 | target = "proguardFiles" 103 | method = "getDefaultProguardFile" 104 | params = [ "proguard-android.txt" ] 105 | 106 | [android.buildTypes.release] 107 | applicationIdSuffix = ".debug" 108 | debuggable = true 109 | 110 | [android.buildTypes.staging] 111 | initWith = "debug" 112 | manifestPlaceholders = { hostName = "internal.example.com" } 113 | applicationIdSuffix = ".debugStaging" 114 | """.trimIndent() 115 | ) 116 | GenericDeclarativeParser(project).parse( 117 | toml.getTable("android")!!, 118 | ApplicationExtension::class, 119 | extension 120 | ) 121 | Mockito.verify(extension).buildTypes 122 | Truth.assertThat(placeHolders).containsEntry("hostName", "internal.example.com") 123 | Truth.assertThat(proguardFiles.map { it.name }).containsExactly( 124 | "proguard-android.txt", "proguard-rules.pro" 125 | ) 126 | 127 | Mockito.verify(buildTypeContainer).maybeCreate("debug") 128 | Mockito.verify(buildTypeContainer).maybeCreate("release") 129 | Mockito.verify(buildTypeContainer).maybeCreate("staging") 130 | Mockito.verify(debug).isMinifyEnabled = true 131 | Mockito.verify(release).applicationIdSuffix = ".debug" 132 | Mockito.verify(release).isDebuggable = true 133 | Mockito.verify(staging).initWith(debug) 134 | Mockito.verify(staging).applicationIdSuffix = ".debugStaging" 135 | } 136 | 137 | @Test 138 | fun testMatchingFallbacks() { 139 | val buildTypeContainer = Mockito.mock(NamedDomainObjectContainer::class.java) 140 | as NamedDomainObjectContainer 141 | 142 | Mockito.`when`(extension.buildTypes).thenReturn(buildTypeContainer) 143 | Mockito.`when`(buildTypeContainer.maybeCreate("debug")).thenReturn(debug) 144 | val matchingFallback = mutableListOf() 145 | Mockito.`when`(staging.matchingFallbacks).thenReturn(matchingFallback) 146 | Mockito.`when`(buildTypeContainer.maybeCreate("release")).thenReturn(release) 147 | Mockito.`when`(buildTypeContainer.maybeCreate("staging")).thenReturn(staging) 148 | 149 | val toml = Toml.parse( 150 | """ 151 | [android.buildTypes.debug] 152 | [android.buildTypes.release] 153 | [android.buildTypes.staging] 154 | matchingFallbacks = [ "debug", "qa", "release" ] 155 | """.trimIndent() 156 | ) 157 | GenericDeclarativeParser(project).parse( 158 | toml.getTable("android")!!, 159 | ApplicationExtension::class, 160 | extension 161 | ) 162 | Mockito.verify(extension).buildTypes 163 | Truth.assertThat(matchingFallback).containsExactly("debug", "qa", "release") 164 | 165 | Mockito.verify(buildTypeContainer).maybeCreate("debug") 166 | Mockito.verify(buildTypeContainer).maybeCreate("release") 167 | Mockito.verify(buildTypeContainer).maybeCreate("staging") 168 | Mockito.verify(staging).matchingFallbacks 169 | } 170 | } -------------------------------------------------------------------------------- /project-plugin/src/test/kotlin/com/android/declarative/project/agp/agp/BundleTest.kt: -------------------------------------------------------------------------------- 1 | package com.android.declarative.project.agp.agp 2 | 3 | import com.android.build.api.dsl.ApplicationExtension 4 | import com.android.build.api.dsl.Bundle 5 | import com.android.build.api.dsl.BundleAbi 6 | import com.android.build.api.dsl.BundleCodeTransparency 7 | import com.android.build.api.dsl.BundleDensity 8 | import com.android.build.api.dsl.BundleDeviceTier 9 | import com.android.build.api.dsl.BundleLanguage 10 | import com.android.build.api.dsl.BundleTexture 11 | import com.android.build.api.dsl.SigningConfig 12 | import com.android.declarative.project.GenericDeclarativeParser 13 | import org.junit.Test 14 | 15 | import org.mockito.Mock 16 | import org.mockito.Mockito 17 | import org.tomlj.Toml 18 | import java.io.File 19 | 20 | 21 | @Suppress("UnstableApiUsage") 22 | class BundleTest: AgpDslTest() { 23 | 24 | @Mock 25 | lateinit var extension: ApplicationExtension 26 | 27 | @Mock 28 | lateinit var bundle: Bundle 29 | 30 | @Test 31 | fun testBundleAbi() { 32 | Mockito.`when`(extension.bundle).thenReturn(bundle) 33 | val abi = Mockito.mock(BundleAbi::class.java) 34 | Mockito.`when`(bundle.abi).thenReturn(abi) 35 | 36 | val toml = Toml.parse( 37 | """ 38 | [android.bundle.abi] 39 | enableSplit = true 40 | """.trimIndent() 41 | ) 42 | GenericDeclarativeParser(project).parse( 43 | toml.getTable("android")!!, 44 | ApplicationExtension::class, 45 | extension 46 | ) 47 | Mockito.verify(extension).bundle 48 | Mockito.verify(bundle).abi 49 | Mockito.verify(abi).enableSplit = true 50 | } 51 | 52 | @Test 53 | fun testBundleCodeTransparency() { 54 | Mockito.`when`(extension.bundle).thenReturn(bundle) 55 | val bundleCodeTransparency = Mockito.mock(BundleCodeTransparency::class.java) 56 | Mockito.`when`(bundle.codeTransparency).thenReturn(bundleCodeTransparency) 57 | val signingConfig = Mockito.mock(SigningConfig::class.java) 58 | Mockito.`when`(bundleCodeTransparency.signing).thenReturn(signingConfig) 59 | 60 | val toml = Toml.parse( 61 | """ 62 | [android.bundle.codeTransparency.signing] 63 | keyAlias = "alias" 64 | keyPassword = "pwd" 65 | storeFile = "path/to/file" 66 | storePassword = "pwd2" 67 | storeType = "type" 68 | """.trimIndent() 69 | ) 70 | GenericDeclarativeParser(project).parse( 71 | toml.getTable("android")!!, 72 | ApplicationExtension::class, 73 | extension 74 | ) 75 | Mockito.verify(extension).bundle 76 | Mockito.verify(bundle).codeTransparency 77 | Mockito.verify(bundleCodeTransparency).signing 78 | Mockito.verify(signingConfig).keyAlias = "alias" 79 | Mockito.verify(signingConfig).keyPassword = "pwd" 80 | Mockito.verify(signingConfig).storeFile = File("path/to/file") 81 | Mockito.verify(signingConfig).storePassword = "pwd2" 82 | Mockito.verify(signingConfig).storeType = "type" 83 | } 84 | 85 | @Test 86 | fun testBundleDensity() { 87 | Mockito.`when`(extension.bundle).thenReturn(bundle) 88 | val density = Mockito.mock(BundleDensity::class.java) 89 | Mockito.`when`(bundle.density).thenReturn(density) 90 | 91 | val toml = Toml.parse( 92 | """ 93 | [android.bundle.density] 94 | enableSplit = true 95 | """.trimIndent() 96 | ) 97 | GenericDeclarativeParser(project).parse( 98 | toml.getTable("android")!!, 99 | ApplicationExtension::class, 100 | extension 101 | ) 102 | Mockito.verify(extension).bundle 103 | Mockito.verify(bundle).density 104 | Mockito.verify(density).enableSplit = true 105 | } 106 | 107 | @Test 108 | fun testBundleDeviceTier() { 109 | Mockito.`when`(extension.bundle).thenReturn(bundle) 110 | val deviceTier = Mockito.mock(BundleDeviceTier::class.java) 111 | Mockito.`when`(bundle.deviceTier).thenReturn(deviceTier) 112 | 113 | val toml = Toml.parse( 114 | """ 115 | [android.bundle.deviceTier] 116 | defaultTier = "default" 117 | enableSplit = true 118 | """.trimIndent() 119 | ) 120 | GenericDeclarativeParser(project).parse( 121 | toml.getTable("android")!!, 122 | ApplicationExtension::class, 123 | extension 124 | ) 125 | Mockito.verify(extension).bundle 126 | Mockito.verify(bundle).deviceTier 127 | Mockito.verify(deviceTier).defaultTier = "default" 128 | Mockito.verify(deviceTier).enableSplit = true 129 | } 130 | 131 | @Test 132 | fun testBundleLanguage() { 133 | Mockito.`when`(extension.bundle).thenReturn(bundle) 134 | val bundleLanguage = Mockito.mock(BundleLanguage::class.java) 135 | Mockito.`when`(bundle.language).thenReturn(bundleLanguage) 136 | 137 | val toml = Toml.parse( 138 | """ 139 | [android.bundle.language] 140 | enableSplit = true 141 | """.trimIndent() 142 | ) 143 | GenericDeclarativeParser(project).parse( 144 | toml.getTable("android")!!, 145 | ApplicationExtension::class, 146 | extension 147 | ) 148 | Mockito.verify(extension).bundle 149 | Mockito.verify(bundle).language 150 | Mockito.verify(bundleLanguage).enableSplit = true 151 | } 152 | 153 | @Test 154 | fun testBundleTexture() { 155 | Mockito.`when`(extension.bundle).thenReturn(bundle) 156 | val bundleTexture = Mockito.mock(BundleTexture::class.java) 157 | Mockito.`when`(bundle.texture).thenReturn(bundleTexture) 158 | 159 | val toml = Toml.parse( 160 | """ 161 | [android.bundle.texture] 162 | defaultFormat = "format" 163 | enableSplit = true 164 | """.trimIndent() 165 | ) 166 | GenericDeclarativeParser(project).parse( 167 | toml.getTable("android")!!, 168 | ApplicationExtension::class, 169 | extension 170 | ) 171 | Mockito.verify(extension).bundle 172 | Mockito.verify(bundle).texture 173 | Mockito.verify(bundleTexture).defaultFormat = "format" 174 | Mockito.verify(bundleTexture).enableSplit = true 175 | } 176 | } -------------------------------------------------------------------------------- /project-plugin/src/test/kotlin/com/android/declarative/project/agp/agp/variant/LibraryVariantApiTest.kt: -------------------------------------------------------------------------------- 1 | package com.android.declarative.project.agp.agp.variant 2 | 3 | import com.android.build.api.dsl.LibraryExtension 4 | import com.android.build.api.variant.AndroidComponentsExtension 5 | import com.android.build.api.variant.LibraryAndroidComponentsExtension 6 | import com.android.build.api.variant.LibraryVariant 7 | import com.android.build.api.variant.LibraryVariantBuilder 8 | import com.android.declarative.project.agp.agp.AgpDslTest 9 | import com.android.declarative.project.agp.fixtures.FakeAndroidComponentsExtension 10 | import com.android.declarative.project.agp.fixtures.FakeSelector 11 | import com.android.declarative.project.variantApi.AndroidComponentsParser 12 | import com.google.common.truth.Truth 13 | import org.gradle.api.provider.Property 14 | import org.junit.Test 15 | import org.mockito.Mock 16 | import org.mockito.Mockito 17 | import org.tomlj.Toml 18 | 19 | class LibraryVariantApiTest : AgpDslTest() { 20 | 21 | @Mock 22 | lateinit var variantBuilder: LibraryVariantBuilder 23 | 24 | @Mock 25 | lateinit var variant: LibraryVariant 26 | 27 | private val selector = FakeSelector() 28 | 29 | private val extension: AndroidComponentsExtension by lazy { 30 | FakeAndroidComponentsExtension( 31 | selector, 32 | mapOf("debug" to variantBuilder), 33 | mapOf("debug" to variant), 34 | ) 35 | } 36 | 37 | @Test 38 | fun testVariantBuilderEnable() { 39 | 40 | val toml = Toml.parse( 41 | """ 42 | [androidComponents.beforeVariants.debug] 43 | enable = false 44 | """.trimIndent() 45 | ) 46 | AndroidComponentsParser(project).parse( 47 | toml.getTable("androidComponents")!!, 48 | LibraryAndroidComponentsExtension::class, 49 | extension 50 | ) 51 | Truth.assertThat(selector.matches("debug")).isTrue() 52 | Mockito.verify(variantBuilder).enable = false 53 | } 54 | 55 | @Test 56 | fun testVariantEnable() { 57 | 58 | @Suppress("UNCHECKED_CAST") 59 | val pseudoLocalesEnabledProperty = Mockito.mock(Property::class.java) as Property 60 | Mockito.`when`(variant.pseudoLocalesEnabled).thenReturn(pseudoLocalesEnabledProperty) 61 | 62 | val toml = Toml.parse( 63 | """ 64 | [androidComponents.onVariants.debug] 65 | pseudoLocalesEnabled = true 66 | """.trimIndent() 67 | ) 68 | AndroidComponentsParser(project).parse( 69 | toml.getTable("androidComponents")!!, 70 | LibraryAndroidComponentsExtension::class, 71 | extension 72 | ) 73 | Truth.assertThat(selector.matches("debug")).isTrue() 74 | Mockito.verify(variant).pseudoLocalesEnabled 75 | Mockito.verify(pseudoLocalesEnabledProperty).set(true) 76 | } 77 | } -------------------------------------------------------------------------------- /project-plugin/src/test/kotlin/com/android/declarative/project/agp/fixtures/FakeAndroidComponentsExtension.kt: -------------------------------------------------------------------------------- 1 | package com.android.declarative.project.agp.fixtures 2 | 3 | import com.android.build.api.AndroidPluginVersion 4 | import com.android.build.api.dsl.CommonExtension 5 | import com.android.build.api.dsl.SdkComponents 6 | import com.android.build.api.instrumentation.manageddevice.ManagedDeviceRegistry 7 | import com.android.build.api.variant.AndroidComponentsExtension 8 | import com.android.build.api.variant.DslExtension 9 | import com.android.build.api.variant.Variant 10 | import com.android.build.api.variant.VariantBuilder 11 | import com.android.build.api.variant.VariantExtension 12 | import com.android.build.api.variant.VariantExtensionConfig 13 | import com.android.build.api.variant.VariantSelector 14 | import org.gradle.api.Action 15 | 16 | open @Suppress("UnstableApiUsage") 17 | class FakeAndroidComponentsExtension< 18 | CommonExtensionT: CommonExtension<*, *, *, *, *>, 19 | VariantBuilderT: VariantBuilder, 20 | VariantT: Variant>( 21 | private val selector: VariantSelector, 22 | private val variantBuilders: Map, 23 | private val variants: Map, 24 | ): AndroidComponentsExtension { 25 | 26 | override val managedDeviceRegistry: ManagedDeviceRegistry 27 | get() = TODO("Not yet implemented") 28 | override val pluginVersion: AndroidPluginVersion 29 | get() = TODO("Not yet implemented") 30 | override val sdkComponents: SdkComponents 31 | get() = TODO("Not yet implemented") 32 | 33 | override fun finalizeDsl(callback: Action) { 34 | TODO("Not yet implemented") 35 | } 36 | 37 | override fun finalizeDsl(callback: (CommonExtensionT) -> Unit) { 38 | TODO("Not yet implemented") 39 | } 40 | 41 | override fun registerSourceType(name: String) { 42 | TODO("Not yet implemented") 43 | } 44 | 45 | override fun selector(): VariantSelector = selector 46 | 47 | 48 | override fun registerExtension( 49 | dslExtension: DslExtension, 50 | configurator: (variantExtensionConfig: VariantExtensionConfig) -> VariantExtension 51 | ) { 52 | TODO("Not yet implemented") 53 | } 54 | 55 | override fun onVariants(selector: VariantSelector, callback: Action) { 56 | TODO("Not yet implemented") 57 | } 58 | 59 | override fun onVariants(selector: VariantSelector, callback: (VariantT) -> Unit) { 60 | if (selector is FakeSelector) { 61 | if (selector.selectorName == "all") { 62 | variants.values.forEach(callback) 63 | } else { 64 | variants[selector.selectorName]?.let { 65 | callback(it) 66 | } ?: throw RuntimeException("Cannot find variant ${selector.selectorName}") 67 | } 68 | } else { 69 | variants.values.forEach(callback) 70 | } 71 | } 72 | 73 | @Deprecated("Replaced by finalizeDsl", replaceWith = ReplaceWith("finalizeDsl(callback)")) 74 | override fun finalizeDSl(callback: Action) { 75 | TODO("Not yet implemented") 76 | } 77 | 78 | override fun beforeVariants(selector: VariantSelector, callback: Action) { 79 | TODO("Not yet implemented") 80 | } 81 | 82 | override fun beforeVariants(selector: VariantSelector, callback: (VariantBuilderT) -> Unit) { 83 | if (selector is FakeSelector) { 84 | if (selector.selectorName == "all") { 85 | variantBuilders.values.forEach(callback) 86 | } else { 87 | variantBuilders[selector.selectorName]?.let { 88 | callback(it) 89 | } ?: throw RuntimeException("Cannot find variant ${selector.selectorName}") 90 | } 91 | } else { 92 | variantBuilders.values.forEach(callback) 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /project-plugin/src/test/kotlin/com/android/declarative/project/agp/fixtures/FakeSelector.kt: -------------------------------------------------------------------------------- 1 | package com.android.declarative.project.agp.fixtures 2 | 3 | import com.android.build.api.variant.VariantSelector 4 | import java.util.regex.Pattern 5 | 6 | class FakeSelector: VariantSelector { 7 | 8 | var selectorName: String? = null 9 | override fun all(): VariantSelector { 10 | selectorName = "all" 11 | return this 12 | } 13 | 14 | override fun withBuildType(buildType: String): VariantSelector { 15 | TODO("Not yet implemented") 16 | } 17 | 18 | override fun withFlavor(flavorToDimension: Pair): VariantSelector { 19 | TODO("Not yet implemented") 20 | } 21 | 22 | override fun withFlavor(dimension: String, flavorName: String): VariantSelector { 23 | TODO("Not yet implemented") 24 | } 25 | 26 | override fun withName(pattern: Pattern): VariantSelector { 27 | TODO("Not yet implemented") 28 | } 29 | 30 | override fun withName(name: String): VariantSelector { 31 | selectorName = name 32 | return this 33 | } 34 | 35 | fun matches(value: String): Boolean { 36 | return selectorName == value 37 | } 38 | } -------------------------------------------------------------------------------- /project-plugin/src/test/kotlin/com/android/declarative/project/agp/java/JavaPluginExtensionTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 com.android.declarative.project.agp.java 18 | 19 | import com.android.declarative.project.GenericDeclarativeParser 20 | import org.gradle.api.JavaVersion 21 | import org.gradle.api.Project 22 | import org.gradle.api.plugins.JavaPluginExtension 23 | import org.gradle.testfixtures.ProjectBuilder 24 | import org.junit.Before 25 | import org.junit.Rule 26 | import org.junit.Test 27 | import org.junit.rules.TemporaryFolder 28 | import org.mockito.Mockito 29 | import org.mockito.junit.MockitoJUnit 30 | import org.mockito.junit.MockitoRule 31 | import org.mockito.quality.Strictness 32 | import org.tomlj.Toml 33 | import java.io.File 34 | 35 | class JavaPluginExtensionTest { 36 | @get:Rule 37 | val rule: MockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS) 38 | 39 | @get:Rule 40 | val temporaryFolder= TemporaryFolder() 41 | 42 | lateinit var project: Project 43 | 44 | @Before 45 | fun setup() { 46 | File(temporaryFolder.root, "gradle.properties").writeText( 47 | """ 48 | org.gradle.logging.level=debug 49 | """.trimIndent() 50 | ) 51 | project = ProjectBuilder.builder() 52 | .withProjectDir(temporaryFolder.root) 53 | .build() 54 | } 55 | 56 | @Test 57 | fun testJavaVersionUsingProperty() { 58 | val extension = Mockito.mock(JavaPluginExtension::class.java) 59 | 60 | val toml = Toml.parse( 61 | """ 62 | [java.sourceCompatibility] 63 | valueOf = "VERSION_17" 64 | 65 | [java.targetCompatibility] 66 | valueOf = "VERSION_18" 67 | """.trimIndent() 68 | ) 69 | GenericDeclarativeParser(project).parse( 70 | toml.getTable("java")!!, 71 | JavaPluginExtension::class, 72 | extension 73 | ) 74 | Mockito.verify(extension).sourceCompatibility = JavaVersion.VERSION_17 75 | Mockito.verify(extension).targetCompatibility = JavaVersion.VERSION_18 76 | } 77 | 78 | @Test 79 | fun testJavaVersionUsingMethod() { 80 | val extension = Mockito.mock(JavaPluginExtension::class.java) 81 | 82 | val toml = Toml.parse( 83 | """ 84 | [java.setSourceCompatibility] 85 | valueOf = "VERSION_17" 86 | 87 | [java.setTargetCompatibility] 88 | valueOf = "VERSION_18" 89 | """.trimIndent() 90 | ) 91 | GenericDeclarativeParser(project).parse( 92 | toml.getTable("java")!!, 93 | JavaPluginExtension::class, 94 | extension 95 | ) 96 | Mockito.verify(extension).sourceCompatibility = JavaVersion.VERSION_17 97 | Mockito.verify(extension).targetCompatibility = JavaVersion.VERSION_18 98 | } 99 | 100 | @Test 101 | fun testJavaVersionUsingPropertyWithAny() { 102 | val extension = Mockito.mock(JavaPluginExtension::class.java) 103 | 104 | val toml = Toml.parse( 105 | """ 106 | [java] 107 | sourceCompatibility = "VERSION_17 108 | 109 | [java] 110 | targetCompatibility = "VERSION_18" 111 | """.trimIndent() 112 | ) 113 | GenericDeclarativeParser(project).parse( 114 | toml.getTable("java")!!, 115 | JavaPluginExtension::class, 116 | extension 117 | ) 118 | Mockito.verify(extension).setSourceCompatibility("VERSION_17") 119 | Mockito.verify(extension).setTargetCompatibility("VERSION_18") 120 | } 121 | 122 | @Test 123 | fun testJavaVersionUsingMethodWithAny() { 124 | val extension = Mockito.mock(JavaPluginExtension::class.java) 125 | 126 | val toml = Toml.parse( 127 | """ 128 | [java] 129 | setSourceCompatibility = "VERSION_17 130 | 131 | [java] 132 | setTargetCompatibility = "VERSION_18" 133 | """.trimIndent() 134 | ) 135 | GenericDeclarativeParser(project).parse( 136 | toml.getTable("java")!!, 137 | JavaPluginExtension::class, 138 | extension 139 | ) 140 | Mockito.verify(extension).setSourceCompatibility("VERSION_17") 141 | Mockito.verify(extension).setTargetCompatibility("VERSION_18") 142 | } 143 | } -------------------------------------------------------------------------------- /settings-api/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 | 18 | plugins { 19 | id("declarative-plugin-module-conventions") 20 | alias(libs.plugins.kotlin.jvm) 21 | //alias(libs.plugins.plugin.publish) 22 | } 23 | 24 | gradlePlugin { 25 | plugins { 26 | create("comAndroidDeclarativeSettings") { 27 | id = "com.android.experiments.declarative.settings" 28 | version = Constants.PLUGIN_VERSION 29 | implementationClass = "com.android.declarative.settings.api.SettingsDeclarativePlugin" 30 | } 31 | } 32 | } 33 | 34 | dependencies { 35 | implementation(project(":settings-plugin")) 36 | } 37 | 38 | -------------------------------------------------------------------------------- /settings-api/src/main/kotlin/com/android/declarative/settings/api/SettingsDeclarativePlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 com.android.declarative.settings.api 18 | 19 | import org.gradle.api.Plugin 20 | import org.gradle.api.initialization.Settings 21 | 22 | class SettingsDeclarativePlugin: Plugin { 23 | override fun apply(settings: Settings) { 24 | settings.apply(mapOf("plugin" to "com.android.internal.settings-declarative")) 25 | } 26 | } -------------------------------------------------------------------------------- /settings-plugin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("declarative-plugin-module-conventions") 3 | alias(libs.plugins.kotlin.jvm) 4 | //alias(libs.plugins.plugin.publish) 5 | } 6 | 7 | val copyConstantsTask = tasks.register("copyConstants") { 8 | from(layout.projectDirectory.file("../buildSrc/src/main/kotlin/Constants.kt")) 9 | into(layout.buildDirectory.dir("src/generated")) 10 | } 11 | kotlin.sourceSets["main"].kotlin { 12 | srcDir(copyConstantsTask) 13 | } 14 | 15 | gradlePlugin { 16 | plugins { 17 | create("comAndroidDeclarativeSettings") { 18 | id = "com.android.internal.settings-declarative" 19 | version = Constants.PLUGIN_VERSION 20 | implementationClass = "com.android.declarative.internal.SettingsDeclarativePlugin" 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation(project(":common")) 27 | implementation(gradleApi()) 28 | implementation(libs.tomlj) 29 | implementation(libs.coroutines) 30 | implementation(libs.toolsCommon) 31 | implementation(libs.declarativeModel) 32 | implementation(libs.agpApi) 33 | 34 | testImplementation(libs.junit) 35 | testImplementation(libs.mockito) 36 | testImplementation(libs.truth) 37 | } 38 | -------------------------------------------------------------------------------- /settings-plugin/src/main/kotlin/com/android/declarative/internal/configurators/RepositoriesConfigurator.kt: -------------------------------------------------------------------------------- 1 | package com.android.declarative.internal.configurators 2 | 3 | import com.android.declarative.internal.IssueLogger 4 | import com.android.declarative.internal.model.PreDefinedRepositoryInfo 5 | import com.android.declarative.internal.model.RepositoryInfo 6 | import org.gradle.api.artifacts.dsl.RepositoryHandler 7 | 8 | /** 9 | * Configures a [RepositoryHandler] with repositories. 10 | */ 11 | class RepositoriesConfigurator( 12 | private val issueLogger: IssueLogger, 13 | ) { 14 | 15 | /** 16 | * Add the [List] of [RepositoryInfo]s to the provided [RepositoryHandler] 17 | * 18 | * @param context a user friendly string to provide context to error/warning messages. 19 | * @param repositoryHandler the [RepositoryHandler] to add repositories to. 20 | * @param repositoryModels [List] of repository model to add. 21 | */ 22 | fun apply( 23 | context: String, 24 | repositoryHandler: RepositoryHandler, 25 | repositoryModels: List 26 | ) { 27 | repositoryModels.forEach { 28 | when(it) { 29 | is PreDefinedRepositoryInfo -> { 30 | when(it.name) { 31 | "google" -> { 32 | issueLogger.logger.info("Adding google repository to $context") 33 | repositoryHandler.google() 34 | } 35 | "mavenCentral" -> { 36 | issueLogger.logger.info("Adding mavenCentral repository to $context") 37 | repositoryHandler.mavenCentral() 38 | } 39 | "mavenLocal" -> { 40 | issueLogger.logger.info("Adding mavenCentral repository to $context") 41 | repositoryHandler.mavenLocal() 42 | } 43 | "gradlePluginPortal" -> { 44 | issueLogger.logger.info("Adding gradlePluginPortal repository to $context") 45 | repositoryHandler.gradlePluginPortal() 46 | } 47 | else -> { 48 | throw RuntimeException("Unknown repository named : ${it.name}") 49 | } 50 | } 51 | } 52 | else -> throw RuntimeException("Unhandled repository type: ${it.type}, ${it.javaClass}") 53 | } 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /settings-plugin/src/test/kotlin/com/android/declarative/internal/configurators/RepositoriesConfiguratorTest.kt: -------------------------------------------------------------------------------- 1 | package com.android.declarative.internal.configurators 2 | 3 | import com.android.declarative.internal.IssueLogger 4 | import com.android.declarative.internal.model.PreDefinedRepositoryInfo 5 | import com.android.declarative.internal.model.RepositoryInfo 6 | import com.android.declarative.internal.model.RepositoryType 7 | import com.android.utils.ILogger 8 | import org.gradle.api.artifacts.dsl.RepositoryHandler 9 | import org.junit.Rule 10 | import org.junit.Test 11 | import org.mockito.Mock 12 | import org.mockito.Mockito 13 | import org.mockito.junit.MockitoJUnit 14 | import org.mockito.junit.MockitoRule 15 | import org.mockito.quality.Strictness 16 | 17 | class RepositoriesConfiguratorTest { 18 | 19 | @get:Rule 20 | val rule: MockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS) 21 | 22 | @Mock 23 | lateinit var repositoryHandler: RepositoryHandler 24 | 25 | @Mock 26 | lateinit var logger: ILogger 27 | 28 | private val issueLogger by lazy { IssueLogger(false, logger) } 29 | 30 | @Test 31 | fun testGoogle() { 32 | RepositoriesConfigurator(issueLogger).apply( 33 | "test", 34 | repositoryHandler, 35 | listOf(PreDefinedRepositoryInfo("google")) 36 | ) 37 | 38 | Mockito.verify(repositoryHandler).google() 39 | Mockito.verifyNoMoreInteractions(repositoryHandler) 40 | } 41 | 42 | @Test 43 | fun testMavenCentral() { 44 | RepositoriesConfigurator(issueLogger).apply( 45 | "test", 46 | repositoryHandler, 47 | listOf(PreDefinedRepositoryInfo("mavenCentral")) 48 | ) 49 | 50 | Mockito.verify(repositoryHandler).mavenCentral() 51 | Mockito.verifyNoMoreInteractions(repositoryHandler) 52 | } 53 | 54 | @Test 55 | fun testMavenLocal() { 56 | RepositoriesConfigurator(issueLogger).apply( 57 | "test", 58 | repositoryHandler, 59 | listOf(PreDefinedRepositoryInfo("mavenLocal")) 60 | ) 61 | 62 | Mockito.verify(repositoryHandler).mavenLocal() 63 | Mockito.verifyNoMoreInteractions(repositoryHandler) 64 | } 65 | 66 | @Test 67 | fun testGradlePluginPortal() { 68 | RepositoriesConfigurator(issueLogger).apply( 69 | "test", 70 | repositoryHandler, 71 | listOf(PreDefinedRepositoryInfo("gradlePluginPortal")) 72 | ) 73 | 74 | Mockito.verify(repositoryHandler).gradlePluginPortal() 75 | Mockito.verifyNoMoreInteractions(repositoryHandler) 76 | } 77 | 78 | @Test(expected = RuntimeException::class) 79 | fun testUnknownNamed() { 80 | RepositoriesConfigurator(issueLogger).apply( 81 | "test", 82 | repositoryHandler, 83 | listOf(PreDefinedRepositoryInfo("unknown")) 84 | ) 85 | 86 | Mockito.verifyNoInteractions(repositoryHandler) 87 | } 88 | 89 | @Test(expected = RuntimeException::class) 90 | fun testUnknownRepositoryInfo() { 91 | val repositoryInfo = object: RepositoryInfo { 92 | override val type: RepositoryType = RepositoryType.PRE_DEFINED 93 | } 94 | RepositoriesConfigurator(issueLogger).apply( 95 | "test", 96 | repositoryHandler, 97 | listOf(repositoryInfo) 98 | ) 99 | 100 | Mockito.verifyNoInteractions(repositoryHandler) 101 | } 102 | } -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | // repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 3 | } 4 | 5 | rootProject.name = "com.android.experiments.declarative" 6 | 7 | include("settings-api") 8 | include("project-api") 9 | include("common") 10 | include("project-plugin") 11 | include("settings-plugin") 12 | include("tests") 13 | 14 | // The remaining part of this script is to deal with importing dependencies from studio-main workspace. 15 | // First, we need to piggyback on the prebuilts and the built android plugins in order to target testing 16 | // with the latest version of the AGP plugins. 17 | // Second, the tests are using integration-test framework as a dependency to use all the facilities 18 | // related to test project building and APK files exploration. 19 | // Therefore, one must define the "com.android.workspace.location" property to point to the location of the 20 | // studio-main workspace. 21 | val agpWorkspaceLocationProperty = providers.gradleProperty("com.android.workspace.location") 22 | val agpWorkspaceLocation = if (agpWorkspaceLocationProperty.isPresent) { 23 | agpWorkspaceLocationProperty.get() 24 | } else { 25 | System.getenv("AGP_WORKSPACE_LOCATION") ?: 26 | throw java.lang.IllegalArgumentException( 27 | "com.android.workspace.location Gradle property must point to your checked out studio-main workspace" 28 | ) 29 | } 30 | 31 | // Provide the maven repositories to find all binaries and built dependencies. 32 | if (System.getenv("CUSTOM_REPO") != null) { 33 | System.getenv("CUSTOM_REPO").split(File.pathSeparatorChar).forEach { repo -> 34 | println("Using maven repository : $repo") 35 | dependencyResolutionManagement.repositories { 36 | maven { url = java.net.URI("file://$repo") } 37 | } 38 | } 39 | } else { 40 | // no environment variable, revert to hardcoded versions from the workspace location 41 | dependencyResolutionManagement.repositories { 42 | maven { url = java.net.URI("file://$agpWorkspaceLocation/out/repo") } 43 | maven { url = java.net.URI("file://$agpWorkspaceLocation/prebuilts/tools/common/m2/repository") } 44 | } 45 | } 46 | 47 | // any tests module automatically on published plugins 48 | settings.gradle.beforeProject { 49 | extra.apply { 50 | set("agpWorkspace", agpWorkspaceLocation) 51 | } 52 | if (path.contains("tests")) { 53 | this.afterEvaluate { 54 | this.getTasksByName("test", false).forEach { 55 | it.dependsOn(":settings-api:publish", ":settings-plugin:publish", ":project-api:publish", ":project-plugin:publish") 56 | } 57 | } 58 | } 59 | } 60 | 61 | // Include the studio-main build to retrieve the integration-test framework dependency that 'tests' project is using. 62 | includeBuild("$agpWorkspaceLocation/tools") { 63 | dependencySubstitution { 64 | substitute(module("com.android.build.integration-test:framework")).using(project(":base:build-system:integration-test:framework")) 65 | substitute(module("com.android.tools.build.declarative:model")).using(project(":base:declarative-gradle:model")) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.gradle.kotlin.dsl.extra 2 | 3 | /* 4 | * Copyright (C) 2023 The Android Open Source Project 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | plugins { 20 | alias(libs.plugins.kotlin.jvm) 21 | } 22 | 23 | val agpWorkspace = extra.get("agpWorkspace") 24 | 25 | tasks.test { 26 | this.environment("CUSTOM_REPO", System.getenv("CUSTOM_REPO") + File.pathSeparatorChar + "${project.rootDir}/out/repo") 27 | this.environment("TEST_TMPDIR", project.buildDir) 28 | this.environment("TEST_ROOTDIR", project.rootProject.projectDir) 29 | this.environment("AGP_WORKSPACE_LOCATION", agpWorkspace!!) 30 | this.environment("PLUGIN_VERSION", Constants.PLUGIN_VERSION) 31 | } 32 | 33 | dependencies { 34 | testImplementation(gradleApi()) 35 | testImplementation(libs.tomlj) 36 | testImplementation(libs.junit) 37 | testImplementation(libs.truth) 38 | testImplementation(libs.testutils) 39 | testImplementation(libs.testFramework) 40 | testImplementation(libs.toolsCommon) 41 | testImplementation(libs.agpApi) 42 | } 43 | -------------------------------------------------------------------------------- /tests/src/test-projects/nestedBuildScriptSingleLibApplication/app/build.gradle.toml: -------------------------------------------------------------------------------- 1 | includeBuildFiles = ["../toml/paid.toml", "../toml/demo.toml"] 2 | 3 | [[plugins]] 4 | id = "com.android.application" 5 | 6 | [android] 7 | compileSdk = 33 8 | namespace = "com.example.app" 9 | 10 | [android.defaultConfig] 11 | minSdk = 21 12 | 13 | [dependencies] 14 | implementation = [ 15 | { project = ":lib" } 16 | ] -------------------------------------------------------------------------------- /tests/src/test-projects/nestedBuildScriptSingleLibApplication/app/src/androidTest/java/com/example/app/HelloWorldTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 com.example.app; 18 | 19 | import android.support.test.filters.MediumTest; 20 | import android.support.test.rule.ActivityTestRule; 21 | import android.support.test.runner.AndroidJUnit4; 22 | import android.widget.TextView; 23 | import org.junit.Assert; 24 | import org.junit.Before; 25 | import org.junit.Rule; 26 | import org.junit.Test; 27 | import org.junit.runner.RunWith; 28 | 29 | @RunWith(AndroidJUnit4.class) 30 | public class HelloWorldTest { 31 | @Rule public ActivityTestRule rule = new ActivityTestRule<>(HelloWorld.class); 32 | private TextView mTextView; 33 | 34 | @Before 35 | public void setUp() throws Exception { 36 | final HelloWorld a = rule.getActivity(); 37 | // ensure a valid handle to the activity has been returned 38 | Assert.assertNotNull(a); 39 | mTextView = (TextView) a.findViewById(R.id.text); 40 | 41 | } 42 | 43 | @Test 44 | @MediumTest 45 | public void testPreconditions() { 46 | Assert.assertNotNull(mTextView); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/src/test-projects/nestedBuildScriptSingleLibApplication/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /tests/src/test-projects/nestedBuildScriptSingleLibApplication/app/src/main/java/com/example/app/HelloWorld.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 com.example.app; 18 | 19 | import android.app.Activity; 20 | import android.os.Bundle; 21 | import com.example.helloworld.Lib1Utils; 22 | 23 | import java.util.logging.Logger; 24 | import java.util.logging.Level; 25 | 26 | public class HelloWorld extends Activity { 27 | /** Called when the activity is first created. */ 28 | @Override 29 | public void onCreate(Bundle savedInstanceState) { 30 | 31 | super.onCreate(savedInstanceState); 32 | Logger.getLogger("app").log(Level.INFO, new Lib1Utils().someUtility()); 33 | setContentView(R.layout.main); 34 | // onCreate 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/src/test-projects/nestedBuildScriptSingleLibApplication/app/src/main/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 22 | 28 | 29 | -------------------------------------------------------------------------------- /tests/src/test-projects/nestedBuildScriptSingleLibApplication/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | HelloWorld 19 | 20 | -------------------------------------------------------------------------------- /tests/src/test-projects/nestedBuildScriptSingleLibApplication/lib/build.gradle.toml: -------------------------------------------------------------------------------- 1 | includeBuildFiles = ["../toml/common.toml"] 2 | 3 | [[plugins]] 4 | id = "com.android.library" 5 | 6 | [android] 7 | namespace = "com.example.helloworld" 8 | 9 | [android.defaultConfig] 10 | minSdk = 21 -------------------------------------------------------------------------------- /tests/src/test-projects/nestedBuildScriptSingleLibApplication/lib/src/androidTest/java/com/example/helloworld/HelloWorldTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 com.example.helloworld; 18 | 19 | import android.support.test.filters.MediumTest; 20 | import android.support.test.rule.ActivityTestRule; 21 | import android.support.test.runner.AndroidJUnit4; 22 | import android.widget.TextView; 23 | import org.junit.Assert; 24 | import org.junit.Before; 25 | import org.junit.Rule; 26 | import org.junit.Test; 27 | import org.junit.runner.RunWith; 28 | 29 | @RunWith(AndroidJUnit4.class) 30 | public class HelloWorldTest { 31 | @Rule public ActivityTestRule rule = new ActivityTestRule<>(HelloWorld.class); 32 | private TextView mTextView; 33 | 34 | @Before 35 | public void setUp() throws Exception { 36 | final HelloWorld a = rule.getActivity(); 37 | // ensure a valid handle to the activity has been returned 38 | Assert.assertNotNull(a); 39 | mTextView = (TextView) a.findViewById(R.id.text); 40 | 41 | } 42 | 43 | @Test 44 | @MediumTest 45 | public void testPreconditions() { 46 | Assert.assertNotNull(mTextView); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/src/test-projects/nestedBuildScriptSingleLibApplication/lib/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /tests/src/test-projects/nestedBuildScriptSingleLibApplication/lib/src/main/java/com/example/helloworld/HelloWorld.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 com.example.helloworld; 18 | 19 | import android.app.Activity; 20 | import android.os.Bundle; 21 | 22 | public class HelloWorld extends Activity { 23 | /** Called when the activity is first created. */ 24 | @Override 25 | public void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | setContentView(R.layout.main); 28 | // onCreate 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/src/test-projects/nestedBuildScriptSingleLibApplication/lib/src/main/java/com/example/helloworld/Lib1Utils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 com.example.helloworld; 18 | 19 | public class Lib1Utils { 20 | public String someUtility() { 21 | return "Lib1" + System.currentTimeMillis(); 22 | } 23 | } -------------------------------------------------------------------------------- /tests/src/test-projects/nestedBuildScriptSingleLibApplication/lib/src/main/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 22 | 28 | 29 | -------------------------------------------------------------------------------- /tests/src/test-projects/nestedBuildScriptSingleLibApplication/lib/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | HelloWorld 19 | 20 | -------------------------------------------------------------------------------- /tests/src/test-projects/nestedBuildScriptSingleLibApplication/settings.gradle.toml: -------------------------------------------------------------------------------- 1 | include = [ ":app", ":lib" ] 2 | 3 | [[plugins]] 4 | module = "com.android.tools.build:gradle" 5 | version = "8.3.0-dev" 6 | 7 | -------------------------------------------------------------------------------- /tests/src/test-projects/nestedBuildScriptSingleLibApplication/toml/common.toml: -------------------------------------------------------------------------------- 1 | [android] 2 | compileSdk = 33 3 | 4 | [android.defaultConfig] 5 | minSdk = 21 6 | -------------------------------------------------------------------------------- /tests/src/test-projects/nestedBuildScriptSingleLibApplication/toml/demo.toml: -------------------------------------------------------------------------------- 1 | [android.productFlavors.demo] 2 | dimension="version" 3 | applicationIdSuffix=".demo" 4 | versionNameSuffix="-demo" 5 | 6 | [android.defaultConfig] 7 | minSdk = 21 8 | -------------------------------------------------------------------------------- /tests/src/test-projects/nestedBuildScriptSingleLibApplication/toml/paid.toml: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | flavorDimensions = [ "version" ] 4 | compileSdk = 31 5 | 6 | [android.productFlavors.paid] 7 | dimension="version" 8 | applicationIdSuffix=".paid" 9 | versionNameSuffix="-paid" 10 | 11 | [android.defaultConfig] 12 | minSdk = 19 13 | -------------------------------------------------------------------------------- /tests/src/test-projects/simpleApplication/app/build.gradle.toml: -------------------------------------------------------------------------------- 1 | [[plugins]] 2 | id = "com.android.application" 3 | 4 | [android] 5 | compileSdk = 33 6 | namespace = "com.example.app" 7 | 8 | [android.defaultConfig] 9 | minSdk = 21 10 | 11 | [androidComponents.beforeVariants.release] 12 | enable = false -------------------------------------------------------------------------------- /tests/src/test-projects/simpleApplication/app/src/androidTest/java/com/example/app/HelloWorldTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 com.example.app; 18 | 19 | import android.support.test.filters.MediumTest; 20 | import android.support.test.rule.ActivityTestRule; 21 | import android.support.test.runner.AndroidJUnit4; 22 | import android.widget.TextView; 23 | import org.junit.Assert; 24 | import org.junit.Before; 25 | import org.junit.Rule; 26 | import org.junit.Test; 27 | import org.junit.runner.RunWith; 28 | 29 | @RunWith(AndroidJUnit4.class) 30 | public class HelloWorldTest { 31 | @Rule public ActivityTestRule rule = new ActivityTestRule<>(HelloWorld.class); 32 | private TextView mTextView; 33 | 34 | @Before 35 | public void setUp() throws Exception { 36 | final HelloWorld a = rule.getActivity(); 37 | // ensure a valid handle to the activity has been returned 38 | Assert.assertNotNull(a); 39 | mTextView = (TextView) a.findViewById(R.id.text); 40 | 41 | } 42 | 43 | @Test 44 | @MediumTest 45 | public void testPreconditions() { 46 | Assert.assertNotNull(mTextView); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/src/test-projects/simpleApplication/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /tests/src/test-projects/simpleApplication/app/src/main/java/com/example/app/HelloWorld.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 com.example.app; 18 | 19 | import android.app.Activity; 20 | import android.os.Bundle; 21 | 22 | public class HelloWorld extends Activity { 23 | /** Called when the activity is first created. */ 24 | @Override 25 | public void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | setContentView(R.layout.main); 28 | // onCreate 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/src/test-projects/simpleApplication/app/src/main/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 22 | 28 | 29 | -------------------------------------------------------------------------------- /tests/src/test-projects/simpleApplication/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | HelloWorld 19 | 20 | -------------------------------------------------------------------------------- /tests/src/test-projects/simpleApplication/settings.gradle.toml: -------------------------------------------------------------------------------- 1 | [[pluginsManagement.repositories.inheritSettings]] 2 | 3 | [[plugins]] 4 | id = "com.android.application" 5 | module = "com.android.tools.build:gradle" 6 | version = "8.3.0-dev" 7 | 8 | [include] 9 | app = ":app" -------------------------------------------------------------------------------- /tests/src/test-projects/singleLibApplication/app/build.gradle.toml: -------------------------------------------------------------------------------- 1 | [[plugins]] 2 | id = "com.android.application" 3 | 4 | [android] 5 | compileSdk = 33 6 | namespace = "com.example.app" 7 | 8 | [android.defaultConfig] 9 | minSdk = 21 10 | 11 | [dependencies] 12 | implementation = [ 13 | { project = ":lib" } 14 | ] -------------------------------------------------------------------------------- /tests/src/test-projects/singleLibApplication/app/src/androidTest/java/com/example/app/HelloWorldTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 com.example.app; 18 | 19 | import android.support.test.filters.MediumTest; 20 | import android.support.test.rule.ActivityTestRule; 21 | import android.support.test.runner.AndroidJUnit4; 22 | import android.widget.TextView; 23 | import org.junit.Assert; 24 | import org.junit.Before; 25 | import org.junit.Rule; 26 | import org.junit.Test; 27 | import org.junit.runner.RunWith; 28 | 29 | @RunWith(AndroidJUnit4.class) 30 | public class HelloWorldTest { 31 | @Rule public ActivityTestRule rule = new ActivityTestRule<>(HelloWorld.class); 32 | private TextView mTextView; 33 | 34 | @Before 35 | public void setUp() throws Exception { 36 | final HelloWorld a = rule.getActivity(); 37 | // ensure a valid handle to the activity has been returned 38 | Assert.assertNotNull(a); 39 | mTextView = (TextView) a.findViewById(R.id.text); 40 | 41 | } 42 | 43 | @Test 44 | @MediumTest 45 | public void testPreconditions() { 46 | Assert.assertNotNull(mTextView); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/src/test-projects/singleLibApplication/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /tests/src/test-projects/singleLibApplication/app/src/main/java/com/example/app/HelloWorld.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 com.example.app; 18 | 19 | import android.app.Activity; 20 | import android.os.Bundle; 21 | import com.example.helloworld.Lib1Utils; 22 | 23 | import java.util.logging.Logger; 24 | import java.util.logging.Level; 25 | 26 | public class HelloWorld extends Activity { 27 | /** Called when the activity is first created. */ 28 | @Override 29 | public void onCreate(Bundle savedInstanceState) { 30 | 31 | super.onCreate(savedInstanceState); 32 | Logger.getLogger("app").log(Level.INFO, new Lib1Utils().someUtility()); 33 | setContentView(R.layout.main); 34 | // onCreate 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/src/test-projects/singleLibApplication/app/src/main/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 22 | 28 | 29 | -------------------------------------------------------------------------------- /tests/src/test-projects/singleLibApplication/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | HelloWorld 19 | 20 | -------------------------------------------------------------------------------- /tests/src/test-projects/singleLibApplication/lib/build.gradle.toml: -------------------------------------------------------------------------------- 1 | [[plugins]] 2 | id = "com.android.library" 3 | 4 | [android] 5 | compileSdk = 33 6 | namespace = "com.example.helloworld" 7 | 8 | [android.defaultConfig] 9 | minSdk = 21 -------------------------------------------------------------------------------- /tests/src/test-projects/singleLibApplication/lib/src/androidTest/java/com/example/helloworld/HelloWorldTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 com.example.helloworld; 18 | 19 | import android.support.test.filters.MediumTest; 20 | import android.support.test.rule.ActivityTestRule; 21 | import android.support.test.runner.AndroidJUnit4; 22 | import android.widget.TextView; 23 | import org.junit.Assert; 24 | import org.junit.Before; 25 | import org.junit.Rule; 26 | import org.junit.Test; 27 | import org.junit.runner.RunWith; 28 | 29 | @RunWith(AndroidJUnit4.class) 30 | public class HelloWorldTest { 31 | @Rule public ActivityTestRule rule = new ActivityTestRule<>(HelloWorld.class); 32 | private TextView mTextView; 33 | 34 | @Before 35 | public void setUp() throws Exception { 36 | final HelloWorld a = rule.getActivity(); 37 | // ensure a valid handle to the activity has been returned 38 | Assert.assertNotNull(a); 39 | mTextView = (TextView) a.findViewById(R.id.text); 40 | 41 | } 42 | 43 | @Test 44 | @MediumTest 45 | public void testPreconditions() { 46 | Assert.assertNotNull(mTextView); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/src/test-projects/singleLibApplication/lib/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /tests/src/test-projects/singleLibApplication/lib/src/main/java/com/example/helloworld/HelloWorld.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 com.example.helloworld; 18 | 19 | import android.app.Activity; 20 | import android.os.Bundle; 21 | 22 | public class HelloWorld extends Activity { 23 | /** Called when the activity is first created. */ 24 | @Override 25 | public void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | setContentView(R.layout.main); 28 | // onCreate 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/src/test-projects/singleLibApplication/lib/src/main/java/com/example/helloworld/Lib1Utils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 com.example.helloworld; 18 | 19 | public class Lib1Utils { 20 | public String someUtility() { 21 | return "Lib1" + System.currentTimeMillis(); 22 | } 23 | } -------------------------------------------------------------------------------- /tests/src/test-projects/singleLibApplication/lib/src/main/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 22 | 28 | 29 | -------------------------------------------------------------------------------- /tests/src/test-projects/singleLibApplication/lib/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | HelloWorld 19 | 20 | -------------------------------------------------------------------------------- /tests/src/test-projects/singleLibApplication/settings.gradle.toml: -------------------------------------------------------------------------------- 1 | include = [ ":app", ":lib" ] 2 | 3 | [pluginManagement.repositories] 4 | inheritSettings = true 5 | 6 | [[plugins]] 7 | module = "com.android.tools.build:gradle" 8 | version = "8.3.0-dev" 9 | 10 | -------------------------------------------------------------------------------- /tests/src/test/kotlin/com/android/declarative/infra/DeclarativeTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 | package com.android.declarative.infra 17 | 18 | import com.android.build.gradle.integration.common.fixture.GradleProject 19 | import java.io.File 20 | import java.nio.file.Path 21 | 22 | class DeclarativeTest( 23 | path: String? = null 24 | ): GradleProject(path) { 25 | 26 | val additionalMavenRepositories: List = listOf() 27 | 28 | override fun write(projectDir: File, buildScriptContent: String?, projectRepoScript: String) { 29 | for (sourceFile in getAllSourceFiles()) { 30 | sourceFile.writeToDir(projectDir) 31 | } 32 | generateSettingsFile(projectDir, projectRepoScript) 33 | } 34 | 35 | override fun containsFullBuildScript(): Boolean { 36 | TODO("Not yet implemented") 37 | } 38 | 39 | private fun generateSettingsFile(projectDir: File, projectRepoScript: String) { 40 | File(projectDir, "settings.gradle").writeText( 41 | """ 42 | pluginManagement { 43 | $projectRepoScript 44 | } 45 | 46 | plugins { 47 | id 'com.android.experiments.declarative.settings' version '0.0.1' 48 | } 49 | 50 | dependencyResolutionManagement { 51 | RepositoriesMode.PREFER_SETTINGS 52 | $projectRepoScript 53 | } 54 | """ 55 | ) 56 | } 57 | } -------------------------------------------------------------------------------- /tests/src/test/kotlin/com/android/declarative/infra/DeclarativeTestProject.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 | package com.android.declarative.infra 17 | 18 | import com.android.SdkConstants 19 | import com.android.build.gradle.integration.common.fixture.BaseGradleExecutor 20 | import com.android.build.gradle.integration.common.fixture.GradleBuildResult 21 | import com.android.build.gradle.integration.common.fixture.GradleTaskExecutor 22 | import com.android.build.gradle.integration.common.fixture.GradleTestProject 23 | import com.android.build.gradle.integration.common.fixture.GradleTestProjectBuilder 24 | import com.android.build.gradle.integration.common.fixture.GradleTestRule 25 | import com.android.build.gradle.integration.common.fixture.gradle_project.ProjectLocation 26 | import com.android.build.gradle.integration.common.fixture.gradle_project.initializeProjectLocation 27 | import com.android.build.gradle.integration.common.truth.forEachLine 28 | import com.android.testutils.TestUtils 29 | import com.android.utils.FileUtils 30 | import com.android.utils.combineAsCamelCase 31 | import com.google.common.truth.Truth 32 | import org.gradle.tooling.GradleConnector 33 | import org.gradle.tooling.ProjectConnection 34 | import org.gradle.tooling.internal.consumer.DefaultGradleConnector 35 | import org.junit.runner.Description 36 | import org.junit.runners.model.Statement 37 | import java.io.File 38 | import java.nio.file.Path 39 | import java.util.concurrent.TimeUnit 40 | 41 | class DeclarativeTestProject constructor( 42 | val name : String = GradleTestProject.DEFAULT_TEST_PROJECT_NAME, 43 | private val testProject: DeclarativeTest, 44 | private val gradleDistributionDirectory: File = 45 | TestUtils.resolveWorkspacePath("tools/external/gradle").toFile(), 46 | private var mutableProjectLocation: ProjectLocation? = null, 47 | override val androidSdkDir: File? = null, 48 | override val androidNdkSxSRootSymlink: File? = null, 49 | override val additionalMavenRepoDir: Path? = null, 50 | override val heapSize: GradleTestProjectBuilder.MemoryRequirement = GradleTestProjectBuilder.MemoryRequirement.useDefault(), 51 | override val withConfigurationCaching: BaseGradleExecutor.ConfigurationCaching = BaseGradleExecutor.ConfigurationCaching.PROJECT_ISOLATION, 52 | ) : GradleTestRule 53 | { 54 | /** Returns the latest build result. */ 55 | private var _buildResult: GradleBuildResult? = null 56 | val buildResult: GradleBuildResult 57 | get() = _buildResult ?: throw RuntimeException("No result available. Run Gradle first.") 58 | 59 | private val openConnections: MutableList = mutableListOf() 60 | 61 | private val projectConnection: ProjectConnection by lazy { 62 | 63 | val connector = GradleConnector.newConnector() 64 | (connector as DefaultGradleConnector) 65 | .daemonMaxIdleTime( 66 | GradleTestProject.GRADLE_DEAMON_IDLE_TIME_IN_SECONDS, 67 | TimeUnit.SECONDS 68 | ) 69 | 70 | connector 71 | .useGradleUserHomeDir(location.testLocation.gradleUserHome.toFile()) 72 | .forProjectDirectory(location.projectDir) 73 | 74 | 75 | val distributionName = String.format( 76 | "gradle-%s-bin.zip", 77 | GradleTestProject.GRADLE_TEST_VERSION 78 | ) 79 | val distributionZip = File(gradleDistributionDirectory, distributionName) 80 | Truth.assertThat(distributionZip.isFile).isTrue() 81 | 82 | connector.useDistribution(distributionZip.toURI()) 83 | 84 | connector.connect().also { connection -> 85 | openConnections.add(connection) 86 | } 87 | } 88 | 89 | /** 90 | * Create a GradleTestProject representing a subproject. 91 | * 92 | * @param name name of the subProject, or the subProject's gradle project path 93 | */ 94 | fun getSubproject(name: String): DeclarativeTestProject { 95 | return DeclarativeTestProject( 96 | name = name, 97 | testProject = DeclarativeTest(FileUtils.join(location.projectDir.path, name)), 98 | mutableProjectLocation = location.createSubProjectLocation(name) 99 | ) 100 | } 101 | 102 | override val location: ProjectLocation 103 | get() = mutableProjectLocation ?: error("Project location has not been initialized yet") 104 | 105 | override fun getProfileDirectory(): Path? = null 106 | 107 | override fun setLastBuildResult(lastBuildResult: GradleBuildResult) { 108 | this._buildResult = lastBuildResult 109 | } 110 | 111 | val projectDir: File 112 | get() = location.projectDir 113 | 114 | /** Return the output directory from Android plugins. */ 115 | val outputDir: File 116 | get() = FileUtils.join(projectDir, "build", SdkConstants.FD_OUTPUTS) 117 | 118 | /** Return the output directory from Android plugins. */ 119 | val intermediatesDir: File 120 | get() = FileUtils 121 | .join(projectDir, "build", SdkConstants.FD_INTERMEDIATES) 122 | 123 | fun getMergedManifestFile(vararg dimensions: String) : File { 124 | return FileUtils.join(intermediatesDir, "merged_manifest", dimensions.toList().combineAsCamelCase(), SdkConstants.FN_ANDROID_MANIFEST_XML) 125 | } 126 | 127 | fun getApkLocation(vararg dimensions: String) : File { 128 | return FileUtils.join(outputDir, "apk", *dimensions) 129 | } 130 | 131 | fun getOutputMetadataJson(vararg dimensions: String) : File { 132 | return FileUtils.join(getApkLocation(*dimensions), METADATA_FILE_NAME) 133 | } 134 | 135 | override fun apply(base: Statement, description: Description): Statement { 136 | return object: Statement() { 137 | override fun evaluate() { 138 | if (mutableProjectLocation == null) { 139 | mutableProjectLocation = initializeProjectLocation( 140 | description.testClass, 141 | description.methodName, 142 | name 143 | ) 144 | } 145 | populateTestDirectory() 146 | var testFailed = false 147 | try { 148 | base.evaluate() 149 | } catch (t: Throwable) { 150 | testFailed = true 151 | throw t 152 | } finally { 153 | openConnections.forEach(ProjectConnection::close) 154 | 155 | if (testFailed) { 156 | _buildResult?.let { 157 | System.err 158 | .println("==============================================") 159 | System.err 160 | .println("= Test $description failed. Last build:") 161 | System.err 162 | .println("==============================================") 163 | System.err 164 | .println("=================== Stderr ===================") 165 | // All output produced during build execution is written to the standard 166 | // output file handle since Gradle 4.7. This should be empty. 167 | it.stderr.forEachLine { System.err.println(it) } 168 | System.err 169 | .println("=================== Stdout ===================") 170 | it.stdout.forEachLine { System.err.println(it) } 171 | System.err 172 | .println("==============================================") 173 | System.err 174 | .println("=============== End last build ===============") 175 | System.err 176 | .println("==============================================") 177 | } 178 | } 179 | } 180 | 181 | } 182 | 183 | } 184 | } 185 | 186 | fun executor(): GradleTaskExecutor { 187 | return GradleTaskExecutor(this, projectConnection) 188 | } 189 | 190 | private fun getRepoDirectories(): List { 191 | val builder = mutableListOf() 192 | builder.addAll(GradleTestProject.localRepositories) 193 | testProject.additionalMavenRepositories.forEach(builder::add) 194 | return builder.toList() 195 | } 196 | 197 | private fun generateRepoScript(): String = 198 | StringBuilder().also {builder -> 199 | builder.append("repositories {\n") 200 | getRepoDirectories().forEach { builder.append(GradleTestProject.mavenSnippet(it)) } 201 | builder.append("}\n") 202 | }.toString() 203 | 204 | fun populateTestDirectory() { 205 | val projectDir = projectDir 206 | projectDir.deleteRecursively() 207 | projectDir.mkdirs() 208 | testProject.write( 209 | projectDir, 210 | null, 211 | generateRepoScript() 212 | ) 213 | } 214 | companion object { 215 | private const val METADATA_FILE_NAME = "output-metadata.json" 216 | } 217 | } -------------------------------------------------------------------------------- /tests/src/test/kotlin/com/android/declarative/infra/DeclarativeTestProjectBuilder.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 | package com.android.declarative.infra 17 | 18 | import com.android.build.gradle.integration.common.fixture.GradleTestProject 19 | import com.android.build.gradle.integration.common.fixture.app.TestSourceFile 20 | import com.android.build.gradle.integration.common.utils.TestFileUtils 21 | import com.google.common.io.Files 22 | import java.io.File 23 | import java.io.IOException 24 | import java.io.UncheckedIOException 25 | import java.nio.file.Path 26 | import kotlin.io.path.name 27 | import kotlin.io.path.toPath 28 | 29 | class DeclarativeTestProjectBuilder { 30 | 31 | fun getTestProjectsDir(): Path { 32 | val thisClass = this.javaClass.classLoader.getResource( 33 | "${this.javaClass.name.replace(".", "/")}.class") 34 | ?: throw RuntimeException("Cannot locate test directories") 35 | // now go up until I find the project root folder 36 | var path : Path? = thisClass.toURI().toPath() 37 | while(path != null && path.name != "gradle-declarative") { 38 | path = path.parent 39 | } 40 | return path?.resolve("tests/src/test-projects") 41 | ?: throw RuntimeException("project not located in declarative-gradle") 42 | } 43 | 44 | private var testProject: DeclarativeTest? = null 45 | 46 | private fun fromTestApp(testProject: DeclarativeTest): DeclarativeTestProjectBuilder { 47 | this.testProject = testProject 48 | return this 49 | } 50 | 51 | fun create(): DeclarativeTestProject { 52 | return DeclarativeTestProject( 53 | name = GradleTestProject.DEFAULT_TEST_PROJECT_NAME, 54 | this.testProject!!, 55 | ) 56 | } 57 | 58 | companion object { 59 | fun createTestProject(location: File): DeclarativeTestProjectBuilder { 60 | val testProject = DeclarativeTest(location.absolutePath) 61 | try { 62 | for (filePath in TestFileUtils.listFiles(location.toPath())) { 63 | testProject.addFile( 64 | TestSourceFile( 65 | filePath!!, Files.toByteArray(File(location, filePath)) 66 | ) 67 | ) 68 | } 69 | } catch (e: IOException) { 70 | throw UncheckedIOException(e) 71 | } 72 | return DeclarativeTestProjectBuilder().fromTestApp(testProject) 73 | } 74 | } 75 | 76 | } -------------------------------------------------------------------------------- /tests/src/test/kotlin/com/android/declarative/tests/DeclarativeTest.kt: -------------------------------------------------------------------------------- 1 | package com.android.declarative.tests 2 | 3 | import com.google.common.truth.Truth 4 | import java.io.File 5 | import java.nio.file.Files 6 | 7 | /** 8 | * Adds a gradle wrapper declaration, using the one located under 9 | * TEST_ROOTDIR environment variable. 10 | */ 11 | fun addGradleWrapper(testRootFolder: File) { 12 | File(testRootFolder, "gradle").also { gradle -> 13 | gradle.maybeCreateFolder() 14 | File(gradle, "wrapper").also { wrapper -> 15 | wrapper.maybeCreateFolder() 16 | val origin = File(System.getenv("TEST_ROOTDIR"), "gradle/wrapper") 17 | File(origin, "gradle-wrapper.jar").maybeCopy(wrapper) 18 | File(origin, "gradle-wrapper.properties").maybeCopy(wrapper) 19 | } 20 | } 21 | 22 | } 23 | 24 | private fun File.maybeCreateFolder() { 25 | if (exists()) return 26 | Truth.assertThat(mkdirs()).isTrue() 27 | } 28 | 29 | private fun File.maybeCopy(destination: File) { 30 | File(destination, name).let { destinationFile -> 31 | if (destinationFile.exists()) return 32 | Files.copy(toPath(), destinationFile.toPath()) 33 | } 34 | } -------------------------------------------------------------------------------- /tests/src/test/kotlin/com/android/declarative/tests/JavaLibraryDeclarativeTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 com.android.declarative.tests 18 | 19 | import com.android.build.gradle.integration.common.fixture.GradleProject 20 | import com.android.build.gradle.integration.common.fixture.GradleTestProject 21 | import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp 22 | import com.android.build.gradle.integration.common.fixture.app.MultiModuleTestProject 23 | import org.junit.Rule 24 | import org.junit.Test 25 | import java.io.File 26 | 27 | class JavaLibraryDeclarativeTest { 28 | 29 | @Rule 30 | @JvmField 31 | val project: GradleTestProject = GradleTestProject.builder() 32 | .withExtraPluginClasspath("com.android.experiments.declarative:project-api:${System.getenv("PLUGIN_VERSION")}") 33 | .fromTestApp( 34 | MultiModuleTestProject( 35 | mapOf( 36 | ":app" to HelloWorldApp.noBuildFile("com.example.app"), 37 | ":javaLib" to javaLib 38 | ) 39 | ) 40 | ).create() 41 | 42 | companion object { 43 | val javaLib = object : GradleProject() { 44 | override fun containsFullBuildScript(): Boolean { 45 | return false 46 | } 47 | } 48 | 49 | fun initJavaLib(project: GradleTestProject, includeDeclarativePlugin: Boolean = true) { 50 | val lib = project.getSubproject(":javaLib") 51 | File(lib.projectDir, "build.gradle.toml").writeText(""" 52 | [[plugins]] 53 | id = "java" 54 | 55 | [java.sourceCompatibility] 56 | valueOf = "VERSION_17" 57 | 58 | [java.targetCompatibility] 59 | valueOf = "VERSION_17" 60 | 61 | """.trimIndent()) 62 | if (includeDeclarativePlugin) { 63 | lib.buildFile.writeText( 64 | """ 65 | apply plugin: 'com.android.experiments.declarative.project' 66 | """.trimIndent() 67 | ) 68 | } 69 | } 70 | } 71 | 72 | @Test 73 | fun testLoadingFromDeclarativeForm() { 74 | val app: GradleTestProject = project.getSubproject(":app") 75 | File(app.projectDir, "build.gradle.toml").writeText(""" 76 | [[plugins]] 77 | id = "com.android.application" 78 | 79 | [android] 80 | compileSdk = 33 81 | namespace = "com.example.app" 82 | 83 | [android.defaultConfig] 84 | minSdk = 21 85 | [android.aaptOptions] 86 | noCompress = ["2"] 87 | 88 | [dependencies] 89 | implementation = [ 90 | { project = ":javaLib" }, 91 | ] 92 | """.trimIndent()) 93 | app.buildFile.writeText( 94 | """ 95 | apply plugin: 'com.android.experiments.declarative.project' 96 | """.trimIndent() 97 | ) 98 | 99 | initJavaLib(project) 100 | 101 | project.executor().run("assembleDebug") 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /tests/src/test/kotlin/com/android/declarative/tests/LargeDeclarativeTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 com.android.declarative.tests 18 | 19 | import com.android.build.gradle.integration.common.fixture.GradleProject 20 | import com.android.build.gradle.integration.common.fixture.GradleTestProject 21 | import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp 22 | import com.android.build.gradle.integration.common.fixture.app.MultiModuleTestProject 23 | import org.junit.Rule 24 | import org.junit.Test 25 | import java.io.File 26 | import java.util.* 27 | 28 | class LargeDeclarativeTest { 29 | 30 | val nbOfApplications = 10 31 | val nbOfAndroidLibs = 50 32 | val nbOfJavaLibs = 100 33 | 34 | val nbOfAndroidLibsDependenciesPerApp: Int = nbOfApplications / 3 35 | val nbOfJavaLibsDependenciesPerApp: Int = nbOfAndroidLibs / 10 36 | val nbOfJavaLibsDependenciesPerAndroidLib: Int = nbOfJavaLibs / 40 37 | 38 | private val random = Random(1) 39 | 40 | @Rule 41 | @JvmField 42 | val project: GradleTestProject = GradleTestProject.builder() 43 | .withHeap("4G") 44 | .withExtraPluginClasspath("com.android.experiments.declarative:settings-api:${System.getenv("PLUGIN_VERSION")}") 45 | .fromTestApp( 46 | MultiModuleTestProject( 47 | mutableMapOf().also { projects -> 48 | repeat(nbOfApplications) { appIndex -> 49 | projects[":app$appIndex"] = HelloWorldApp.noBuildFile("com.example.app$appIndex") 50 | } 51 | repeat(nbOfAndroidLibs) {androidLibIndex -> 52 | projects[":lib$androidLibIndex"] = HelloWorldApp.noBuildFile("com.example.lib$androidLibIndex") 53 | } 54 | repeat(nbOfJavaLibs) { javaLibIndex -> 55 | projects[":javaLib$javaLibIndex"] = object : GradleProject() { 56 | override fun containsFullBuildScript(): Boolean { 57 | return false 58 | } 59 | } 60 | } 61 | }.toMap() 62 | ) 63 | ).create() 64 | 65 | @Test 66 | fun testLoadingFromDeclarativeForm() { 67 | repeat(nbOfApplications) { appIndex -> 68 | val app: GradleTestProject = project.getSubproject(":app$appIndex") 69 | File(app.projectDir, "build.gradle.toml").writeText( 70 | StringBuilder().also { builder -> 71 | builder.append( 72 | """ 73 | [[plugins]] 74 | id = "com.android.application" 75 | 76 | [android] 77 | compileSdk = 33 78 | namespace = "com.example.app$appIndex" 79 | 80 | [android.defaultConfig] 81 | minSdk = 21 82 | 83 | [dependencies] 84 | implementation = [ 85 | 86 | """.trimIndent() 87 | ) 88 | val alreadyAllocated = mutableListOf() 89 | repeat(nbOfAndroidLibsDependenciesPerApp) { _ -> 90 | var nextIndex = random.nextInt(nbOfAndroidLibs) 91 | while (alreadyAllocated.contains(nextIndex)) { 92 | nextIndex = random.nextInt(nbOfAndroidLibs) 93 | } 94 | alreadyAllocated.add(nextIndex) 95 | builder.append(" { project = \":lib$nextIndex\" },\n") 96 | } 97 | alreadyAllocated.clear() 98 | repeat(nbOfJavaLibsDependenciesPerApp) { _ -> 99 | var nextIndex = random.nextInt(nbOfJavaLibs) 100 | while (alreadyAllocated.contains(nextIndex)) { 101 | nextIndex = random.nextInt(nbOfJavaLibs) 102 | } 103 | alreadyAllocated.add(nextIndex) 104 | builder.append(" { project = \":javaLib$nextIndex\" },\n") 105 | } 106 | builder.append("]\n") 107 | }.toString() 108 | ) 109 | app.buildFile.delete() 110 | } 111 | repeat(nbOfAndroidLibs) { androidLibIndex -> 112 | val androidLib: GradleTestProject = project.getSubproject(":lib$androidLibIndex") 113 | File(androidLib.projectDir, "build.gradle.toml").writeText( 114 | StringBuilder().also {builder -> 115 | builder.append( 116 | """ 117 | [[plugins]] 118 | id = "com.android.library" 119 | 120 | [android] 121 | compileSdk = 33 122 | namespace = "com.example.lib$androidLibIndex" 123 | 124 | [android.defaultConfig] 125 | minSdk = 21 126 | 127 | [dependencies] 128 | implementation = [ 129 | """.trimIndent()) 130 | val alreadyAllocated = mutableListOf() 131 | repeat(nbOfJavaLibsDependenciesPerAndroidLib) { _ -> 132 | var nextIndex = random.nextInt(nbOfJavaLibs) 133 | while (alreadyAllocated.contains(nextIndex)) { 134 | nextIndex = random.nextInt(nbOfJavaLibs) 135 | } 136 | alreadyAllocated.add(nextIndex) 137 | builder.append(" { project = \":javaLib$nextIndex\" },\n") 138 | } 139 | builder.append("]\n") 140 | }.toString() 141 | ) 142 | androidLib.buildFile.delete() 143 | } 144 | repeat(nbOfJavaLibs) { javaLibIndex -> 145 | initJavaLib( 146 | javaLibIndex = javaLibIndex, 147 | project = project, 148 | includeDeclarativePlugin = false 149 | ) 150 | } 151 | project.settingsFile.writeText(StringBuilder().also { 152 | it.append( 153 | """ 154 | pluginManagement { 155 | repositories { 156 | """.trimIndent() 157 | ) 158 | 159 | System.getenv("CUSTOM_REPO").split(File.pathSeparatorChar).forEach { repository -> 160 | it.append(" maven { url '$repository'}\n") 161 | } 162 | it.append( 163 | """ 164 | } 165 | } 166 | plugins { 167 | id 'com.android.experiments.declarative.settings' version '${System.getenv("PLUGIN_VERSION")}' 168 | } 169 | dependencyResolutionManagement { 170 | RepositoriesMode.PREFER_SETTINGS 171 | repositories { 172 | """.trimIndent()) 173 | System.getenv("CUSTOM_REPO").split(File.pathSeparatorChar).forEach { repository -> 174 | it.append(" maven { url '$repository'}\n") 175 | } 176 | it.append( 177 | """ 178 | } 179 | } 180 | """.trimIndent()) 181 | }.toString()) 182 | 183 | File(project.projectDir, "settings.gradle.toml").writeText( 184 | StringBuilder().also { builder -> 185 | builder.append(""" 186 | [[plugins]] 187 | id = "com.android.application" 188 | module = "com.android.tools.build:gradle" 189 | version = "8.3.0-dev" 190 | 191 | [[plugins]] 192 | id = "com.android.library" 193 | module = "com.android.tools.build:gradle" 194 | version = "8.3.0-dev" 195 | 196 | [include] 197 | 198 | """.trimIndent()) 199 | 200 | repeat(nbOfApplications) { 201 | builder.append("app$it = \":app$it\"\n") 202 | } 203 | repeat(nbOfAndroidLibs) { 204 | builder.append("lib$it = \":lib$it\"\n") 205 | } 206 | repeat(nbOfJavaLibs) { 207 | builder.append("javaLib$it = \":javaLib$it\"\n") 208 | } 209 | }.toString() 210 | ) 211 | 212 | File(project.projectDir, "gradle.properties").writeText( 213 | """org.gradle.jvmargs=-Xmx6096m""" 214 | ) 215 | 216 | addGradleWrapper(project.projectDir) 217 | 218 | project.executor().run("assembleDebug") 219 | } 220 | 221 | private fun initJavaLib( 222 | javaLibIndex: Int, 223 | project: GradleTestProject, 224 | includeDeclarativePlugin: Boolean = true) { 225 | val lib = project.getSubproject(":javaLib$javaLibIndex") 226 | File(lib.projectDir, "build.gradle.toml").writeText(""" 227 | [[plugins]] 228 | id = "java" 229 | 230 | [java.sourceCompatibility] 231 | valueOf = "VERSION_17" 232 | 233 | [java.targetCompatibility] 234 | valueOf = "VERSION_17" 235 | 236 | """.trimIndent()) 237 | if (includeDeclarativePlugin) { 238 | lib.buildFile.writeText( 239 | """ 240 | apply plugin: 'com.android.experiments.declarative' 241 | """.trimIndent() 242 | ) 243 | } 244 | } 245 | } -------------------------------------------------------------------------------- /tests/src/test/kotlin/com/android/declarative/tests/SettingsDeclaredPluginTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 | package com.android.declarative.tests 17 | 18 | import com.android.build.gradle.integration.common.fixture.GradleProject 19 | import com.android.build.gradle.integration.common.fixture.GradleTestProject 20 | import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp 21 | import com.android.build.gradle.integration.common.fixture.app.MultiModuleTestProject 22 | import org.junit.Rule 23 | import org.junit.Test 24 | import java.io.File 25 | 26 | class SettingsDeclaredPluginTest { 27 | 28 | @Rule 29 | @JvmField 30 | val project: GradleTestProject = GradleTestProject.builder() 31 | .withExtraPluginClasspath("com.android.experiments.declarative:settings-api:${System.getenv("PLUGIN_VERSION")}") 32 | .fromTestApp( 33 | MultiModuleTestProject( 34 | mapOf( 35 | ":app" to HelloWorldApp.noBuildFile("com.example.app"), 36 | ":javaLib" to JavaLibraryDeclarativeTest.javaLib 37 | ) 38 | ) 39 | ).create() 40 | 41 | @Test 42 | fun testLoadingFromDeclarativeForm() { 43 | val app: GradleTestProject = project.getSubproject(":app") 44 | File(app.projectDir, "build.gradle.toml").writeText(""" 45 | [[plugins]] 46 | id = "com.android.application" 47 | 48 | [android] 49 | compileSdk = 33 50 | namespace = "com.example.app" 51 | 52 | [android.defaultConfig] 53 | minSdk = 21 54 | 55 | [dependencies] 56 | implementation = [ 57 | { project = ":javaLib" } 58 | ] 59 | """.trimIndent()) 60 | 61 | JavaLibraryDeclarativeTest.initJavaLib(project, false) 62 | 63 | project.settingsFile.writeText(StringBuilder().also { 64 | it.append( 65 | """ 66 | pluginManagement { 67 | repositories { 68 | """.trimIndent() 69 | ) 70 | 71 | System.getenv("CUSTOM_REPO").split(File.pathSeparatorChar).forEach { repository -> 72 | it.append(" maven { url '$repository'}\n") 73 | } 74 | it.append( 75 | """ 76 | } 77 | } 78 | plugins { 79 | id 'com.android.experiments.declarative.settings' version '${System.getenv("PLUGIN_VERSION")}' 80 | } 81 | """.trimIndent() 82 | ) 83 | }.toString()) 84 | 85 | File(project.projectDir, "settings.gradle.toml").writeText(""" 86 | [[plugins]] 87 | id = "com.android.application" 88 | module = "com.android.tools.build:gradle" 89 | version = "8.3.0-dev" 90 | 91 | [includes] 92 | app = ":app" 93 | javaLib = ":javaLib" 94 | 95 | """.trimIndent()) 96 | 97 | project.executor().run("tasks") 98 | } 99 | } -------------------------------------------------------------------------------- /tests/src/test/kotlin/com/android/declarative/tests/SingleLibraryDeclarativeTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 com.android.declarative.tests 18 | 19 | import com.android.build.gradle.integration.common.fixture.GradleTestProject 20 | import com.android.build.gradle.integration.common.fixture.app.HelloWorldLibraryApp 21 | import org.junit.Rule 22 | import org.junit.Test 23 | import java.io.File 24 | 25 | class SingleLibraryDeclarativeTest { 26 | 27 | @get:Rule 28 | val project: GradleTestProject = GradleTestProject.builder() 29 | .withExtraPluginClasspath("com.android.experiments.declarative:project-api:${System.getenv("PLUGIN_VERSION")}") 30 | .fromTestApp(HelloWorldLibraryApp.create()).create() 31 | 32 | @Test 33 | fun testLoadingFromDeclarativeForm() { 34 | val app: GradleTestProject = project.getSubproject(":app") 35 | File(app.projectDir, "build.gradle.toml").writeText(""" 36 | [[plugins]] 37 | id = "com.android.application" 38 | 39 | [android] 40 | compileSdk = 33 41 | namespace = "com.example.app" 42 | 43 | [android.defaultConfig] 44 | minSdk = 21 45 | 46 | [android.defaultConfig.ndk] 47 | ldLibs = ["315"] 48 | 49 | # nullable property 50 | [android.defaultConfig.signingConfig] 51 | storePassword = "333" 52 | storeType = "334" 53 | 54 | [android.buildTypes.debug] 55 | proguardFiles = ["/Users/amishar/git/gradle-declarative/tests/258/259/260"] 56 | 57 | [android.buildTypes.debug.shaders] 58 | glslcArgs = ["261"] 59 | 60 | # map type field 61 | [android.buildTypes.debug.shaders.scopedGlslcArgs] 62 | "262" = "263" 63 | 64 | # property with no backing field and accessor 65 | [android.dataBinding] 66 | addDefaultAdapters = true 67 | 68 | [android.buildTypes.debug.signingConfig] 69 | enableV1Signing = true 70 | enableV2Signing = true 71 | enableV3Signing = false 72 | enableV4Signing = true 73 | 74 | [android.buildTypes.debug.externalNativeBuild.ndkBuild] 75 | abiFilters = ["240"] 76 | 77 | [android.buildTypes.debug.externalNativeBuild.experimentalProperties] 78 | "238" = "239" 79 | 80 | [dependencies] 81 | implementation = [ 82 | { project = ":lib" } 83 | ] 84 | """.trimIndent()) 85 | app.buildFile.writeText( 86 | """ 87 | apply plugin: 'com.android.experiments.declarative.project' 88 | """.trimIndent() 89 | ) 90 | 91 | project.executor().run("assembleDebug") 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /tests/src/test/kotlin/com/android/declarative/tests/samples/NestedBuildScriptSingleLibraryApplication.kt: -------------------------------------------------------------------------------- 1 | package com.android.declarative.tests.samples 2 | 3 | import com.android.declarative.infra.DeclarativeTestProject 4 | import com.android.declarative.infra.DeclarativeTestProjectBuilder 5 | import com.android.testutils.truth.PathSubject.assertThat 6 | import org.junit.Rule 7 | import org.junit.Test 8 | import java.io.File 9 | 10 | class NestedBuildScriptSingleLibraryApplication { 11 | @Rule 12 | @JvmField 13 | val project: DeclarativeTestProject = DeclarativeTestProjectBuilder.createTestProject( 14 | File(DeclarativeTestProjectBuilder().getTestProjectsDir().toFile(), "nestedBuildScriptSingleLibApplication") 15 | ).create() 16 | 17 | @Test 18 | fun buildAll() { 19 | project.executor().run("assembleDebug") 20 | val appProject = project.getSubproject("app") 21 | // test that demo.toml build file is included and applied 22 | val demoManifest = appProject.getMergedManifestFile("demo", "debug") 23 | assertThat(appProject.getOutputMetadataJson("demo", "debug")) 24 | .contains(""""applicationId": "com.example.app.demo"""") 25 | 26 | // test that minSdkVersion defined in demo.toml build file is overridden 27 | assertThat(demoManifest).contains("""android:minSdkVersion="21"""") 28 | 29 | // test that paid.toml build file is included and applied 30 | val paidManifest = appProject.getMergedManifestFile("paid", "debug") 31 | assertThat(appProject.getOutputMetadataJson("paid", "debug")) 32 | .contains(""""applicationId": "com.example.app.paid"""") 33 | // test that minSdkVersion defined in paid.toml build file is overridden 34 | assertThat(paidManifest).contains("""android:minSdkVersion="21"""") 35 | 36 | 37 | // test that libProject is built successfully 38 | val libProject = project.getSubproject("lib") 39 | assertThat(libProject.getMergedManifestFile("debug")) 40 | .contains("""android:minSdkVersion="21"""") 41 | 42 | } 43 | } -------------------------------------------------------------------------------- /tests/src/test/kotlin/com/android/declarative/tests/samples/SimpleApplication.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 com.android.declarative.tests.samples 18 | 19 | import com.android.declarative.infra.DeclarativeTestProject 20 | import com.android.declarative.infra.DeclarativeTestProjectBuilder 21 | import org.junit.Rule 22 | import org.junit.Test 23 | import java.io.File 24 | 25 | class SimpleApplication { 26 | 27 | @Rule 28 | @JvmField 29 | val project: DeclarativeTestProject = DeclarativeTestProjectBuilder.createTestProject( 30 | File(DeclarativeTestProjectBuilder().getTestProjectsDir().toFile(), "simpleApplication") 31 | ).create() 32 | 33 | @Test 34 | fun buildAll() { 35 | project.executor().run("assembleDebug") 36 | } 37 | } -------------------------------------------------------------------------------- /tests/src/test/kotlin/com/android/declarative/tests/samples/SingleLibraryApplication.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 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 com.android.declarative.tests.samples 18 | 19 | import com.android.declarative.infra.DeclarativeTestProject 20 | import com.android.declarative.infra.DeclarativeTestProjectBuilder 21 | import org.junit.Rule 22 | import org.junit.Test 23 | import java.io.File 24 | 25 | class SingleLibraryApplication { 26 | @Rule 27 | @JvmField 28 | val project: DeclarativeTestProject = DeclarativeTestProjectBuilder.createTestProject( 29 | File(DeclarativeTestProjectBuilder().getTestProjectsDir().toFile(), "singleLibApplication") 30 | ).create() 31 | 32 | @Test 33 | fun buildAll() { 34 | project.executor().run("assembleDebug") 35 | } 36 | } --------------------------------------------------------------------------------