tagsrustdevops

August 2023

A simple GitLab CI configuration for Rust

visualization of the GitLab pipeline produced by the configuration described in this post.

CI pipelines can be such a rabbit hole. Lucky for you, I dig holes for a living.

The setup below is what I found working best for me. I use it in a couple of different projects. Is this better than the official template? Yes. Should the official template just look like this instead? Probably not: I’m a code kobold, I have opinions, and my configuration is a bit opinionated.

It runs the clippy linter and, if linting is green, all tests. In parallel it runs a format checker. That’s it. Then again there are a few tricks that one can pull to do the above efficiently: see the commented CI configuration below.

“Show me the code or I’ll tell the Dark Lord who has been stealing his body lotion”

Alrightalrightjeez! Just copy these two files in your repo at the locations indicated in their first lines.

It’s a simple spell, but quite unbreakable.

# .gitlab-ci.yml
image: "rust:latest"

variables:
  # move cargo data into the project directory so it can be cached
  CARGO_HOME: ${CI_PROJECT_DIR}/.cargo
  # treat compiler warnings as errors (in clippy, when running tests, etc.)
  RUSTFLAGS: -Dwarnings

default:
  # cancel the job if a newer pipeline starts for the same MR or branch
  interruptible: true
  cache:
    # use the git branch or tag as cache key, so the cache will be
    # shared among CI runs for the same branch or tag
    key: ${CI_COMMIT_REF_SLUG}
    # we cache .cargo/ and target/, which effectively enables
    # incremental builds across different executions of the CI
    # for the same branch or the same merge request
    paths:
      - .cargo
      - target

# a format check is run in parallel with the rest
cargo:fmt:
  script:
    - rustup component add rustfmt
    - cargo fmt --check

# the clippy linter runs before tests are executed
cargo:clippy:
  script:
    - rustup component add clippy
    - cargo clippy --all-targets

cargo:test:
  # this job is only run if clippy passes
  needs: ["cargo:clippy"]
  script:
    # install nextest (I swear this is the recommended way)
    - mkdir -p $CARGO_HOME/bin && curl -LsSf https://get.nexte.st/latest/linux | tar zxf - -C $CARGO_HOME/bin
    # run nextest with the "ci" profile, which produces a junit XML test report
    - cargo nextest run --profile ci
  artifacts:
    when: always
    reports:
      # point GitLab to the junit test report: it will automatically display it in a dashboard
      junit: $CI_PROJECT_DIR/target/nextest/ci/junit.xml

It is accompanied by this configuration for nextest:

# .config/nextest.toml
[profile.ci]
# print out output for failing tests as soon as they fail,
# and also at the end of the run (to make perusing easier)
failure-output = "immediate-final"
# do not bail out on the first failed test, run them all
fail-fast = false

[profile.ci.junit]
path = "junit.xml"

Ta-daaaah! **jazz hands!**

What is this nextest that you speak of?

I recently switched from the built-in cargo test to nextest as a test driver.

The main reason for the change was that, since Rust 1.70, there is no good way that I know of to produce JUnit reports from the built-in cargo test.

nextest has first-class support for CI pipelines, including the production of JUnit reports that look pretty in a GitLab dashboard. So far it gave me no reason to look back. So far…

Did you find this useful? Do you have any suggestion to improve the configuration? Let me know at codekobold@pm.me