GitHub Actions for Rust

A few weeks ago I got beta access to the GitHub CI/CD platform called Actions. For my own Rust projects I was using Travis CI mostly before and had started migrating to the Azure Pipelines recently, but now I’m considering moving to the Actions instead.

Configuration syntax is a bit nicer comparing to Azure one, there are Linux, macOS and Windows environments available (pretty much as everywhere now) and integration with other GitHub parts is just amazing. But the most important thing there for me is a possibility to create “Actions” — custom tasks to be executed in the CI workflow. So, about that.

What does usual Rust CI looks alike

In order to make a decent CI workflow for a Rust project there are few things you might want to do:

  1. Check that there are cargo and rustup available (might be missing at some CI services)
  2. Install the toolchain needed (stable, nightly or some specific version, and maybe a MSRV version too)
  3. Run the cargo check, cargo test and so on
  4. Run cargo clippy and cargo fmt too

Also, I prefer to use the nightly version of clippy, as it is fancier than stable one, but it might not be available in the latest nightly toolchain, so either I’m left with a broken build for a whole day or I should permanently mark that job as an “okay to fail“ and check the output manually each time.

At first, I found myself copying all the same scripts from Azure Pipelines configurations, but then I thought that it would be great to share them somehow between my projects, got carried away a little and now I’m excited to present GitHub Actions for Rust — basic blocks to build a CI workflows for Rust projects without worries.

cargo

This Action is needed when you want to call cargo. It is pretty much the same as - run: cargo build line in the job steps, but it can do two additional things:

  1. If you are using cross-platform compilation, it can install cross automatically on demand, and call it instead of the cargo executable.
  2. It checks if cargo is available at all. As for 2019-09-15, Rust is not installed for a macOS environment by default, so this Action shows a nice error message, which suggests to install the rustup first (see the next section).

Here is an example:

- uses: actions-rs/cargo@v1
  with:
    command: build
    arguments: --release --all-features

Sure, it is pretty much redundant and can be replaced with that one:

- run: cargo build --release --all-features

But the following example will install cross automatically for you and call it instead. Cool, right?

- uses: actions-rs/cargo@v1
  with:
    use-cross: true
    command: build
    args: --target armv7-unknown-linux-gnueabihf

Okay, yes, but where does armv7-unknown-linux-gnueabihf target come from?

toolchain

This one Action, as you might have guessed, can install the Rust toolchain and, if needed, specific compilation target for it.

In a simplest form, it looks like this:

- uses: actions-rs/toolchain@v1
  with:
    toolchain: stable
    override: true

Which is basically the same as:

$ rustup toolchain install stable
$ rustup override set stable

Now, let’s add the target input to make that armv7 example from above to compile:

- uses: actions-rs/toolchain@v1
  with:
    toolchain: stable
    target: armv7-unknown-linux-gnueabihf
    override: true

And it is equal to these commands:

$ rustup toolchain install stable
$ rustup target add --toolchain stable armv7-unknown-linux-gnueabihf
$ rustup override set stable

As I’ve mentioned before, cargo executable might not be available for all environments and rustup might be missing too. This Action will automatically install the rustup on a first call if needed, no more headaches, yay!

components-nightly

Third one Action helps with a missing clippy and rustfmt components for nightly builds; when executed, it finds the most recent toolchain with the requested component available. Combine it with the toolchain Action from above and you can fall back to the specific nightly build instead of breaking a CI job.

For example, clippy is missing for nightly-x86_64-unknown-linux-gnu-2019-09-16, ..-2019-09-15 and ..-2019-09-14 builds, but available for nightly-..-2019-09-13. We can use the components-nightly output to install the specific nightly version and use its clippy:

- id: component
  uses: actions-rs/components-nightly@v1
  with:
    target: x86_64-unknown-linux-gnu
    component: clippy
- uses: actions-rs/toolchain@v1
  with:
    toolchain: ${{ steps.component.outputs.toolchain }}
    override: true
- uses: actions-rs/cargo@v1
  with:
    command: clippy
    args: -- -D warnings

And you can do the same thing for rustfmt, miri or any other rustup component if you want too.

Conclusion

So far that’s pretty much it.
I like an idea of hiding up the setup complexity and making the workflow configuration totally declarative, GitHub Actions looks like a great thing to help with that.

Check out the @actions-rs organization for sources, examples and some workflow recipes and let me know what are you thinking!