Make CI pipeline faster for Android with modular checks on Github Actions

Imagine having a modular Android app. Different teams working on different modules without even touching another module. We will make the CI pipeline faster for those team members. In the below images, you are seeing a modular app structure and relations between modules.


feature1
and feature2
modules are the main modules and they won't affect each other when a change happens inside them. This means we can run checks for only changed modules and if they can build, our app can build too without breaking the existing code.
But when util
or common
module changed we don't know sure whether it will break feature1
or feature2
because those modules used in multiple modules. It is not safe to run modular checks on common modules. Because you can build them safely but you can break the dependent module functionality.
Enough with talking! In this section, we will detect changed modules and run modular checks with Github Actions. Here is an example workflow file:
At the Check changed modules step we are using a beautiful action from dorny: paths-filter. It uses picomatch library under the hood. Here is our file_filter_config:
In this config, we are telling picomatch: If anything changed inside the feature1 folder mark them as feature1
and same as feature2
. If anything changed inside a folder rather than feature1
and feature2
mark them as common
.
Then based on changed files dorny/paths_filter action will give us an output like this [feature1,feature2,common]
. Then we set an environment variable named changed_modules with stringifying that output.
Now we know which modules changed. We will create a Powershell script with some if-else logic to determine which gradle task to run. Here is our script:
This script will give us an output named gradleTask. It can be equal to feature1:lintDebug
, feature2:lintDebug
or for common just lintDebug
.
Now we know which modular gradle task to run. We will run the task with: ./gradlew ${{steps.gradle_task_name.outputs.gradleTask}} — — continue
This is a generic step and you can run different modular checks by changing env: task
variable at the start of the workflow. For example, you can have modular ktlint, detekt, unitTest, lint tasks.
In our CI pipeline, the longest task was Android Lint which was taking around 16–17 minutes.
With modular checks, we reduced lint duration for feature1
module to 11–12 minutes and feature2
module to 4–5 minutes.
We still run the usual lint task when you change a common module or multiple modules, but this was the case before.
Thanks for reading. Stay safe, and have a nice day!