rix/building/
mod.rs

1use crate::derivations::{load_derivation, Derivation};
2use crate::sandbox::{mount_path, mount_paths, run_in_sandbox};
3use std::env::set_var;
4use std::fs::File;
5use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
6use std::os::unix::process::CommandExt;
7use std::path::Path;
8use std::process::{Command, Stdio};
9
10pub struct BuildConfig<'a> {
11    derivation: &'a Derivation,
12    build_dir: &'a Path,
13    stdout: Option<&'a File>,
14    stderr: Option<&'a File>,
15}
16
17impl<'a> BuildConfig<'a> {
18    pub fn new(derivation: &'a Derivation, build_dir: &'a Path) -> BuildConfig<'a> {
19        BuildConfig {
20            derivation: derivation,
21            build_dir: build_dir,
22            stderr: None,
23            stdout: None,
24        }
25    }
26
27    pub fn stdout_to_file(&mut self, file: &'a File) {
28        self.stdout = Some(file);
29    }
30
31    pub fn stderr_to_file(&mut self, file: &'a File) {
32        self.stderr = Some(file);
33    }
34}
35
36pub fn build_derivation_sandboxed(config: &BuildConfig) -> Result<i32, String> {
37    // this function assumes all derivation inputs are present and won't be
38    // GC'd for the duration of this build
39    // TODO: need to mount input_srcs of input_drvs too.
40    let stdout_fd = config.stdout.map(|file| file.as_raw_fd());
41    let stderr_fd = config.stderr.map(|file| file.as_raw_fd());
42    // return value is the error code of the builder or 255 if anything went
43    // wrong and we failed to execute the builder
44    run_in_sandbox(
45        config.build_dir,
46        || prepare_sandbox(config),
47        || run_build(config, stdout_fd, stderr_fd),
48    )
49}
50
51pub fn build_derivation_command(derivation: &Derivation, build_dir: &Path) -> Command {
52    // This function assumes that the sandbox is already fully set up
53    let mut cmd = Command::new(&derivation.builder);
54    cmd.args(&derivation.args)
55        .envs(&derivation.env)
56        .current_dir(build_dir);
57    cmd
58}
59
60fn prepare_sandbox(config: &BuildConfig) -> Result<(), String> {
61    set_env(&config.derivation);
62    mount_standard_paths(config)?;
63    mount_input_drvs(config)?;
64    mount_paths(
65        config.derivation.input_srcs.iter().map(Path::new),
66        config.build_dir,
67    )
68}
69
70fn run_build(config: &BuildConfig, stdout_fd: Option<RawFd>, stderr_fd: Option<RawFd>) -> isize {
71    let mut cmd = build_derivation_command(&config.derivation, &Path::new("/"));
72    if let Some(stdout_fd) = stdout_fd {
73        cmd.stdout(unsafe { Stdio::from_raw_fd(stdout_fd) });
74    }
75    if let Some(stderr_fd) = stderr_fd {
76        cmd.stderr(unsafe { Stdio::from_raw_fd(stderr_fd) });
77    }
78    let exec_error = cmd.exec();
79    // we should never get here because we exec into the builder above (i.e. the builder
80    // process takes over). So, it's an error no matter what if we get here.
81    eprintln!("Error executing builder: {}", exec_error);
82    255
83}
84
85fn set_env(derivation: &Derivation) {
86    for (var_name, var_value) in &derivation.env {
87        set_var(var_name, var_value);
88    }
89}
90
91fn mount_input_drvs(config: &BuildConfig) -> Result<(), String> {
92    for (drv_path, outputs) in &config.derivation.input_drvs {
93        let derivation = load_derivation(drv_path)?;
94        for output in outputs {
95            let drv_output = derivation.outputs.get(output).ok_or_else(|| {
96                format!(
97                    "Could not find output '{}' of derivation {:?}",
98                    output, drv_path
99                )
100            })?;
101            mount_path(Path::new(&drv_output.path), config.build_dir)?;
102            // TODO: mount runtime dependencies of the output path too.
103        }
104    }
105    Ok(())
106}
107
108fn mount_standard_paths(config: &BuildConfig) -> Result<(), String> {
109    mount_path(Path::new("/dev/null"), config.build_dir)
110}