-
Notifications
You must be signed in to change notification settings - Fork 59
fix(build): stamp the revision the binary is actually built from #1262
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
scarmuega
wants to merge
3
commits into
main
Choose a base branch
from
fix/build-version-sha
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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
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
| 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})"); | ||
| } |
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
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
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
| 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; | ||
| } | ||
|
|
||
| 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), | ||
| ); | ||
| } | ||
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.