snix_build/buildservice/
bwrap.rs1use std::path::PathBuf;
2
3use bstr::BStr;
4use snix_castore::{
5 blobservice::BlobService,
6 directoryservice::DirectoryService,
7 fs::fuse::FuseDaemon,
8 import::fs::ingest_path,
9 refscan::{ReferencePattern, ReferenceScanner},
10};
11use tonic::async_trait;
12use tracing::{Span, debug, info, instrument, warn};
13use uuid::Uuid;
14
15use super::BuildService;
16use crate::{
17 buildservice::{BuildConstraints, BuildOutput, BuildRequest, BuildResult},
18 bwrap::Bwrap,
19 sandbox::SandboxSpec,
20};
21const SANDBOX_SHELL: &str = env!("SNIX_BUILD_SANDBOX_SHELL");
22
23pub struct BubblewrapBuildService<BS, DS> {
24 workdir: PathBuf,
26
27 blob_service: BS,
29 directory_service: DS,
31
32 concurrent_builds: tokio::sync::Semaphore,
35}
36impl<BS, DS> BubblewrapBuildService<BS, DS> {
37 pub fn new(workdir: PathBuf, blob_service: BS, directory_service: DS) -> Self {
38 Self {
43 workdir,
44 blob_service,
45 directory_service,
46 concurrent_builds: tokio::sync::Semaphore::new(2),
47 }
48 }
49}
50
51#[async_trait]
52impl<BS, DS> BuildService for BubblewrapBuildService<BS, DS>
53where
54 BS: BlobService + Clone + 'static,
55 DS: DirectoryService + Clone + 'static,
56{
57 #[instrument(skip_all, err)]
58 async fn do_build(&self, request: BuildRequest) -> std::io::Result<BuildResult> {
59 let _permit = self.concurrent_builds.acquire().await.unwrap();
60
61 let build_name = Uuid::new_v4();
62 let sandbox_path = self.workdir.join(build_name.to_string());
63 info!(%build_name, "Starting bwrap build");
64
65 let span = Span::current();
66 span.record("build_name", build_name.to_string());
67
68 let blob_service = self.blob_service.clone();
69 let directory_service = self.directory_service.clone();
70
71 let spec = SandboxSpec::builder()
72 .host_workdir(sandbox_path)
73 .sandbox_workdir(request.working_dir)
74 .scratches(request.scratch_paths)
75 .command(request.command_args)
76 .env_vars(request.environment_vars)
77 .additional_files(request.additional_files)
78 .with_inputs(request.inputs_dir, move |path| {
79 let root_nodes = Box::new(request.inputs.clone());
80 let fs = snix_castore::fs::SnixStoreFs::new(
81 blob_service.clone(),
82 directory_service.clone(),
83 root_nodes,
84 snix_castore::fs::FSSettings {
85 list_root: true,
86 uid_gid_override: None,
87 show_xattr: false,
88 },
89 tokio::runtime::Handle::current(),
90 );
91 FuseDaemon::new(fs, path, 4, false)
93 })
94 .allow_network(
95 request
96 .constraints
97 .contains(&BuildConstraints::NetworkAccess),
98 )
99 .provide_shell(
100 request
101 .constraints
102 .contains(&BuildConstraints::ProvideBinSh)
103 .then_some(SANDBOX_SHELL.into()),
104 )
105 .build();
106
107 let outcome = Bwrap::initialize(spec)?.run().await?;
108
109 if !outcome.output().status.success() {
110 let stdout = BStr::new(&outcome.output().stdout);
111 let stderr = BStr::new(&outcome.output().stderr);
112
113 warn!(stdout=%stdout, stderr=%stderr, exit_code=%outcome.output().status, "build failed");
114
115 return Err(std::io::Error::other("nonzero exit code".to_string()));
116 }
117
118 let outputs: Vec<_> = request
119 .outputs
120 .iter()
121 .filter_map(|o| outcome.find_path(o))
122 .collect();
123 if outputs.len() != request.outputs.len() {
124 warn!("Not all outputs produced");
125 return Err(std::io::Error::other(
126 "Not all outputs produced".to_string(),
127 ));
128 }
129 let patterns = ReferencePattern::new(request.refscan_needles);
130 let outputs = futures::future::try_join_all(outputs.into_iter().enumerate().map(
131 |(i, host_output_path)| {
132 let output_path = &request.outputs[i];
133 debug!(host.path=?host_output_path, output.path=?output_path, "ingesting path");
134 let patterns = patterns.clone();
135 async move {
136 let scanner = ReferenceScanner::new(patterns);
137 Ok::<_, std::io::Error>(BuildOutput {
138 node: ingest_path(
139 &self.blob_service,
140 &self.directory_service,
141 host_output_path,
142 Some(&scanner),
143 )
144 .await
145 .map_err(|e| {
146 std::io::Error::new(
147 std::io::ErrorKind::InvalidData,
148 format!("Unable to ingest output: {e}"),
149 )
150 })?,
151
152 output_needles: scanner
153 .matches()
154 .into_iter()
155 .enumerate()
156 .filter(|(_, val)| *val)
157 .map(|(idx, _)| idx as u64)
158 .collect(),
159 })
160 }
161 },
162 ))
163 .await?;
164 Ok(BuildResult { outputs })
165 }
166}