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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
# ======================================================
# Continuous Integration: making sure the codebase works
# ======================================================
#
# This workflow tests modifications to 'domain', ensuring that 'domain' can be
# used by others successfully. It verifies certain aspects of the codebase,
# such as the formatting and feature flag combinations, and runs the full test
# suite. It runs on Ubuntu, Mac OS, and Windows.
name: CI
# When the workflow runs
# ----------------------
on:
# Execute when a pull request is (re-) opened or its head changes (e.g. new
# commits are added or the commit history is rewritten) ... but only if
# build-related files change.
pull_request:
paths:
- '**.rs'
- 'Cargo.toml'
- 'Cargo.lock'
- '.github/workflows/ci.yml'
# If a pull request is merged, at least one commit is added to the target
# branch. If the target is another pull request, it will be caught by the
# above event. We miss PRs that merge to a non-PR branch, except for the
# 'main' branch.
# Execute when a commit is pushed to 'main' (including merged PRs) or to a
# release tag ... but only if build-related files change.
push:
branches:
- 'main'
- 'releases/**'
paths:
- '**.rs'
- 'Cargo.toml'
- 'Cargo.lock'
- '.github/workflows/ci.yml'
# Rebuild 'main' every week. This will account for changes to dependencies
# and to Rust, either of which can trigger new failures. Rust releases are
# every 6 weeks, on a Thursday; this event runs every Friday.
schedule:
- cron: '0 10 * * FRI'
defaults:
run:
shell: bash
# Jobs
# ----------------------------------------------------------------------------
jobs:
# Check Formatting
# ----------------
#
# NOTE: This job is run even if no '.rs' files have changed. Inserting such
# a check would require using a separate workflow file or using third-party
# actions. Most commits do change '.rs' files, and 'cargo-fmt' is pretty
# fast, so optimizing this is not necessary.
check-fmt:
name: Check formatting
runs-on: ubuntu-latest
steps:
# Load the repository.
- name: Checkout repository
uses: actions/checkout@v4
# Set up the Rust toolchain.
#
# Disable the cache since it's not relevant for formatting.
- name: Set up Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
components: rustfmt
cache: false
# Do the actual formatting check.
- name: Check formatting
run: cargo fmt --all -- --check
# Determine MSRV
# --------------
#
# The MSRV needs to be determined as we will test 'domain' against the Rust
# compiler at that version.
determine-msrv:
name: Determine MSRV
runs-on: ubuntu-latest
outputs:
msrv: ${{ steps.determine-msrv.outputs.msrv }}
steps:
# Load the repository.
- name: Checkout repository
uses: actions/checkout@v4
# Determine the MSRV.
- name: Determine MSRV
id: determine-msrv
run: |
msrv=`cargo metadata --no-deps --format-version 1 | jq -r '.packages[]|select(.name=="domain")|.rust_version'`
echo "msrv=$msrv" >> "$GITHUB_OUTPUT"
# Check Feature Flags
# -------------------
#
# Rust does not provide any way to check that all possible feature flag
# combinations will succeed, so we need to try them manually here. We will
# assume this choice is not influenced by the OS or Rust version.
check-feature-flags:
name: Check feature flags
runs-on: ubuntu-latest
steps:
# Load the repository.
- name: Checkout repository
uses: actions/checkout@v4
# Set up the Rust toolchain.
- name: Set up Rust
id: setup-rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
cache: false
# Restore a cache of dependencies and 'target'.
- name: Restore a dependency cache
id: cache-restore
uses: actions/cache/restore@v4
with:
path: |
Cargo.cached.lock
~/.cargo
target/
# Cache by OS and Rust version.
key: ${{ runner.os }}-${{ steps.setup-rust.outputs.cachekey }}-
# Do the actual feature flag checks.
#
# NOTE: This does not benefit from the 'target' folder cached by a 'cargo
# check --all-features --all-targets' execution. Due to the minimal
# dependency set, it still runs fairly quickly.
- name: Check empty feature set
run: cargo check --all-targets --no-default-features
# Check the required feature flags for every example.
- name: Check required features of examples
run: |
# Scrape crate metadata and construct the right 'check' commands.
# Cargo deosn't have an option to select the right features for us.
# See: https://github.com/rust-lang/cargo/issues/4663
cargo metadata --no-deps --format-version 1 \
| jq -r '.packages[].targets[]|select(.kind|any(.=="example"))|{name,features:(.["required-features"]|join(","))}|"\(.name) \(.features)"' \
| while read -r name features; do
cargo check --example=$name --no-default-features --features=$features
done
# Check Minimal Versions
# ----------------------
#
# Ensure that 'domain' compiles with the oldest compatible versions of all
# packages, even those 'domain' depends upon indirectly.
check-minimal-versions:
name: Check minimal versions
runs-on: ubuntu-latest
env:
RUSTFLAGS: "-D warnings"
steps:
# Load the repository.
- name: Checkout repository
uses: actions/checkout@v4
# Set up the Rust toolchain.
- name: Set up Rust nightly
id: setup-rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: nightly, stable
cache: false
# TODO: Cache minimal-version dependencies?
# Lock all dependencies to their minimal versions.
- name: Lock dependencies to minimal versions
run: cargo +nightly update -Z minimal-versions
# Check that 'domain' compiles.
#
# NOTE: This does not benefit from the 'target' folder cached by a 'cargo
# check --all-features --all-targets' execution. It may be worthwhile to
# cache this 'target' folder separately (TODO).
- name: Check
run: cargo check --all-targets --all-features --locked
# Clippy
# ------
#
# We run Clippy separately, and only on nightly Rust because it offers a
# superset of the lints.
#
# 'cargo clippy' and 'cargo build' can share some state for fast execution,
# but it's faster to execute them in parallel than to establish an ordering
# between them.
clippy:
name: Clippy
runs-on: ubuntu-latest
env:
RUSTFLAGS: "-D warnings"
steps:
# Load the repository.
- name: Checkout repository
uses: actions/checkout@v4
# Set up the Rust toolchain.
- name: Set up Rust nightly
id: setup-rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: nightly
components: clippy
cache: false
# Restore a cache of dependencies and 'target'.
- name: Restore from cache
id: cache-restore
uses: actions/cache/restore@v4
with:
path: |
Cargo.cached.lock
~/.cargo
target/
# Cache by OS and Rust version.
key: ${{ runner.os }}-${{ steps.setup-rust.outputs.cachekey }}-
# Do the actually Clippy run.
- name: Check Clippy
run: cargo +nightly clippy --all-targets --all-features
# Test
# ----
#
# Ensure that 'domain' compiles and its test suite passes, on a large number
# of operating systems and Rust versions.
test:
name: Test
needs: determine-msrv
strategy:
matrix:
os:
rust:
runs-on: ${{ matrix.os }}
env:
RUSTFLAGS: "-D warnings"
DOMAIN_FEATURES: "--all-features"
steps:
# Load the repository.
- name: Checkout repository
uses: actions/checkout@v4
# Prepare the environment on Windows
- name: Prepare Windows environment
if: matrix.os == 'windows-latest'
shell: bash
run: |
# Cargo doesn't support enabling all but one feature, so determine the
# complete feature list and filter out 'openssl'.
features=`cargo metadata --no-deps --format-version 1 | jq -r '.packages[]|select(.name == "domain")|.features|keys|map(select(.!="openssl"))|join(",")'`
# Overwrite the 'DOMAIN_FEATURES' environment variable.
echo "DOMAIN_FEATURES=--features=$features" >> "$GITHUB_ENV"
# See <https://github.com/actions/runner-images/issues/12432>
echo "CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER=rust-lld" >> "$GITHUB_ENV"
# Set up the Rust toolchain.
- name: Set up Rust ${{ matrix.rust }}
id: setup-rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: ${{ matrix.rust }}
cache: false
# Restore a cache of dependencies and 'target'.
- name: Restore from cache
id: cache-restore
uses: actions/cache/restore@v4
with:
path: |
Cargo.cached.lock
~/.cargo
target/
# Cache by OS and Rust version.
key: ${{ runner.os }}-${{ steps.setup-rust.outputs.cachekey }}-
# For MSRV builds, use the cached 'Cargo.lock'.
- name: Use the cached Cargo lockfile
if: matrix.rust == needs.determine-msrv.outputs.msrv
# Remove 'Cargo.lock' even if a cached lockfile is unavailable.
run: mv Cargo.cached.lock Cargo.lock || rm Cargo.lock
# Build and run the test suite.
- name: Test
run: cargo test --all-targets $DOMAIN_FEATURES
# Test docs.
- name: Test docs
run: cargo test --doc $DOMAIN_FEATURES
# Build Cache
# -----------
#
# Prepare a cache for checking and building 'domain', on 'main'.
cache:
name: Cache
needs: determine-msrv
strategy:
matrix:
os:
rust:
runs-on: ${{ matrix.os }}
if: github.ref == 'refs/heads/main'
env:
RUSTFLAGS: "-D warnings"
DOMAIN_FEATURES: "--all-features"
# See <https://github.com/actions/runner-images/issues/12432>
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER: "rust-lld"
steps:
# Load the repository.
- name: Checkout repository
uses: actions/checkout@v4
# Prepare the environment on Windows
- name: Prepare Windows environment
if: matrix.os == 'windows-latest'
shell: bash
run: |
# Cargo doesn't support enabling all but one feature, so determine the
# complete feature list and filter out 'openssl'.
features=`cargo metadata --no-deps --format-version 1 | jq -r '.packages[]|select(.name == "domain")|.features|keys|map(select(.!="openssl"))|join(",")'`
# Overwrite the 'DOMAIN_FEATURES' environment variable.
echo "DOMAIN_FEATURES=--features=$features" >> "$GITHUB_ENV"
# Set up the Rust toolchain.
- name: Set up Rust ${{ matrix.rust }}
id: setup-rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: ${{ matrix.rust }}
cache: false
# Restore a cache of dependencies and 'target'.
- name: Restore from cache
uses: actions/cache/restore@v4
with:
path: |
Cargo.cached.lock
~/.cargo
target/
# Cache by OS and Rust version.
key: ${{ runner.os }}-${{ steps.setup-rust.outputs.cachekey }}-
# For MSRV builds, use the cached 'Cargo.lock'.
- name: Use the cached Cargo lockfile
if: matrix.rust == needs.determine-msrv.outputs.msrv
# Remove 'Cargo.lock' even if a cached lockfile is unavailable.
run: mv Cargo.cached.lock Cargo.lock || rm Cargo.lock
# Build all of 'domain'.
- name: Build
run: cargo build --all-targets $DOMAIN_FEATURES
# Copy to the Cargo lockfile for optional caching.
- name: Copy the Cargo lockfile for optional caching
run: cp Cargo.lock Cargo.cached.lock
# Save to the cache.
- name: Save to the cache
uses: actions/cache/save@v4
with:
path: |
Cargo.cached.lock
~/.cargo
target/
# Cache by OS and Rust version.
#
# GitHub treats cache entries as immutable (although it _does_ allow
# explicitly removing and re-adding them). We include the run ID so
# the cache key is unique every time; when restoring from the cache,
# we don't specify the run ID, and GitHub will automatically find the
# most recent cache entry using the key as a prefix.
key: ${{ runner.os }}-${{ steps.setup-rust.outputs.cachekey }}-${{ github.run_id }}
# TODO: Use 'cargo-semver-checks' on releases.