diff --git a/BUILD b/BUILD index 4502f06c..1d78d1d5 100644 --- a/BUILD +++ b/BUILD @@ -157,3 +157,7 @@ recipe_test( recipe_test( name = "variantOutput", ) + +recipe_test( + name = "registerPreBuild", +) diff --git a/recipes/registerPreBuild/README.md b/recipes/registerPreBuild/README.md new file mode 100644 index 00000000..ebb119b4 --- /dev/null +++ b/recipes/registerPreBuild/README.md @@ -0,0 +1,32 @@ +# Register tasks to run before build using LifecycleTasks.registerPreBuild() + +This sample demonstrates how to use the [`LifecycleTasks.registerPreBuild()`](https://developer.android.com/reference/tools/gradle-api/current/com/android/build/api/variant/LifecycleTasks#registerPreBuild(kotlin.Array)) API +to register tasks to run before a build begins. + +| Module | Content | +|----------------------------|-------------------------------------------------------------| +| [build-logic](build-logic) | Contains the Project plugin that is the core of the recipe. | +| [app](app) | An Android application that has the plugin applied. | + +The [build-logic](build-logic) sub-project contains the [`CustomPlugin`](build-logic/plugins/src/main/kotlin/CustomPlugin.kt) +and [`PreBuildValidationTask`](build-logic/plugins/src/main/kotlin/PreBuildValidationTask.kt) classes. + +[`CustomPlugin`](build-logic/plugins/src/main/kotlin/CustomPlugin.kt) contains the creation of the task provider for the [`PreBuildValidationTask`](build-logic/plugins/src/main/kotlin/PreBuildValidationTask.kt). The API call +to register this task per-variant is below: + +``` +androidComponents.onVariants { variant -> + variant.lifecycleTasks.registerPreBuild(preBuildTaskProvider) +} +``` + +[`CustomPlugin`](build-logic/plugins/src/main/kotlin/CustomPlugin.kt) also registers the `ValidateTask` which verifies that the pre-build task ran. + +## To Run +To run the example, you can just do + +``` +./gradlew :app:build +``` + +and you should see the file created by the task in `app/build/preBuildOutput/gradle_version.txt`. diff --git a/recipes/registerPreBuild/app/build.gradle.kts b/recipes/registerPreBuild/app/build.gradle.kts new file mode 100644 index 00000000..b30b3444 --- /dev/null +++ b/recipes/registerPreBuild/app/build.gradle.kts @@ -0,0 +1,36 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) + id("android.recipes.register_pre_build") +} + +android { + namespace = "com.example.android.recipes.register_pre_build" + compileSdk = $COMPILE_SDK + defaultConfig { + minSdk = $MINIMUM_SDK + targetSdk = $COMPILE_SDK + } +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} diff --git a/recipes/registerPreBuild/app/src/main/AndroidManifest.xml b/recipes/registerPreBuild/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..8b7fed3d --- /dev/null +++ b/recipes/registerPreBuild/app/src/main/AndroidManifest.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/recipes/registerPreBuild/build-logic/gradle.properties b/recipes/registerPreBuild/build-logic/gradle.properties new file mode 100644 index 00000000..3dcf88f0 --- /dev/null +++ b/recipes/registerPreBuild/build-logic/gradle.properties @@ -0,0 +1,2 @@ +# Gradle properties are not passed to included builds https://github.com/gradle/gradle/issues/2534 +org.gradle.parallel=true diff --git a/recipes/registerPreBuild/build-logic/gradle/libs.versions.toml b/recipes/registerPreBuild/build-logic/gradle/libs.versions.toml new file mode 100644 index 00000000..d362ae08 --- /dev/null +++ b/recipes/registerPreBuild/build-logic/gradle/libs.versions.toml @@ -0,0 +1,9 @@ +[versions] +androidGradlePlugin = $AGP_VERSION +kotlin = $KOTLIN_VERSION + +[libraries] +android-gradlePlugin-api = { group = "com.android.tools.build", name = "gradle-api", version.ref = "androidGradlePlugin" } + +[plugins] +kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } diff --git a/recipes/registerPreBuild/build-logic/plugins/build.gradle.kts b/recipes/registerPreBuild/build-logic/plugins/build.gradle.kts new file mode 100644 index 00000000..6539205a --- /dev/null +++ b/recipes/registerPreBuild/build-logic/plugins/build.gradle.kts @@ -0,0 +1,40 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +plugins { + `java-gradle-plugin` + alias(libs.plugins.kotlin.jvm) +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +dependencies { + compileOnly(libs.android.gradlePlugin.api) + implementation(gradleKotlinDsl()) +} + +gradlePlugin { + plugins { + create("customPlugin") { + id = "android.recipes.register_pre_build" + implementationClass = "CustomPlugin" + } + } +} diff --git a/recipes/registerPreBuild/build-logic/plugins/src/main/kotlin/CustomPlugin.kt b/recipes/registerPreBuild/build-logic/plugins/src/main/kotlin/CustomPlugin.kt new file mode 100644 index 00000000..8632b11c --- /dev/null +++ b/recipes/registerPreBuild/build-logic/plugins/src/main/kotlin/CustomPlugin.kt @@ -0,0 +1,84 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import com.android.build.api.variant.AndroidComponentsExtension +import com.android.build.gradle.AppPlugin +import org.gradle.api.Plugin +import org.gradle.api.DefaultTask +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.Project +import org.gradle.api.tasks.InputDirectory +import org.gradle.api.tasks.TaskAction +import org.gradle.configurationcache.extensions.capitalized +import org.gradle.kotlin.dsl.register + +/** + * This custom plugin will register a callback that is applied to all variants. + */ +class CustomPlugin : Plugin { + override fun apply(project: Project) { + // Registers a callback on the application of the Android Application plugin. + // This allows the CustomPlugin to work whether it's applied before or after + // the Android Application plugin. + project.plugins.withType(AppPlugin::class.java) { + + // Create the provider for the pre-build task + val preBuildTaskProvider = project.tasks.register("preBuildValidation") { + gradleVersion.set(project.gradle.gradleVersion) + output.set(project.layout.buildDirectory.dir("preBuildOutput")) + } + + val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java) + + // Registers a callback to be called, when a new variant is configured + androidComponents.onVariants { variant -> + // Register the pre-build task with the LifecycleTask API + variant.lifecycleTasks.registerPreBuild(preBuildTaskProvider) + + // -- Verification -- + // the following is just to validate the recipe and is not actually part of the recipe itself + project.tasks.register("validate${variant.name.capitalized()}") { + // The input of the validation task should be the output of the pre-build task. + // The normal way to do this would be: + // input.set(preBuildTaskProvider.flatMap { it.output } + // However, doing this will force running the pre-build task when we want it to run + // automatically when build is invoked. + // So we set the input manually, and the validation task will have to be called + // separately (in a separate Gradle execution or Gradle will detect the + // lack of dependency between the 2 tasks and complain). + input.set(project.layout.buildDirectory.dir("preBuildOutput")) + } + } + } + } +} + +/** + * Validation task to verify the behavior of the recipe + */ +abstract class ValidateTask : DefaultTask() { + + @get:InputDirectory + abstract val input: DirectoryProperty + + @TaskAction + fun taskAction() { + val gradleVersionFile = input.get().file("gradle_version.txt").asFile + if (!gradleVersionFile.exists()) { + throw RuntimeException("Expected file missing: $gradleVersionFile") + } + } +} diff --git a/recipes/registerPreBuild/build-logic/plugins/src/main/kotlin/PreBuildValidationTask.kt b/recipes/registerPreBuild/build-logic/plugins/src/main/kotlin/PreBuildValidationTask.kt new file mode 100644 index 00000000..f080b8b3 --- /dev/null +++ b/recipes/registerPreBuild/build-logic/plugins/src/main/kotlin/PreBuildValidationTask.kt @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import org.gradle.api.DefaultTask +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.TaskAction +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.Property +import java.io.File + +/** + * This is the pre-build task that runs before the build executes. + * The task does a trivial validation of the Gradle version, and produces an output for verification in another task. + */ +abstract class PreBuildValidationTask : DefaultTask() { + + // In order for the task to be up-to-date when the inputs have not changed, + // the task must declare an output, even if it's not used. Tasks with no + // output are always run regardless of whether the inputs changed + @get:OutputDirectory + abstract val output: DirectoryProperty + + @get:Input + abstract val gradleVersion: Property + + @TaskAction + fun taskAction() { + // This is a trivial validation, but a more useful one could be used in this task + if (gradleVersion.get().isEmpty()) { + throw RuntimeException("The Gradle version must not be empty.") + } + + // The next part of this task is done only for verification purposes + // delete the previous content. This task does not support incremental mode but could be modified to do so + val outputFile = output.get().asFile + outputFile.deleteRecursively() + outputFile.mkdirs() + + val newFile = File(outputFile, "gradle_version.txt") + newFile.writeText(gradleVersion.get()) + } +} diff --git a/recipes/registerPreBuild/build-logic/settings.gradle.kts b/recipes/registerPreBuild/build-logic/settings.gradle.kts new file mode 100644 index 00000000..e2e5e9e5 --- /dev/null +++ b/recipes/registerPreBuild/build-logic/settings.gradle.kts @@ -0,0 +1,33 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +rootProject.name = "build-logic" + +pluginManagement { + repositories { + $AGP_REPOSITORY + $PLUGIN_REPOSITORIES + } +} + +dependencyResolutionManagement { + repositories { + $AGP_REPOSITORY + $DEPENDENCY_REPOSITORIES + } +} + +include(":plugins") diff --git a/recipes/registerPreBuild/build.gradle.kts b/recipes/registerPreBuild/build.gradle.kts new file mode 100644 index 00000000..68e631f2 --- /dev/null +++ b/recipes/registerPreBuild/build.gradle.kts @@ -0,0 +1,20 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +plugins { + alias(libs.plugins.android.application) apply false + alias(libs.plugins.kotlin.android) apply false +} diff --git a/recipes/registerPreBuild/gradle.properties b/recipes/registerPreBuild/gradle.properties new file mode 100644 index 00000000..55cce922 --- /dev/null +++ b/recipes/registerPreBuild/gradle.properties @@ -0,0 +1,23 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true diff --git a/recipes/registerPreBuild/gradle/libs.versions.toml b/recipes/registerPreBuild/gradle/libs.versions.toml new file mode 100644 index 00000000..8c672bba --- /dev/null +++ b/recipes/registerPreBuild/gradle/libs.versions.toml @@ -0,0 +1,9 @@ +[versions] +androidGradlePlugin = $AGP_VERSION +kotlin = $KOTLIN_VERSION + +[plugins] +android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } + + diff --git a/recipes/registerPreBuild/recipe_metadata.toml b/recipes/registerPreBuild/recipe_metadata.toml new file mode 100644 index 00000000..e9e1e805 --- /dev/null +++ b/recipes/registerPreBuild/recipe_metadata.toml @@ -0,0 +1,29 @@ +# optional (if present and non-blank) name to use in the index +indexName = "" +# optional (if present and non-blank) folder name to use when converting recipe in RELEASE mode +destinationFolder = "" + +description =""" + This sample shows how to register a task dependency on the PreBuild lifecycle task. + """ + +[agpVersion] +min = "8.4.0-alpha13" + +# Relevant Gradle tasks to run per recipe +[gradleTasks] +tasks = [ + "assembleDebug" +] +validationTasks = [ + "validateDebug" +] + +# All the relevant metadata fields to create an index based on language/API/etc' +[indexMetadata] +index = [ + "APIs/AndroidComponentsExtension.onVariants()", + "Call chains/androidComponents.onVariants {}", + "Call chains/variant.registerPreBuild()", + "APIs/LifeCycleTasks.registerPreBuild()" +] diff --git a/recipes/registerPreBuild/settings.gradle.kts b/recipes/registerPreBuild/settings.gradle.kts new file mode 100644 index 00000000..71944cfa --- /dev/null +++ b/recipes/registerPreBuild/settings.gradle.kts @@ -0,0 +1,35 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +rootProject.name = "registerPreBuild" + +pluginManagement { + includeBuild("build-logic") + repositories { + $AGP_REPOSITORY + $PLUGIN_REPOSITORIES + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + $AGP_REPOSITORY + $DEPENDENCY_REPOSITORIES + } +} + +include(":app") diff --git a/recipes/transformManifest/build-logic/plugins/src/main/kotlin/CustomPlugin.kt b/recipes/transformManifest/build-logic/plugins/src/main/kotlin/CustomPlugin.kt index 1de7d982..baa04622 100644 --- a/recipes/transformManifest/build-logic/plugins/src/main/kotlin/CustomPlugin.kt +++ b/recipes/transformManifest/build-logic/plugins/src/main/kotlin/CustomPlugin.kt @@ -26,7 +26,7 @@ import java.io.File import com.android.build.api.variant.AndroidComponentsExtension /** - * This custom plugin will register tree tasks that will update and veridy Android manifest. + * This custom plugin will register tree tasks that will update and verify Android manifest. */ class CustomPlugin : Plugin { override fun apply(project: Project) {