Skip to main content

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}