Skip to main content

rustbasic_cli/
builder.rs

1use std::process::Command;
2use std::io::Write;
3use std::fs;
4use std::path::Path;
5use rustbasic_core::colored::*;
6
7fn setup_java_home() {
8    if std::env::var("JAVA_HOME").is_err() {
9        let mut custom_java_home: Option<String> = None;
10        if cfg!(target_os = "macos") {
11            let paths = vec![
12                "/Applications/Android Studio.app/Contents/jbr/Contents/Home",
13                "/Applications/Android Studio.app/Contents/jre/Contents/Home",
14                "/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home",
15            ];
16            for path in &paths {
17                if std::path::Path::new(path).exists() {
18                    custom_java_home = Some(path.to_string());
19                    break;
20                }
21            }
22        } else if cfg!(target_os = "windows") {
23            let win_paths = [
24                "C:\\Program Files\\Android\\Android Studio\\jbr",
25                "C:\\Program Files\\Android\\Android Studio\\jre",
26            ];
27            for path in &win_paths {
28                if std::path::Path::new(path).exists() {
29                    custom_java_home = Some(path.to_string());
30                    break;
31                }
32            }
33        } else {
34            // Linux & other Unix-like OS
35            let unix_paths = [
36                "/opt/android-studio/jbr",
37                "/opt/android-studio/jre",
38                "/snap/android-studio/current/jbr",
39                "/snap/android-studio/current/jre",
40                "/usr/local/android-studio/jbr",
41                "/usr/local/android-studio/jre",
42                "/usr/lib/jvm/default-java",
43            ];
44            for path in &unix_paths {
45                if std::path::Path::new(path).exists() {
46                    custom_java_home = Some(path.to_string());
47                    break;
48                }
49            }
50        }
51        if let Some(jh) = custom_java_home {
52            unsafe {
53                std::env::set_var("JAVA_HOME", &jh);
54            }
55        }
56    }
57}
58
59fn get_cargo_package_name() -> String {
60    if let Ok(content) = fs::read_to_string("Cargo.toml") {
61        for line in content.lines() {
62            let line = line.trim();
63            if line.starts_with("name") {
64                let parts: Vec<&str> = line.split('=').collect();
65                if parts.len() >= 2 {
66                    return parts[1].trim().trim_matches('"').trim_matches('\'').to_string();
67                }
68            }
69        }
70    }
71    "rustbasic".to_string()
72}
73
74pub fn build_native_project(run_android: bool, run_desktop: bool) {
75    println!("\n{}", "šŸš€ RustBasic Native Build Manager".magenta().bold());
76    println!("{}", "---------------------------------".magenta());
77
78    // 1. Jalankan npm run build untuk Frontend
79    if Path::new("package.json").exists() {
80        println!("\n{}", "šŸ“¦ Memulai kompilasi aset frontend (npm run build)...".blue());
81        let npm_cmd = if cfg!(target_os = "windows") { "npm.cmd" } else { "npm" };
82        let status = Command::new(npm_cmd)
83            .args(["run", "build"])
84            .status();
85
86        match status {
87            Ok(s) if s.success() => {
88                println!("{}", "āœ… Kompilasi frontend berhasil!".green().bold());
89            }
90            Ok(s) => {
91                println!("{} {}", "āŒ Error: npm run build keluar dengan kode:".red().bold(), s);
92                println!("{}", "āš ļø  Proses build dihentikan karena kompilasi frontend gagal.".yellow());
93                return;
94            }
95            Err(e) => {
96                println!("{} {}", "āŒ Error: Gagal mengeksekusi 'npm'. Pastikan npm terinstal.".red().bold(), e);
97                return;
98            }
99        }
100    }
101
102    if run_desktop {
103        println!("\n{}", "šŸ› ļø  Menyiapkan build Desktop Wrapper...".blue());
104        if !Path::new("native/desktop/src/main.rs").exists() {
105            println!("{}", "āŒ Error: File native/desktop/src/main.rs tidak ditemukan. Jalankan 'rustbasic-native install' terlebih dahulu.".red().bold());
106            return;
107        }
108
109        let mut cmd = Command::new("cargo");
110        cmd.args(["build", "--bin", "rustbasic-desktop", "--release", "--features", "desktop"]);
111        println!("{} {:?}", "šŸš€ Menjalankan:".blue().bold(), cmd);
112        
113        let status = cmd.status();
114        match status {
115            Ok(s) if s.success() => {
116                let bin_name = if cfg!(target_os = "windows") {
117                    "rustbasic-desktop.exe"
118                } else {
119                    "rustbasic-desktop"
120                };
121                let bin_path = Path::new("target/release").join(bin_name);
122                println!("\nšŸŽ‰ {}", "Build Desktop Wrapper berhasil!".green().bold());
123                println!("šŸš€ Hasil executable berada di: {}", bin_path.display().to_string().cyan().bold());
124            }
125            _ => {
126                println!("\nāŒ {}", "Build Desktop Wrapper gagal.".red().bold());
127            }
128        }
129    }
130    if run_android {
131        println!("\n{}", "šŸ› ļø  Menyiapkan build Android Wrapper...".blue());
132        if !Path::new("native/android/build.gradle").exists() {
133            println!("{}", "āŒ Error: Folder native/android tidak ditemukan. Jalankan 'rustbasic-native install' terlebih dahulu.".red().bold());
134            return;
135        }
136
137        // Jalankan JNI compilation
138        println!(" JNI shared libraries...");
139        if !compile_jni_libraries() {
140            println!("{}", "āŒ Error: Gagal mengompilasi JNI libraries.".red().bold());
141            return;
142        }
143
144        setup_java_home();
145        let jh_val = std::env::var("JAVA_HOME").ok();
146
147        // 1. Generate Keystore if not exists
148        let keystore_path = Path::new("native/android/app/release.keystore");
149        if !keystore_path.exists() {
150            println!("šŸ”‘ Menghasilkan developer release keystore baru...");
151            let keytool_bin = if let Some(jh) = jh_val.as_ref() {
152                let jh_bin = Path::new(jh).join("bin/keytool");
153                if jh_bin.exists() {
154                    jh_bin.display().to_string()
155                } else {
156                    "keytool".to_string()
157                }
158            } else {
159                "keytool".to_string()
160            };
161
162            let mut keytool_cmd = Command::new(keytool_bin);
163            keytool_cmd.args([
164                "-genkeypair",
165                "-v",
166                "-keystore",
167                "native/android/app/release.keystore",
168                "-alias",
169                "rustbasic",
170                "-keyalg",
171                "RSA",
172                "-keysize",
173                "2048",
174                "-validity",
175                "10000",
176                "-storepass",
177                "rustbasic",
178                "-keypass",
179                "rustbasic",
180                "-dname",
181                "CN=RustBasic Developer, O=RustBasic, C=ID"
182            ]);
183            let _ = keytool_cmd.status();
184        }
185
186        // 2. Inject signingConfigs into build.gradle if not already present
187        let gradle_path = Path::new("native/android/app/build.gradle");
188        if gradle_path.exists()
189            && let Ok(content) = fs::read_to_string(gradle_path)
190                && !content.contains("signingConfigs") {
191                    println!("šŸ“ Menyematkan konfigurasi tanda tangan (signingConfigs) ke build.gradle...");
192                    let updated_content = content
193                        .replace(
194                            "buildTypes {",
195                            "signingConfigs {\n        release {\n            storeFile file(\"release.keystore\")\n            storePassword \"rustbasic\"\n            keyAlias \"rustbasic\"\n            keyPassword \"rustbasic\"\n        }\n    }\n\n    buildTypes {"
196                        )
197                        .replace(
198                            "buildTypes {\n        release {\n            minifyEnabled",
199                            "buildTypes {\n        release {\n            signingConfig signingConfigs.release\n            minifyEnabled"
200                        )
201                        .replace(
202                            "buildTypes {\r\n        release {\r\n            minifyEnabled",
203                            "buildTypes {\r\n        release {\r\n            signingConfig signingConfigs.release\r\n            minifyEnabled"
204                        );
205                    let _ = fs::write(gradle_path, updated_content);
206                }
207
208        println!("šŸ”Ø Memulai kompilasi APK & AAB menggunakan Gradle...");
209        let gradlew_bin = if cfg!(target_os = "windows") { "gradlew.bat" } else { "./gradlew" };
210        let mut gradle_cmd = Command::new(gradlew_bin);
211        gradle_cmd.args(["assembleRelease", "bundleRelease"]);
212        gradle_cmd.current_dir("native/android");
213
214        if let Some(jh) = jh_val.as_ref() {
215            gradle_cmd.env("JAVA_HOME", jh);
216        }
217
218        let status = gradle_cmd.status();
219        match status {
220            Ok(s) if s.success() => {
221                println!("\nšŸŽ‰ {}", "Build Android Wrapper berhasil!".green().bold());
222                println!("šŸ“¦ Hasil output:");
223                let apk_signed = "native/android/app/build/outputs/apk/release/app-release.apk";
224                let final_apk = if Path::new(apk_signed).exists() {
225                    apk_signed
226                } else {
227                    "native/android/app/build/outputs/apk/release/app-release-unsigned.apk"
228                };
229                println!("   - APK: {}", final_apk.cyan().bold());
230                println!("   - AAB: {}", "native/android/app/build/outputs/bundle/release/app-release.aab".cyan().bold());
231            }
232            _ => {
233                println!("\nāŒ {}", "Build Android Wrapper gagal.".red().bold());
234            }
235        }
236    }
237}
238
239/// Build Docker image — auto-generate Dockerfile jika belum ada
240pub fn build_docker(custom_tag: &str) {
241    // 1. Cek apakah docker tersedia
242    let docker_check = Command::new("docker")
243        .arg("version")
244        .stdout(std::process::Stdio::null())
245        .stderr(std::process::Stdio::null())
246        .status();
247
248    match docker_check {
249        Ok(status) if status.success() => {}
250        _ => {
251            println!("āŒ Docker tidak ditemukan! Pastikan Docker sudah terinstall dan berjalan.");
252            println!("   Install Docker: https://docs.docker.com/get-docker/");
253            return;
254        }
255    }
256
257    // 2. Generate Dockerfile jika belum ada
258    let dockerfile_path = Path::new("Dockerfile");
259    if !dockerfile_path.exists() {
260        println!("šŸ“ Membuat Dockerfile...");
261        let is_monorepo = Path::new("../rustbasic-core").exists() || Path::new("rustbasic-core").exists();
262        let binary_name = get_cargo_package_name();
263
264        let dockerfile_content = if is_monorepo {
265            r#"# ============================================================
266# RustBasic Docker Build — Multi-stage
267# ============================================================
268
269# Stage 1: Builder
270FROM rust:1-slim-bookworm AS builder
271
272RUN apt-get update && apt-get install -y \
273    pkg-config libssl-dev \
274    && rm -rf /var/lib/apt/lists/*
275
276WORKDIR /build
277
278# Copy rustbasic-core (dari konteks workspace root)
279COPY rustbasic-core /build/rustbasic-core
280
281# Copy proyek utama rustbasic
282COPY rustbasic /build/rustbasic
283
284WORKDIR /build/rustbasic
285
286# Build release binary
287RUN cargo build --release --bin rustbasic
288
289# Stage 2: Runtime
290FROM debian:bookworm-slim
291
292RUN apt-get update && apt-get install -y \
293    ca-certificates libssl3 \
294    && rm -rf /var/lib/apt/lists/*
295
296WORKDIR /app
297
298# Copy binary dari builder stage
299COPY --from=builder /build/rustbasic/target/release/rustbasic .
300
301# Copy assets yang diperlukan dari builder stage (lebih aman dan bersih)
302COPY --from=builder /build/rustbasic/src/resources/views/ src/resources/views/
303COPY --from=builder /build/rustbasic/src/dist/ src/dist/
304COPY --from=builder /build/rustbasic/public/ public/
305COPY --from=builder /build/rustbasic/database/migrations/ database/migrations/
306COPY --from=builder /build/rustbasic/database/seeders/ database/seeders/
307COPY --from=builder /build/rustbasic/.env.example .env
308
309# Expose port aplikasi
310EXPOSE 4000
311
312CMD ["./rustbasic"]
313"#.to_string()
314        } else {
315            format!(r#"# ============================================================
316# RustBasic Docker Build — Multi-stage
317# ============================================================
318
319# Stage 1: Builder
320FROM rust:1-slim-bookworm AS builder
321
322RUN apt-get update && apt-get install -y \
323    pkg-config libssl-dev \
324    && rm -rf /var/lib/apt/lists/*
325
326WORKDIR /build
327
328# Copy proyek utama
329COPY . .
330
331# Build release binary
332RUN cargo build --release --bin {bin_name}
333
334# Stage 2: Runtime
335FROM debian:bookworm-slim
336
337RUN apt-get update && apt-get install -y \
338    ca-certificates libssl3 \
339    && rm -rf /var/lib/apt/lists/*
340
341WORKDIR /app
342
343# Copy binary dari builder stage
344COPY --from=builder /build/target/release/{bin_name} .
345
346# Copy assets yang diperlukan dari builder stage
347COPY --from=builder /build/src/resources/views/ src/resources/views/
348COPY --from=builder /build/src/dist/ src/dist/
349COPY --from=builder /build/public/ public/
350COPY --from=builder /build/database/migrations/ database/migrations/
351COPY --from=builder /build/database/seeders/ database/seeders/
352COPY --from=builder /build/.env.example .env
353
354# Expose port aplikasi
355EXPOSE 4000
356
357CMD ["./{bin_name}"]
358"#, bin_name = binary_name)
359        };
360
361        if let Err(e) = fs::write(dockerfile_path, dockerfile_content) {
362            println!("āŒ Gagal membuat Dockerfile: {}", e);
363            return;
364        }
365        println!("āœ… Dockerfile berhasil dibuat.");
366    }
367
368    // 3. Tentukan image tag
369    let app_name = std::env::var("BUILD_NAME")
370        .or_else(|_| std::env::var("APP_NAME"))
371        .unwrap_or_else(|_| get_cargo_package_name())
372        .to_lowercase();
373    
374    let image_tag = if custom_tag.is_empty() {
375        format!("{}:latest", app_name)
376    } else {
377        custom_tag.to_string()
378    };
379
380    let core_context = if std::path::Path::new("../rustbasic-core").exists() {
381        "core=../rustbasic-core"
382    } else {
383        "core=."
384    };
385
386    // 4. Build Docker image dengan named context 'core' untuk rustbasic-core
387    println!("\n🐳 Memulai Docker build...");
388    println!("   Image tag: {}", image_tag);
389    println!("   Running: docker build --build-context {} -t {} .", core_context, image_tag);
390
391    let mut cmd = Command::new("docker")
392        .args(["build", "--build-context", core_context, "-t", &image_tag, "."])
393        .stdin(std::process::Stdio::inherit())
394        .stdout(std::process::Stdio::inherit())
395        .stderr(std::process::Stdio::inherit())
396        .spawn()
397        .expect("Gagal menjalankan docker build");
398
399    let status = cmd.wait().expect("Gagal menunggu docker build");
400
401    if status.success() {
402        println!("\nāœ… Docker build selesai dengan sukses!");
403        println!("šŸ“¦ Image: {}", image_tag);
404        println!("\n   Jalankan container (Development/Lokal):");
405        println!("   docker run -p 4000:4000 --env-file .env {}", image_tag);
406        println!("\n   Jalankan container (Produksi/Server - Auto Restart):");
407        println!("   docker run -d -p 80:4000 --restart unless-stopped --env-file .env {}", image_tag);
408    } else {
409        println!("\nāŒ Docker build gagal.");
410    }
411}
412
413/// Unified interactive build entry point — menampilkan menu untuk memilih target build
414pub fn build_interactive(args: &[String]) {
415    let mut build_docker_flag = args.iter().any(|arg| arg == "--docker");
416    let mut build_desktop = args.iter().any(|arg| arg == "--desktop");
417    let mut build_android = args.iter().any(|arg| arg == "--android");
418    let mut release_mode = args.iter().any(|arg| arg == "--release" || arg == "-r");
419    let mut target_type = String::new(); // apk / aab
420    let mut docker_tag = String::new();
421
422    // Parse arguments
423    for i in 0..args.len() {
424        if args[i] == "--type" && i + 1 < args.len() {
425            target_type = args[i+1].to_lowercase();
426        }
427        if args[i] == "--tag" && i + 1 < args.len() {
428            docker_tag = args[i+1].clone();
429        }
430    }
431
432    if !build_docker_flag && !build_desktop && !build_android {
433        let is_native_installed = crate::packages::read_manifest()
434            .packages
435            .iter()
436            .any(|pkg| pkg.name == "rustbasic-native");
437
438        println!("šŸ› ļø  RustBasic Build CLI");
439        println!("Pilih platform target untuk di-build:");
440        println!("  [1] Docker (Container Image)");
441        
442        let max_choice = if is_native_installed {
443            println!("  [2] Desktop Wrapper (Windows, macOS, Linux)");
444            println!("  [3] Android Wrapper (APK, AAB)");
445            3
446        } else {
447            1
448        };
449
450        let prompt_str = format!("šŸ‘‰ Pilih nomor platform (1-{}): ", max_choice);
451        let choice = crate::utils::prompt_choice(&prompt_str, 1, max_choice);
452        match choice {
453            1 => build_docker_flag = true,
454            2 => build_desktop = true,
455            3 => build_android = true,
456            _ => {}
457        }
458    }
459
460    if build_docker_flag {
461        build_docker(&docker_tag);
462    } else if build_desktop {
463        let mut target_os = String::new();
464        for i in 0..args.len() {
465            if args[i] == "--os" && i + 1 < args.len() {
466                target_os = args[i+1].clone();
467            }
468        }
469
470        let mut target_triple = "";
471        if target_os.is_empty() {
472            println!("\nPilih OS Target Desktop:");
473            println!("  [1] Current OS (Sistem saat ini)");
474            println!("  [2] macOS Intel (x86_64)");
475            println!("  [3] macOS Apple Silicon (aarch64)");
476            println!("  [4] Windows MSVC (x86_64-pc-windows-msvc)");
477            println!("  [5] Windows GNU (x86_64-pc-windows-gnu - Rekomendasi Cross-compile dari macOS/Linux)");
478            println!("  [6] Linux (x86_64-unknown-linux-gnu)");
479            let choice = crate::utils::prompt_choice("šŸ‘‰ Pilih nomor target OS (1-6): ", 1, 6);
480            match choice {
481                2 => target_triple = "x86_64-apple-darwin",
482                3 => target_triple = "aarch64-apple-darwin",
483                4 => target_triple = "x86_64-pc-windows-msvc",
484                5 => target_triple = "x86_64-pc-windows-gnu",
485                6 => target_triple = "x86_64-unknown-linux-gnu",
486                _ => {}
487            }
488        } else {
489            match target_os.as_str() {
490                "macos-intel" | "macos_intel" => target_triple = "x86_64-apple-darwin",
491                "macos-silicon" | "macos_silicon" => target_triple = "aarch64-apple-darwin",
492                "macos" => {
493                    #[cfg(target_arch = "aarch64")]
494                    { target_triple = "aarch64-apple-darwin"; }
495                    #[cfg(not(target_arch = "aarch64"))]
496                    { target_triple = "x86_64-apple-darwin"; }
497                }
498                "windows" => target_triple = "x86_64-pc-windows-msvc",
499                "windows-gnu" => target_triple = "x86_64-pc-windows-gnu",
500                "linux" => target_triple = "x86_64-unknown-linux-gnu",
501                _ => {
502                    println!("āš ļø Warning: Target OS '{}' tidak dikenal, menggunakan default OS saat ini.", target_os);
503                }
504            }
505        }
506
507        if !args.iter().any(|arg| arg == "--release" || arg == "-r" || arg == "--debug" || arg == "-d") {
508            println!("\nPilih Mode Build:");
509            println!("  [1] Debug (Cepat compile)");
510            println!("  [2] Release (Optimasi penuh)");
511            let choice = crate::utils::prompt_choice("šŸ‘‰ Pilih nomor mode (1-2): ", 1, 2);
512            if choice == 2 {
513                release_mode = true;
514            }
515        }
516
517        println!("\nšŸ–„ļø  Memulai proses build Desktop...");
518        let mut build_args = vec!["build", "--bin", "rustbasic-desktop", "--features", "desktop"];
519        if release_mode {
520            build_args.push("--release");
521        }
522        if !target_triple.is_empty() {
523            build_args.push("--target");
524            build_args.push(target_triple);
525            let _ = Command::new("rustup")
526                .args(["target", "add", target_triple])
527                .status();
528        }
529
530        println!("   Running: cargo {}", build_args.join(" "));
531        let mut cmd = Command::new("cargo")
532            .args(&build_args)
533            .stdin(std::process::Stdio::inherit())
534            .stdout(std::process::Stdio::inherit())
535            .stderr(std::process::Stdio::inherit())
536            .spawn()
537            .expect("Gagal menjalankan cargo build");
538        
539        let status = cmd.wait().expect("Gagal menunggu proses cargo build");
540        if status.success() {
541            println!("\nāœ… Build Desktop selesai dengan sukses!");
542            let mode_str = if release_mode { "release" } else { "debug" };
543            let path_str = if target_triple.is_empty() {
544                format!("target/{}/rustbasic-desktop", mode_str)
545            } else {
546                format!("target/{}/{}/rustbasic-desktop", target_triple, mode_str)
547            };
548            println!("šŸ“‚ Output biner: {}", path_str);
549        } else {
550            println!("\nāŒ Build Desktop gagal.");
551        }
552
553    } else if build_android {
554        let mut is_aab = false;
555        if target_type.is_empty() {
556            println!("\nPilih Format Output Android:");
557            println!("  [1] APK (Android Package - Siap install)");
558            println!("  [2] AAB (Android App Bundle - Siap Google Play)");
559            let choice = crate::utils::prompt_choice("šŸ‘‰ Pilih format (1-2): ", 1, 2);
560            if choice == 2 {
561                is_aab = true;
562            }
563        } else {
564            is_aab = target_type == "aab";
565        }
566
567        if !args.iter().any(|arg| arg == "--release" || arg == "-r" || arg == "--debug" || arg == "-d") {
568            println!("\nPilih Mode Build:");
569            println!("  [1] Debug");
570            println!("  [2] Release (Produksi)");
571            let choice = crate::utils::prompt_choice("šŸ‘‰ Pilih nomor mode (1-2): ", 1, 2);
572            if choice == 2 {
573                release_mode = true;
574            }
575        }
576
577        println!("\nšŸ”Ø Membangun JNI library untuk Android...");
578        if !compile_jni_libraries() {
579            println!("āŒ Gagal membangun JNI libraries.");
580            return;
581        }
582
583        // Setup Android home and SDK environments
584        let os = std::env::consts::OS;
585        let home = std::env::var("HOME").unwrap_or_default();
586        let android_home = if let Ok(val) = std::env::var("ANDROID_HOME") {
587            val
588        } else {
589            if os == "macos" {
590                format!("{}/Library/Android/sdk", home)
591            } else {
592                format!("{}/Android/Sdk", home)
593            }
594        };
595        unsafe {
596            std::env::set_var("ANDROID_HOME", &android_home);
597        }
598        
599        if std::env::var("JAVA_HOME").is_err() {
600            let studio_jbr = if os == "macos" {
601                let mut jbr = "/Applications/Android Studio.app/Contents/jbr/Contents/Home".to_string();
602                if !Path::new(&jbr).exists() {
603                    jbr = "/Applications/Android Studio.app/Contents/jre/Contents/Home".to_string();
604                }
605                jbr
606            } else {
607                let mut jbr = "/opt/android-studio/jbr".to_string();
608                if !Path::new(&jbr).exists() {
609                    jbr = "/usr/local/android-studio/jbr".to_string();
610                }
611                jbr
612            };
613            if Path::new(&studio_jbr).exists() {
614                unsafe {
615                    std::env::set_var("JAVA_HOME", &studio_jbr);
616                }
617            }
618        }
619
620        let local_props = Path::new("native/android/local.properties");
621        if !local_props.exists()
622            && let Ok(mut file) = fs::File::create(local_props) {
623                let _ = writeln!(file, "sdk.dir={}", android_home);
624            }
625
626        let gradle_task = match (is_aab, release_mode) {
627            (false, false) => "assembleDebug",
628            (false, true) => "assembleRelease",
629            (true, false) => "bundleDebug",
630            (true, true) => "bundleRelease",
631        };
632
633        println!("\nšŸ”Ø Membangun target Android via Gradle (task: {})...", gradle_task);
634        
635        let mut build_cmd = if Path::new("native/android/gradlew").exists() {
636            let mut cmd = Command::new("./gradlew");
637            cmd.arg(gradle_task);
638            cmd.current_dir("native/android");
639            cmd
640        } else {
641            let mut cmd = Command::new("gradle");
642            cmd.arg(gradle_task);
643            cmd.current_dir("native/android");
644            cmd
645        };
646
647        let spawn_res = build_cmd
648            .stdin(std::process::Stdio::inherit())
649            .stdout(std::process::Stdio::inherit())
650            .stderr(std::process::Stdio::inherit())
651            .spawn();
652
653        if let Ok(mut child) = spawn_res {
654            let status = child.wait().expect("Gagal menunggu Gradle build");
655            if status.success() {
656                println!("\nāœ… Build Android selesai dengan sukses!");
657                let output_dir = if is_aab {
658                    let mode_folder = if release_mode { "release" } else { "debug" };
659                    format!("native/android/app/build/outputs/bundle/{}", mode_folder)
660                } else {
661                    let mode_folder = if release_mode { "release" } else { "debug" };
662                    format!("native/android/app/build/outputs/apk/{}", mode_folder)
663                };
664                println!("šŸ“‚ Folder output: {}", output_dir);
665            } else {
666                println!("\nāŒ Gradle build gagal.");
667            }
668        } else {
669            println!("āŒ Gagal mengeksekusi Gradle wrapper. Pastikan Java dan Gradle wrapper terkonfigurasi dengan benar.");
670        }
671    }
672}
673
674/// Jalankan native runner script (Android / Desktop)
675pub fn run_native(run_android: bool, run_desktop: bool) {
676    if run_android {
677        println!("šŸš€ Memulai RustBasic Android Wrapper...");
678        
679        let os = std::env::consts::OS;
680        let home = std::env::var("HOME").unwrap_or_default();
681        let android_home = if let Ok(val) = std::env::var("ANDROID_HOME") {
682            val
683        } else {
684            if os == "macos" {
685                format!("{}/Library/Android/sdk", home)
686            } else {
687                format!("{}/Android/Sdk", home)
688            }
689        };
690
691        let mut custom_java_home = None;
692        // Coba deteksi Android Studio JDK di berbagai platform
693        if cfg!(target_os = "macos") {
694            let mac_studio_jdk = "/Applications/Android Studio.app/Contents/jbr/Contents/Home";
695            if Path::new(mac_studio_jdk).exists() {
696                custom_java_home = Some(mac_studio_jdk.to_string());
697            }
698        } else if cfg!(target_os = "windows") {
699            let win_paths = [
700                "C:\\Program Files\\Android\\Android Studio\\jbr",
701                "C:\\Program Files\\Android\\Android Studio\\jre",
702            ];
703            for path in &win_paths {
704                if Path::new(path).exists() {
705                    custom_java_home = Some(path.to_string());
706                    break;
707                }
708            }
709        } else {
710            // Linux & other Unix-like OS
711            let unix_paths = [
712                "/opt/android-studio/jbr",
713                "/opt/android-studio/jre",
714                "/snap/android-studio/current/jbr",
715                "/snap/android-studio/current/jre",
716                "/usr/local/android-studio/jbr",
717                "/usr/local/android-studio/jre",
718                "/usr/lib/jvm/default-java",
719            ];
720            for path in &unix_paths {
721                if Path::new(path).exists() {
722                    custom_java_home = Some(path.to_string());
723                    break;
724                }
725            }
726        }
727
728        let adb = get_adb_bin();
729        let mut devices = get_adb_devices();
730        if devices.is_empty() {
731            println!("šŸ“± Perangkat Android atau emulator tidak terdeteksi aktif.");
732            let mut emulator_bin = format!("{}/emulator/emulator", android_home);
733            if !Path::new(&emulator_bin).exists() {
734                let fallback = format!("{}/tools/emulator", android_home);
735                if Path::new(&fallback).exists() {
736                    emulator_bin = fallback;
737                } else {
738                    emulator_bin = "emulator".to_string();
739                }
740            }
741
742            let avd_output = Command::new(&emulator_bin).arg("-list-avds").output();
743            if let Ok(avd_out) = avd_output {
744                let avds_str = String::from_utf8_lossy(&avd_out.stdout);
745                if let Some(avd_name) = avds_str.lines().next() {
746                    println!("šŸš€ Menyalakan emulator AVD: {}...", avd_name);
747                    let _ = Command::new(&emulator_bin)
748                        .args(["-avd", avd_name])
749                        .stdout(std::process::Stdio::null())
750                        .stderr(std::process::Stdio::null())
751                        .spawn();
752                    
753                    println!("ā³ Menunggu emulator menyala dan terdeteksi adb...");
754                    let _ = Command::new(&adb).arg("wait-for-device").status();
755                    println!("āœ… Emulator berhasil aktif!");
756                    std::thread::sleep(std::time::Duration::from_secs(3));
757                    devices = get_adb_devices();
758                }
759            }
760        }
761
762        let (device_id, device_name) = if devices.len() == 1 {
763            let d = devices[0].clone();
764            println!("šŸ“± Menggunakan perangkat tunggal: {} ({})", d.1, d.0);
765            d
766        } else if devices.len() > 1 {
767            println!("šŸ“± Terdeteksi beberapa perangkat Android. Silakan pilih target:");
768            for (idx, d) in devices.iter().enumerate() {
769                println!("  [{}] {} ({})", idx + 1, d.1, d.0);
770            }
771            let choice = crate::utils::prompt_choice("šŸ‘‰ Pilih nomor perangkat: ", 1, devices.len());
772            devices[choice - 1].clone()
773        } else {
774            println!("āŒ Error: Tidak ada perangkat Android terhubung.");
775            return;
776        };
777
778        // 3. build JNI
779        if !compile_jni_libraries() {
780            return;
781        }
782
783        // 4. local.properties
784        let local_props = Path::new("native/android/local.properties");
785        if !local_props.exists() {
786            if let Ok(mut file) = fs::File::create(local_props) {
787                let _ = writeln!(file, "sdk.dir={}", android_home);
788            }
789        }
790
791        // 5. gradlew assembleDebug
792        println!("šŸ”Ø Membangun debug APK menggunakan Gradle...");
793        let gradlew_bin = if cfg!(target_os = "windows") { "gradlew.bat" } else { "./gradlew" };
794        let mut gradle_cmd = Command::new(gradlew_bin);
795        gradle_cmd.arg("assembleDebug");
796        gradle_cmd.current_dir("native/android");
797
798        if let Some(jh) = &custom_java_home {
799            gradle_cmd.env("JAVA_HOME", jh);
800        }
801
802        let gradle_status = gradle_cmd.status();
803        if gradle_status.is_err() || !gradle_status.unwrap().success() {
804            println!("āŒ Gradle build assembleDebug gagal.");
805            return;
806        }
807
808        // 6. adb install
809        println!("šŸ”Ø Memasang APK ke perangkat {} ({})...", device_name, device_id);
810        let install_status = Command::new(&adb)
811            .args(["-s", &device_id, "install", "-r", "native/android/app/build/outputs/apk/debug/app-debug.apk"])
812            .status();
813
814        if install_status.is_err() || !install_status.unwrap().success() {
815            println!("āŒ Gagal memasang APK ke device.");
816            return;
817        }
818
819        // 7. adb reverse
820        let vite_port = "5173"; // default
821        let reverse_status = Command::new(&adb)
822            .args(["-s", &device_id, "reverse", &format!("tcp:{}", vite_port), &format!("tcp:{}", vite_port)])
823            .status();
824        if reverse_status.is_err() {
825            println!("āš ļø Warning: Gagal melakukan adb reverse port {}", vite_port);
826        }
827
828        // 8. adb shell am start
829        println!("šŸš€ Membuka aplikasi di perangkat {}...", device_name);
830        let _ = Command::new(&adb)
831            .args(["-s", &device_id, "logcat", "-c"])
832            .status();
833        
834        let _ = Command::new(&adb)
835            .args(["-s", &device_id, "shell", "am", "start", "-n", "com.rustbasic.mobile/com.rustbasic.mobile.MainActivity"])
836            .status();
837
838        println!("šŸ“‹ Menampilkan log realtime dari perangkat {} (Tekan Ctrl+C untuk keluar)...", device_name);
839        let mut logcat_cmd = Command::new(&adb);
840        logcat_cmd.args(["-s", &device_id, "logcat", "-s", "RustBasicServer"]);
841        let mut child = logcat_cmd.spawn().expect("Gagal menjalankan adb logcat");
842        let _ = child.wait();
843    } else if run_desktop {
844        println!("šŸš€ Memulai RustBasic Desktop Wrapper...");
845        let mut cmd = Command::new("cargo");
846        cmd.args(["run", "--bin", "rustbasic-desktop", "--features", "desktop"]);
847        let status = cmd.status();
848        match status {
849            Ok(s) if s.success() => {}
850            _ => {
851                println!("āŒ Gagal menjalankan Desktop Wrapper.");
852            }
853        }
854    }
855}
856
857fn prompt_string(prompt: &str, default: &str) -> String {
858    print!("{}", prompt);
859    let _ = std::io::stdout().flush();
860    let mut input = String::new();
861    if std::io::stdin().read_line(&mut input).is_ok() {
862        let trimmed = input.trim();
863        if trimmed.is_empty() {
864            default.to_string()
865        } else {
866            trimmed.to_string()
867        }
868    } else {
869        default.to_string()
870    }
871}
872
873pub fn deploy_interactive() {
874    println!("\n{}", "šŸš€ RustBasic Docker Deploy CLI".magenta().bold());
875    println!("{}", "------------------------------".magenta());
876
877    // 1. Konfigurasi Image & Pengiriman
878    let image_name = prompt_string("šŸ‘‰ Masukkan Nama/Tag Docker Image (default: rustbasic:latest): ", "rustbasic:latest");
879    
880    // Tentukan nama container dari env DOCKER_CONTAINER_NAME atau fallback dari nama image
881    let container_name = std::env::var("DOCKER_CONTAINER_NAME").unwrap_or_else(|_| {
882        let base_name = image_name
883            .split(':')
884            .next()
885            .unwrap_or("rustbasic")
886            .split('/')
887            .last()
888            .unwrap_or("rustbasic")
889            .to_lowercase();
890        format!("{}-app", base_name)
891    });
892
893    // Cek apakah Docker image sudah ada
894    let inspect = Command::new("docker")
895        .args(["image", "inspect", &image_name])
896        .stdout(std::process::Stdio::null())
897        .stderr(std::process::Stdio::null())
898        .status();
899
900    match inspect {
901        Ok(status) if status.success() => {}
902        _ => {
903            println!("{}", format!("āš ļø  Peringatan: Image '{}' tidak ditemukan lokal.", image_name).yellow());
904            let proceed = prompt_string("šŸ‘‰ Tetap lanjutkan proses ekspor? (y/n) [default: n]: ", "n");
905            if proceed.to_lowercase() != "y" {
906                println!("āŒ Proses dihentikan.");
907                return;
908            }
909        }
910    }
911
912    // 2. Ekspor ke tar
913    println!("šŸ“¦ Mengekspor Docker image '{}' ke 'rustbasic.tar'...", image_name);
914    let save_status = Command::new("docker")
915        .args(["save", "-o", "rustbasic.tar", &image_name])
916        .status();
917
918    match save_status {
919        Ok(status) if status.success() => {
920            println!("{}", "āœ… Image berhasil diekspor ke rustbasic.tar.".green());
921        }
922        _ => {
923            println!("{}", "āŒ Gagal mengekspor Docker image.".red().bold());
924            return;
925        }
926    }
927
928    // 3. Konfigurasi Pengiriman
929    println!("\n{}", "🌐 Konfigurasi Pengiriman ke Server".cyan().bold());
930    println!("{}", "-----------------------------------".cyan());
931    let ssh_user = prompt_string("šŸ‘‰ Masukkan SSH Username Server (contoh: root) [default: root]: ", "root");
932    let ssh_ip = prompt_string("šŸ‘‰ Masukkan IP Address Server: ", "");
933    if ssh_ip.is_empty() {
934        println!("{}", "āŒ IP Address server tidak boleh kosong.".red().bold());
935        let _ = fs::remove_file("rustbasic.tar");
936        return;
937    }
938    let ssh_port = prompt_string("šŸ‘‰ Masukkan SSH Port Server (default: 22): ", "22");
939    let dest_dir = prompt_string("šŸ‘‰ Masukkan Folder Tujuan di Server (default: ~/app): ", "~/app");
940    let server_port = prompt_string("šŸ‘‰ Masukkan Port Server Mapping (contoh: 80:4000) [default: 80:4000]: ", "80:4000");
941    let env_file = prompt_string("šŸ‘‰ Masukkan File Env yang akan dikirim (default: .env): ", ".env");
942
943    println!("\nšŸš€ Menyiapkan folder tujuan di server...");
944    let mkdir_status = Command::new("ssh")
945        .args([
946            "-p", &ssh_port,
947            &format!("{}@{}", ssh_user, ssh_ip),
948            &format!("mkdir -p {}", dest_dir)
949        ])
950        .status();
951
952    match mkdir_status {
953        Ok(status) if status.success() => {}
954        _ => {
955            println!("{}", "āŒ Gagal terhubung ke server menggunakan SSH.".red().bold());
956            let _ = fs::remove_file("rustbasic.tar");
957            return;
958        }
959    }
960
961    println!("šŸš€ Mengirimkan berkas rustbasic.tar & {} ke server...", env_file);
962    let scp_status = Command::new("scp")
963        .args([
964            "-P", &ssh_port,
965            "rustbasic.tar", &env_file,
966            &format!("{}@{}:{}", ssh_user, ssh_ip, dest_dir)
967        ])
968        .status();
969
970    // Hapus file tar lokal
971    let _ = fs::remove_file("rustbasic.tar");
972
973    if let Ok(status) = scp_status {
974        if !status.success() {
975            println!("{}", "āŒ Gagal mengirimkan berkas via SCP.".red().bold());
976            return;
977        }
978    } else {
979        println!("{}", "āŒ Gagal menjalankan SCP.".red().bold());
980        return;
981    }
982
983    println!("{}", "āœ… Pengiriman berkas berhasil!".green());
984
985    // 4. Eksekusi SSH otomatis di server jika disetujui
986    let auto_run = prompt_string("\nšŸ‘‰ Apakah Anda ingin langsung menjalankan container di server secara otomatis? (y/n) [default: y]: ", "y");
987    if auto_run.to_lowercase() == "y" || auto_run.is_empty() {
988        println!("\nšŸš€ Memuat image di server (docker load)...");
989        let load_status = Command::new("ssh")
990            .args([
991                "-p", &ssh_port,
992                &format!("{}@{}", ssh_user, ssh_ip),
993                &format!("docker load -i {}/rustbasic.tar", dest_dir)
994            ])
995            .status();
996
997        match load_status {
998            Ok(status) if status.success() => {
999                println!("{}", "āœ… Image berhasil dimuat di server.".green());
1000            }
1001            _ => {
1002                println!("{}", "āŒ Gagal memuat image di server.".red().bold());
1003                return;
1004            }
1005        }
1006
1007        println!("šŸš€ Menghentikan & menghapus container lama '{}' jika ada...", container_name);
1008        let stop_status = Command::new("ssh")
1009            .args([
1010                "-p", &ssh_port,
1011                &format!("{}@{}", ssh_user, ssh_ip),
1012                &format!("docker stop {} || true && docker rm {} || true", container_name, container_name)
1013            ])
1014            .status();
1015
1016        if let Err(e) = stop_status {
1017            println!("āš ļø Peringatan saat membersihkan container lama: {}", e);
1018        }
1019
1020        println!("šŸš€ Menjalankan container baru '{}'...", container_name);
1021        let run_cmd = format!(
1022            "docker run -d --name {} -p {} --restart unless-stopped --env-file {}/.env {}",
1023            container_name, server_port, dest_dir, image_name
1024        );
1025        let run_status = Command::new("ssh")
1026            .args([
1027                "-p", &ssh_port,
1028                &format!("{}@{}", ssh_user, ssh_ip),
1029                &run_cmd
1030            ])
1031            .status();
1032
1033        match run_status {
1034            Ok(status) if status.success() => {
1035                println!("{}", format!("šŸŽ‰ Container '{}' berhasil dijalankan di server!", container_name).green().bold());
1036            }
1037            _ => {
1038                println!("{}", "āŒ Gagal menjalankan container di server.".red().bold());
1039                return;
1040            }
1041        }
1042
1043        println!("šŸš€ Membersihkan file tar di server...");
1044        let rm_status = Command::new("ssh")
1045            .args([
1046                "-p", &ssh_port,
1047                &format!("{}@{}", ssh_user, ssh_ip),
1048                &format!("rm {}/rustbasic.tar", dest_dir)
1049            ])
1050            .status();
1051
1052        if let Err(e) = rm_status {
1053            println!("āš ļø Peringatan saat membersihkan file tar di server: {}", e);
1054        }
1055
1056        println!("\n{}", "šŸŽ‰ Deployment selesai!".green().bold());
1057        println!("{}", "--------------------------------------------------------".green());
1058        println!("Untuk melihat log aplikasi di server, jalankan:");
1059        println!("ssh -p {} {}@{} \"docker logs -f {}\"", ssh_port, ssh_user, ssh_ip, container_name);
1060        println!("{}", "--------------------------------------------------------".green());
1061    } else {
1062        println!("\n{}", "šŸ–„ļø  Langkah Selanjutnya di Server Anda:".cyan().bold());
1063        println!("{}", "--------------------------------------------------------".green());
1064        println!("1. Hubungkan ke server via SSH:");
1065        println!("   ssh -p {} {}@{}", ssh_port, ssh_user, ssh_ip);
1066        println!("");
1067        println!("2. Masuk ke folder tujuan:");
1068        println!("   cd {}", dest_dir);
1069        println!("");
1070        println!("3. Muat (load) image Docker dari berkas tar:");
1071        println!("   docker load -i rustbasic.tar");
1072        println!("");
1073        println!("4. Jalankan container dengan fitur auto-restart:");
1074        println!("   docker run -d --name {} -p {} --restart unless-stopped --env-file .env {}", container_name, server_port, image_name);
1075        println!("");
1076        println!("5. Hapus file tar di server untuk menghemat disk:");
1077        println!("   rm rustbasic.tar");
1078        println!("{}", "--------------------------------------------------------".green());
1079    }
1080}
1081
1082fn get_adb_bin() -> String {
1083    // 1. Cek apakah adb terdaftar di PATH dan bisa dijalankan
1084    if let Ok(output) = Command::new("adb").arg("devices").output() {
1085        if output.status.success() {
1086            return "adb".to_string();
1087        }
1088    }
1089
1090    // 2. Jika tidak ada di PATH, cari di folder SDK default
1091    let os = std::env::consts::OS;
1092    let home = std::env::var("HOME").unwrap_or_default();
1093    let android_home = if let Ok(val) = std::env::var("ANDROID_HOME") {
1094        val
1095    } else {
1096        if os == "macos" {
1097            format!("{}/Library/Android/sdk", home)
1098        } else if os == "windows" {
1099            let local_app_data = std::env::var("LOCALAPPDATA").unwrap_or_default();
1100            format!("{}\\Android\\Sdk", local_app_data)
1101        } else {
1102            format!("{}/Android/Sdk", home)
1103        }
1104    };
1105
1106    let adb_name = if os == "windows" { "adb.exe" } else { "adb" };
1107    let path = Path::new(&android_home).join("platform-tools").join(adb_name);
1108    if path.exists() {
1109        path.display().to_string()
1110    } else {
1111        "adb".to_string() // fallback terakhir jika semua gagal
1112    }
1113}
1114
1115fn get_adb_devices() -> Vec<(String, String)> {
1116    let adb = get_adb_bin();
1117    let output = Command::new(&adb).arg("devices").output();
1118    let mut devices = Vec::new();
1119    if let Ok(out) = output {
1120        let stdout = String::from_utf8_lossy(&out.stdout);
1121        for line in stdout.lines() {
1122            let line = line.trim();
1123            if line.is_empty() || line.starts_with("List of devices") {
1124                continue;
1125            }
1126            let parts: Vec<&str> = line.split_whitespace().collect();
1127            if parts.len() >= 2 && parts[1] == "device" {
1128                let device_id = parts[0].to_string();
1129                let model_out = Command::new(&adb)
1130                    .args(["-s", &device_id, "shell", "getprop", "ro.product.model"])
1131                    .output();
1132                let model = if let Ok(m_out) = model_out {
1133                    String::from_utf8_lossy(&m_out.stdout).trim().to_string()
1134                } else {
1135                    "Unknown Device".to_string()
1136                };
1137                devices.push((device_id, model));
1138            }
1139        }
1140    }
1141    devices
1142}
1143
1144fn compile_jni_libraries() -> bool {
1145    println!("šŸš€ Building Rust library for Android (Native Rust implementation)...");
1146
1147    let _ = Command::new("rustup")
1148        .args(["target", "add", "aarch64-linux-android", "armv7-linux-androideabi", "x86_64-linux-android"])
1149        .status();
1150
1151    let home = std::env::var("HOME").unwrap_or_default();
1152    let android_ndk_home = if let Ok(val) = std::env::var("ANDROID_NDK_HOME") {
1153        val
1154    } else {
1155        let mac_ndk = format!("{}/Library/Android/sdk/ndk", home);
1156        if Path::new(&mac_ndk).exists() {
1157            if let Ok(entries) = fs::read_dir(&mac_ndk) {
1158                let mut paths: Vec<_> = entries.flatten().map(|e| e.path()).collect();
1159                paths.sort();
1160                if let Some(highest) = paths.last() {
1161                    highest.display().to_string()
1162                } else {
1163                    "".to_string()
1164                }
1165            } else {
1166                "".to_string()
1167            }
1168        } else {
1169            "".to_string()
1170        }
1171    };
1172
1173    if android_ndk_home.is_empty() {
1174        println!("āŒ Error: ANDROID_NDK_HOME is not set. Please set ANDROID_NDK_HOME.");
1175        return false;
1176    }
1177
1178    println!("Using NDK: {}", android_ndk_home);
1179
1180    let os = std::env::consts::OS;
1181    let toolchain_sub = if os == "macos" { "darwin-x86_64" } else { "linux-x86_64" };
1182    let toolchain_bin_path = Path::new(&android_ndk_home)
1183        .join("toolchains/llvm/prebuilt")
1184        .join(toolchain_sub)
1185        .join("bin");
1186
1187    if !toolchain_bin_path.exists() {
1188        println!("āŒ Error: Toolchain bin path not found: {}", toolchain_bin_path.display());
1189        return false;
1190    }
1191
1192    let sqlite_version = "3450100";
1193    let sqlite_dir = format!("target/sqlite-amalgamation-{}", sqlite_version);
1194    if !Path::new(&sqlite_dir).exists() {
1195        println!("šŸ“„ Downloading SQLite source amalgamation...");
1196        fs::create_dir_all("target").ok();
1197        
1198        let zip_path = "target/sqlite.zip";
1199        let sqlite_url = format!("https://www.sqlite.org/2024/sqlite-amalgamation-{}.zip", sqlite_version);
1200        
1201        let curl_status = Command::new("curl")
1202            .args(["-sSLo", zip_path, &sqlite_url])
1203            .status();
1204        
1205        if curl_status.is_err() || !curl_status.unwrap().success() {
1206            println!("āŒ Gagal men-download SQLite source.");
1207            return false;
1208        }
1209
1210        let unzip_status = Command::new("unzip")
1211            .args(["-q", zip_path, "-d", "target/"])
1212            .status();
1213
1214        let _ = fs::remove_file(zip_path);
1215
1216        if unzip_status.is_err() || !unzip_status.unwrap().success() {
1217            println!("āŒ Gagal mengekstrak SQLite source.");
1218            return false;
1219        }
1220    }
1221
1222    let targets = vec![
1223        ("aarch64-linux-android", "arm64-v8a", "aarch64-linux-android21-clang"),
1224        ("armv7-linux-androideabi", "armeabi-v7a", "armv7a-linux-androideabi21-clang"),
1225        ("x86_64-linux-android", "x86_64", "x86_64-linux-android21-clang"),
1226    ];
1227
1228    let jnilibs_dir = "native/android/app/src/main/jniLibs";
1229
1230    for (target, arch, clang_name) in targets {
1231        println!("šŸ”Ø Preparing SQLite static library for {}...", target);
1232        
1233        let clang_path = toolchain_bin_path.join(clang_name);
1234        let ar_path = toolchain_bin_path.join("llvm-ar");
1235
1236        if !clang_path.exists() {
1237            println!("āŒ Error: Compiler not found: {}", clang_path.display());
1238            return false;
1239        }
1240
1241        let sqlite_out = format!("target/{}/sqlite", target);
1242        fs::create_dir_all(&sqlite_out).ok();
1243
1244        let libsqlite3_a = format!("{}/libsqlite3.a", sqlite_out);
1245        if !Path::new(&libsqlite3_a).exists() {
1246            println!("   Compiling SQLite static lib for {}...", target);
1247            let sqlite3_o = format!("{}/sqlite3.o", sqlite_out);
1248            let sqlite3_c = format!("{}/sqlite3.c", sqlite_dir);
1249            
1250            let compile_status = Command::new(&clang_path)
1251                .args(["-O2", "-c", &sqlite3_c, "-o", &sqlite3_o])
1252                .status();
1253
1254            if compile_status.is_err() || !compile_status.unwrap().success() {
1255                println!("āŒ Gagal mengompilasi sqlite3.o");
1256                return false;
1257            }
1258
1259            let archive_status = Command::new(&ar_path)
1260                .args(["rcs", &libsqlite3_a, &sqlite3_o])
1261                .status();
1262
1263            if archive_status.is_err() || !archive_status.unwrap().success() {
1264                println!("āŒ Gagal mengarsip libsqlite3.a");
1265                return false;
1266            }
1267        }
1268
1269        println!("šŸ”Ø Compiling Rust library for {}...", target);
1270        let mut cargo_cmd = Command::new("cargo");
1271        cargo_cmd.args(["build", "--target", target, "--release"]);
1272
1273        let clang_path_str = clang_path.display().to_string();
1274        let ar_path_str = ar_path.display().to_string();
1275
1276        let target_upper = target.replace("-", "_").to_uppercase();
1277        let linker_env = format!("CARGO_TARGET_{}_LINKER", target_upper);
1278        let cc_env = format!("CC_{}", target.replace("-", "_"));
1279        let ar_env = format!("AR_{}", target.replace("-", "_"));
1280
1281        cargo_cmd.env(&linker_env, &clang_path_str);
1282        cargo_cmd.env(&cc_env, &clang_path_str);
1283        cargo_cmd.env(&ar_env, &ar_path_str);
1284
1285        let cargo_status = cargo_cmd.status();
1286        if cargo_status.is_err() || !cargo_status.unwrap().success() {
1287            println!("āŒ Gagal mengompilasi library Rust untuk target {}", target);
1288            return false;
1289        }
1290
1291        let dest_dir = format!("{}/{}", jnilibs_dir, arch);
1292        fs::create_dir_all(&dest_dir).ok();
1293
1294        let src_so = format!("target/{}/release/librustbasic.so", target);
1295        let dest_so = format!("{}/librustbasic_mobile.so", dest_dir);
1296
1297        if let Err(e) = fs::copy(&src_so, &dest_so) {
1298            println!("āŒ Gagal menyalin {}: {}", src_so, e);
1299            return false;
1300        }
1301    }
1302
1303    println!("āœ… Android JNI libraries built successfully!");
1304    true
1305}