Skip to content

Commit 72ccd93

Browse files
committed
all: Add serpent_buildinfo crate and use it from boulder/moss
Adds the serpent_buildinfo crate which performs several common build-metadata related tasks. Also updates boulder and moss to use the functions provided by this library instead of doing it themselves Signed-off-by: Reilly Brogan <[email protected]>
1 parent 4ecad5d commit 72ccd93

File tree

12 files changed

+311
-37
lines changed

12 files changed

+311
-37
lines changed

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

boulder/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ rust-version.workspace = true
1010
config = { path = "../crates/config" }
1111
container = { path = "../crates/container" }
1212
moss = { path = "../moss" }
13+
serpent_buildinfo = { path = "../crates/serpent_buildinfo" }
1314
stone = { path = "../crates/stone" }
1415
stone_recipe = { path = "../crates/stone_recipe" }
1516
tui = { path = "../crates/tui" }

boulder/src/cli.rs

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ mod profile;
1313
mod recipe;
1414

1515
#[derive(Debug, Parser)]
16-
#[command(version = version())]
16+
#[command(version = serpent_buildinfo::get_simple_version())]
1717
pub struct Command {
1818
#[command(flatten)]
1919
pub global: Global,
@@ -94,15 +94,3 @@ pub enum Error {
9494
#[error("recipe")]
9595
Recipe(#[from] recipe::Error),
9696
}
97-
98-
fn version() -> String {
99-
use moss::environment;
100-
101-
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
102-
103-
let hash = environment::GIT_HASH
104-
.map(|hash| format!(" ({hash})"))
105-
.unwrap_or_default();
106-
107-
format!("{VERSION}{hash}")
108-
}

crates/serpent_buildinfo/Cargo.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "serpent_buildinfo"
3+
edition.workspace = true
4+
version.workspace = true
5+
rust-version.workspace = true
6+
build = "build.rs"
7+
8+
[dependencies]
9+
chrono.workspace = true
10+
11+
[build-dependencies]
12+
chrono.workspace = true
13+
thiserror.workspace = true

crates/serpent_buildinfo/build.rs

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
// build.rs
2+
use std::os::unix::ffi::OsStringExt;
3+
4+
use chrono::{DateTime, Utc};
5+
6+
/// Returns value of given environment variable or error if missing.
7+
///
8+
/// This also outputs necessary ‘cargo:rerun-if-env-changed’ tag to make sure
9+
/// build script is rerun if the environment variable changes.
10+
fn env(key: &str) -> Result<std::ffi::OsString, Box<dyn std::error::Error>> {
11+
println!("cargo:rerun-if-env-changed={}", key);
12+
std::env::var_os(key).ok_or_else(|| Box::from(format!("Missing `{}` environmental variable", key)))
13+
}
14+
15+
/// Calls program with given arguments and returns its standard output. If
16+
/// calling the program fails or it exits with non-zero exit status returns an
17+
/// error.
18+
fn command(prog: &str, args: &[&str], cwd: Option<std::path::PathBuf>) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
19+
println!("cargo:rerun-if-env-changed=PATH");
20+
let mut cmd = std::process::Command::new(prog);
21+
cmd.args(args);
22+
cmd.stderr(std::process::Stdio::inherit());
23+
if let Some(cwd) = cwd {
24+
cmd.current_dir(cwd);
25+
}
26+
let out = cmd.output()?;
27+
if out.status.success() {
28+
let mut stdout = out.stdout;
29+
if let Some(b'\n') = stdout.last() {
30+
stdout.pop();
31+
if let Some(b'\r') = stdout.last() {
32+
stdout.pop();
33+
}
34+
}
35+
Ok(stdout)
36+
} else if let Some(code) = out.status.code() {
37+
Err(Box::from(format!("{}: terminated with {}", prog, code)))
38+
} else {
39+
Err(Box::from(format!("{}: killed by signal", prog)))
40+
}
41+
}
42+
43+
/// Checks to see if we're building from a git source and if so attempts to gather information about the git status
44+
fn get_git_info() -> Result<(), Box<dyn std::error::Error>> {
45+
let pkg_dir = std::path::PathBuf::from(env("CARGO_MANIFEST_DIR")?);
46+
let git_dir = command("git", &["rev-parse", "--git-dir"], Some(pkg_dir.clone()));
47+
let git_dir = match git_dir {
48+
Ok(git_dir) => {
49+
println!("cargo:rustc-cfg=BUILDINFO_IS_GIT_BUILD");
50+
51+
std::path::PathBuf::from(std::ffi::OsString::from_vec(git_dir))
52+
}
53+
Err(msg) => {
54+
// We're not in a git repo, most likely we're building from a source archive
55+
println!("cargo:warning=unable to determine git version (not in git repository?)");
56+
println!("cargo:warning={}", msg);
57+
58+
// It's unlikely, but possible that someone could run git init. Might as well catch that.
59+
println!("cargo::rerun-if-changed={}/.git", pkg_dir.display());
60+
return Ok(());
61+
}
62+
};
63+
64+
// Make Cargo rerun us if currently checked out commit or the state of the
65+
// working tree changes. We try to accomplish that by looking at a few
66+
// crucial git state files. This probably may result in some false
67+
// negatives but it’s best we’ve got.
68+
for subpath in ["HEAD", "logs/HEAD", "index"] {
69+
let path = git_dir.join(subpath).canonicalize()?;
70+
println!("cargo:rerun-if-changed={}", path.display());
71+
}
72+
73+
// Get the full git hash
74+
let args = &["rev-parse", "--output-object-format=sha1", "HEAD"];
75+
let out = command("git", args, None)?;
76+
match String::from_utf8_lossy(&out) {
77+
std::borrow::Cow::Borrowed(full_hash) => {
78+
println!("cargo:rustc-env=BUILDINFO_GIT_FULL_HASH={}", full_hash.trim());
79+
}
80+
std::borrow::Cow::Owned(full_hash) => return Err(Box::from(format!("git: Invalid output: {}", full_hash))),
81+
}
82+
83+
// Get the short git hash
84+
let args = &["rev-parse", "--output-object-format=sha1", "--short", "HEAD"];
85+
let out = command("git", args, None)?;
86+
match String::from_utf8_lossy(&out) {
87+
std::borrow::Cow::Borrowed(short_hash) => {
88+
println!("cargo:rustc-env=BUILDINFO_GIT_SHORT_HASH={}", short_hash.trim());
89+
}
90+
std::borrow::Cow::Owned(short_hash) => return Err(Box::from(format!("git: Invalid output: {}", short_hash))),
91+
}
92+
93+
// Get whether this is built from a dirty tree
94+
let args = &["status", "--porcelain"];
95+
let out = command("git", args, None)?;
96+
match String::from_utf8_lossy(&out) {
97+
std::borrow::Cow::Borrowed(output) => match output.trim().len() {
98+
0 => {}
99+
_ => println!("cargo:rustc-cfg=BUILDINFO_IS_DIRTY"),
100+
},
101+
std::borrow::Cow::Owned(output) => return Err(Box::from(format!("git: Invalid output: {}", output))),
102+
}
103+
104+
// Get the commit summary
105+
let args = &["show", "--format=\"%s\"", "-s"];
106+
let out = command("git", args, None)?;
107+
match String::from_utf8_lossy(&out) {
108+
std::borrow::Cow::Borrowed(summary) => {
109+
println!("cargo:rustc-env=BUILDINFO_GIT_SUMMARY={}", summary.trim());
110+
}
111+
std::borrow::Cow::Owned(summary) => return Err(Box::from(format!("git: Invalid output: {}", summary))),
112+
}
113+
114+
Ok(())
115+
}
116+
117+
fn get_build_time() -> Result<(), Box<dyn std::error::Error>> {
118+
// Propagate SOURCE_DATE_EPOCH if set
119+
if let Ok(epoch_env) = env("SOURCE_DATE_EPOCH") {
120+
if let Ok(seconds) = epoch_env.to_string_lossy().parse::<i64>() {
121+
if let Some(time) = DateTime::from_timestamp(seconds, 0) {
122+
println!("cargo:rustc-env=BUILDINFO_BUILD_TIME={}", time.timestamp());
123+
return Ok(());
124+
}
125+
}
126+
}
127+
128+
println!("cargo:rustc-env=BUILDINFO_BUILD_TIME={}", Utc::now().timestamp());
129+
Ok(())
130+
}
131+
132+
fn main() -> Result<(), Box<dyn std::error::Error>> {
133+
// This should include all top-level directories that contain source code or otherwise modify the build in meaningful ways
134+
let top_level = std::path::PathBuf::from("../..").canonicalize()?;
135+
println!("cargo::rerun-if-changed={}/boulder", top_level.display());
136+
println!("cargo::rerun-if-changed={}/crates", top_level.display());
137+
println!("cargo::rerun-if-changed={}/moss", top_level.display());
138+
println!("cargo::rerun-if-changed={}/test", top_level.display());
139+
println!("cargo::rerun-if-changed={}/Cargo.toml", top_level.display());
140+
141+
let version = env("CARGO_PKG_VERSION")?;
142+
println!("cargo:rustc-env=BUILDINFO_VERSION={}", version.to_string_lossy());
143+
144+
get_build_time()?;
145+
146+
get_git_info()?;
147+
148+
Ok(())
149+
}

crates/serpent_buildinfo/src/lib.rs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// SPDX-FileCopyrightText: Copyright © 2020-2024 Serpent OS Developers
2+
//
3+
// SPDX-License-Identifier: MPL-2.0
4+
5+
use chrono::DateTime;
6+
7+
mod values;
8+
9+
/// Returns the version of the project, as defined in the top-level Cargo.toml
10+
///
11+
/// This will look like "0.1.0"
12+
pub const fn get_version() -> &'static str {
13+
values::VERSION
14+
}
15+
16+
/// Returns the build time of the project, printed in UTC time format
17+
///
18+
/// If SOURCE_DATE_EPOCH is set during the build then that will be the timestamp returned
19+
///
20+
/// This will look like "2024-07-09T19:20:40+00:00"
21+
pub fn get_build_time() -> String {
22+
if let Ok(time) = values::BUILD_TIME.parse::<i64>() {
23+
if let Some(build_time) = DateTime::from_timestamp(time, 0) {
24+
return build_time.to_rfc3339();
25+
}
26+
}
27+
"unknown".to_string()
28+
}
29+
30+
/// Returns `true` if the project was built from a git source, `false` otherwise
31+
pub const fn get_if_git_build() -> bool {
32+
cfg!(BUILDINFO_IS_GIT_BUILD)
33+
}
34+
35+
/// Returns `-dirty` if the project was built from a dirty git source, `` otherwise
36+
pub const fn get_git_dirty() -> &'static str {
37+
if cfg!(BUILDINFO_IS_DIRTY) {
38+
"-dirty"
39+
} else {
40+
""
41+
}
42+
}
43+
44+
/// Returns the git hash that the project was built from if built from a git source
45+
///
46+
/// This currently returns the SHA1 hash, though eventually it will return the SHA256 one
47+
///
48+
/// If built from a non-git source (like a release archive) this will return "unknown"
49+
#[cfg(BUILDINFO_IS_GIT_BUILD)]
50+
pub const fn get_git_full_hash() -> &'static str {
51+
values::GIT_FULL_HASH
52+
}
53+
54+
/// Returns the git hash that the project was built from if built from a git source
55+
///
56+
/// This currently returns the SHA1 hash, though eventually it will return the SHA256 one
57+
///
58+
/// If built from a non-git source (like a release archive) this will return "unknown"
59+
#[cfg(not(BUILDINFO_IS_GIT_BUILD))]
60+
pub const fn get_git_full_hash() -> &'static str {
61+
"unknown"
62+
}
63+
64+
/// Returns the shortened form of the git hash that this project was built from if built from git source
65+
///
66+
/// If built from a non-git source (like a release archive) this will return "unknown"
67+
#[cfg(BUILDINFO_IS_GIT_BUILD)]
68+
pub const fn get_git_short_hash() -> &'static str {
69+
values::GIT_SHORT_HASH
70+
}
71+
72+
/// Returns the shortened form of the git hash that this project was built from if built from git source
73+
///
74+
/// If built from a non-git source (like a release archive) this will return "unknown"
75+
#[cfg(not(BUILDINFO_IS_GIT_BUILD))]
76+
pub const fn get_git_short_hash() -> &'static str {
77+
"unknown"
78+
}
79+
80+
/// Returns the summary of the git commit that the project was built from
81+
///
82+
/// If built from a non-git source (like a release archive) this will return "unknown"
83+
#[cfg(BUILDINFO_IS_GIT_BUILD)]
84+
pub const fn get_git_summary() -> &'static str {
85+
values::GIT_SUMMARY
86+
}
87+
88+
/// Returns the summary of the git commit that the project was built from
89+
///
90+
/// If built from a non-git source (like a release archive) this will return "unknown"
91+
#[cfg(not(BUILDINFO_IS_GIT_BUILD))]
92+
pub const fn get_git_summary() -> &'static str {
93+
"unknown"
94+
}
95+
96+
/// For git builds this returns a string like `v0.1.0 (git 4ecad5d7e70c2cdc81350dc6b46fb55b1ccb18f5-dirty)`
97+
///
98+
/// For builds from a non-git source just the version will be returned: `v0.1.0`
99+
pub fn get_simple_version() -> String {
100+
let git = if cfg!(BUILDINFO_IS_GIT_BUILD) {
101+
format!(" (Git ref {}{})", get_git_full_hash(), get_git_dirty())
102+
} else {
103+
"".to_string()
104+
};
105+
format!("v{}{}", values::VERSION, git)
106+
}
107+
108+
/// For git builds this returns a string like `v0.1.0 (git 4ecad5d7e70c2cdc81350dc6b46fb55b1ccb18f5-dirty)`
109+
///
110+
/// For builds from a non-git source just the version will be returned: `v0.1.0`
111+
pub fn get_full_version() -> String {
112+
let git = if cfg!(BUILDINFO_IS_GIT_BUILD) {
113+
format!(" (Git ref {}{})", get_git_full_hash(), get_git_dirty())
114+
} else {
115+
"".to_string()
116+
};
117+
format!("version v{}{} (Built at {})", values::VERSION, git, get_build_time())
118+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// SPDX-FileCopyrightText: Copyright © 2020-2024 Serpent OS Developers
2+
//
3+
// SPDX-License-Identifier: MPL-2.0
4+
5+
pub(crate) const VERSION: &str = env!("BUILDINFO_VERSION");
6+
7+
pub(crate) const BUILD_TIME: &str = env!("BUILDINFO_BUILD_TIME");
8+
9+
#[cfg(BUILDINFO_IS_GIT_BUILD)]
10+
pub(crate) const GIT_FULL_HASH: &str = env!("BUILDINFO_GIT_FULL_HASH");
11+
12+
#[cfg(BUILDINFO_IS_GIT_BUILD)]
13+
pub(crate) const GIT_SHORT_HASH: &str = env!("BUILDINFO_GIT_SHORT_HASH");
14+
15+
#[cfg(BUILDINFO_IS_GIT_BUILD)]
16+
pub(crate) const GIT_SUMMARY: &str = env!("BUILDINFO_GIT_SUMMARY");

moss/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ rust-version.workspace = true
88
config = { path = "../crates/config" }
99
container = { path = "../crates/container" }
1010
dag = { path = "../crates/dag" }
11+
serpent_buildinfo = { path = "../crates/serpent_buildinfo" }
1112
stone = { path = "../crates/stone" }
1213
triggers = { path = "../crates/triggers" }
1314
tui = { path = "../crates/tui" }

moss/build.rs

Lines changed: 0 additions & 15 deletions
This file was deleted.

0 commit comments

Comments
 (0)