cxx_build/
deps.rs

1use std::collections::BTreeMap;
2use std::env;
3use std::ffi::OsString;
4use std::path::PathBuf;
5
6#[derive(Default)]
7pub(crate) struct Crate {
8    pub include_prefix: Option<PathBuf>,
9    pub links: Option<OsString>,
10    pub header_dirs: Vec<HeaderDir>,
11}
12
13pub(crate) struct HeaderDir {
14    pub exported: bool,
15    pub path: PathBuf,
16}
17
18impl Crate {
19    pub(crate) fn print_to_cargo(&self) {
20        if let Some(include_prefix) = &self.include_prefix {
21            println!(
22                "cargo:CXXBRIDGE_PREFIX={}",
23                include_prefix.to_string_lossy(),
24            );
25        }
26        if let Some(links) = &self.links {
27            println!("cargo:CXXBRIDGE_LINKS={}", links.to_string_lossy());
28        }
29        for (i, header_dir) in self.header_dirs.iter().enumerate() {
30            if header_dir.exported {
31                println!(
32                    "cargo:CXXBRIDGE_DIR{}={}",
33                    i,
34                    header_dir.path.to_string_lossy(),
35                );
36            }
37        }
38    }
39}
40
41pub(crate) fn direct_dependencies() -> Vec<Crate> {
42    let mut crates: BTreeMap<String, Crate> = BTreeMap::new();
43    let mut exported_header_dirs: BTreeMap<String, Vec<(usize, PathBuf)>> = BTreeMap::new();
44
45    // Only variables set from a build script of direct dependencies are
46    // observable. That's exactly what we want! Your crate needs to declare a
47    // direct dependency on the other crate in order to be able to #include its
48    // headers.
49    //
50    // Also, they're only observable if the dependency's manifest contains a
51    // `links` key. This is important because Cargo imposes no ordering on the
52    // execution of build scripts without a `links` key. When exposing a
53    // generated header for the current crate to #include, we need to be sure
54    // the dependency's build script has already executed and emitted that
55    // generated header.
56    //
57    // References:
58    //   - https://doc.rust-lang.org/cargo/reference/build-scripts.html#the-links-manifest-key
59    //   - https://doc.rust-lang.org/cargo/reference/build-script-examples.html#using-another-sys-crate
60    for (k, v) in env::vars_os() {
61        let mut k = k.to_string_lossy().into_owned();
62        if !k.starts_with("DEP_") {
63            continue;
64        }
65
66        if k.ends_with("_CXXBRIDGE_PREFIX") {
67            k.truncate(k.len() - "_CXXBRIDGE_PREFIX".len());
68            crates.entry(k).or_default().include_prefix = Some(PathBuf::from(v));
69            continue;
70        }
71
72        if k.ends_with("_CXXBRIDGE_LINKS") {
73            k.truncate(k.len() - "_CXXBRIDGE_LINKS".len());
74            crates.entry(k).or_default().links = Some(v);
75            continue;
76        }
77
78        let without_counter = k.trim_end_matches(|ch: char| ch.is_ascii_digit());
79        let counter_len = k.len() - without_counter.len();
80        if counter_len == 0 || !without_counter.ends_with("_CXXBRIDGE_DIR") {
81            continue;
82        }
83
84        let sort_key = k[k.len() - counter_len..]
85            .parse::<usize>()
86            .unwrap_or(usize::MAX);
87        k.truncate(k.len() - counter_len - "_CXXBRIDGE_DIR".len());
88        exported_header_dirs
89            .entry(k)
90            .or_default()
91            .push((sort_key, PathBuf::from(v)));
92    }
93
94    for (k, mut dirs) in exported_header_dirs {
95        dirs.sort_by_key(|(sort_key, _dir)| *sort_key);
96        crates
97            .entry(k)
98            .or_default()
99            .header_dirs
100            .extend(dirs.into_iter().map(|(_sort_key, dir)| HeaderDir {
101                exported: true,
102                path: dir,
103            }));
104    }
105
106    crates.into_iter().map(|entry| entry.1).collect()
107}