pub mod binary;
pub mod managers;
pub mod update;
pub use binary::{BinaryPackager, BinaryPackagingConfig};
pub use managers::{generate_all_packages, PackageManager, PackageManagerFactory, PackageMetadata};
pub use update::{UpdateChannel, UpdateConfig, UpdateManager, UpdateState, VersionInfo};
use crate::error::VoirsCLIError;
use anyhow::Result;
use std::path::PathBuf;
use tracing::{error, info};
#[derive(Debug, Clone)]
pub struct PackagingOptions {
pub binary_config: BinaryPackagingConfig,
pub package_metadata: PackageMetadata,
pub output_directory: PathBuf,
pub managers: Vec<String>,
pub update_config: UpdateConfig,
}
impl Default for PackagingOptions {
fn default() -> Self {
Self {
binary_config: BinaryPackagingConfig::default(),
package_metadata: PackageMetadata::default(),
output_directory: PathBuf::from("packages"),
managers: vec![
"homebrew".to_string(),
"chocolatey".to_string(),
"scoop".to_string(),
"debian".to_string(),
],
update_config: UpdateConfig::default(),
}
}
}
pub struct PackagingPipeline {
options: PackagingOptions,
}
impl PackagingPipeline {
pub fn new(options: PackagingOptions) -> Self {
Self { options }
}
pub async fn run_full_packaging(&self) -> Result<Vec<PathBuf>> {
info!("Starting full packaging pipeline");
let mut package_paths = Vec::new();
info!("Step 1: Building optimized binary");
let binary_packager = BinaryPackager::new(self.options.binary_config.clone());
let binary_path = binary_packager.package_binary()?;
if !binary_packager.validate_binary(&binary_path)? {
return Err(
VoirsCLIError::PackagingError("Binary validation failed".to_string()).into(),
);
}
let binary_size = binary_packager.get_binary_size(&binary_path)?;
info!(
"Binary size: {} bytes ({:.2} MB)",
binary_size,
binary_size as f64 / 1_048_576.0
);
let mut metadata = self.options.package_metadata.clone();
metadata.binary_path = binary_path;
info!(
"Step 2: Generating packages for managers: {:?}",
self.options.managers
);
for manager_name in &self.options.managers {
match self
.generate_package_for_manager(manager_name, &metadata)
.await
{
Ok(path) => {
package_paths.push(path);
info!("Successfully generated {} package", manager_name);
}
Err(e) => {
error!("Failed to generate {} package: {}", manager_name, e);
}
}
}
info!(
"Packaging pipeline completed. Generated {} packages",
package_paths.len()
);
Ok(package_paths)
}
async fn generate_package_for_manager(
&self,
manager_name: &str,
metadata: &PackageMetadata,
) -> Result<PathBuf> {
let manager = PackageManagerFactory::create_manager(manager_name)?;
let manager_output_dir = self.options.output_directory.join(manager_name);
std::fs::create_dir_all(&manager_output_dir)?;
let package_path = manager.generate_package(metadata, &manager_output_dir)?;
if !manager.validate_package(&package_path)? {
return Err(VoirsCLIError::PackagingError(format!(
"Package validation failed for {}",
manager_name
))
.into());
}
Ok(package_path)
}
pub fn get_package_info(&self) -> PackageInfo {
PackageInfo {
name: self.options.package_metadata.name.clone(),
version: self.options.package_metadata.version.clone(),
supported_managers: managers::PackageManagerFactory::get_supported_managers()
.iter()
.map(|&s| s.to_string())
.collect(),
binary_targets: binary::get_supported_targets()
.iter()
.map(|&s| s.to_string())
.collect(),
}
}
}
#[derive(Debug, Clone)]
pub struct PackageInfo {
pub name: String,
pub version: String,
pub supported_managers: Vec<String>,
pub binary_targets: Vec<String>,
}
pub fn validate_packaging_environment() -> Result<Vec<String>> {
info!("Validating packaging environment");
let mut issues = Vec::new();
let required_tools = vec![
("cargo", "Rust package manager"),
("git", "Version control system"),
];
for (tool, description) in required_tools {
if !is_tool_available(tool) {
issues.push(format!("Missing required tool: {} ({})", tool, description));
}
}
let optional_tools = vec![
("strip", "Binary stripping tool"),
("upx", "Binary compression tool"),
("cross", "Cross-compilation tool"),
];
for (tool, description) in optional_tools {
if !is_tool_available(tool) {
info!("Optional tool not available: {} ({})", tool, description);
}
}
if issues.is_empty() {
info!("Packaging environment validation passed");
} else {
error!(
"Packaging environment validation failed with {} issues",
issues.len()
);
}
Ok(issues)
}
fn is_tool_available(tool: &str) -> bool {
std::process::Command::new(tool)
.arg("--version")
.output()
.map(|output| output.status.success())
.unwrap_or(false)
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_packaging_options_default() {
let options = PackagingOptions::default();
assert_eq!(options.package_metadata.name, "voirs");
assert!(!options.managers.is_empty());
assert_eq!(options.output_directory, PathBuf::from("packages"));
}
#[test]
fn test_packaging_pipeline_creation() {
let options = PackagingOptions::default();
let pipeline = PackagingPipeline::new(options);
assert_eq!(pipeline.options.package_metadata.name, "voirs");
}
#[test]
fn test_package_info() {
let options = PackagingOptions::default();
let pipeline = PackagingPipeline::new(options);
let info = pipeline.get_package_info();
assert_eq!(info.name, "voirs");
assert!(!info.supported_managers.is_empty());
assert!(!info.binary_targets.is_empty());
}
#[test]
fn test_validate_packaging_environment() {
let issues = validate_packaging_environment().unwrap();
assert!(issues.is_empty() || !issues.is_empty());
}
#[test]
fn test_is_tool_available() {
let _result = is_tool_available("echo");
}
}