light_fw_cli_core/
build.rs1use std::path::{Path, PathBuf};
8
9use crate::error::{Error, Result};
10use crate::process::CommandRunner;
11
12#[derive(Debug, Clone, Default)]
14pub struct BuildOptions {
15 pub release: bool,
17 pub features: Vec<String>,
19 pub no_default_features: bool,
21 pub extra_cargo_args: Vec<String>,
23}
24
25impl BuildOptions {
26 pub fn default_release() -> Self {
28 Self {
29 release: true,
30 ..Self::default()
31 }
32 }
33}
34
35pub fn build_args(opts: &BuildOptions) -> Vec<String> {
37 let mut args = vec!["build".to_string()];
38 if opts.release {
39 args.push("--release".to_string());
40 }
41 if opts.no_default_features {
42 args.push("--no-default-features".to_string());
43 }
44 if !opts.features.is_empty() {
45 args.push("--features".to_string());
46 args.push(opts.features.join(","));
47 }
48 args.extend(opts.extra_cargo_args.iter().cloned());
49 args
50}
51
52pub fn run_build(
54 runner: &dyn CommandRunner,
55 cwd: &Path,
56 opts: &BuildOptions,
57) -> Result<crate::process::CommandOutput> {
58 let label = if opts.release {
59 "Сборка (cargo build --release)"
60 } else {
61 "Сборка (cargo build)"
62 };
63 let output = runner.run_with_progress("cargo", &build_args(opts), Some(cwd), label)?;
64 if !output.success() {
65 return Err(Error::CommandFailed {
66 program: "cargo build".to_string(),
67 code: output.status,
68 stderr: output.stderr.trim().to_string(),
69 });
70 }
71 Ok(output)
72}
73
74pub fn locate_binary(target_dir: &Path, bin_name: &str, release: bool) -> Result<PathBuf> {
79 let profile = if release { "release" } else { "debug" };
80 let base = target_dir.join(profile).join(bin_name);
81 let candidates = [
82 format!("{}.exe", base.display()),
83 base.to_string_lossy().into_owned(),
84 ];
85 for candidate in &candidates {
86 let p = PathBuf::from(candidate);
87 if p.exists() {
88 return Ok(p);
89 }
90 }
91 Ok(candidates[0].clone().into())
94}
95
96pub fn format_size(bytes: u64) -> String {
98 if bytes < 1024 {
99 format!("{bytes} B")
100 } else if bytes < 1024 * 1024 {
101 format!("{:.1} KiB", bytes as f64 / 1024.0)
102 } else if bytes < 1024 * 1024 * 1024 {
103 format!("{:.1} MiB", bytes as f64 / (1024.0 * 1024.0))
104 } else {
105 format!("{:.1} GiB", bytes as f64 / (1024.0 * 1024.0 * 1024.0))
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[test]
114 fn release_build_args() {
115 let opts = BuildOptions::default_release();
116 assert_eq!(build_args(&opts), vec!["build", "--release"]);
117 }
118
119 #[test]
120 fn debug_build_args() {
121 let opts = BuildOptions::default();
122 assert_eq!(build_args(&opts), vec!["build"]);
123 }
124
125 #[test]
126 fn build_args_with_features() {
127 let mut opts = BuildOptions::default_release();
128 opts.features = vec!["a".to_string(), "b".to_string()];
129 assert_eq!(
130 build_args(&opts),
131 vec!["build", "--release", "--features", "a,b"]
132 );
133 }
134
135 #[test]
136 fn build_args_no_default_features() {
137 let mut opts = BuildOptions::default_release();
138 opts.no_default_features = true;
139 assert_eq!(
140 build_args(&opts),
141 vec!["build", "--release", "--no-default-features"]
142 );
143 }
144
145 #[test]
146 fn build_args_with_extra() {
147 let mut opts = BuildOptions::default_release();
148 opts.extra_cargo_args = vec!["--jobs".to_string(), "4".to_string()];
149 assert_eq!(build_args(&opts), vec!["build", "--release", "--jobs", "4"]);
150 }
151
152 #[test]
153 fn format_size_bytes() {
154 assert_eq!(format_size(512), "512 B");
155 assert_eq!(format_size(1023), "1023 B");
156 }
157
158 #[test]
159 fn format_size_kib() {
160 assert_eq!(format_size(1024), "1.0 KiB");
161 assert_eq!(format_size(1536), "1.5 KiB");
162 }
163
164 #[test]
165 fn format_size_mib() {
166 assert_eq!(format_size(1024 * 1024), "1.0 MiB");
167 assert_eq!(format_size(2 * 1024 * 1024 + 512 * 1024), "2.5 MiB");
168 }
169
170 #[test]
171 fn format_size_gib() {
172 assert_eq!(format_size(1024 * 1024 * 1024), "1.0 GiB");
173 }
174
175 #[test]
176 fn locate_binary_existent() {
177 let dir = std::env::temp_dir().join(format!("light-build-test-{}", std::process::id()));
178 let release_dir = dir.join("release");
179 if let Err(e) = std::fs::create_dir_all(&release_dir) {
180 eprintln!("skip: cannot create temp dir: {e}");
181 return;
182 }
183 let bin = release_dir.join("myapp.exe");
184 std::fs::write(&bin, b"fake").unwrap();
185 let found = locate_binary(&dir, "myapp", true).unwrap();
186 assert_eq!(found, bin);
187 let _ = std::fs::remove_dir_all(&dir);
188 }
189
190 #[test]
191 fn locate_binary_nonexistent_returns_expected_path() {
192 let dir = std::env::temp_dir().join("light-build-missing");
193 let expected = dir.join("release").join("nothere.exe");
194 let found = locate_binary(&dir, "nothere", true).unwrap();
195 assert_eq!(found, expected);
196 }
197}