1use std::path::Path;
9
10use anyhow::{Result, anyhow};
11
12pub fn run(
21 package_file: &Path,
22 build_type: Option<&str>,
23 platforms: &[String],
24 sign_key: Option<String>,
25 output_dir: Option<&Path>,
26 version_override: Option<&str>,
27 sub_packages: Option<Vec<String>>,
28 fakeroot: bool,
29 install_deps: bool,
30 test: bool
31) -> Result<()> {
32 #[cfg(not(target_os = "linux"))]
33 {
34 let _ = (
35 package_file,
36 build_type,
37 platforms,
38 sign_key,
39 output_dir,
40 version_override,
41 sub_packages,
42 fakeroot,
43 install_deps,
44 test
45 );
46 return Err(anyhow!(
47 "Bubblewrap ('bwrap') is only supported on Linux."
48 ));
49 }
50
51 #[cfg(target_os = "linux")]
52 {
53 use std::fmt::Write;
54 use std::path::PathBuf;
55 use std::process::Command;
56
57 use colored::Colorize;
58 use zoi_core::utils;
59
60 println!(
61 "{} Building package using Bubblewrap sandbox...",
62 "::".bold().blue()
63 );
64
65 if !utils::command_exists("bwrap") {
66 return Err(anyhow!(
67 "Bubblewrap ('bwrap') is not installed or not in PATH. Please \
68 install it to use this method."
69 ));
70 }
71
72 let abs_package_file = package_file.canonicalize()?;
73 let package_dir = abs_package_file.parent().ok_or_else(|| {
74 anyhow!("Could not get parent directory of package file")
75 })?;
76
77 let abs_output_dir = if let Some(dir) = output_dir {
78 if !dir.exists() {
79 std::fs::create_dir_all(dir)?;
80 }
81 dir.canonicalize()?
82 } else {
83 package_dir.to_path_buf()
84 };
85
86 let container_workdir = "/work";
88 let container_output_dir = "/output";
89
90 let zoi_exe = std::env::current_exe()?;
91 let zoi_exe_dir = zoi_exe
92 .parent()
93 .ok_or_else(|| anyhow!("Could not get zoi executable directory"))?;
94
95 let home_dir = zoi_core::utils::get_user_home()
96 .ok_or_else(|| anyhow!("Could not get home directory"))?;
97 let zoi_home = home_dir.join(".zoi");
98
99 let package_filename = abs_package_file
100 .file_name()
101 .ok_or_else(|| anyhow!("Invalid package file name"))?
102 .to_string_lossy();
103
104 let mut inner_cmd = format!(
106 "zoi package build {package_filename} --output-dir \
107 {container_output_dir} --method native"
108 );
109
110 if let Some(bt) = build_type {
111 let _ = write!(inner_cmd, " --type {bt}");
112 }
113
114 for p in platforms {
115 let _ = write!(inner_cmd, " --platform {p}");
116 }
117
118 if let Some(sk) = sign_key {
119 let _ = write!(inner_cmd, " --sign {sk}");
120 }
121
122 if let Some(v) = version_override {
123 let _ = write!(inner_cmd, " --version-override {v}");
124 }
125
126 if let Some(subs) = sub_packages {
127 for s in subs {
128 let _ = write!(inner_cmd, " --sub {s}");
129 }
130 }
131
132 if fakeroot {
133 inner_cmd.push_str(" --fakeroot");
134 }
135
136 if install_deps {
137 inner_cmd.push_str(" --install-deps");
138 }
139
140 if test {
141 inner_cmd.push_str(" --test");
142 }
143
144 let sysroot = zoi_core::sysroot::get_sysroot();
146
147 let mut envs = std::collections::HashMap::new();
148 envs.insert(
149 "PATH".to_string(),
150 "/zoi_bin:/usr/bin:/bin:/usr/sbin:/sbin".to_string()
151 );
152 envs.insert("ZOI_SKIP_LOCK".to_string(), "1".to_string());
153 envs.insert("HOME".to_string(), home_dir.display().to_string());
154
155 let status = if let Some(root) = &sysroot {
156 println!(
157 "{} Isolated build using sysroot: {}",
158 "::".bold().yellow(),
159 root.display()
160 );
161
162 let extra_binds = vec![
163 (package_dir.to_path_buf(), PathBuf::from(container_workdir)),
164 (abs_output_dir.clone(), PathBuf::from(container_output_dir)),
165 (zoi_exe_dir.to_path_buf(), PathBuf::from("/zoi_bin")),
166 ];
167
168 let mut cmd = zoi_sandbox::wrap_command_in_root(
169 root,
170 &PathBuf::from("/bin/bash"),
171 &["-c".to_string(), inner_cmd],
172 &envs,
173 &extra_binds,
174 fakeroot
175 )?;
176 cmd.status()?
177 } else {
178 let mut bwrap_args = vec![
180 "--unshare-all".to_string(),
181 "--share-net".to_string(),
182 "--hostname".to_string(),
183 "zoi-build".to_string(),
184 "--dev".to_string(),
185 "/dev".to_string(),
186 "--proc".to_string(),
187 "/proc".to_string(),
188 "--tmpfs".to_string(),
189 "/tmp".to_string(),
190 "--tmpfs".to_string(),
191 "/run".to_string(),
192 "--tmpfs".to_string(),
193 "/var".to_string(),
194 "--ro-bind".to_string(),
195 "/usr".to_string(),
196 "/usr".to_string(),
197 "--symlink".to_string(),
198 "/usr/bin".to_string(),
199 "/bin".to_string(),
200 "--symlink".to_string(),
201 "/usr/lib".to_string(),
202 "/lib".to_string(),
203 "--symlink".to_string(),
204 "/usr/lib64".to_string(),
205 "/lib64".to_string(),
206 "--symlink".to_string(),
207 "/usr/sbin".to_string(),
208 "/sbin".to_string(),
209 "--ro-bind".to_string(),
210 "/etc".to_string(),
211 "/etc".to_string(),
212 "--bind".to_string(),
213 package_dir.display().to_string(),
214 container_workdir.to_string(),
215 "--bind".to_string(),
216 abs_output_dir.display().to_string(),
217 container_output_dir.to_string(),
218 "--ro-bind".to_string(),
219 zoi_exe_dir.display().to_string(),
220 "/zoi_bin".to_string(),
221 "--chdir".to_string(),
222 container_workdir.to_string(),
223 "--setenv".to_string(),
224 "PATH".to_string(),
225 "/zoi_bin:/usr/bin:/bin:/usr/sbin:/sbin".to_string(),
226 "--setenv".to_string(),
227 "ZOI_SKIP_LOCK".to_string(),
228 "1".to_string(),
229 ];
230
231 if zoi_home.exists() {
232 bwrap_args.push("--bind".to_string());
233 bwrap_args.push(zoi_home.display().to_string());
234 bwrap_args.push(zoi_home.display().to_string());
235 }
236
237 let system_zoi = Path::new("/var/lib/zoi");
238 if system_zoi.exists() {
239 bwrap_args.push("--bind".to_string());
240 bwrap_args.push(system_zoi.display().to_string());
241 bwrap_args.push(system_zoi.display().to_string());
242 }
243
244 if fakeroot {
245 bwrap_args.push("--uid".to_string());
246 bwrap_args.push("0".to_string());
247 bwrap_args.push("--gid".to_string());
248 bwrap_args.push("0".to_string());
249 } else {
250 let uid = nix::unistd::getuid().as_raw();
251 let gid = nix::unistd::getgid().as_raw();
252 bwrap_args.push("--uid".to_string());
253 bwrap_args.push(uid.to_string());
254 bwrap_args.push("--gid".to_string());
255 bwrap_args.push(gid.to_string());
256 }
257
258 bwrap_args.push("--setenv".to_string());
259 bwrap_args.push("HOME".to_string());
260 bwrap_args.push(home_dir.display().to_string());
261
262 bwrap_args.push("bash".to_string());
263 bwrap_args.push("-c".to_string());
264 bwrap_args.push(inner_cmd);
265
266 Command::new("bwrap").args(&bwrap_args).status()?
267 };
268
269 if !status.success() {
270 return Err(anyhow!(
271 "Bubblewrap build failed with exit code {:?}",
272 status.code()
273 ));
274 }
275
276 println!("{}", "Bubblewrap build successful!".green());
277
278 Ok(())
279 }
280}