1 use std::collections::BTreeMap;
2 use std::env;
3 use std::ffi::OsString;
4 use std::path::PathBuf;
5
6 #[derive(Default)]
7 pub struct Crate {
8 pub include_prefix: Option<PathBuf>,
9 pub links: Option<OsString>,
10 pub header_dirs: Vec<HeaderDir>,
11 }
12
13 pub struct HeaderDir {
14 pub exported: bool,
15 pub path: PathBuf,
16 }
17
18 impl Crate {
print_to_cargo(&self)19 pub 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
direct_dependencies() -> Vec<Crate>41 pub 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 }
108