1use anyhow::{Result, anyhow};
2use colored::*;
3use std::path::{Path, PathBuf};
4use std::process::Command;
5use zoi_core::utils;
6
7pub fn run(
8 package_file: &Path,
9 build_type: Option<&str>,
10 platforms: &[String],
11 sign_key: Option<String>,
12 output_dir: Option<&Path>,
13 version_override: Option<&str>,
14 sub_packages: Option<Vec<String>>,
15 image: &str,
16 fakeroot: bool,
17 install_deps: bool,
18 test: bool,
19) -> Result<()> {
20 println!("{} Building package using Docker...", "::".bold().blue());
21 println!("Image: {}", image.cyan());
22
23 if !utils::command_exists("docker") {
24 return Err(anyhow!(
25 "Docker is not installed or not in PATH. Please install Docker to use this method."
26 ));
27 }
28
29 let abs_package_file = package_file.canonicalize()?;
30 let package_dir = abs_package_file
31 .parent()
32 .ok_or_else(|| anyhow!("Could not get parent directory of package file"))?;
33
34 let abs_output_dir = if let Some(dir) = output_dir {
35 if !dir.exists() {
36 std::fs::create_dir_all(dir)?;
37 }
38 dir.canonicalize()?
39 } else {
40 package_dir.to_path_buf()
41 };
42
43 let container_workdir = "/work";
44 let container_output_dir = "/output";
45
46 let mut docker_args = vec![
47 "run".to_string(),
48 "--rm".to_string(),
49 "-v".to_string(),
50 format!("{}:{}", package_dir.display(), container_workdir),
51 "-v".to_string(),
52 format!("{}:{}", abs_output_dir.display(), container_output_dir),
53 "-w".to_string(),
54 container_workdir.to_string(),
55 ];
56
57 if let Ok(user_id) = Command::new("id").arg("-u").output() {
58 let uid = String::from_utf8_lossy(&user_id.stdout).trim().to_string();
59 if let Ok(group_id) = Command::new("id").arg("-g").output() {
60 let gid = String::from_utf8_lossy(&group_id.stdout).trim().to_string();
61 docker_args.push("--user".to_string());
62 docker_args.push(format!("{}:{}", uid, gid));
63 }
64 }
65
66 if sign_key.is_some() {
67 let host_gpg_home = std::env::var("GNUPGHOME")
68 .map(PathBuf::from)
69 .unwrap_or_else(|_| {
70 utils::get_user_home()
71 .map(|h| h.join(".gnupg"))
72 .unwrap_or_default()
73 });
74
75 if host_gpg_home.exists() {
76 let container_gpg_home = "/gpg_home";
77 docker_args.push("-v".to_string());
78 docker_args.push(format!(
79 "{}:{}",
80 host_gpg_home.display(),
81 container_gpg_home
82 ));
83 docker_args.push("-e".to_string());
84 docker_args.push(format!("GNUPGHOME={}", container_gpg_home));
85 }
86 }
87
88 if let Ok(password) = std::env::var("GPG_PASSWORD") {
89 docker_args.push("-e".to_string());
90 docker_args.push(format!("GPG_PASSWORD={}", password));
91 }
92
93 docker_args.push(image.to_string());
94
95 let package_filename = abs_package_file
96 .file_name()
97 .ok_or_else(|| anyhow!("Invalid package file name"))?
98 .to_string_lossy();
99
100 let mut inner_cmd = format!(
101 "if ! command -v sudo >/dev/null 2>&1 && [ \"$(id -u)\" -eq 0 ]; then \
102 if command -v pacman >/dev/null 2>&1; then pacman -Sy --noconfirm sudo gnupg; \
103 elif command -v apt-get >/dev/null 2>&1; then apt-get update && apt-get install -y sudo gnupg; \
104 elif command -v dnf >/dev/null 2>&1; then dnf install -y sudo gnupg; \
105 elif command -v apk >/dev/null 2>&1; then apk add --update sudo gnupg; fi; \
106 fi && \
107 if command -v pacman >/dev/null 2>&1; then pacman -Sy --noconfirm base-devel git; \
108 elif command -v apt-get >/dev/null 2>&1; then apt-get update && apt-get install -y build-essential git; \
109 elif command -v dnf >/dev/null 2>&1; then dnf install -y @development-tools git; \
110 elif command -v apk >/dev/null 2>&1; then apk add --update build-base git; fi && \
111 curl -fsSL https://zillowe.pages.dev/scripts/zoi/install.sh | bash && \
112 export PATH=\"$HOME/.local/bin:$PATH\" && \
113 zoi sync && \
114 zoi package build {} --output-dir {}",
115 package_filename, container_output_dir
116 );
117
118 if let Some(bt) = build_type {
119 inner_cmd.push_str(&format!(" --type {}", bt));
120 }
121
122 for p in platforms {
123 inner_cmd.push_str(&format!(" --platform {}", p));
124 }
125
126 if let Some(sk) = sign_key {
127 inner_cmd.push_str(&format!(" --sign {}", sk));
128 }
129
130 if let Some(v) = version_override {
131 inner_cmd.push_str(&format!(" --version-override {}", v));
132 }
133
134 if let Some(subs) = sub_packages {
135 for s in subs {
136 inner_cmd.push_str(&format!(" --sub {}", s));
137 }
138 }
139
140 if fakeroot {
141 inner_cmd.push_str(" --fakeroot");
142 }
143
144 if install_deps {
145 inner_cmd.push_str(" --install-deps");
146 }
147
148 if test {
149 inner_cmd.push_str(" --test");
150 }
151
152 docker_args.push("bash".to_string());
153 docker_args.push("-c".to_string());
154 docker_args.push(inner_cmd);
155
156 println!("Running docker command: {}", "docker".cyan());
157 let status = Command::new("docker").args(&docker_args).status()?;
158
159 if !status.success() {
160 return Err(anyhow!(
161 "Docker build failed with exit code {:?}",
162 status.code()
163 ));
164 }
165
166 println!("{}", "Docker build successful!".green());
167
168 Ok(())
169}