Repository navigation
Conversation
JP-Ellis
added this pull request to stack #567
October 16, 2026 06:34
Release tooling now treats a version as released when its tag exists. The pre-bumped versions (one patch ahead of each tag) would be tagged and released on the first run. Release PRs now carry each bump. Re-resolving the lockfile after the reset moves a few Windows-only dependencies from windows-sys 0.61 to 0.60, within their declared ranges. Assisted-by: Claude Code:claude-opus-5-5
A version requirement in a [patch.crates-io] entry has to match the local crate. Release PRs bump pact_matching, so the requirement would break `cargo update` on every minor or major bump. The path alone is enough, as it already is for pact_models. Assisted-by: Claude Code:claude-opus-5-5
release-plz computes each crate's next version and changelog from the conventional commits since its last crates.io release. It runs in registry mode: git_only mode opens the git repository at the Cargo workspace directory, and fails with the workspace in rust/. Tagging, publishing and GitHub releases stay with the workflows. New changelog sections go above the existing entries, under a `# Changelog` header that replaces the old git-log instructions. changelog-section.sh extracts one version's notes for release PR bodies and GitHub releases. It stops at the next section heading, including the single-`#` headings of entries written before release-plz. mise pins release-plz, cargo-semver-checks and dist. Assisted-by: Claude Code:claude-opus-5-5
A crate's published manifest requires its siblings at the versions in its Cargo.toml. If such a version has no tag, that sibling has not been released, and publishing would fail after the artifacts were built. The check names the sibling to release first. It runs on draft release PRs too, since it only reads cargo metadata and tags. Assisted-by: Claude Code:claude-opus-5-5
release-plz supports only a single release PR, so crates could not ship independently. A matrix now runs `release-plz update -p <crate>` and keeps a draft `release/<crate>` PR. Once someone pushes to that PR, the bot leaves it alone, so manual edits to versions or changelogs survive until merge. On every push to master, tag-releases.sh tags each crate whose version has no tag yet. A crate's version only moves when its release PR merges, so a missing tag marks a merged release. Each tag goes out in its own push with the bot App's token, so the release workflow starts. The check reads tags, not crates.io. Between the tag and the publish, the registry lags master, and a registry-based check would tag twice. Assisted-by: Claude Code:claude-opus-5-5
The tag workflow calls this after every artifact has built, because a crates.io publish cannot be undone. A version already on crates.io is skipped, so a re-run after a later failure succeeds. Forks can set PUBLISH_DRY_RUN to rehearse a release. `--locked` builds the packaged crate against the workspace lockfile, so the published crate is verified with the dependency versions CI tested. Assisted-by: Claude Code:claude-opus-5-5
dist builds pact_ffi (shared and static libraries plus generated headers) and pact_verifier_cli on native runners for all eight targets, replacing the cross/QEMU release scripts. The pinned toolchain from rust-toolchain.toml builds every target; dist adds each target to it. On a tag push, dist builds everything first, then publishes the crate, then creates the GitHub release. Ready release PRs for these two crates run the same build as a gate. Other PRs, and tags of library-only crates, never do. Two settings keep the artifacts as they were: - pact_ffi's musl builds link the C runtime dynamically, because rustc drops a cdylib on a crt-static target. dist forces +crt-static through RUSTFLAGS, so build-setup.yml overrides it with CARGO_ENCODED_RUSTFLAGS. - Windows builds link the MSVC runtime dynamically (msvc-crt-static = false), as releases always have. dist reads `repository` to find the GitHub repo and can't parse the blob URLs, so both crates now name the repository root. The headers need nightly cbindgen in the global-artifacts job, which build-setup steps don't reach. That job carries a marked hand edit installing cbindgen from a prebuilt binary; build-headers.sh installs a minimal nightly when missing. Assisted-by: Claude Code:claude-opus-5-5
Downstream installers download `libpact_ffi-v<version>/<legacy name>` and `pact-verifier-<os>-<arch>.gz`. The job repackages dist's archives, with no rebuild, into those names. For pact_ffi it publishes them under a `libpact_ffi-v<version>` tag and release, which never becomes "latest". It self-disables on 2027-06-30. dist's Windows archives hold the DLL and the static library but not the DLL's import library (`pact_ffi.dll.lib`). import-lib.sh rebuilds it from the DLL's export table with llvm-dlltool, marking exports outside the executable section as DATA. Against the 0.5.11 release DLLs, the rebuilt libraries define the same symbols as rustc's, for x86_64 and aarch64. legacy-artifacts.test.sh runs the script against archives in dist's layout through a stub `gh`, and checks the names it produces against the asset lists of libpact_ffi-v0.5.11 and pact_verifier_cli-v1.3.5. Assisted-by: Claude Code:claude-opus-5-5
The image was built and pushed by hand. It now builds for amd64 and arm64 from the release's static musl archives, and is pushed to Docker Hub and GHCR as `<version>` and `latest`. The per-architecture tags (`-amd64`, `-arm64v8`) are replaced by one multi-platform manifest. Assisted-by: Claude Code:claude-opus-5-5
dist only builds crates with artifacts. Tags for pact_models, pact_matching, pact_consumer and pact_verifier publish the crate, then create a GitHub release from the changelog section. Assisted-by: Claude Code:claude-opus-5-5
Releases now go through per-crate release PRs, tag-triggered dist builds and trusted publishing; RELEASING.md describes the flow. The Conan recipes are removed. Their remote (pactfoundation.jfrog.io) is deactivated, and the recipe version had not advanced past 0.0.4 since 2021. pact_verifier's own release scripts go too: they packaged a shared library that no release has shipped in years. The crates.io ownership steps in CONTRIBUTING.md are gone, since trusted publishing releases without a personal token. Assisted-by: Claude Code:claude-opus-5-5
The Release PR workflow shares one concurrency group, and GitHub drops a pending run when a newer one queues. The run that catches up then starts at a later commit, and tagging HEAD would put the release tag past the release PR's merge. tag-releases.sh now tags the most recent commit on master's first-parent line whose diff wrote the crate's current `version` line: the release PR's squash commit or merge commit. Assisted-by: Claude Code:claude-opus-5-5
Three gaps between the tags and the registry: - A version on crates.io without a tag means a migration tag was never pushed, or a manual publish. tag-releases.sh used to tag it, on whatever commit last set the version, and start a release of a version that already exists. For pact_ffi 0.5.11 that release would overwrite the real libpact_ffi-v0.5.11 legacy assets. It now warns and skips. - Between a release tag and its publish, a full release build, release PRs regenerated against a registry that lacks the new version. The prepare job now waits for the publish. - release-deps checked only that sibling requirements name tagged versions. A crate using a sibling's unreleased code passed, and failed only at `cargo publish`, after its tag. Ready release PRs now run `cargo package`, which builds against the released siblings. crate-published.sh holds the sparse-index lookup that publish-crate.yml used inline; CRATES_INDEX_URL points the tests at a local index. Assisted-by: Claude Code:claude-opus-5-5
dist's default Linux runners changed what the libraries link against: - A musl cdylib linked on a glibc host needs glibc's libc.so.6, so pact_ffi's musl `.so` would not load on Alpine. The musl targets now build in cross-rs's musl toolchain images, which built every musl release so far, on x64 runners. GitHub has no Alpine Node for its arm64 runners, so an Alpine container there could not run checkout. - Ubuntu 22.04 and 24.04 runners raise the glibc floor to 2.34 or more. The gnu targets now build in manylinux_2_28 (AlmaLinux 8): glibc 2.28, the oldest among supported major distributions (RHEL 8 and its rebuilds). The 0.5.11 libraries needed 2.18; distributions older than RHEL 8 are out of support. check-linkage.sh asserts both: musl libraries link musl and the CLI is static, and gnu artifacts need glibc 2.28 at most. legacy-artifacts.sh runs it before packaging, so a mislinked library never reaches the names downstream installers download. Also in the dist config: - aarch64-pc-windows-msvc stays on a Windows runner with MSVC; dist would otherwise cross-compile it with cargo-xwin. - The GitHub release is created after the crates.io publish (github-release = "announce"), so a failed publish leaves no release. - The generated workflow pins its actions by commit. - No step installs protoc: pact-plugin-driver compiles its protobufs only when PACT_PLUGIN_BUILD_PROTOBUFS is set. Assisted-by: Claude Code:claude-opus-5-5
…e-pr runs The musl CRT override was job-wide and keyed on the tag. dist compiles every dist crate in a job, so a pact_verifier_cli job built pact_ffi without the override, and a release gate PR could not get both crates right. With precise-builds each crate gets its own cargo invocation, and dist-cargo.sh, the cargo dist runs in musl jobs, applies the dynamic CRT to pact_ffi's alone. dist-cargo.sh also turns the `cargo zigbuild` that dist picks for aarch64-musl in an x86_64 container back into `cargo build`, so the cross-rs image's musl toolchain links it. dist would otherwise try to pip-install cargo-zigbuild, which the Ubuntu-based image cannot, and zig would replace the toolchain the image exists to provide. release-pr.yml loses its manual trigger. Every case it served already recovers on its own: failed runs re-run, closed release PRs return on the next master push, and missing tags are created then too. Run on another branch, it would have tagged that branch's versions and started a release. Assisted-by: Claude Code:claude-opus-5-5
Drop this commit before merging. The release workflow builds on pull requests without publishing: host, publish, announce and the post-announce jobs need a tag. Letting this PR's branch through runs the full eight-target dist build, in the real containers and runners, so its artifacts can be checked before anything is tagged. Assisted-by: Claude Code:claude-opus-5-5
With LEGACY_ARCHIVES pointing at a directory of dist archives, such as a downloaded release gate run, legacy-artifacts.sh reads them there in place of a release. The same run checks every Linux library's linkage, rebuilds the Windows import libraries and produces the legacy names, all before anything is tagged. Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Claude Code:claude-opus-5-5
Without a tag, dist plans every dist crate at once. With pr-run-mode "upload" it then needs one coherent announcement, and pact_ffi and pact_verifier_cli never share a version, so the gate failed at `dist plan`. The plan job now derives `<crate>-v<version>` from the release PR's branch and the crate's manifest, and plans and builds that tag. Nothing publishes: a pull request still skips host and every later job. Assisted-by: Claude Code:claude-opus-5-5
Drop this commit before merging, with the other TEMPORARY commit. The release-process PR's branch names no crate; this maps it to pact_ffi, the first crate to release. Assisted-by: Claude Code:claude-opus-5-5
dist's installer unpacks a .tar.xz, and the cross-rs musl images ship no xz, so both musl jobs failed at "Install dist". The musl setup step now installs xz-utils when it is missing. Assisted-by: Claude Code:claude-opus-5-5
dist runs an extra-artifacts build command from the package's directory, so the repository-relative `rust/pact_ffi/build-headers.sh` did not exist there and the global artifacts job failed. A local `dist build --artifacts=global` now produces pact.h and pact-cpp.h. Assisted-by: Claude Code:claude-opus-5-5
JP-Ellis
force-pushed
the
ci/release-plz
branch
from
October 17, 2026 01:27
f5793ba to
d20a331
Compare
JP-Ellis
marked this pull request as ready for review
October 17, 2026 03:27
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Important
This PR stacks on #564. Two commits must be dropped before merge:
ci: TEMPORARY build release artifacts on the release-process PRci: TEMPORARY gate pact_ffi on the release-process PRTogether they let this PR run the full
pact_ffirelease build, so its artifacts can be checked before anything is tagged.Goal
Replace the per-crate
release.groovyscripts, run locally by named people, with a PR-based flow:master.Crates release independently. Downstream FFI installers keep their URLs and asset names until 2027-06-30.
How it works
release-pr.ymlruns on every push tomaster.preparerunsrelease-plz update -p <crate>for each crate and keeps a draftrelease/<crate>PR.tagjob runstag-releases.sh, which tags each crate whose version has no tag.release.ymlis generated by dist, with five marked hand edits. Forpact_ffiandpact_verifier_cliit runs:publish-crate.yml)legacy-artifacts.yml)pact-verifierimage (publish-containers.yml)release-crate.yml: publish, then a GitHub release.pact_ffiorpact_verifier_clirelease PR ready runs the same eight-target build, without publishing.release-depsnames any sibling crate that must be released first.cargo packagebuilds the crate against its released siblings.RELEASING.mddescribes the flow for maintainers.Artifacts
manylinux_2_28, a glibc 2.28 floor. That is the oldest glibc among supported major distributions: the RHEL 8 family has 2.28, RHEL 9 and Amazon Linux 2023 2.34, Ubuntu 22.04 2.35, Debian 12 2.36. 0.5.11 needed 2.18; the distributions between 2.18 and 2.28 are all out of support.pact_ffi's.solinked against musl, and the CLI static.check-linkage.shchecks every Linux library and the CLI before the legacy names are published.pact_ffi.dll.lib.import-lib.shrebuilds it from the DLL's export table. Against the 0.5.11 DLLs, the rebuilt libraries define the same symbols as rustc's.libpact_ffi-v0.5.11andpact_verifier_cli-v1.3.5. Forpact_ffithey go to alibpact_ffi-v<version>release, which never becomes "latest".<version>andlatest. The per-architecture tags<version>-amd64and<version>-arm64v8are no longer pushed.Commit sequence
chore: reset crate versions to their last releases: each version equals its last tag, so the first run tags nothing.build: patch pact_matching by path alone: a version requirement in[patch]breakscargo updateonce release PRs bump the crate.build: configure release-plz for per-crate releases: registry mode, the changelog template,changelog-section.sh, and the new tools inmise.toml.ci: block release PRs that need an unreleased workspace crateci: maintain one draft release PR per crate and tag merged releasesci: publish crates through crates.io trusted publishingci: build releases with distci: republish pre-dist asset names during a transition periodci: publish the pact-verifier image after each CLI releaseci: release library-only crates from their tagschore: remove manual release tooling: the groovy and shell release scripts, Cross.toml, and the Conan recipes. The Conan remote is deactivated, and the recipe has not moved past 0.0.4 since 2021.fix(ci): tag the commit that set each released versionfix(ci): guard tagging and release PRs against crates.io statefix(ci): build Linux releases in containers to keep their linkagefix(ci): give each musl crate its own CRT linkage, drop manual release-pr runsci: TEMPORARY build release artifacts on the release-process PR: drop before merge.feat(ci): check release gate artifacts locally with legacy-artifacts.shdocs: describe checking a release gate build before mergefix(ci): plan a release gate build for the release PR's own tag: without a tag, dist plans both dist crates together and refuses their unrelated versions. This PR's first build caught it.ci: TEMPORARY gate pact_ffi on the release-process PR: drop before merge.fix(ci): install xz in the musl build containers: dist's installer needs xz, and the cross-rs images lack it.fix(ci): run the header build from pact_ffi's directory: dist runs extra-artifact builds from the package directory.Alternatives weighed
git_onlymode. It opens the git repository at the Cargo workspace directory without searching parent directories, so it fails with the workspace inrust/. Registry mode works fromrust/. refactor: move the Cargo workspace to the repository root #565 proposes moving the workspace to the root, separately.release-plz releasefor tagging. In registry mode it would decide by crates.io, which lags the tag by a full release build.tag-releases.shdecides by tags.libc.so.6.cargo-xwinfor aarch64 Windows. It cross-compiles on Linux, unlike every release so far.Before merge
pact-plugin-driverfrom crates.io, and every 1.2.x release requireszip8.x, which is all yanked.GLIBC_2.28; musl needs musl'slibc.soandlibgcc_s.so.1, like 0.5.11; every build used rustc 1.99.1. The temporary commits run thepact_ffirelease build on this PR, draft or not. Run the artifact check on that run with tagpact_ffi-v0.5.11.pact_verifier_cliis first built by its own release PR's gate.masterafter ci: consolidate and speed up CI #564 merges.Maintainer setup
crates.io trusted publishers. For each of the six crates, add one with repository
pact-foundation/pact-referenceand environmentcrates-io.release.ymlforpact_ffiandpact_verifier_cli.release-crate.ymlforpact_models,pact_matching,pact_consumerandpact_verifier.crates-ioenvironment. Create it.Bot App. Install
PACT_FOUNDATION_BOTon this repository, with contents and pull-request write access.Branch protection. Allow the bot to push
release/*branches and tags. RequireCI complete.Container registries.
DOCKER_HUB_USERNAMEandDOCKER_HUB_TOKENavailable to this repository.ghcr.io/pact-foundation/pact-verifierpackage.Migration tags. Push them before merging:
Without them, the first
tagrun would find these versions untagged.tag-releases.shrefuses to tag a version already on crates.io, but the tags are needed forrelease-depsand the changelog links.First release: pact_ffi, live
The first release is the rehearsal, so check each step before taking the next.
master.tagcreates no tags.prepareopens draftrelease/<crate>PRs for crates with changes.release/pact_ffi.test.ymlruns, includingrelease-depsandcargo package, and so does the eight-target release gate.release/pact_ffi.tagjob pushespact_ffi-v<version>.crates.io lists
pact_ffi <version>.Release
pact_ffi-v<version>has eight archives, with.sha256files, pluspact.handpact-cpp.h.Release
libpact_ffi-v<version>is not marked latest, and its assets match the expected list:diff .github/scripts/tests/legacy-expected-ffi.txt \ <(gh release view "libpact_ffi-v<version>" --json assets --jq '.assets[].name' | sort)A downstream installer can fetch the new version. pact-python and pact-js build
releases/download/libpact_ffi-v<version>/<name>.If a step fails
tagjobmaster. The script tags the release merge commit either way.Re-run failed jobs only. A full re-run of the release workflow fails at release creation once the release exists.
Checking a release build's artifacts
This runs against a release gate run, or this PR's own run while the temporary commit is in place. It needs
gh, LLVM (llvm-dlltool,llvm-readobj) andreadelf. On macOS, use Homebrew'sllvm.legacy-artifacts.shfails if any of these is wrong:The two
diffs print nothing when the asset names match. Every build job's log should showrustc 1.99.1, because 1.99.0 miscompiled Cboolreturns.