1use std::process::Command;
2use std::io::{self, Write};
3use std::fs;
4use std::path::{Path, PathBuf};
5use rustbasic_core::colored::*;
6
7fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
9 fs::create_dir_all(&dst)?;
10 for entry in fs::read_dir(src)? {
11 let entry = entry?;
12 let ty = entry.file_type()?;
13 if ty.is_dir() {
14 copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?;
15 } else {
16 fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
17 }
18 }
19 Ok(())
20}
21
22fn update_env_app_debug(is_release: bool) {
24 let env_path = Path::new(".env");
25 if !env_path.exists() {
26 return;
27 }
28 if let Ok(content) = fs::read_to_string(env_path) {
29 let target_value = if is_release { "false" } else { "true" };
30 let mut lines: Vec<String> = content.lines().map(|s| s.to_string()).collect();
31 let mut found = false;
32 for line in &mut lines {
33 if line.trim_start().starts_with("APP_DEBUG=") {
34 *line = format!("APP_DEBUG={}", target_value);
35 found = true;
36 break;
37 }
38 }
39 if !found {
40 lines.push(format!("APP_DEBUG={}", target_value));
41 }
42 let new_content = lines.join("\n") + "\n";
43 if let Err(e) = fs::write(env_path, new_content) {
44 println!("{} {}", "โ ๏ธ Gagal memperbarui file .env:".yellow(), e);
45 } else {
46 println!("{} {}{}", "๐".green(), "APP_DEBUG diatur ke ".dimmed(), target_value.cyan());
47 }
48 }
49}
50
51pub fn build_project() {
52 println!("\n{}", "๐ RustBasic Build Manager".magenta().bold());
53 println!("{}", "--------------------------".magenta());
54
55 println!("{}", "--- Pilih Target OS ---".cyan().bold());
57 println!("1) Native (Sesuai OS Anda)");
58 println!("2) Windows x86_64 (x86_64-pc-windows-msvc)");
59 println!("3) Linux x86_64 GNU (x86_64-unknown-linux-gnu)");
60 println!("4) Linux x86_64 MUSL (x86_64-unknown-linux-musl)");
61 println!("5) Linux ARM64 GNU (aarch64-unknown-linux-gnu)");
62 println!("6) Linux ARM64 MUSL (aarch64-unknown-linux-musl)");
63 println!("7) macOS ARM64 (aarch64-apple-darwin)");
64 println!("8) macOS Intel (x86_64-apple-darwin)");
65 println!("9) Batal");
66 print!("\n{}", "Masukkan pilihan target (1-9): ".bold());
67 io::stdout().flush().unwrap();
68
69 let mut target_choice = String::new();
70 io::stdin().read_line(&mut target_choice).ok();
71 let target_choice = target_choice.trim();
72
73 if target_choice == "9" {
74 println!("{}", "๐ Build dibatalkan.".yellow());
75 return;
76 }
77
78 let target = match target_choice {
79 "2" => Some("x86_64-pc-windows-msvc"),
80 "3" => Some("x86_64-unknown-linux-gnu"),
81 "4" => Some("x86_64-unknown-linux-musl"),
82 "5" => Some("aarch64-unknown-linux-gnu"),
83 "6" => Some("aarch64-unknown-linux-musl"),
84 "7" => Some("aarch64-apple-darwin"),
85 "8" => Some("x86_64-apple-darwin"),
86 _ => None, };
88
89 println!("\n{}", "--- Pilih Mode Build ---".cyan().bold());
91 println!("1) Development");
92 println!("2) Production (Release)");
93 print!("\n{}", "Masukkan pilihan mode (1-2): ".bold());
94 io::stdout().flush().unwrap();
95
96 let mut mode_choice = String::new();
97 io::stdin().read_line(&mut mode_choice).ok();
98 let is_release = mode_choice.trim() == "2";
99
100 println!("\n{}", "๐ง Menyiapkan konfigurasi .env...".blue());
102 update_env_app_debug(is_release);
103
104 if Path::new("package.json").exists() {
106 println!("\n{}", "๐ฆ Memulai kompilasi aset frontend (npm run build)...".blue());
107 let npm_cmd = if cfg!(target_os = "windows") { "npm.cmd" } else { "npm" };
108 let status = Command::new(npm_cmd)
109 .args(["run", "build"])
110 .status();
111
112 match status {
113 Ok(s) if s.success() => {
114 println!("{}", "โ
Kompilasi frontend berhasil!".green().bold());
115 }
116 Ok(s) => {
117 println!("{} {}", "โ Error: npm run build keluar dengan kode:".red().bold(), s);
118 println!("{}", "โ ๏ธ Proses build dihentikan karena kompilasi frontend gagal.".yellow());
119 return;
120 }
121 Err(e) => {
122 println!("{} {}", "โ Error: Gagal mengeksekusi 'npm'. Pastikan npm terinstal.".red().bold(), e);
123 return;
124 }
125 }
126 }
127
128 println!("\n{}", "๐ ๏ธ Menyiapkan build Rust...".blue());
130
131 let has_zigbuild = Command::new("cargo")
132 .arg("zigbuild")
133 .arg("--version")
134 .output()
135 .is_ok();
136
137 let mut use_zigbuild = false;
138 if has_zigbuild && target.is_some() {
139 println!("\n{}", "--- Pilih Compiler untuk Kompilasi Silang ---".cyan().bold());
140 println!("1) Cargo Build Standard (Membutuhkan target toolchain terpasang)");
141 println!("2) Cargo Zigbuild (Lebih mudah untuk kompilasi silang)");
142 print!("\n{}", "Masukkan pilihan compiler (1-2, default 2): ".bold());
143 io::stdout().flush().unwrap();
144
145 let mut compiler_choice = String::new();
146 io::stdin().read_line(&mut compiler_choice).ok();
147 let choice = compiler_choice.trim();
148 if choice == "1" {
149 use_zigbuild = false;
150 } else {
151 use_zigbuild = true;
152 }
153 }
154
155 let mut cmd = if use_zigbuild {
156 println!("{}", "โจ Menggunakan cargo-zigbuild untuk kompilasi silang...".green().italic());
157 let mut c = Command::new("cargo");
158 c.arg("zigbuild");
159 c
160 } else {
161 if let Some(t) = target {
162 println!("{} {} {}", "๐ฆ Menambahkan target".blue(), t.yellow(), "via rustup...".blue());
163 Command::new("rustup")
164 .args(["target", "add", t])
165 .status()
166 .ok();
167 }
168 let mut c = Command::new("cargo");
169 c.arg("build");
170 c
171 };
172
173 if is_release {
174 cmd.arg("--release");
175 }
176
177 if let Some(t) = target {
178 cmd.arg("--target").arg(t);
179 }
180
181 println!("{} {:?}", "๐ Menjalankan:".blue().bold(), cmd);
182 let status = crate::utils::run_cargo_with_progress(cmd).expect("Gagal menjalankan perintah build");
183
184 if status.success() {
185 println!("\n{}", "โ
Build Rust berhasil!".green().bold());
186
187 println!("\n{}", "๐ Menyiapkan folder deploy...".cyan().bold());
189
190 let deploy_dir = Path::new("deploy");
191 if deploy_dir.exists() {
192 println!("{}", "๐งน Membersihkan folder deploy lama...".dimmed());
193 let _ = crate::utils::remove_dir_all_recursive(deploy_dir);
194 }
195
196 if let Err(e) = fs::create_dir_all(deploy_dir) {
197 println!("{} {}", "โ Gagal membuat folder deploy:".red().bold(), e);
198 return;
199 }
200
201 if Path::new("database").exists() {
203 print!(" {} Menyalin folder database... ", "๐ฆ".blue());
204 io::stdout().flush().unwrap();
205 if copy_dir_all("database", "deploy/database").is_ok() {
206 println!("{}", "selesai".green());
207 } else {
208 println!("{}", "gagal".red());
209 }
210 }
211
212 let mut dist_copied = false;
214 if Path::new("src/dist").exists() {
215 print!(" {} Menyalin folder src/dist... ", "๐ฆ".blue());
216 io::stdout().flush().unwrap();
217 if copy_dir_all("src/dist", "deploy/dist").is_ok() {
218 println!("{}", "selesai".green());
219 dist_copied = true;
220 } else {
221 println!("{}", "gagal".red());
222 }
223 }
224
225 if !dist_copied && Path::new("dist").exists() {
226 print!(" {} Menyalin folder dist... ", "๐ฆ".blue());
227 io::stdout().flush().unwrap();
228 if copy_dir_all("dist", "deploy/dist").is_ok() {
229 println!("{}", "selesai".green());
230 } else {
231 println!("{}", "gagal".red());
232 }
233 }
234
235 if Path::new("storage").exists() {
237 print!(" {} Menyalin folder storage... ", "๐ฆ".blue());
238 io::stdout().flush().unwrap();
239 if copy_dir_all("storage", "deploy/storage").is_ok() {
240 println!("{}", "selesai".green());
241 } else {
242 println!("{}", "gagal".red());
243 }
244 }
245
246 if Path::new(".env").exists() {
248 print!(" {} Menyalin file .env... ", "๐".blue());
249 io::stdout().flush().unwrap();
250 if fs::copy(".env", "deploy/.env").is_ok() {
251 println!("{}", "selesai".green());
252 } else {
253 println!("{}", "gagal".red());
254 }
255 }
256
257 let source_binary_filename = if let Some(t) = target {
259 if t.contains("windows") {
260 "rustbasic.exe"
261 } else {
262 "rustbasic"
263 }
264 } else if cfg!(target_os = "windows") {
265 "rustbasic.exe"
266 } else {
267 "rustbasic"
268 };
269
270 let build_name = std::env::var("BUILD_NAME")
272 .unwrap_or_else(|_| "rustbasic".to_string());
273
274 let dest_binary_filename = if let Some(t) = target {
275 if t.contains("windows") {
276 format!("{}.exe", build_name)
277 } else {
278 build_name.clone()
279 }
280 } else if cfg!(target_os = "windows") {
281 format!("{}.exe", build_name)
282 } else {
283 build_name.clone()
284 };
285
286 let mode_dir = if is_release { "release" } else { "debug" };
287 let binary_path = if let Some(t) = target {
288 PathBuf::from("target")
289 .join(t)
290 .join(mode_dir)
291 .join(source_binary_filename)
292 } else {
293 PathBuf::from("target")
294 .join(mode_dir)
295 .join(source_binary_filename)
296 };
297
298 if binary_path.exists() {
299 let dest_binary_path = deploy_dir.join(&dest_binary_filename);
300 print!(" {} Menyalin binary ke deploy/{}... ", "๐".blue(), dest_binary_filename.cyan());
301 io::stdout().flush().unwrap();
302 if fs::copy(&binary_path, &dest_binary_path).is_ok() {
303 println!("{}", "selesai".green());
304 println!("\n๐ {}", "Proses deployment berhasil disiapkan di folder 'deploy'!".green().bold());
305 } else {
306 println!("{}", "gagal".red());
307 }
308 } else {
309 println!("\n{}", format!("โ File binary tidak ditemukan di: {}", binary_path.display()).red().bold());
310 }
311 } else {
312 println!("\n{}", "โ Build Rust gagal.".red().bold());
313 println!("{}", "๐ก Penyebab: Linker untuk target tersebut tidak ditemukan di sistem Anda.".yellow());
314
315 if target_choice == "2" {
316 println!("\n{}", "๐ง Cara memperbaiki untuk Windows:".cyan());
317 println!(" Jalankan: {}", "brew install mingw-w64".white().on_black());
318 } else if target_choice == "3" {
319 println!("\n{}", "๐ง Cara memperbaiki untuk Linux:".cyan());
320 println!(" Jalankan: {}", "brew install messense/macos-cross-toolchains/x86_64-unknown-linux-gnu".white().on_black());
321 }
322
323 println!("\n{}", "Atau gunakan 'cargo-zigbuild' untuk kompilasi silang yang lebih mudah:".cyan());
324 println!("1. brew install zig");
325 println!("2. cargo install cargo-zigbuild");
326 println!("3. Gunakan '{}'", "cargo zigbuild --target <target>".white().on_black());
327 }
328}