<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nick Mosher</title>
    <description>The latest articles on DEV Community by Nick Mosher (@nicholastmosher).</description>
    <link>https://dev.to/nicholastmosher</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F618554%2F65ed256e-372e-4db7-98b3-0029ba5dd498.jpeg</url>
      <title>DEV Community: Nick Mosher</title>
      <link>https://dev.to/nicholastmosher</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nicholastmosher"/>
    <language>en</language>
    <item>
      <title>GitHub Actions best practices for Rust projects</title>
      <dc:creator>Nick Mosher</dc:creator>
      <pubDate>Wed, 21 Apr 2021 15:57:55 +0000</pubDate>
      <link>https://dev.to/infinyon/github-actions-best-practices-for-rust-projects-1j9p</link>
      <guid>https://dev.to/infinyon/github-actions-best-practices-for-rust-projects-1j9p</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/infinyon/fluvio/" rel="noopener noreferrer"&gt;Fluvio&lt;/a&gt; is a high-performance distributed streaming platform written in Rust.&lt;/p&gt;

&lt;p&gt;As a fairly large project, we have a lot of build configurations and testing scenarios that we automate in order to make sure we don't break things by accident. We've been using GitHub Actions in order to run our CI/CD workflows, but as we've grown, things have naturally gotten messy over time. This week, I took some time to re-visit our workflow definitions to clean things up and try to increase our team's productivity.&lt;/p&gt;

&lt;p&gt;I specifically want to talk about two main improvements I worked on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Consolidating multiple jobs using the build matrix

&lt;ul&gt;
&lt;li&gt;This cut our workflow file size almost in half, from 477 lines to 264, making CI easier to maintain.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Setting up &lt;a href="https://github.com/mozilla/sccache" rel="noopener noreferrer"&gt;&lt;code&gt;sccache&lt;/code&gt;&lt;/a&gt; to improve our building and testing speed

&lt;ul&gt;
&lt;li&gt;We actually had already set up &lt;code&gt;sccache&lt;/code&gt; but it was misconfigured.
I'll talk about how to check that everything is set up properly.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The first half of this post should be generally useful for anybody who needs to use GitHub Actions and wants to learn more about the build matrix. The second half should be useful to Rust developers who want a good starting point for a solid CI setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the GitHub workflows build matrix
&lt;/h2&gt;

&lt;p&gt;The reason I was working on workflows this week was because our CI build and test time had grown to a point that it was interfering with our team's ability to move quickly. When I started reading through our workflow definitions, what I saw was a lot of independent jobs with a lot of duplicated steps. Most of the jobs would install the Rust toolchain, install &lt;a href="https://github.com/mozilla/sccache" rel="noopener noreferrer"&gt;&lt;code&gt;sccache&lt;/code&gt;&lt;/a&gt;, cache the Cargo registry and the sccache directory, and then run a single task from our project Makefile. The boilerplate to set up each of these jobs came out to at least 60 lines of configuration. I won't post any of the "before" workflow code here, but if you are interested in seeing it you can &lt;a href="https://github.com/infinyon/fluvio/blob/6eced8a04a41552e4c5276c26a8300c00c990007/.github/workflows/ci.yml" rel="noopener noreferrer"&gt;look at this old commit&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Instead, I'm going to show you the &lt;em&gt;new&lt;/em&gt; job definition, and the matrix setup that goes with it. One thing I learned while doing this is that GitHub's workflow documentation does not really give the matrix feature justice because they use such simple examples. Here's the matrix definition I came up with for our new job definition. I'll briefly explain how the matrix feature works in case you are unfamiliar with it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/ci.yml&lt;/span&gt;
&lt;span class="na"&gt;tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.make.name }} (${{ matrix.os }})&lt;/span&gt;
  &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.os }}&lt;/span&gt;
  &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;fail-fast&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;macos-latest&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;rust&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;stable&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;make&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Clippy&lt;/span&gt;
          &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;check-clippy"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Unit tests&lt;/span&gt;
          &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;build-all-test&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;run-all-unit-test"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Doc tests&lt;/span&gt;
          &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;run-all-doc-test"&lt;/span&gt;
      &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
          &lt;span class="na"&gt;sccache-path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/home/runner/.cache/sccache&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;macos-latest&lt;/span&gt;
          &lt;span class="na"&gt;sccache-path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/Users/runner/Library/Caches/Mozilla.sccache&lt;/span&gt;
      &lt;span class="na"&gt;exclude&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;macos-latest&lt;/span&gt;
          &lt;span class="na"&gt;rust&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;stable&lt;/span&gt;
          &lt;span class="na"&gt;make&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Clippy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The part of this that we're interested in is the &lt;code&gt;matrix&lt;/code&gt; object. Notice that it is a proper key-value object, it has keys &lt;code&gt;os&lt;/code&gt;, &lt;code&gt;rust&lt;/code&gt;, and &lt;code&gt;make&lt;/code&gt;. These keys are arbitrary, you can choose any names that you want for them. I like to think of them as the "dimensions" of the matrix. The value at each of these keys must be a list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For the key &lt;code&gt;os&lt;/code&gt;, the list is &lt;code&gt;[ubuntu-latest, macos-latest]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;For the key &lt;code&gt;rust&lt;/code&gt;, we have a list with a single element: &lt;code&gt;[stable]&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;And even though the value under &lt;code&gt;make&lt;/code&gt; looks different, notice that it is still a list,
it is just a list of more yaml objects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When GitHub Actions reads your job definition, it performs a sort of cross-product on each entry in your matrix, creating a list of all the combinations of items in your lists. For this matrix definition, GitHub will generate the following list of configurations to run the job with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
  &lt;span class="na"&gt;rust&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;stable&lt;/span&gt;
  &lt;span class="na"&gt;make&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Clippy&lt;/span&gt;
    &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;check-clippy"&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;macos-latest&lt;/span&gt;
  &lt;span class="na"&gt;rust&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;stable&lt;/span&gt;
  &lt;span class="na"&gt;make&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Clippy&lt;/span&gt;
    &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;check-clippy"&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
  &lt;span class="na"&gt;rust&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;stable&lt;/span&gt;
  &lt;span class="na"&gt;make&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Unit tests&lt;/span&gt;
    &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;build-all-test&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;run-all-unit-test"&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;macos-latest&lt;/span&gt;
  &lt;span class="na"&gt;rust&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;stable&lt;/span&gt;
  &lt;span class="na"&gt;make&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Unit tests&lt;/span&gt;
    &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;build-all-test&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;run-all-unit-test"&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
  &lt;span class="na"&gt;rust&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;stable&lt;/span&gt;
  &lt;span class="na"&gt;make&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Doc tests&lt;/span&gt;
    &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;run-all-doc-test"&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;macos-latest&lt;/span&gt;
  &lt;span class="na"&gt;rust&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;stable&lt;/span&gt;
  &lt;span class="na"&gt;make&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Doc tests&lt;/span&gt;
    &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;run-all-doc-test"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the rest of the job definition, you can access the fields of the active configuration using the &lt;code&gt;${{ matrix.KEY }}&lt;/code&gt; syntax. You can see in the job definition above that this is used in the line &lt;code&gt;runs-on: ${{ matrix.os }}&lt;/code&gt;, which is how we tell the runner which type of machine to run the job on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Include and Exclude rules
&lt;/h3&gt;

&lt;p&gt;You may be wondering right now, "Hey, what happened to &lt;code&gt;include&lt;/code&gt; and &lt;code&gt;exclude&lt;/code&gt;? They didn't get mixed into the matrix!", and you would be right. &lt;code&gt;include&lt;/code&gt; and &lt;code&gt;exclude&lt;/code&gt; are special keys that allow you to manually edit the resulting configurations.&lt;/p&gt;

&lt;p&gt;When writing a rule for &lt;code&gt;include&lt;/code&gt;, we are essentially writing a pattern that matches against the output configurations and may add new data to them. Let's look at the effect of a specific &lt;code&gt;include&lt;/code&gt; rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
          &lt;span class="na"&gt;sccache-path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/home/runner/.cache/sccache&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This says: "for any configuration that has &lt;code&gt;os: ubuntu-latest&lt;/code&gt;, add another key that has &lt;code&gt;sccache-path: /home/runner/.cache/sccache&lt;/code&gt;". If we apply this rule to our output configuration, it would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;  - os: ubuntu-latest
    rust: stable
    make:
      name: Unit tests
      task: "build-all-test run-all-unit-test"
&lt;span class="gi"&gt;+   sccache-path: /home/runner/.cache/sccache
&lt;/span&gt;  - os: macos-latest
    rust: stable
    make:
      name: Unit tests
      task: "build-all-test run-all-unit-test"
  - os: ubuntu-latest
    rust: stable
    make:
      name: Doc tests
      task: "run-all-doc-test"
&lt;span class="gi"&gt;+   sccache-path: /home/runner/.cache/sccache
&lt;/span&gt;  - os: macos-latest
    rust: stable
    make:
      name: Doc tests
      task: "run-all-doc-test"
  - os: ubuntu-latest
    rust: stable
    make:
      name: Clippy
      task: "check-clippy"
&lt;span class="gi"&gt;+   sccache-path: /home/runner/.cache/sccache
&lt;/span&gt;  - os: macos-latest
    rust: stable
    make:
      name: Clippy
      task: "check-clippy"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: the &lt;code&gt;+&lt;/code&gt; at the beginning of the green lines is not part of the workflow file, &lt;br&gt;
it is part of the diff syntax I'm using to show you that this line was added&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Similarly, we can use &lt;code&gt;exclude&lt;/code&gt; rules to describe objects in the output configuration to discard. For example, we currently have two configurations that will cause Clippy to be run, but we really only need Clippy to run once. Let's look at the effect the following &lt;code&gt;exclude&lt;/code&gt; rule from our matrix has on the output configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;exclude&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;macos-latest&lt;/span&gt;
    &lt;span class="na"&gt;rust&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;stable&lt;/span&gt;
    &lt;span class="na"&gt;make&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Clippy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When this rule gets applied, it removes the entry for Clippy on MacOS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;  - os: ubuntu-latest
    rust: stable
    make:
      name: Unit tests
      task: "build-all-test run-all-unit-test"
  - os: macos-latest
    rust: stable
    make:
      name: Unit tests
      task: "build-all-test run-all-unit-test"
  - os: ubuntu-latest
    rust: stable
    make:
      name: Doc tests
      task: "run-all-doc-test"
  - os: macos-latest
    rust: stable
    make:
      name: Doc tests
      task: "run-all-doc-test"
  - os: ubuntu-latest
    rust: stable
    make:
      name: Clippy
      task: "check-clippy"
&lt;span class="gd"&gt;- - os: macos-latest
-   rust: stable
-   make:
-     name: Clippy
-     task: "check-clippy"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: The first &lt;code&gt;-&lt;/code&gt; on the red lines is also not part of the workflow file,&lt;br&gt;
it is part of the removed-lines diff syntax&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Putting it all together, our matrix definition with all the include and exclude rules applied will look like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;  - os: ubuntu-latest
    rust: stable
    make:
      name: Unit tests
      task: "build-all-test run-all-unit-test"
&lt;span class="gi"&gt;+   sccache-path: /home/runner/.cache/sccache
&lt;/span&gt;  - os: macos-latest
    rust: stable
    make:
      name: Unit tests
      task: "build-all-test run-all-unit-test"
&lt;span class="gi"&gt;+   sccache-path: /Users/runner/Library/Caches/Mozilla.sccache
&lt;/span&gt;  - os: ubuntu-latest
    rust: stable
    make:
      name: Doc tests
      task: "run-all-doc-test"
&lt;span class="gi"&gt;+   sccache-path: /home/runner/.cache/sccache
&lt;/span&gt;  - os: macos-latest
    rust: stable
    make:
      name: Doc tests
      task: "run-all-doc-test"
&lt;span class="gi"&gt;+   sccache-path: /Users/runner/Library/Caches/Mozilla.sccache
&lt;/span&gt;  - os: ubuntu-latest
    rust: stable
    make:
      name: Clippy
      task: "check-clippy"
&lt;span class="gi"&gt;+   sccache-path: /home/runner/.cache/sccache
&lt;/span&gt;&lt;span class="gd"&gt;- - os: macos-latest
-   rust: stable
-   make:
-     name: Clippy
-     task: "check-clippy"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok, awesome. So now we have a strategy for adding new configurations as well as for tweaking options on specific configurations.&lt;/p&gt;

&lt;p&gt;If you're trying to consolidate a bunch of duplicate jobs, a good strategy is to start identifying the small pieces of each job that are different from the others, and put those as options in the matrix. Next I'll walk through the rest of the job definition and talk about how we set it up to meet our Rust project needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optimizing Rust's build speed with &lt;code&gt;sccache&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;For the rest of this post I'll just be talking about how I set up the rest of this job definition to build and test our Rust binaries using &lt;code&gt;sccache&lt;/code&gt;. &lt;code&gt;sccache&lt;/code&gt; is a tool built by Mozilla that can cache the output of &lt;code&gt;rustc&lt;/code&gt; and re-use those build artifacts if nothing has changed. &lt;/p&gt;

&lt;p&gt;The basic setup we'll need in our job is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install &lt;a href="https://github.com/mozilla/sccache" rel="noopener noreferrer"&gt;&lt;code&gt;sccache&lt;/code&gt;&lt;/a&gt; for the OS we're running on&lt;/li&gt;
&lt;li&gt;Use the &lt;a href="https://github.com/actions/cache" rel="noopener noreferrer"&gt;GitHub actions cache&lt;/a&gt; to save the &lt;code&gt;sccache&lt;/code&gt; cache directory&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let me start by just posting the job definition up front, including the matrix definition we've already seen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/ci.yml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CI&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.make.name }} (${{ matrix.os }})&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.os }}&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;fail-fast&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;macos-latest&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;rust&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;stable&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;make&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Clippy&lt;/span&gt;
            &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;check-clippy"&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Unit tests&lt;/span&gt;
            &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;build-all-test&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;run-all-unit-test"&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Doc tests&lt;/span&gt;
            &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;run-all-doc-test"&lt;/span&gt;
        &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
            &lt;span class="na"&gt;sccache-path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/home/runner/.cache/sccache&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;macos-latest&lt;/span&gt;
            &lt;span class="na"&gt;sccache-path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/Users/runner/Library/Caches/Mozilla.sccache&lt;/span&gt;
        &lt;span class="na"&gt;exclude&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;macos-latest&lt;/span&gt;
            &lt;span class="na"&gt;rust&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;stable&lt;/span&gt;
            &lt;span class="na"&gt;make&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Clippy&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;RUST_BACKTRACE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;full&lt;/span&gt;
      &lt;span class="na"&gt;RUSTC_WRAPPER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sccache&lt;/span&gt;
      &lt;span class="na"&gt;RUSTV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.rust }}&lt;/span&gt;
      &lt;span class="na"&gt;SCCACHE_CACHE_SIZE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2G&lt;/span&gt;
      &lt;span class="na"&gt;SCCACHE_DIR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.sccache-path }}&lt;/span&gt;
      &lt;span class="c1"&gt;# SCCACHE_RECACHE: 1 # Uncomment this to clear cache, then comment it back out&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install sccache (ubuntu-latest)&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;matrix.os == 'ubuntu-latest'&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;LINK&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/mozilla/sccache/releases/download&lt;/span&gt;
          &lt;span class="na"&gt;SCCACHE_VERSION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.2.13&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;SCCACHE_FILE=sccache-$SCCACHE_VERSION-x86_64-unknown-linux-musl&lt;/span&gt;
          &lt;span class="s"&gt;mkdir -p $HOME/.local/bin&lt;/span&gt;
          &lt;span class="s"&gt;curl -L "$LINK/$SCCACHE_VERSION/$SCCACHE_FILE.tar.gz" | tar xz&lt;/span&gt;
          &lt;span class="s"&gt;mv -f $SCCACHE_FILE/sccache $HOME/.local/bin/sccache&lt;/span&gt;
          &lt;span class="s"&gt;echo "$HOME/.local/bin" &amp;gt;&amp;gt; $GITHUB_PATH&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install sccache (macos-latest)&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;matrix.os == 'macos-latest'&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;brew update&lt;/span&gt;
          &lt;span class="s"&gt;brew install sccache&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Rust ${{ matrix.rust }}&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions-rs/toolchain@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;toolchain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.rust }}&lt;/span&gt;
          &lt;span class="na"&gt;profile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;minimal&lt;/span&gt;
          &lt;span class="na"&gt;override&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Cache cargo registry&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/cache@v2&lt;/span&gt;
        &lt;span class="na"&gt;continue-on-error&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;~/.cargo/registry&lt;/span&gt;
            &lt;span class="s"&gt;~/.cargo/git&lt;/span&gt;
          &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}&lt;/span&gt;
          &lt;span class="na"&gt;restore-keys&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;${{ runner.os }}-cargo-&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Save sccache&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/cache@v2&lt;/span&gt;
        &lt;span class="na"&gt;continue-on-error&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.sccache-path }}&lt;/span&gt;
          &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ runner.os }}-sccache-${{ hashFiles('**/Cargo.lock') }}&lt;/span&gt;
          &lt;span class="na"&gt;restore-keys&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;${{ runner.os }}-sccache-&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Start sccache server&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sccache --start-server&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.make.name }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;make ${{ matrix.make.task }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Print sccache stats&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sccache --show-stats&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Stop sccache server&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sccache --stop-server || &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This job definition is 95% setup and about 5% task-running. In fact, the entirety of our project-specific building and testing specifications are defined inside our &lt;code&gt;Makefile&lt;/code&gt;, so the step where we call &lt;code&gt;make ${{ matrix.make.task }}&lt;/code&gt; is the only step that is unique to our project, the rest could be re-used as boilerplate for other Rust projects ;)&lt;/p&gt;

&lt;p&gt;Let's start with the &lt;code&gt;env&lt;/code&gt; section. First, we set &lt;code&gt;RUST_BACKTRACE: full&lt;/code&gt; because if any of our tests panic we want to know why. &lt;code&gt;RUSTV&lt;/code&gt; is an environment variable that we use inside our &lt;code&gt;Makefile&lt;/code&gt; to tell cargo commands which release channel to use.&lt;/p&gt;

&lt;p&gt;The rest of the env has to do with &lt;a href="https://github.com/mozilla/sccache" rel="noopener noreferrer"&gt;&lt;code&gt;sccache&lt;/code&gt;&lt;/a&gt;. As I mentioned before, we want to use &lt;a href="https://github.com/mozilla/sccache" rel="noopener noreferrer"&gt;&lt;code&gt;sccache&lt;/code&gt;&lt;/a&gt; to reduce the number of times we need to re-build crates when possible.&lt;/p&gt;

&lt;p&gt;Here is a quick summary of the other variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;RUSTC_WRAPPER: sccache&lt;/code&gt; is read by &lt;code&gt;cargo&lt;/code&gt; itself and tells it to run compiler commands using &lt;code&gt;sccache rustc ...&lt;/code&gt; rather than just &lt;code&gt;rustc ...&lt;/code&gt;. This is the easiest way to use &lt;code&gt;sccache&lt;/code&gt; with Rust.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;SCCACHE_CACHE_SIZE&lt;/code&gt; tells sccache the maximum size you want to allow your cache to grow to. This is one of the most important things to get right: if you set this too small and the cache runs out of space, &lt;code&gt;sccache&lt;/code&gt; will start evicting artifacts from the cache and you will end up recompiling more than you need to.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;SCCACHE_DIR&lt;/code&gt; tells sccache where to store build artifacts. This path is different on different OS's, which is why we assign it using a matrix parameter.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now let's run through the step definitions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a pretty standard GH Actions step that just checks out your git repository in the current directory. This gives further steps the ability to build or interact with your code.&lt;/p&gt;

&lt;p&gt;Next up is installing &lt;code&gt;sccache&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install sccache (ubuntu-latest)&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;matrix.os == 'ubuntu-latest'&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;LINK&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/mozilla/sccache/releases/download&lt;/span&gt;
          &lt;span class="na"&gt;SCCACHE_VERSION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.2.13&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;SCCACHE_FILE=sccache-$SCCACHE_VERSION-x86_64-unknown-linux-musl&lt;/span&gt;
          &lt;span class="s"&gt;mkdir -p $HOME/.local/bin&lt;/span&gt;
          &lt;span class="s"&gt;curl -L "$LINK/$SCCACHE_VERSION/$SCCACHE_FILE.tar.gz" | tar xz&lt;/span&gt;
          &lt;span class="s"&gt;mv -f $SCCACHE_FILE/sccache $HOME/.local/bin/sccache&lt;/span&gt;
          &lt;span class="s"&gt;echo "$HOME/.local/bin" &amp;gt;&amp;gt; $GITHUB_PATH&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install sccache (macos-latest)&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;matrix.os == 'macos-latest'&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;brew update&lt;/span&gt;
          &lt;span class="s"&gt;brew install sccache&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a good example of adapting steps according to a matrix. Depending on whether we are running on &lt;code&gt;ubuntu-latest&lt;/code&gt; or &lt;code&gt;macos-latest&lt;/code&gt;, there is a different procedure for installing &lt;code&gt;sccache&lt;/code&gt;. Notice that only one of these two steps will ever run in a given execution of a job: the &lt;code&gt;if:&lt;/code&gt; conditional checks which &lt;code&gt;matrix.os&lt;/code&gt; value is specified in this job instance.&lt;/p&gt;

&lt;p&gt;This is also a great example of a good opportunity for creating a reusable GitHub Action definition. If there's an action definition out there for more easily installing &lt;code&gt;sccache&lt;/code&gt; (or if one of you readers decides to put one together), let me know!&lt;/p&gt;

&lt;p&gt;The next step just installs the Rust toolchain. If you have used Rust on GitHub actions before you have probably seen this one already:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Rust ${{ matrix.rust }}&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions-rs/toolchain@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;toolchain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.rust }}&lt;/span&gt;
          &lt;span class="na"&gt;profile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;minimal&lt;/span&gt;
          &lt;span class="na"&gt;override&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even though in our matrix we only have one value, &lt;code&gt;rust: [stable]&lt;/code&gt;, it is nice to use &lt;code&gt;toolchain: ${{ matrix.rust }}&lt;/code&gt; so that in the future if you (or a coworker of yours) decides to run your workflow against other release channels, the only edit that will need to be made is in the matrix.&lt;/p&gt;

&lt;p&gt;This next one has to do with &lt;code&gt;sccache&lt;/code&gt; again, and it has to do with saving the cache directory itself. When using the free GitHub Action runners, jobs are always run on a fresh instance of a container or virtual machine somewhere. This means that our sccache would have to start from scratch on every job and we wouldn't get any benefit out of it. To fix this, we can use the &lt;code&gt;actions/cache@v2&lt;/code&gt; action to preserve certain directories between job runs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Cache cargo registry&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/cache@v2&lt;/span&gt;
        &lt;span class="na"&gt;continue-on-error&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;~/.cargo/registry&lt;/span&gt;
            &lt;span class="s"&gt;~/.cargo/git&lt;/span&gt;
          &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}&lt;/span&gt;
          &lt;span class="na"&gt;restore-keys&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;${{ runner.os }}-cargo-&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Save sccache&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/cache@v2&lt;/span&gt;
        &lt;span class="na"&gt;continue-on-error&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.sccache-path }}&lt;/span&gt;
          &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ runner.os }}-sccache-${{ hashFiles('**/Cargo.lock') }}&lt;/span&gt;
          &lt;span class="na"&gt;restore-keys&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;${{ runner.os }}-sccache-&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;actions/cache@v2&lt;/code&gt; action lets us specify directories that the GitHub runner will save at the end of each job, and attempt to restore at the beginning of the next job. You can have multiple different types of things you want to cache, so you have to choose a &lt;code&gt;key&lt;/code&gt; for each cache to be labeled with when it gets saved. You also need to give it &lt;code&gt;restore-keys&lt;/code&gt;&lt;br&gt;
that tell it how to choose an existing saved cache that you want to restore from. You can think of &lt;code&gt;restore-keys&lt;/code&gt; as a pattern for matching some prefix of an actual key: the more specifically a key matches your restore-key, the higher precedence it will have.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;On a side note about GitHub caches, does anybody know whether it makes more sense to keep separate caches separate or to combine them? Here I have separate caches for the Cargo registry and for the Sccache directory, but does it make any practical difference?&lt;br&gt;
If you know one way or another, &lt;a href="https://twitter.com/RazzleRustacean" rel="noopener noreferrer"&gt;let me know&lt;/a&gt;!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Finally, we have the last steps of our jobs. Here we start the sccache server, run our Make task, then print statistics and stop the sccache server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Start sccache server&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sccache --start-server&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.make.name }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;make ${{ matrix.make.task }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Print sccache stats&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sccache --show-stats&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Stop sccache server&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sccache --stop-server || &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a couple of things you will want to check on when you first set this up to make sure you are actually getting benefit from the cache:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make sure your cargo commands are actually running sccache&lt;/li&gt;
&lt;li&gt;Make sure that sccache is actually hitting the cache rather than always rebuilding&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's an example of output when the &lt;code&gt;sccache&lt;/code&gt; server has just started up and has not processed any compile requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;sccache &lt;span class="nt"&gt;--show-stats&lt;/span&gt;
Compile requests                      8
Compile requests executed             0
Cache hits                            0
Cache misses                          0
Cache timeouts                        0
Cache &lt;span class="nb"&gt;read &lt;/span&gt;errors                     0
Forced recaches                       0
Cache write errors                    0
Compilation failures                  0
Cache errors                          0
Non-cacheable compilations            0
Non-cacheable calls                   8
Non-compilation calls                 0
Unsupported compiler calls            0
Average cache write               0.000 s
Average cache &lt;span class="nb"&gt;read &lt;/span&gt;miss           0.000 s
Average cache &lt;span class="nb"&gt;read &lt;/span&gt;hit            0.000 s
Failed distributed compilations       0

Non-cacheable reasons:
incremental                           7
crate-type                            1

Cache location                  Local disk: &lt;span class="s2"&gt;"/Users/runner/Library/Caches/Mozilla.sccache"&lt;/span&gt;
Cache size                          545 MiB
Max cache size                        2 GiB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you build your project and see this immediately afterwards, something went wrong. I would recommend running your build with &lt;code&gt;--verbose&lt;/code&gt; and double-checking that the commands cargo is running all begin with &lt;code&gt;sccache rustc&lt;/code&gt; rather than just &lt;code&gt;rustc&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If your verbose cargo output looks like this, your sccache setup is working:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   Compiling fluvio-spu v0.5.0 &lt;span class="o"&gt;(&lt;/span&gt;/Users/nick/infinyon/fluvio/src/spu&lt;span class="o"&gt;)&lt;/span&gt;
     Running &lt;span class="sb"&gt;`&lt;/span&gt;/Users/nick/.cargo/bin/sccache rustc &lt;span class="nt"&gt;--crate-name&lt;/span&gt; fluvio_spu &lt;span class="nt"&gt;--edition&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2018 ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But if you see output like this, sccache is not being invoked at all, and you should probably double-check that your &lt;code&gt;RUSTC_WRAPPER&lt;/code&gt; environment variable is set properly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   Compiling fluvio-spu v0.5.0 &lt;span class="o"&gt;(&lt;/span&gt;/Users/nick/infinyon/fluvio/src/spu&lt;span class="o"&gt;)&lt;/span&gt;
     Running &lt;span class="sb"&gt;`&lt;/span&gt;rustc &lt;span class="nt"&gt;--crate-name&lt;/span&gt; fluvio_spu &lt;span class="nt"&gt;--edition&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2018
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Verifying the &lt;code&gt;sccache&lt;/code&gt; results
&lt;/h3&gt;

&lt;p&gt;When you have set things up so that sccache is properly running, you will see stats that have actual numbers in them rather than zeros. The next step is to make sure that those numbers are telling you that you hit the cache rather than rebuilding (missing the cache).&lt;/p&gt;

&lt;p&gt;You might miss the cache for a couple of reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is the first time you have built using sccache and so you are populating the cache for the first time

&lt;ul&gt;
&lt;li&gt;In this case, just run your job again to test if you hit the cache on the second go-round&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Something went wrong when re-loading the cache directory (e.g. from the GitHub cache).&lt;/li&gt;

&lt;li&gt;You have added a ton of dependencies to your project that you haven't built before, so they're not in the cache&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Here is an example of what your stats might look like if you have missed the cache. Notice that the number of hits is actually not zero, it's just a very low number. I think this is probably because of caching duplicate dependencies within your dependency tree. However, if you see these results after a build, then your build did not really benefit from&lt;br&gt;
&lt;code&gt;sccache&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Compile requests                    484
Compile requests executed           343
Cache hits                           13
Cache hits &lt;span class="o"&gt;(&lt;/span&gt;Rust&lt;span class="o"&gt;)&lt;/span&gt;                    13
Cache misses                        330
Cache misses &lt;span class="o"&gt;(&lt;/span&gt;Rust&lt;span class="o"&gt;)&lt;/span&gt;                 330
Cache timeouts                        0
Cache &lt;span class="nb"&gt;read &lt;/span&gt;errors                     0
Forced recaches                       0
Cache write errors                    0
Compilation failures                  0
Cache errors                        313
Cache errors &lt;span class="o"&gt;(&lt;/span&gt;Rust&lt;span class="o"&gt;)&lt;/span&gt;                 313
Non-cacheable compilations            0
Non-cacheable calls                 141
Non-compilation calls                 0
Unsupported compiler calls            0
Average cache write               0.000 s
Average cache &lt;span class="nb"&gt;read &lt;/span&gt;miss           2.249 s
Average cache &lt;span class="nb"&gt;read &lt;/span&gt;hit            0.001 s
Failed distributed compilations       0

Non-cacheable reasons:
crate-type                          111
incremental                          28
-                                     2

Cache location                  Local disk: &lt;span class="s2"&gt;"/Users/runner/Library/Caches/Mozilla.sccache"&lt;/span&gt;
Cache size                            4 GiB
Max cache size                       10 GiB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Usually if you get these results, all you need to do is run the build again, and the second build will be able to leverage the pre-compiled artifacts from the previous run.&lt;/p&gt;

&lt;p&gt;The stats from the second build might look more like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Compile requests                    481
Compile requests executed           330
Cache hits                          318
Cache hits &lt;span class="o"&gt;(&lt;/span&gt;Rust&lt;span class="o"&gt;)&lt;/span&gt;                   318
Cache misses                         12
Cache misses &lt;span class="o"&gt;(&lt;/span&gt;Rust&lt;span class="o"&gt;)&lt;/span&gt;                  12
Cache timeouts                        0
Cache &lt;span class="nb"&gt;read &lt;/span&gt;errors                     0
Forced recaches                       0
Cache write errors                    0
Compilation failures                  0
Cache errors                          0
Non-cacheable compilations            0
Non-cacheable calls                 151
Non-compilation calls                 0
Unsupported compiler calls            0
Average cache write               0.001 s
Average cache &lt;span class="nb"&gt;read &lt;/span&gt;miss           1.242 s
Average cache &lt;span class="nb"&gt;read &lt;/span&gt;hit            0.001 s
Failed distributed compilations       0

Non-cacheable reasons:
crate-type                          108
incremental                          41
-                                     2

Cache location                  Local disk: &lt;span class="s2"&gt;"/Users/runner/Library/Caches/Mozilla.sccache"&lt;/span&gt;
Cache size                          545 MiB
Max cache size                        2 GiB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that we have many more cache hits than cache misses, that's the indicator that we are getting good value out of &lt;code&gt;sccache&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The end result
&lt;/h2&gt;

&lt;p&gt;When you push this workflow and trigger it, you'll see a job start running for each combination of matrix parameters you provided:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.fluvio.io%2Fblog%2Fimages%2Frust-workflows%2Fworkflow-jobs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.fluvio.io%2Fblog%2Fimages%2Frust-workflows%2Fworkflow-jobs.png" alt="Workflow jobs as shown by the GitHub Actions dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I took this screenshot from our full workflow file which includes a job for Rustfmt. The rest of the jobs in this list were generated from the single job definition I showed above.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Thanks for reading, I hope this can be helpful to others who are also setting up GitHub actions with Rust projects. If you want to learn more about the Fluvio project, feel free to &lt;a href="https://github.com/infinyon/fluvio/" rel="noopener noreferrer"&gt;check out our GitHub&lt;/a&gt; or come &lt;a href="https://discordapp.com/invite/bBG2dTz" rel="noopener noreferrer"&gt;join our community Discord&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
