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 //! # Internally build system libraries
136 //!
137 //! `-sys` crates can provide support for building and statically link their underlying system library as part of their build process.
138 //! Here is how to do this in your `build.rs`:
139 //! ```should_panic
140 //! fn main() {
141 //!     system_deps::Config::new()
142 //!         .add_build_internal("testlib", |lib, version| {
143 //!             // Actually build the library here
144 //!             system_deps::Library::from_internal_pkg_config("build/path-to-pc-file", lib, version)
145 //!          })
146 //!         .probe()
147 //!         .unwrap();
148 //! }
149 //! ```
150 //!
151 //! This feature can be controlled using the `SYSTEM_DEPS_$NAME_BUILD_INTERNAL` environment variable
152 //! which can have the following values:
153 //! - `auto`: build the dependency only if the required version has not been found by `pkg-config`;
154 //! - `always`: always build the dependency, ignoring any version which may be installed on the system;
155 //! - `never`: (default) never build the dependency, `system-deps` will fail if the required version is not found on the system.
156 //!
157 //! You can also use the `SYSTEM_DEPS_BUILD_INTERNAL` environment variable with the same values
158 //! defining the behavior for all the dependencies which don't have `SYSTEM_DEPS_$NAME_BUILD_INTERNAL` defined.
159 //!
160 //! # Static linking
161 //!
162 //! By default all libraries are dynamically linked, except when build internally as [described above](#internally-build-system-libraries).
163 //! Libraries can be statically linked by defining the environment variable `SYSTEM_DEPS_$NAME_LINK=static`.
164 //! You can also use `SYSTEM_DEPS_LINK=static` to statically link all the libraries.
165 
166 #![deny(missing_docs)]
167 
168 #[cfg(test)]
169 #[macro_use]
170 extern crate lazy_static;
171 
172 #[cfg(test)]
173 mod test;
174 
175 use heck::{ShoutySnakeCase, SnakeCase};
176 use itertools::Itertools;
177 use std::collections::HashMap;
178 use std::env;
179 use std::fmt;
180 use std::path::{Path, PathBuf};
181 use std::str::FromStr;
182 use strum::IntoEnumIterator;
183 use strum_macros::{EnumIter, EnumString};
184 use thiserror::Error;
185 use version_compare::VersionCompare;
186 
187 mod metadata;
188 use metadata::MetaData;
189 
190 /// system-deps errors
191 #[derive(Error, Debug)]
192 pub enum Error {
193     /// pkg-config error
194     #[error(transparent)]
195     PkgConfig(#[from] pkg_config::Error),
196     /// One of the `Config::add_build_internal` closures failed
197     #[error("Failed to build {0}: {1}")]
198     BuildInternalClosureError(String, #[source] BuildInternalClosureError),
199     /// Failed to read `Cargo.toml`
200     #[error("{0}")]
201     FailToRead(String, #[source] std::io::Error),
202     /// Raised when an error is detected in the metadata defined in `Cargo.toml`
203     #[error("{0}")]
204     InvalidMetadata(String),
205     /// Raised when dependency defined manually using `SYSTEM_DEPS_$NAME_NO_PKG_CONFIG`
206     /// did not define at least one lib using `SYSTEM_DEPS_$NAME_LIB` or
207     /// `SYSTEM_DEPS_$NAME_LIB_FRAMEWORK`
208     #[error("You should define at least one lib using {} or {}", EnvVariable::new_lib(.0).to_string(), EnvVariable::new_lib_framework(.0))]
209     MissingLib(String),
210     /// An environment variable in the form of `SYSTEM_DEPS_$NAME_BUILD_INTERNAL`
211     /// contained an invalid value (allowed: `auto`, `always`, `never`)
212     #[error("{0}")]
213     BuildInternalInvalid(String),
214     /// system-deps has been asked to internally build a lib, through
215     /// `SYSTEM_DEPS_$NAME_BUILD_INTERNAL=always' or `SYSTEM_DEPS_$NAME_BUILD_INTERNAL=auto',
216     /// but not closure has been defined using `Config::add_build_internal` to build
217     /// this lib
218     #[error("Missing build internal closure for {0} (version {1})")]
219     BuildInternalNoClosure(String, String),
220     /// The library which has been build internally does not match the
221     /// required version defined in `Cargo.toml`
222     #[error("Internally built {0} {1} but minimum required version is {2}")]
223     BuildInternalWrongVersion(String, String, String),
224     /// The `cfg()` expression used in `Cargo.toml` is currently not supported
225     #[error("Unsupported cfg() expression: {0}")]
226     UnsupportedCfg(String),
227 }
228 
229 #[derive(Debug, Default)]
230 /// All the system dependencies retrieved by [Config::probe].
231 pub struct Dependencies {
232     libs: HashMap<String, Library>,
233 }
234 
235 impl Dependencies {
236     /// Retrieve details about a system dependency.
237     ///
238     /// # Arguments
239     ///
240     /// * `name`: the name of the `toml` key defining the dependency in `Cargo.toml`
get_by_name(&self, name: &str) -> Option<&Library>241     pub fn get_by_name(&self, name: &str) -> Option<&Library> {
242         self.libs.get(name)
243     }
244 
245     /// An iterator visiting all system dependencies in arbitrary order.
246     /// The first element of the tuple is the name of the `toml` key defining the
247     /// dependency in `Cargo.toml`.
iter(&self) -> impl Iterator<Item = (&str, &Library)>248     pub fn iter(&self) -> impl Iterator<Item = (&str, &Library)> {
249         self.libs.iter().map(|(k, v)| (k.as_str(), v))
250     }
251 
aggregate_str<F: Fn(&Library) -> &Vec<String>>( &self, getter: F, ) -> impl Iterator<Item = &str>252     fn aggregate_str<F: Fn(&Library) -> &Vec<String>>(
253         &self,
254         getter: F,
255     ) -> impl Iterator<Item = &str> {
256         self.libs
257             .values()
258             .map(|l| getter(l))
259             .flatten()
260             .map(|s| s.as_str())
261             .sorted()
262             .dedup()
263     }
264 
aggregate_path_buf<F: Fn(&Library) -> &Vec<PathBuf>>( &self, getter: F, ) -> impl Iterator<Item = &PathBuf>265     fn aggregate_path_buf<F: Fn(&Library) -> &Vec<PathBuf>>(
266         &self,
267         getter: F,
268     ) -> impl Iterator<Item = &PathBuf> {
269         self.libs
270             .values()
271             .map(|l| getter(l))
272             .flatten()
273             .sorted()
274             .dedup()
275     }
276 
277     /// An iterator returning each [Library::libs] of each library, removing duplicates.
all_libs(&self) -> impl Iterator<Item = &str>278     pub fn all_libs(&self) -> impl Iterator<Item = &str> {
279         self.aggregate_str(|l| &l.libs)
280     }
281 
282     /// An iterator returning each [Library::link_paths] of each library, removing duplicates.
all_link_paths(&self) -> impl Iterator<Item = &PathBuf>283     pub fn all_link_paths(&self) -> impl Iterator<Item = &PathBuf> {
284         self.aggregate_path_buf(|l| &l.link_paths)
285     }
286 
287     /// An iterator returning each [Library::frameworks] of each library, removing duplicates.
all_frameworks(&self) -> impl Iterator<Item = &str>288     pub fn all_frameworks(&self) -> impl Iterator<Item = &str> {
289         self.aggregate_str(|l| &l.frameworks)
290     }
291 
292     /// An iterator returning each [Library::framework_paths] of each library, removing duplicates.
all_framework_paths(&self) -> impl Iterator<Item = &PathBuf>293     pub fn all_framework_paths(&self) -> impl Iterator<Item = &PathBuf> {
294         self.aggregate_path_buf(|l| &l.framework_paths)
295     }
296 
297     /// An iterator returning each [Library::include_paths] of each library, removing duplicates.
all_include_paths(&self) -> impl Iterator<Item = &PathBuf>298     pub fn all_include_paths(&self) -> impl Iterator<Item = &PathBuf> {
299         self.aggregate_path_buf(|l| &l.include_paths)
300     }
301 
302     /// An iterator returning each [Library::defines] of each library, removing duplicates.
all_defines(&self) -> impl Iterator<Item = (&str, &Option<String>)>303     pub fn all_defines(&self) -> impl Iterator<Item = (&str, &Option<String>)> {
304         self.libs
305             .values()
306             .map(|l| l.defines.iter())
307             .flatten()
308             .map(|(k, v)| (k.as_str(), v))
309             .sorted()
310             .dedup()
311     }
312 
add(&mut self, name: &str, lib: Library)313     fn add(&mut self, name: &str, lib: Library) {
314         self.libs.insert(name.to_string(), lib);
315     }
316 
override_from_flags(&mut self, env: &EnvVariables)317     fn override_from_flags(&mut self, env: &EnvVariables) {
318         for (name, lib) in self.libs.iter_mut() {
319             if let Some(value) = env.get(&EnvVariable::new_search_native(name)) {
320                 lib.link_paths = split_paths(&value);
321             }
322             if let Some(value) = env.get(&EnvVariable::new_search_framework(name)) {
323                 lib.framework_paths = split_paths(&value);
324             }
325             if let Some(value) = env.get(&EnvVariable::new_lib(name)) {
326                 lib.libs = split_string(&value);
327             }
328             if let Some(value) = env.get(&EnvVariable::new_lib_framework(name)) {
329                 lib.frameworks = split_string(&value);
330             }
331             if let Some(value) = env.get(&EnvVariable::new_include(name)) {
332                 lib.include_paths = split_paths(&value);
333             }
334         }
335     }
336 
gen_flags(&self) -> Result<BuildFlags, Error>337     fn gen_flags(&self) -> Result<BuildFlags, Error> {
338         let mut flags = BuildFlags::new();
339         let mut include_paths = Vec::new();
340 
341         for (name, lib) in self.libs.iter() {
342             include_paths.extend(lib.include_paths.clone());
343 
344             if lib.source == Source::EnvVariables
345                 && lib.libs.is_empty()
346                 && lib.frameworks.is_empty()
347             {
348                 return Err(Error::MissingLib(name.clone()));
349             }
350 
351             lib.link_paths
352                 .iter()
353                 .for_each(|l| flags.add(BuildFlag::SearchNative(l.to_string_lossy().to_string())));
354             lib.framework_paths.iter().for_each(|f| {
355                 flags.add(BuildFlag::SearchFramework(f.to_string_lossy().to_string()))
356             });
357             lib.libs
358                 .iter()
359                 .for_each(|l| flags.add(BuildFlag::Lib(l.clone(), lib.statik)));
360             lib.frameworks
361                 .iter()
362                 .for_each(|f| flags.add(BuildFlag::LibFramework(f.clone())));
363         }
364 
365         // Export DEP_$CRATE_INCLUDE env variable with the headers paths,
366         // see https://kornel.ski/rust-sys-crate#headers
367         if !include_paths.is_empty() {
368             if let Ok(paths) = std::env::join_paths(include_paths) {
369                 flags.add(BuildFlag::Include(paths.to_string_lossy().to_string()));
370             }
371         }
372 
373         // Export cargo:rerun-if-env-changed instructions for all env variables affecting system-deps behaviour
374         flags.add(BuildFlag::RerunIfEnvChanged(
375             EnvVariable::new_build_internal(None),
376         ));
377         flags.add(BuildFlag::RerunIfEnvChanged(EnvVariable::new_link(None)));
378 
379         for (name, _lib) in self.libs.iter() {
380             for var in EnvVariable::iter() {
381                 let var = match var {
382                     EnvVariable::Lib(_) => EnvVariable::new_lib(name),
383                     EnvVariable::LibFramework(_) => EnvVariable::new_lib_framework(name),
384                     EnvVariable::SearchNative(_) => EnvVariable::new_search_native(name),
385                     EnvVariable::SearchFramework(_) => EnvVariable::new_search_framework(name),
386                     EnvVariable::Include(_) => EnvVariable::new_include(name),
387                     EnvVariable::NoPkgConfig(_) => EnvVariable::new_no_pkg_config(name),
388                     EnvVariable::BuildInternal(_) => EnvVariable::new_build_internal(Some(name)),
389                     EnvVariable::Link(_) => EnvVariable::new_link(Some(name)),
390                 };
391                 flags.add(BuildFlag::RerunIfEnvChanged(var));
392             }
393         }
394 
395         Ok(flags)
396     }
397 }
398 
399 #[derive(Error, Debug)]
400 /// Error used in return value of `Config::add_build_internal` closures
401 pub enum BuildInternalClosureError {
402     /// `pkg-config` error
403     #[error(transparent)]
404     PkgConfig(#[from] pkg_config::Error),
405     /// General failure
406     #[error("{0}")]
407     Failed(String),
408 }
409 
410 impl BuildInternalClosureError {
411     /// Create a new `BuildInternalClosureError::Failed` representing a general
412     /// failure.
413     ///
414     /// # Arguments
415     ///
416     /// * `details`: human-readable details about the failure
failed(details: &str) -> Self417     pub fn failed(details: &str) -> Self {
418         Self::Failed(details.to_string())
419     }
420 }
421 
422 // enums representing the environment variables user can define to tune system-deps
423 #[derive(Debug, PartialEq, EnumIter)]
424 enum EnvVariable {
425     Lib(String),
426     LibFramework(String),
427     SearchNative(String),
428     SearchFramework(String),
429     Include(String),
430     NoPkgConfig(String),
431     BuildInternal(Option<String>),
432     Link(Option<String>),
433 }
434 
435 impl EnvVariable {
new_lib(lib: &str) -> Self436     fn new_lib(lib: &str) -> Self {
437         Self::Lib(lib.to_string())
438     }
439 
new_lib_framework(lib: &str) -> Self440     fn new_lib_framework(lib: &str) -> Self {
441         Self::LibFramework(lib.to_string())
442     }
443 
new_search_native(lib: &str) -> Self444     fn new_search_native(lib: &str) -> Self {
445         Self::SearchNative(lib.to_string())
446     }
447 
new_search_framework(lib: &str) -> Self448     fn new_search_framework(lib: &str) -> Self {
449         Self::SearchFramework(lib.to_string())
450     }
451 
new_include(lib: &str) -> Self452     fn new_include(lib: &str) -> Self {
453         Self::Include(lib.to_string())
454     }
455 
new_no_pkg_config(lib: &str) -> Self456     fn new_no_pkg_config(lib: &str) -> Self {
457         Self::NoPkgConfig(lib.to_string())
458     }
459 
new_build_internal(lib: Option<&str>) -> Self460     fn new_build_internal(lib: Option<&str>) -> Self {
461         Self::BuildInternal(lib.map(|l| l.to_string()))
462     }
463 
new_link(lib: Option<&str>) -> Self464     fn new_link(lib: Option<&str>) -> Self {
465         Self::Link(lib.map(|l| l.to_string()))
466     }
467 
suffix(&self) -> &'static str468     fn suffix(&self) -> &'static str {
469         match self {
470             EnvVariable::Lib(_) => "LIB",
471             EnvVariable::LibFramework(_) => "LIB_FRAMEWORK",
472             EnvVariable::SearchNative(_) => "SEARCH_NATIVE",
473             EnvVariable::SearchFramework(_) => "SEARCH_FRAMEWORK",
474             EnvVariable::Include(_) => "INCLUDE",
475             EnvVariable::NoPkgConfig(_) => "NO_PKG_CONFIG",
476             EnvVariable::BuildInternal(_) => "BUILD_INTERNAL",
477             EnvVariable::Link(_) => "LINK",
478         }
479     }
480 }
481 
482 impl fmt::Display for EnvVariable {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result483     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
484         let suffix = match self {
485             EnvVariable::Lib(lib)
486             | EnvVariable::LibFramework(lib)
487             | EnvVariable::SearchNative(lib)
488             | EnvVariable::SearchFramework(lib)
489             | EnvVariable::Include(lib)
490             | EnvVariable::NoPkgConfig(lib)
491             | EnvVariable::BuildInternal(Some(lib))
492             | EnvVariable::Link(Some(lib)) => {
493                 format!("{}_{}", lib.to_shouty_snake_case(), self.suffix())
494             }
495             EnvVariable::BuildInternal(None) | EnvVariable::Link(None) => self.suffix().to_string(),
496         };
497         write!(f, "SYSTEM_DEPS_{}", suffix)
498     }
499 }
500 
501 type FnBuildInternal =
502     dyn FnOnce(&str, &str) -> std::result::Result<Library, BuildInternalClosureError>;
503 
504 /// Structure used to configure `metadata` before starting to probe for dependencies
505 pub struct Config {
506     env: EnvVariables,
507     build_internals: HashMap<String, Box<FnBuildInternal>>,
508 }
509 
510 impl Default for Config {
default() -> Self511     fn default() -> Self {
512         Self::new_with_env(EnvVariables::Environnement)
513     }
514 }
515 
516 impl Config {
517     /// Create a new set of configuration
new() -> Self518     pub fn new() -> Self {
519         Self::default()
520     }
521 
new_with_env(env: EnvVariables) -> Self522     fn new_with_env(env: EnvVariables) -> Self {
523         Self {
524             env,
525             build_internals: HashMap::new(),
526         }
527     }
528 
529     /// Probe all libraries configured in the Cargo.toml
530     /// `[package.metadata.system-deps]` section.
531     ///
532     /// The returned hash is using the `toml` key defining the dependency as key.
probe(self) -> Result<Dependencies, Error>533     pub fn probe(self) -> Result<Dependencies, Error> {
534         let libraries = self.probe_full()?;
535         let flags = libraries.gen_flags()?;
536 
537         // Output cargo flags
538         println!("{}", flags);
539 
540         for (name, _) in libraries.iter() {
541             println!("cargo:rustc-cfg=system_deps_have_{}", name.to_snake_case());
542         }
543 
544         Ok(libraries)
545     }
546 
547     /// Add hook so system-deps can internally build library `name` if requested by user.
548     ///
549     /// It will only be triggered if the environment variable
550     /// `SYSTEM_DEPS_$NAME_BUILD_INTERNAL` is defined with either `always` or
551     /// `auto` as value. In the latter case, `func` is called only if the requested
552     /// version of the library was not found on the system.
553     ///
554     /// # Arguments
555     /// * `name`: the name of the library, as defined in `Cargo.toml`
556     /// * `func`: closure called when internally building the library.
557     /// 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>,558     pub fn add_build_internal<F>(self, name: &str, func: F) -> Self
559     where
560         F: 'static + FnOnce(&str, &str) -> std::result::Result<Library, BuildInternalClosureError>,
561     {
562         let mut build_internals = self.build_internals;
563         build_internals.insert(name.to_string(), Box::new(func));
564 
565         Self {
566             env: self.env,
567             build_internals,
568         }
569     }
570 
probe_full(mut self) -> Result<Dependencies, Error>571     fn probe_full(mut self) -> Result<Dependencies, Error> {
572         let mut libraries = self.probe_pkg_config()?;
573         libraries.override_from_flags(&self.env);
574 
575         Ok(libraries)
576     }
577 
probe_pkg_config(&mut self) -> Result<Dependencies, Error>578     fn probe_pkg_config(&mut self) -> Result<Dependencies, Error> {
579         let dir = self
580             .env
581             .get("CARGO_MANIFEST_DIR")
582             .ok_or_else(|| Error::InvalidMetadata("$CARGO_MANIFEST_DIR not set".into()))?;
583         let mut path = PathBuf::from(dir);
584         path.push("Cargo.toml");
585 
586         let metadata = MetaData::from_file(&path)?;
587 
588         let mut libraries = Dependencies::default();
589 
590         for dep in metadata.deps.iter() {
591             if let Some(cfg) = &dep.cfg {
592                 // Check if `cfg()` expression matches the target settings
593                 if !self.check_cfg(cfg)? {
594                     continue;
595                 }
596             }
597 
598             let mut enabled_feature_overrides = Vec::new();
599 
600             for o in dep.version_overrides.iter() {
601                 if self.has_feature(&o.key) {
602                     enabled_feature_overrides.push(o);
603                 }
604             }
605 
606             if let Some(feature) = dep.feature.as_ref() {
607                 if !self.has_feature(feature) {
608                     continue;
609                 }
610             }
611 
612             let (version, lib_name, optional) = {
613                 // Pick the highest feature enabled version
614                 if !enabled_feature_overrides.is_empty() {
615                     enabled_feature_overrides.sort_by(|a, b| {
616                         VersionCompare::compare(&a.version, &b.version)
617                             .expect("failed to compare versions")
618                             .ord()
619                             .expect("invalid version")
620                     });
621                     let highest = enabled_feature_overrides.into_iter().last().unwrap();
622                     (
623                         Some(&highest.version),
624                         highest.name.clone().unwrap_or_else(|| dep.lib_name()),
625                         highest.optional.unwrap_or(dep.optional),
626                     )
627                 } else {
628                     (dep.version.as_ref(), dep.lib_name(), dep.optional)
629                 }
630             };
631 
632             let version = version.ok_or_else(|| {
633                 Error::InvalidMetadata(format!("No version defined for {}", dep.key))
634             })?;
635 
636             let name = &dep.key;
637             let build_internal = self.get_build_internal_status(name)?;
638 
639             // should the lib be statically linked?
640             let statik = self
641                 .env
642                 .has_value(&EnvVariable::new_link(Some(name)), "static")
643                 || self.env.has_value(&EnvVariable::new_link(None), "static");
644 
645             let mut library = if self.env.contains(&EnvVariable::new_no_pkg_config(name)) {
646                 Library::from_env_variables(name)
647             } else if build_internal == BuildInternal::Always {
648                 self.call_build_internal(&lib_name, version)?
649             } else {
650                 match pkg_config::Config::new()
651                     .atleast_version(version)
652                     .print_system_libs(false)
653                     .cargo_metadata(false)
654                     .statik(statik)
655                     .probe(&lib_name)
656                 {
657                     Ok(lib) => Library::from_pkg_config(&lib_name, lib),
658                     Err(e) => {
659                         if build_internal == BuildInternal::Auto {
660                             // Try building the lib internally as a fallback
661                             self.call_build_internal(name, version)?
662                         } else if optional {
663                             // If the dep is optional just skip it
664                             continue;
665                         } else {
666                             return Err(e.into());
667                         }
668                     }
669                 }
670             };
671 
672             library.statik = statik;
673 
674             libraries.add(name, library);
675         }
676         Ok(libraries)
677     }
678 
get_build_internal_env_var(&self, var: EnvVariable) -> Result<Option<BuildInternal>, Error>679     fn get_build_internal_env_var(&self, var: EnvVariable) -> Result<Option<BuildInternal>, Error> {
680         match self.env.get(&var).as_deref() {
681             Some(s) => {
682                 let b = BuildInternal::from_str(s).map_err(|_| {
683                     Error::BuildInternalInvalid(format!(
684                         "Invalid value in {}: {} (allowed: 'auto', 'always', 'never')",
685                         var, s
686                     ))
687                 })?;
688                 Ok(Some(b))
689             }
690             None => Ok(None),
691         }
692     }
693 
get_build_internal_status(&self, name: &str) -> Result<BuildInternal, Error>694     fn get_build_internal_status(&self, name: &str) -> Result<BuildInternal, Error> {
695         match self.get_build_internal_env_var(EnvVariable::new_build_internal(Some(name)))? {
696             Some(b) => Ok(b),
697             None => Ok(self
698                 .get_build_internal_env_var(EnvVariable::new_build_internal(None))?
699                 .unwrap_or_default()),
700         }
701     }
702 
call_build_internal(&mut self, name: &str, version: &str) -> Result<Library, Error>703     fn call_build_internal(&mut self, name: &str, version: &str) -> Result<Library, Error> {
704         let lib = match self.build_internals.remove(name) {
705             Some(f) => {
706                 f(name, version).map_err(|e| Error::BuildInternalClosureError(name.into(), e))?
707             }
708             None => return Err(Error::BuildInternalNoClosure(name.into(), version.into())),
709         };
710 
711         // Check that the lib built internally matches the required version
712         match VersionCompare::compare(&lib.version, version) {
713             Ok(version_compare::CompOp::Lt) => Err(Error::BuildInternalWrongVersion(
714                 name.into(),
715                 lib.version.clone(),
716                 version.into(),
717             )),
718             _ => Ok(lib),
719         }
720     }
721 
has_feature(&self, feature: &str) -> bool722     fn has_feature(&self, feature: &str) -> bool {
723         let var: &str = &format!("CARGO_FEATURE_{}", feature.to_uppercase().replace('-', "_"));
724         self.env.contains(var)
725     }
726 
check_cfg(&self, cfg: &cfg_expr::Expression) -> Result<bool, Error>727     fn check_cfg(&self, cfg: &cfg_expr::Expression) -> Result<bool, Error> {
728         use cfg_expr::{targets::get_builtin_target_by_triple, Predicate};
729 
730         let target = self
731             .env
732             .get("TARGET")
733             .expect("no TARGET env variable defined");
734         let target = get_builtin_target_by_triple(&target)
735             .unwrap_or_else(|| panic!("Invalid TARGET: {}", target));
736 
737         let res = cfg.eval(|pred| match pred {
738             Predicate::Target(tp) => Some(tp.matches(target)),
739             _ => None,
740         });
741 
742         res.ok_or_else(|| Error::UnsupportedCfg(cfg.original().to_string()))
743     }
744 }
745 
746 #[derive(Debug, PartialEq)]
747 /// From where the library settings have been retrieved
748 pub enum Source {
749     /// Settings have been retrieved from `pkg-config`
750     PkgConfig,
751     /// Settings have been defined using user defined environment variables
752     EnvVariables,
753 }
754 
755 #[derive(Debug)]
756 /// A system dependency
757 pub struct Library {
758     /// Name of the library
759     pub name: String,
760     /// From where the library settings have been retrieved
761     pub source: Source,
762     /// libraries the linker should link on
763     pub libs: Vec<String>,
764     /// directories where the compiler should look for libraries
765     pub link_paths: Vec<PathBuf>,
766     /// frameworks the linker should link on
767     pub frameworks: Vec<String>,
768     /// directories where the compiler should look for frameworks
769     pub framework_paths: Vec<PathBuf>,
770     /// directories where the compiler should look for header files
771     pub include_paths: Vec<PathBuf>,
772     /// macros that should be defined by the compiler
773     pub defines: HashMap<String, Option<String>>,
774     /// library version
775     pub version: String,
776     /// library is statically linked
777     pub statik: bool,
778 }
779 
780 impl Library {
from_pkg_config(name: &str, l: pkg_config::Library) -> Self781     fn from_pkg_config(name: &str, l: pkg_config::Library) -> Self {
782         Self {
783             name: name.to_string(),
784             source: Source::PkgConfig,
785             libs: l.libs,
786             link_paths: l.link_paths,
787             include_paths: l.include_paths,
788             frameworks: l.frameworks,
789             framework_paths: l.framework_paths,
790             defines: l.defines,
791             version: l.version,
792             statik: false,
793         }
794     }
795 
from_env_variables(name: &str) -> Self796     fn from_env_variables(name: &str) -> Self {
797         Self {
798             name: name.to_string(),
799             source: Source::EnvVariables,
800             libs: Vec::new(),
801             link_paths: Vec::new(),
802             include_paths: Vec::new(),
803             frameworks: Vec::new(),
804             framework_paths: Vec::new(),
805             defines: HashMap::new(),
806             version: String::new(),
807             statik: false,
808         }
809     }
810 
811     /// Create a `Library` by probing `pkg-config` on an internal directory.
812     /// This helper is meant to be used by `Config::add_build_internal` closures
813     /// after having built the lib to return the library information to system-deps.
814     ///
815     /// This library will be statically linked.
816     ///
817     /// # Arguments
818     ///
819     /// * `pkg_config_dir`: the directory where the library `.pc` file is located
820     /// * `lib`: the name of the library to look for
821     /// * `version`: the minimum version of `lib` required
822     ///
823     /// # Examples
824     ///
825     /// ```
826     /// let mut config = system_deps::Config::new();
827     /// config.add_build_internal("mylib", |lib, version| {
828     ///   // Actually build the library here
829     ///   system_deps::Library::from_internal_pkg_config("build-dir",
830     ///       lib, version)
831     /// });
832     /// ```
from_internal_pkg_config<P>( pkg_config_dir: P, lib: &str, version: &str, ) -> Result<Self, BuildInternalClosureError> where P: AsRef<Path>,833     pub fn from_internal_pkg_config<P>(
834         pkg_config_dir: P,
835         lib: &str,
836         version: &str,
837     ) -> Result<Self, BuildInternalClosureError>
838     where
839         P: AsRef<Path>,
840     {
841         // save current PKG_CONFIG_PATH, so we can restore it
842         let old = env::var("PKG_CONFIG_PATH");
843 
844         match old {
845             Ok(ref s) => {
846                 let mut paths = env::split_paths(s).collect::<Vec<_>>();
847                 paths.push(PathBuf::from(pkg_config_dir.as_ref()));
848                 let paths = env::join_paths(paths).unwrap();
849                 env::set_var("PKG_CONFIG_PATH", paths)
850             }
851             Err(_) => env::set_var("PKG_CONFIG_PATH", pkg_config_dir.as_ref()),
852         }
853 
854         let pkg_lib = pkg_config::Config::new()
855             .atleast_version(version)
856             .print_system_libs(false)
857             .cargo_metadata(false)
858             .statik(true)
859             .probe(lib);
860 
861         env::set_var("PKG_CONFIG_PATH", &old.unwrap_or_else(|_| "".into()));
862 
863         match pkg_lib {
864             Ok(pkg_lib) => {
865                 let mut lib = Self::from_pkg_config(lib, pkg_lib);
866                 lib.statik = true;
867                 Ok(lib)
868             }
869             Err(e) => Err(e.into()),
870         }
871     }
872 }
873 
874 #[derive(Debug)]
875 enum EnvVariables {
876     Environnement,
877     #[cfg(test)]
878     Mock(HashMap<&'static str, String>),
879 }
880 
881 trait EnvVariablesExt<T> {
contains(&self, var: T) -> bool882     fn contains(&self, var: T) -> bool {
883         self.get(var).is_some()
884     }
885 
get(&self, var: T) -> Option<String>886     fn get(&self, var: T) -> Option<String>;
887 
has_value(&self, var: T, val: &str) -> bool888     fn has_value(&self, var: T, val: &str) -> bool {
889         match self.get(var) {
890             Some(v) => v == val,
891             None => false,
892         }
893     }
894 }
895 
896 impl EnvVariablesExt<&str> for EnvVariables {
get(&self, var: &str) -> Option<String>897     fn get(&self, var: &str) -> Option<String> {
898         match self {
899             EnvVariables::Environnement => env::var(var).ok(),
900             #[cfg(test)]
901             EnvVariables::Mock(vars) => vars.get(var).cloned(),
902         }
903     }
904 }
905 
906 impl EnvVariablesExt<&EnvVariable> for EnvVariables {
get(&self, var: &EnvVariable) -> Option<String>907     fn get(&self, var: &EnvVariable) -> Option<String> {
908         let s = var.to_string();
909         let var: &str = s.as_ref();
910         self.get(var)
911     }
912 }
913 
914 #[derive(Debug, PartialEq)]
915 enum BuildFlag {
916     Include(String),
917     SearchNative(String),
918     SearchFramework(String),
919     Lib(String, bool), // true if static
920     LibFramework(String),
921     RerunIfEnvChanged(EnvVariable),
922 }
923 
924 impl fmt::Display for BuildFlag {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result925     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
926         match self {
927             BuildFlag::Include(paths) => write!(f, "include={}", paths),
928             BuildFlag::SearchNative(lib) => write!(f, "rustc-link-search=native={}", lib),
929             BuildFlag::SearchFramework(lib) => write!(f, "rustc-link-search=framework={}", lib),
930             BuildFlag::Lib(lib, statik) => {
931                 if *statik {
932                     write!(f, "rustc-link-lib=static={}", lib)
933                 } else {
934                     write!(f, "rustc-link-lib={}", lib)
935                 }
936             }
937             BuildFlag::LibFramework(lib) => write!(f, "rustc-link-lib=framework={}", lib),
938             BuildFlag::RerunIfEnvChanged(env) => write!(f, "rerun-if-env-changed={}", env),
939         }
940     }
941 }
942 
943 #[derive(Debug, PartialEq)]
944 struct BuildFlags(Vec<BuildFlag>);
945 
946 impl BuildFlags {
new() -> Self947     fn new() -> Self {
948         Self(Vec::new())
949     }
950 
add(&mut self, flag: BuildFlag)951     fn add(&mut self, flag: BuildFlag) {
952         self.0.push(flag);
953     }
954 }
955 
956 impl fmt::Display for BuildFlags {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result957     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
958         for flag in self.0.iter() {
959             writeln!(f, "cargo:{}", flag)?;
960         }
961         Ok(())
962     }
963 }
964 
split_paths(value: &str) -> Vec<PathBuf>965 fn split_paths(value: &str) -> Vec<PathBuf> {
966     if !value.is_empty() {
967         let paths = env::split_paths(&value);
968         paths.map(|p| Path::new(&p).into()).collect()
969     } else {
970         Vec::new()
971     }
972 }
973 
split_string(value: &str) -> Vec<String>974 fn split_string(value: &str) -> Vec<String> {
975     if !value.is_empty() {
976         value.split(' ').map(|s| s.to_string()).collect()
977     } else {
978         Vec::new()
979     }
980 }
981 
982 #[derive(Debug, PartialEq, EnumString)]
983 #[strum(serialize_all = "snake_case")]
984 enum BuildInternal {
985     Auto,
986     Always,
987     Never,
988 }
989 
990 impl Default for BuildInternal {
default() -> Self991     fn default() -> Self {
992         BuildInternal::Never
993     }
994 }
995