metadeps/
lib.rs

1//! metadeps lets you write `pkg-config` dependencies in `Cargo.toml` metadata,
2//! rather than programmatically in `build.rs`.  This makes those dependencies
3//! declarative, so other tools can read them as well.
4//!
5//! metadeps parses metadata like this in `Cargo.toml`:
6//!
7//! ```toml
8//! [package.metadata.pkg-config]
9//! testlib = "1.2"
10//! testdata = { version = "4.5", feature = "some-feature" }
11//! ```
12
13#![deny(missing_docs, warnings)]
14
15#[macro_use]
16extern crate error_chain;
17extern crate pkg_config;
18extern crate toml;
19
20use std::collections::HashMap;
21use std::env;
22use std::fs;
23use std::io::Read;
24use std::path::PathBuf;
25use pkg_config::{Config, Library};
26
27error_chain! {
28    foreign_links {
29        PkgConfig(pkg_config::Error) #[doc="pkg-config error"];
30    }
31}
32
33/// Probe all libraries configured in the Cargo.toml
34/// `[package.metadata.pkg-config]` section.
35pub fn probe() -> Result<HashMap<String, Library>> {
36    let dir = try!(env::var_os("CARGO_MANIFEST_DIR").ok_or("$CARGO_MANIFEST_DIR not set"));
37    let mut path = PathBuf::from(dir);
38    path.push("Cargo.toml");
39    let mut manifest = try!(fs::File::open(&path).chain_err(||
40        format!("Error opening {}", path.display())
41    ));
42    let mut manifest_str = String::new();
43    try!(manifest.read_to_string(&mut manifest_str).chain_err(||
44        format!("Error reading {}", path.display())
45    ));
46    let toml = try!(manifest_str.parse::<toml::Value>().map_err(|e|
47        format!("Error parsing TOML from {}: {:?}", path.display(), e)
48    ));
49    let key = "package.metadata.pkg-config";
50    let meta = try!(toml.lookup(key).ok_or(
51        format!("No {} in {}", key, path.display())
52    ));
53    let table = try!(meta.as_table().ok_or(
54        format!("{} not a table in {}", key, path.display())
55    ));
56    let mut libraries = HashMap::new();
57    for (name, value) in table {
58        let ref version = match value {
59            &toml::Value::String(ref s) => s,
60            &toml::Value::Table(ref t) => {
61                let mut feature = None;
62                let mut version = None;
63                for (tname, tvalue) in t {
64                    match (tname.as_str(), tvalue) {
65                        ("feature", &toml::Value::String(ref s)) => { feature = Some(s); }
66                        ("version", &toml::Value::String(ref s)) => { version = Some(s); }
67                        _ => bail!("Unexpected key {}.{}.{} type {}", key, name, tname, tvalue.type_str()),
68                    }
69                }
70                if let Some(feature) = feature {
71                    let var = format!("CARGO_FEATURE_{}", feature.to_uppercase().replace('-', "_"));
72                    if env::var_os(var).is_none() {
73                        continue;
74                    }
75                }
76                try!(version.ok_or(format!("No version in {}.{}", key, name)))
77            }
78            _ => bail!("{}.{} not a string or table", key, name),
79        };
80        let library = try!(Config::new().atleast_version(&version).probe(name));
81        libraries.insert(name.clone(), library);
82    }
83    Ok(libraries)
84}