use std::path::Path;
use crate::config::{BuildConfig, BuilderKind, CrateConfig};
pub fn crate_declares_bin(crate_path: &str, wanted: &str) -> bool {
let path = Path::new(crate_path);
let doc = std::fs::read_to_string(path.join("Cargo.toml"))
.ok()
.and_then(|c| c.parse::<toml_edit::DocumentMut>().ok());
let bin_tables = doc
.as_ref()
.and_then(|d| d.get("bin"))
.and_then(|b| b.as_array_of_tables());
if let Some(arr) = bin_tables
&& arr
.iter()
.any(|t| t.get("name").and_then(|v| v.as_str()) == Some(wanted))
{
return true;
}
if path.join("src/main.rs").exists()
&& doc
.as_ref()
.and_then(|d| d.get("package"))
.and_then(|p| p.get("name"))
.and_then(|v| v.as_str())
== Some(wanted)
{
return true;
}
let stem_file = format!("{wanted}.rs");
if path.join("src/bin").join(&stem_file).exists() {
let reclaimed_under_other_name = bin_tables.is_some_and(|arr| {
arr.iter().any(|t| {
t.get("name").and_then(|v| v.as_str()) != Some(wanted)
&& t.get("path")
.and_then(|v| v.as_str())
.and_then(|p| Path::new(p).file_name()?.to_str().map(str::to_owned))
.as_deref()
== Some(stem_file.as_str())
})
});
return !reclaimed_under_other_name;
}
false
}
pub fn planned_builds(krate: &CrateConfig) -> Option<Vec<BuildConfig>> {
match krate.builds.as_deref() {
Some(b) if !b.is_empty() => Some(b.to_vec()),
_ => crate_declares_bin(&krate.path, &krate.name).then(|| {
vec![BuildConfig {
binary: Some(krate.name.clone()),
..Default::default()
}]
}),
}
}
pub fn build_produces(krate: &CrateConfig, build: &BuildConfig) -> bool {
matches!(build.builder, Some(BuilderKind::Prebuilt))
|| build.binary.is_some()
|| crate_declares_bin(&krate.path, &krate.name)
}
pub fn crate_target_list(krate: &CrateConfig, default_targets: &[String]) -> Vec<String> {
let Some(builds) = planned_builds(krate) else {
return Vec::new();
};
let mut out: Vec<String> = Vec::new();
for build in &builds {
if !build_produces(krate, build) {
continue;
}
let chosen: &[String] = match build.targets.as_deref() {
Some(ts) => ts,
None => default_targets,
};
for t in chosen {
if !out.contains(t) {
out.push(t.clone());
}
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn crate_dir(cargo_toml: &str, with_main: bool) -> tempfile::TempDir {
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("Cargo.toml"), cargo_toml).unwrap();
if with_main {
std::fs::create_dir_all(dir.path().join("src")).unwrap();
std::fs::write(dir.path().join("src/main.rs"), "fn main() {}\n").unwrap();
}
dir
}
fn krate_at(name: &str, path: &str, builds: Option<Vec<BuildConfig>>) -> CrateConfig {
CrateConfig {
name: name.to_string(),
path: path.to_string(),
builds,
..Default::default()
}
}
#[test]
fn build_produces_false_for_binary_none_library_crate() {
let dir = crate_dir("[package]\nname = \"lib\"\nversion = \"0.0.0\"\n", false);
let krate = krate_at("lib", dir.path().to_str().unwrap(), None);
let build = BuildConfig::default();
assert!(!build_produces(&krate, &build));
}
#[test]
fn build_produces_true_for_prebuilt() {
let dir = crate_dir("[package]\nname = \"lib\"\nversion = \"0.0.0\"\n", false);
let krate = krate_at("lib", dir.path().to_str().unwrap(), None);
let build = BuildConfig {
builder: Some(BuilderKind::Prebuilt),
..Default::default()
};
assert!(build_produces(&krate, &build));
}
#[test]
fn build_produces_true_for_explicit_binary() {
let dir = crate_dir("[package]\nname = \"lib\"\nversion = \"0.0.0\"\n", false);
let krate = krate_at("lib", dir.path().to_str().unwrap(), None);
let build = BuildConfig {
binary: Some("app".to_string()),
..Default::default()
};
assert!(build_produces(&krate, &build));
}
#[test]
fn build_produces_true_for_declared_bin() {
let dir = crate_dir("[package]\nname = \"app\"\nversion = \"0.0.0\"\n", true);
let krate = krate_at("app", dir.path().to_str().unwrap(), None);
let build = BuildConfig::default();
assert!(build_produces(&krate, &build));
}
#[test]
fn crate_target_list_empty_for_library_with_materialized_binary_none_build() {
let dir = crate_dir("[package]\nname = \"lib\"\nversion = \"0.0.0\"\n", false);
let krate = krate_at(
"lib",
dir.path().to_str().unwrap(),
Some(vec![BuildConfig::default()]),
);
let defaults = vec!["x86_64-unknown-linux-gnu".to_string()];
assert!(crate_target_list(&krate, &defaults).is_empty());
}
#[test]
fn crate_target_list_uses_default_targets_for_declared_bin() {
let dir = crate_dir("[package]\nname = \"app\"\nversion = \"0.0.0\"\n", true);
let krate = krate_at("app", dir.path().to_str().unwrap(), None);
let defaults = vec![
"x86_64-unknown-linux-gnu".to_string(),
"aarch64-unknown-linux-gnu".to_string(),
];
assert_eq!(crate_target_list(&krate, &defaults), defaults);
}
}