├── .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 | }
--------------------------------------------------------------------------------