Skip to main content

mecha10_deploy/services/package/
mod.rs

1//! Package service for creating deployment packages
2//!
3//! This service provides operations for building deployment packages containing
4//! binaries, configurations, and assets for distribution to target systems.
5
6#![allow(dead_code)]
7
8mod collector;
9mod packager;
10mod types;
11mod utils;
12
13pub use types::{AssetInfo, BinaryInfo, ConfigInfo, PackageConfig, PackageManifest};
14
15use anyhow::{Context, Result};
16use chrono::Utc;
17use indicatif::{ProgressBar, ProgressStyle};
18use std::collections::HashMap;
19use std::path::PathBuf;
20use std::process::Command;
21use std::time::Duration;
22
23pub struct PackageService {
24    project_name: String,
25    project_version: String,
26    project_root: PathBuf,
27}
28
29impl PackageService {
30    /// Create a new package service
31    ///
32    /// # Arguments
33    ///
34    /// * `project_name` - Name of the project
35    /// * `project_version` - Version string
36    /// * `project_root` - Root directory of the project
37    pub fn new(project_name: String, project_version: String, project_root: impl Into<PathBuf>) -> Result<Self> {
38        let project_root = project_root.into();
39        if !project_root.exists() {
40            return Err(anyhow::anyhow!(
41                "Project root does not exist: {}",
42                project_root.display()
43            ));
44        }
45
46        Ok(Self {
47            project_name,
48            project_version,
49            project_root,
50        })
51    }
52
53    /// Build a deployment package
54    ///
55    /// # Arguments
56    ///
57    /// * `config` - Package configuration
58    pub fn build(&self, config: PackageConfig) -> Result<PathBuf> {
59        self.build_binaries(&config)?;
60
61        let binaries = self.collect_binaries(&config)?;
62        let configs = self.collect_configs()?;
63        let assets = if config.include_assets {
64            self.collect_assets()?
65        } else {
66            Vec::new()
67        };
68
69        let manifest = self.create_manifest(&config, &binaries, &configs, &assets)?;
70
71        self.validate_package(&manifest)?;
72
73        let package_path = self.create_package(&config, &manifest, &binaries, &assets)?;
74
75        Ok(package_path)
76    }
77
78    /// Build binaries for target architecture
79    fn build_binaries(&self, config: &PackageConfig) -> Result<()> {
80        let spinner = ProgressBar::new_spinner();
81        spinner.set_style(
82            ProgressStyle::default_spinner()
83                .template("   {spinner:.green} {msg}")
84                .unwrap(),
85        );
86        spinner.set_message(format!("Building for {}...", config.target_arch.as_str()));
87        spinner.enable_steady_tick(Duration::from_millis(100));
88
89        let mut cmd = Command::new("cargo");
90        cmd.arg("build").arg("--workspace").current_dir(&self.project_root);
91
92        if config.build_profile == "release" {
93            cmd.arg("--release");
94        }
95
96        cmd.arg("--target").arg(config.target_arch.as_str());
97
98        let status = cmd.status().context("Failed to run cargo build")?;
99
100        spinner.finish_and_clear();
101
102        if !status.success() {
103            return Err(anyhow::anyhow!(
104                "Build failed for target {}",
105                config.target_arch.as_str()
106            ));
107        }
108
109        Ok(())
110    }
111
112    /// Collect built binaries
113    fn collect_binaries(&self, config: &PackageConfig) -> Result<Vec<BinaryInfo>> {
114        collector::collect_binaries(&self.project_root, config)
115    }
116
117    /// Collect configuration files
118    fn collect_configs(&self) -> Result<Vec<ConfigInfo>> {
119        collector::collect_configs(&self.project_root)
120    }
121
122    /// Collect asset files
123    fn collect_assets(&self) -> Result<Vec<AssetInfo>> {
124        collector::collect_assets(&self.project_root)
125    }
126
127    /// Create package manifest
128    fn create_manifest(
129        &self,
130        config: &PackageConfig,
131        binaries: &[BinaryInfo],
132        configs: &[ConfigInfo],
133        assets: &[AssetInfo],
134    ) -> Result<PackageManifest> {
135        Ok(PackageManifest {
136            format_version: PackageManifest::FORMAT_VERSION.to_string(),
137            name: self.project_name.clone(),
138            version: self.project_version.clone(),
139            build_timestamp: Utc::now(),
140            target_arch: config.target_arch,
141            binaries: binaries.to_vec(),
142            configs: configs.to_vec(),
143            assets: assets.to_vec(),
144            dependencies: self.collect_dependencies()?,
145            metadata: config.custom_metadata.clone(),
146            build_profile: config.build_profile.clone(),
147            git_commit: self.get_git_commit(),
148            environment: config.environment.clone(),
149        })
150    }
151
152    /// Validate package contents
153    fn validate_package(&self, manifest: &PackageManifest) -> Result<()> {
154        if manifest.format_version != PackageManifest::FORMAT_VERSION {
155            return Err(anyhow::anyhow!(
156                "Invalid format version: {} (expected {})",
157                manifest.format_version,
158                PackageManifest::FORMAT_VERSION
159            ));
160        }
161
162        if manifest.binaries.is_empty() {
163            return Err(anyhow::anyhow!("Package must contain at least one binary"));
164        }
165
166        for binary in &manifest.binaries {
167            if binary.name.is_empty() {
168                return Err(anyhow::anyhow!("Binary name cannot be empty"));
169            }
170            if binary.size_bytes == 0 {
171                return Err(anyhow::anyhow!("Binary {} has zero size", binary.name));
172            }
173        }
174
175        for config in &manifest.configs {
176            if config.name.is_empty() {
177                return Err(anyhow::anyhow!("Config name cannot be empty"));
178            }
179        }
180
181        Ok(())
182    }
183
184    /// Create package archive
185    fn create_package(
186        &self,
187        config: &PackageConfig,
188        manifest: &PackageManifest,
189        binaries: &[BinaryInfo],
190        assets: &[AssetInfo],
191    ) -> Result<PathBuf> {
192        packager::create_package(
193            &self.project_root,
194            &self.project_name,
195            &self.project_version,
196            config,
197            manifest,
198            binaries,
199            assets,
200        )
201    }
202
203    /// Collect project dependencies from Cargo.lock
204    fn collect_dependencies(&self) -> Result<HashMap<String, String>> {
205        utils::collect_dependencies(&self.project_root)
206    }
207
208    /// Get git commit hash
209    fn get_git_commit(&self) -> Option<String> {
210        utils::get_git_commit(&self.project_root)
211    }
212}