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 {
24 project_name: String,
25 project_version: String,
26 project_root: PathBuf,
27}
28
29impl PackageService {
30 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 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 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 fn collect_binaries(&self, config: &PackageConfig) -> Result<Vec<BinaryInfo>> {
114 collector::collect_binaries(&self.project_root, config)
115 }
116
117 fn collect_configs(&self) -> Result<Vec<ConfigInfo>> {
119 collector::collect_configs(&self.project_root)
120 }
121
122 fn collect_assets(&self) -> Result<Vec<AssetInfo>> {
124 collector::collect_assets(&self.project_root)
125 }
126
127 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 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 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 fn collect_dependencies(&self) -> Result<HashMap<String, String>> {
205 utils::collect_dependencies(&self.project_root)
206 }
207
208 fn get_git_commit(&self) -> Option<String> {
210 utils::get_git_commit(&self.project_root)
211 }
212}