[MUSIC PLAYING]
MURAT YENER: Welcome to a new episode in MAD Skills
series on Gradle and Android Gradle plugin APIs.
In the last episode, you learned about writing your own plugin
and using the new variants API.
In this episode, you'll learn about Gradle tasks, providers,
properties, and basics of I/O. Next,
you'll take your plugin a step further
and learn how to get access to various
build artifacts using the new Artifact API.
Let's jump in and start coding.
Let's say I want to build a plugin which automatically
updates the version code specified in the app
manifest with the Git version.
To achieve that, I need to add two tasks to my build.
The first task will obtain the Git version,
and the second task will use the Git version
to update the manifest file.
I'll start with creating a new task called GetVersionTask.
This class needs to override DefaultTask and implement
the notated taskAction() function.
Here, I have some code, which, queries
the tip of the Git tree.
Since I cannot cache this value, I want to start a versioning
for it in an intermediate file so other tasks can read and use
this value.
To do this, I need to use a RegularFileProperty.
Properties can be used for task inputs and outputs.
In this case, the property will act as a container
to represent the task output.
I create a RegularFileProperty and annotate it with
@get:OutputFile.
OutputFile is a marker annotation
which is attached to the getter function.
With this, the property is marked as the output
file for this task.
Now that I declared my task output,
Let's jump back into the TaskAction,
where I can access the file and write
the text which I want to store.
In this case, I'll start a Git version
that will be the output of this task.
To keep things simple, I replace the gitVersion query
with a hardcoded string.
Now that the task is ready, let's register
this task in the plugin code.
First, I'll create a new plugin class called ExamplePlugin
and implement Plugin.
If you are not familiar with creating plugins
in the buildSrc folder, make sure to take
a look at the previous videos in this series.
Next, I'll register the GitVersionTask
and set the output file property to an intermediate file
in the build folder.
I'll also set upToDateWhen to false
so that the outputs from the previous executions
of this task will not be reused.
This means that the task will be executed on every build
since it'd never be up to date.
After executing the task, I check the output file
under the build intermediates folder.
I can verify that the tasks stored the value which
I hardcoded in the task.
Now, let's switch to the second task,
which will update the version in the manifest file.
Let's call this task ManifestTransformerTask
I'll use two RegularFileProperty objects
as the inputs of this task.
I'll use the first RegularFileProperty
to read the contents of the output file, which
is generated by GitVersionTask.
I'll use the second RegularFileProperty
to read the manifest file of the app.
Next, I can replace the version code
in the manifest with the version code stored in the gitVersion
variable from the gitInfoFile.
Now I can write the updated manifest.
To do that, first, I'll create another RegularFileProperty
for the output and annotate it with @get:OutputFile.
Note, I could actually use the variant output
to set the version code directly without having
to rewrite the manifest.
But I'll use this property to show you
how to use artifact transformations
for the same effect.
Let's go back to the plugin to wire things up.
First, I get the AndroidComponentsExtension.
I want this new task to run after AGP decides which
variants to create, but before values are locked and cannot be
modified.
The onVariants callback allows me
to set properties, which will be used to configure the task's
input before the execution.
I can use providers to wire the properties to other tasks that
need to perform time-consuming operations, such as reading
from external inputs, like files or the network.
I'll start with registering the ManifestTransformerTask.
This task requires the gitVersionOutputFile,
which is the output of the previous task.
To access this property, I'll use a provider.
A provider can be used to access a value of given type,
either directly by using the get() function or by converting
the value to a new provider using the operator functions,
such as map() and flatMap().
When I look back at the property interface,
it implements the Provider interface.
You can lazily set values on properties
and later lazily access these values using providers.
When I look at the return type of the register function,
this returns a TaskProvider of the given type.
I'll assign this to a new val.
Now back to setting the input to the ManifestTransformerTask,
when I try to map the value from the provider to the input
property, I get an error.
The lumped argument to map() takes one value, such as T,
and produces another value type, such as S. However,
in this case, the set function expects a Provider.
I can use the flatMap() function,
which similarly takes a value such as T but this time
produces a provider of type S instead of producing the value
S directly.
Next, I need to tell the artifacts of this variant
to use manifestUpdater and wire the manifest
as the input and the updatedManifest as the output.
Finally, I call the toTransform() function
to transform a SingleArtifact type.
When I run this task, I can see that the version
code in the app manifest is now updated
with the value in the gitVersion file.
Note that I didn't explicitly ask the GitProviderTask to run.
The GitProviderTask is executed because this task's output
is used as the input to the ManifestTransformerTask, which
I requested to run.
Let's add another task to see how
we can access the updatedManifest
and verify that it's updated successfully.
I create a new task called VerifyManifestTask.
In order to read the manifest, I need
to access the APK file, which is an artifact of the build task.
To do this, I need to set the build apkFolder
as the input to this task.
Notice this time, I'm using DirectoryProperty instead
of FileProperty because the SingleArtifact APK
object represents the directory where APK files are
placed after they're built.
As the second input of this task,
I need another property of type BuiltArtifactsLoader, which
I'll use to load BuiltArtifacts instances from metadata
files that describe the files in the APK directory.
Builds can produce several APKs when
you have native components, various languages, and so on.
The BuiltArtifactsLoader abstracts the process
of identifying each APK as well as their attributes,
like API and languages.
Now, it's time to implement the task.
First, I load the BuiltArtifacts,
make sure that it contains only one APK,
and download this APK as a file instance.
At this point, I can access the manifest in the APK
and verify that the version is updated.
To keep things simple, I'll only check
if the APK exists, and the reminder
to check the manifest here, and print a success message.
Now, back to the plugin code register this task,
in the plugin code, I registered a new task as Verifier,
pass in the apkFolder and the BuiltArtifactsLoader object
of the current variant's artifacts.
This time, when I run the task, I
can see that the new task loads the APK
and prints the success message.
Notice, again, I didn't explicitly
request the manifest transformation to happen,
but because the Verifier task requested the final version
of the manifest artifact, the transformation
happened automatically.
There are three tasks.
My plugin first checks the current Git version tree
and stores that information in an intermediate file.
Next, my plugin uses the output of this task lazily,
using your provider to update the current manifest file
with the version code.
Finally, my plugin uses another task
to access the BuiltArtifacts and check
if the manifest is updated.
If you want to see more samples like this,
make sure to check out the gradle-recipes repository
on GitHub.
That's it.
Starting with version 7.0, Android Gradle plugin
offers official extension points so that you
can write your own plugins.
With these new APIs, you can control build inputs, read,
modify, or even replace intermediate and final
artifacts.
To learn more and to keep your builds sufficient,
make sure to check out the documentation, resources,
and recipes which are all linked in the notes below.
See you next time.
[MUSIC PLAYING]