Azure Pipelines for Rust

Sometimes, the main template azure/stages.yml is just too far from what you want your CI to be. Or, maybe you just want to run a few extra steps, like publishing your documentation to GitHub Pages or making a new binary release available from tagged pushes to master. Do not fret, we’ve got you covered there too! The CI scripts are arranged as re-usable components, so you can re-use only the parts that you need.

To write your own stage, simply add another list item under stages: in your azure-pipelines.yml file:

 - stage: foobar
 - template: azure/stages.yml@templates
 - stage: skynet

    - repository: templates
      type: github
      name: crate-ci/azure-pipelines
      endpoint: PLACEHOLDER

You can also remove the azure/stages.yml entirely if you do not want to use the standard set of stages and just roll your own. The Azure DevOps documentation on writing stages and jobs is surprisingly good, so we won’t repeat that here. You may still want to use some of the components that this repository provides though, so we’ll go through those below.

A good idea might be to start by copy-pasting our azure/stages.yml and then starting from there. If you do, you should start by removing the cruft from all the lines that look like this:

 - stage: ${{ format('{0}check', parameters.prefix) }}
 - stage: ${{ format('{0}test', parameters.prefix) }}
   dependsOn: ${{ format('{0}check', parameters.prefix) }}

So that they instead just read

 - stage: check
 - stage: test
   dependsOn: check

You can also simplify the various ifs like this one

${{ if ne(parameters.prefix, '') }}:
  displayName: ${{ format('Test suite ({0})', parameters.prefix) }}
${{ if eq(parameters.prefix, '') }}:
  displayName: Test suite

by keeping just the last line and removing the double-space indent.

Task templates

These are steps that you can re-use in your own jobs. That is to say, you can write:

 - job: myjob
     - script: run this cmd
     - template: azure/foobar.yml@templates
     - script: run this other cmd

To re-use the CI steps from the template azure/foobar.yml.

Install Rust

template: azure/install-rust.yml@templates

Installs Rust and additional components and targets as needed.


Job templates

These are jobs that you can re-use wholesale in your own stages. That is to say, you can write:

 - stage: mystage
   displayName: Some custom stage
     - job: my other job
     - template: azure/foobar.yml@templates
     - job: another job

To re-use the CI job template azure/foobar.yml.

Compilation check

template: azure/cargo-check.yml@templates

Runs cargo check with no features, default features, and all features against all subcrates and targets. You can pass the parameter benches: true to also test benchmarks, rust: 1.34.0 to test on a particular Rust version, and setup: [...] to run additional setup steps.

By default, this template will also run cargo check --all-features to ensure that all of your features compile, even when they are all used together (cargo features should be additive). This is usually what you want, but in some cases you have features that should only be enabled on particular compiler versions, targets, or platforms. If that applies to you, you can set the parameter all_features to false, and then the --all-features check will not be run.


template: azure/tests.yml@templates

Runs cargo test on all platforms and across stable, beta, and nightly. You can pass the parameter envs: {...} to pass environment variables, and setup: [...] to run additional setup steps. You can also pass features and/or nightly_feature to include additional cargo features when running the tests. nightly_feature will only be included on runs with the nightly compiler. See the test docs for details.


template: azure/test.yml@templates
  rust: <string> = 'stable'
  cross: <bool> = false
  allow_fail: <bool> = false
  test_ignored: <bool> = false
  single_threaded: <bool> = false
  features: <string> = ''
    NAME: value
    NAME: container
    - task

Runs cargo test on Linux (or all platforms if cross: true) You can pass the parameter envs: {...} to pass environment variables, and setup: [...] to run additional setup steps. If you pass allow_fail: true, errors during testing will not count as a failure of the job, which can be useful for things like ignoring failures on nightly versions of the compiler. To run tests marked with #[ignore], set test_ignored: true. To run tests with --test-threads=1, set single_threaded: true. To run tests with particular features enabled, pass features: "feat1,feat2,subcrate/feat3". To spin up additional service containers, pass them in services (though note that these will generally only work on Linux, so cross: true likely won’t work).


template: azure/style.yml@templates

Runs rustfmt and clippy on stable Rust and on beta with allowed failures. You can include the parameter setup: [...] to run additional setup steps.

Rust formatting check (rustfmt)

template: azure/rustfmt.yml@templates

Runs rustfmt on Rust stable (set with rust parameter) optionally allowing failures (allow_fail parameter).

Rust linter (clippy)

template: azure/cargo-clippy.yml@templates

Runs cargo clippy on Rust stable (set with rust parameter) optionally allowing failures (allow_fail parameter). You can also pass setup: [...] to run additional setup steps.


template: azure/coverage.yml@templates

This job will run tarpaulin and upload the coverage test results to It takes the required parameter codecov_token that includes the upload token (see the setup instructions). You can also pass the parameter envs: {...} to pass environment variables, services: {...} to run additional service containers, and setup: [...] to run additional setup steps.

By default, your pipeline will test against the stable Rust version bundled with the tarpaulin Docker image. If your project only compiles on nightly, you can use the nightly Docker image (latest-nightly) instead by setting the parameter nightly to true. Note that you cannot set a specific nightly version, but are instead tied to the version that tarpaulin ships. For this reason, nightly coverage will always be run with failures allowed (yellow CI) to avoid spurious CI failures.

A note on git submodules

By default these jobs will all set the submodule fetch policy to recursive, and will thus fetch all your repository’s git submodules recursively. You cannot generally override this behavior when re-using components from CI. If you must, you should write your own jobs on top of install-rust.yml and coverage.yml. install-rust will not change checkout: self at all, and coverage lets you override it by setting the parameter submodules: true for single-depth checkout, submodules: false for no submodules, or submodules: manual with a checkout: self entry in setup for complete manual control.