mecha10_deploy/services/package/
mod.rs1#![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 {
50 project_name: String,
51 project_version: String,
52 project_root: PathBuf,
53}
54
55impl PackageService {
56 pub fn new(project_name: String, project_version: String, project_root: impl Into<PathBuf>) -> Result<Self> {
64 let project_root = project_root.into();
65 if !project_root.exists() {
66 return Err(anyhow::anyhow!(
67 "Project root does not exist: {}",
68 project_root.display()
69 ));
70 }
71
72 Ok(Self {
73 project_name,
74 project_version,
75 project_root,
76 })
77 }
78
79 pub fn build(&self, config: PackageConfig) -> Result<PathBuf> {
85 self.build_binaries(&config)?;
86
87 let binaries = self.collect_binaries(&config)?;
88 let configs = self.collect_configs()?;
89 let assets = if config.include_assets {
90 self.collect_assets()?
91 } else {
92 Vec::new()
93 };
94
95 let manifest = self.create_manifest(&config, &binaries, &configs, &assets)?;
96
97 self.validate_package(&manifest)?;
98
99 let package_path = self.create_package(&config, &manifest, &binaries, &assets)?;
100
101 Ok(package_path)
102 }
103
104 fn build_binaries(&self, config: &PackageConfig) -> Result<()> {
106 let spinner = ProgressBar::new_spinner();
107 spinner.set_style(
108 ProgressStyle::default_spinner()
109 .template(" {spinner:.green} {msg}")
110 .unwrap(),
111 );
112 spinner.set_message(format!("Building for {}...", config.target_arch.as_str()));
113 spinner.enable_steady_tick(Duration::from_millis(100));
114
115 let mut cmd = Command::new("cargo");
116 cmd.arg("build").arg("--workspace").current_dir(&self.project_root);
117
118 if config.build_profile == "release" {
119 cmd.arg("--release");
120 }
121
122 cmd.arg("--target").arg(config.target_arch.as_str());
123
124 let status = cmd.status().context("Failed to run cargo build")?;
125
126 spinner.finish_and_clear();
127
128 if !status.success() {
129 return Err(anyhow::anyhow!(
130 "Build failed for target {}",
131 config.target_arch.as_str()
132 ));
133 }
134
135 Ok(())
136 }
137
138 fn collect_binaries(&self, config: &PackageConfig) -> Result<Vec<BinaryInfo>> {
140 collector::collect_binaries(&self.project_root, config)
141 }
142
143 fn collect_configs(&self) -> Result<Vec<ConfigInfo>> {
145 collector::collect_configs(&self.project_root)
146 }
147
148 fn collect_assets(&self) -> Result<Vec<AssetInfo>> {
150 collector::collect_assets(&self.project_root)
151 }
152
153 fn create_manifest(
155 &self,
156 config: &PackageConfig,
157 binaries: &[BinaryInfo],
158 configs: &[ConfigInfo],
159 assets: &[AssetInfo],
160 ) -> Result<PackageManifest> {
161 Ok(PackageManifest {
162 format_version: PackageManifest::FORMAT_VERSION.to_string(),
163 name: self.project_name.clone(),
164 version: self.project_version.clone(),
165 build_timestamp: Utc::now(),
166 target_arch: config.target_arch,
167 binaries: binaries.to_vec(),
168 configs: configs.to_vec(),
169 assets: assets.to_vec(),
170 dependencies: self.collect_dependencies()?,
171 metadata: config.custom_metadata.clone(),
172 build_profile: config.build_profile.clone(),
173 git_commit: self.get_git_commit(),
174 environment: config.environment.clone(),
175 })
176 }
177
178 fn validate_package(&self, manifest: &PackageManifest) -> Result<()> {
180 if manifest.format_version != PackageManifest::FORMAT_VERSION {
181 return Err(anyhow::anyhow!(
182 "Invalid format version: {} (expected {})",
183 manifest.format_version,
184 PackageManifest::FORMAT_VERSION
185 ));
186 }
187
188 if manifest.binaries.is_empty() {
189 return Err(anyhow::anyhow!("Package must contain at least one binary"));
190 }
191
192 for binary in &manifest.binaries {
193 if binary.name.is_empty() {
194 return Err(anyhow::anyhow!("Binary name cannot be empty"));
195 }
196 if binary.size_bytes == 0 {
197 return Err(anyhow::anyhow!("Binary {} has zero size", binary.name));
198 }
199 }
200
201 for config in &manifest.configs {
202 if config.name.is_empty() {
203 return Err(anyhow::anyhow!("Config name cannot be empty"));
204 }
205 }
206
207 Ok(())
208 }
209
210 fn create_package(
212 &self,
213 config: &PackageConfig,
214 manifest: &PackageManifest,
215 binaries: &[BinaryInfo],
216 assets: &[AssetInfo],
217 ) -> Result<PathBuf> {
218 packager::create_package(
219 &self.project_root,
220 &self.project_name,
221 &self.project_version,
222 config,
223 manifest,
224 binaries,
225 assets,
226 )
227 }
228
229 fn collect_dependencies(&self) -> Result<HashMap<String, String>> {
231 utils::collect_dependencies(&self.project_root)
232 }
233
234 fn get_git_commit(&self) -> Option<String> {
236 utils::get_git_commit(&self.project_root)
237 }
238}