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 true,
85 None,
86 false,
87 );
88 FuseDaemon::new(fs, path, 4, false)
90 })
91 .allow_network(
92 request
93 .constraints
94 .contains(&BuildConstraints::NetworkAccess),
95 )
96 .provide_shell(
97 request
98 .constraints
99 .contains(&BuildConstraints::ProvideBinSh)
100 .then_some(SANDBOX_SHELL.into()),
101 )
102 .build();
103
104 let outcome = Bwrap::initialize(spec)?.run().await?;
105
106 if !outcome.output().status.success() {
107 let stdout = BStr::new(&outcome.output().stdout);
108 let stderr = BStr::new(&outcome.output().stderr);
109
110 warn!(stdout=%stdout, stderr=%stderr, exit_code=%outcome.output().status, "build failed");
111
112 return Err(std::io::Error::other("nonzero exit code".to_string()));
113 }
114
115 let outputs: Vec<_> = request
116 .outputs
117 .iter()
118 .filter_map(|o| outcome.find_path(o))
119 .collect();
120 if outputs.len() != request.outputs.len() {
121 warn!("Not all outputs produced");
122 return Err(std::io::Error::other(
123 "Not all outputs produced".to_string(),
124 ));
125 }
126 let patterns = ReferencePattern::new(request.refscan_needles);
127 let outputs = futures::future::try_join_all(outputs.into_iter().enumerate().map(
128 |(i, host_output_path)| {
129 let output_path = &request.outputs[i];
130 debug!(host.path=?host_output_path, output.path=?output_path, "ingesting path");
131 let patterns = patterns.clone();
132 async move {
133 let scanner = ReferenceScanner::new(patterns);
134 Ok::<_, std::io::Error>(BuildOutput {
135 node: ingest_path(
136 &self.blob_service,
137 &self.directory_service,
138 host_output_path,
139 Some(&scanner),
140 )
141 .await
142 .map_err(|e| {
143 std::io::Error::new(
144 std::io::ErrorKind::InvalidData,
145 format!("Unable to ingest output: {e}"),
146 )
147 })?,
148
149 output_needles: scanner
150 .matches()
151 .into_iter()
152 .enumerate()
153 .filter(|(_, val)| *val)
154 .map(|(idx, _)| idx as u64)
155 .collect(),
156 })
157 }
158 },
159 ))
160 .await?;
161 Ok(BuildResult { outputs })
162 }
163}