xtask/src/twin.rs
//! **`cargo xtask twin [--row <product>]…`: each product's row and its `cargo install` line
//! build the same binary** (Holger v2.0 H5, docs/HOLGER-V2.md: "reproducible, both ways";
//! the four products since HOL-PRODUCTS).
//!
//! For every product of `holger_recipe::twins::PRODUCTS` (or the ones named):
//!
//! 1. the row is built by the Build Thing (`build_variant_in_container`, the pinned image,
//! `--offline --locked`, the remapped paths);
//! 2. its `cargo install` line runs in the SAME image, from the same checkout, into a
//! second target dir that the container sees at the same path (`/target`), so the
//! remapped paths are the same and nothing but the command differs;
//! 3. the two binaries are compared byte for byte. A difference is named: the sizes, how
//! many bytes differ and where the first one is.
//!
//! `holger-ui` lives in the holger-ui repository: it is checked only when `../holger-ui` is
//! checked out beside holger (its install line then runs there, `--path crates/holger-ui`),
//! and skipped by name otherwise.
//!
//! `tests/rows_equal_cargo_install.rs` (holger-recipe) holds the two COMMANDS equal on
//! every test run; this verb holds the two BINARIES equal, which needs a container engine.
use std::path::{Path, PathBuf};
use anyhow::{bail, Context, Result};
use nornir_build_thing::cargo_container::{self as cc, CargoInvocation, RunEnv};
/// Where the twin's `cargo install --root` puts its bin, inside the container.
pub const INSTALL_ROOT: &str = "/target/twin-root";
/// Pure: the container argv of an install line. `cargo` is dropped (the script adds it),
/// `--locked` too (the script appends it, and cargo refuses it twice), and the install
/// goes to [`INSTALL_ROOT`] with the container's target dir, so its paths are the row's.
pub fn container_install_args(line: &str) -> Vec<String> {
let mut a: Vec<String> = line
.split_whitespace()
.skip(1)
.filter(|w| *w != "--locked")
.map(String::from)
.collect();
a.extend([
"--root".to_string(),
INSTALL_ROOT.to_string(),
"--target-dir".into(),
cc::TARGETDIR.to_string(),
]);
a
}
/// How two binaries compare.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Identity {
Identical {
bytes: u64,
},
Differ {
row: u64,
install: u64,
differing: u64,
first_at: u64,
},
}
/// Pure: compare the row's bytes with the install's.
pub fn compare(row: &[u8], install: &[u8]) -> Identity {
if row == install {
return Identity::Identical {
bytes: row.len() as u64,
};
}
let common = row.len().min(install.len());
let tail = (row.len().max(install.len()) - common) as u64;
let differing = tail + row.iter().zip(install).filter(|(a, b)| a != b).count() as u64;
let first_at = row
.iter()
.zip(install)
.position(|(a, b)| a != b)
.unwrap_or(common) as u64;
Identity::Differ {
row: row.len() as u64,
install: install.len() as u64,
differing,
first_at,
}
}
/// Pure: the products to check: the named ones (each must be a product), or all of them.
pub fn chosen_rows(args: &[String]) -> Result<Vec<&'static holger_recipe::twins::Product>> {
let mut names = Vec::new();
let mut it = args.iter();
while let Some(a) = it.next() {
match a.as_str() {
"--row" => names.push(it.next().context("`--row` needs a row name")?.clone()),
other => {
bail!("twin: unknown argument `{other}` (usage: cargo xtask twin [--row <row>]…)")
}
}
}
let all = holger_recipe::twins::PRODUCTS;
if names.is_empty() {
return Ok(all.iter().collect());
}
names
.iter()
.map(|n| all.iter().find(|r| r.name == n).with_context(|| format!("`{n}` is no product (the products: holger, holger-downstream, holger-ui, holger-cli)")))
.collect()
}
/// Where a product's install line runs, and the line: holger's root for its own crates;
/// the holger-ui checkout beside it for the console (`None` when it is not there).
pub fn install_at(root: &Path, r: &holger_recipe::twins::Product) -> Option<(PathBuf, String)> {
if r.name == holger_recipe::variants::HOLGER_UI {
let ui = root.join("../holger-ui");
return ui.join("crates/holger-ui/Cargo.toml").is_file().then(|| {
(
ui,
"cargo install --locked --path crates/holger-ui".to_string(),
)
});
}
Some((root.to_path_buf(), r.install.to_string()))
}
/// The verb. `Ok(true)` when every row's binary is its install's, byte for byte.
pub fn run(root: &Path, args: &[String]) -> Result<bool> {
let rows = chosen_rows(args)?;
let road = crate::cargo_road(root)?;
let set = holger_build_space::build_variant_set()?;
let twin_target = PathBuf::from(format!("{}-twin", road.target_dir.display()));
std::fs::create_dir_all(&twin_target)?;
let mut all_same = true;
let mut lines = Vec::new();
for r in rows {
let Some((install_root, install)) = install_at(root, r) else {
let line = format!(
"{}: skipped, ../holger-ui is not checked out beside holger",
r.name
);
println!("{line}");
lines.push(line);
continue;
};
let v = set.require(r.name)?;
let bin = v
.bin
.clone()
.with_context(|| format!("row `{}` names no bin", r.name))?;
println!("\n=== twin `{}`: the row ===", r.name);
let out = cc::build_variant_in_container(
&set,
r.name,
root,
&road.target_dir,
road.registry.as_deref(),
&road.tc,
&road.runner,
)?;
if !out.success {
bail!("the row `{}` did not build", r.name);
}
let row_bin = out
.artifacts
.iter()
.find(|p| p.file_name().and_then(|f| f.to_str()) == Some(bin.as_str()))
.cloned()
.with_context(|| {
format!(
"the row `{}` produced no `{bin}`: {:?}",
r.name, out.artifacts
)
})?;
println!("=== twin `{}`: {install} ===", r.name);
let run = cc::run_cargo_in_container(
&road.tc,
&CargoInvocation {
label: format!("twin-{}", r.name),
repo_root: install_root,
target_dir: twin_target.clone(),
registry: road.registry.clone(),
cargo_args: container_install_args(&install),
cfg: v.cfg.clone(),
env: RunEnv::new(),
extra_binds: Vec::new(),
},
&road.runner,
)?;
if !run.success() {
bail!(
"`{install}` did not build in the container:\n{}",
run.outcome.stderr.join("\n")
);
}
let install_bin = twin_target.join("twin-root/bin").join(&bin);
let (a, b) = (
std::fs::read(&row_bin).with_context(|| format!("read {}", row_bin.display()))?,
std::fs::read(&install_bin)
.with_context(|| format!("read {}", install_bin.display()))?,
);
let line = match compare(&a, &b) {
Identity::Identical { bytes } => format!(
"{}: row = `{install}`: byte-identical, {bytes} bytes",
r.name
),
Identity::Differ {
row,
install: install_n,
differing,
first_at,
} => {
all_same = false;
format!(
"{}: row != `{install}`: {row} against {} bytes, {differing} differ, the first at offset {first_at} ({} and {})",
r.name,
install_n,
row_bin.display(),
install_bin.display()
)
}
};
println!("{line}");
lines.push(line);
}
println!("\n=== twin ===\n{}", lines.join("\n"));
Ok(all_same)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_install_runs_into_the_containers_target_dir_and_names_lock_once() {
let a = container_install_args(
"cargo install --locked --path holger-downstream --features full",
);
assert_eq!(
a,
[
"install",
"--path",
"holger-downstream",
"--features",
"full",
"--root",
INSTALL_ROOT,
"--target-dir",
"/target"
]
);
assert!(
INSTALL_ROOT.starts_with(cc::TARGETDIR),
"the installed bin lands in the mounted twin target"
);
}
#[test]
fn identical_bytes_are_identical_and_a_difference_is_located() {
assert_eq!(compare(b"abc", b"abc"), Identity::Identical { bytes: 3 });
assert_eq!(
compare(b"abcd", b"abxd"),
Identity::Differ {
row: 4,
install: 4,
differing: 1,
first_at: 2
}
);
assert_eq!(
compare(b"ab", b"abcd"),
Identity::Differ {
row: 2,
install: 4,
differing: 2,
first_at: 2
}
);
}
#[test]
fn every_product_is_checked_unless_some_are_named() {
assert_eq!(chosen_rows(&[]).unwrap().len(), 4);
let one = chosen_rows(&["--row".into(), "holger-downstream".into()]).unwrap();
assert_eq!(
one.iter().map(|r| r.name).collect::<Vec<_>>(),
["holger-downstream"]
);
assert!(
chosen_rows(&["--row".into(), "mvp-server-musl".into()]).is_err(),
"only a product has a twin"
);
assert!(chosen_rows(&["--all".into()]).is_err());
}
#[test]
fn the_console_installs_from_the_holger_ui_checkout_or_is_skipped() {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path().join("holger");
std::fs::create_dir_all(&root).unwrap();
let ui = holger_recipe::twins::PRODUCTS
.iter()
.find(|r| r.name == "holger-ui")
.unwrap();
assert!(
install_at(&root, ui).is_none(),
"no holger-ui beside: skipped"
);
std::fs::create_dir_all(tmp.path().join("holger-ui/crates/holger-ui")).unwrap();
std::fs::write(tmp.path().join("holger-ui/crates/holger-ui/Cargo.toml"), "").unwrap();
let (at, line) = install_at(&root, ui).unwrap();
assert!(at.ends_with("holger/../holger-ui"), "{}", at.display());
assert_eq!(line, "cargo install --locked --path crates/holger-ui");
let server = holger_recipe::twins::PRODUCTS
.iter()
.find(|r| r.name == "holger")
.unwrap();
assert_eq!(
install_at(&root, server).unwrap(),
(
root.clone(),
"cargo install --locked --path server/cli".to_string()
)
);
}
}