I was recently working on the CI setup for my ubi
project
with a couple goals. First, I wanted to stop using
unmaintained actions from
the actions-rs
organization. Second, I wanted to add many more
release targets for different platforms and architectures.
Replacing some of what I used from actions-rs
was pretty easy:
But what about actions-rs/cargo
? You’d think that running
cargo
wouldn’t even need an action, and you’d be right. Except that this action doesn’t just run
cargo
. If you set its use-cross
parameter to true it uses
cross
to do the build instead of cargo
, making it
trivial to cross-compile a Rust project.
I was already doing some cross-compilation for all my Rust projects, and I wanted to add more. So I
needed to replace this action with something of my own. I couldn’t find any already written,
probably because everyone who moved away from actions-rs
kept saying things like “this is too
trivial to need an action, it’s just running cargo build
.”
So for my first pass, I simply embedded the build pieces directly in the GitHub workflow for UBI,
like this:
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
100
101
102
103
104
105
106
107
108
109
110
| jobs:
release:
name: Release - ${{ matrix.platform.os_name }}
if: startsWith( github.ref, 'refs/tags/v' ) || github.ref == 'refs/tags/test-release'
strategy:
matrix:
platform:
- os_name: FreeBSD-x86_64
os: ubuntu-20.04
target: x86_64-unknown-freebsd
bin: ubi
name: ubi-FreeBSD-x86_64.tar.gz
cross: true
cargo_command: ./cross
- os_name: Linux-x86_64
os: ubuntu-20.04
target: x86_64-unknown-linux-musl
bin: ubi
name: ubi-Linux-x86_64-musl.tar.gz
cross: false
cargo_command: cargo
- os_name: Windows-aarch64
os: windows-latest
target: aarch64-pc-windows-msvc
bin: ubi.exe
name: ubi-Windows-aarch64.zip
cross: false
cargo_command: cargo
- os_name: macOS-x86_64
os: macOS-latest
target: x86_64-apple-darwin
bin: ubi
name: ubi-Darwin-x86_64.tar.gz
cross: false
cargo_command: cargo
runs-on: ${{ matrix.platform.os }}
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install toolchain if not cross-compiling
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.platform.target }}
if: ${{ !matrix.platform.cross }}
- name: Install musl-tools on Linux
run: sudo apt-get update --yes && sudo apt-get install --yes musl-tools
if: contains(matrix.platform.os, 'ubuntu') && !matrix.platform.cross
- name: Install cross if cross-compiling (*nix)
id: cross-nix
shell: bash
run: |
set -e
export TARGET="$HOME/bin"
mkdir -p "$TARGET"
./bootstrap/bootstrap-ubi.sh
"$HOME/bin/ubi" --project cross-rs/cross --matching musl --in .
if: matrix.platform.cross && !contains(matrix.platform.os, 'windows')
- name: Install cross if cross-compiling (Windows)
id: cross-windows
shell: powershell
run: |
.\bootstrap\bootstrap-ubi.ps1
.\ubi --project cross-rs/cross --in .
if: matrix.platform.cross && contains(matrix.platform.os, 'windows')
- name: Build binary (*nix)
shell: bash
run: |
${{ matrix.platform.cargo_command }} build --locked --release --target ${{ matrix.platform.target }}
if: ${{ !contains(matrix.platform.os, 'windows') }}
- name: Build binary (Windows)
# We have to use the platform's native shell. If we use bash on
# Windows then OpenSSL complains that the Perl it finds doesn't use
# the platform's native paths and refuses to build.
shell: powershell
run: |
& ${{ matrix.platform.cargo_command }} build --locked --release --target ${{ matrix.platform.target }}
if: contains(matrix.platform.os, 'windows')
- name: Strip binary
shell: bash
run: |
strip target/${{ matrix.platform.target }}/release/${{ matrix.platform.bin }}
# strip doesn't work with cross-arch binaries on Linux or Windows.
if: ${{ !(matrix.platform.cross || matrix.platform.target == 'aarch64-pc-windows-msvc') }}
- name: Package as archive
shell: bash
run: |
cd target/${{ matrix.platform.target }}/release
if [[ "${{ matrix.platform.os }}" == "windows-latest" ]]; then
7z a ../../../${{ matrix.platform.name }} ${{ matrix.platform.bin }}
else
tar czvf ../../../${{ matrix.platform.name }} ${{ matrix.platform.bin }}
fi
cd -
- name: Publish release artifacts
uses: actions/upload-artifact@v3
with:
name: ubi-${{ matrix.platform.os_name }}
path: "ubi*"
if: github.ref == 'refs/tags/test-release'
- name: Publish GitHub release
uses: softprops/action-gh-release@v1
with:
draft: true
files: "ubi*"
body_path: Changes.md
if: startsWith( github.ref, 'refs/tags/v' )
|
I’ve actually cut quite a bit of this out, notably the other 15 or so platforms in the matrix.
Here are the highlights:
- If it needs
cross
it will install that from its latest GitHub release using ubi
itself. This
is much faster than compiling cross
by running cargo install
. Otherwise it uses
dtolnay/rust-toolchain
to install the Rust toolchain. - It will run the build command (
cross
or cargo
) in the appropriate shell for the platform.
Using the right shell matters for some corner cases. Notably, ubi
now depends on
the openssl
crate with the vendored
feature enabled. With
that feature, the crate will actually compile OpenSSL and statically link it into your binary.
But OpenSSL fails to compile in an msys shell on Windows!
And that’s really it. I’ve extracted the generic bits and turned it into a reusable action called
Build Rust Projects with Cross.
You can see it in use in
the release job for ubi
.
The YAML for precious
is nearly identical (which suggests that maybe I need to write another
action).