Skip to content

Commit

Permalink
Merge "Update addCustomAsset and addCustomSourceFolders to highlight"…
Browse files Browse the repository at this point in the history
… into studio-main
  • Loading branch information
micahjo7 authored and Android (Google) Code Review committed May 9, 2024
2 parents 7898842 + ddd60fc commit 6d22fea
Show file tree
Hide file tree
Showing 31 changed files with 92 additions and 60 deletions.
4 changes: 2 additions & 2 deletions BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ recipe_test(
)

recipe_test(
name = "addCustomAsset",
name = "addGeneratedSourceFolder",
)

recipe_test(
Expand Down Expand Up @@ -115,7 +115,7 @@ recipe_test(
)

recipe_test(
name = "addCustomSourceFolders",
name = "addCustomSourceType",
)

recipe_test(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,22 @@ This sample shows how to add a new custom source folders to the Variant for newl
The source folder will not be used by any AGP tasks (since we do not know about it), however, it can
be used by plugins and tasks participating into the Variant API callbacks.

In this recipe, a custom source type is registered in the app's [build.gradle.kts](app/build.gradle.kts):

```
androidComponents {
registerSourceType("toml")
}
```

To access the custom sources, you just need to use
`sourceFolders.set(variant.sources.getByName("toml").getAll())`
which can be used as [Task] input directly.

There are two types of [`SourceDirectories`](https://developer.android.com/reference/tools/gradle-api/current/com/android/build/api/variant/SourceDirectories):
[`Flat`](https://developer.android.com/reference/tools/gradle-api/current/com/android/build/api/variant/SourceDirectories.Flat) and [`Layered`](https://developer.android.com/reference/tools/gradle-api/8.0/com/android/build/api/variant/SourceDirectories.Layered).
Custom sources are always of type `Flat`, meaning the directories are stored in type `Provider<Collection<Directory>>`.

To add a folder which content will be generated during execution time by [Task], you need
to use [SourceDirectories.addGeneratedSourceDirectory] and the pointer to the output folder
where source files will be generated.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
id("android.recipes.customSourceFolders")
id("android.recipes.custom_plugin")
}

android {
namespace = "com.example.android.recipes.customSourceFolders"
namespace = "com.example.android.recipes.recipe"
compileSdk = $COMPILE_SDK
defaultConfig {
minSdk = $MINIMUM_SDK
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,15 @@ class CustomPlugin : Plugin<Project> {
it.addGeneratedSourceDirectory(addSourceTaskProvider, AddCustomSources::outputFolder)
}

project.tasks.register<DisplayAllSources>("${variant.name}DisplayAllSources") {
//to print all directories that are part of `toml` source type
// -- Verification --
// the following is just to validate the recipe and is not actually part of the recipe itself
val taskName = "${variant.name}DisplayAllSources"
project.tasks.register<DisplayAllSources>(taskName) {
// to print all directories that are part of `toml` source type
// `variant.sources.getByName("toml")` here and any other custom source is of type
// SourceDirectories.Flat
sourceFolders.set(variant.sources.getByName("toml").all)
output.set(project.layout.buildDirectory.dir("intermediates/$taskName"))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ import java.nio.file.Path

abstract class DisplayAllSources: 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:InputFiles
abstract val sourceFolders: ListProperty<Directory>

Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ index = [
"Call chains/androidComponents.onVariants {}",
"Call chains/variant.sources.*.addGeneratedSourceDirectory()",
"Call chains/variant.sources.*.addStaticSourceDirectory()",
"Call chains/variant.sources.*.all",
"APIs/SourceDirectories.addGeneratedSourceDirectory()",
"APIs/SourceDirectories.addStaticSourceDirectory()",
"APIs/Component.sources",
"registerSourceType",
"SourceDirectories.add",
"SourceDirectories.Flat"
]
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

rootProject.name = "addCustomAsset"
rootProject.name = "addCustomSourceType"

pluginManagement {
includeBuild("build-logic")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
# Adding source folder with dynamic content.
# Adding source folder with dynamic content

This recipe show how you can add a source folder to the set of source folders for a specific type of
This recipe shows how you can add a source folder to the set of source folders for a specific type of
android source files. In this example, we add a source folder to Android's `assets`. The source folder
content is provided by a Task and it is therefore dynamic.

In the Variant API, the source folders are accessible through the `Component.sources` method. This `sources`
will provide access to all the variant's source folders for each source type.

Therefore, other types of android source files can be extended in a similar way like java, kotlin, java resources,
android resources, shaders, etc... See `Component.sources` for the complete list.
android resources, shaders, etc... See [`Component.sources`](https://developer.android.com/reference/tools/gradle-api/current/com/android/build/api/variant/Sources) for the complete list.

In this recipe, we use the `SourceDirectories.addGeneratedSourceDirectory` to add a new folder for `assets`
processing. You can extrapolate the same mechanism for other types of source files.
There are two types of [`SourceDirectories`](https://developer.android.com/reference/tools/gradle-api/current/com/android/build/api/variant/SourceDirectories):
[`Flat`](https://developer.android.com/reference/tools/gradle-api/current/com/android/build/api/variant/SourceDirectories.Flat) and [`Layered`](https://developer.android.com/reference/tools/gradle-api/8.0/com/android/build/api/variant/SourceDirectories.Layered).
In this recipe, we use `assets`, which are of type `Layered`, meaning the directories are stored in type
`Provider<List<Collection<Directory>>>`.

We use `SourceDirectories.addGeneratedSourceDirectory` to add a new folder for `assets` processing. You can extrapolate
the same mechanism for other types of source files.

| Module | Content |
|----------------------------|------------------------------------------------------------------------------|
Expand All @@ -20,7 +25,7 @@ processing. You can extrapolate the same mechanism for other types of source fil

## Details

### Dynamically generated sources.
### Dynamically generated sources

When you need to generate source files dynamically (based on other source files for instance), you should do so
in a Task with proper Input and Output declarations so you get correct up-to-date checks and caching.
Expand All @@ -31,18 +36,14 @@ Once the `TaskProvider` is created, you need to use `SourceDirectories.addGenera
output as a new source folder.
```
variant.sources.assets?.addGeneratedSourceDirectory(
assetCreationTask,
AssetCreatorTask::outputDirectory)
assetCreationTask,
AssetCreatorTask::outputDirectory)
```

### Run the example

To run the examples, you can just do:

```
./gradlew debugVerifyAsset
```
and the output should be:
./gradlew :app:verifyDebugAsset
```
> Task :app:debugVerifyAsset
Success: Found asset in resulting APK !
```
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ dependencies {

gradlePlugin {
plugins {
create("customSourceFoldersPlugin") {
id = "android.recipes.customSourceFolders"
create("customPlugin") {
id = "android.recipes.custom_plugin"
implementationClass = "CustomPlugin"
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,25 @@
*/

import com.android.build.api.variant.ApplicationAndroidComponentsExtension
import com.android.build.api.variant.BuiltArtifactsLoader
import com.android.build.api.artifact.MultipleArtifact
import com.android.build.api.artifact.SingleArtifact
import com.android.build.gradle.AppPlugin
import java.io.File
import java.util.jar.JarFile
import org.gradle.api.DefaultTask
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
import org.gradle.configurationcache.extensions.capitalized
import org.gradle.kotlin.dsl.register
import java.lang.IllegalStateException
import java.lang.RuntimeException

/**
* This custom plugin will register a task output as a generated source folder for
Expand All @@ -58,7 +60,7 @@ class CustomPlugin : Plugin<Project> {
?.let {
// create the task that will add new source files to the asset source folder.
val assetCreationTask =
project.tasks.register<AssetCreatorTask>("create${variant.name}Asset")
project.tasks.register<AssetCreatorTask>("create${variant.name.capitalized()}Asset")

// registers the newly created Task as the provider for a new generated
// source folder for the 'assets' type.
Expand All @@ -70,14 +72,18 @@ class CustomPlugin : Plugin<Project> {
)
}

// create the verification task
project.tasks.register<VerifyAssetTask>("${variant.name}VerifyAsset") {
output.set(
project.layout.buildDirectory.dir("intermediates/recipe/$it.name")
)
// -- Verification --
// the following is just to validate the recipe and is not actually part of the recipe itself
val taskName = "verify${variant.name.capitalized()}Asset"
project.tasks.register<VerifyAssetTask>(taskName) {
// the verifying task will look at the merged assets folder and ensure
// the file added by the assetCreationTask is present.
assets.set(variant.artifacts.get(SingleArtifact.ASSETS))
mergedAssets.set(variant.artifacts.get(SingleArtifact.ASSETS))
// also verify that the file exists in `variant.sources.assets.all`
// Note: this should not be done in practice- it is only to demonstrate that the assets are a
// layered source type (SourceDirectories.Layered)
variant.sources.assets?.let { assetsAllSources.set(it.all) }
output.set(project.layout.buildDirectory.dir("intermediates/$taskName"))
}
}
}
Expand All @@ -100,29 +106,35 @@ abstract class AssetCreatorTask: DefaultTask() {
}

/**
* This task here to verify that the API does what is says.
* This task here to verify that the API does what it says.
*/
abstract class VerifyAssetTask : DefaultTask() {

// In order of the task to be up-to-date when the input has not changed,
// 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 or not
// output are always run regardless of whether the inputs changed
@get:OutputDirectory
abstract val output: DirectoryProperty

@get:InputDirectory
@get:Optional
abstract val assets: DirectoryProperty
abstract val mergedAssets: DirectoryProperty

@get:InputFiles
abstract val assetsAllSources: ListProperty<Iterable<Directory>>

@TaskAction
fun taskAction() {
File(assets.get().asFile, "custom_asset.txt").let {
if (it.exists()) {
println("Found ${it} in merged assets folder")
} else {
throw IllegalStateException("custom_asset.txt file not " +
"present in merged asset folder : ${assets.get().asFile}")
}
// Validate the assets artifact contains the expected file
if (!File(mergedAssets.get().asFile, "custom_asset.txt").exists()) {
throw RuntimeException("custom_asset.txt file not present in merged asset folder: " +
"${mergedAssets.get().asFile}")
}

// Validate that `variant.sources.assets.all` contains the expected file
val addedDirectory = assetsAllSources.get().flatten().first { it.asFile.name == "createDebugAsset" }
if (!File(addedDirectory.asFile, "custom_asset.txt").exists()) {
throw RuntimeException("custom_asset.txt not present in assets all sources")
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ min = "8.1.0"
# Relevant Gradle tasks to run per recipe
[gradleTasks]
tasks = [
"debugVerifyAsset"
":app:verifyDebugAsset"
]

# All the relevant metadata fields to create an index based on language/API/etc'
Expand All @@ -28,6 +28,8 @@ index = [
"APIs/Component.artifacts",
"APIs/Component.sources",
"Call chains/variant.sources.*.addGeneratedSourceDirectory()",
"Call chains/variant.sources.*.all",
"Call chains/variant.artifacts.get()",
"Call chains/androidComponents.onVariants {}"
"Call chains/androidComponents.onVariants {}",
"SourceDirectories.Layered"
]
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

rootProject.name = "addCustomDourceFolders"
rootProject.name = "addGeneratedSourceFolder"

pluginManagement {
includeBuild("build-logic")
Expand Down
5 changes: 0 additions & 5 deletions recipes/legacyTaskBridging/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,3 @@ To run the examples, you can just do:
```
./gradlew debugVerifyAsset
```
and the output should be:
```
> Task :app:debugVerifyAsset
Success: Found asset in resulting APK !
```
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,9 @@ abstract class VerifyAssetTask : DefaultTask() {

@TaskAction
fun taskAction() {
File(assets.get().asFile, "custom_asset.txt").let {
if (it.exists()) {
println("Found ${it} in merged assets folder")
} else {
throw IllegalStateException("custom_asset.txt file not " +
"present in merged asset folder : ${assets.get().asFile}")
}
if (!File(assets.get().asFile, "custom_asset.txt").exists()) {
throw RuntimeException("custom_asset.txt file not present in merged asset folder: " +
"${assets.get().asFile}")
}
}
}

0 comments on commit 6d22fea

Please sign in to comment.