Skip to main content

zoi_cli/cmd/package/
build.rs

1use anyhow::Result;
2use clap::Parser;
3use colored::*;
4use std::path::PathBuf;
5
6#[derive(Parser, Debug)]
7pub struct BuildCommand {
8    /// Path to the package file (e.g. path/to/name.pkg.lua)
9    #[arg(required = true)]
10    pub package_file: PathBuf,
11
12    /// The type of package to build (e.g. 'source', 'pre-compiled').
13    #[arg(long)]
14    pub r#type: Option<String>,
15
16    /// The platform to build for (e.g. 'linux-amd64', 'windows-arm64', 'all', 'current').
17    /// Can be specified multiple times.
18    #[arg(long, short, num_args = 1.., default_values_t = vec!["current".to_string()])]
19    pub platform: Vec<String>,
20
21    /// The sub-packages to build.
22    #[arg(long, num_args = 1..)]
23    pub sub: Option<Vec<String>>,
24
25    /// Sign the package with the given PGP key (name or fingerprint)
26    #[arg(long)]
27    pub sign: Option<String>,
28
29    /// Run tests before building
30    #[arg(long)]
31    pub test: bool,
32
33    /// Directory to output the built package to
34    #[arg(long, short = 'o')]
35    pub output_dir: Option<PathBuf>,
36
37    /// Automatically install build-time dependencies
38    #[arg(long)]
39    pub install_deps: bool,
40
41    /// Override the package version
42    #[arg(long)]
43    pub version_override: Option<String>,
44
45    /// Method to use for building ('native', 'bwrap', or 'docker')
46    #[arg(long, default_value = "native")]
47    pub method: String,
48
49    /// Docker image to use when method is 'docker'
50    #[arg(long)]
51    pub image: Option<String>,
52
53    /// Force root ownership (UID/GID 0) in the built archive
54    #[arg(long)]
55    pub fakeroot: bool,
56
57    /// Build in a clean, isolated sysroot.
58    /// Requires a base package (specified by --root-package) to be installed into the sysroot.
59    #[arg(long)]
60    pub pure: bool,
61
62    /// The base package to install when using --pure (default: @core/base:dev).
63    #[arg(long, default_value = "@core/base:dev")]
64    pub root_package: String,
65}
66
67pub fn run(mut args: BuildCommand) -> Result<()> {
68    let mut _temp_root = None;
69
70    if args.pure {
71        if args.method == "docker" {
72            return Err(anyhow::anyhow!(
73                "--pure is not compatible with --method docker"
74            ));
75        }
76        args.method = "bwrap".to_string();
77
78        let temp = tempfile::Builder::new().prefix("zoi-pure-").tempdir()?;
79        println!(
80            "{} Initializing pure build environment in {}...",
81            "::".bold().blue(),
82            temp.path().display()
83        );
84
85        // Create ZoiOS marker so Zoi uses /usr/bin instead of /usr/local/bin
86        // This ensures the root package and build deps land where bwrap expects them.
87        let etc_dir = temp.path().join("etc");
88        std::fs::create_dir_all(&etc_dir)?;
89        std::fs::write(etc_dir.join("os-release"), "ID=zoios\nID_LIKE=zoios\n")?;
90
91        crate::pkg::sysroot::set_sysroot(temp.path().to_path_buf());
92        _temp_root = Some(temp);
93
94        // Install the root base environment package
95        let root_dep = crate::pkg::dependencies::parse_dependency_string(&args.root_package)?;
96        crate::pkg::install::dep_install::install_dependency(
97            &root_dep,
98            "pure-root",
99            crate::pkg::types::Scope::System,
100            true,
101            true,
102            &std::sync::Mutex::new(std::collections::HashSet::new()),
103            &mut Vec::new(),
104            None,
105        )?;
106    }
107
108    if args.install_deps {
109        install_dependencies_for_build(&args, args.test)?;
110    }
111
112    if args.test {
113        println!("Running tests before building...");
114        crate::pkg::package::test::run(&args)?;
115        println!("Tests passed, proceeding with build...");
116    }
117
118    crate::pkg::package::build::run(
119        &args.package_file,
120        args.r#type.as_deref(),
121        &args.platform,
122        args.sign,
123        args.output_dir.as_deref(),
124        args.version_override.as_deref(),
125        args.sub,
126        false,
127        &args.method,
128        args.image.as_deref(),
129        args.fakeroot,
130        args.install_deps,
131        args.test,
132    )
133}
134
135pub fn install_dependencies_for_build(args: &BuildCommand, include_test: bool) -> Result<()> {
136    for platform in &args.platform {
137        let current_platform = if platform == "current" {
138            crate::pkg::utils::get_platform()?
139        } else {
140            platform.clone()
141        };
142
143        if let Some(mut dep_strings) = crate::pkg::package::build::get_build_dependencies(
144            &args.package_file,
145            args.r#type.as_deref(),
146            &current_platform,
147            args.version_override.as_deref(),
148            false,
149        )? {
150            if include_test
151                && let Some(test_deps) = crate::pkg::package::build::get_test_dependencies(
152                    &args.package_file,
153                    &current_platform,
154                    args.version_override.as_deref(),
155                    false,
156                )?
157            {
158                dep_strings.extend(test_deps);
159            }
160
161            if !dep_strings.is_empty() {
162                println!(
163                    "{} Installing build/test dependencies...",
164                    "::".bold().blue()
165                );
166                let processed = std::sync::Mutex::new(std::collections::HashSet::new());
167                let mut installed = Vec::new();
168                for dep_str in dep_strings {
169                    let dep = crate::pkg::dependencies::parse_dependency_string(&dep_str)?;
170                    crate::pkg::install::dep_install::install_dependency(
171                        &dep,
172                        "build",
173                        if crate::pkg::sysroot::get_sysroot().is_some() {
174                            crate::pkg::types::Scope::System
175                        } else {
176                            crate::pkg::types::Scope::User
177                        },
178                        true,
179                        true,
180                        &processed,
181                        &mut installed,
182                        None,
183                    )?;
184                }
185            }
186        }
187    }
188    Ok(())
189}