The build line names its release: benilla build 0.2.0 · <sha>, and 0.2.0+N for a commit N past its tag (#452)
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) <noreply@anthropic.com>
This commit is contained in:
committed by
GitHub
co-authored by
Claude Opus 5.5
parent
e4cc695f84
commit
a5cae4eb40
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<String> {
|
||||
@@ -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}={}",
|
||||
|
||||
@@ -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 (`<tag>-<n>-g<hash>`) is, or `None`
|
||||
/// when the describe is empty, malformed or names another version's tag.
|
||||
fn commits_past_tag(describe: &str, version: &str) -> Option<u32> {
|
||||
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<BuildId>) {
|
||||
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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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"),
|
||||
|
||||
Reference in New Issue
Block a user