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
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[] | select(.name == "gridoxide") | .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.
#
# `--workspace` packages the other workspace crates too, and
# verifies each crate against the others it just packaged rather than
# against crates.io, where this version does not exist yet. 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 --workspace
# The `cgmes` feature is the one whose dependencies come from outside
# this repo (cimoxide's `cimoxide-model`), 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[] | select(.name == "gridoxide") | .version')
# Every workspace crate's .crate, unpacked side by side.
for crate in target/package/*-$version.crate; do
tar xzf "$crate" -C target/package
done
# This version of the workspace's other crates is not on crates.io
# until the step below, so point the extracted gridoxide at the
# extracted crates — again, exactly what a consumer gets.
patches=()
for dir in target/package/gridoxide-*-$version/; do
name=$(basename "$dir" "-$version")
patches+=(--config "patch.crates-io.$name.path='$PWD/$dir'")
done
cargo build --features cgmes \
--manifest-path "target/package/gridoxide-$version/Cargo.toml" "${patches[@]}"
# 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 .crates; re-verifying would just compile faer twice.
# `--workspace` publishes in dependency order, gridoxide-sparse first
# and gridoxide last.
- name: Publish
if: ${{ github.event_name != 'workflow_dispatch' || !inputs.dry_run }}
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: cargo publish --workspace --no-verify