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
name: Publish Crates
# Publishes the workspace crates to crates.io in dependency order when a
# version tag (e.g. v0.4.0) is pushed. Can also be run manually for a dry run.
on:
push:
tags:
workflow_dispatch:
inputs:
dry_run:
description: "Package and verify without uploading"
type: boolean
default: true
env:
CARGO_TERM_COLOR: always
# Workspace publishing (`cargo publish --workspace`) resolves internal path
# deps locally, so a dry-run validates the whole graph before anything is on
# the index. It needs a toolchain new enough to have the flag stabilized.
RUST_TOOLCHAIN: "1.95.0"
# The repo pins the MSRV via rust-toolchain.toml (channel = "1.89.0"), and
# that file wins over the installed default for plain `cargo`. RUSTUP_TOOLCHAIN
# has higher precedence than the file, so it forces this job's cargo to the
# newer toolchain installed above without touching the repo-wide MSRV pin.
RUSTUP_TOOLCHAIN: "1.95.0"
jobs:
publish:
name: Publish to crates.io
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
- name: Install protoc
run: |
sudo apt-get update
sudo apt-get install -y protobuf-compiler
# Guard against tagging a release whose version doesn't match the
# workspace. The tag drives the release; the manifest must agree.
- name: Verify tag matches workspace version
if: github.ref_type == 'tag'
run: |
tag="${GITHUB_REF_NAME#v}"
ws=$(cargo metadata --format-version 1 --no-deps \
| jq -r '.packages[] | select(.name=="macp-runtime") | .version')
echo "tag=$tag workspace=$ws"
if [ "$tag" != "$ws" ]; then
echo "::error::tag $GITHUB_REF_NAME does not match workspace version $ws"
exit 1
fi
# `cargo publish --workspace` computes the dependency order itself, builds
# each crate's verify step against its sibling path deps (so a dry-run
# validates the whole graph without anything on the index yet), and waits
# for each upload to appear on the index before publishing its dependents.
#
# Re-run safety: a crate whose current version is already live is passed
# via --exclude, so re-running after a mid-release failure skips the
# already-published members instead of erroring on "already exists".
- name: Publish workspace to crates.io
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run }}
run: |
set -euo pipefail
is_published() {
local name="$1" ver="$2" code
code=$(curl -s -o /dev/null -w '%{http_code}' \
-A "macp-runtime-release (https://github.com/multiagentcoordinationprotocol/macp-runtime)" \
"https://crates.io/api/v1/crates/$name/$ver")
[ "$code" = "200" ]
}
total=0
exclude_args=()
while read -r name ver; do
total=$((total + 1))
if is_published "$name" "$ver"; then
echo "==> skip $name@$ver (already on crates.io)"
exclude_args+=(--exclude "$name")
fi
done < <(cargo metadata --format-version 1 --no-deps \
| jq -r '.packages[] | "\(.name) \(.version)"')
if [ "${#exclude_args[@]}" -eq $((total * 2)) ]; then
echo "All workspace crates already published; nothing to do."
exit 0
fi
# ${arr[@]+...} guards against an empty array tripping `set -u`.
if [ "${DRY_RUN:-false}" = "true" ]; then
echo "==> dry-run publish workspace"
cargo publish --workspace --dry-run --locked ${exclude_args[@]+"${exclude_args[@]}"}
else
echo "==> publish workspace"
cargo publish --workspace --locked ${exclude_args[@]+"${exclude_args[@]}"}
fi