How to setup GitHub Actions for Rust to build binaries

January 20, 2020

GitHub actions are very handy for releasing binaries automatically for every tag in your repository. If you have a public repo, GitHub actions services are free for that repo. In this tutorial, we'll use it to build binaries for different platforms. GitHub actions allows you to use VM's for different platforms: Ubuntu, Windows, Mac. Let's leverage those capabilities to setup our CI/CD.

Configuration Structure

First, let's create a configuration file in our repository at .github/workflows/rust.yml. File name can be whatever you want, but throughout this tutorial I will use the rust.yml.

The base structure for configuration file should look like this:

name: Rust

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v1

Let's breakdown the options given above:

on property defines the action that triggers the pipeline. You can refer to GitHub documentation to see what's available, but for our use case we want to trigger it whenever something is pushed to our repository.

jobs is a list of tasks that will run the trigger condition is satisfied. In this list, we've added a task named build which runs on a VM which is based ubuntu. In steps section, it is possible to breakdown the task into different sections. By default, as a first step, we checkout the repo with a given state to the filesystem.

Steps required to build the binary

Secondly, we need couple of other commands to build and release the binary. The next thing it is needed to be taken care of is getting the latest stable rust toolchain to the vm. ubuntu VM is bundled with a rust toolchain, therefore this step is optional. We'll use a third party action provider called actions-rs/toolchain@v1 and override the existing toolchain.

Then, we are ready to build our binary. We can use cargo for that. You can also rename the binary so that it reflects the platform that can be used.

Finally, another custom action is called softprops/action-gh-release@v1 to deploy the binary to github releases. This action needs a token to deploy the artifacts. By default, a valid token is available for every CI run as an environment variable named GITHUB_TOKEN. Moreover, you can configure when to do a release by specifying some rules. The final configuration is set so that it will only run when there is a new git tag in the repository.

The whole configuration to build and release looks like this:

---
jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v1

      - name: Install latest rust toolchain
        uses: actions-rs/toolchain@v1
        with:
          toolchain: stable
          default: true
          override: true

      - name: Build
        run: cargo build --all --release && strip target/release/PROJECT_NAME && mv target/release/$PROJECT_NAME target/release/PROJECT_NAME_amd64

      - name: Release
        uses: softprops/action-gh-release@v1
        if: startsWith(github.ref, 'refs/tags/')
        with:
          files: |
            target/release/PROJECT_NAME_amd64
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Support for other platforms

To support other platforms like windows and mac, it is possible to add new jobs which runs on those platforms. The steps of the jobs do not differ much from the one we write for ubuntu. You can also leverage a feature called "Build Matrix". But I find it creating dedicated jobs for specific platforms easier to understand.

One additional thing worth mentioning is that mac VM does not come with the rust toolchain, so actions-rs/toolchain@v1 is mandatory for building something for mac.

TL,DR: Complete Configuration

The final configuration file is ended up looking like this:

name: Rust

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v1

      - name: Install latest rust toolchain
        uses: actions-rs/toolchain@v1
        with:
          toolchain: stable
          default: true
          override: true

      - name: Build
        run: cargo build --all --release && strip target/release/PROJECT_NAME && mv target/release/PROJECT_NAME target/release/PROJECT_NAME_amd64

      - name: Release
        uses: softprops/action-gh-release@v1
        if: startsWith(github.ref, 'refs/tags/')
        with:
          files: |
            target/release/PROJECT_NAME_amd64
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  build-win:
    runs-on: windows-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v1

      - name: Install latest rust toolchain
        uses: actions-rs/toolchain@v1
        with:
          toolchain: stable
          default: true
          override: true

      - name: Build
        run: cargo build --all --release

      - name: Release
        uses: softprops/action-gh-release@v1
        if: startsWith(github.ref, 'refs/tags/')
        with:
          files: target/release/PROJECT_NAME.exe
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  build-mac:
    runs-on: macos-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v1

      - name: Install latest rust toolchain
        uses: actions-rs/toolchain@v1
        with:
          toolchain: stable
          target: x86_64-apple-darwin
          default: true
          override: true

      - name: Build for mac
        run: cargo build --all --release && strip target/release/PROJECT_NAME && mv target/release/PROJECT_NAME target/release/PROJECT_NAME_darwin

      - name: Release
        uses: softprops/action-gh-release@v1
        if: startsWith(github.ref, 'refs/tags/')
        with:
          files: |
            target/release/PROJECT_NAME_darwin
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}