use std::collections::BTreeSet;
use std::path::Path;
use std::process::Command;
use fleetreach_core::semver::Version;
pub fn host_triple() -> Option<String> {
let output = Command::new("rustc").arg("-vV").output().ok()?;
if !output.status.success() {
return None;
}
let text = String::from_utf8(output.stdout).ok()?;
text.lines()
.find_map(|line| line.strip_prefix("host: "))
.map(|triple| triple.trim().to_string())
}
pub fn built_package_set(
project_dir: &Path,
host_triple: &str,
) -> Result<BTreeSet<(String, Version)>, String> {
let output = Command::new("cargo")
.current_dir(project_dir)
.env("CARGO_NET_GIT_FETCH_WITH_CLI", "false")
.args([
"tree",
"--edges",
"normal,build",
"--prefix",
"none",
"--target",
host_triple,
"--format",
"{p}",
"--locked",
"--offline",
])
.output()
.map_err(|e| format!("running cargo tree: {e}"))?;
if !output.status.success() {
return Err(format!(
"cargo tree failed in {}: {}",
project_dir.display(),
String::from_utf8_lossy(&output.stderr).trim()
));
}
let text = String::from_utf8_lossy(&output.stdout);
Ok(parse_package_specs(&text))
}
fn parse_package_specs(text: &str) -> BTreeSet<(String, Version)> {
let mut set = BTreeSet::new();
for line in text.lines() {
let mut parts = line.split_whitespace();
let (Some(name), Some(version)) = (parts.next(), parts.next()) else {
continue;
};
if let Ok(version) = Version::parse(version.trim_start_matches('v')) {
set.insert((name.to_string(), version));
}
}
set
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_cargo_tree_package_specs() {
let text = "fleetreach-cli v0.1.0 (/path)\njiff v0.2.0\nnot a version line\n";
let set = parse_package_specs(text);
assert!(set.contains(&("jiff".to_string(), Version::new(0, 2, 0))));
assert!(set.contains(&("fleetreach-cli".to_string(), Version::new(0, 1, 0))));
assert_eq!(set.len(), 2);
}
}