[FOSS Project] Explanation of should-skip-ci

This article describes how we found a solution to only run the test suite covering the latest changes on git repositories which are hosting multiple apps (a.k.a. monorepos).

Context

Most of the client’s projects we’re building are consisting into a backend API and a frontend application. Depending on the client needs and on the dev team habits, we’re not always using multiple git repositories for the project (i.e. one for the backend app and one for the frontend app).

So some of our projects are structured into a mono git repository, in which both backend and frontend apps are stored (and sometimes also other apps depending on the client needs, such as workers, CLI tools, etc…).

Basically, our monorepo arch looks like this :

├── apps/
│   ├── api/      # the backend API
│   └── frontend/ # the frontend app
└── README.md

To make sure that the product is working as expected, we setup a Continuous Integration (CI) toolchain which is executing the tests we wrote for each app in the repo. Everytime we push a change to the repo, the CI chain is executed and runs the tests.

In order to avoid to spend too much time on the CI build, we can parallelize the builds of each app :

# time ->

# Don't
API build .................... OK ? Frontend build ........ OK ? GREEN BUILD


# Do
API build .................... OK ? |
Frontend build ........ OK ?        | GREEN BUILD (when both OK)

Such parallelization makes us gain precious minutes. But again, we’re still wasting time here. As the apps hosted in the repo are well defined enough and have different scopes, we do not need to run the test suite of app A when changes only occured on app B.

Let’s say I make a change on the frontend app, then I’d only run the test suite covering my changes (i.e. the frontend app test suite). I do not need to run the API app test suite, but unfortunately, the current CI setup will still run it.

It would be nice to be able to identify whether the test suite should be skipped or not, depending on the changes of the pull request.

Decision

We can rely on git to identify whether the changes are related to app A or to app B.

The git log command can print information about the changes on the given paths. For instance, git log apps/frontendwill print the latest commits that were made on the apps/frontend app.

However, we should also specify a commit range to use for that git log command. As we do not want to inspect all the history in this path, but only see if there are modifications for the commit range of our latest changes, we should first resolve this commit range.

To know from which commit a branch was issued (i.e. for a PR), we can use the git merge-base command. This will give us our start commit of the range, and then we can use HEAD as the end commit of the range.

So finally, we should run git log <start_commit>..HEAD apps/frontend to know if we’ve made changes on the apps/frontend path for this commit range.

When the output of this command is empty, it means that no changes were brung on that path for this commit range. So we’re now able to identify whether the CI build should continue or should be skipped.

To ease the usage of these git commands, we can write a CLI tool which would automatically resolve the commit range for us. The tool should also work in different use cases :

  • determine the changes on a pull request
  • determine the changes on a merge on the base branch

Status

Implemented.

This tool is available here : https://github.com/KnpLabs/should-skip-ci.

We used the rust programming language for the following reasons :

  • compiles to a single binary (easy to install and to use)
  • memory efficient by design (avoid leaks)
  • fast (no garbage collector)
  • modern system programming language

Consequences

By using this tool, we can now skip the CI build of apps that are not related to the latest changes, and so we only run the test suite that are covering these changes.

We’re now freeing computing power and time during CI builds of our monorepo project. This means CI costs reduction and faster feedback on our work.

The following schema represents a CI build for a pull requests which makes changes only on the frontend app :

# time ->

API build . (skipped, OK)    |
Frontend build ........ OK ? | GREEN BUILD (when both OK)

Of course, depending on your needs, you may still want to run the test suites of multiple apps when you make changes on a single app (i.e. for integration purposes). This is still possible, all you have to do is to specify which paths should be inspected by the tool.

Have a look to the documentation to know how to integrate this tool to your CI setup and how to use it.

Thanks for reading.