1use serde_json::Value;
2use std::{
3 collections::HashMap,
4 fs::File,
5 io,
6 ops::Deref,
7 path::{Path, PathBuf},
8};
9
10const BUILD_INFO: &str = "conanbuildinfo.json";
11
12pub struct BuildInfoSet {
13 info: HashMap<&'static str, BuildInfo>,
14}
15impl BuildInfoSet {
16 pub fn find_all() -> io::Result<Self> {
17 let current_dir = std::env::current_dir()?;
18 let info = Self::path_from_filesystem(¤t_dir)
19 .chain(Self::path_from_env())
20 .filter(|path| path.exists())
21 .map(|path| BuildInfo::read_build_info(&path).map_err(|e| (path, e)))
22 .filter_map(|r| {
23 r.map(|info| (info.target(), info))
24 .map_err(|(path, e)| eprintln!("Error opening {path:?}: {e}"))
25 .ok()
26 })
27 .collect();
28
29 Ok(Self { info })
30 }
31
32 pub fn path_from_env() -> impl Iterator<Item = PathBuf> {
33 std::env::vars().filter_map(|(key, path)| match key.split('_').next_back() {
34 Some("CONANBUILDINFO") => Some(path.into()),
35 None if key == "CONANBUILDINFO" => Some(path.into()),
36 _ => None,
37 })
38 }
39
40 pub fn path_from_filesystem(current_dir: &Path) -> impl Iterator<Item = PathBuf> + use<'_> {
41 current_dir
42 .ancestors()
43 .collect::<Vec<_>>()
44 .into_iter()
45 .rev()
46 .flat_map(|dir| {
47 dir.read_dir()
48 .unwrap()
49 .map(|path| path.expect("read dir may not fail").path().join(BUILD_INFO))
50 .chain([dir.join(BUILD_INFO)])
51 })
52 }
53
54 pub fn get_current_target(&self, host: &str) -> Option<&BuildInfo> {
55 self.info.get(host)
56 }
57
58 pub fn all_targets<'a>(
59 &'a self,
60 host: &'a str,
61 ) -> impl Iterator<Item = (bool, &'a BuildInfo)> + use<'a> {
62 self.info.iter().map(move |(&target, info)| {
63 let is_host = target == host;
64
65 (is_host, info)
66 })
67 }
68
69 pub fn targets_and_paths(&self) -> impl Iterator<Item = (&'static str, &Path)> + use<'_> {
70 self.info
71 .iter()
72 .map(|(key, info)| (*key, info.path.deref()))
73 }
74}
75
76pub struct BuildInfo {
77 path: PathBuf,
78 info: HashMap<String, Value>,
79 libs: HashMap<String, Link>,
80 settings: Value,
81}
82impl BuildInfo {
83 pub fn read_build_info<P: AsRef<Path>>(path: P) -> io::Result<Self> {
84 let info: Value = serde_json::from_str(&std::fs::read_to_string(path.as_ref())?)
85 .expect("Invalid build info json");
86
87 let settings = info["settings"].clone();
88 let info = crate::build_info(&info);
89 let libs = crate::find_all_libs(info.iter())?;
90
91 Ok(Self {
92 path: path.as_ref().to_owned(),
93 info,
94 libs,
95 settings,
96 })
97 }
98
99 pub fn target(&self) -> &'static str {
100 Self::target_from_arch_and_os(self.arch(), self.os())
101 }
102
103 pub fn arch(&self) -> &str {
104 self.settings["arch"]
105 .as_str()
106 .expect("arch in settings must be present")
107 }
108
109 pub fn os(&self) -> &str {
110 self.settings["os"]
111 .as_str()
112 .expect("os in settings must be present")
113 }
114
115 pub fn all_deps(&self) -> impl Iterator<Item = &str> + Clone {
116 self.info.keys().map(String::as_str)
117 }
118
119 pub fn get_depends_on<'a, I: IntoIterator<Item = &'a str>>(&self, packages: I) -> DependsOn {
120 packages
121 .into_iter()
122 .map(|package| self.get_depends_on_package(package))
123 .fold(DependsOn::default(), |mut a, b| {
124 a.extend(b);
125 a
126 })
127 }
128
129 pub fn get_depends_on_package(&self, package: &str) -> DependsOn {
130 let libs = self
131 .libs_for(package)
132 .into_iter()
133 .map(|name| Lib {
134 is_static: !self.is_shared(name),
135 name: name.to_string(),
136 })
137 .collect();
138 let libdirs = self
139 .libdir_for(package)
140 .into_iter()
141 .map(|dir| LibDir(dir.to_string()))
142 .collect();
143
144 DependsOn { libs, libdirs }
145 }
146
147 pub fn is_shared(&self, lib: &str) -> bool {
148 self.libs.get(lib).copied().unwrap_or(Link::Shared) == Link::Shared
149 }
150
151 pub fn libdir_for(&self, package: &str) -> Vec<&str> {
152 Self::libdir_for_package(self.package(package)).collect()
153 }
154
155 pub fn libdir_for_package(value: &Value) -> impl Iterator<Item = &str> {
156 value["lib_paths"]
157 .as_array()
158 .unwrap()
159 .iter()
160 .map(|lib| lib.as_str().unwrap())
161 }
162
163 pub fn libs_for(&self, package: &str) -> Vec<&str> {
164 self.package(package)["libs"]
165 .as_array()
166 .unwrap()
167 .iter()
168 .map(|lib| lib.as_str().unwrap())
169 .collect()
170 }
171
172 pub fn includes_for(&self, package: &str) -> Vec<&str> {
173 self.package(package)["include_paths"]
174 .as_array()
175 .unwrap()
176 .iter()
177 .map(|json| json.as_str().unwrap())
178 .collect()
179 }
180
181 pub fn bindir_for(&self, package: &str) -> Vec<&str> {
182 self.package(package)["bin_paths"]
183 .as_array()
184 .unwrap()
185 .iter()
186 .map(|lib| lib.as_str().unwrap())
187 .collect()
188 }
189
190 pub fn rootpath_for(&self, package: &str) -> &str {
191 self.package(package)["rootpath"].as_str().unwrap()
192 }
193
194 pub fn package(&self, package: &str) -> &Value {
195 self.try_package(package)
196 .unwrap_or_else(|| panic!("No dependency {package:?} in conan info"))
197 }
198
199 pub fn try_package(&self, package: &str) -> Option<&Value> {
200 self.info.get(package)
201 }
202
203 pub fn write_env_source<W1, W2>(&self, is_host: bool, mut sh: W1, mut ps1: W2) -> io::Result<()>
204 where
205 W1: io::Write,
206 W2: io::Write,
207 {
208 let prefix = self.target().replace('-', "_");
209
210 writeln!(
211 sh,
212 "export {prefix}_CONANBUILDINFO={}",
213 self.path.to_string_lossy()
214 )?;
215 writeln!(
216 ps1,
217 "$env:{prefix}_CONANBUILDINFO=\"{}\"",
218 self.path.to_string_lossy()
219 )?;
220
221 let shared_deps = self.all_deps().filter(|package| {
222 self.libs_for(package)
223 .into_iter()
224 .any(|lib| self.is_shared(lib))
225 });
226
227 let libdirs = shared_deps
228 .clone()
229 .flat_map(|package| self.libdir_for(package).into_iter())
230 .collect::<Vec<_>>()
231 .join(":");
232
233 if !libdirs.is_empty() && is_host {
234 writeln!(sh, "export LD_LIBRARY_PATH={libdirs}")?;
235 }
236 let bindirs = shared_deps
237 .flat_map(|package| self.bindir_for(package))
238 .collect::<Vec<_>>()
239 .join(";")
240 .replace('\\', "\\\\");
241
242 if !bindirs.is_empty() && is_host {
243 writeln!(ps1, "$env:PATH=\"{bindirs};$env:PATH\"")?;
244 }
245
246 if self.try_package("openssl").is_some() {
247 let openssl_dir = self.rootpath_for("openssl");
248 writeln!(sh, "export {prefix}_OPENSSL_DIR={openssl_dir}",)?;
249 writeln!(ps1, "$env:{prefix}_OPENSSL_DIR=\"{openssl_dir}\"")?;
250
251 if is_host {
252 writeln!(sh, "export OPENSSL_DIR={openssl_dir}",)?;
253 writeln!(ps1, "$env:OPENSSL_DIR=\"{openssl_dir}\"")?;
254 }
255 }
256
257 sh.flush()?;
258 ps1.flush()?;
259
260 Ok(())
261 }
262
263 pub fn libcxx(&self) -> impl Iterator<Item = Lib> + use<'_> {
264 let main = self.libcxx_name();
265
266 let android_abi = self.libcxx_name().into_iter().filter_map(|name| {
267 (name == "c++_static" && self.os() == "Android").then_some("c++abi")
268 });
269
270 main.into_iter().chain(android_abi).map(|name| Lib {
271 is_static: false,
272 name: name.to_string(),
273 })
274 }
275
276 fn libcxx_name(&self) -> Option<&str> {
277 let libcxx = self
278 .settings
279 .as_object()
280 .expect("settings attribute is an object")
281 .get("compiler.libcxx")?
282 .as_str()
283 .expect("compiler.libcxx attribute is an string");
284
285 Some(match libcxx {
286 "libstdc++11" => "stdc++",
287 x if x.starts_with("lib") => &x[3..],
288 x => x,
289 })
290 }
291
292 fn target_from_arch_and_os(arch: &str, os: &str) -> &'static str {
293 match os {
294 "Linux" => match arch {
295 "x86_64" => "x86_64-unknown-linux-gnu",
296 "x86" => "i686-unknown-linux-gnu",
297 arch => unimplemented!("Unsupported architecture {arch:?}/{os:?}"),
298 },
299 "Windows" => match arch {
300 "x86_64" => "x86_64-pc-windows-msvc",
301 "x86" => "i686-pc-windows-msvc",
302 arch => unimplemented!("Unsupported architecture {arch:?}/{os:?}"),
303 },
304 "Macos" => match arch {
305 "armv8" => "aarch64-apple-darwin",
306 "x86_64" => "x86_64-apple-darwin",
307 arch => unimplemented!("Unsupported architecture {arch:?}/{os:?}"),
308 },
309 "iOS" => match arch {
310 "armv8" => "aarch64-apple-ios",
311 arch => unimplemented!("Unsupported architecture {arch:?}/{os:?}"),
312 },
313 "Android" => match arch {
314 "armv8" => "aarch64-linux-android",
315 "armv7" => "armv7-linux-androideabi",
316 "x86" => "i686-linux-android",
317 "x86_64" => "x86_64-linux-android",
318 arch => unimplemented!("Unsupported architecture {arch:?}/{os:?}"),
319 },
320 os => unimplemented!("Unsupported OS {os:?}"),
321 }
322 }
323}
324
325pub struct Conan {
326 build_info_set: BuildInfoSet,
327 host: String,
328 rerun_if_changed: bool,
329}
330impl Default for Conan {
331 fn default() -> Self {
332 Self::new()
333 }
334}
335impl Conan {
336 pub fn new() -> Conan {
337 let host = std::env::var("TARGET").expect("TARGET variable must be set");
338 Self::with_host(host)
339 }
340
341 pub fn with_host(host: String) -> Conan {
342 let build_info_set = BuildInfoSet::find_all().expect("Failure reading conanbuildinfo");
343
344 eprintln!("Targets:");
345 for (target, path) in build_info_set.targets_and_paths() {
346 eprintln!(" {target}: {}", path.to_string_lossy());
347 }
348
349 Conan {
350 build_info_set,
351 host,
352 rerun_if_changed: false,
353 }
354 }
355
356 pub fn mark_rerun_if_changed(&mut self) {
357 if self.rerun_if_changed {
358 return;
359 }
360
361 self.rerun_if_changed = true;
362 let build_info = self.build_info();
363 println!(
364 "cargo:rerun-if-changed={path}",
365 path = build_info.path.to_string_lossy()
366 );
367 }
368
369 pub fn build_info(&self) -> &BuildInfo {
370 self.build_info_set
371 .get_current_target(&self.host)
372 .unwrap_or_else(|| {
373 panic!(
374 "Could not find build info for {:?}, available are: {:?}",
375 self.host,
376 self.build_info_set.info.keys().collect::<Vec<_>>()
377 )
378 })
379 }
380
381 pub fn depends_on<'a, I: IntoIterator<Item = &'a str>>(&mut self, packages: I) {
382 self.mark_rerun_if_changed();
383 let info = self.build_info();
384 DependsOn::extend_all(
385 packages
386 .into_iter()
387 .map(|package| info.get_depends_on_package(package)),
388 )
389 .apply()
390 }
391
392 pub fn depends_on_optional<'a, I: IntoIterator<Item = &'a str>>(&mut self, packages: I) {
393 self.mark_rerun_if_changed();
394 let info = self.build_info();
395 DependsOn::extend_all(
396 packages
397 .into_iter()
398 .filter(|package| info.try_package(package).is_some())
399 .map(|package| info.get_depends_on_package(package)),
400 )
401 .apply()
402 }
403
404 pub fn depends_on_libcxx(&mut self) {
405 self.mark_rerun_if_changed();
406 for cxx in self.build_info().libcxx() {
407 cxx.apply();
408 }
409 }
410
411 pub fn generate_env_source(&self) {
412 let mut sh = File::create("env.sh").unwrap();
413 let mut ps1 = File::create("env.ps1").unwrap();
414
415 for (is_host, info) in self.build_info_set.all_targets(&self.host) {
416 info.write_env_source(is_host, &mut sh, &mut ps1).unwrap();
417 }
418 }
419
420 pub fn package_is_shared(options: &HashMap<String, String>, package: &str) -> Option<bool> {
421 let option = format!("{package}:shared");
422
423 let r = options.get(&option)?.to_lowercase().parse().unwrap();
424
425 Some(r)
426 }
427}
428
429#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
430pub enum Link {
431 Static,
432 Shared,
433}
434
435fn build_info(root: &Value) -> HashMap<String, Value> {
436 root["dependencies"]
437 .as_array()
438 .unwrap()
439 .iter()
440 .map(|dep| {
441 let name = dep["name"].as_str().unwrap().to_string();
442 (name, dep.clone())
443 })
444 .collect()
445}
446
447fn find_all_libs<'a, I>(it: I) -> io::Result<HashMap<String, Link>>
448where
449 I: Iterator<Item = (&'a String, &'a Value)>,
450{
451 let mut result = HashMap::new();
452 for (_, v) in it {
453 for path in BuildInfo::libdir_for_package(v) {
454 let libs = Path::new(path)
455 .read_dir()
456 .map_err(|e| {
457 io::Error::new(e.kind(), format!("Failure reading dir {path:?}: {e}"))
458 })?
459 .filter_map(|entry| {
460 let entry = match entry {
461 Ok(entry) => entry,
462 Err(e) => return Some(Err(e)),
463 };
464
465 let lib = entry.file_name().to_string_lossy().into_owned();
466 if lib.ends_with(".lib") {
467 let lib = &lib[..lib.len() - 4];
468
469 let mut dll = entry.path();
470 dll.pop();
471 dll.pop();
472 dll.push("bin");
473 dll.push(format!("{lib}.dll"));
474
475 let link = match dll.exists() {
476 true => Link::Shared,
477 false => Link::Static,
478 };
479
480 return Some(Ok((lib.to_string(), link)));
481 }
482
483 let link;
484 if lib.ends_with(".so") {
485 link = Link::Shared;
486 } else if lib.ends_with(".a") {
487 link = Link::Static;
488 } else {
489 return None;
490 }
491
492 let lib = match lib.starts_with("lib") {
493 true => &lib[3..],
494 false => &lib,
495 };
496 let ext = lib.rfind('.');
497 let lib = match ext {
498 Some(ext) => &lib[..ext],
499 None => lib,
500 };
501
502 Some(Ok((lib.to_string(), link)))
503 });
504
505 for lib_r in libs {
506 let (key, lib) = lib_r?;
507 result.insert(key, lib);
508 }
509 }
510 }
511
512 Ok(result)
513}
514
515pub trait Applyable {
516 fn apply(&self);
517}
518
519pub struct Lib {
520 pub is_static: bool,
521 pub name: String,
522}
523impl Applyable for Lib {
524 fn apply(&self) {
525 let name = &self.name;
526 let is_static = self.is_static;
527
528 let static_ = match is_static {
529 true => "static=",
530 false => "",
531 };
532
533 println!("cargo:rustc-link-lib={static_}{name}");
534 }
535}
536
537pub struct LibDir(pub String);
538impl Applyable for LibDir {
539 fn apply(&self) {
540 println!("cargo:rustc-link-search={dir}", dir = self.0);
541 }
542}
543
544#[derive(Default)]
545pub struct DependsOn {
546 pub libs: Vec<Lib>,
547 pub libdirs: Vec<LibDir>,
548}
549impl DependsOn {
550 pub fn extend(&mut self, rhs: DependsOn) {
551 self.libs.extend(rhs.libs);
552 self.libdirs.extend(rhs.libdirs);
553 }
554
555 fn extend_all<I: IntoIterator<Item = DependsOn>>(iter: I) -> DependsOn {
556 iter.into_iter()
557 .reduce(|mut a, b| {
558 a.extend(b);
559 a
560 })
561 .unwrap_or_default()
562 }
563}
564impl Applyable for DependsOn {
565 fn apply(&self) {
566 self.libs.iter().for_each(Applyable::apply);
567 self.libdirs.iter().for_each(Applyable::apply);
568 }
569}