use cargo_metadata::MetadataCommand;
use std::path::{Path, PathBuf};
use thiserror::Error;
pub const KNOWN_GLIBC_ONLY_CRATES: &[&str] = &[
"cudarc",
"dbus",
"hyper-tls",
"libnss-mdns",
"libudev-sys",
"nss-files",
"openssl-sys",
"rust-cuda",
];
#[derive(Debug, Error, PartialEq, Eq)]
pub enum MuslPreflightError {
#[error("`cargo metadata` failed for `{package}`: {reason}")]
CargoMetadata { package: String, reason: String },
#[error("package `{package}` not found in the workspace metadata at {manifest_path}")]
PackageNotFound {
package: String,
manifest_path: PathBuf,
},
#[error(
"package `{package}` cannot build musl-static — depends on glibc-only crate(s): {offenders:?}. \
Route this build to the container fallback (set `runtime = \"container\"` on the upstream build step, \
or use the `cross` toolchain) — or replace the offending dep with a musl-friendly alternative."
)]
NotMuslSafe {
package: String,
offenders: Vec<String>,
},
}
pub fn check_dep_list<I, S>(package: &str, dep_names: I) -> Result<(), MuslPreflightError>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let mut offenders: Vec<String> = dep_names
.into_iter()
.filter(|d| KNOWN_GLIBC_ONLY_CRATES.contains(&d.as_ref()))
.map(|d| d.as_ref().to_string())
.collect();
offenders.sort();
offenders.dedup();
if offenders.is_empty() {
Ok(())
} else {
Err(MuslPreflightError::NotMuslSafe {
package: package.to_string(),
offenders,
})
}
}
pub fn check_musl_compatibility(
workspace_root: &Path,
package: &str,
) -> Result<(), MuslPreflightError> {
let manifest_path = workspace_root.join("Cargo.toml");
let metadata = MetadataCommand::new()
.manifest_path(&manifest_path)
.exec()
.map_err(|e| MuslPreflightError::CargoMetadata {
package: package.to_string(),
reason: e.to_string(),
})?;
let root_id = metadata
.workspace_members
.iter()
.find(|id| metadata[id].name == package)
.cloned()
.ok_or_else(|| MuslPreflightError::PackageNotFound {
package: package.to_string(),
manifest_path: manifest_path.clone(),
})?;
let resolve = metadata
.resolve
.as_ref()
.ok_or_else(|| MuslPreflightError::CargoMetadata {
package: package.to_string(),
reason: "cargo metadata returned no resolved dep graph".into(),
})?;
let node_by_id: std::collections::HashMap<_, _> =
resolve.nodes.iter().map(|n| (n.id.clone(), n)).collect();
let mut seen = std::collections::HashSet::new();
let mut frontier = vec![root_id.clone()];
let mut reachable_names = Vec::new();
while let Some(id) = frontier.pop() {
if !seen.insert(id.clone()) {
continue;
}
if let Some(node) = node_by_id.get(&id) {
reachable_names.push(metadata[&id].name.clone());
for dep in &node.dependencies {
frontier.push(dep.clone());
}
}
}
check_dep_list(package, reachable_names.iter().map(String::as_str))
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AuditRow {
pub package: String,
pub offenders: Vec<String>,
}
impl AuditRow {
pub fn is_clean(&self) -> bool {
self.offenders.is_empty()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WorkspaceAudit {
pub rows: Vec<AuditRow>,
}
impl WorkspaceAudit {
pub fn clean(&self) -> impl Iterator<Item = &AuditRow> {
self.rows.iter().filter(|r| r.is_clean())
}
pub fn blocked(&self) -> impl Iterator<Item = &AuditRow> {
self.rows.iter().filter(|r| !r.is_clean())
}
}
pub fn audit_workspace(workspace_root: &Path) -> Result<WorkspaceAudit, MuslPreflightError> {
let manifest_path = workspace_root.join("Cargo.toml");
let metadata = MetadataCommand::new()
.manifest_path(&manifest_path)
.exec()
.map_err(|e| MuslPreflightError::CargoMetadata {
package: "<workspace>".into(),
reason: e.to_string(),
})?;
let resolve = metadata
.resolve
.as_ref()
.ok_or_else(|| MuslPreflightError::CargoMetadata {
package: "<workspace>".into(),
reason: "cargo metadata returned no resolved dep graph".into(),
})?;
let node_by_id: std::collections::HashMap<_, _> =
resolve.nodes.iter().map(|n| (n.id.clone(), n)).collect();
let mut rows: Vec<AuditRow> = Vec::with_capacity(metadata.workspace_members.len());
for member_id in &metadata.workspace_members {
let package = metadata[member_id].name.clone();
let mut seen = std::collections::HashSet::new();
let mut frontier = vec![member_id.clone()];
let mut reachable_names = Vec::new();
while let Some(id) = frontier.pop() {
if !seen.insert(id.clone()) {
continue;
}
if let Some(node) = node_by_id.get(&id) {
reachable_names.push(metadata[&id].name.clone());
for dep in &node.dependencies {
frontier.push(dep.clone());
}
}
}
let offenders = match check_dep_list(&package, reachable_names.iter().map(String::as_str)) {
Ok(()) => Vec::new(),
Err(MuslPreflightError::NotMuslSafe { offenders, .. }) => offenders,
Err(other) => return Err(other),
};
rows.push(AuditRow { package, offenders });
}
rows.sort_by(|a, b| a.package.cmp(&b.package));
Ok(WorkspaceAudit { rows })
}
pub fn render_markdown(audit: &WorkspaceAudit) -> String {
let total = audit.rows.len();
let clean = audit.clean().count();
let blocked = total - clean;
let mut out = String::new();
out.push_str("# yah workspace musl-static audit\n\n");
out.push_str(&format!(
"**{clean}/{total} workspace members are musl-static clean** ({blocked} blocked).\n\n"
));
out.push_str(
"Gated crate list: [`KNOWN_GLIBC_ONLY_CRATES`](../../crates/yah/qed/src/preflight.rs). \
Regenerate with `yah qed audit-musl`.\n\n",
);
out.push_str("| Package | Musl-static | Glibc-only deps |\n");
out.push_str("|---------|-------------|-----------------|\n");
for row in audit.clean() {
out.push_str(&format!("| `{}` | ✓ | — |\n", row.package));
}
for row in audit.blocked() {
let offenders = row
.offenders
.iter()
.map(|o| format!("`{o}`"))
.collect::<Vec<_>>()
.join(", ");
out.push_str(&format!("| `{}` | ✗ | {} |\n", row.package, offenders));
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn check_dep_list_passes_clean_tree() {
let deps = ["tokio", "serde", "thiserror", "uuid"];
check_dep_list("yubaba", deps).unwrap();
}
#[test]
fn check_dep_list_flags_openssl_sys() {
let deps = ["tokio", "openssl-sys", "serde"];
let err = check_dep_list("yubaba", deps).unwrap_err();
match err {
MuslPreflightError::NotMuslSafe { package, offenders } => {
assert_eq!(package, "yubaba");
assert_eq!(offenders, vec!["openssl-sys".to_string()]);
}
other => panic!("expected NotMuslSafe, got {other:?}"),
}
}
#[test]
fn check_dep_list_collects_and_dedups_multiple_offenders() {
let deps = [
"tokio",
"openssl-sys",
"hyper-tls",
"openssl-sys",
"dbus",
"hyper-tls",
];
let err = check_dep_list("yah", deps).unwrap_err();
match err {
MuslPreflightError::NotMuslSafe { offenders, .. } => {
assert_eq!(offenders, vec!["dbus", "hyper-tls", "openssl-sys"]);
}
other => panic!("expected NotMuslSafe, got {other:?}"),
}
}
#[test]
fn known_glibc_list_is_sorted_for_easy_diff_review() {
let mut sorted = KNOWN_GLIBC_ONLY_CRATES.to_vec();
sorted.sort();
assert_eq!(
sorted, KNOWN_GLIBC_ONLY_CRATES,
"KNOWN_GLIBC_ONLY_CRATES must stay sorted",
);
}
#[test]
fn qed_workspace_passes_musl_gate() {
let workspace_root = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.ancestors()
.find(|p| p.join("Cargo.lock").is_file())
.expect("workspace root has Cargo.lock")
.to_path_buf();
check_musl_compatibility(&workspace_root, "yah-qed").expect("yah-qed crate is musl-safe");
}
#[test]
fn audit_workspace_returns_one_row_per_member_and_qed_is_clean() {
let workspace_root = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.ancestors()
.find(|p| p.join("Cargo.lock").is_file())
.expect("workspace root has Cargo.lock")
.to_path_buf();
let audit = audit_workspace(&workspace_root).expect("workspace audit succeeds");
assert!(!audit.rows.is_empty(), "workspace has members");
let qed = audit
.rows
.iter()
.find(|r| r.package == "yah-qed")
.expect("yah-qed appears in audit");
assert!(qed.is_clean(), "yah-qed should be musl-static clean: {qed:?}");
let mut sorted = audit.rows.clone();
sorted.sort_by(|a, b| a.package.cmp(&b.package));
assert_eq!(audit.rows, sorted, "audit rows are alphabetically sorted");
}
#[test]
fn render_markdown_groups_clean_first_then_blocked_with_summary() {
let audit = WorkspaceAudit {
rows: vec![
AuditRow {
package: "yubaba".into(),
offenders: vec![],
},
AuditRow {
package: "yah".into(),
offenders: vec!["openssl-sys".into()],
},
AuditRow {
package: "qed".into(),
offenders: vec![],
},
AuditRow {
package: "desktop".into(),
offenders: vec!["dbus".into(), "hyper-tls".into()],
},
],
};
let md = render_markdown(&audit);
assert!(md.contains("**2/4 workspace members are musl-static clean** (2 blocked)."));
assert!(md.contains("| Package | Musl-static | Glibc-only deps |"));
assert!(md.contains("| `yubaba` | ✓ | — |"));
assert!(md.contains("| `qed` | ✓ | — |"));
assert!(md.contains("| `yah` | ✗ | `openssl-sys` |"));
assert!(md.contains("| `desktop` | ✗ | `dbus`, `hyper-tls` |"));
let clean_at = md.find("| `yubaba` |").unwrap();
let blocked_at = md.find("| `yah` |").unwrap();
assert!(
clean_at < blocked_at,
"clean rows render before blocked rows for scanability",
);
}
#[test]
fn render_markdown_is_byte_stable_for_the_same_audit() {
let audit = WorkspaceAudit {
rows: vec![
AuditRow {
package: "a".into(),
offenders: vec![],
},
AuditRow {
package: "b".into(),
offenders: vec!["openssl-sys".into()],
},
],
};
assert_eq!(render_markdown(&audit), render_markdown(&audit));
}
#[test]
fn workspace_audit_clean_and_blocked_iterators_partition_rows() {
let audit = WorkspaceAudit {
rows: vec![
AuditRow {
package: "a".into(),
offenders: vec![],
},
AuditRow {
package: "b".into(),
offenders: vec!["dbus".into()],
},
AuditRow {
package: "c".into(),
offenders: vec![],
},
],
};
assert_eq!(audit.clean().count(), 2);
assert_eq!(audit.blocked().count(), 1);
assert_eq!(audit.blocked().next().unwrap().package, "b");
}
#[test]
fn package_not_found_surfaces_clearly() {
let workspace_root = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.ancestors()
.find(|p| p.join("Cargo.lock").is_file())
.expect("workspace root has Cargo.lock")
.to_path_buf();
let err =
check_musl_compatibility(&workspace_root, "definitely-not-a-real-package").unwrap_err();
assert!(matches!(err, MuslPreflightError::PackageNotFound { .. }));
}
}