use super::types::{AssetInfo, BinaryInfo, ConfigInfo, PackageConfig};
use super::utils;
use anyhow::Result;
use std::fs;
use std::path::{Path, PathBuf};
pub(super) fn collect_binaries(project_root: &Path, config: &PackageConfig) -> Result<Vec<BinaryInfo>> {
let target_dir = project_root
.join("target")
.join(config.target_arch.as_str())
.join(&config.build_profile);
if !target_dir.exists() {
return Err(anyhow::anyhow!(
"Target directory not found: {}. Did the build succeed?",
target_dir.display()
));
}
let mut binaries = Vec::new();
for entry in fs::read_dir(&target_dir)? {
let entry = entry?;
let path = entry.path();
if path.is_file() && utils::is_executable(&path)? {
if let Some(filename) = path.file_name() {
let filename_str = filename.to_string_lossy();
if filename_str.starts_with('.') || filename_str.contains(".d") || filename_str.ends_with(".rlib") {
continue;
}
let metadata = path.metadata()?;
let checksum = utils::calculate_checksum(&path)?;
binaries.push(BinaryInfo {
name: filename_str.to_string(),
path: format!("bin/{}", filename_str),
size_bytes: metadata.len(),
checksum,
});
}
}
}
if binaries.is_empty() {
return Err(anyhow::anyhow!("No binaries found in target directory"));
}
Ok(binaries)
}
pub(super) fn collect_configs(project_root: &Path) -> Result<Vec<ConfigInfo>> {
let files = collect_files_from_roots(project_root, &["config", "configs"], |path| {
let extension = path.extension().and_then(|e| e.to_str());
matches!(extension, Some("json") | Some("yaml") | Some("yml") | Some("toml"))
})?;
files
.into_iter()
.map(|(manifest_path, path)| {
let config_type = utils::determine_config_type(&path);
let checksum = utils::calculate_checksum(&path)?;
Ok(ConfigInfo {
name: path.file_name().unwrap().to_string_lossy().to_string(),
path: manifest_path,
config_type,
checksum,
})
})
.collect()
}
pub(super) fn collect_assets(project_root: &Path) -> Result<Vec<AssetInfo>> {
let files = collect_files_from_roots(project_root, &["assets", "models", "data", "calibration"], |_| true)?;
files
.into_iter()
.map(|(manifest_path, path)| {
let asset_type = utils::determine_asset_type(&path);
let metadata = path.metadata()?;
let checksum = utils::calculate_checksum(&path)?;
Ok(AssetInfo {
name: path.file_name().unwrap().to_string_lossy().to_string(),
path: manifest_path,
asset_type,
size_bytes: metadata.len(),
checksum,
})
})
.collect()
}
fn collect_files_from_roots(
project_root: &Path,
roots: &[&str],
matches: impl Fn(&Path) -> bool,
) -> Result<Vec<(String, PathBuf)>> {
let mut results = Vec::new();
for root_name in roots {
let root_dir = project_root.join(root_name);
if root_dir.exists() {
collect_files_recursive(&root_dir, &root_dir, root_name, &matches, &mut results)?;
}
}
Ok(results)
}
fn collect_files_recursive(
base_dir: &Path,
current_dir: &Path,
root_name: &str,
matches: &impl Fn(&Path) -> bool,
results: &mut Vec<(String, PathBuf)>,
) -> Result<()> {
for entry in fs::read_dir(current_dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
collect_files_recursive(base_dir, &path, root_name, matches, results)?;
} else if path.is_file() && matches(&path) {
let relative_path = path.strip_prefix(base_dir)?;
results.push((format!("{}/{}", root_name, relative_path.display()), path));
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
#[test]
fn collect_assets_preserves_distinct_root_in_manifest_path() {
let project_root = tempfile::tempdir().unwrap();
let models_dir = project_root.path().join("models");
let data_dir = project_root.path().join("data");
fs::create_dir_all(&models_dir).unwrap();
fs::create_dir_all(&data_dir).unwrap();
fs::write(models_dir.join("foo.bin"), b"model bytes").unwrap();
fs::write(data_dir.join("foo.bin"), b"data bytes").unwrap();
let assets = collect_assets(project_root.path()).unwrap();
let paths: Vec<&str> = assets.iter().map(|a| a.path.as_str()).collect();
assert_eq!(assets.len(), 2, "expected one asset per source root, got: {:?}", paths);
assert!(
paths.contains(&"models/foo.bin"),
"expected models/foo.bin in manifest paths, got: {:?}",
paths
);
assert!(
paths.contains(&"data/foo.bin"),
"expected data/foo.bin in manifest paths, got: {:?}",
paths
);
assert!(
!paths.contains(&"assets/foo.bin"),
"manifest paths must not collapse distinct roots into a single prefix: {:?}",
paths
);
}
#[test]
fn collect_configs_preserves_distinct_root_in_manifest_path() {
let project_root = tempfile::tempdir().unwrap();
let config_dir = project_root.path().join("config");
let configs_dir = project_root.path().join("configs");
fs::create_dir_all(&config_dir).unwrap();
fs::create_dir_all(&configs_dir).unwrap();
fs::write(config_dir.join("settings.json"), b"{}").unwrap();
fs::write(configs_dir.join("settings.json"), b"{}").unwrap();
let configs = collect_configs(project_root.path()).unwrap();
let paths: Vec<&str> = configs.iter().map(|c| c.path.as_str()).collect();
assert_eq!(
configs.len(),
2,
"expected one config per source root, got: {:?}",
paths
);
assert!(paths.contains(&"config/settings.json"), "got: {:?}", paths);
assert!(paths.contains(&"configs/settings.json"), "got: {:?}", paths);
}
}