From 1445bfdd425cb26e965ef845359153d6644c9238 Mon Sep 17 00:00:00 2001 From: Sam Sung <51686767+samwhosung@users.noreply.github.com> Date: Sun, 27 Sep 2026 08:48:12 +0700 Subject: [PATCH] =?UTF-8?q?The=20build=20line=20names=20its=20release:=20b?= =?UTF-8?q?enilla=20build=200.2.0=20=C2=B7=20,=20and=200.2.0+N=20for?= =?UTF-8?q?=20a=20commit=20N=20past=20its=20tag=20(#452)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The startup line, crash reports and the debug panel now name the release a build is, so a bug report carries it: `benilla build 0.2.0 · b17be27 · 2026-09-27 · release` on a release, `0.2.0+12 · …` twelve commits past its tag. - `benilla-buildstamp` also stamps `git describe --tags --long --match 'v[0-9]*'` and watches `packed-refs` and `refs/tags`, so a fetched tag restamps without a new commit. - `BuildId` carries the workspace version and the describe. `release()` reads the version, with `+N` when the describe's tag is that version's; with no tag in reach (a source archive, a shallow clone) or the previous version's tag (the release commit before its tag exists) it reads the version alone. - The bug template's example line shows the new form. Checked: the four new `build_id` tests fail with the distance parser broken and pass restored; the describe command in a scratch clone reads empty with no tag, `v0.1.0-443-g…` over a launch tag, `v0.2.0-0-g…` on the release commit, and skips tags that are not `v`-numbered; the build script's output carries the new watches and variable. Gates green on macOS; no platform seam touched. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/ISSUE_TEMPLATE/bug.yml | 2 +- crates/benilla-app/src/crash.rs | 4 +- crates/benilla-buildstamp/src/lib.rs | 27 ++++--- crates/benilla-world/src/build_id.rs | 117 ++++++++++++++++++++++++--- crates/benilla-worldview/src/main.rs | 2 + crates/benilla/src/main.rs | 2 + 6 files changed, 132 insertions(+), 22 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index e27a53a9..1f99894b 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -22,4 +22,4 @@ body: attributes: label: Build and setup description: The `benilla build` line from the start of the terminal output, your OS, and the server you play on. - placeholder: "benilla build 64cca13 · 2026-09-24 · release, macOS 15, vmangos" + placeholder: "benilla build 0.2.0 · 64cca13 · 2026-09-24 · release, macOS 15, vmangos" diff --git a/crates/benilla-app/src/crash.rs b/crates/benilla-app/src/crash.rs index d1c26422..eb17c7ff 100644 --- a/crates/benilla-app/src/crash.rs +++ b/crates/benilla-app/src/crash.rs @@ -133,6 +133,8 @@ mod tests { use super::*; const BUILD: BuildId = BuildId { + version: "0.2.0", + describe: "v0.2.0-3-g0123456", sha: "0123456789abcdef0123456789abcdef01234567", short: "0123456", date: "2026-09-16", @@ -154,7 +156,7 @@ mod tests { }); for needle in [ "sha 0123456789abcdef0123456789abcdef01234567", - "0123456 · 2026-09-16 · debug", + "0.2.0+3 · 0123456 · 2026-09-16 · debug", "12.3 s after launch", "thread: main", "at: crates/benilla-app/src/net/apply.rs:100:5", diff --git a/crates/benilla-buildstamp/src/lib.rs b/crates/benilla-buildstamp/src/lib.rs index d313954d..a6bebeac 100644 --- a/crates/benilla-buildstamp/src/lib.rs +++ b/crates/benilla-buildstamp/src/lib.rs @@ -6,20 +6,23 @@ use std::path::Path; use std::process::Command; -/// Stamp the commit this binary was built from into the calling package as four `rustc-env` -/// vars, which its `main.rs` reads back with `env!` into a `BuildId`: +/// Stamp the commit this binary was built from into the calling package as five `rustc-env` +/// vars, which its `main.rs` reads back with `env!` into a `BuildId`, beside its own +/// `CARGO_PKG_VERSION`: /// /// - `BENILLA_GIT_SHA`: the full sha. /// - `BENILLA_GIT_SHORT`: git's own abbreviation of it. /// - `BENILLA_GIT_DATE`: the commit date (`%cs`, `YYYY-MM-DD`), not the build date. +/// - `BENILLA_GIT_DESCRIBE`: `git describe --tags --long` against the nearest release tag +/// (`v0.2.0-12-gb17be27`), which says how far past its release the commit is. /// - `BENILLA_PROFILE`: the profile directory's name, which tells `ship` from `release` where /// cargo's `PROFILE` does not. /// -/// The git vars are empty when git cannot answer (no `.git`, no `git` on `PATH`), which the -/// runtime reports as an unknown build. The rerun triggers are `HEAD` and the ref it names, -/// resolved with `git rev-parse --git-path` so a linked worktree resolves too; only paths that -/// exist are emitted, since cargo reruns a build script whose watched path is missing on every -/// build. +/// The git vars are empty when git cannot answer (no `.git`, no `git` on `PATH`, no tag in +/// reach), which the runtime reports as an unknown commit or the bare version. The rerun +/// triggers are `HEAD`, the ref it names, `packed-refs` and the tags, resolved with +/// `git rev-parse --git-path` so a linked worktree resolves too; only paths that exist are +/// emitted, since cargo reruns a build script whose watched path is missing on every build. pub fn emit() { let dir = std::env::var("CARGO_MANIFEST_DIR").expect("cargo sets CARGO_MANIFEST_DIR"); let git = |args: &[&str]| -> Option { @@ -44,10 +47,12 @@ pub fn emit() { }; watch(git(&["rev-parse", "--git-path", "HEAD"])); if let Some(git_ref) = git(&["symbolic-ref", "-q", "HEAD"]) { - // The loose ref, plus `packed-refs`: a fresh clone's branch has no loose file. watch(git(&["rev-parse", "--git-path", &git_ref])); - watch(git(&["rev-parse", "--git-path", "packed-refs"])); } + // A fresh clone's refs live only in `packed-refs`; a fetched or new tag lands in either, and + // moves the describe without moving `HEAD`. Cargo scans a watched directory whole. + watch(git(&["rev-parse", "--git-path", "packed-refs"])); + watch(git(&["rev-parse", "--git-path", "refs/tags"])); // An edit to the rule restamps: cargo reruns a build script when `build.rs` or one of its // build-dependencies changes. println!("cargo::rerun-if-changed=build.rs"); @@ -56,6 +61,10 @@ pub fn emit() { ("BENILLA_GIT_SHA", &["rev-parse", "HEAD"][..]), ("BENILLA_GIT_SHORT", &["rev-parse", "--short", "HEAD"]), ("BENILLA_GIT_DATE", &["log", "-1", "--format=%cs"]), + ( + "BENILLA_GIT_DESCRIBE", + &["describe", "--tags", "--long", "--match", "v[0-9]*"], + ), ] { println!( "cargo::rustc-env={var}={}", diff --git a/crates/benilla-world/src/build_id.rs b/crates/benilla-world/src/build_id.rs index 1773634f..03857dd4 100644 --- a/crates/benilla-world/src/build_id.rs +++ b/crates/benilla-world/src/build_id.rs @@ -1,13 +1,19 @@ -//! Which build is running: the commit the binary was built from, stamped at compile time by the -//! launcher shims' build script (`benilla-buildstamp`) and passed to `run` as the [`BuildId`] -//! resource. It shows in the startup log line ([`banner`]) and in the debug panel's footer. +//! Which build is running: the release and the commit the binary was built from, stamped at +//! compile time by the launcher shims' build script (`benilla-buildstamp`) and passed to `run` as +//! the [`BuildId`] resource. It shows in the startup log line ([`banner`]), in crash reports and in +//! the debug panel's footer. use bevy::prelude::*; -/// The launcher shim's compile-time git stamp. The git fields are empty when the build had no -/// checkout or no `git` on `PATH`, which [`BuildId::summary`] reports as an unknown build. +/// The launcher shim's compile-time stamp. The git fields are empty when the build had no +/// checkout or no `git` on `PATH`, which [`BuildId::summary`] reports as an unknown commit. #[derive(Resource, Clone, Copy)] pub struct BuildId { + /// The workspace version from `Cargo.toml`, which every release bumps. + pub version: &'static str, + /// `git describe --tags --long` against the nearest `v*` tag (`v0.2.0-12-gb17be27`); empty + /// when no release tag is in reach, as in a source archive or a shallow clone. + pub describe: &'static str, /// The full sha, which the debug panel copies. pub sha: &'static str, /// Git's own abbreviation of [`sha`](Self::sha). @@ -19,17 +25,106 @@ pub struct BuildId { } impl BuildId { - /// The one-line build id: short sha, commit date and profile. - pub fn summary(&self) -> String { - if self.short.is_empty() { - format!("unknown (built without a git checkout) · {}", self.profile) - } else { - format!("{} · {} · {}", self.short, self.date, self.profile) + /// The release this build is: the version, and `+N` when the commit is N past that version's + /// tag. With no tag for this version in reach (none fetched, or the release commit before its + /// tag exists), the version alone. + pub fn release(&self) -> String { + match commits_past_tag(self.describe, self.version) { + Some(n) if n > 0 => format!("{}+{n}", self.version), + _ => self.version.to_owned(), } } + + /// The one-line build id: release, short sha, commit date and profile. + pub fn summary(&self) -> String { + if self.short.is_empty() { + format!( + "{} · unknown commit (built without a git checkout) · {}", + self.release(), + self.profile + ) + } else { + format!( + "{} · {} · {} · {}", + self.release(), + self.short, + self.date, + self.profile + ) + } + } +} + +/// How many commits past `v{version}` a `--long` describe (`--g`) is, or `None` +/// when the describe is empty, malformed or names another version's tag. +fn commits_past_tag(describe: &str, version: &str) -> Option { + let mut parts = describe.rsplitn(3, '-'); + parts.next().filter(|hash| hash.starts_with('g'))?; + let n: u32 = parts.next()?.parse().ok()?; + let tag = parts.next()?; + (tag.strip_prefix('v')? == version).then_some(n) } /// Log the build id once at startup, not env-gated, so every pasted log carries it. pub fn banner(build: Res) { info!("benilla build {}", build.summary()); } + +#[cfg(test)] +mod tests { + use super::*; + + fn build(version: &'static str, describe: &'static str, short: &'static str) -> BuildId { + BuildId { + version, + describe, + sha: "", + short, + date: "2026-09-27", + profile: "release", + } + } + + #[test] + fn a_tagged_release_reads_as_its_version() { + let b = build("0.2.0", "v0.2.0-0-gb17be27", "b17be27"); + assert_eq!(b.summary(), "0.2.0 · b17be27 · 2026-09-27 · release"); + } + + #[test] + fn a_commit_past_the_release_counts_its_distance() { + assert_eq!( + build("0.2.0", "v0.2.0-12-gb17be27", "b17be27").release(), + "0.2.0+12" + ); + // A pre-release tag's own hyphens stay in the tag. + assert_eq!( + build("1.0.0-rc1", "v1.0.0-rc1-3-gabc1234", "abc1234").release(), + "1.0.0-rc1+3" + ); + } + + #[test] + fn another_versions_tag_or_none_reads_as_the_version_alone() { + // The release commit itself, before its tag exists: the nearest tag is the previous one. + assert_eq!( + build("0.2.0", "v0.1.0-450-gb17be27", "b17be27").release(), + "0.2.0" + ); + // No release tag in reach: a source archive or a shallow clone. + assert_eq!(build("0.2.0", "", "b17be27").release(), "0.2.0"); + assert_eq!( + build("0.2.0", "not-a-describe", "b17be27").release(), + "0.2.0" + ); + } + + #[test] + fn a_build_without_git_still_names_its_release() { + let b = build("0.2.0", "", ""); + assert_eq!( + b.summary(), + "0.2.0 · unknown commit (built without a git checkout) · release" + ); + } +} diff --git a/crates/benilla-worldview/src/main.rs b/crates/benilla-worldview/src/main.rs index f99659ba..07ef880b 100644 --- a/crates/benilla-worldview/src/main.rs +++ b/crates/benilla-worldview/src/main.rs @@ -6,6 +6,8 @@ use benilla_world::build_id::BuildId; fn main() -> benilla_world::AppExit { benilla_world::worldview::run(BuildId { + version: env!("CARGO_PKG_VERSION"), + describe: env!("BENILLA_GIT_DESCRIBE"), sha: env!("BENILLA_GIT_SHA"), short: env!("BENILLA_GIT_SHORT"), date: env!("BENILLA_GIT_DATE"), diff --git a/crates/benilla/src/main.rs b/crates/benilla/src/main.rs index b14bc7ea..c9631222 100644 --- a/crates/benilla/src/main.rs +++ b/crates/benilla/src/main.rs @@ -5,6 +5,8 @@ use benilla_app::BuildId; fn main() -> benilla_app::AppExit { benilla_app::run(BuildId { + version: env!("CARGO_PKG_VERSION"), + describe: env!("BENILLA_GIT_DESCRIBE"), sha: env!("BENILLA_GIT_SHA"), short: env!("BENILLA_GIT_SHORT"), date: env!("BENILLA_GIT_DATE"),