Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 0 additions & 49 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 0 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,6 @@ serial_test = "3"
dolos-redb3 = { path = "crates/redb3" }
stelae = { path = "crates/stelae" }

[build-dependencies]
vergen-gitcl = "9.1.0"

[[bench]]
name = "archive_backends"
harness = false
Expand Down
87 changes: 82 additions & 5 deletions build.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,86 @@
use vergen_gitcl::{Emitter, GitclBuilder};
//! Stamps the revision the binary is built from into the compiled artifact.
//!
//! The interesting part is not reading the revision — it is making sure the
//! recorded one cannot outlive the code it names. Cargo caches a build
//! script's output and re-runs the script only when one of the paths it asked
//! to watch has changed. Any git path this script could watch belongs to the
//! worktree that happened to build first: with a `CARGO_TARGET_DIR` shared
//! across worktrees there is a single cached output for all of them, so a
//! second worktree at a different commit silently inherits the first
//! worktree's revision. Watching a `HEAD` that is a symbolic ref makes it
//! worse still — its contents do not change when the branch advances, so even
//! the original worktree keeps the first revision it ever recorded.
//!
//! So the script asks to be re-run unconditionally, by watching a path under
//! `OUT_DIR` that it never creates. The cost is one `git` invocation per
//! build plus a recompile of this package; the gain is that
//! `dolos --version` names the revision it was actually built from, or says
//! `unknown`, and never a confident wrong answer.

fn main() -> Result<(), Box<dyn std::error::Error>> {
let gitcl = GitclBuilder::default().sha(true).build()?;
use std::path::Path;
use std::process::Command;

Emitter::default().add_instructions(&gitcl)?.emit()?;
/// Set this to record a revision without consulting git — for a build from a
/// source archive, or a pipeline that already knows the commit it checked out.
const REVISION_OVERRIDE: &str = "DOLOS_GIT_SHA";

Ok(())
/// Runs `git` in the package directory, returning its trimmed stdout on
/// success. Any failure — no git, no repository, no commit — is `None`, which
/// the caller turns into `unknown` rather than into a guess.
fn git(manifest_dir: &str, args: &[&str]) -> Option<String> {
let output = Command::new("git")
.arg("--no-optional-locks")
.args(args)
.current_dir(manifest_dir)
.output()
.ok()?;

if !output.status.success() {
return None;
}

Some(String::from_utf8_lossy(&output.stdout).trim().to_owned())
}

/// The revision to stamp: the override if one is set, else `HEAD` abbreviated
/// to eight characters, suffixed `-dirty` when tracked files differ from it.
fn revision(manifest_dir: &str) -> String {
if let Ok(sha) = std::env::var(REVISION_OVERRIDE) {
if !sha.trim().is_empty() {
return sha.trim().to_owned();
}
}

let Some(sha) =
git(manifest_dir, &["rev-parse", "--short=8", "HEAD"]).filter(|s| !s.is_empty())
else {
return "unknown".to_owned();
};

match git(
manifest_dir,
&["status", "--porcelain", "--untracked-files=no"],
) {
Some(status) if !status.is_empty() => format!("{sha}-dirty"),
_ => sha,
}
}

fn main() {
// Cargo reads these from the environment of *this* process, so they
// describe the build actually running. The `env!` equivalents would be
// baked into the build script binary, which is itself cached across
// worktrees — the very staleness this script exists to avoid.
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR");
let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR");
let package_version = std::env::var("CARGO_PKG_VERSION").expect("CARGO_PKG_VERSION");

let never_created = Path::new(&out_dir).join("always-rerun");
println!("cargo:rerun-if-changed={}", never_created.display());
println!("cargo:rerun-if-env-changed={REVISION_OVERRIDE}");

let revision = revision(&manifest_dir);

println!("cargo:rustc-env=DOLOS_GIT_SHA={revision}");
println!("cargo:rustc-env=DOLOS_VERSION={package_version} ({revision})");
}
8 changes: 1 addition & 7 deletions src/bin/dolos/banner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,5 @@ pub fn print_init_banner() {

println!("\x1b[90moooooooooooooooooooooooooooooooooooooooo\x1b[0m");

let git_sha = option_env!("VERGEN_GIT_SHA").unwrap_or("unknown");

println!(
"\x1b[1;95mv{} ({})\x1b[0m\n",
env!("CARGO_PKG_VERSION"),
git_sha
);
println!("\x1b[1;95mv{}\x1b[0m\n", env!("DOLOS_VERSION"));
}
2 changes: 1 addition & 1 deletion src/bin/dolos/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ enum Command {
#[derive(Debug, Parser)]
#[clap(name = "Dolos")]
#[clap(bin_name = "dolos")]
#[clap(author, version, about, long_about = None)]
#[clap(author, version = env!("DOLOS_VERSION"), about, long_about = None)]
struct Cli {
#[command(subcommand)]
command: Command,
Expand Down
65 changes: 65 additions & 0 deletions tests/build_revision.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//! Guards the revision the binary reports about itself.
//!
//! A build script's output is cached, and every git path it could watch
//! belongs to whichever worktree ran it first — so with a `CARGO_TARGET_DIR`
//! shared across worktrees a binary used to report a commit it was not built
//! from, confidently and with no way to tell. `build.rs` now re-runs on every
//! build; this test asserts the property that failed, so a future change that
//! reintroduces caching is caught here rather than in a report that
//! attributes results to the wrong commit.

use std::process::Command;

fn git(args: &[&str]) -> Option<String> {
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").ok()?;

let output = Command::new("git")
.arg("--no-optional-locks")
.args(args)
.current_dir(manifest_dir)
.output()
.ok()?;

if !output.status.success() {
return None;
}

Some(String::from_utf8_lossy(&output.stdout).trim().to_owned())
}

#[test]
fn stamped_revision_names_the_tree_it_was_built_from() {
if std::env::var("DOLOS_GIT_SHA").is_ok() {
eprintln!("skipped: the revision was overridden via DOLOS_GIT_SHA");
return;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

let Some(head) = git(&["rev-parse", "--short=8", "HEAD"]).filter(|s| !s.is_empty()) else {
eprintln!("skipped: no git revision available for this tree");
return;
};

let Some(status) = git(&["status", "--porcelain", "--untracked-files=no"]) else {
eprintln!("skipped: git could not report the state of the working tree");
return;
};

let expected = if status.is_empty() {
head
} else {
format!("{head}-dirty")
};

assert_eq!(
env!("DOLOS_GIT_SHA"),
expected,
"the binary reports a revision it was not built from; the build script's \
output was reused from an earlier build (a target directory shared across \
git worktrees is the usual cause)",
);

assert_eq!(
env!("DOLOS_VERSION"),
format!("{} ({})", env!("CARGO_PKG_VERSION"), expected),
);
}
Loading