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 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 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 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 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 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
239pub fn build_docker(custom_tag: &str, extract_binary: bool) {
241 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 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 ā Standalone (Cached)
267# ============================================================
268
269# Stage 1: Builder
270FROM rust:1-slim-bookworm AS builder
271
272RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
273 --mount=type=cache,target=/var/lib/apt,sharing=locked \
274 apt-get update && apt-get install -y \
275 pkg-config libssl-dev
276
277# Copy rustbasic-core (dari konteks workspace root)
278WORKDIR /rustbasic-core
279COPY --from=core . .
280
281# Copy proyek utama rustbasic
282WORKDIR /build
283COPY . .
284
285# Build release binary using Cargo registry, git cache, and target cache
286ENV CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse
287RUN --mount=type=cache,target=/usr/local/cargo/registry \
288 --mount=type=cache,target=/usr/local/cargo/git \
289 --mount=type=cache,target=/build/target \
290 cargo build --release --bin rustbasic && \
291 cp target/release/rustbasic /build/rustbasic-bin
292
293# Stage 2: Runtime
294FROM debian:bookworm-slim
295
296RUN apt-get update && apt-get install -y \
297 ca-certificates libssl3 \
298 && rm -rf /var/lib/apt/lists/*
299
300WORKDIR /app
301
302# Copy binary dari builder stage
303COPY --from=builder /build/rustbasic-bin ./rustbasic
304
305# Copy assets yang diperlukan dari builder stage (lebih aman dan bersih)
306COPY --from=builder /build/src/resources/views/ src/resources/views/
307COPY --from=builder /build/src/dist/ src/dist/
308COPY --from=builder /build/public/ public/
309COPY --from=builder /build/database/ database/
310COPY --from=builder /build/.env.example .env
311
312# Expose port aplikasi
313EXPOSE 4000
314
315CMD ["./rustbasic"]
316"#.to_string()
317 } else {
318 format!(r#"# ============================================================
319# RustBasic Docker Build ā Standalone (Cached)
320# ============================================================
321
322# Stage 1: Builder
323FROM rust:1-slim-bookworm AS builder
324
325RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
326 --mount=type=cache,target=/var/lib/apt,sharing=locked \
327 apt-get update && apt-get install -y \
328 pkg-config libssl-dev
329
330WORKDIR /build
331
332COPY . .
333
334# Build release binary using Cargo registry, git cache, and target cache
335ENV CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse
336RUN --mount=type=cache,target=/usr/local/cargo/registry \
337 --mount=type=cache,target=/usr/local/cargo/git \
338 --mount=type=cache,target=/build/target \
339 cargo build --release --bin {bin_name} && \
340 cp target/release/{bin_name} /build/{bin_name}-bin
341
342# Stage 2: Runtime
343FROM debian:bookworm-slim
344
345RUN apt-get update && apt-get install -y \
346 ca-certificates libssl3 \
347 && rm -rf /var/lib/apt/lists/*
348
349WORKDIR /app
350
351# Copy binary dari builder stage
352COPY --from=builder /build/{bin_name}-bin ./{bin_name}
353
354# Copy assets yang diperlukan dari builder stage
355COPY --from=builder /build/src/resources/views/ src/resources/views/
356COPY --from=builder /build/src/dist/ src/dist/
357COPY --from=builder /build/public/ public/
358COPY --from=builder /build/database/ database/
359COPY --from=builder /build/.env.example .env
360
361# Expose port aplikasi
362EXPOSE 4000
363
364CMD ["./{bin_name}"]
365"#, bin_name = binary_name)
366 };
367
368 if let Err(e) = fs::write(dockerfile_path, dockerfile_content) {
369 println!("ā Gagal membuat Dockerfile: {}", e);
370 return;
371 }
372 println!("ā
Dockerfile berhasil dibuat.");
373 }
374
375 let app_name = std::env::var("BUILD_NAME")
377 .or_else(|_| std::env::var("APP_NAME"))
378 .unwrap_or_else(|_| get_cargo_package_name())
379 .to_lowercase();
380
381 let image_tag = if custom_tag.is_empty() {
382 format!("{}:latest", app_name)
383 } else {
384 custom_tag.to_string()
385 };
386
387 let core_context = if std::path::Path::new("../rustbasic-core").exists() {
388 "core=../rustbasic-core"
389 } else {
390 "core=."
391 };
392
393 let mut docker_platform = String::new();
395 println!("\nPilih Platform Target CPU Docker:");
396 println!(" [1] Current Host Platform (Sesuai OS komputer Anda)");
397 println!(" [2] Linux AMD64 / x86_64 (Standard VPS Intel/AMD - Umum/Rekomendasi)");
398 println!(" [3] Linux ARM64 / aarch64 (Server berbasis ARM / AWS Graviton)");
399 match crate::utils::prompt_choice("š Pilih (1-3): ", 1, 3) {
400 2 => docker_platform = "linux/amd64".to_string(),
401 3 => docker_platform = "linux/arm64".to_string(),
402 _ => {}
403 }
404
405 let host_arch = get_host_arch();
407 let is_mismatch = (host_arch == "aarch64" && docker_platform == "linux/amd64")
408 || (host_arch == "x86_64" && docker_platform == "linux/arm64");
409
410 if is_mismatch {
411 println!("\nā ļø {}", "PERINGATAN: Arsitektur CPU Mismatch (Sangat Lambat)".yellow().bold());
412 println!(" Anda berada di host dengan CPU '{}' tetapi memilih target Docker '{}'.", host_arch, docker_platform);
413 println!(" Docker akan menggunakan emulasi CPU (QEMU) yang membuat proses kompilasi");
414 println!(" Rust berjalan {} (bisa memakan waktu 10-30 menit).", "10x-20x LEBIH LAMBAT".red().bold());
415 println!(" ");
416 println!(" š” {} Kami telah mengaktifkan target caching untuk mempercepat build.", "TIPS:".cyan().bold());
417 println!(" Build pertama tetap lambat, namun build berikutnya akan sangat cepat (1-2 menit)");
418 println!(" karena target directory dan dependency cache disimpan oleh Docker.");
419 println!(" ");
420 let proceed = prompt_string("š Apakah Anda ingin melanjutkan proses build? (y/n) [default: y]: ", "y");
421 if !proceed.to_lowercase().starts_with('y') {
422 println!("ā Build dibatalkan oleh pengguna.");
423 return;
424 }
425 }
426
427 println!("\nš³ Memulai Docker build...");
429 println!(" Image tag: {}", image_tag);
430
431 let mut build_args = vec![
432 "build".to_string(),
433 "--build-context".to_string(),
434 core_context.to_string(),
435 "--progress=plain".to_string(),
436 ];
437
438 if !docker_platform.is_empty() {
439 build_args.push("--platform".to_string());
440 build_args.push(docker_platform.clone());
441 println!(" Platform: {}", docker_platform);
442 }
443
444 build_args.push("-t".to_string());
445 build_args.push(image_tag.clone());
446 build_args.push(".".to_string());
447
448 println!(" Running: docker {}", build_args.join(" "));
449
450 let mut cmd = Command::new("docker")
451 .args(&build_args)
452 .stdin(std::process::Stdio::inherit())
453 .stdout(std::process::Stdio::inherit())
454 .stderr(std::process::Stdio::inherit())
455 .spawn()
456 .expect("Gagal menjalankan docker build");
457
458 let status = cmd.wait().expect("Gagal menunggu docker build");
459
460 if status.success() {
461 println!("\nā
Docker build selesai dengan sukses!");
462 println!("š¦ Image: {}", image_tag);
463
464 if extract_binary {
465 println!("š¦ Mengekstrak biner Linux dari image Docker...");
466 let container_name = format!("temp-extract-{}", std::time::SystemTime::now()
467 .duration_since(std::time::UNIX_EPOCH)
468 .unwrap_or_default()
469 .as_secs());
470
471 let create_res = Command::new("docker")
473 .args(["create", "--name", &container_name, &image_tag])
474 .status();
475
476 if create_res.is_ok() && create_res.unwrap().success() {
477 let _ = std::fs::create_dir_all("build-output");
479
480 let binary_name = get_cargo_package_name();
482
483 let cp_status = Command::new("docker")
485 .args(["cp", &format!("{}:/app/{}", container_name, binary_name), &format!("build-output/{}", binary_name)])
486 .status();
487
488 let _ = Command::new("docker")
490 .args(["rm", &container_name])
491 .status();
492
493 if cp_status.is_ok() && cp_status.unwrap().success() {
494 println!("ā
Biner berhasil diekstrak ke: {}", format!("build-output/{}", binary_name).cyan().bold());
495 } else {
496 println!("ā Gagal mengekstrak biner dari container.");
497 }
498 } else {
499 println!("ā Gagal membuat temporary container untuk ekstraksi.");
500 }
501 }
502
503 println!("\n Jalankan container (Development/Lokal):");
504 println!(" docker run -p 4000:4000 --env-file .env {}", image_tag);
505 println!("\n Jalankan container (Produksi/Server - Auto Restart):");
506 println!(" docker run -d -p 80:4000 --restart unless-stopped --env-file .env {}", image_tag);
507 } else {
508 println!("\nā Docker build gagal.");
509 }
510}
511
512pub fn build_interactive(args: &[String]) {
514 let mut build_docker_flag = args.iter().any(|arg| arg == "--docker");
515 let mut build_desktop = args.iter().any(|arg| arg == "--desktop");
516 let mut build_android = args.iter().any(|arg| arg == "--android");
517 let mut release_mode = args.iter().any(|arg| arg == "--release" || arg == "-r");
518 let mut target_type = String::new(); let mut docker_tag = String::new();
520 let extract_binary = args.iter().any(|arg| arg == "--extract" || arg == "-e");
521
522 for i in 0..args.len() {
524 if args[i] == "--type" && i + 1 < args.len() {
525 target_type = args[i+1].to_lowercase();
526 }
527 if args[i] == "--tag" && i + 1 < args.len() {
528 docker_tag = args[i+1].clone();
529 }
530 }
531
532 if !build_docker_flag && !build_desktop && !build_android {
533 let is_native_installed = crate::packages::read_manifest()
534 .packages
535 .iter()
536 .any(|pkg| pkg.name == "rustbasic-native");
537
538 println!("š ļø RustBasic Build CLI");
539 println!("Pilih platform target untuk di-build:");
540 println!(" [1] Docker (Container Image)");
541
542 let max_choice = if is_native_installed {
543 println!(" [2] Desktop Wrapper (Windows, macOS, Linux)");
544 println!(" [3] Android Wrapper (APK, AAB)");
545 3
546 } else {
547 1
548 };
549
550 let prompt_str = format!("š Pilih nomor platform (1-{}): ", max_choice);
551 let choice = crate::utils::prompt_choice(&prompt_str, 1, max_choice);
552 match choice {
553 1 => build_docker_flag = true,
554 2 => build_desktop = true,
555 3 => build_android = true,
556 _ => {}
557 }
558 }
559
560 if build_docker_flag {
561 let mut extract = extract_binary;
562 if !extract && !args.iter().any(|arg| arg == "--docker") {
563 let extract_choice = prompt_string("š Apakah Anda ingin mengekstrak biner Linux hasil build ke folder './build-output'? (y/n) [default: n]: ", "n");
564 extract = extract_choice.to_lowercase().starts_with('y');
565 }
566 build_docker(&docker_tag, extract);
567 } else if build_desktop {
568 let mut target_os = String::new();
569 for i in 0..args.len() {
570 if args[i] == "--os" && i + 1 < args.len() {
571 target_os = args[i+1].clone();
572 }
573 }
574
575 let mut target_triple = "";
576 if target_os.is_empty() {
577 println!("\nPilih OS Target Desktop:");
578 println!(" [1] Current OS (Sistem saat ini)");
579 println!(" [2] macOS Intel (x86_64)");
580 println!(" [3] macOS Apple Silicon (aarch64)");
581 println!(" [4] Windows MSVC (x86_64-pc-windows-msvc)");
582 println!(" [5] Windows GNU (x86_64-pc-windows-gnu - Rekomendasi Cross-compile dari macOS/Linux)");
583 println!(" [6] Linux (x86_64-unknown-linux-gnu)");
584 let choice = crate::utils::prompt_choice("š Pilih nomor target OS (1-6): ", 1, 6);
585 match choice {
586 2 => target_triple = "x86_64-apple-darwin",
587 3 => target_triple = "aarch64-apple-darwin",
588 4 => target_triple = "x86_64-pc-windows-msvc",
589 5 => target_triple = "x86_64-pc-windows-gnu",
590 6 => target_triple = "x86_64-unknown-linux-gnu",
591 _ => {}
592 }
593 } else {
594 match target_os.as_str() {
595 "macos-intel" | "macos_intel" => target_triple = "x86_64-apple-darwin",
596 "macos-silicon" | "macos_silicon" => target_triple = "aarch64-apple-darwin",
597 "macos" => {
598 #[cfg(target_arch = "aarch64")]
599 { target_triple = "aarch64-apple-darwin"; }
600 #[cfg(not(target_arch = "aarch64"))]
601 { target_triple = "x86_64-apple-darwin"; }
602 }
603 "windows" => target_triple = "x86_64-pc-windows-msvc",
604 "windows-gnu" => target_triple = "x86_64-pc-windows-gnu",
605 "linux" => target_triple = "x86_64-unknown-linux-gnu",
606 _ => {
607 println!("ā ļø Warning: Target OS '{}' tidak dikenal, menggunakan default OS saat ini.", target_os);
608 }
609 }
610 }
611
612 if !args.iter().any(|arg| arg == "--release" || arg == "-r" || arg == "--debug" || arg == "-d") {
613 println!("\nPilih Mode Build:");
614 println!(" [1] Debug (Cepat compile)");
615 println!(" [2] Release (Optimasi penuh)");
616 let choice = crate::utils::prompt_choice("š Pilih nomor mode (1-2): ", 1, 2);
617 if choice == 2 {
618 release_mode = true;
619 }
620 }
621
622 println!("\nš„ļø Memulai proses build Desktop...");
623 let mut build_args = vec!["build", "--bin", "rustbasic-desktop", "--features", "desktop"];
624 if release_mode {
625 build_args.push("--release");
626 }
627 if !target_triple.is_empty() {
628 build_args.push("--target");
629 build_args.push(target_triple);
630 let _ = Command::new("rustup")
631 .args(["target", "add", target_triple])
632 .status();
633 }
634
635 println!(" Running: cargo {}", build_args.join(" "));
636 let mut cmd = Command::new("cargo")
637 .args(&build_args)
638 .stdin(std::process::Stdio::inherit())
639 .stdout(std::process::Stdio::inherit())
640 .stderr(std::process::Stdio::inherit())
641 .spawn()
642 .expect("Gagal menjalankan cargo build");
643
644 let status = cmd.wait().expect("Gagal menunggu proses cargo build");
645 if status.success() {
646 println!("\nā
Build Desktop selesai dengan sukses!");
647 let mode_str = if release_mode { "release" } else { "debug" };
648 let path_str = if target_triple.is_empty() {
649 format!("target/{}/rustbasic-desktop", mode_str)
650 } else {
651 format!("target/{}/{}/rustbasic-desktop", target_triple, mode_str)
652 };
653 println!("š Output biner: {}", path_str);
654 } else {
655 println!("\nā Build Desktop gagal.");
656 }
657
658 } else if build_android {
659 let mut is_aab = false;
660 if target_type.is_empty() {
661 println!("\nPilih Format Output Android:");
662 println!(" [1] APK (Android Package - Siap install)");
663 println!(" [2] AAB (Android App Bundle - Siap Google Play)");
664 let choice = crate::utils::prompt_choice("š Pilih format (1-2): ", 1, 2);
665 if choice == 2 {
666 is_aab = true;
667 }
668 } else {
669 is_aab = target_type == "aab";
670 }
671
672 if !args.iter().any(|arg| arg == "--release" || arg == "-r" || arg == "--debug" || arg == "-d") {
673 println!("\nPilih Mode Build:");
674 println!(" [1] Debug");
675 println!(" [2] Release (Produksi)");
676 let choice = crate::utils::prompt_choice("š Pilih nomor mode (1-2): ", 1, 2);
677 if choice == 2 {
678 release_mode = true;
679 }
680 }
681
682 println!("\nšØ Membangun JNI library untuk Android...");
683 if !compile_jni_libraries() {
684 println!("ā Gagal membangun JNI libraries.");
685 return;
686 }
687
688 let os = std::env::consts::OS;
690 let home = std::env::var("HOME").unwrap_or_default();
691 let android_home = if let Ok(val) = std::env::var("ANDROID_HOME") {
692 val
693 } else {
694 if os == "macos" {
695 format!("{}/Library/Android/sdk", home)
696 } else {
697 format!("{}/Android/Sdk", home)
698 }
699 };
700 unsafe {
701 std::env::set_var("ANDROID_HOME", &android_home);
702 }
703
704 if std::env::var("JAVA_HOME").is_err() {
705 let studio_jbr = if os == "macos" {
706 let mut jbr = "/Applications/Android Studio.app/Contents/jbr/Contents/Home".to_string();
707 if !Path::new(&jbr).exists() {
708 jbr = "/Applications/Android Studio.app/Contents/jre/Contents/Home".to_string();
709 }
710 jbr
711 } else {
712 let mut jbr = "/opt/android-studio/jbr".to_string();
713 if !Path::new(&jbr).exists() {
714 jbr = "/usr/local/android-studio/jbr".to_string();
715 }
716 jbr
717 };
718 if Path::new(&studio_jbr).exists() {
719 unsafe {
720 std::env::set_var("JAVA_HOME", &studio_jbr);
721 }
722 }
723 }
724
725 let local_props = Path::new("native/android/local.properties");
726 if !local_props.exists()
727 && let Ok(mut file) = fs::File::create(local_props) {
728 let _ = writeln!(file, "sdk.dir={}", android_home);
729 }
730
731 let gradle_task = match (is_aab, release_mode) {
732 (false, false) => "assembleDebug",
733 (false, true) => "assembleRelease",
734 (true, false) => "bundleDebug",
735 (true, true) => "bundleRelease",
736 };
737
738 println!("\nšØ Membangun target Android via Gradle (task: {})...", gradle_task);
739
740 let mut build_cmd = if Path::new("native/android/gradlew").exists() {
741 let mut cmd = Command::new("./gradlew");
742 cmd.arg(gradle_task);
743 cmd.current_dir("native/android");
744 cmd
745 } else {
746 let mut cmd = Command::new("gradle");
747 cmd.arg(gradle_task);
748 cmd.current_dir("native/android");
749 cmd
750 };
751
752 let spawn_res = build_cmd
753 .stdin(std::process::Stdio::inherit())
754 .stdout(std::process::Stdio::inherit())
755 .stderr(std::process::Stdio::inherit())
756 .spawn();
757
758 if let Ok(mut child) = spawn_res {
759 let status = child.wait().expect("Gagal menunggu Gradle build");
760 if status.success() {
761 println!("\nā
Build Android selesai dengan sukses!");
762 let output_dir = if is_aab {
763 let mode_folder = if release_mode { "release" } else { "debug" };
764 format!("native/android/app/build/outputs/bundle/{}", mode_folder)
765 } else {
766 let mode_folder = if release_mode { "release" } else { "debug" };
767 format!("native/android/app/build/outputs/apk/{}", mode_folder)
768 };
769 println!("š Folder output: {}", output_dir);
770 } else {
771 println!("\nā Gradle build gagal.");
772 }
773 } else {
774 println!("ā Gagal mengeksekusi Gradle wrapper. Pastikan Java dan Gradle wrapper terkonfigurasi dengan benar.");
775 }
776 }
777}
778
779pub fn run_native(run_android: bool, run_desktop: bool) {
781 if run_android {
782 println!("š Memulai RustBasic Android Wrapper...");
783
784 let os = std::env::consts::OS;
785 let home = std::env::var("HOME").unwrap_or_default();
786 let android_home = if let Ok(val) = std::env::var("ANDROID_HOME") {
787 val
788 } else {
789 if os == "macos" {
790 format!("{}/Library/Android/sdk", home)
791 } else {
792 format!("{}/Android/Sdk", home)
793 }
794 };
795
796 let mut custom_java_home = None;
797 if cfg!(target_os = "macos") {
799 let mac_studio_jdk = "/Applications/Android Studio.app/Contents/jbr/Contents/Home";
800 if Path::new(mac_studio_jdk).exists() {
801 custom_java_home = Some(mac_studio_jdk.to_string());
802 }
803 } else if cfg!(target_os = "windows") {
804 let win_paths = [
805 "C:\\Program Files\\Android\\Android Studio\\jbr",
806 "C:\\Program Files\\Android\\Android Studio\\jre",
807 ];
808 for path in &win_paths {
809 if Path::new(path).exists() {
810 custom_java_home = Some(path.to_string());
811 break;
812 }
813 }
814 } else {
815 let unix_paths = [
817 "/opt/android-studio/jbr",
818 "/opt/android-studio/jre",
819 "/snap/android-studio/current/jbr",
820 "/snap/android-studio/current/jre",
821 "/usr/local/android-studio/jbr",
822 "/usr/local/android-studio/jre",
823 "/usr/lib/jvm/default-java",
824 ];
825 for path in &unix_paths {
826 if Path::new(path).exists() {
827 custom_java_home = Some(path.to_string());
828 break;
829 }
830 }
831 }
832
833 let adb = get_adb_bin();
834 let mut devices = get_adb_devices();
835 if devices.is_empty() {
836 println!("š± Perangkat Android atau emulator tidak terdeteksi aktif.");
837 let mut emulator_bin = format!("{}/emulator/emulator", android_home);
838 if !Path::new(&emulator_bin).exists() {
839 let fallback = format!("{}/tools/emulator", android_home);
840 if Path::new(&fallback).exists() {
841 emulator_bin = fallback;
842 } else {
843 emulator_bin = "emulator".to_string();
844 }
845 }
846
847 let avd_output = Command::new(&emulator_bin).arg("-list-avds").output();
848 if let Ok(avd_out) = avd_output {
849 let avds_str = String::from_utf8_lossy(&avd_out.stdout);
850 if let Some(avd_name) = avds_str.lines().next() {
851 println!("š Menyalakan emulator AVD: {}...", avd_name);
852 let _ = Command::new(&emulator_bin)
853 .args(["-avd", avd_name])
854 .stdout(std::process::Stdio::null())
855 .stderr(std::process::Stdio::null())
856 .spawn();
857
858 println!("ā³ Menunggu emulator menyala dan terdeteksi adb...");
859 let _ = Command::new(&adb).arg("wait-for-device").status();
860 println!("ā
Emulator berhasil aktif!");
861 std::thread::sleep(std::time::Duration::from_secs(3));
862 devices = get_adb_devices();
863 }
864 }
865 }
866
867 let (device_id, device_name) = if devices.len() == 1 {
868 let d = devices[0].clone();
869 println!("š± Menggunakan perangkat tunggal: {} ({})", d.1, d.0);
870 d
871 } else if devices.len() > 1 {
872 println!("š± Terdeteksi beberapa perangkat Android. Silakan pilih target:");
873 for (idx, d) in devices.iter().enumerate() {
874 println!(" [{}] {} ({})", idx + 1, d.1, d.0);
875 }
876 let choice = crate::utils::prompt_choice("š Pilih nomor perangkat: ", 1, devices.len());
877 devices[choice - 1].clone()
878 } else {
879 println!("ā Error: Tidak ada perangkat Android terhubung.");
880 return;
881 };
882
883 if !compile_jni_libraries() {
885 return;
886 }
887
888 let local_props = Path::new("native/android/local.properties");
890 if !local_props.exists() {
891 if let Ok(mut file) = fs::File::create(local_props) {
892 let _ = writeln!(file, "sdk.dir={}", android_home);
893 }
894 }
895
896 println!("šØ Membangun debug APK menggunakan Gradle...");
898 let gradlew_bin = if cfg!(target_os = "windows") { "gradlew.bat" } else { "./gradlew" };
899 let mut gradle_cmd = Command::new(gradlew_bin);
900 gradle_cmd.arg("assembleDebug");
901 gradle_cmd.current_dir("native/android");
902
903 if let Some(jh) = &custom_java_home {
904 gradle_cmd.env("JAVA_HOME", jh);
905 }
906
907 let gradle_status = gradle_cmd.status();
908 if gradle_status.is_err() || !gradle_status.unwrap().success() {
909 println!("ā Gradle build assembleDebug gagal.");
910 return;
911 }
912
913 println!("šØ Memasang APK ke perangkat {} ({})...", device_name, device_id);
915 let install_status = Command::new(&adb)
916 .args(["-s", &device_id, "install", "-r", "native/android/app/build/outputs/apk/debug/app-debug.apk"])
917 .status();
918
919 if install_status.is_err() || !install_status.unwrap().success() {
920 println!("ā Gagal memasang APK ke device.");
921 return;
922 }
923
924 let vite_port = "5173"; let reverse_status = Command::new(&adb)
927 .args(["-s", &device_id, "reverse", &format!("tcp:{}", vite_port), &format!("tcp:{}", vite_port)])
928 .status();
929 if reverse_status.is_err() {
930 println!("ā ļø Warning: Gagal melakukan adb reverse port {}", vite_port);
931 }
932
933 println!("š Membuka aplikasi di perangkat {}...", device_name);
935 let _ = Command::new(&adb)
936 .args(["-s", &device_id, "logcat", "-c"])
937 .status();
938
939 let _ = Command::new(&adb)
940 .args(["-s", &device_id, "shell", "am", "start", "-n", "com.rustbasic.mobile/com.rustbasic.mobile.MainActivity"])
941 .status();
942
943 println!("š Menampilkan log realtime dari perangkat {} (Tekan Ctrl+C untuk keluar)...", device_name);
944 let mut logcat_cmd = Command::new(&adb);
945 logcat_cmd.args(["-s", &device_id, "logcat", "-s", "RustBasicServer"]);
946 let mut child = logcat_cmd.spawn().expect("Gagal menjalankan adb logcat");
947 let _ = child.wait();
948 } else if run_desktop {
949 println!("š Memulai RustBasic Desktop Wrapper...");
950 let mut cmd = Command::new("cargo");
951 cmd.args(["run", "--bin", "rustbasic-desktop", "--features", "desktop"]);
952 let status = cmd.status();
953 match status {
954 Ok(s) if s.success() => {}
955 _ => {
956 println!("ā Gagal menjalankan Desktop Wrapper.");
957 }
958 }
959 }
960}
961
962fn prompt_string(prompt: &str, default: &str) -> String {
963 print!("{}", prompt);
964 let _ = std::io::stdout().flush();
965 let mut input = String::new();
966 if std::io::stdin().read_line(&mut input).is_ok() {
967 let trimmed = input.trim();
968 if trimmed.is_empty() {
969 default.to_string()
970 } else {
971 trimmed.to_string()
972 }
973 } else {
974 default.to_string()
975 }
976}
977
978pub fn deploy_interactive() {
979 println!("\n{}", "š RustBasic Docker Deploy CLI".magenta().bold());
980 println!("{}", "------------------------------".magenta());
981
982 let image_name = prompt_string("š Masukkan Nama/Tag Docker Image (default: rustbasic:latest): ", "rustbasic:latest");
984
985 let container_name = std::env::var("DOCKER_CONTAINER_NAME").unwrap_or_else(|_| {
987 let base_name = image_name
988 .split(':')
989 .next()
990 .unwrap_or("rustbasic")
991 .split('/')
992 .last()
993 .unwrap_or("rustbasic")
994 .to_lowercase();
995 format!("{}-app", base_name)
996 });
997
998 let inspect = Command::new("docker")
1000 .args(["image", "inspect", &image_name])
1001 .stdout(std::process::Stdio::null())
1002 .stderr(std::process::Stdio::null())
1003 .status();
1004
1005 match inspect {
1006 Ok(status) if status.success() => {}
1007 _ => {
1008 println!("{}", format!("ā ļø Peringatan: Image '{}' tidak ditemukan lokal.", image_name).yellow());
1009 let proceed = prompt_string("š Tetap lanjutkan proses ekspor? (y/n) [default: n]: ", "n");
1010 if proceed.to_lowercase() != "y" {
1011 println!("ā Proses dihentikan.");
1012 return;
1013 }
1014 }
1015 }
1016
1017 println!("š¦ Mengekspor Docker image '{}' ke 'rustbasic.tar'...", image_name);
1019 let save_status = Command::new("docker")
1020 .args(["save", "-o", "rustbasic.tar", &image_name])
1021 .status();
1022
1023 match save_status {
1024 Ok(status) if status.success() => {
1025 println!("{}", "ā
Image berhasil diekspor ke rustbasic.tar.".green());
1026 }
1027 _ => {
1028 println!("{}", "ā Gagal mengekspor Docker image.".red().bold());
1029 return;
1030 }
1031 }
1032
1033 println!("\n{}", "š Konfigurasi Pengiriman ke Server".cyan().bold());
1035 println!("{}", "-----------------------------------".cyan());
1036 let ssh_user = prompt_string("š Masukkan SSH Username Server (contoh: root) [default: root]: ", "root");
1037 let ssh_ip = prompt_string("š Masukkan IP Address Server: ", "");
1038 if ssh_ip.is_empty() {
1039 println!("{}", "ā IP Address server tidak boleh kosong.".red().bold());
1040 let _ = fs::remove_file("rustbasic.tar");
1041 return;
1042 }
1043 let ssh_port = prompt_string("š Masukkan SSH Port Server (default: 22): ", "22");
1044 let dest_dir = prompt_string("š Masukkan Folder Tujuan di Server (default: ~/app): ", "~/app");
1045 let server_port = prompt_string("š Masukkan Port Server Mapping (contoh: 80:4000) [default: 80:4000]: ", "80:4000");
1046 let env_file = prompt_string("š Masukkan File Env yang akan dikirim (default: .env): ", ".env");
1047
1048 println!("\nš Menyiapkan folder tujuan di server...");
1049 let mkdir_status = Command::new("ssh")
1050 .args([
1051 "-p", &ssh_port,
1052 &format!("{}@{}", ssh_user, ssh_ip),
1053 &format!("mkdir -p {}", dest_dir)
1054 ])
1055 .status();
1056
1057 match mkdir_status {
1058 Ok(status) if status.success() => {}
1059 _ => {
1060 println!("{}", "ā Gagal terhubung ke server menggunakan SSH.".red().bold());
1061 let _ = fs::remove_file("rustbasic.tar");
1062 return;
1063 }
1064 }
1065
1066 println!("š Mengirimkan berkas rustbasic.tar & {} ke server...", env_file);
1067 let scp_status = Command::new("scp")
1068 .args([
1069 "-P", &ssh_port,
1070 "rustbasic.tar", &env_file,
1071 &format!("{}@{}:{}", ssh_user, ssh_ip, dest_dir)
1072 ])
1073 .status();
1074
1075 let _ = fs::remove_file("rustbasic.tar");
1077
1078 if let Ok(status) = scp_status {
1079 if !status.success() {
1080 println!("{}", "ā Gagal mengirimkan berkas via SCP.".red().bold());
1081 return;
1082 }
1083 } else {
1084 println!("{}", "ā Gagal menjalankan SCP.".red().bold());
1085 return;
1086 }
1087
1088 println!("{}", "ā
Pengiriman berkas berhasil!".green());
1089
1090 let auto_run = prompt_string("\nš Apakah Anda ingin langsung menjalankan container di server secara otomatis? (y/n) [default: y]: ", "y");
1092 if auto_run.to_lowercase() == "y" || auto_run.is_empty() {
1093 println!("\nš Memuat image di server (docker load)...");
1094 let load_status = Command::new("ssh")
1095 .args([
1096 "-p", &ssh_port,
1097 &format!("{}@{}", ssh_user, ssh_ip),
1098 &format!("docker load -i {}/rustbasic.tar", dest_dir)
1099 ])
1100 .status();
1101
1102 match load_status {
1103 Ok(status) if status.success() => {
1104 println!("{}", "ā
Image berhasil dimuat di server.".green());
1105 }
1106 _ => {
1107 println!("{}", "ā Gagal memuat image di server.".red().bold());
1108 return;
1109 }
1110 }
1111
1112 println!("š Menghentikan & menghapus container lama '{}' jika ada...", container_name);
1113 let stop_status = Command::new("ssh")
1114 .args([
1115 "-p", &ssh_port,
1116 &format!("{}@{}", ssh_user, ssh_ip),
1117 &format!("docker stop {} || true && docker rm {} || true", container_name, container_name)
1118 ])
1119 .status();
1120
1121 if let Err(e) = stop_status {
1122 println!("ā ļø Peringatan saat membersihkan container lama: {}", e);
1123 }
1124
1125 println!("š Menjalankan container baru '{}'...", container_name);
1126 let run_cmd = format!(
1127 "docker run -d --name {} -p {} --restart unless-stopped --env-file {}/.env {}",
1128 container_name, server_port, dest_dir, image_name
1129 );
1130 let run_status = Command::new("ssh")
1131 .args([
1132 "-p", &ssh_port,
1133 &format!("{}@{}", ssh_user, ssh_ip),
1134 &run_cmd
1135 ])
1136 .status();
1137
1138 match run_status {
1139 Ok(status) if status.success() => {
1140 println!("{}", format!("š Container '{}' berhasil dijalankan di server!", container_name).green().bold());
1141 }
1142 _ => {
1143 println!("{}", "ā Gagal menjalankan container di server.".red().bold());
1144 return;
1145 }
1146 }
1147
1148 println!("š Membersihkan file tar di server...");
1149 let rm_status = Command::new("ssh")
1150 .args([
1151 "-p", &ssh_port,
1152 &format!("{}@{}", ssh_user, ssh_ip),
1153 &format!("rm {}/rustbasic.tar", dest_dir)
1154 ])
1155 .status();
1156
1157 if let Err(e) = rm_status {
1158 println!("ā ļø Peringatan saat membersihkan file tar di server: {}", e);
1159 }
1160
1161 println!("\n{}", "š Deployment selesai!".green().bold());
1162 println!("{}", "--------------------------------------------------------".green());
1163 println!("Untuk melihat log aplikasi di server, jalankan:");
1164 println!("ssh -p {} {}@{} \"docker logs -f {}\"", ssh_port, ssh_user, ssh_ip, container_name);
1165 println!("{}", "--------------------------------------------------------".green());
1166 } else {
1167 println!("\n{}", "š„ļø Langkah Selanjutnya di Server Anda:".cyan().bold());
1168 println!("{}", "--------------------------------------------------------".green());
1169 println!("1. Hubungkan ke server via SSH:");
1170 println!(" ssh -p {} {}@{}", ssh_port, ssh_user, ssh_ip);
1171 println!("");
1172 println!("2. Masuk ke folder tujuan:");
1173 println!(" cd {}", dest_dir);
1174 println!("");
1175 println!("3. Muat (load) image Docker dari berkas tar:");
1176 println!(" docker load -i rustbasic.tar");
1177 println!("");
1178 println!("4. Jalankan container dengan fitur auto-restart:");
1179 println!(" docker run -d --name {} -p {} --restart unless-stopped --env-file .env {}", container_name, server_port, image_name);
1180 println!("");
1181 println!("5. Hapus file tar di server untuk menghemat disk:");
1182 println!(" rm rustbasic.tar");
1183 println!("{}", "--------------------------------------------------------".green());
1184 }
1185}
1186
1187fn get_adb_bin() -> String {
1188 if let Ok(output) = Command::new("adb").arg("devices").output() {
1190 if output.status.success() {
1191 return "adb".to_string();
1192 }
1193 }
1194
1195 let os = std::env::consts::OS;
1197 let home = std::env::var("HOME").unwrap_or_default();
1198 let android_home = if let Ok(val) = std::env::var("ANDROID_HOME") {
1199 val
1200 } else {
1201 if os == "macos" {
1202 format!("{}/Library/Android/sdk", home)
1203 } else if os == "windows" {
1204 let local_app_data = std::env::var("LOCALAPPDATA").unwrap_or_default();
1205 format!("{}\\Android\\Sdk", local_app_data)
1206 } else {
1207 format!("{}/Android/Sdk", home)
1208 }
1209 };
1210
1211 let adb_name = if os == "windows" { "adb.exe" } else { "adb" };
1212 let path = Path::new(&android_home).join("platform-tools").join(adb_name);
1213 if path.exists() {
1214 path.display().to_string()
1215 } else {
1216 "adb".to_string() }
1218}
1219
1220fn get_adb_devices() -> Vec<(String, String)> {
1221 let adb = get_adb_bin();
1222 let output = Command::new(&adb).arg("devices").output();
1223 let mut devices = Vec::new();
1224 if let Ok(out) = output {
1225 let stdout = String::from_utf8_lossy(&out.stdout);
1226 for line in stdout.lines() {
1227 let line = line.trim();
1228 if line.is_empty() || line.starts_with("List of devices") {
1229 continue;
1230 }
1231 let parts: Vec<&str> = line.split_whitespace().collect();
1232 if parts.len() >= 2 && parts[1] == "device" {
1233 let device_id = parts[0].to_string();
1234 let model_out = Command::new(&adb)
1235 .args(["-s", &device_id, "shell", "getprop", "ro.product.model"])
1236 .output();
1237 let model = if let Ok(m_out) = model_out {
1238 String::from_utf8_lossy(&m_out.stdout).trim().to_string()
1239 } else {
1240 "Unknown Device".to_string()
1241 };
1242 devices.push((device_id, model));
1243 }
1244 }
1245 }
1246 devices
1247}
1248
1249fn compile_jni_libraries() -> bool {
1250 println!("š Building Rust library for Android (Native Rust implementation)...");
1251
1252 let _ = Command::new("rustup")
1253 .args(["target", "add", "aarch64-linux-android", "armv7-linux-androideabi", "x86_64-linux-android"])
1254 .status();
1255
1256 let home = std::env::var("HOME").unwrap_or_default();
1257 let android_ndk_home = if let Ok(val) = std::env::var("ANDROID_NDK_HOME") {
1258 val
1259 } else {
1260 let mac_ndk = format!("{}/Library/Android/sdk/ndk", home);
1261 if Path::new(&mac_ndk).exists() {
1262 if let Ok(entries) = fs::read_dir(&mac_ndk) {
1263 let mut paths: Vec<_> = entries.flatten().map(|e| e.path()).collect();
1264 paths.sort();
1265 if let Some(highest) = paths.last() {
1266 highest.display().to_string()
1267 } else {
1268 "".to_string()
1269 }
1270 } else {
1271 "".to_string()
1272 }
1273 } else {
1274 "".to_string()
1275 }
1276 };
1277
1278 if android_ndk_home.is_empty() {
1279 println!("ā Error: ANDROID_NDK_HOME is not set. Please set ANDROID_NDK_HOME.");
1280 return false;
1281 }
1282
1283 println!("Using NDK: {}", android_ndk_home);
1284
1285 let os = std::env::consts::OS;
1286 let toolchain_sub = if os == "macos" { "darwin-x86_64" } else { "linux-x86_64" };
1287 let toolchain_bin_path = Path::new(&android_ndk_home)
1288 .join("toolchains/llvm/prebuilt")
1289 .join(toolchain_sub)
1290 .join("bin");
1291
1292 if !toolchain_bin_path.exists() {
1293 println!("ā Error: Toolchain bin path not found: {}", toolchain_bin_path.display());
1294 return false;
1295 }
1296
1297 let sqlite_version = "3450100";
1298 let sqlite_dir = format!("target/sqlite-amalgamation-{}", sqlite_version);
1299 if !Path::new(&sqlite_dir).exists() {
1300 println!("š„ Downloading SQLite source amalgamation...");
1301 fs::create_dir_all("target").ok();
1302
1303 let zip_path = "target/sqlite.zip";
1304 let sqlite_url = format!("https://www.sqlite.org/2024/sqlite-amalgamation-{}.zip", sqlite_version);
1305
1306 let curl_status = Command::new("curl")
1307 .args(["-sSLo", zip_path, &sqlite_url])
1308 .status();
1309
1310 if curl_status.is_err() || !curl_status.unwrap().success() {
1311 println!("ā Gagal men-download SQLite source.");
1312 return false;
1313 }
1314
1315 let unzip_status = Command::new("unzip")
1316 .args(["-q", zip_path, "-d", "target/"])
1317 .status();
1318
1319 let _ = fs::remove_file(zip_path);
1320
1321 if unzip_status.is_err() || !unzip_status.unwrap().success() {
1322 println!("ā Gagal mengekstrak SQLite source.");
1323 return false;
1324 }
1325 }
1326
1327 let targets = vec![
1328 ("aarch64-linux-android", "arm64-v8a", "aarch64-linux-android21-clang"),
1329 ("armv7-linux-androideabi", "armeabi-v7a", "armv7a-linux-androideabi21-clang"),
1330 ("x86_64-linux-android", "x86_64", "x86_64-linux-android21-clang"),
1331 ];
1332
1333 let jnilibs_dir = "native/android/app/src/main/jniLibs";
1334
1335 for (target, arch, clang_name) in targets {
1336 println!("šØ Preparing SQLite static library for {}...", target);
1337
1338 let clang_path = toolchain_bin_path.join(clang_name);
1339 let ar_path = toolchain_bin_path.join("llvm-ar");
1340
1341 if !clang_path.exists() {
1342 println!("ā Error: Compiler not found: {}", clang_path.display());
1343 return false;
1344 }
1345
1346 let sqlite_out = format!("target/{}/sqlite", target);
1347 fs::create_dir_all(&sqlite_out).ok();
1348
1349 let libsqlite3_a = format!("{}/libsqlite3.a", sqlite_out);
1350 if !Path::new(&libsqlite3_a).exists() {
1351 println!(" Compiling SQLite static lib for {}...", target);
1352 let sqlite3_o = format!("{}/sqlite3.o", sqlite_out);
1353 let sqlite3_c = format!("{}/sqlite3.c", sqlite_dir);
1354
1355 let compile_status = Command::new(&clang_path)
1356 .args(["-O2", "-c", &sqlite3_c, "-o", &sqlite3_o])
1357 .status();
1358
1359 if compile_status.is_err() || !compile_status.unwrap().success() {
1360 println!("ā Gagal mengompilasi sqlite3.o");
1361 return false;
1362 }
1363
1364 let archive_status = Command::new(&ar_path)
1365 .args(["rcs", &libsqlite3_a, &sqlite3_o])
1366 .status();
1367
1368 if archive_status.is_err() || !archive_status.unwrap().success() {
1369 println!("ā Gagal mengarsip libsqlite3.a");
1370 return false;
1371 }
1372 }
1373
1374 println!("šØ Compiling Rust library for {}...", target);
1375 let mut cargo_cmd = Command::new("cargo");
1376 cargo_cmd.args(["build", "--target", target, "--release"]);
1377
1378 let clang_path_str = clang_path.display().to_string();
1379 let ar_path_str = ar_path.display().to_string();
1380
1381 let target_upper = target.replace("-", "_").to_uppercase();
1382 let linker_env = format!("CARGO_TARGET_{}_LINKER", target_upper);
1383 let cc_env = format!("CC_{}", target.replace("-", "_"));
1384 let ar_env = format!("AR_{}", target.replace("-", "_"));
1385
1386 cargo_cmd.env(&linker_env, &clang_path_str);
1387 cargo_cmd.env(&cc_env, &clang_path_str);
1388 cargo_cmd.env(&ar_env, &ar_path_str);
1389
1390 let cargo_status = cargo_cmd.status();
1391 if cargo_status.is_err() || !cargo_status.unwrap().success() {
1392 println!("ā Gagal mengompilasi library Rust untuk target {}", target);
1393 return false;
1394 }
1395
1396 let dest_dir = format!("{}/{}", jnilibs_dir, arch);
1397 fs::create_dir_all(&dest_dir).ok();
1398
1399 let src_so = format!("target/{}/release/librustbasic.so", target);
1400 let dest_so = format!("{}/librustbasic_mobile.so", dest_dir);
1401
1402 if let Err(e) = fs::copy(&src_so, &dest_so) {
1403 println!("ā Gagal menyalin {}: {}", src_so, e);
1404 return false;
1405 }
1406 }
1407
1408 println!("ā
Android JNI libraries built successfully!");
1409 true
1410}
1411
1412fn get_host_arch() -> String {
1413 if cfg!(any(target_os = "macos", target_os = "linux")) {
1415 if let Ok(output) = std::process::Command::new("uname").arg("-m").output() {
1416 let arch = String::from_utf8_lossy(&output.stdout).trim().to_lowercase();
1417 if !arch.is_empty() {
1418 if arch.contains("aarch64") || arch.contains("arm64") {
1419 return "aarch64".to_string();
1420 } else if arch.contains("x86_64") || arch.contains("amd64") {
1421 return "x86_64".to_string();
1422 }
1423 return arch;
1424 }
1425 }
1426 }
1427
1428 if cfg!(target_os = "windows") {
1430 if let Ok(arch) = std::env::var("PROCESSOR_ARCHITECTURE") {
1431 let arch_lower = arch.to_lowercase();
1432 if arch_lower.contains("amd64") || arch_lower.contains("x64") {
1433 return "x86_64".to_string();
1434 } else if arch_lower.contains("arm64") {
1435 return "aarch64".to_string();
1436 }
1437 }
1438 }
1439
1440 let const_arch = std::env::consts::ARCH;
1442 if const_arch == "x86_64" {
1443 "x86_64".to_string()
1444 } else if const_arch == "aarch64" {
1445 "aarch64".to_string()
1446 } else {
1447 const_arch.to_string()
1448 }
1449}