1#![allow(clippy::needless_doctest_main)]
2#![allow(clippy::result_large_err)]
3//!`system-deps` lets you write system dependencies in `Cargo.toml` metadata,
4//! rather than programmatically in `build.rs`. This makes those dependencies
5//! declarative, so other tools can read them as well.
6//!
7//! # Usage
8//! In your `Cargo.toml`:
9//!
10//! ```toml
11//! [build-dependencies]
12//! system-deps = "2.0"
13//! ```
14//!
15//! Then, to declare a dependency on `testlib >= 1.2`
16//! add the following section:
17//!
18//! ```toml
19//! [package.metadata.system-deps]
20//! testlib = "1.2"
21//! ```
22//!
23//! Finally, in your `build.rs`, add:
24//!
25//! ```should_panic
26//! fn main() {
27//! system_deps::Config::new().probe().unwrap();
28//! }
29//! ```
30//!
31//! # Version format
32//!
33//! Versions can be expressed in the following formats
34//!
35//! * "1.2" or ">= 1.2": At least version 1.2
36//! * ">= 1.2, < 2.0": At least version 1.2 but less than version 2.0
37//!
38//! In the future more complicated version expressions might be supported.
39//!
40//! Note that these versions are not interpreted according to the semver rules, but based on the
41//! rules defined by pkg-config.
42//!
43//! # Feature-specific dependency
44//! You can easily declare an optional system dependency by associating it with a feature:
45//!
46//! ```toml
47//! [package.metadata.system-deps]
48//! testdata = { version = "4.5", feature = "use-testdata" }
49//! ```
50//!
51//! `system-deps` will check for `testdata` only if the `use-testdata` feature has been enabled.
52//!
53//! # Optional dependency
54//!
55//! Another option is to use the `optional` setting, which can also be used using [features versions](#feature-versions):
56//!
57//! ```toml
58//! [package.metadata.system-deps]
59//! test-data = { version = "4.5", optional = true }
60//! testmore = { version = "2", v3 = { version = "3.0", optional = true }}
61//! ```
62//!
63//! `system-deps` will automatically export for each dependency a feature `system_deps_have_$DEP` where `$DEP`
64//! is the `toml` key defining the dependency in [snake_case](https://en.wikipedia.org/wiki/Snake_case).
65//! This can be used to check if an optional dependency has been found or not:
66//!
67//! ```
68//! #[cfg(system_deps_have_testdata)]
69//! println!("found test-data");
70//! ```
71//!
72//! # Overriding library name
73//! `toml` keys cannot contain dot characters so if your library name does, you can define it using the `name` field:
74//!
75//! ```toml
76//! [package.metadata.system-deps]
77//! glib = { name = "glib-2.0", version = "2.64" }
78//! ```
79//!
80//! # Fallback library names
81//! Some libraries may be available under different names on different platforms or distributions.
82//! To allow for this, you can define fallback names to search for if the main library name does not work.
83//!
84//! ```toml
85//! [package.metadata.system-deps]
86//! aravis = { fallback-names = ["aravis-0.8"] }
87//! ```
88//!
89//! You may also specify different fallback names for different versions:
90//!
91//! [package.metadata.system-deps.libfoo]
92//! version = "0.1"
93//! fallback-names = ["libfoo-0.1"]
94//! v1 = { version = "1.0", fallback-names = ["libfoo1"] }
95//! v2 = { version = "2.0", fallback-names = ["libfoo2"] }
96//!
97//! # Feature versions
98//! `-sys` crates willing to support various versions of their underlying system libraries
99//! can use features to control the version of the dependency required.
100//! `system-deps` will pick the highest version among enabled features.
101//! Such version features must use the pattern `v1_0`, `v1_2`, etc.
102//!
103//! ```toml
104//! [features]
105//! v1_2 = []
106//! v1_4 = ["v1_2"]
107//! v1_6 = ["v1_4"]
108//!
109//! [package.metadata.system-deps.gstreamer_1_0]
110//! name = "gstreamer-1.0"
111//! version = "1.0"
112//! v1_2 = { version = "1.2" }
113//! v1_4 = { version = "1.4" }
114//! v1_6 = { version = "1.6" }
115//! ```
116//!
117//! The same mechanism can be used to require a different library name depending on the version:
118//!
119//! ```toml
120//! [package.metadata.system-deps.gst_gl]
121//! name = "gstreamer-gl-1.0"
122//! version = "1.14"
123//! v1_18 = { version = "1.18", name = "gstreamer-gl-egl-1.0" }
124//! ```
125//!
126//! # Target specific dependencies
127//!
128//! You can define target specific dependencies:
129//!
130//! ```toml
131//! [package.metadata.system-deps.'cfg(target_os = "linux")']
132//! testdata = "1"
133//! [package.metadata.system-deps.'cfg(not(target_os = "macos"))']
134//! testlib = "1"
135//! [package.metadata.system-deps.'cfg(unix)']
136//! testanotherlib = { version = "1", optional = true }
137//! ```
138//!
139//! See [the Rust documentation](https://doc.rust-lang.org/reference/conditional-compilation.html)
140//! for the exact syntax.
141//! Currently, those keys are supported:
142//! - `target_arch`
143//! - `target_endian`
144//! - `target_env`
145//! - `target_family`
146//! - `target_os`
147//! - `target_pointer_width`
148//! - `target_vendor`
149//! - `unix` and `windows`
150//!
151//! # Overriding build flags
152//! By default `system-deps` automatically defines the required build flags for each dependency using the information fetched from `pkg-config`.
153//! These flags can be overridden using environment variables if needed:
154//! - `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;
155//! - `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;
156//! - `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;
157//! - `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;
158//! - `SYSTEM_DEPS_$NAME_INCLUDE` to override the [`cargo:include`](https://kornel.ski/rust-sys-crate#headers) flag.
159//!
160//! With `$NAME` being the upper case name of the key defining the dependency in `Cargo.toml`.
161//! For example `SYSTEM_DEPS_TESTLIB_SEARCH_NATIVE=/opt/lib` could be used to override a dependency named `testlib`.
162//!
163//! One can also define the environment variable `SYSTEM_DEPS_$NAME_NO_PKG_CONFIG` to fully disable `pkg-config` lookup
164//! for the given dependency. In this case at least SYSTEM_DEPS_$NAME_LIB or SYSTEM_DEPS_$NAME_LIB_FRAMEWORK should be defined as well.
165//!
166//! # Internally build system libraries
167//!
168//! `-sys` crates can provide support for building and statically link their underlying system library as part of their build process.
169//! Here is how to do this in your `build.rs`:
170//! ```should_panic
171//! fn main() {
172//! system_deps::Config::new()
173//! .add_build_internal("testlib", |lib, version| {
174//! // Actually build the library here that fulfills the passed in version requirements
175//! system_deps::Library::from_internal_pkg_config("build/path-to-pc-file", lib, "1.2.4")
176//! })
177//! .probe()
178//! .unwrap();
179//! }
180//! ```
181//!
182//! This feature can be controlled using the `SYSTEM_DEPS_$NAME_BUILD_INTERNAL` environment variable
183//! which can have the following values:
184//! - `auto`: build the dependency only if the required version has not been found by `pkg-config`;
185//! - `always`: always build the dependency, ignoring any version which may be installed on the system;
186//! - `never`: (default) never build the dependency, `system-deps` will fail if the required version is not found on the system.
187//!
188//! You can also use the `SYSTEM_DEPS_BUILD_INTERNAL` environment variable with the same values
189//! defining the behavior for all the dependencies which don't have `SYSTEM_DEPS_$NAME_BUILD_INTERNAL` defined.
190//!
191//! # Static linking
192//!
193//! By default all libraries are dynamically linked, except when build internally as [described above](#internally-build-system-libraries).
194//! Libraries can be statically linked by defining the environment variable `SYSTEM_DEPS_$NAME_LINK=static`.
195//! You can also use `SYSTEM_DEPS_LINK=static` to statically link all the libraries.
196
197#![deny(missing_docs)]
198
199#[cfg(test)]
200#[macro_use]
201extern crate lazy_static;
202
203#[cfg(test)]
204mod test;
205
206use heck::{ToShoutySnakeCase, ToSnakeCase};
207use std::collections::HashMap;
208use std::env;
209use std::fmt;
210use std::ops::RangeBounds;
211use std::path::{Path, PathBuf};
212use std::str::FromStr;
213
214mod metadata;
215use metadata::MetaData;
216
217/// system-deps errors
218#[derive(Debug)]
219pub enum Error {
220 /// pkg-config error
221 PkgConfig(pkg_config::Error),
222 /// One of the `Config::add_build_internal` closures failed
223 BuildInternalClosureError(String, BuildInternalClosureError),
224 /// Failed to read `Cargo.toml`
225 FailToRead(String, std::io::Error),
226 /// Raised when an error is detected in the metadata defined in `Cargo.toml`
227 InvalidMetadata(String),
228 /// Raised when dependency defined manually using `SYSTEM_DEPS_$NAME_NO_PKG_CONFIG`
229 /// did not define at least one lib using `SYSTEM_DEPS_$NAME_LIB` or
230 /// `SYSTEM_DEPS_$NAME_LIB_FRAMEWORK`
231 MissingLib(String),
232 /// An environment variable in the form of `SYSTEM_DEPS_$NAME_BUILD_INTERNAL`
233 /// contained an invalid value (allowed: `auto`, `always`, `never`)
234 BuildInternalInvalid(String),
235 /// system-deps has been asked to internally build a lib, through
236 /// `SYSTEM_DEPS_$NAME_BUILD_INTERNAL=always' or `SYSTEM_DEPS_$NAME_BUILD_INTERNAL=auto',
237 /// but not closure has been defined using `Config::add_build_internal` to build
238 /// this lib
239 BuildInternalNoClosure(String, String),
240 /// The library which has been build internally does not match the
241 /// required version defined in `Cargo.toml`
242 BuildInternalWrongVersion(String, String, String),
243 /// The `cfg()` expression used in `Cargo.toml` is currently not supported
244 UnsupportedCfg(String),
245}
246
247impl From<pkg_config::Error> for Error {
248 fn from(err: pkg_config::Error) -> Self {
249 Self::PkgConfig(err)
250 }
251}
252
253impl std::error::Error for Error {
254 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
255 match self {
256 Self::PkgConfig(e: &Error) => Some(e),
257 Self::BuildInternalClosureError(_, e: &BuildInternalClosureError) => Some(e),
258 Self::FailToRead(_, e: &Error) => Some(e),
259 _ => None,
260 }
261 }
262}
263
264impl fmt::Display for Error {
265 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
266 match self {
267 Self::PkgConfig(e) => write!(f, "{}", e),
268 Self::BuildInternalClosureError(s, e) => write!(f, "Failed to build {}: {}", s, e),
269 Self::FailToRead(s, _) => write!(f, "{}", s),
270 Self::InvalidMetadata(s) => write!(f, "{}", s),
271 Self::MissingLib(s) => write!(
272 f,
273 "You should define at least one lib using {} or {}",
274 EnvVariable::new_lib(s),
275 EnvVariable::new_lib_framework(s),
276 ),
277 Self::BuildInternalInvalid(s) => write!(f, "{}", s),
278 Self::BuildInternalNoClosure(s1, s2) => write!(
279 f,
280 "Missing build internal closure for {} (version {})",
281 s1, s2
282 ),
283 Self::BuildInternalWrongVersion(s1, s2, s3) => write!(
284 f,
285 "Internally built {} {} but minimum required version is {}",
286 s1, s2, s3
287 ),
288 Self::UnsupportedCfg(s) => write!(f, "Unsupported cfg() expression: {}", s),
289 }
290 }
291}
292
293#[derive(Debug, Default)]
294/// All the system dependencies retrieved by [Config::probe].
295pub struct Dependencies {
296 libs: HashMap<String, Library>,
297}
298
299impl Dependencies {
300 /// Retrieve details about a system dependency.
301 ///
302 /// # Arguments
303 ///
304 /// * `name`: the name of the `toml` key defining the dependency in `Cargo.toml`
305 pub fn get_by_name(&self, name: &str) -> Option<&Library> {
306 self.libs.get(name)
307 }
308
309 /// A vector listing all system dependencies in sorted (for build reproducibility) order.
310 /// The first element of the tuple is the name of the `toml` key defining the
311 /// dependency in `Cargo.toml`.
312 pub fn iter(&self) -> Vec<(&str, &Library)> {
313 let mut v = self
314 .libs
315 .iter()
316 .map(|(k, v)| (k.as_str(), v))
317 .collect::<Vec<_>>();
318 v.sort_by_key(|x| x.0);
319 v
320 }
321
322 fn aggregate_str<F: Fn(&Library) -> &Vec<String>>(&self, getter: F) -> Vec<&str> {
323 let mut v = self
324 .libs
325 .values()
326 .flat_map(getter)
327 .map(|s| s.as_str())
328 .collect::<Vec<_>>();
329 v.sort_unstable();
330 v.dedup();
331 v
332 }
333
334 fn aggregate_path_buf<F: Fn(&Library) -> &Vec<PathBuf>>(&self, getter: F) -> Vec<&PathBuf> {
335 let mut v = self.libs.values().flat_map(getter).collect::<Vec<_>>();
336 v.sort();
337 v.dedup();
338 v
339 }
340
341 /// Returns a vector of [Library::libs] of each library, removing duplicates.
342 pub fn all_libs(&self) -> Vec<&str> {
343 let mut v = self
344 .libs
345 .values()
346 .flat_map(|l| l.libs.iter().map(|lib| lib.name.as_str()))
347 .collect::<Vec<_>>();
348 v.sort_unstable();
349 v.dedup();
350 v
351 }
352
353 /// Returns a vector of [Library::link_paths] of each library, removing duplicates.
354 pub fn all_link_paths(&self) -> Vec<&PathBuf> {
355 self.aggregate_path_buf(|l| &l.link_paths)
356 }
357
358 /// Returns a vector of [Library::frameworks] of each library, removing duplicates.
359 pub fn all_frameworks(&self) -> Vec<&str> {
360 self.aggregate_str(|l| &l.frameworks)
361 }
362
363 /// Returns a vector of [Library::framework_paths] of each library, removing duplicates.
364 pub fn all_framework_paths(&self) -> Vec<&PathBuf> {
365 self.aggregate_path_buf(|l| &l.framework_paths)
366 }
367
368 /// Returns a vector of [Library::include_paths] of each library, removing duplicates.
369 pub fn all_include_paths(&self) -> Vec<&PathBuf> {
370 self.aggregate_path_buf(|l| &l.include_paths)
371 }
372
373 /// Returns a vector of [Library::defines] of each library, removing duplicates.
374 pub fn all_defines(&self) -> Vec<(&str, &Option<String>)> {
375 let mut v = self
376 .libs
377 .values()
378 .flat_map(|l| l.defines.iter())
379 .map(|(k, v)| (k.as_str(), v))
380 .collect::<Vec<_>>();
381 v.sort();
382 v.dedup();
383 v
384 }
385
386 fn add(&mut self, name: &str, lib: Library) {
387 self.libs.insert(name.to_string(), lib);
388 }
389
390 fn override_from_flags(&mut self, env: &EnvVariables) {
391 for (name, lib) in self.libs.iter_mut() {
392 if let Some(value) = env.get(&EnvVariable::new_search_native(name)) {
393 lib.link_paths = split_paths(&value);
394 }
395 if let Some(value) = env.get(&EnvVariable::new_search_framework(name)) {
396 lib.framework_paths = split_paths(&value);
397 }
398 if let Some(value) = env.get(&EnvVariable::new_lib(name)) {
399 let should_be_linked_statically = env
400 .has_value(&EnvVariable::new_link(Some(name)), "static")
401 || env.has_value(&EnvVariable::new_link(None), "static");
402
403 // If somebody manually mandates static linking, that is a
404 // clear intent. Let's just assume that a static lib is
405 // available and let the linking fail if the user is wrong.
406 let is_static_lib_available = should_be_linked_statically;
407
408 lib.libs = split_string(&value)
409 .into_iter()
410 .map(|l| InternalLib::new(l, is_static_lib_available))
411 .collect();
412 }
413 if let Some(value) = env.get(&EnvVariable::new_lib_framework(name)) {
414 lib.frameworks = split_string(&value);
415 }
416 if let Some(value) = env.get(&EnvVariable::new_include(name)) {
417 lib.include_paths = split_paths(&value);
418 }
419 }
420 }
421
422 fn gen_flags(&self) -> Result<BuildFlags, Error> {
423 let mut flags = BuildFlags::new();
424 let mut include_paths = Vec::new();
425
426 for (name, lib) in self.iter() {
427 include_paths.extend(lib.include_paths.clone());
428
429 if lib.source == Source::EnvVariables
430 && lib.libs.is_empty()
431 && lib.frameworks.is_empty()
432 {
433 return Err(Error::MissingLib(name.to_string()));
434 }
435
436 lib.link_paths
437 .iter()
438 .for_each(|l| flags.add(BuildFlag::SearchNative(l.to_string_lossy().to_string())));
439 lib.framework_paths.iter().for_each(|f| {
440 flags.add(BuildFlag::SearchFramework(f.to_string_lossy().to_string()))
441 });
442 lib.libs.iter().for_each(|l| {
443 flags.add(BuildFlag::Lib(
444 l.name.clone(),
445 lib.statik && l.is_static_available,
446 ))
447 });
448 lib.frameworks
449 .iter()
450 .for_each(|f| flags.add(BuildFlag::LibFramework(f.clone())));
451 }
452
453 // Export DEP_$CRATE_INCLUDE env variable with the headers paths,
454 // see https://kornel.ski/rust-sys-crate#headers
455 if !include_paths.is_empty() {
456 if let Ok(paths) = std::env::join_paths(include_paths) {
457 flags.add(BuildFlag::Include(paths.to_string_lossy().to_string()));
458 }
459 }
460
461 // Export cargo:rerun-if-env-changed instructions for all env variables affecting system-deps behaviour
462 flags.add(BuildFlag::RerunIfEnvChanged(
463 EnvVariable::new_build_internal(None),
464 ));
465 flags.add(BuildFlag::RerunIfEnvChanged(EnvVariable::new_link(None)));
466
467 for (name, _lib) in self.libs.iter() {
468 EnvVariable::set_rerun_if_changed_for_all_variants(&mut flags, name);
469 }
470
471 Ok(flags)
472 }
473}
474
475#[derive(Debug)]
476/// Error used in return value of `Config::add_build_internal` closures
477pub enum BuildInternalClosureError {
478 /// `pkg-config` error
479 PkgConfig(pkg_config::Error),
480 /// General failure
481 Failed(String),
482}
483
484impl From<pkg_config::Error> for BuildInternalClosureError {
485 fn from(err: pkg_config::Error) -> Self {
486 Self::PkgConfig(err)
487 }
488}
489
490impl BuildInternalClosureError {
491 /// Create a new `BuildInternalClosureError::Failed` representing a general
492 /// failure.
493 ///
494 /// # Arguments
495 ///
496 /// * `details`: human-readable details about the failure
497 pub fn failed(details: &str) -> Self {
498 Self::Failed(details.to_string())
499 }
500}
501
502impl std::error::Error for BuildInternalClosureError {
503 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
504 match self {
505 Self::PkgConfig(e: &Error) => Some(e),
506 _ => None,
507 }
508 }
509}
510
511impl fmt::Display for BuildInternalClosureError {
512 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
513 match self {
514 Self::PkgConfig(e: &Error) => write!(f, "{}", e),
515 Self::Failed(s: &String) => write!(f, "{}", s),
516 }
517 }
518}
519
520// Enum representing the environment variables user can define to tune system-deps.
521#[derive(Debug, PartialEq)]
522enum EnvVariable {
523 Lib(String),
524 LibFramework(String),
525 SearchNative(String),
526 SearchFramework(String),
527 Include(String),
528 NoPkgConfig(String),
529 BuildInternal(Option<String>),
530 Link(Option<String>),
531}
532
533impl EnvVariable {
534 fn new_lib(lib: &str) -> Self {
535 Self::Lib(lib.to_string())
536 }
537
538 fn new_lib_framework(lib: &str) -> Self {
539 Self::LibFramework(lib.to_string())
540 }
541
542 fn new_search_native(lib: &str) -> Self {
543 Self::SearchNative(lib.to_string())
544 }
545
546 fn new_search_framework(lib: &str) -> Self {
547 Self::SearchFramework(lib.to_string())
548 }
549
550 fn new_include(lib: &str) -> Self {
551 Self::Include(lib.to_string())
552 }
553
554 fn new_no_pkg_config(lib: &str) -> Self {
555 Self::NoPkgConfig(lib.to_string())
556 }
557
558 fn new_build_internal(lib: Option<&str>) -> Self {
559 Self::BuildInternal(lib.map(|l| l.to_string()))
560 }
561
562 fn new_link(lib: Option<&str>) -> Self {
563 Self::Link(lib.map(|l| l.to_string()))
564 }
565
566 fn suffix(&self) -> &'static str {
567 match self {
568 EnvVariable::Lib(_) => "LIB",
569 EnvVariable::LibFramework(_) => "LIB_FRAMEWORK",
570 EnvVariable::SearchNative(_) => "SEARCH_NATIVE",
571 EnvVariable::SearchFramework(_) => "SEARCH_FRAMEWORK",
572 EnvVariable::Include(_) => "INCLUDE",
573 EnvVariable::NoPkgConfig(_) => "NO_PKG_CONFIG",
574 EnvVariable::BuildInternal(_) => "BUILD_INTERNAL",
575 EnvVariable::Link(_) => "LINK",
576 }
577 }
578
579 fn set_rerun_if_changed_for_all_variants(flags: &mut BuildFlags, name: &str) {
580 #[inline]
581 fn add_to_flags(flags: &mut BuildFlags, var: EnvVariable) {
582 flags.add(BuildFlag::RerunIfEnvChanged(var));
583 }
584 add_to_flags(flags, EnvVariable::new_lib(name));
585 add_to_flags(flags, EnvVariable::new_lib_framework(name));
586 add_to_flags(flags, EnvVariable::new_search_native(name));
587 add_to_flags(flags, EnvVariable::new_search_framework(name));
588 add_to_flags(flags, EnvVariable::new_include(name));
589 add_to_flags(flags, EnvVariable::new_no_pkg_config(name));
590 add_to_flags(flags, EnvVariable::new_build_internal(Some(name)));
591 add_to_flags(flags, EnvVariable::new_link(Some(name)));
592 }
593}
594
595impl fmt::Display for EnvVariable {
596 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
597 let suffix: String = match self {
598 EnvVariable::Lib(lib: &String)
599 | EnvVariable::LibFramework(lib: &String)
600 | EnvVariable::SearchNative(lib: &String)
601 | EnvVariable::SearchFramework(lib: &String)
602 | EnvVariable::Include(lib: &String)
603 | EnvVariable::NoPkgConfig(lib: &String)
604 | EnvVariable::BuildInternal(Some(lib: &String))
605 | EnvVariable::Link(Some(lib: &String)) => {
606 format!("{}_{}", lib.to_shouty_snake_case(), self.suffix())
607 }
608 EnvVariable::BuildInternal(None) | EnvVariable::Link(None) => self.suffix().to_string(),
609 };
610 write!(f, "SYSTEM_DEPS_{}", suffix)
611 }
612}
613
614type FnBuildInternal =
615 dyn FnOnce(&str, &str) -> std::result::Result<Library, BuildInternalClosureError>;
616
617/// Structure used to configure `metadata` before starting to probe for dependencies
618pub struct Config {
619 env: EnvVariables,
620 build_internals: HashMap<String, Box<FnBuildInternal>>,
621}
622
623impl Default for Config {
624 fn default() -> Self {
625 Self::new_with_env(EnvVariables::Environment)
626 }
627}
628
629impl Config {
630 /// Create a new set of configuration
631 pub fn new() -> Self {
632 Self::default()
633 }
634
635 fn new_with_env(env: EnvVariables) -> Self {
636 Self {
637 env,
638 build_internals: HashMap::new(),
639 }
640 }
641
642 /// Probe all libraries configured in the Cargo.toml
643 /// `[package.metadata.system-deps]` section.
644 ///
645 /// The returned hash is using the `toml` key defining the dependency as key.
646 pub fn probe(self) -> Result<Dependencies, Error> {
647 let libraries = self.probe_full()?;
648 let flags = libraries.gen_flags()?;
649
650 // Output cargo flags
651 println!("{}", flags);
652
653 for (name, _) in libraries.iter() {
654 println!("cargo:rustc-cfg=system_deps_have_{}", name.to_snake_case());
655 }
656
657 Ok(libraries)
658 }
659
660 /// Add hook so system-deps can internally build library `name` if requested by user.
661 ///
662 /// It will only be triggered if the environment variable
663 /// `SYSTEM_DEPS_$NAME_BUILD_INTERNAL` is defined with either `always` or
664 /// `auto` as value. In the latter case, `func` is called only if the requested
665 /// version of the library was not found on the system.
666 ///
667 /// # Arguments
668 /// * `name`: the name of the library, as defined in `Cargo.toml`
669 /// * `func`: closure called when internally building the library.
670 /// It receives as argument the library name, and the minimum version required.
671 pub fn add_build_internal<F>(self, name: &str, func: F) -> Self
672 where
673 F: 'static + FnOnce(&str, &str) -> std::result::Result<Library, BuildInternalClosureError>,
674 {
675 let mut build_internals = self.build_internals;
676 build_internals.insert(name.to_string(), Box::new(func));
677
678 Self {
679 env: self.env,
680 build_internals,
681 }
682 }
683
684 fn probe_full(mut self) -> Result<Dependencies, Error> {
685 let mut libraries = self.probe_pkg_config()?;
686 libraries.override_from_flags(&self.env);
687
688 Ok(libraries)
689 }
690
691 fn probe_pkg_config(&mut self) -> Result<Dependencies, Error> {
692 let dir = self
693 .env
694 .get("CARGO_MANIFEST_DIR")
695 .ok_or_else(|| Error::InvalidMetadata("$CARGO_MANIFEST_DIR not set".into()))?;
696 let mut path = PathBuf::from(dir);
697 path.push("Cargo.toml");
698
699 let metadata = MetaData::from_file(&path)?;
700
701 let mut libraries = Dependencies::default();
702
703 for dep in metadata.deps.iter() {
704 if let Some(cfg) = &dep.cfg {
705 // Check if `cfg()` expression matches the target settings
706 if !self.check_cfg(cfg)? {
707 continue;
708 }
709 }
710
711 let mut enabled_feature_overrides = Vec::new();
712
713 for o in dep.version_overrides.iter() {
714 if self.has_feature(&o.key) {
715 enabled_feature_overrides.push(o);
716 }
717 }
718
719 if let Some(feature) = dep.feature.as_ref() {
720 if !self.has_feature(feature) {
721 continue;
722 }
723 }
724
725 // Pick the highest feature enabled version
726 let version;
727 let lib_name;
728 let fallback_lib_names;
729 let optional;
730 if enabled_feature_overrides.is_empty() {
731 version = dep.version.as_deref();
732 lib_name = dep.lib_name();
733 fallback_lib_names = dep.fallback_names.as_deref().unwrap_or(&[]);
734 optional = dep.optional;
735 } else {
736 enabled_feature_overrides.sort_by(|a, b| {
737 fn min_version(r: metadata::VersionRange) -> &str {
738 match r.start_bound() {
739 std::ops::Bound::Unbounded => unreachable!(),
740 std::ops::Bound::Excluded(_) => unreachable!(),
741 std::ops::Bound::Included(b) => b,
742 }
743 }
744
745 let a = min_version(metadata::parse_version(&a.version));
746 let b = min_version(metadata::parse_version(&b.version));
747
748 version_compare::compare(a, b)
749 .expect("failed to compare versions")
750 .ord()
751 .expect("invalid version")
752 });
753 let highest = enabled_feature_overrides.into_iter().last().unwrap();
754
755 version = Some(highest.version.as_str());
756 lib_name = highest.name.as_deref().unwrap_or(dep.lib_name());
757 fallback_lib_names = highest
758 .fallback_names
759 .as_deref()
760 .or(dep.fallback_names.as_deref())
761 .unwrap_or(&[]);
762 optional = highest.optional.unwrap_or(dep.optional);
763 };
764
765 let version = version.ok_or_else(|| {
766 Error::InvalidMetadata(format!("No version defined for {}", dep.key))
767 })?;
768
769 let name = &dep.key;
770 let build_internal = self.get_build_internal_status(name)?;
771
772 // should the lib be statically linked?
773 let statik = self
774 .env
775 .has_value(&EnvVariable::new_link(Some(name)), "static")
776 || self.env.has_value(&EnvVariable::new_link(None), "static");
777
778 let mut library = if self.env.contains(&EnvVariable::new_no_pkg_config(name)) {
779 Library::from_env_variables(name)
780 } else if build_internal == BuildInternal::Always {
781 self.call_build_internal(lib_name, version)?
782 } else {
783 let mut config = pkg_config::Config::new();
784 config
785 .print_system_libs(false)
786 .cargo_metadata(false)
787 .range_version(metadata::parse_version(version))
788 .statik(statik);
789
790 match Self::probe_with_fallback(config, lib_name, fallback_lib_names) {
791 Ok((lib_name, lib)) => Library::from_pkg_config(lib_name, lib),
792 Err(e) => {
793 if build_internal == BuildInternal::Auto {
794 // Try building the lib internally as a fallback
795 self.call_build_internal(name, version)?
796 } else if optional {
797 // If the dep is optional just skip it
798 continue;
799 } else {
800 return Err(e.into());
801 }
802 }
803 }
804 };
805
806 library.statik = statik;
807
808 libraries.add(name, library);
809 }
810 Ok(libraries)
811 }
812
813 fn probe_with_fallback<'a>(
814 config: pkg_config::Config,
815 name: &'a str,
816 fallback_names: &'a [String],
817 ) -> Result<(&'a str, pkg_config::Library), pkg_config::Error> {
818 let error = match config.probe(name) {
819 Ok(x) => return Ok((name, x)),
820 Err(e) => e,
821 };
822 for name in fallback_names {
823 if let Ok(library) = config.probe(name) {
824 return Ok((name, library));
825 }
826 }
827 Err(error)
828 }
829
830 fn get_build_internal_env_var(&self, var: EnvVariable) -> Result<Option<BuildInternal>, Error> {
831 match self.env.get(&var).as_deref() {
832 Some(s) => {
833 let b = BuildInternal::from_str(s).map_err(|_| {
834 Error::BuildInternalInvalid(format!(
835 "Invalid value in {}: {} (allowed: 'auto', 'always', 'never')",
836 var, s
837 ))
838 })?;
839 Ok(Some(b))
840 }
841 None => Ok(None),
842 }
843 }
844
845 fn get_build_internal_status(&self, name: &str) -> Result<BuildInternal, Error> {
846 match self.get_build_internal_env_var(EnvVariable::new_build_internal(Some(name)))? {
847 Some(b) => Ok(b),
848 None => Ok(self
849 .get_build_internal_env_var(EnvVariable::new_build_internal(None))?
850 .unwrap_or_default()),
851 }
852 }
853
854 fn call_build_internal(&mut self, name: &str, version_str: &str) -> Result<Library, Error> {
855 let lib = match self.build_internals.remove(name) {
856 Some(f) => f(name, version_str)
857 .map_err(|e| Error::BuildInternalClosureError(name.into(), e))?,
858 None => {
859 return Err(Error::BuildInternalNoClosure(
860 name.into(),
861 version_str.into(),
862 ))
863 }
864 };
865
866 // Check that the lib built internally matches the required version
867 let version = metadata::parse_version(version_str);
868 fn min_version(r: metadata::VersionRange) -> &str {
869 match r.start_bound() {
870 std::ops::Bound::Unbounded => unreachable!(),
871 std::ops::Bound::Excluded(_) => unreachable!(),
872 std::ops::Bound::Included(b) => b,
873 }
874 }
875 fn max_version(r: metadata::VersionRange) -> Option<&str> {
876 match r.end_bound() {
877 std::ops::Bound::Included(_) => unreachable!(),
878 std::ops::Bound::Unbounded => None,
879 std::ops::Bound::Excluded(b) => Some(*b),
880 }
881 }
882
883 let min = min_version(version.clone());
884 if version_compare::compare(&lib.version, min) == Ok(version_compare::Cmp::Lt) {
885 return Err(Error::BuildInternalWrongVersion(
886 name.into(),
887 lib.version,
888 version_str.into(),
889 ));
890 }
891
892 if let Some(max) = max_version(version) {
893 if version_compare::compare(&lib.version, max) == Ok(version_compare::Cmp::Ge) {
894 return Err(Error::BuildInternalWrongVersion(
895 name.into(),
896 lib.version,
897 version_str.into(),
898 ));
899 }
900 }
901
902 Ok(lib)
903 }
904
905 fn has_feature(&self, feature: &str) -> bool {
906 let var: &str = &format!("CARGO_FEATURE_{}", feature.to_uppercase().replace('-', "_"));
907 self.env.contains(var)
908 }
909
910 fn check_cfg(&self, cfg: &cfg_expr::Expression) -> Result<bool, Error> {
911 use cfg_expr::{targets::get_builtin_target_by_triple, Predicate};
912
913 let target = self
914 .env
915 .get("TARGET")
916 .expect("no TARGET env variable defined");
917
918 let res = if let Some(target) = get_builtin_target_by_triple(&target) {
919 cfg.eval(|pred| match pred {
920 Predicate::Target(tp) => Some(tp.matches(target)),
921 _ => None,
922 })
923 } else {
924 // Attempt to parse the triple, the target is not an official builtin
925 let triple: cfg_expr::target_lexicon::Triple = target.parse().unwrap_or_else(|e| panic!("TARGET {} is not a builtin target, and it could not be parsed as a valid triplet: {}", target, e));
926
927 cfg.eval(|pred| match pred {
928 Predicate::Target(tp) => Some(tp.matches(&triple)),
929 _ => None,
930 })
931 };
932
933 res.ok_or_else(|| Error::UnsupportedCfg(cfg.original().to_string()))
934 }
935}
936
937#[derive(Debug, PartialEq, Eq)]
938/// From where the library settings have been retrieved
939pub enum Source {
940 /// Settings have been retrieved from `pkg-config`
941 PkgConfig,
942 /// Settings have been defined using user defined environment variables
943 EnvVariables,
944}
945
946#[derive(Debug, PartialEq, Eq)]
947/// Internal library name and if a static library is available on the system
948pub struct InternalLib {
949 /// Name of the library
950 pub name: String,
951 /// Indicates if a static library is available on the system
952 pub is_static_available: bool,
953}
954
955impl InternalLib {
956 fn new(name: String, is_static_available: bool) -> Self {
957 InternalLib {
958 name,
959 is_static_available,
960 }
961 }
962}
963
964#[derive(Debug)]
965/// A system dependency
966pub struct Library {
967 /// Name of the library
968 pub name: String,
969 /// From where the library settings have been retrieved
970 pub source: Source,
971 /// libraries the linker should link on
972 pub libs: Vec<InternalLib>,
973 /// directories where the compiler should look for libraries
974 pub link_paths: Vec<PathBuf>,
975 /// frameworks the linker should link on
976 pub frameworks: Vec<String>,
977 /// directories where the compiler should look for frameworks
978 pub framework_paths: Vec<PathBuf>,
979 /// directories where the compiler should look for header files
980 pub include_paths: Vec<PathBuf>,
981 /// macros that should be defined by the compiler
982 pub defines: HashMap<String, Option<String>>,
983 /// library version
984 pub version: String,
985 /// library is statically linked
986 pub statik: bool,
987}
988
989impl Library {
990 fn from_pkg_config(name: &str, l: pkg_config::Library) -> Self {
991 // taken from: https://github.com/rust-lang/pkg-config-rs/blob/54325785816695df031cef3b26b6a9a203bbc01b/src/lib.rs#L502
992 let system_roots = if cfg!(target_os = "macos") {
993 vec![PathBuf::from("/Library"), PathBuf::from("/System")]
994 } else {
995 let sysroot = env::var_os("PKG_CONFIG_SYSROOT_DIR")
996 .or_else(|| env::var_os("SYSROOT"))
997 .map(PathBuf::from);
998
999 if cfg!(target_os = "windows") {
1000 if let Some(sysroot) = sysroot {
1001 vec![sysroot]
1002 } else {
1003 vec![]
1004 }
1005 } else {
1006 vec![sysroot.unwrap_or_else(|| PathBuf::from("/usr"))]
1007 }
1008 };
1009
1010 let is_static_available = |name: &String| -> bool {
1011 let libnames = {
1012 let mut names = vec![format!("lib{}.a", name)];
1013
1014 if cfg!(target_os = "windows") {
1015 names.push(format!("{}.lib", name));
1016 }
1017
1018 names
1019 };
1020
1021 l.link_paths.iter().any(|dir| {
1022 let library_exists = libnames.iter().any(|libname| dir.join(libname).exists());
1023 library_exists && !system_roots.iter().any(|sys| dir.starts_with(sys))
1024 })
1025 };
1026
1027 Self {
1028 name: name.to_string(),
1029 source: Source::PkgConfig,
1030 libs: l
1031 .libs
1032 .iter()
1033 .map(|lib| InternalLib::new(lib.to_owned(), is_static_available(lib)))
1034 .collect(),
1035 link_paths: l.link_paths,
1036 include_paths: l.include_paths,
1037 frameworks: l.frameworks,
1038 framework_paths: l.framework_paths,
1039 defines: l.defines,
1040 version: l.version,
1041 statik: false,
1042 }
1043 }
1044
1045 fn from_env_variables(name: &str) -> Self {
1046 Self {
1047 name: name.to_string(),
1048 source: Source::EnvVariables,
1049 libs: Vec::new(),
1050 link_paths: Vec::new(),
1051 include_paths: Vec::new(),
1052 frameworks: Vec::new(),
1053 framework_paths: Vec::new(),
1054 defines: HashMap::new(),
1055 version: String::new(),
1056 statik: false,
1057 }
1058 }
1059
1060 /// Create a `Library` by probing `pkg-config` on an internal directory.
1061 /// This helper is meant to be used by `Config::add_build_internal` closures
1062 /// after having built the lib to return the library information to system-deps.
1063 ///
1064 /// This library will be statically linked.
1065 ///
1066 /// # Arguments
1067 ///
1068 /// * `pkg_config_dir`: the directory where the library `.pc` file is located
1069 /// * `lib`: the name of the library to look for
1070 /// * `version`: the minimum version of `lib` required
1071 ///
1072 /// # Examples
1073 ///
1074 /// ```
1075 /// let mut config = system_deps::Config::new();
1076 /// config.add_build_internal("mylib", |lib, version| {
1077 /// // Actually build the library here that fulfills the passed in version requirements
1078 /// system_deps::Library::from_internal_pkg_config("build-dir",
1079 /// lib, "1.2.4")
1080 /// });
1081 /// ```
1082 pub fn from_internal_pkg_config<P>(
1083 pkg_config_dir: P,
1084 lib: &str,
1085 version: &str,
1086 ) -> Result<Self, BuildInternalClosureError>
1087 where
1088 P: AsRef<Path>,
1089 {
1090 // save current PKG_CONFIG_PATH, so we can restore it
1091 let old = env::var("PKG_CONFIG_PATH");
1092
1093 match old {
1094 Ok(ref s) => {
1095 let mut paths = env::split_paths(s).collect::<Vec<_>>();
1096 paths.push(PathBuf::from(pkg_config_dir.as_ref()));
1097 let paths = env::join_paths(paths).unwrap();
1098 env::set_var("PKG_CONFIG_PATH", paths)
1099 }
1100 Err(_) => env::set_var("PKG_CONFIG_PATH", pkg_config_dir.as_ref()),
1101 }
1102
1103 let pkg_lib = pkg_config::Config::new()
1104 .atleast_version(version)
1105 .print_system_libs(false)
1106 .cargo_metadata(false)
1107 .statik(true)
1108 .probe(lib);
1109
1110 env::set_var("PKG_CONFIG_PATH", old.unwrap_or_else(|_| "".into()));
1111
1112 match pkg_lib {
1113 Ok(pkg_lib) => {
1114 let mut lib = Self::from_pkg_config(lib, pkg_lib);
1115 lib.statik = true;
1116 Ok(lib)
1117 }
1118 Err(e) => Err(e.into()),
1119 }
1120 }
1121}
1122
1123#[derive(Debug)]
1124enum EnvVariables {
1125 Environment,
1126 #[cfg(test)]
1127 Mock(HashMap<&'static str, String>),
1128}
1129
1130trait EnvVariablesExt<T> {
1131 fn contains(&self, var: T) -> bool {
1132 self.get(var).is_some()
1133 }
1134
1135 fn get(&self, var: T) -> Option<String>;
1136
1137 fn has_value(&self, var: T, val: &str) -> bool {
1138 match self.get(var) {
1139 Some(v: String) => v == val,
1140 None => false,
1141 }
1142 }
1143}
1144
1145impl EnvVariablesExt<&str> for EnvVariables {
1146 fn get(&self, var: &str) -> Option<String> {
1147 match self {
1148 EnvVariables::Environment => env::var(key:var).ok(),
1149 #[cfg(test)]
1150 EnvVariables::Mock(vars) => vars.get(var).cloned(),
1151 }
1152 }
1153}
1154
1155impl EnvVariablesExt<&EnvVariable> for EnvVariables {
1156 fn get(&self, var: &EnvVariable) -> Option<String> {
1157 let s: String = var.to_string();
1158 let var: &str = s.as_ref();
1159 self.get(var)
1160 }
1161}
1162
1163#[derive(Debug, PartialEq)]
1164enum BuildFlag {
1165 Include(String),
1166 SearchNative(String),
1167 SearchFramework(String),
1168 Lib(String, bool), // true if static
1169 LibFramework(String),
1170 RerunIfEnvChanged(EnvVariable),
1171}
1172
1173impl fmt::Display for BuildFlag {
1174 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1175 match self {
1176 BuildFlag::Include(paths: &String) => write!(f, "include={}", paths),
1177 BuildFlag::SearchNative(lib: &String) => write!(f, "rustc-link-search=native={}", lib),
1178 BuildFlag::SearchFramework(lib: &String) => write!(f, "rustc-link-search=framework={}", lib),
1179 BuildFlag::Lib(lib: &String, statik: &bool) => {
1180 if *statik {
1181 write!(f, "rustc-link-lib=static={}", lib)
1182 } else {
1183 write!(f, "rustc-link-lib={}", lib)
1184 }
1185 }
1186 BuildFlag::LibFramework(lib: &String) => write!(f, "rustc-link-lib=framework={}", lib),
1187 BuildFlag::RerunIfEnvChanged(env: &EnvVariable) => write!(f, "rerun-if-env-changed={}", env),
1188 }
1189 }
1190}
1191
1192#[derive(Debug, PartialEq)]
1193struct BuildFlags(Vec<BuildFlag>);
1194
1195impl BuildFlags {
1196 fn new() -> Self {
1197 Self(Vec::new())
1198 }
1199
1200 fn add(&mut self, flag: BuildFlag) {
1201 self.0.push(flag);
1202 }
1203}
1204
1205impl fmt::Display for BuildFlags {
1206 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1207 for flag: &BuildFlag in self.0.iter() {
1208 writeln!(f, "cargo:{}", flag)?;
1209 }
1210 Ok(())
1211 }
1212}
1213
1214fn split_paths(value: &str) -> Vec<PathBuf> {
1215 if !value.is_empty() {
1216 let paths: SplitPaths<'_> = env::split_paths(&value);
1217 paths.map(|p: PathBuf| Path::new(&p).into()).collect()
1218 } else {
1219 Vec::new()
1220 }
1221}
1222
1223fn split_string(value: &str) -> Vec<String> {
1224 if !value.is_empty() {
1225 value.split(' ').map(|s: &str| s.to_string()).collect()
1226 } else {
1227 Vec::new()
1228 }
1229}
1230
1231#[derive(Debug, PartialEq)]
1232enum BuildInternal {
1233 Auto,
1234 Always,
1235 Never,
1236}
1237
1238impl Default for BuildInternal {
1239 fn default() -> Self {
1240 Self::Never
1241 }
1242}
1243
1244impl FromStr for BuildInternal {
1245 type Err = ParseError;
1246
1247 fn from_str(s: &str) -> Result<Self, Self::Err> {
1248 match s {
1249 "auto" => Ok(Self::Auto),
1250 "always" => Ok(Self::Always),
1251 "never" => Ok(Self::Never),
1252 v: &str => Err(ParseError::VariantNotFound(v.to_owned())),
1253 }
1254 }
1255}
1256
1257#[derive(Debug, PartialEq)]
1258enum ParseError {
1259 VariantNotFound(String),
1260}
1261
1262impl std::error::Error for ParseError {}
1263
1264impl fmt::Display for ParseError {
1265 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1266 match self {
1267 Self::VariantNotFound(v: &String) => write!(f, "Unknown variant: `{}`", v),
1268 }
1269 }
1270}
1271