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

Yasin Kaçmaz
ProAndroidDev
Published in
3 min readMar 8, 2021

--

Image from Unsplash

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: taskvariable 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!

--

--

I value ideas that can touch and simplify daily lives. I am passionate about mobile technologies, UI and UX trends.