use std::process::Command;
fn main() {
let describe = Command::new("git")
.args([
"-c", "safe.directory=*",
"-c", "core.checkStat=minimal",
"-c", "filter.lfs.required=false",
"-c", "filter.lfs.process=",
"-c", "filter.lfs.clean=",
"-c", "filter.lfs.smudge=",
"describe",
"--tags",
"--always",
"--dirty=-modified",
"--abbrev=7",
])
.output()
.ok()
.filter(|o| o.status.success())
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_owned())
.filter(|s| !s.is_empty());
self_plugin(describe.as_deref());
let mut inputs: Vec<(&str, String)> = Vec::new();
if let Some(d) = &describe {
inputs.push(("source-commit", d.clone()));
}
match gunnar_recipe::emit::write_receipt(&inputs) {
Ok(emitted) => {
if let Some(variant) = emitted.attribution.variant() {
println!("cargo:rustc-env=GUNNAR_BUILD_VARIANT={}", variant.name);
}
}
Err(e) => panic!(
"the build-thing receipt was refused: {e:#}\n\
This is a defect in gunnar-build-thing's declaration, not in this build. \
A binary that ships with a receipt its own audit would reject is worse than \
one with none, so the build stops here."
),
}
for what in ["HEAD", "refs"] {
let path = Command::new("git")
.args(["rev-parse", "--git-path", what])
.output()
.ok()
.filter(|o| o.status.success())
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_owned())
.filter(|s| !s.is_empty());
if let Some(path) = path {
if std::path::Path::new(&path).exists() {
println!("cargo:rerun-if-changed={path}");
}
}
}
}
fn self_plugin(describe: Option<&str>) {
use std::fmt::Write as _;
let out = std::path::PathBuf::from(std::env::var("OUT_DIR").expect("cargo sets OUT_DIR"));
let manifest = std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect("cargo sets CARGO_MANIFEST_DIR"));
println!("cargo:rerun-if-env-changed=GUNNAR_SERVER_PLUGIN_DIR");
let dir = std::env::var_os("GUNNAR_SERVER_PLUGIN_DIR").map(std::path::PathBuf::from).unwrap_or_else(|| manifest.join("../../.plugin-out"));
let module = dir.join("gunnar_monetize_guest.wasm");
let sig = dir.join("gunnar_monetize_guest.wasm.p7s");
println!("cargo:rerun-if-changed={}", module.display());
println!("cargo:rerun-if-changed={}", sig.display());
let metered = std::env::var_os("CARGO_FEATURE_METERED").is_some();
let mut code = String::new();
let staged = metered && module.is_file() && sig.is_file();
if staged {
let bytes = std::fs::read(&module).expect("read the staged plugin");
let stamp = custom_section(&bytes, "gunnar_build_ref").map(|s| String::from_utf8_lossy(s).to_string());
match (stamp.as_deref(), describe) {
(Some(s), Some(d)) if s != d => panic!(
"REFUSED [plugin-other-build] the staged plugin {} is build `{s}`, and this gunnar-server is `{d}`: \
the metered medium carries its OWN plugin, built and signed from the same commit. Re-stage it \
(`cargo xtask iso-installer` does, for a metered variant) or remove {}",
module.display(),
dir.display()
),
(None, _) => panic!("REFUSED [plugin-no-build-ref] the staged plugin {} carries no `gunnar_build_ref` section: it is not gunnar's own plugin", module.display()),
_ => {}
}
let _ = writeln!(code, "/// The signed module (`gunnar_monetize_guest.wasm`).\npub static MODULE: &[u8] = include_bytes!({:?});", module.canonicalize().unwrap_or(module.clone()).display().to_string());
let _ = writeln!(code, "/// Its detached CMS signature.\npub static SIGNATURE: &[u8] = include_bytes!({:?});", sig.canonicalize().unwrap_or(sig.clone()).display().to_string());
let _ = writeln!(code, "/// Where it was staged from at build time.\npub const SOURCE: Option<&str> = Some({:?});", dir.display().to_string());
} else {
if metered {
println!("cargo:warning=gunnar-server: no staged plugin in {} (gunnar_monetize_guest.wasm + .p7s) — this metered binary carries none and will not register one", dir.display());
}
let _ = writeln!(code, "/// No plugin was staged for this build.\npub static MODULE: &[u8] = &[];\npub static SIGNATURE: &[u8] = &[];\npub const SOURCE: Option<&str> = None;");
}
std::fs::write(out.join("self_plugin.rs"), code).expect("write self_plugin.rs");
}
fn custom_section<'a>(m: &'a [u8], name: &str) -> Option<&'a [u8]> {
fn leb(b: &[u8], at: &mut usize) -> Option<usize> {
let (mut n, mut shift) = (0usize, 0u32);
loop {
let x = *b.get(*at)?;
*at += 1;
n |= ((x & 0x7f) as usize).checked_shl(shift)?;
if x & 0x80 == 0 {
return Some(n);
}
shift += 7;
if shift > 35 {
return None;
}
}
}
if m.len() < 8 || &m[..4] != b"\0asm" {
return None;
}
let mut at = 8;
while at < m.len() {
let id = m[at];
at += 1;
let len = leb(m, &mut at)?;
let end = at.checked_add(len).filter(|e| *e <= m.len())?;
if id == 0 {
let mut p = at;
let n = leb(m, &mut p)?;
let name_end = p.checked_add(n).filter(|e| *e <= end)?;
if &m[p..name_end] == name.as_bytes() {
return Some(&m[name_end..end]);
}
}
at = end;
}
None
}