Skip to main content

zoi_package/
docker.rs

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}