Blazing fast CI suite for Elixir on Circle CI 2.0

Blazing fast CI suite for Elixir on Circle CI 2.0

I’ve been frustrated for so many times on waiting. Whether it is waiting for a test to run, the last season of Game of Thrones to arrive or simply to go to sleep while lying in bed.

While it is quite hard to make Game of Thrones arrive sooner there are things that we actually can make more effective. This is how I got my CI suite to run a lot faster with the help of Circle CI 2.0 and their new workflows.

Reusable defaults

Since the best way to achieve a lot of different stuff in the shortest manner possible is to run jobs in parallel when possible that’s what we’re gonna do. So to make sure we’re gonna have an easy time changing versions and so on we’ll create some default settings that will be used in every job

version: 2
defaults: &defaults
  working_directory: ~/metatags
  docker:
    - image: elixir:1.6.5
      environment:
        MIX_ENV: test

jobs:

Prerequisites

As circle will run all jobs inside a container we need to do things like checking out the code, fetch dependencies and compilation. We’re going to use some caching so that we do not have to do time consuming tasks when we don’t need to. At the end we’re going to persist the workspace which is basically a cache that can be used between multiple jobs in a workflow

jobs:
  build:
    <<: *defaults
    steps:
      - checkout

      - restore_cache:
          keys:
            - v2-deps-cache-{{ checksum "mix.lock" }}
            - v2-deps-cache
      - run: mix local.hex --force
      - run: mix local.rebar --force
      - run: mix deps.get
      - run: mix deps.compile
      - run: mix compile
      - save_cache:
          key: v2-deps-cache-{{ checksum "mix.lock" }}
          paths:
            - _build
            - deps
            - ~/.mix
      - persist_to_workspace:
          root: ~/
          paths:
            - metatags
            - .mix

Running the tests

To run the test all we have to do is to add the test job (which we’ll attach to the workspace) and setup the workflow to know that the test job requires build to be run first.

jobs:
  # ...
  test:
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/
      - run:
          name: Run tests
          command: mix test

workflows:
  version: 2
  continuous_integration:
    jobs:
      - build
      - test:
        requires: 
          - build

Adding other tools to pipeline

In order to add more tools to the pipeline we’ll just have to add the job, make sure it uses the persisted workspace and it to the workflow with proper requirements.

Below you can see my full configuration that is currently being used for my metatags library.

version: 2
defaults: &defaults
  working_directory: ~/metatags
  docker:
    - image: elixir:1.6.5
      environment:
        MIX_ENV: test

jobs:
  build:
    <<: *defaults
    steps:
      - checkout

      - restore_cache:
          keys:
            - v2-deps-cache-{{ checksum "mix.lock" }}
            - v2-deps-cache
      - run: mix local.hex --force
      - run: mix local.rebar --force
      - run: mix deps.get
      - run: mix deps.compile
      - run: mix compile
      - save_cache:
          key: v2-deps-cache-{{ checksum "mix.lock" }}
          paths:
            - _build
            - deps
            - ~/.mix
      - persist_to_workspace:
          root: ~/
          paths:
            - metatags
            - .mix

  test:
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/
      - run:
          name: Run tests
          command: mix test

  credo:
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/
      - run:
          name: Run credo
          command: mix credo

  check_formatted:
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/
      - run:
          name: Verify formatted
          command: mix format --check-formatted

  dialyzer:
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/
      - restore_cache:
          keys:
            - v1-plt-cache-{{ checksum "mix.lock" }}
            - v1-plt-cache
      - run: mix dialyzer --plt
      - save_cache:
          key: v1-plt-cache-{{ checksum "mix.lock" }}
          paths:
            - _build
            - ~/.mix
      - run:
          name: Run dialyzer
          command: mix dialyzer

  coverage:
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/
      - run:
          name: Analyze coverage
          command: mix coveralls.circle

workflows:
  version: 2
  continuous_integration:
    jobs:
      - build
      - test:
          requires:
            - build
      - test:
          requires:
            - build
      - credo:
          requires:
            - build
      - dialyzer:
          requires:
            - build
      - coverage:
          requires:
            - build

Since metatags is only a library there is no use in adding deployment steps to the process. However there’s different opinions on how you should deploy your elixir application around the community so I’ll leave those steps out for now.