Rust
is a great programming language, but it’s compiler is known to be slow. The main reason is that it checks so many things during the compilation in order to provides memory and concurrency safety. Like in many other cases, pipelines may be speeded up by caching, but it is very important to set the caching properly, otherwise it won’t help. Besides compiling your code may be slow, you also may want to use some
cargo
tools to lint and check your code and this will require to be compiled as well, if you don’t want to download binaries. Some popular tools are:
I keep all my projects at GitHub, both public and private and I like the features of
GitHub Actions
. They support caching and for this purpose I use
actions/cache
. Fortunately, there are actions/cache
snippets for all major languages including
Rust
. Before I implemented this action properly, my caching didn’t work so I had lint, check and test actions runtime at up to 15 minutes. After fixing it, all of these steps last just ~1 minute. But let’s show the
reusable workflow
that I use:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
| on:
workflow_call:
inputs:
rust-version:
type: string
required: false
default: nightly
jobs:
check:
runs-on: ubuntu-latest
steps:
- name: Check out
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ inputs.rust-version }}
override: true
components: rustfmt, clippy
- name: Set up cargo cache
uses: actions/cache@v3
continue-on-error: false
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-
- name: Lint
run: |
cargo fmt --all -- --check
cargo clippy -- -D warnings
- name: Install cargo check tools
run: |
cargo install --locked cargo-deny || true
cargo install --locked cargo-outdated || true
cargo install --locked cargo-udeps || true
cargo install --locked cargo-audit || true
cargo install --locked cargo-pants || true
- name: Check
run: |
cargo deny check
cargo outdated --exit-code 1
cargo udeps
rm -rf ~/.cargo/advisory-db
cargo audit
cargo pants
- name: Test
run: cargo test
|
As you can see, I just used the recommended setup for actions/cache
but then some cargo install
commands used to fail in subsequent runs when binary is already installed in ~/.cargo/bin
. So this is why I had to ignore these errors by adding || true
after each cargo install command
. There is also some funny error related to the cached advisory-db, but as temporary solution I just delete it before running cargo audit
and cargo pants
. I will address it properly at some time later.
This workflow can be invoked from any Rust project by:
1
2
3
4
5
6
7
8
9
10
| name: pipeline
on:
push:
pull_request:
workflow_dispatch:
jobs:
check:
uses: ectobit/reusable-workflows/.github/workflows/rust-check.yaml@main
|
But this is not all, what about the build, actually specifically docker build
? By using registry
type of caching in
docker/build-push-action
, I speeded up the builds from ~50 to ~2 minutes. At the end, my project’s pipeline speeded up from ~65 minutes in total to just ~2 minutes. Yeah, this is ~30 times faster, great. Again, here is the reusable workflow which may be used by other projects as well, not just Rust:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
| on:
workflow_call:
inputs:
image:
type: string
required: true
platforms:
type: string
required: false
default: linux/amd64,linux/arm64
license:
type: string
required: false
default: BSD-2-Clause-Patent
vendor:
type: string
required: false
default: ectobit.com
hadolint-dockerfile:
type: string
required: false
default: Dockerfile
hadolint-ignore:
type: string
required: false
default: ''
build-args:
type: string
required: false
default: ''
secrets:
container-registry-username:
required: true
container-registry-password:
required: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out
uses: actions/checkout@v3
- name: Lint Dockerfile with ignore
if: ${{ inputs.hadolint-ignore != ''}}
uses: hadolint/hadolint-action@v2.1.0
with:
dockerfile: ${{ inputs.hadolint-dockerfile }}
ignore: ${{ inputs.hadolint-ignore }}
- name: Lint Dockerfile
if: ${{ inputs.hadolint-ignore == ''}}
uses: hadolint/hadolint-action@v2.1.0
with:
dockerfile: ${{ inputs.hadolint-dockerfile }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.0.0
- name: Set up tags
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ inputs.image }}
labels: |
org.opencontainers.image.licenses=${{ inputs.license }}
org.opencontainers.image.vendor=${{ inputs.vendor }}
tags: |
type=schedule
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=edge
type=sha
- name: Login to container registry
uses: docker/login-action@v2.0.0
with:
username: ${{ secrets.container-registry-username }}
password: ${{ secrets.container-registry-password }}
- name: Build and push image with build args to container registry
uses: docker/build-push-action@v3.0.0
if: ${{ inputs.build-args != ''}}
with:
build-args: ${{ inputs.build-args }}
platforms: ${{ inputs.platforms }}
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=${{ inputs.image }}:buildcache
cache-to: type=registry,ref=${{ inputs.image }}:buildcache,mode=max
- name: Build and push image to container registry
uses: docker/build-push-action@v3.0.0
if: ${{ inputs.build-args == ''}}
with:
platforms: ${{ inputs.platforms }}
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=${{ inputs.image }}:buildcache
cache-to: type=registry,ref=${{ inputs.image }}:buildcache,mode=max
|
This can be invoked in your project by:
1
2
3
4
5
6
7
8
| build:
uses: ectobit/reusable-workflows/.github/workflows/buildx.yaml@main
needs: check
with:
image: ectobit/bond
secrets:
container-registry-username: ${{ secrets.REGISTRY_USERNAME }}
container-registry-password: ${{ secrets.REGISTRY_PASSWORD }}
|
That’s all folks, you may try to use this in your projects and have faster build times.