Skip to content

Commit

Permalink
all: Add serpent_buildinfo crate and use it from boulder/moss
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
ReillyBrogan committed Jul 9, 2024
1 parent 4ecad5d commit 9dd084e
Show file tree
Hide file tree
Showing 12 changed files with 317 additions and 37 deletions.
10 changes: 10 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions boulder/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ rust-version.workspace = true
config = { path = "../crates/config" }
container = { path = "../crates/container" }
moss = { path = "../moss" }
serpent_buildinfo = { path = "../crates/serpent_buildinfo" }
stone = { path = "../crates/stone" }
stone_recipe = { path = "../crates/stone_recipe" }
tui = { path = "../crates/tui" }
Expand Down
14 changes: 1 addition & 13 deletions boulder/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ mod profile;
mod recipe;

#[derive(Debug, Parser)]
#[command(version = version())]
#[command(version = serpent_buildinfo::get_simple_version())]
pub struct Command {
#[command(flatten)]
pub global: Global,
Expand Down Expand Up @@ -94,15 +94,3 @@ pub enum Error {
#[error("recipe")]
Recipe(#[from] recipe::Error),
}

fn version() -> String {
use moss::environment;

pub const VERSION: &str = env!("CARGO_PKG_VERSION");

let hash = environment::GIT_HASH
.map(|hash| format!(" ({hash})"))
.unwrap_or_default();

format!("{VERSION}{hash}")
}
13 changes: 13 additions & 0 deletions crates/serpent_buildinfo/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "serpent_buildinfo"
edition.workspace = true
version.workspace = true
rust-version.workspace = true
build = "build.rs"

[dependencies]
chrono.workspace = true

[build-dependencies]
chrono.workspace = true
thiserror.workspace = true
171 changes: 171 additions & 0 deletions crates/serpent_buildinfo/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
// build.rs
use std::{io, os::unix::ffi::OsStringExt};

use chrono::{DateTime, Utc};
use thiserror::Error;

/// Returns value of given environment variable or error if missing.
///
/// This also outputs necessary ‘cargo:rerun-if-env-changed’ tag to make sure
/// build script is rerun if the environment variable changes.
fn env(key: &str) -> Result<std::ffi::OsString, Error> {
println!("cargo:rerun-if-env-changed={}", key);
std::env::var_os(key).ok_or_else(|| Error::Env(key.to_string()))
}

/// Calls program with given arguments and returns its standard output. If
/// calling the program fails or it exits with non-zero exit status returns an
/// error.
fn command(prog: &str, args: &[&str], cwd: Option<std::path::PathBuf>) -> Result<Vec<u8>, Error> {
println!("cargo:rerun-if-env-changed=PATH");
let mut cmd = std::process::Command::new(prog);
cmd.args(args);
cmd.stderr(std::process::Stdio::inherit());
if let Some(cwd) = cwd {
cmd.current_dir(cwd);
}
let out = cmd.output()?;
if out.status.success() {
let mut stdout = out.stdout;
if let Some(b'\n') = stdout.last() {
stdout.pop();
if let Some(b'\r') = stdout.last() {
stdout.pop();
}
}
Ok(stdout)
} else if let Some(code) = out.status.code() {
Err(Error::CommandExit(prog.to_string(), code.to_string()))
} else {
Err(Error::CommandKilled(prog.to_string()))
}
}

/// Checks to see if we're building from a git source and if so attempts to gather information about the git status
fn get_git_info() -> Result<(), Error> {
let pkg_dir = std::path::PathBuf::from(env("CARGO_MANIFEST_DIR")?);
let git_dir = command("git", &["rev-parse", "--git-dir"], Some(pkg_dir.clone()));
let git_dir = match git_dir {
Ok(git_dir) => {
println!("cargo:rustc-cfg=BUILDINFO_IS_GIT_BUILD");

std::path::PathBuf::from(std::ffi::OsString::from_vec(git_dir))
}
Err(msg) => {
// We're not in a git repo, most likely we're building from a source archive
println!("cargo:warning=unable to determine git version (not in git repository?)");
println!("cargo:warning={}", msg);

// It's unlikely, but possible that someone could run git init. Might as well catch that.
println!("cargo::rerun-if-changed={}/.git", pkg_dir.display());
return Ok(());
}
};

// Make Cargo rerun us if currently checked out commit or the state of the
// working tree changes. We try to accomplish that by looking at a few
// crucial git state files. This probably may result in some false
// negatives but it’s best we’ve got.
for subpath in ["HEAD", "logs/HEAD", "index"] {
let path = git_dir.join(subpath).canonicalize()?;
println!("cargo:rerun-if-changed={}", path.display());
}

// Get the full git hash
let args = &["rev-parse", "--output-object-format=sha1", "HEAD"];
let out = command("git", args, None)?;
match String::from_utf8_lossy(&out) {
std::borrow::Cow::Borrowed(full_hash) => {
println!("cargo:rustc-env=BUILDINFO_GIT_FULL_HASH={}", full_hash.trim());
}
std::borrow::Cow::Owned(full_hash) => return Err(Error::Git(full_hash)),
}

// Get the short git hash
let args = &["rev-parse", "--output-object-format=sha1", "--short", "HEAD"];
let out = command("git", args, None)?;
match String::from_utf8_lossy(&out) {
std::borrow::Cow::Borrowed(short_hash) => {
println!("cargo:rustc-env=BUILDINFO_GIT_SHORT_HASH={}", short_hash.trim());
}
std::borrow::Cow::Owned(short_hash) => return Err(Error::Git(short_hash)),
}

// Get whether this is built from a dirty tree
let args = &["status", "--porcelain"];
let out = command("git", args, None)?;
match String::from_utf8_lossy(&out) {
std::borrow::Cow::Borrowed(output) => match output.trim().len() {
0 => {}
_ => println!("cargo:rustc-cfg=BUILDINFO_IS_DIRTY"),
},
std::borrow::Cow::Owned(output) => return Err(Error::Git(output)),
}

// Get the commit summary
let args = &["show", "--format=\"%s\"", "-s"];
let out = command("git", args, None)?;
match String::from_utf8_lossy(&out) {
std::borrow::Cow::Borrowed(summary) => {
println!("cargo:rustc-env=BUILDINFO_GIT_SUMMARY={}", summary.trim());
}
std::borrow::Cow::Owned(summary) => return Err(Error::Git(summary)),
}

Ok(())
}

fn get_build_time() -> Result<(), Error> {
// Propagate SOURCE_DATE_EPOCH if set
if let Ok(epoch_env) = env("SOURCE_DATE_EPOCH") {
if let Ok(seconds) = epoch_env.to_string_lossy().parse::<i64>() {
if let Some(time) = DateTime::from_timestamp(seconds, 0) {
println!("cargo:rustc-env=BUILDINFO_BUILD_TIME={}", time.timestamp());
return Ok(());
}
}
}

println!("cargo:rustc-env=BUILDINFO_BUILD_TIME={}", Utc::now().timestamp());
Ok(())
}

fn main() {
if let Err(err) = try_main() {
eprintln!("{}", err);
std::process::exit(1);
}
}

fn try_main() -> Result<(), Error> {
// This should include all top-level directories that contain source code or otherwise modify the build in meaningful ways
let top_level = std::path::PathBuf::from("../..").canonicalize()?;
println!("cargo::rerun-if-changed={}/boulder", top_level.display());
println!("cargo::rerun-if-changed={}/crates", top_level.display());
println!("cargo::rerun-if-changed={}/moss", top_level.display());
println!("cargo::rerun-if-changed={}/test", top_level.display());
println!("cargo::rerun-if-changed={}/Cargo.toml", top_level.display());

let version = env("CARGO_PKG_VERSION")?;
println!("cargo:rustc-env=BUILDINFO_VERSION={}", version.to_string_lossy());

get_build_time()?;

get_git_info()?;

Ok(())
}

#[derive(Debug, Error)]
pub enum Error {
#[error("Missing `{0}` environment variable")]
Env(String),
#[error("{0}: terminated with {1}")]
CommandExit(String, String),
#[error("{0}: killed by signal")]
CommandKilled(String),
#[error("git: invalid output: {0}")]
Git(String),
#[error("io")]
Io(#[from] io::Error),
}
102 changes: 102 additions & 0 deletions crates/serpent_buildinfo/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// SPDX-FileCopyrightText: Copyright © 2020-2024 Serpent OS Developers
//
// SPDX-License-Identifier: MPL-2.0

use std::num::ParseIntError;

use chrono::DateTime;

mod values;

/// Returns the version of the project, as defined in the top-level Cargo.toml
///
/// This will look like "0.1.0"
pub const fn get_version() -> &'static str {
values::VERSION
}

/// Returns the build time of the project, printed in UTC time format
///
/// If SOURCE_DATE_EPOCH is set during the build then that will be the timestamp returned
///
/// This will look like "Tue, 9 Jul 2024 03:28:36 +0000"
pub fn get_build_time() -> Result<String, ParseIntError> {
let time = values::BUILD_TIME.parse::<i64>()?;
let build_time = DateTime::from_timestamp(time, 0).unwrap();

Ok(build_time.to_rfc2822())
}

/// Returns `true` if the project was built from a git source, `false` otherwise
pub const fn get_if_git_build() -> bool {
cfg!(BUILDINFO_IS_GIT_BUILD)
}

/// Returns `true` if the project was built from a dirty git source, `false` otherwise
pub const fn get_if_git_dirty() -> bool {
cfg!(BUILDINFO_IS_DIRTY)
}

/// Returns the git hash that the project was built from if built from a git source
///
/// This currently returns the SHA1 hash, though eventually it will return the SHA256 one
///
/// If built from a non-git source (like a release archive) this will return "unknown"
#[cfg(BUILDINFO_IS_GIT_BUILD)]
pub const fn get_git_full_hash() -> &'static str {
values::GIT_FULL_HASH
}

/// Returns the git hash that the project was built from if built from a git source
///
/// This currently returns the SHA1 hash, though eventually it will return the SHA256 one
///
/// If built from a non-git source (like a release archive) this will return "unknown"
#[cfg(not(BUILDINFO_IS_GIT_BUILD))]
pub const fn get_git_full_hash() -> &'static str {
"unknown"
}

/// Returns the shortened form of the git hash that this project was built from if built from git source
///
/// If built from a non-git source (like a release archive) this will return "unknown"
#[cfg(BUILDINFO_IS_GIT_BUILD)]
pub const fn get_git_short_hash() -> &'static str {
values::GIT_SHORT_HASH
}

/// Returns the shortened form of the git hash that this project was built from if built from git source
///
/// If built from a non-git source (like a release archive) this will return "unknown"
#[cfg(not(BUILDINFO_IS_GIT_BUILD))]
pub const fn get_git_short_hash() -> &'static str {
"unknown"
}

/// Returns the summary of the git commit that the project was built from
///
/// If built from a non-git source (like a release archive) this will return "unknown"
#[cfg(BUILDINFO_IS_GIT_BUILD)]
pub const fn get_git_summary() -> &'static str {
values::GIT_SUMMARY
}

/// Returns the summary of the git commit that the project was built from
///
/// If built from a non-git source (like a release archive) this will return "unknown"
#[cfg(not(BUILDINFO_IS_GIT_BUILD))]
pub const fn get_git_summary() -> &'static str {
"unknown"
}

/// For git builds this returns a string like `v0.1.0 (git 4ecad5d7e70c2cdc81350dc6b46fb55b1ccb18f5-dirty)`
///
/// For builds from a non-git source just the version will be returned: `v0.1.0`
pub fn get_simple_version() -> String {
if cfg!(BUILDINFO_IS_GIT_BUILD) {
let dirty = if get_if_git_dirty() { "-dirty" } else { "" };
format!("v{} (git {}{})", values::VERSION, get_git_full_hash(), dirty)
} else {
format!("v{}", values::VERSION)
}
}
16 changes: 16 additions & 0 deletions crates/serpent_buildinfo/src/values.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-FileCopyrightText: Copyright © 2020-2024 Serpent OS Developers
//
// SPDX-License-Identifier: MPL-2.0

pub(crate) const VERSION: &str = env!("BUILDINFO_VERSION");

pub(crate) const BUILD_TIME: &str = env!("BUILDINFO_BUILD_TIME");

#[cfg(BUILDINFO_IS_GIT_BUILD)]
pub(crate) const GIT_FULL_HASH: &str = env!("BUILDINFO_GIT_FULL_HASH");

#[cfg(BUILDINFO_IS_GIT_BUILD)]
pub(crate) const GIT_SHORT_HASH: &str = env!("BUILDINFO_GIT_SHORT_HASH");

#[cfg(BUILDINFO_IS_GIT_BUILD)]
pub(crate) const GIT_SUMMARY: &str = env!("BUILDINFO_GIT_SUMMARY");
1 change: 1 addition & 0 deletions moss/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ rust-version.workspace = true
config = { path = "../crates/config" }
container = { path = "../crates/container" }
dag = { path = "../crates/dag" }
serpent_buildinfo = { path = "../crates/serpent_buildinfo" }
stone = { path = "../crates/stone" }
triggers = { path = "../crates/triggers" }
tui = { path = "../crates/tui" }
Expand Down
15 changes: 0 additions & 15 deletions moss/build.rs

This file was deleted.

Loading

0 comments on commit 9dd084e

Please sign in to comment.