1 #![allow(clippy::needless_doctest_main)]
2 //!`system-deps` lets you write system dependencies in `Cargo.toml` metadata,
3 //! rather than programmatically in `build.rs`. This makes those dependencies
4 //! declarative, so other tools can read them as well.
5 //!
6 //! # Usage
7 //! In your `Cargo.toml`:
8 //!
9 //! ```toml
10 //! [build-dependencies]
11 //! system-deps = "2.0"
12 //! ```
13 //!
14 //! Then, to declare a dependency on `testlib >= 1.2`
15 //! add the following section:
16 //!
17 //! ```toml
18 //! [package.metadata.system-deps]
19 //! testlib = "1.2"
20 //! ```
21 //!
22 //! Finally, in your `build.rs`, add:
23 //!
24 //! ```should_panic
25 //! fn main() {
26 //!     system_deps::Config::new().probe().unwrap();
27 //! }
28 //! ```
29 //!
30 //! # Feature-specific dependency
31 //! You can easily declare an optional system dependency by associating it with a feature:
32 //!
33 //! ```toml
34 //! [package.metadata.system-deps]
35 //! testdata = { version = "4.5", feature = "use-testdata" }
36 //! ```
37 //!
38 //! `system-deps` will check for `testdata` only if the `use-testdata` feature has been enabled.
39 //!
40 //! # Optional dependency
41 //!
42 //! Another option is to use the `optional` setting, which can also be used using [features versions](#feature-versions):
43 //!
44 //! ```toml
45 //! [package.metadata.system-deps]
46 //! test-data = { version = "4.5", optional = true }
47 //! testmore = { version = "2", v3 = { version = "3.0", optional = true }}
48 //! ```
49 //!
50 //! `system-deps` will automatically export for each dependency a feature `system_deps_have_$DEP` where `$DEP`
51 //! is the `toml` key defining the dependency in [snake_case](https://en.wikipedia.org/wiki/Snake_case).
52 //! This can be used to check if an optional dependency has been found or not:
53 //!
54 //! ```
55 //! #[cfg(system_deps_have_testdata)]
56 //! println!("found test-data");
57 //! ```
58 //!
59 //! # Overriding library name
60 //! `toml` keys cannot contain dot characters so if your library name does, you can define it using the `name` field:
61 //!
62 //! ```toml
63 //! [package.metadata.system-deps]
64 //! glib = { name = "glib-2.0", version = "2.64" }
65 //! ```
66 //! # Feature versions
67 //! `-sys` crates willing to support various versions of their underlying system libraries
68 //! can use features to control the version of the dependency required.
69 //! `system-deps` will pick the highest version among enabled features.
70 //! Such version features must use the pattern `v1_0`, `v1_2`, etc.
71 //!
72 //! ```toml
73 //! [features]
74 //! v1_2 = []
75 //! v1_4 = ["v1_2"]
76 //! v1_6 = ["v1_4"]
77 //!
78 //! [package.metadata.system-deps.gstreamer_1_0]
79 //! name = "gstreamer-1.0"
80 //! version = "1.0"
81 //! v1_2 = { version = "1.2" }
82 //! v1_4 = { version = "1.4" }
83 //! v1_6 = { version = "1.6" }
84 //! ```
85 //!
86 //! The same mechanism can be used to require a different library name depending on the version:
87 //!
88 //! ```toml
89 //! [package.metadata.system-deps.gst_gl]
90 //! name = "gstreamer-gl-1.0"
91 //! version = "1.14"
92 //! v1_18 = { version = "1.18", name = "gstreamer-gl-egl-1.0" }
93 //! ```
94 //!
95 //! # Target specific dependencies
96 //!
97 //! You can define target specific dependencies:
98 //!
99 //! ```toml
100 //! [package.metadata.system-deps.'cfg(target_os = "linux")']
101 //! testdata = "1"
102 //! [package.metadata.system-deps.'cfg(not(target_os = "macos"))']
103 //! testlib = "1"
104 //! [package.metadata.system-deps.'cfg(unix)']
105 //! testanotherlib = { version = "1", optional = true }
106 //! ```
107 //!
108 //! See [the Rust documentation](https://doc.rust-lang.org/reference/conditional-compilation.html)
109 //! for the exact syntax.
110 //! Currently, those keys are supported:
111 //! - `target_arch`
112 //! - `target_endian`
113 //! - `target_env`
114 //! - `target_family`
115 //! - `target_os`
116 //! - `target_pointer_width`
117 //! - `target_vendor`
118 //! - `unix` and `windows`
119 //!
120 //! # Overriding build flags
121 //! By default `system-deps` automatically defines the required build flags for each dependency using the information fetched from `pkg-config`.
122 //! These flags can be overridden using environment variables if needed:
123 //! - `SYSTEM_DEPS_$NAME_SEARCH_NATIVE` to override the [`cargo:rustc-link-search=native`](https://doc.rust-lang.org/cargo/reference/build-scripts.html#cargorustc-link-searchkindpath) flag;
124 //! - `SYSTEM_DEPS_$NAME_SEARCH_FRAMEWORK` to override the [`cargo:rustc-link-search=framework`](https://doc.rust-lang.org/cargo/reference/build-scripts.html#cargorustc-link-searchkindpath) flag;
125 //! - `SYSTEM_DEPS_$NAME_LIB` to override the [`cargo:rustc-link-lib`](https://doc.rust-lang.org/cargo/reference/build-scripts.html#rustc-link-lib) flag;
126 //! - `SYSTEM_DEPS_$NAME_LIB_FRAMEWORK` to override the [`cargo:rustc-link-lib=framework`](https://doc.rust-lang.org/cargo/reference/build-scripts.html#rustc-link-lib) flag;
127 //! - `SYSTEM_DEPS_$NAME_INCLUDE` to override the [`cargo:include`](https://kornel.ski/rust-sys-crate#headers) flag.
128 //!
129 //! With `$NAME` being the upper case name of the key defining the dependency in `Cargo.toml`.
130 //! For example `SYSTEM_DEPS_TESTLIB_SEARCH_NATIVE=/opt/lib` could be used to override a dependency named `testlib`.
131 //!
132 //! One can also define the environment variable `SYSTEM_DEPS_$NAME_NO_PKG_CONFIG` to fully disable `pkg-config` lookup
133 //! for the given dependency. In this case at least SYSTEM_DEPS_$NAME_LIB or SYSTEM_DEPS_$NAME_LIB_FRAMEWORK should be defined as well.
134 //!
135 //! # Statically build system library
136 //! `-sys` crates can provide support for building and statically link their underlying system library as part of their build process.
137 //! Here is how to do this in your `build.rs`:
138 //! ```should_panic
139 //! fn main() {
140 //!     system_deps::Config::new()
141 //!         .add_build_internal("testlib", |lib, version| {
142 //!             // Actually build the library here
143 //!             system_deps::Library::from_internal_pkg_config("build/path-to-pc-file", lib, version)
144 //!          })
145 //!         .probe()
146 //!         .unwrap();
147 //! }
148 //! ```
149 //!
150 //! This feature can be controlled using the `SYSTEM_DEPS_$NAME_BUILD_INTERNAL` environment variable
151 //! which can have the following values:
152 //! - `auto`: build the dependency only if the required version has not been found by `pkg-config`;
153 //! - `always`: always build the dependency, ignoring any version which may be installed on the system;
154 //! - `never`: (default) never build the dependency, `system-deps` will fail if the required version is not found on the system.
155 //!
156 //! You can also use the `SYSTEM_DEPS_BUILD_INTERNAL` environment variable with the same values
157 //! defining the behavior for all the dependencies which don't have `SYSTEM_DEPS_$NAME_BUILD_INTERNAL` defined.
158 
159 #![deny(missing_docs)]
160 
161 #[cfg(test)]
162 #[macro_use]
163 extern crate lazy_static;
164 
165 #[cfg(test)]
166 mod test;
167 
168 use heck::{ShoutySnakeCase, SnakeCase};
169 use itertools::Itertools;
170 use std::collections::HashMap;
171 use std::env;
172 use std::fmt;
173 use std::path::{Path, PathBuf};
174 use std::str::FromStr;
175 use strum::IntoEnumIterator;
176 use strum_macros::{EnumIter, EnumString};
177 use thiserror::Error;
178 use version_compare::VersionCompare;
179 
180 mod metadata;
181 use metadata::MetaData;
182 
183 /// system-deps errors
184 #[derive(Error, Debug)]
185 pub enum Error {
186     /// pkg-config error
187     #[error(transparent)]
188     PkgConfig(#[from] pkg_config::Error),
189     /// One of the `Config::add_build_internal` closures failed
190     #[error("Failed to build {0}: {1}")]
191     BuildInternalClosureError(String, #[source] BuildInternalClosureError),
192     /// Failed to read `Cargo.toml`
193     #[error("{0}")]
194     FailToRead(String, #[source] std::io::Error),
195     /// Raised when an error is detected in the metadata defined in `Cargo.toml`
196     #[error("{0}")]
197     InvalidMetadata(String),
198     /// Raised when dependency defined manually using `SYSTEM_DEPS_$NAME_NO_PKG_CONFIG`
199     /// did not define at least one lib using `SYSTEM_DEPS_$NAME_LIB` or
200     /// `SYSTEM_DEPS_$NAME_LIB_FRAMEWORK`
201     #[error("You should define at least one lib using {} or {}", EnvVariable::new_lib(.0).to_string(), EnvVariable::new_lib_framework(.0))]
202     MissingLib(String),
203     /// An environment variable in the form of `SYSTEM_DEPS_$NAME_BUILD_INTERNAL`
204     /// contained an invalid value (allowed: `auto`, `always`, `never`)
205     #[error("{0}")]
206     BuildInternalInvalid(String),
207     /// system-deps has been asked to internally build a lib, through
208     /// `SYSTEM_DEPS_$NAME_BUILD_INTERNAL=always' or `SYSTEM_DEPS_$NAME_BUILD_INTERNAL=auto',
209     /// but not closure has been defined using `Config::add_build_internal` to build
210     /// this lib
211     #[error("Missing build internal closure for {0} (version {1})")]
212     BuildInternalNoClosure(String, String),
213     /// The library which has been build internally does not match the
214     /// required version defined in `Cargo.toml`
215     #[error("Internally built {0} {1} but minimum required version is {2}")]
216     BuildInternalWrongVersion(String, String, String),
217     /// The `cfg()` expression used in `Cargo.toml` is currently not supported
218     #[error("Unsupported cfg() expression: {0}")]
219     UnsupportedCfg(String),
220 }
221 
222 #[derive(Debug, Default)]
223 /// All the system dependencies retrieved by [Config::probe].
224 pub struct Dependencies {
225     libs: HashMap<String, Library>,
226 }
227 
228 impl Dependencies {
229     /// Retrieve details about a system dependency.
230     ///
231     /// # Arguments
232     ///
233     /// * `name`: the name of the `toml` key defining the dependency in `Cargo.toml`
get_by_name(&self, name: &str) -> Option<&Library>234     pub fn get_by_name(&self, name: &str) -> Option<&Library> {
235         self.libs.get(name)
236     }
237 
238     /// An iterator visiting all system dependencies in arbitrary order.
239     /// The first element of the tuple is the name of the `toml` key defining the
240     /// dependency in `Cargo.toml`.
iter(&self) -> impl Iterator<Item = (&str, &Library)>241     pub fn iter(&self) -> impl Iterator<Item = (&str, &Library)> {
242         self.libs.iter().map(|(k, v)| (k.as_str(), v))
243     }
244 
aggregate_str<F: Fn(&Library) -> &Vec<String>>( &self, getter: F, ) -> impl Iterator<Item = &str>245     fn aggregate_str<F: Fn(&Library) -> &Vec<String>>(
246         &self,
247         getter: F,
248     ) -> impl Iterator<Item = &str> {
249         self.libs
250             .values()
251             .map(|l| getter(l))
252             .flatten()
253             .map(|s| s.as_str())
254             .sorted()
255             .dedup()
256     }
257 
aggregate_path_buf<F: Fn(&Library) -> &Vec<PathBuf>>( &self, getter: F, ) -> impl Iterator<Item = &PathBuf>258     fn aggregate_path_buf<F: Fn(&Library) -> &Vec<PathBuf>>(
259         &self,
260         getter: F,
261     ) -> impl Iterator<Item = &PathBuf> {
262         self.libs
263             .values()
264             .map(|l| getter(l))
265             .flatten()
266             .sorted()
267             .dedup()
268     }
269 
270     /// An iterator returning each [Library::libs] of each library, removing duplicates.
all_libs(&self) -> impl Iterator<Item = &str>271     pub fn all_libs(&self) -> impl Iterator<Item = &str> {
272         self.aggregate_str(|l| &l.libs)
273     }
274 
275     /// An iterator returning each [Library::link_paths] of each library, removing duplicates.
all_link_paths(&self) -> impl Iterator<Item = &PathBuf>276     pub fn all_link_paths(&self) -> impl Iterator<Item = &PathBuf> {
277         self.aggregate_path_buf(|l| &l.link_paths)
278     }
279 
280     /// An iterator returning each [Library::frameworks] of each library, removing duplicates.
all_frameworks(&self) -> impl Iterator<Item = &str>281     pub fn all_frameworks(&self) -> impl Iterator<Item = &str> {
282         self.aggregate_str(|l| &l.frameworks)
283     }
284 
285     /// An iterator returning each [Library::framework_paths] of each library, removing duplicates.
all_framework_paths(&self) -> impl Iterator<Item = &PathBuf>286     pub fn all_framework_paths(&self) -> impl Iterator<Item = &PathBuf> {
287         self.aggregate_path_buf(|l| &l.framework_paths)
288     }
289 
290     /// An iterator returning each [Library::include_paths] of each library, removing duplicates.
all_include_paths(&self) -> impl Iterator<Item = &PathBuf>291     pub fn all_include_paths(&self) -> impl Iterator<Item = &PathBuf> {
292         self.aggregate_path_buf(|l| &l.include_paths)
293     }
294 
295     /// An iterator returning each [Library::defines] of each library, removing duplicates.
all_defines(&self) -> impl Iterator<Item = (&str, &Option<String>)>296     pub fn all_defines(&self) -> impl Iterator<Item = (&str, &Option<String>)> {
297         self.libs
298             .values()
299             .map(|l| l.defines.iter())
300             .flatten()
301             .map(|(k, v)| (k.as_str(), v))
302             .sorted()
303             .dedup()
304     }
305 
add(&mut self, name: &str, lib: Library)306     fn add(&mut self, name: &str, lib: Library) {
307         self.libs.insert(name.to_string(), lib);
308     }
309 
override_from_flags(&mut self, env: &EnvVariables)310     fn override_from_flags(&mut self, env: &EnvVariables) {
311         for (name, lib) in self.libs.iter_mut() {
312             if let Some(value) = env.get(&EnvVariable::new_search_native(name)) {
313                 lib.link_paths = split_paths(&value);
314             }
315             if let Some(value) = env.get(&EnvVariable::new_search_framework(name)) {
316                 lib.framework_paths = split_paths(&value);
317             }
318             if let Some(value) = env.get(&EnvVariable::new_lib(name)) {
319                 lib.libs = split_string(&value);
320             }
321             if let Some(value) = env.get(&EnvVariable::new_lib_framework(name)) {
322                 lib.frameworks = split_string(&value);
323             }
324             if let Some(value) = env.get(&EnvVariable::new_include(name)) {
325                 lib.include_paths = split_paths(&value);
326             }
327         }
328     }
329 
gen_flags(&self) -> Result<BuildFlags, Error>330     fn gen_flags(&self) -> Result<BuildFlags, Error> {
331         let mut flags = BuildFlags::new();
332         let mut include_paths = Vec::new();
333 
334         for (name, lib) in self.libs.iter() {
335             include_paths.extend(lib.include_paths.clone());
336 
337             if lib.source == Source::EnvVariables
338                 && lib.libs.is_empty()
339                 && lib.frameworks.is_empty()
340             {
341                 return Err(Error::MissingLib(name.clone()));
342             }
343 
344             lib.link_paths
345                 .iter()
346                 .for_each(|l| flags.add(BuildFlag::SearchNative(l.to_string_lossy().to_string())));
347             lib.framework_paths.iter().for_each(|f| {
348                 flags.add(BuildFlag::SearchFramework(f.to_string_lossy().to_string()))
349             });
350             lib.libs
351                 .iter()
352                 .for_each(|l| flags.add(BuildFlag::Lib(l.clone())));
353             lib.frameworks
354                 .iter()
355                 .for_each(|f| flags.add(BuildFlag::LibFramework(f.clone())));
356         }
357 
358         // Export DEP_$CRATE_INCLUDE env variable with the headers paths,
359         // see https://kornel.ski/rust-sys-crate#headers
360         if !include_paths.is_empty() {
361             if let Ok(paths) = std::env::join_paths(include_paths) {
362                 flags.add(BuildFlag::Include(paths.to_string_lossy().to_string()));
363             }
364         }
365 
366         // Export cargo:rerun-if-env-changed instructions for all env variables affecting system-deps behaviour
367         flags.add(BuildFlag::RerunIfEnvChanged(
368             EnvVariable::new_build_internal(None),
369         ));
370 
371         for (name, _lib) in self.libs.iter() {
372             for var in EnvVariable::iter() {
373                 let var = match var {
374                     EnvVariable::Lib(_) => EnvVariable::new_lib(name),
375                     EnvVariable::LibFramework(_) => EnvVariable::new_lib_framework(name),
376                     EnvVariable::SearchNative(_) => EnvVariable::new_search_native(name),
377                     EnvVariable::SearchFramework(_) => EnvVariable::new_search_framework(name),
378                     EnvVariable::Include(_) => EnvVariable::new_include(name),
379                     EnvVariable::NoPkgConfig(_) => EnvVariable::new_no_pkg_config(name),
380                     EnvVariable::BuildInternal(_) => EnvVariable::new_build_internal(Some(name)),
381                 };
382                 flags.add(BuildFlag::RerunIfEnvChanged(var));
383             }
384         }
385 
386         Ok(flags)
387     }
388 }
389 
390 #[derive(Error, Debug)]
391 /// Error used in return value of `Config::add_build_internal` closures
392 pub enum BuildInternalClosureError {
393     /// `pkg-config` error
394     #[error(transparent)]
395     PkgConfig(#[from] pkg_config::Error),
396     /// General failure
397     #[error("{0}")]
398     Failed(String),
399 }
400 
401 impl BuildInternalClosureError {
402     /// Create a new `BuildInternalClosureError::Failed` representing a general
403     /// failure.
404     ///
405     /// # Arguments
406     ///
407     /// * `details`: human-readable details about the failure
failed(details: &str) -> Self408     pub fn failed(details: &str) -> Self {
409         Self::Failed(details.to_string())
410     }
411 }
412 
413 // enums representing the environment variables user can define to tune system-deps
414 #[derive(Debug, PartialEq, EnumIter)]
415 enum EnvVariable {
416     Lib(String),
417     LibFramework(String),
418     SearchNative(String),
419     SearchFramework(String),
420     Include(String),
421     NoPkgConfig(String),
422     BuildInternal(Option<String>),
423 }
424 
425 impl EnvVariable {
new_lib(lib: &str) -> Self426     fn new_lib(lib: &str) -> Self {
427         Self::Lib(lib.to_string())
428     }
429 
new_lib_framework(lib: &str) -> Self430     fn new_lib_framework(lib: &str) -> Self {
431         Self::LibFramework(lib.to_string())
432     }
433 
new_search_native(lib: &str) -> Self434     fn new_search_native(lib: &str) -> Self {
435         Self::SearchNative(lib.to_string())
436     }
437 
new_search_framework(lib: &str) -> Self438     fn new_search_framework(lib: &str) -> Self {
439         Self::SearchFramework(lib.to_string())
440     }
441 
new_include(lib: &str) -> Self442     fn new_include(lib: &str) -> Self {
443         Self::Include(lib.to_string())
444     }
445 
new_no_pkg_config(lib: &str) -> Self446     fn new_no_pkg_config(lib: &str) -> Self {
447         Self::NoPkgConfig(lib.to_string())
448     }
449 
new_build_internal(lib: Option<&str>) -> Self450     fn new_build_internal(lib: Option<&str>) -> Self {
451         Self::BuildInternal(lib.map(|l| l.to_string()))
452     }
453 
suffix(&self) -> &'static str454     fn suffix(&self) -> &'static str {
455         match self {
456             EnvVariable::Lib(_) => "LIB",
457             EnvVariable::LibFramework(_) => "LIB_FRAMEWORK",
458             EnvVariable::SearchNative(_) => "SEARCH_NATIVE",
459             EnvVariable::SearchFramework(_) => "SEARCH_FRAMEWORK",
460             EnvVariable::Include(_) => "INCLUDE",
461             EnvVariable::NoPkgConfig(_) => "NO_PKG_CONFIG",
462             EnvVariable::BuildInternal(_) => "BUILD_INTERNAL",
463         }
464     }
465 }
466 
467 impl fmt::Display for EnvVariable {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result468     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
469         let suffix = match self {
470             EnvVariable::Lib(lib)
471             | EnvVariable::LibFramework(lib)
472             | EnvVariable::SearchNative(lib)
473             | EnvVariable::SearchFramework(lib)
474             | EnvVariable::Include(lib)
475             | EnvVariable::NoPkgConfig(lib)
476             | EnvVariable::BuildInternal(Some(lib)) => {
477                 format!("{}_{}", lib.to_shouty_snake_case(), self.suffix())
478             }
479             EnvVariable::BuildInternal(None) => self.suffix().to_string(),
480         };
481         write!(f, "SYSTEM_DEPS_{}", suffix)
482     }
483 }
484 
485 type FnBuildInternal =
486     dyn FnOnce(&str, &str) -> std::result::Result<Library, BuildInternalClosureError>;
487 
488 /// Structure used to configure `metadata` before starting to probe for dependencies
489 pub struct Config {
490     env: EnvVariables,
491     build_internals: HashMap<String, Box<FnBuildInternal>>,
492 }
493 
494 impl Default for Config {
default() -> Self495     fn default() -> Self {
496         Self::new_with_env(EnvVariables::Environnement)
497     }
498 }
499 
500 impl Config {
501     /// Create a new set of configuration
new() -> Self502     pub fn new() -> Self {
503         Self::default()
504     }
505 
new_with_env(env: EnvVariables) -> Self506     fn new_with_env(env: EnvVariables) -> Self {
507         Self {
508             env,
509             build_internals: HashMap::new(),
510         }
511     }
512 
513     /// Probe all libraries configured in the Cargo.toml
514     /// `[package.metadata.system-deps]` section.
515     ///
516     /// The returned hash is using the `toml` key defining the dependency as key.
probe(self) -> Result<Dependencies, Error>517     pub fn probe(self) -> Result<Dependencies, Error> {
518         let libraries = self.probe_full()?;
519         let flags = libraries.gen_flags()?;
520 
521         // Output cargo flags
522         println!("{}", flags);
523 
524         for (name, _) in libraries.iter() {
525             println!("cargo:rustc-cfg=system_deps_have_{}", name.to_snake_case());
526         }
527 
528         Ok(libraries)
529     }
530 
531     /// Add hook so system-deps can internally build library `name` if requested by user.
532     ///
533     /// It will only be triggered if the environment variable
534     /// `SYSTEM_DEPS_$NAME_BUILD_INTERNAL` is defined with either `always` or
535     /// `auto` as value. In the latter case, `func` is called only if the requested
536     /// version of the library was not found on the system.
537     ///
538     /// # Arguments
539     /// * `name`: the name of the library, as defined in `Cargo.toml`
540     /// * `func`: closure called when internally building the library.
541     /// It receives as argument the library name, and the minimum version required.
add_build_internal<F>(self, name: &str, func: F) -> Self where F: 'static + FnOnce(&str, &str) -> std::result::Result<Library, BuildInternalClosureError>,542     pub fn add_build_internal<F>(self, name: &str, func: F) -> Self
543     where
544         F: 'static + FnOnce(&str, &str) -> std::result::Result<Library, BuildInternalClosureError>,
545     {
546         let mut build_internals = self.build_internals;
547         build_internals.insert(name.to_string(), Box::new(func));
548 
549         Self {
550             env: self.env,
551             build_internals,
552         }
553     }
554 
probe_full(mut self) -> Result<Dependencies, Error>555     fn probe_full(mut self) -> Result<Dependencies, Error> {
556         let mut libraries = self.probe_pkg_config()?;
557         libraries.override_from_flags(&self.env);
558 
559         Ok(libraries)
560     }
561 
probe_pkg_config(&mut self) -> Result<Dependencies, Error>562     fn probe_pkg_config(&mut self) -> Result<Dependencies, Error> {
563         let dir = self
564             .env
565             .get("CARGO_MANIFEST_DIR")
566             .ok_or_else(|| Error::InvalidMetadata("$CARGO_MANIFEST_DIR not set".into()))?;
567         let mut path = PathBuf::from(dir);
568         path.push("Cargo.toml");
569 
570         let metadata = MetaData::from_file(&path)?;
571 
572         let mut libraries = Dependencies::default();
573 
574         for dep in metadata.deps.iter() {
575             if let Some(cfg) = &dep.cfg {
576                 // Check if `cfg()` expression matches the target settings
577                 if !self.check_cfg(cfg)? {
578                     continue;
579                 }
580             }
581 
582             let mut enabled_feature_overrides = Vec::new();
583 
584             for o in dep.version_overrides.iter() {
585                 if self.has_feature(&o.key) {
586                     enabled_feature_overrides.push(o);
587                 }
588             }
589 
590             if let Some(feature) = dep.feature.as_ref() {
591                 if !self.has_feature(feature) {
592                     continue;
593                 }
594             }
595 
596             let (version, lib_name, optional) = {
597                 // Pick the highest feature enabled version
598                 if !enabled_feature_overrides.is_empty() {
599                     enabled_feature_overrides.sort_by(|a, b| {
600                         VersionCompare::compare(&a.version, &b.version)
601                             .expect("failed to compare versions")
602                             .ord()
603                             .expect("invalid version")
604                     });
605                     let highest = enabled_feature_overrides.into_iter().last().unwrap();
606                     (
607                         Some(&highest.version),
608                         highest.name.clone().unwrap_or_else(|| dep.lib_name()),
609                         highest.optional.unwrap_or(dep.optional),
610                     )
611                 } else {
612                     (dep.version.as_ref(), dep.lib_name(), dep.optional)
613                 }
614             };
615 
616             let version = version.ok_or_else(|| {
617                 Error::InvalidMetadata(format!("No version defined for {}", dep.key))
618             })?;
619 
620             let name = &dep.key;
621             let build_internal = self.get_build_internal_status(name)?;
622 
623             let library = if self.env.contains(&EnvVariable::new_no_pkg_config(name)) {
624                 Library::from_env_variables(name)
625             } else if build_internal == BuildInternal::Always {
626                 self.call_build_internal(&lib_name, version)?
627             } else {
628                 match pkg_config::Config::new()
629                     .atleast_version(version)
630                     .print_system_libs(false)
631                     .cargo_metadata(false)
632                     .probe(&lib_name)
633                 {
634                     Ok(lib) => Library::from_pkg_config(&lib_name, lib),
635                     Err(e) => {
636                         if build_internal == BuildInternal::Auto {
637                             // Try building the lib internally as a fallback
638                             self.call_build_internal(name, version)?
639                         } else if optional {
640                             // If the dep is optional just skip it
641                             continue;
642                         } else {
643                             return Err(e.into());
644                         }
645                     }
646                 }
647             };
648 
649             libraries.add(name, library);
650         }
651         Ok(libraries)
652     }
653 
get_build_internal_env_var(&self, var: EnvVariable) -> Result<Option<BuildInternal>, Error>654     fn get_build_internal_env_var(&self, var: EnvVariable) -> Result<Option<BuildInternal>, Error> {
655         match self.env.get(&var).as_deref() {
656             Some(s) => {
657                 let b = BuildInternal::from_str(s).map_err(|_| {
658                     Error::BuildInternalInvalid(format!(
659                         "Invalid value in {}: {} (allowed: 'auto', 'always', 'never')",
660                         var, s
661                     ))
662                 })?;
663                 Ok(Some(b))
664             }
665             None => Ok(None),
666         }
667     }
668 
get_build_internal_status(&self, name: &str) -> Result<BuildInternal, Error>669     fn get_build_internal_status(&self, name: &str) -> Result<BuildInternal, Error> {
670         match self.get_build_internal_env_var(EnvVariable::new_build_internal(Some(name)))? {
671             Some(b) => Ok(b),
672             None => Ok(self
673                 .get_build_internal_env_var(EnvVariable::new_build_internal(None))?
674                 .unwrap_or_default()),
675         }
676     }
677 
call_build_internal(&mut self, name: &str, version: &str) -> Result<Library, Error>678     fn call_build_internal(&mut self, name: &str, version: &str) -> Result<Library, Error> {
679         let lib = match self.build_internals.remove(name) {
680             Some(f) => {
681                 f(name, version).map_err(|e| Error::BuildInternalClosureError(name.into(), e))?
682             }
683             None => return Err(Error::BuildInternalNoClosure(name.into(), version.into())),
684         };
685 
686         // Check that the lib built internally matches the required version
687         match VersionCompare::compare(&lib.version, version) {
688             Ok(version_compare::CompOp::Lt) => Err(Error::BuildInternalWrongVersion(
689                 name.into(),
690                 lib.version.clone(),
691                 version.into(),
692             )),
693             _ => Ok(lib),
694         }
695     }
696 
has_feature(&self, feature: &str) -> bool697     fn has_feature(&self, feature: &str) -> bool {
698         let var: &str = &format!("CARGO_FEATURE_{}", feature.to_uppercase().replace('-', "_"));
699         self.env.contains(var)
700     }
701 
check_cfg(&self, cfg: &cfg_expr::Expression) -> Result<bool, Error>702     fn check_cfg(&self, cfg: &cfg_expr::Expression) -> Result<bool, Error> {
703         use cfg_expr::{targets::get_builtin_target_by_triple, Predicate};
704 
705         let target = self
706             .env
707             .get("TARGET")
708             .expect("no TARGET env variable defined");
709         let target = get_builtin_target_by_triple(&target)
710             .unwrap_or_else(|| panic!("Invalid TARGET: {}", target));
711 
712         let res = cfg.eval(|pred| match pred {
713             Predicate::Target(tp) => Some(tp.matches(target)),
714             _ => None,
715         });
716 
717         res.ok_or_else(|| Error::UnsupportedCfg(cfg.original().to_string()))
718     }
719 }
720 
721 #[derive(Debug, PartialEq)]
722 /// From where the library settings have been retrieved
723 pub enum Source {
724     /// Settings have been retrieved from `pkg-config`
725     PkgConfig,
726     /// Settings have been defined using user defined environment variables
727     EnvVariables,
728 }
729 
730 #[derive(Debug)]
731 /// A system dependency
732 pub struct Library {
733     /// Name of the library
734     pub name: String,
735     /// From where the library settings have been retrieved
736     pub source: Source,
737     /// libraries the linker should link on
738     pub libs: Vec<String>,
739     /// directories where the compiler should look for libraries
740     pub link_paths: Vec<PathBuf>,
741     /// frameworks the linker should link on
742     pub frameworks: Vec<String>,
743     /// directories where the compiler should look for frameworks
744     pub framework_paths: Vec<PathBuf>,
745     /// directories where the compiler should look for header files
746     pub include_paths: Vec<PathBuf>,
747     /// macros that should be defined by the compiler
748     pub defines: HashMap<String, Option<String>>,
749     /// library version
750     pub version: String,
751 }
752 
753 impl Library {
from_pkg_config(name: &str, l: pkg_config::Library) -> Self754     fn from_pkg_config(name: &str, l: pkg_config::Library) -> Self {
755         Self {
756             name: name.to_string(),
757             source: Source::PkgConfig,
758             libs: l.libs,
759             link_paths: l.link_paths,
760             include_paths: l.include_paths,
761             frameworks: l.frameworks,
762             framework_paths: l.framework_paths,
763             defines: l.defines,
764             version: l.version,
765         }
766     }
767 
from_env_variables(name: &str) -> Self768     fn from_env_variables(name: &str) -> Self {
769         Self {
770             name: name.to_string(),
771             source: Source::EnvVariables,
772             libs: Vec::new(),
773             link_paths: Vec::new(),
774             include_paths: Vec::new(),
775             frameworks: Vec::new(),
776             framework_paths: Vec::new(),
777             defines: HashMap::new(),
778             version: String::new(),
779         }
780     }
781 
782     /// Create a `Library` by probing `pkg-config` on an internal directory.
783     /// This helper is meant to be used by `Config::add_build_internal` closures
784     /// after having built the lib to return the library information to system-deps.
785     ///
786     /// # Arguments
787     ///
788     /// * `pkg_config_dir`: the directory where the library `.pc` file is located
789     /// * `lib`: the name of the library to look for
790     /// * `version`: the minimum version of `lib` required
791     ///
792     /// # Examples
793     ///
794     /// ```
795     /// let mut config = system_deps::Config::new();
796     /// config.add_build_internal("mylib", |lib, version| {
797     ///   // Actually build the library here
798     ///   system_deps::Library::from_internal_pkg_config("build-dir",
799     ///       lib, version)
800     /// });
801     /// ```
from_internal_pkg_config<P>( pkg_config_dir: P, lib: &str, version: &str, ) -> Result<Self, BuildInternalClosureError> where P: AsRef<Path>,802     pub fn from_internal_pkg_config<P>(
803         pkg_config_dir: P,
804         lib: &str,
805         version: &str,
806     ) -> Result<Self, BuildInternalClosureError>
807     where
808         P: AsRef<Path>,
809     {
810         // save current PKG_CONFIG_PATH, so we can restore it
811         let old = env::var("PKG_CONFIG_PATH");
812 
813         match old {
814             Ok(ref s) => {
815                 let mut paths = env::split_paths(s).collect::<Vec<_>>();
816                 paths.push(PathBuf::from(pkg_config_dir.as_ref()));
817                 let paths = env::join_paths(paths).unwrap();
818                 env::set_var("PKG_CONFIG_PATH", paths)
819             }
820             Err(_) => env::set_var("PKG_CONFIG_PATH", pkg_config_dir.as_ref()),
821         }
822 
823         let pkg_lib = pkg_config::Config::new()
824             .atleast_version(version)
825             .print_system_libs(false)
826             .cargo_metadata(false)
827             .probe(lib);
828 
829         env::set_var("PKG_CONFIG_PATH", &old.unwrap_or_else(|_| "".into()));
830 
831         match pkg_lib {
832             Ok(pkg_lib) => Ok(Self::from_pkg_config(lib, pkg_lib)),
833             Err(e) => Err(e.into()),
834         }
835     }
836 }
837 
838 #[derive(Debug)]
839 enum EnvVariables {
840     Environnement,
841     #[cfg(test)]
842     Mock(HashMap<&'static str, String>),
843 }
844 
845 trait EnvVariablesExt<T> {
contains(&self, var: T) -> bool846     fn contains(&self, var: T) -> bool {
847         self.get(var).is_some()
848     }
get(&self, var: T) -> Option<String>849     fn get(&self, var: T) -> Option<String>;
850 }
851 
852 impl EnvVariablesExt<&str> for EnvVariables {
get(&self, var: &str) -> Option<String>853     fn get(&self, var: &str) -> Option<String> {
854         match self {
855             EnvVariables::Environnement => env::var(var).ok(),
856             #[cfg(test)]
857             EnvVariables::Mock(vars) => vars.get(var).cloned(),
858         }
859     }
860 }
861 
862 impl EnvVariablesExt<&EnvVariable> for EnvVariables {
get(&self, var: &EnvVariable) -> Option<String>863     fn get(&self, var: &EnvVariable) -> Option<String> {
864         let s = var.to_string();
865         let var: &str = s.as_ref();
866         self.get(var)
867     }
868 }
869 
870 // TODO: add support for "rustc-link-lib=static=" ?
871 #[derive(Debug, PartialEq)]
872 enum BuildFlag {
873     Include(String),
874     SearchNative(String),
875     SearchFramework(String),
876     Lib(String),
877     LibFramework(String),
878     RerunIfEnvChanged(EnvVariable),
879 }
880 
881 impl fmt::Display for BuildFlag {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result882     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
883         match self {
884             BuildFlag::Include(paths) => write!(f, "include={}", paths),
885             BuildFlag::SearchNative(lib) => write!(f, "rustc-link-search=native={}", lib),
886             BuildFlag::SearchFramework(lib) => write!(f, "rustc-link-search=framework={}", lib),
887             BuildFlag::Lib(lib) => write!(f, "rustc-link-lib={}", lib),
888             BuildFlag::LibFramework(lib) => write!(f, "rustc-link-lib=framework={}", lib),
889             BuildFlag::RerunIfEnvChanged(env) => write!(f, "rerun-if-env-changed={}", env),
890         }
891     }
892 }
893 
894 #[derive(Debug, PartialEq)]
895 struct BuildFlags(Vec<BuildFlag>);
896 
897 impl BuildFlags {
new() -> Self898     fn new() -> Self {
899         Self(Vec::new())
900     }
901 
add(&mut self, flag: BuildFlag)902     fn add(&mut self, flag: BuildFlag) {
903         self.0.push(flag);
904     }
905 }
906 
907 impl fmt::Display for BuildFlags {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result908     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
909         for flag in self.0.iter() {
910             writeln!(f, "cargo:{}", flag)?;
911         }
912         Ok(())
913     }
914 }
915 
split_paths(value: &str) -> Vec<PathBuf>916 fn split_paths(value: &str) -> Vec<PathBuf> {
917     if !value.is_empty() {
918         let paths = env::split_paths(&value);
919         paths.map(|p| Path::new(&p).into()).collect()
920     } else {
921         Vec::new()
922     }
923 }
924 
split_string(value: &str) -> Vec<String>925 fn split_string(value: &str) -> Vec<String> {
926     if !value.is_empty() {
927         value.split(' ').map(|s| s.to_string()).collect()
928     } else {
929         Vec::new()
930     }
931 }
932 
933 #[derive(Debug, PartialEq, EnumString)]
934 #[strum(serialize_all = "snake_case")]
935 enum BuildInternal {
936     Auto,
937     Always,
938     Never,
939 }
940 
941 impl Default for BuildInternal {
default() -> Self942     fn default() -> Self {
943         BuildInternal::Never
944     }
945 }
946