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
name: Publish to crates.io
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
dry_run:
description: 'Package and verify everything without uploading'
type: boolean
default: true
env:
CARGO_TERM_COLOR: always
jobs:
publish:
name: Publish to crates.io
runs-on: ubuntu-latest
# Same tag trigger as pypi.yml, so one `v*` tag cuts both the wheels and
# the crate. Nothing here depends on that workflow, though: a PyPI failure
# shouldn't hold back the crate or vice versa.
#
# Holds the CARGO_REGISTRY_TOKEN secret, which is the point of the
# environment — scoping the token to this one job, and letting a required
# reviewer be added later without touching this file.
environment: crate-release
steps:
# No `submodules:` — the two git submodules under tests/data/ hold only
# test fixtures, and Cargo.toml's `exclude` keeps that whole directory
# out of the packaged .crate anyway.
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-publish-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-publish-
# A version published to crates.io can never be replaced, so catch a
# mistyped tag here rather than by burning the wrong version number.
# Skipped on workflow_dispatch, which has no tag to compare against.
- name: Check tag matches crate version
if: startsWith(github.ref, 'refs/tags/')
run: |
set -euo pipefail
version=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version')
tag="${GITHUB_REF_NAME#v}"
if [ "$tag" != "$version" ]; then
echo "::error::Tag $GITHUB_REF_NAME does not match Cargo.toml version $version"
exit 1
fi
echo "Tag $GITHUB_REF_NAME matches Cargo.toml version $version"
# Catches the two ways a .crate can be broken without any local build
# noticing: a file the package needs that `exclude` dropped, and a
# dependency that resolves locally but not from the registry. Verifies on
# default features only, matching build.yml — `klu`/`pardiso` need a C
# toolchain, libclang, or a local oneMKL install. That's a limit on what
# this job *compiles*, not on what ships: the crate is source-only, so
# every feature's source is published either way.
- name: Package
run: cargo package
# The `cgmes` feature is the one whose dependencies come from outside
# this repo (cimoxide's `cimoxide-decoder`/`cimoxide-structs`), so build
# it from the packaged .crate rather than the working tree — that is what
# a crates.io consumer actually gets. Pure Rust, so unlike `klu` it needs
# nothing extra on the runner.
- name: Check the packaged crate builds with --features cgmes
run: |
set -euo pipefail
version=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version')
tar xzf "target/package/gridoxide-$version.crate" -C target/package
cargo build --features cgmes --manifest-path "target/package/gridoxide-$version/Cargo.toml"
# Everything above is the dry run: a `workflow_dispatch` with dry_run
# left on exercises the whole packaging path and stops here, without
# spending a version number.
#
# `--no-verify` because the `cargo package` step above already built the
# very same .crate; re-verifying would just compile faer twice.
- name: Publish
if: ${{ github.event_name != 'workflow_dispatch' || !inputs.dry_run }}
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: cargo publish --no-verify