cargo_dist/build/mod.rs
1//! Compiling Things
2
3use axoproject::PackageId;
4use camino::Utf8PathBuf;
5use cargo_dist_schema::{AssetInfo, DistManifest, TripleNameRef};
6use tracing::info;
7
8use crate::{
9 copy_file, linkage::determine_linkage, Binary, BinaryIdx, BinaryKind, DistError, DistGraph,
10 DistResult, SortedMap,
11};
12
13pub mod cargo;
14pub mod fake;
15pub mod generic;
16
17/// Output expectations for builds, and computed facts (all packages)
18pub struct BuildExpectations {
19 /// Expectations grouped by package
20 pub packages: SortedMap<String, BinaryExpectations>,
21 /// Whether this is fake (--artifacts=lies)
22 fake: bool,
23}
24
25/// Output expectations for builds, and computed facts (one package)
26#[derive(Default)]
27pub struct BinaryExpectations {
28 /// Expected binaries
29 pub binaries: SortedMap<String, ExpectedBinary>,
30}
31
32/// Binaries we expect
33pub struct ExpectedBinary {
34 /// idx of the binary in the DistGraph
35 pub idx: BinaryIdx,
36 /// path to the binary in the build output
37 ///
38 /// Initially this is None, but should be Some by the end of the build from calls to found_bin
39 pub src_path: Option<Utf8PathBuf>,
40 /// paths to the symbols of this binary in the build output
41 ///
42 /// Initially this is empty, but should be Some by the end of the build from calls to found_bin
43 pub sym_paths: Vec<Utf8PathBuf>,
44 /// What kind of binary this is (executable, dynamic library, etc.)
45 pub kind: BinaryKind,
46}
47
48impl BuildExpectations {
49 /// Create a new BuildExpectations
50 pub fn new(dist: &DistGraph, expected_binaries: &[BinaryIdx]) -> Self {
51 let mut packages = SortedMap::<String, BinaryExpectations>::new();
52 for &binary_idx in expected_binaries {
53 let binary = &dist.binary(binary_idx);
54
55 // Get the package id or an empty string (for generic builds)
56 let package_id = package_id_string(binary.pkg_id.as_ref());
57 let exe_name = binary.file_name.clone();
58
59 packages.entry(package_id).or_default().binaries.insert(
60 exe_name,
61 ExpectedBinary {
62 idx: binary_idx,
63 src_path: None,
64 sym_paths: vec![],
65 kind: binary.kind,
66 },
67 );
68 }
69
70 Self {
71 packages,
72 fake: false,
73 }
74 }
75
76 /// Create a new BuildExpectations, but don't sweat things being faked
77 ///
78 /// This is used for --artifacts=lies
79 pub fn new_fake(dist: &DistGraph, expected_binaries: &[BinaryIdx]) -> Self {
80 let mut out = Self::new(dist, expected_binaries);
81 out.fake = true;
82 out
83 }
84
85 /// Report that a binary was found, which might have been expected
86 ///
87 /// This subroutine is responsible for sorting out whether we care about the binary,
88 /// and if the maybe_symbols are in fact symbols we care about.
89 ///
90 /// NOTE: it is correct/expected to hand this a bunch of random paths to things
91 /// that vaguely might be what we want, assuming it knows how to pick the right ones out.
92 pub fn found_bins(&mut self, pkg_id: String, filenames: Vec<Utf8PathBuf>) {
93 // The files we're given by cargo are one big mush of
94 // the executables, libraries, symbols, etc.
95 // Try to partition this into what's probably symbols
96 // and probably exes/libs
97 let (maybe_symbols, maybe_bins): (Vec<_>, Vec<_>) = filenames
98 .into_iter()
99 // FIXME: unhardcode this when we add support for other symbol kinds!
100 .partition(|f| f.extension().map(|e| e == "pdb").unwrap_or(false));
101
102 // lookup the package
103 let Some(pkg) = self.packages.get_mut(&pkg_id) else {
104 return;
105 };
106
107 // NOTE: its expected for these early returns to trigger. It's this functions
108 // jobs to sort through build outputs and grab the ones we actually care about.
109 //
110 // For instance if you build a cargo workspace (something we prefer for stability
111 // of feature resolution), this can produce a bunch of binaries for examples or
112 // packages you don't care about, which cargo/rustc will happily report back to us,
113 // and we need to be aware enough to throw those irrelevant results out.
114 for src_path in maybe_bins {
115 info!("got a new binary: {}", src_path);
116
117 // lookup the binary in the package
118 let Some(bin_name) = src_path.file_name().map(String::from) else {
119 continue;
120 };
121 let Some(bin_result) = pkg.binaries.get_mut(&bin_name) else {
122 continue;
123 };
124 // used to compare to the symbol stem further down
125 let Some(bin_stem) = src_path.file_stem().map(String::from) else {
126 continue;
127 };
128
129 // Cool, we expected this binary, register its location!
130 bin_result.src_path = Some(src_path);
131
132 // Also register symbols
133 for sym_path in &maybe_symbols {
134 let is_for_this_bin = sym_path
135 .file_stem()
136 .map(|stem| stem == bin_stem)
137 .unwrap_or(false);
138 if !is_for_this_bin {
139 continue;
140 }
141
142 // These are symbols we expected! Save the path.
143 bin_result.sym_paths.push(sym_path.to_owned());
144 }
145 }
146 }
147
148 /// Assuming the build is now complete, process the binaries as needed
149 ///
150 /// Currently this is:
151 ///
152 /// * checking src_path was set by found_bin
153 /// * computing linkage for the binary
154 /// * copying the binary and symbols to their final homes
155 ///
156 /// In the future this may also include:
157 ///
158 /// * code signing / hashing
159 /// * stripping
160 pub fn process_bins(&self, dist: &DistGraph, manifest: &mut DistManifest) -> DistResult<()> {
161 let mut missing = vec![];
162 for (pkg_id, pkg) in &self.packages {
163 for (bin_name, result_bin) in &pkg.binaries {
164 // If the src_path is missing, everything is bad
165 let Some(src_path) = result_bin.src_path.as_deref() else {
166 missing.push((pkg_id.to_owned(), bin_name.to_owned()));
167 continue;
168 };
169 if !src_path.exists() {
170 missing.push((pkg_id.to_owned(), bin_name.to_owned()));
171 continue;
172 }
173 let bin = dist.binary(result_bin.idx);
174
175 // compute linkage for the binary
176 self.compute_linkage_and_sign(dist, manifest, result_bin, &bin.target)?;
177
178 // copy files to their final homes
179 self.copy_assets(result_bin, bin)?;
180 }
181 }
182
183 // FIXME: properly bulk these together instead of just returning the first
184 #[allow(clippy::never_loop)]
185 for (pkg_name, bin_name) in missing {
186 return Err(DistError::MissingBinaries { pkg_name, bin_name });
187 }
188
189 Ok(())
190 }
191
192 // Compute the linkage info for this binary and sign it
193 fn compute_linkage_and_sign(
194 &self,
195 dist: &DistGraph,
196 manifest: &mut DistManifest,
197 src: &ExpectedBinary,
198 target: &TripleNameRef,
199 ) -> DistResult<()> {
200 let src_path = src
201 .src_path
202 .as_ref()
203 .expect("bin src_path should have been checked by caller");
204
205 dist.signer.sign(src_path)?;
206
207 // If we're faking it, don't run the linkage stuff
208 let linkage = if self.fake {
209 // FIXME: fake this more interestingly!
210 let mut linkage = cargo_dist_schema::Linkage::default();
211 linkage.other.insert(cargo_dist_schema::Library {
212 path: "fakelib".to_owned(),
213 source: None,
214 package_manager: None,
215 });
216 linkage
217 } else {
218 determine_linkage(src_path, target)
219 };
220
221 let bin = dist.binary(src.idx);
222 manifest.assets.insert(
223 bin.id.clone(),
224 AssetInfo {
225 id: bin.id.clone(),
226 name: bin.name.clone(),
227 system: dist.system_id.clone(),
228 linkage: Some(linkage),
229 target_triples: vec![target.to_owned()],
230 },
231 );
232 Ok(())
233 }
234
235 // Copy the assets for this binary
236 fn copy_assets(&self, src: &ExpectedBinary, dests: &Binary) -> DistResult<()> {
237 // Copy the main binary
238 let src_path = src
239 .src_path
240 .as_deref()
241 .expect("bin src_path should have been checked by caller");
242 for dest_path in &dests.copy_exe_to {
243 copy_file(src_path, dest_path)?;
244 }
245
246 // Copy the symbols
247 for sym_path in &src.sym_paths {
248 for dest_path in &dests.copy_symbols_to {
249 copy_file(sym_path, dest_path)?;
250 }
251 }
252
253 Ok(())
254 }
255}
256
257fn package_id_string(id: Option<&PackageId>) -> String {
258 id.map(ToString::to_string).unwrap_or_default()
259}