Skip to content

Commit 5ae61a4

Browse files
jdclaude
andauthored
feat(cli): plumb release tag into mergify --version (#1576)
`Cargo.toml` is pinned at `0.0.0` because cargo's semver rejects the project's 4-component calver (`2026.4.23.1`), so `--version` reported the placeholder on every release wheel — bad signal for support and diagnostics, and the reason the flag had been disabled outright. Solution: keep `Cargo.toml` at the placeholder, but accept the real version via a `MERGIFY_RELEASE_VERSION` env var the release workflow sets from `$GITHUB_REF`. `crates/mergify-cli/build.rs` re-exports it as a `rustc-env` so `option_env!` at compile time can fold it into a `VERSION` const, with `CARGO_PKG_VERSION` as the dev-build fallback. Clap reads `VERSION` directly (re-enabling `--version`), and the CLI schema generator (`_internal dump-cli-schema`) routes through the same const so the published reference matches what the binary says. The release path is the same as before: `build-wheels.yml`'s stamp step still writes the tag into `pyproject.toml`; it now also pushes the same value into `$GITHUB_ENV` so the maturin build sees it. PR smoke builds skip the stamp step, so the binary reports `0.0.0` — locked in by a new `mergify --version` smoke test in CI and two unit tests pinning the `--version` flag and the env fallback. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 17d7b8a commit 5ae61a4

5 files changed

Lines changed: 93 additions & 3 deletions

File tree

.github/workflows/build-wheels.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,12 @@ jobs:
7676
rm -f pyproject.toml.bak
7777
echo "Stamped pyproject.toml:"
7878
grep '^version = ' pyproject.toml
79+
# Propagate the tag into the maturin build so `mergify
80+
# --version` reports it. `Cargo.toml` stays at `0.0.0`
81+
# (cargo's semver rejects the 4-component calver), and
82+
# `crates/mergify-cli/build.rs` re-exports this env var
83+
# as a `rustc-env` for the binary's `VERSION` const.
84+
echo "MERGIFY_RELEASE_VERSION=${VERSION}" >> "${GITHUB_ENV}"
7985
8086
- name: Build wheel
8187
uses: PyO3/maturin-action@v1

.github/workflows/ci.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,23 @@ jobs:
8585
shell: bash
8686
run: mergify --help
8787

88+
# Lock the dev-build version fallback in. Release builds get
89+
# `MERGIFY_RELEASE_VERSION` from the stamp step in
90+
# `build-wheels.yml`; PR smoke builds don't, so the binary
91+
# must report `Cargo.toml`'s `0.0.0` placeholder. If this
92+
# ever changes silently, every release-version assumption
93+
# downstream (PyPI install reporting, support diagnostics)
94+
# rests on the same code path — catch the drift here.
95+
- name: mergify --version reports placeholder on unstamped build
96+
shell: bash
97+
run: |
98+
out=$(mergify --version)
99+
echo "$out"
100+
[[ "$out" == "mergify 0.0.0" ]] || {
101+
echo "expected 'mergify 0.0.0', got '$out'"
102+
exit 1
103+
}
104+
88105
ci-gate:
89106
if: ${{ !cancelled() }}
90107
needs:

crates/mergify-cli/build.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//! Plumb the release version through to the binary.
2+
//!
3+
//! Cargo's semver rejects the project's 4-component calver
4+
//! (`2026.4.23.1`), so `Cargo.toml` is pinned at the `0.0.0`
5+
//! placeholder and the real version comes in via the
6+
//! `MERGIFY_RELEASE_VERSION` env var the release workflow sets
7+
//! from `$GITHUB_REF`. This script normalises that input into a
8+
//! single rustc-env (`MERGIFY_CLI_VERSION`) the binary reads
9+
//! unconditionally — empty / unset both collapse to
10+
//! `CARGO_PKG_VERSION` here, so `main.rs` is a one-liner that
11+
//! can't accidentally surface an empty version string.
12+
13+
fn main() {
14+
// Rebuild when the env var changes so a release rebuild after a
15+
// dev build actually picks up the new value.
16+
println!("cargo:rerun-if-env-changed=MERGIFY_RELEASE_VERSION");
17+
let resolved = std::env::var("MERGIFY_RELEASE_VERSION")
18+
.ok()
19+
.filter(|v| !v.is_empty())
20+
.unwrap_or_else(|| env!("CARGO_PKG_VERSION").to_string());
21+
println!("cargo:rustc-env=MERGIFY_CLI_VERSION={resolved}");
22+
}

crates/mergify-cli/src/cli_schema.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ pub fn build() -> CliSchema {
117117
generator: "mergify _internal dump-cli-schema",
118118
cli: CliInfo {
119119
name: root.get_name().to_string(),
120-
version: env!("CARGO_PKG_VERSION"),
120+
version: crate::VERSION,
121121
about: root.get_about().map(ToString::to_string),
122122
},
123123
command,

crates/mergify-cli/src/main.rs

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ use mergify_queue::unpause::UnpauseOptions;
3939

4040
mod cli_schema;
4141

42+
/// User-visible CLI version. `build.rs` normalises the
43+
/// `MERGIFY_RELEASE_VERSION` env var the release workflow sets
44+
/// from `$GITHUB_REF` (unset or empty => `CARGO_PKG_VERSION`,
45+
/// i.e. the `0.0.0` placeholder), and writes the result to the
46+
/// `MERGIFY_CLI_VERSION` rustc-env. Reading the normalised var
47+
/// directly means this side can't surface an empty string.
48+
const VERSION: &str = env!("MERGIFY_CLI_VERSION");
49+
4250
fn main() -> ExitCode {
4351
let argv: Vec<String> = env::args().skip(1).collect();
4452

@@ -2607,8 +2615,7 @@ fn run_native(cmd: NativeCommand) -> ExitCode {
26072615
}
26082616

26092617
#[derive(Parser)]
2610-
#[command(name = "mergify", disable_help_subcommand = true)]
2611-
#[command(disable_version_flag = true)]
2618+
#[command(name = "mergify", disable_help_subcommand = true, version = VERSION)]
26122619
struct CliRoot {
26132620
/// Enable verbose debug logging. Mirrors the Python CLI's
26142621
/// top-level `--debug` flag so the same invocations work
@@ -4100,6 +4107,44 @@ mod tests {
41004107
);
41014108
}
41024109

4110+
#[test]
4111+
fn version_const_matches_release_env_or_falls_back_to_cargo_pkg_version() {
4112+
// Mirror `build.rs` exactly: empty `MERGIFY_RELEASE_VERSION`
4113+
// is treated the same as unset, both collapse to
4114+
// `CARGO_PKG_VERSION`. The release path is exercised in
4115+
// `build-wheels.yml`'s stamp step (sets a non-empty calver,
4116+
// expects it in `--version`); this test pins both fallback
4117+
// branches so build.rs's normalisation can't drift.
4118+
let expected = match option_env!("MERGIFY_RELEASE_VERSION") {
4119+
Some(v) if !v.is_empty() => v,
4120+
_ => env!("CARGO_PKG_VERSION"),
4121+
};
4122+
assert_eq!(VERSION, expected);
4123+
}
4124+
4125+
#[test]
4126+
fn cli_root_exposes_version_flag() {
4127+
// Locked-in regression: a previous incarnation had
4128+
// `disable_version_flag = true` because the version source
4129+
// was wrong; switching to the env-driven const lets clap
4130+
// render `--version` properly. If a future refactor
4131+
// removes the `version = VERSION` attribute, `--version`
4132+
// silently becomes an unknown flag and this catches it.
4133+
//
4134+
// `try_parse_from(...).err().expect(...)` over `expect_err`
4135+
// because the Ok variant is `CliRoot` which doesn't
4136+
// implement `Debug` (and shouldn't — it carries no debug
4137+
// intent).
4138+
let err = CliRoot::try_parse_from(["mergify", "--version"])
4139+
.err()
4140+
.expect("--version exits");
4141+
assert_eq!(err.kind(), clap::error::ErrorKind::DisplayVersion);
4142+
assert!(
4143+
err.to_string().contains(VERSION),
4144+
"version output should contain VERSION ({VERSION}), got: {err}",
4145+
);
4146+
}
4147+
41034148
#[test]
41044149
fn ci_scopes_parses_when_mergify_config_path_env_var_is_empty() {
41054150
// Regression for monorepo#33423 / gha-mergify-ci:

0 commit comments

Comments
 (0)