voirs_cli/packaging/
mod.rs1pub mod binary;
34pub mod managers;
35pub mod update;
36
37pub use binary::{BinaryPackager, BinaryPackagingConfig};
38pub use managers::{generate_all_packages, PackageManager, PackageManagerFactory, PackageMetadata};
39pub use update::{UpdateChannel, UpdateConfig, UpdateManager, UpdateState, VersionInfo};
40
41use crate::error::VoirsCLIError;
42use anyhow::Result;
43use std::path::PathBuf;
44use tracing::{error, info};
45
46#[derive(Debug, Clone)]
47pub struct PackagingOptions {
48 pub binary_config: BinaryPackagingConfig,
49 pub package_metadata: PackageMetadata,
50 pub output_directory: PathBuf,
51 pub managers: Vec<String>,
52 pub update_config: UpdateConfig,
53}
54
55impl Default for PackagingOptions {
56 fn default() -> Self {
57 Self {
58 binary_config: BinaryPackagingConfig::default(),
59 package_metadata: PackageMetadata::default(),
60 output_directory: PathBuf::from("packages"),
61 managers: vec![
62 "homebrew".to_string(),
63 "chocolatey".to_string(),
64 "scoop".to_string(),
65 "debian".to_string(),
66 ],
67 update_config: UpdateConfig::default(),
68 }
69 }
70}
71
72pub struct PackagingPipeline {
73 options: PackagingOptions,
74}
75
76impl PackagingPipeline {
77 pub fn new(options: PackagingOptions) -> Self {
78 Self { options }
79 }
80
81 pub async fn run_full_packaging(&self) -> Result<Vec<PathBuf>> {
82 info!("Starting full packaging pipeline");
83
84 let mut package_paths = Vec::new();
85
86 info!("Step 1: Building optimized binary");
88 let binary_packager = BinaryPackager::new(self.options.binary_config.clone());
89 let binary_path = binary_packager.package_binary()?;
90
91 if !binary_packager.validate_binary(&binary_path)? {
93 return Err(
94 VoirsCLIError::PackagingError("Binary validation failed".to_string()).into(),
95 );
96 }
97
98 let binary_size = binary_packager.get_binary_size(&binary_path)?;
100 info!(
101 "Binary size: {} bytes ({:.2} MB)",
102 binary_size,
103 binary_size as f64 / 1_048_576.0
104 );
105
106 let mut metadata = self.options.package_metadata.clone();
108 metadata.binary_path = binary_path;
109
110 info!(
112 "Step 2: Generating packages for managers: {:?}",
113 self.options.managers
114 );
115
116 for manager_name in &self.options.managers {
117 match self
118 .generate_package_for_manager(manager_name, &metadata)
119 .await
120 {
121 Ok(path) => {
122 package_paths.push(path);
123 info!("Successfully generated {} package", manager_name);
124 }
125 Err(e) => {
126 error!("Failed to generate {} package: {}", manager_name, e);
127 }
128 }
129 }
130
131 info!(
132 "Packaging pipeline completed. Generated {} packages",
133 package_paths.len()
134 );
135 Ok(package_paths)
136 }
137
138 async fn generate_package_for_manager(
139 &self,
140 manager_name: &str,
141 metadata: &PackageMetadata,
142 ) -> Result<PathBuf> {
143 let manager = PackageManagerFactory::create_manager(manager_name)?;
144 let manager_output_dir = self.options.output_directory.join(manager_name);
145
146 std::fs::create_dir_all(&manager_output_dir)?;
147
148 let package_path = manager.generate_package(metadata, &manager_output_dir)?;
149
150 if !manager.validate_package(&package_path)? {
152 return Err(VoirsCLIError::PackagingError(format!(
153 "Package validation failed for {}",
154 manager_name
155 ))
156 .into());
157 }
158
159 Ok(package_path)
160 }
161
162 pub fn get_package_info(&self) -> PackageInfo {
163 PackageInfo {
164 name: self.options.package_metadata.name.clone(),
165 version: self.options.package_metadata.version.clone(),
166 supported_managers: managers::PackageManagerFactory::get_supported_managers()
167 .iter()
168 .map(|&s| s.to_string())
169 .collect(),
170 binary_targets: binary::get_supported_targets()
171 .iter()
172 .map(|&s| s.to_string())
173 .collect(),
174 }
175 }
176}
177
178#[derive(Debug, Clone)]
179pub struct PackageInfo {
180 pub name: String,
181 pub version: String,
182 pub supported_managers: Vec<String>,
183 pub binary_targets: Vec<String>,
184}
185
186pub fn validate_packaging_environment() -> Result<Vec<String>> {
187 info!("Validating packaging environment");
188
189 let mut issues = Vec::new();
190
191 let required_tools = vec![
193 ("cargo", "Rust package manager"),
194 ("git", "Version control system"),
195 ];
196
197 for (tool, description) in required_tools {
198 if !is_tool_available(tool) {
199 issues.push(format!("Missing required tool: {} ({})", tool, description));
200 }
201 }
202
203 let optional_tools = vec![
205 ("strip", "Binary stripping tool"),
206 ("upx", "Binary compression tool"),
207 ("cross", "Cross-compilation tool"),
208 ];
209
210 for (tool, description) in optional_tools {
211 if !is_tool_available(tool) {
212 info!("Optional tool not available: {} ({})", tool, description);
213 }
214 }
215
216 if issues.is_empty() {
217 info!("Packaging environment validation passed");
218 } else {
219 error!(
220 "Packaging environment validation failed with {} issues",
221 issues.len()
222 );
223 }
224
225 Ok(issues)
226}
227
228fn is_tool_available(tool: &str) -> bool {
229 std::process::Command::new(tool)
230 .arg("--version")
231 .output()
232 .map(|output| output.status.success())
233 .unwrap_or(false)
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239 use tempfile::TempDir;
240
241 #[test]
242 fn test_packaging_options_default() {
243 let options = PackagingOptions::default();
244 assert_eq!(options.package_metadata.name, "voirs");
245 assert!(!options.managers.is_empty());
246 assert_eq!(options.output_directory, PathBuf::from("packages"));
247 }
248
249 #[test]
250 fn test_packaging_pipeline_creation() {
251 let options = PackagingOptions::default();
252 let pipeline = PackagingPipeline::new(options);
253 assert_eq!(pipeline.options.package_metadata.name, "voirs");
254 }
255
256 #[test]
257 fn test_package_info() {
258 let options = PackagingOptions::default();
259 let pipeline = PackagingPipeline::new(options);
260 let info = pipeline.get_package_info();
261
262 assert_eq!(info.name, "voirs");
263 assert!(!info.supported_managers.is_empty());
264 assert!(!info.binary_targets.is_empty());
265 }
266
267 #[test]
268 fn test_validate_packaging_environment() {
269 let issues = validate_packaging_environment().unwrap();
270 assert!(issues.is_empty() || !issues.is_empty());
273 }
274
275 #[test]
276 fn test_is_tool_available() {
277 let _result = is_tool_available("echo");
279 }
282}