Continuous Integration (CI)
Continuous integration is a software development practice where developers frequently merge code changes into a central repository. Automated builds and tests are run to assert the new code’s correctness before integrating the changes into the main development branch. The goal is to find and correct bugs faster, improve software quality and enable software releases to happen faster. To get started with GitHub Actions in your project, let’s create a “test” workflow. To do this, create this path and file in the root of your project:.github/workflows/elixir.yaml
Let’s look at a sample file. Comments are included to explain and document what
we’re doing and why.
When the Workflow Runs…
When code is pushed to themain branch, this workflow is run. This happens
either from directly pushing to the main branch or after merging a PR into the
main branch.
This workflow is also configured to run checks on PR branches that target the
main branch. This is where it is most helpful. We can work on a fix or a new
feature in a branch and as we work and push our code up, it automatically runs
the full gamut of checks we want.
When all steps in the workflow succeed, the workflow “passes” and the automated
checks say it can be merged. When a step fails, the workflow halts at that point
and “fails”, potentially blocking a merge.
Customizing the Workflow Steps
This workflow is a starting point for a team. Every project is unique and every team values different things. Is this missing something your team wants? Check out some additional steps that can be added to your workflow.- Add a step to run Credo checks.
- Add a step to run Sobelow for security focused static analysis.
- Add a step to run dialyxir. This runs Dialyzer static code analysis on the project. Refer to the project for tips on caching the PLT.
- Customize the
mix testcommand to include code coverage checks. - Add Node setup and caching if
npmassets are part of the project’s test suite. - Add a step to run mix_audit. This provides
a
mix deps.audittask to scan a project’s Mix dependencies for known Elixir security vulnerabilities - Add a step to run
mix hex.audit. This shows all Hex dependencies that have been marked as retired, which the package maintainers no longer recommended using. - Add a set to run
mix deps.unlock --check-unused. This checks that themix.lockfile has no unused dependencies. This is useful if you want to reject contributions with extra dependencies.
Benefits of Caching
It’s worth spending time tweaking your caches. Why? A lot of effort has been put into speeding up Elixir build times. If we don’t cache the build artifacts, then we don’t reap any of those benefits! Of course, faster build times means you spend less money running your CI workflow. But that’s not the reason to do it! Better caches mean the checks are performed faster and that means faster feedback. Faster feedback means that, as a team, you save time and can move faster. No waiting 20 minutes for the checks to complete so a PR can be merged. (Yes, I have felt that pain!) This starting template builds in two caching steps. Ifnode_modules factor
into your project’s tests, then caching there makes a lot of sense too.
Just keep in mind that the reason we cache is to reduce drag on the speed of our
team.
If our caches ever cause a problem, and sometimes they can, it’s good to know
how to clear them. In our project, go to Actions > (Sidebar) Management >
Caches. This is the list of caches saved for the project. We can use our naming
format to identify which cache file is for what.
Retrying Failed Builds
We cache aggressively because we want the productivity benefits that incremental compiles bring the team. However, on occasion, incremental builds can fail. The quick fix is to perform a full recompile. The following step from our CI pipeline does this for us.deps and _build directories.
This compromise works well. Most of the time, we get fast checks and, where
there is a problem, we only need to retry the failed task to force a clean
build.
Additional Resources
- Documentation: Caching dependencies to speed up workflows
- erlef/setup-beam - Set up your BEAM-based GitHub Actions workflow (Erlang, Elixir, and more)
- See project for settings and ENV options
- Elixir setup action - Archived - Includes helpful examples like setting up the Postgres service database.
- Felt blog: Taking Hashrocket’s “Ultimate Elixir CI” to the Next Level
- felt/ultimate-elixir-ci - Good examples on CI setup with explanations.
- Source for cleaning out incremental builds on retry