Skip to main content

rustbasic_cli/
builder.rs

1use std::process::Command;
2use std::io::{self, Write};
3use std::fs;
4use std::path::{Path, PathBuf};
5use colored::*;
6
7/// Fungsi rekursif untuk menyalin seluruh isi folder
8fn 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
22/// Fungsi untuk memperbarui variabel APP_DEBUG di file .env
23fn 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    // 1. Pilih Target
56    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, // Native
87    };
88
89    // 2. Pilih Mode
90    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    // 3. Update File .env (APP_DEBUG)
101    println!("\n{}", "๐Ÿ”ง Menyiapkan konfigurasi .env...".blue());
102    update_env_app_debug(is_release);
103
104    // 4. Jalankan npm run build untuk Frontend
105    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    // 5. Eksekusi Build Rust
129    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 = cmd.status().expect("Gagal menjalankan perintah build");
183
184    if status.success() {
185        println!("\n{}", "โœ… Build Rust berhasil!".green().bold());
186        
187        // 6. Siapkan folder deploy dan salin aset
188        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        // Salin folder database jika ada
202        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        // Salin folder dist jika ada
213        if Path::new("dist").exists() {
214            print!("   {} Menyalin folder dist... ", "๐Ÿ“ฆ".blue());
215            io::stdout().flush().unwrap();
216            if copy_dir_all("dist", "deploy/dist").is_ok() {
217                println!("{}", "selesai".green());
218            } else {
219                println!("{}", "gagal".red());
220            }
221        }
222
223        // Salin folder storage jika ada
224        if Path::new("storage").exists() {
225            print!("   {} Menyalin folder storage... ", "๐Ÿ“ฆ".blue());
226            io::stdout().flush().unwrap();
227            if copy_dir_all("storage", "deploy/storage").is_ok() {
228                println!("{}", "selesai".green());
229            } else {
230                println!("{}", "gagal".red());
231            }
232        }
233
234        // Salin file .env jika ada
235        if Path::new(".env").exists() {
236            print!("   {} Menyalin file .env... ", "๐Ÿ“„".blue());
237            io::stdout().flush().unwrap();
238            if fs::copy(".env", "deploy/.env").is_ok() {
239                println!("{}", "selesai".green());
240            } else {
241            println!("{}", "gagal".red());
242            }
243        }
244
245        // Salin file binary hasil kompilasi
246        let source_binary_filename = if let Some(t) = target {
247            if t.contains("windows") {
248                "rustbasic.exe"
249            } else {
250                "rustbasic"
251            }
252        } else if cfg!(target_os = "windows") {
253            "rustbasic.exe"
254        } else {
255            "rustbasic"
256        };
257
258        // Membaca nama kustom untuk hasil build dari .env
259        let build_name = std::env::var("BUILD_NAME")
260            .unwrap_or_else(|_| "rustbasic".to_string());
261
262        let dest_binary_filename = if let Some(t) = target {
263            if t.contains("windows") {
264                format!("{}.exe", build_name)
265            } else {
266                build_name.clone()
267            }
268        } else if cfg!(target_os = "windows") {
269            format!("{}.exe", build_name)
270        } else {
271            build_name.clone()
272        };
273
274        let mode_dir = if is_release { "release" } else { "debug" };
275        let binary_path = if let Some(t) = target {
276            PathBuf::from("target")
277                .join(t)
278                .join(mode_dir)
279                .join(source_binary_filename)
280        } else {
281            PathBuf::from("target")
282                .join(mode_dir)
283                .join(source_binary_filename)
284        };
285
286        if binary_path.exists() {
287            let dest_binary_path = deploy_dir.join(&dest_binary_filename);
288            print!("   {} Menyalin binary ke deploy/{}... ", "๐Ÿš€".blue(), dest_binary_filename.cyan());
289            io::stdout().flush().unwrap();
290            if fs::copy(&binary_path, &dest_binary_path).is_ok() {
291                println!("{}", "selesai".green());
292                println!("\n๐ŸŽ‰ {}", "Proses deployment berhasil disiapkan di folder 'deploy'!".green().bold());
293            } else {
294                println!("{}", "gagal".red());
295            }
296        } else {
297            println!("\n{}", format!("โŒ File binary tidak ditemukan di: {}", binary_path.display()).red().bold());
298        }
299    } else {
300        println!("\n{}", "โŒ Build Rust gagal.".red().bold());
301        println!("{}", "๐Ÿ’ก Penyebab: Linker untuk target tersebut tidak ditemukan di sistem Anda.".yellow());
302        
303        if target_choice == "2" {
304            println!("\n{}", "๐Ÿ”ง Cara memperbaiki untuk Windows:".cyan());
305            println!("   Jalankan: {}", "brew install mingw-w64".white().on_black());
306        } else if target_choice == "3" {
307            println!("\n{}", "๐Ÿ”ง Cara memperbaiki untuk Linux:".cyan());
308            println!("   Jalankan: {}", "brew install messense/macos-cross-toolchains/x86_64-unknown-linux-gnu".white().on_black());
309        }
310        
311        println!("\n{}", "Atau gunakan 'cargo-zigbuild' untuk kompilasi silang yang lebih mudah:".cyan());
312        println!("1. brew install zig");
313        println!("2. cargo install cargo-zigbuild");
314        println!("3. Gunakan '{}'", "cargo zigbuild --target <target>".white().on_black());
315    }
316}