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