Skip to main content

system_deps/
lib.rs

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//!
9//! In your `Cargo.toml`:
10//!
11//! ```toml
12//! [build-dependencies]
13//! system-deps = "7.0"
14//! ```
15//!
16//! Then, to declare a dependency on `testlib >= 1.2`
17//! add the following section:
18//!
19//! ```toml
20//! [package.metadata.system-deps]
21//! testlib = "1.2"
22//! ```
23//!
24//! Finally, in your `build.rs`, add:
25//!
26//! ```should_panic
27//! fn main() {
28//!     system_deps::Config::new().probe().unwrap();
29//! }
30//! ```
31//!
32//! # Version format
33//!
34//! Versions can be expressed in the following formats
35//!
36//!   * "1.2" or ">= 1.2": At least version 1.2
37//!   * ">= 1.2, < 2.0": At least version 1.2 but less than version 2.0
38//!
39//! In the future more complicated version expressions might be supported.
40//!
41//! Note that these versions are not interpreted according to the semver rules, but based on the
42//! rules defined by pkg-config.
43//!
44//! # Feature-specific dependency
45//! You can easily declare an optional system dependency by associating it with a feature:
46//!
47//! ```toml
48//! [package.metadata.system-deps]
49//! testdata = { version = "4.5", feature = "use-testdata" }
50//! ```
51//!
52//! `system-deps` will check for `testdata` only if the `use-testdata` feature has been enabled.
53//!
54//! # Optional dependency
55//!
56//! Another option is to use the `optional` setting, which can also be used using [features versions](#feature-versions):
57//!
58//! ```toml
59//! [package.metadata.system-deps]
60//! test-data = { version = "4.5", optional = true }
61//! testmore = { version = "2", v3 = { version = "3.0", optional = true }}
62//! ```
63//!
64//! `system-deps` will automatically export for each dependency a feature `system_deps_have_$DEP` where `$DEP`
65//! is the `toml` key defining the dependency in [snake_case](https://en.wikipedia.org/wiki/Snake_case).
66//! This can be used to check if an optional dependency has been found or not:
67//!
68//! ```
69//! #[cfg(system_deps_have_testdata)]
70//! println!("found test-data");
71//! ```
72//!
73//! # Overriding library name
74//! `toml` keys cannot contain dot characters so if your library name does, you can define it using the `name` field:
75//!
76//! ```toml
77//! [package.metadata.system-deps]
78//! glib = { name = "glib-2.0", version = "2.64" }
79//! ```
80//!
81//! # Fallback library names
82//!
83//! Some libraries may be available under different names on different platforms or distributions.
84//! To allow for this, you can define fallback names to search for if the main library name does not work.
85//!
86//! ```toml
87//! [package.metadata.system-deps]
88//! aravis = { fallback-names = ["aravis-0.8"] }
89//! ```
90//!
91//! You may also specify different fallback names for different versions:
92//!
93//! ```toml
94//! [package.metadata.system-deps.libfoo]
95//! version = "0.1"
96//! fallback-names = ["libfoo-0.1"]
97//! v1 = { version = "1.0", fallback-names = ["libfoo1"] }
98//! v2 = { version = "2.0", fallback-names = ["libfoo2"] }
99//! ```
100//!
101//! # Feature versions
102//!
103//! `-sys` crates willing to support various versions of their underlying system libraries
104//! can use features to control the version of the dependency required.
105//! `system-deps` will pick the highest version among enabled features.
106//! Such version features must use the pattern `v1_0`, `v1_2`, etc.
107//!
108//! ```toml
109//! [features]
110//! v1_2 = []
111//! v1_4 = ["v1_2"]
112//! v1_6 = ["v1_4"]
113//!
114//! [package.metadata.system-deps.libfoo_1_0]
115//! name = "libfoo-1.0"
116//! version = "1.0"
117//! v1_2 = { version = "1.2" }
118//! v1_4 = { version = "1.4" }
119//! v1_6 = { version = "1.6" }
120//! ```
121//!
122//! The same mechanism can be used to require a different library name depending on the version:
123//!
124//! ```toml
125//! [package.metadata.system-deps.libfoo_gl]
126//! name = "libfoo-gl-1.0"
127//! version = "1.14"
128//! v1_18 = { version = "1.18", name = "libfoo-gl-egl-1.0" }
129//! ```
130//!
131//! # Target specific dependencies
132//!
133//! You can define target specific dependencies:
134//!
135//! ```toml
136//! [package.metadata.system-deps.'cfg(target_os = "linux")']
137//! testdata = "1"
138//! [package.metadata.system-deps.'cfg(not(target_os = "macos"))']
139//! testlib = "1"
140//! [package.metadata.system-deps.'cfg(unix)']
141//! testanotherlib = { version = "1", optional = true }
142//! ```
143//!
144//! See [the Rust documentation](https://doc.rust-lang.org/reference/conditional-compilation.html)
145//! for the exact syntax.
146//! Currently, those keys are supported:
147//! - `target_arch`
148//! - `target_endian`
149//! - `target_env`
150//! - `target_family`
151//! - `target_os`
152//! - `target_pointer_width`
153//! - `target_vendor`
154//! - `unix` and `windows`
155//!
156//! # Overriding build flags
157//!
158//! By default `system-deps` automatically defines the required build flags for each dependency using the information fetched from `pkg-config`.
159//! These flags can be overridden using environment variables if needed:
160//!
161//! - `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;
162//! - `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;
163//! - `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;
164//! - `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;
165//! - `SYSTEM_DEPS_$NAME_INCLUDE` to override the [`cargo:include`](https://kornel.ski/rust-sys-crate#headers) flag.
166//!
167//! With `$NAME` being the upper case name of the key defining the dependency in `Cargo.toml`.
168//! For example `SYSTEM_DEPS_TESTLIB_SEARCH_NATIVE=/opt/lib` could be used to override a dependency named `testlib`.
169//!
170//! One can also define the environment variable `SYSTEM_DEPS_$NAME_NO_PKG_CONFIG` to fully disable `pkg-config` lookup
171//! for the given dependency. In this case at least SYSTEM_DEPS_$NAME_LIB or SYSTEM_DEPS_$NAME_LIB_FRAMEWORK should be defined as well.
172//!
173//! # Internally build system libraries
174//!
175//! `-sys` crates can provide support for building and statically link their underlying system library as part of their build process.
176//! Here is how to do this in your `build.rs`:
177//!
178//! ```should_panic
179//! fn main() {
180//!     system_deps::Config::new()
181//!         .add_build_internal("testlib", |lib, version| {
182//!             // Actually build the library here that fulfills the passed in version requirements
183//!             system_deps::Library::from_internal_pkg_config("build/path-to-pc-file", lib, "1.2.4")
184//!          })
185//!         .probe()
186//!         .unwrap();
187//! }
188//! ```
189//!
190//! This feature can be controlled using the `SYSTEM_DEPS_$NAME_BUILD_INTERNAL` environment variable
191//! which can have the following values:
192//!
193//! - `auto`: build the dependency only if the required version has not been found by `pkg-config`;
194//! - `always`: always build the dependency, ignoring any version which may be installed on the system;
195//! - `never`: (default) never build the dependency, `system-deps` will fail if the required version is not found on the system.
196//!
197//! You can also use the `SYSTEM_DEPS_BUILD_INTERNAL` environment variable with the same values
198//! defining the behavior for all the dependencies which don't have `SYSTEM_DEPS_$NAME_BUILD_INTERNAL` defined.
199//!
200//! # Static linking
201//!
202//! By default all libraries are dynamically linked, except when build internally as [described above](#internally-build-system-libraries).
203//! Libraries can be statically linked by defining the environment variable `SYSTEM_DEPS_$NAME_LINK=static`.
204//! You can also use `SYSTEM_DEPS_LINK=static` to statically link all the libraries.
205//!
206//! The libraries specified with this option can have any form readable by `pkg-config`, and they will inherit the main libraries'
207//! binary paths if you are using them. If `pkg-config` can't find some entry, it will print a warning but the compilation won't fail.
208//!
209//! # Using prebuilt binaries
210//!
211//! Some system libraries may take too long to build or require a specific environment. `system-deps` allows to download and link against
212//! prebuilt library binaries specified in the crate metadata. To do so, you need to enable the `binary` feature and configure the library metadata.
213//!
214//! ```toml
215//! [package.metadata.system-deps.liba]
216//! name = "liba"
217//! version = "1.0"
218//! url = "https://download/liba-1.0.tar.gz"
219//! checksum = "..."
220//! pkg_paths = [ "lib/pkgconfig" ]
221//! ```
222//!
223//! The snippet above will attempt to download the archive specified in the `url` field, extract it and add the relative paths from `pkg_paths` to the
224//! `PKG_CONFIG_PATH` when looking for `liba`. This is done automatically and dependents of the library don't need to make any changes.
225//! It is recommended to have a feature in the crate's `Cargo.toml` that enables the `binary` feature in `system-deps`, instead of hard-coding it.
226//!
227//! ```toml
228//! [features]
229//! binary = [ "system-deps/binary", "system-deps/gz" ]
230//! ```
231//!
232//! As oppossed to the other metadata in `system-deps`, the metadata section can be specified anywhere in the crate tree, with entries from top level crates having priority.
233//! This allows for a crate to provide a default value for its binaries, and a dependent crate to add extra configuration.
234//!
235//! ```toml
236//! # Crate graph: user_project -> libb -> liba
237//!
238//! # libb/Cargo.toml
239//! [package.metadata.system-deps.liba]
240//! url = "https://download/custom-liba-1.0.tar.gz"
241//!
242//! # user_project/Cargo.toml
243//! [package.metadata.system-deps.liba]
244//! url = "file:///tmp/liba"
245//! ```
246//!
247//! In this example, `libb` overwrites the binaries provided by `liba` (for compatibility reasons, to add flags needed by `libb`, to use a single package for both...).
248//! However, the user project overwrites them again to point at a local file for development.
249//!
250//! The binaries can be configured per target like other `system-deps` options:
251//!
252//! ```toml
253//! [package.metadata.system-deps.liba.'cfg(target = "unix")']
254//! url = "https://download/liba-unix-1.0.tar.gz"
255//!
256//! [package.metadata.system-deps.liba.'cfg(target = "windows")']
257//! url = "https://download/liba-windows-1.0.zip"
258//! ```
259//!
260//! By default, a binary archive adds its paths to `PKG_CONFIG_PATH` only for the library it is defined for. However, sometimes you may want to share a single url
261//! for multiple libraries. While it is possible to repeat the url for every entry, a more concise approach is to use `follows` to copy the configuration from another library.
262//!
263//! ```toml
264//! [package.metadata.system-deps.libb]
265//! follows = "liba" # This name corresponds to the key of the metadata table
266//! ```
267//!
268
269#![deny(missing_docs)]
270
271#[cfg(test)]
272mod test;
273
274use heck::{ToShoutySnakeCase, ToSnakeCase};
275use std::{
276    borrow::Borrow,
277    collections::{BTreeMap, HashMap},
278    env,
279    ffi::OsString,
280    fmt, iter,
281    ops::RangeBounds,
282    path::{Path, PathBuf},
283    str::FromStr,
284};
285
286mod metadata;
287use metadata::MetaData;
288
289#[cfg(all(test, feature = "binary"))]
290mod test_binary;
291
292/// system-deps errors
293#[derive(Debug)]
294pub enum Error {
295    /// pkg-config error
296    PkgConfig(pkg_config::Error),
297    /// One of the `Config::add_build_internal` closures failed
298    BuildInternalClosureError(String, BuildInternalClosureError),
299    /// Failed to read `Cargo.toml`
300    FailToRead(String, std::io::Error),
301    /// Raised when an error is detected in the metadata defined in `Cargo.toml`
302    InvalidMetadata(String),
303    /// Raised when dependency defined manually using `SYSTEM_DEPS_$NAME_NO_PKG_CONFIG`
304    /// did not define at least one lib using `SYSTEM_DEPS_$NAME_LIB` or
305    /// `SYSTEM_DEPS_$NAME_LIB_FRAMEWORK`
306    MissingLib(String),
307    /// An environment variable in the form of `SYSTEM_DEPS_$NAME_BUILD_INTERNAL`
308    /// contained an invalid value (allowed: `auto`, `always`, `never`)
309    BuildInternalInvalid(String),
310    /// system-deps has been asked to internally build a lib, through
311    /// `SYSTEM_DEPS_$NAME_BUILD_INTERNAL=always' or `SYSTEM_DEPS_$NAME_BUILD_INTERNAL=auto',
312    /// but not closure has been defined using `Config::add_build_internal` to build
313    /// this lib
314    BuildInternalNoClosure(String, String),
315    /// The library which has been build internally does not match the
316    /// required version defined in `Cargo.toml`
317    BuildInternalWrongVersion(String, String, String),
318    /// The `cfg()` expression used in `Cargo.toml` is currently not supported
319    UnsupportedCfg(String),
320}
321
322impl From<pkg_config::Error> for Error {
323    fn from(err: pkg_config::Error) -> Self {
324        Self::PkgConfig(err)
325    }
326}
327
328impl std::error::Error for Error {
329    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
330        match self {
331            Self::PkgConfig(e) => Some(e),
332            Self::BuildInternalClosureError(_, e) => Some(e),
333            Self::FailToRead(_, e) => Some(e),
334            _ => None,
335        }
336    }
337}
338
339impl fmt::Display for Error {
340    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
341        match self {
342            Self::PkgConfig(e) => write!(f, "{e}"),
343            Self::BuildInternalClosureError(s, e) => write!(f, "Failed to build {s}: {e}"),
344            Self::FailToRead(s, _) => write!(f, "{s}"),
345            Self::InvalidMetadata(s) => write!(f, "{s}"),
346            Self::MissingLib(s) => write!(
347                f,
348                "You should define at least one lib using {} or {}",
349                EnvVariable::new_lib(s),
350                EnvVariable::new_lib_framework(s),
351            ),
352            Self::BuildInternalInvalid(s) => write!(f, "{s}"),
353            Self::BuildInternalNoClosure(s1, s2) => {
354                write!(f, "Missing build internal closure for {s1} (version {s2})")
355            }
356            Self::BuildInternalWrongVersion(s1, s2, s3) => write!(
357                f,
358                "Internally built {s1} {s2} but minimum required version is {s3}"
359            ),
360            Self::UnsupportedCfg(s) => write!(f, "Unsupported cfg() expression: {s}"),
361        }
362    }
363}
364
365#[derive(Debug, Default)]
366/// All the system dependencies retrieved by [`Config::probe`].
367pub struct Dependencies {
368    libs: BTreeMap<String, Library>,
369}
370
371impl Dependencies {
372    /// Retrieve details about a system dependency.
373    ///
374    /// # Arguments
375    ///
376    /// * `name`: the name of the `toml` key defining the dependency in `Cargo.toml`
377    pub fn get_by_name(&self, name: &str) -> Option<&Library> {
378        self.libs.get(name)
379    }
380
381    /// A vector listing all system dependencies in sorted (for build reproducibility) order.
382    /// The first element of the tuple is the name of the `toml` key defining the
383    /// dependency in `Cargo.toml`.
384    pub fn iter(&self) -> Vec<(&str, &Library)> {
385        let mut v = self
386            .libs
387            .iter()
388            .map(|(k, v)| (k.as_str(), v))
389            .collect::<Vec<_>>();
390        v.sort_by_key(|x| x.0);
391        v
392    }
393
394    fn aggregate_str<F: Fn(&Library) -> &Vec<String>>(&self, getter: F) -> Vec<&str> {
395        let mut v = self
396            .libs
397            .values()
398            .flat_map(getter)
399            .map(|s| s.as_str())
400            .collect::<Vec<_>>();
401        v.sort_unstable();
402        v.dedup();
403        v
404    }
405
406    fn aggregate_path_buf<F: Fn(&Library) -> &Vec<PathBuf>>(&self, getter: F) -> Vec<&PathBuf> {
407        let mut v = self.libs.values().flat_map(getter).collect::<Vec<_>>();
408        v.sort();
409        v.dedup();
410        v
411    }
412
413    /// Returns a vector of [`Library::libs`] of each library, removing duplicates.
414    pub fn all_libs(&self) -> Vec<&str> {
415        let mut v = self
416            .libs
417            .values()
418            .flat_map(|l| l.libs.iter().map(|lib| lib.name.as_str()))
419            .collect::<Vec<_>>();
420        v.sort_unstable();
421        v.dedup();
422        v
423    }
424
425    /// Returns a vector of [`Library::link_paths`] of each library, removing duplicates.
426    pub fn all_link_paths(&self) -> Vec<&PathBuf> {
427        self.aggregate_path_buf(|l| &l.link_paths)
428    }
429
430    /// Returns a vector of [`Library::frameworks`] of each library, removing duplicates.
431    pub fn all_frameworks(&self) -> Vec<&str> {
432        self.aggregate_str(|l| &l.frameworks)
433    }
434
435    /// Returns a vector of [`Library::framework_paths`] of each library, removing duplicates.
436    pub fn all_framework_paths(&self) -> Vec<&PathBuf> {
437        self.aggregate_path_buf(|l| &l.framework_paths)
438    }
439
440    /// Returns a vector of [`Library::include_paths`] of each library, removing duplicates.
441    pub fn all_include_paths(&self) -> Vec<&PathBuf> {
442        self.aggregate_path_buf(|l| &l.include_paths)
443    }
444
445    /// Returns a vector of [`Library::ld_args`] of each library, removing duplicates.
446    pub fn all_linker_args(&self) -> Vec<&Vec<String>> {
447        let mut v = self
448            .libs
449            .values()
450            .flat_map(|l| &l.ld_args)
451            .collect::<Vec<_>>();
452        v.sort_unstable();
453        v.dedup();
454        v
455    }
456
457    /// Returns a vector of [`Library::defines`] of each library, removing duplicates.
458    pub fn all_defines(&self) -> Vec<(&str, &Option<String>)> {
459        let mut v = self
460            .libs
461            .values()
462            .flat_map(|l| l.defines.iter())
463            .map(|(k, v)| (k.as_str(), v))
464            .collect::<Vec<_>>();
465        v.sort();
466        v.dedup();
467        v
468    }
469
470    fn add(&mut self, name: &str, lib: Library) {
471        self.libs.insert(name.to_string(), lib);
472    }
473
474    fn override_from_flags(&mut self, env: &EnvVariables) {
475        for (name, lib) in self.libs.iter_mut() {
476            if let Some(value) = env.get(&EnvVariable::new_search_native(name)) {
477                lib.link_paths = split_paths(&value);
478            }
479            if let Some(value) = env.get(&EnvVariable::new_search_framework(name)) {
480                lib.framework_paths = split_paths(&value);
481            }
482            if let Some(value) = env.get(&EnvVariable::new_lib(name)) {
483                let should_be_linked_statically = env
484                    .has_value(&EnvVariable::new_link(Some(name)), "static")
485                    || env.has_value(&EnvVariable::new_link(None), "static");
486
487                // If somebody manually mandates static linking, that is a
488                // clear intent. Let's just assume that a static lib is
489                // available and let the linking fail if the user is wrong.
490                let is_static_lib_available = should_be_linked_statically;
491
492                lib.libs = split_string(&value)
493                    .into_iter()
494                    .map(|l| InternalLib::new(l, is_static_lib_available))
495                    .collect();
496            }
497            if let Some(value) = env.get(&EnvVariable::new_lib_framework(name)) {
498                lib.frameworks = split_string(&value);
499            }
500            if let Some(value) = env.get(&EnvVariable::new_include(name)) {
501                lib.include_paths = split_paths(&value);
502            }
503            if let Some(value) = env.get(&EnvVariable::new_linker_args(name)) {
504                lib.ld_args = split_string(&value)
505                    .into_iter()
506                    .map(|l| l.split(',').map(|l| l.to_string()).collect())
507                    .collect();
508            }
509        }
510    }
511
512    fn gen_flags(&self) -> Result<BuildFlags, Error> {
513        let mut flags = BuildFlags::new();
514        let mut include_paths = Vec::new();
515
516        for (name, lib) in self.iter() {
517            include_paths.extend(lib.include_paths.clone());
518
519            if lib.source == Source::EnvVariables
520                && lib.libs.is_empty()
521                && lib.frameworks.is_empty()
522            {
523                return Err(Error::MissingLib(name.to_string()));
524            }
525
526            lib.link_paths
527                .iter()
528                .for_each(|l| flags.add(BuildFlag::SearchNative(l.to_string_lossy().to_string())));
529            lib.framework_paths.iter().for_each(|f| {
530                flags.add(BuildFlag::SearchFramework(f.to_string_lossy().to_string()))
531            });
532            lib.libs.iter().for_each(|l| {
533                flags.add(BuildFlag::Lib(
534                    l.name.clone(),
535                    lib.statik && l.is_static_available,
536                ))
537            });
538            lib.frameworks
539                .iter()
540                .for_each(|f| flags.add(BuildFlag::LibFramework(f.clone())));
541            lib.ld_args
542                .iter()
543                .for_each(|f| flags.add(BuildFlag::LinkArg(f.clone())))
544        }
545
546        // Export DEP_$CRATE_INCLUDE env variable with the headers paths,
547        // see https://kornel.ski/rust-sys-crate#headers
548        if !include_paths.is_empty() {
549            if let Ok(paths) = std::env::join_paths(include_paths) {
550                flags.add(BuildFlag::Include(paths.to_string_lossy().to_string()));
551            }
552        }
553
554        // Export cargo:rerun-if-env-changed instructions for all env variables affecting system-deps behaviour
555        flags.add(BuildFlag::RerunIfEnvChanged(
556            EnvVariable::new_build_internal(None),
557        ));
558        flags.add(BuildFlag::RerunIfEnvChanged(EnvVariable::new_link(None)));
559
560        for name in self.libs.keys() {
561            EnvVariable::set_rerun_if_changed_for_all_variants(&mut flags, name);
562        }
563
564        Ok(flags)
565    }
566}
567
568#[derive(Debug)]
569/// Error used in return value of `Config::add_build_internal` closures
570pub enum BuildInternalClosureError {
571    /// `pkg-config` error
572    PkgConfig(pkg_config::Error),
573    /// General failure
574    Failed(String),
575}
576
577impl From<pkg_config::Error> for BuildInternalClosureError {
578    fn from(err: pkg_config::Error) -> Self {
579        Self::PkgConfig(err)
580    }
581}
582
583impl BuildInternalClosureError {
584    /// Create a new `BuildInternalClosureError::Failed` representing a general
585    /// failure.
586    ///
587    /// # Arguments
588    ///
589    /// * `details`: human-readable details about the failure
590    pub fn failed(details: &str) -> Self {
591        Self::Failed(details.to_string())
592    }
593}
594
595impl std::error::Error for BuildInternalClosureError {
596    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
597        match self {
598            Self::PkgConfig(e) => Some(e),
599            _ => None,
600        }
601    }
602}
603
604impl fmt::Display for BuildInternalClosureError {
605    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
606        match self {
607            Self::PkgConfig(e) => write!(f, "{e}"),
608            Self::Failed(s) => write!(f, "{s}"),
609        }
610    }
611}
612
613// Enum representing the environment variables user can define to tune system-deps.
614#[derive(Debug, PartialEq)]
615enum EnvVariable {
616    Lib(String),
617    LibFramework(String),
618    SearchNative(String),
619    SearchFramework(String),
620    Include(String),
621    NoPkgConfig(String),
622    BuildInternal(Option<String>),
623    Link(Option<String>),
624    LinkerArgs(String),
625    NoPrebuilt(Option<String>),
626}
627
628impl EnvVariable {
629    fn new_lib(lib: &str) -> Self {
630        Self::Lib(lib.to_string())
631    }
632
633    fn new_lib_framework(lib: &str) -> Self {
634        Self::LibFramework(lib.to_string())
635    }
636
637    fn new_search_native(lib: &str) -> Self {
638        Self::SearchNative(lib.to_string())
639    }
640
641    fn new_search_framework(lib: &str) -> Self {
642        Self::SearchFramework(lib.to_string())
643    }
644
645    fn new_include(lib: &str) -> Self {
646        Self::Include(lib.to_string())
647    }
648
649    fn new_linker_args(lib: &str) -> Self {
650        Self::LinkerArgs(lib.to_string())
651    }
652
653    fn new_no_pkg_config(lib: &str) -> Self {
654        Self::NoPkgConfig(lib.to_string())
655    }
656
657    fn new_build_internal(lib: Option<&str>) -> Self {
658        Self::BuildInternal(lib.map(|l| l.to_string()))
659    }
660
661    fn new_link(lib: Option<&str>) -> Self {
662        Self::Link(lib.map(|l| l.to_string()))
663    }
664
665    fn new_no_prebuilt(lib: Option<&str>) -> Self {
666        Self::NoPrebuilt(lib.map(|l| l.to_string()))
667    }
668
669    const fn suffix(&self) -> &'static str {
670        match self {
671            EnvVariable::Lib(_) => "LIB",
672            EnvVariable::LibFramework(_) => "LIB_FRAMEWORK",
673            EnvVariable::SearchNative(_) => "SEARCH_NATIVE",
674            EnvVariable::SearchFramework(_) => "SEARCH_FRAMEWORK",
675            EnvVariable::Include(_) => "INCLUDE",
676            EnvVariable::NoPkgConfig(_) => "NO_PKG_CONFIG",
677            EnvVariable::BuildInternal(_) => "BUILD_INTERNAL",
678            EnvVariable::Link(_) => "LINK",
679            EnvVariable::LinkerArgs(_) => "LDFLAGS",
680            EnvVariable::NoPrebuilt(_) => "NO_PREBUILT",
681        }
682    }
683
684    fn set_rerun_if_changed_for_all_variants(flags: &mut BuildFlags, name: &str) {
685        #[inline]
686        fn add_to_flags(flags: &mut BuildFlags, var: EnvVariable) {
687            flags.add(BuildFlag::RerunIfEnvChanged(var));
688        }
689        add_to_flags(flags, EnvVariable::new_lib(name));
690        add_to_flags(flags, EnvVariable::new_lib_framework(name));
691        add_to_flags(flags, EnvVariable::new_search_native(name));
692        add_to_flags(flags, EnvVariable::new_search_framework(name));
693        add_to_flags(flags, EnvVariable::new_include(name));
694        add_to_flags(flags, EnvVariable::new_linker_args(name));
695        add_to_flags(flags, EnvVariable::new_no_pkg_config(name));
696        add_to_flags(flags, EnvVariable::new_build_internal(Some(name)));
697        add_to_flags(flags, EnvVariable::new_link(Some(name)));
698        add_to_flags(flags, EnvVariable::new_no_prebuilt(Some(name)));
699    }
700}
701
702impl fmt::Display for EnvVariable {
703    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
704        let suffix = match self {
705            EnvVariable::Lib(lib)
706            | EnvVariable::LibFramework(lib)
707            | EnvVariable::SearchNative(lib)
708            | EnvVariable::SearchFramework(lib)
709            | EnvVariable::Include(lib)
710            | EnvVariable::LinkerArgs(lib)
711            | EnvVariable::NoPkgConfig(lib)
712            | EnvVariable::BuildInternal(Some(lib))
713            | EnvVariable::Link(Some(lib))
714            | EnvVariable::NoPrebuilt(Some(lib)) => {
715                format!("{}_{}", lib.to_shouty_snake_case(), self.suffix())
716            }
717            EnvVariable::BuildInternal(None)
718            | EnvVariable::Link(None)
719            | EnvVariable::NoPrebuilt(None) => self.suffix().to_string(),
720        };
721        write!(f, "SYSTEM_DEPS_{suffix}")
722    }
723}
724
725type FnBuildInternal =
726    dyn FnOnce(&str, &str) -> std::result::Result<Library, BuildInternalClosureError>;
727
728/// Structure used to configure `metadata` before starting to probe for dependencies
729pub struct Config {
730    env: EnvVariables,
731    build_internals: HashMap<String, Box<FnBuildInternal>>,
732    #[cfg(feature = "binary")]
733    paths: &'static system_deps_meta::binary::Paths,
734}
735
736impl Default for Config {
737    fn default() -> Self {
738        Self::new_with_env(EnvVariables::Environment)
739    }
740}
741
742impl Config {
743    /// Create a new set of configuration
744    pub fn new() -> Self {
745        Self::default()
746    }
747
748    fn new_with_env(env: EnvVariables) -> Self {
749        let me = Self {
750            env,
751            build_internals: HashMap::new(),
752            #[cfg(feature = "binary")]
753            paths: {
754                // Constructed by reading the serialized value saved by the build script
755                const CONTENT: &str = include_str!(env!("SYSTEM_DEPS_BINARY_PATHS"));
756                static PATHS: std::sync::OnceLock<system_deps_meta::binary::Paths> =
757                    std::sync::OnceLock::new();
758                PATHS.get_or_init(|| {
759                    toml::from_str(CONTENT)
760                        .expect("The build script should output valid serialization")
761                })
762            },
763        };
764
765        // Prefer the pkg-config shipped by a downloaded bundle. The bundle's
766        // `.pc` files (absolute paths, prefix expectations, MSVC quirks on
767        // Windows) are only guaranteed to be readable by the pkg-config that
768        // ships with it; system pkg-configs in `PATH` are often misconfigured
769        // (Strawberry Perl, MSYS2, chocolatey, distro forks) and silently
770        // produce wrong flags. Only an explicit `PKG_CONFIG` env var
771        // overrides this.
772        #[cfg(feature = "binary")]
773        if env::var_os("PKG_CONFIG").is_none() {
774            if let Some(pc) = me.paths.pkg_config_binary() {
775                env::set_var("PKG_CONFIG", pc);
776            }
777        }
778
779        me
780    }
781
782    /// Probe all libraries configured in the Cargo.toml
783    /// `[package.metadata.system-deps]` section.
784    ///
785    /// The returned hash is using the `toml` key defining the dependency as key.
786    pub fn probe(self) -> Result<Dependencies, Error> {
787        let libraries = self.probe_full()?;
788        let flags = libraries.gen_flags()?;
789
790        // Output cargo flags
791        println!("{flags}");
792
793        for (name, _) in libraries.iter() {
794            println!("cargo:rustc-cfg=system_deps_have_{}", name.to_snake_case());
795        }
796
797        Ok(libraries)
798    }
799
800    /// Add hook so system-deps can internally build library `name` if requested by user.
801    ///
802    /// It will only be triggered if the environment variable
803    /// `SYSTEM_DEPS_$NAME_BUILD_INTERNAL` is defined with either `always` or
804    /// `auto` as value. In the latter case, `func` is called only if the requested
805    /// version of the library was not found on the system.
806    ///
807    /// # Arguments
808    /// * `name`: the name of the library, as defined in `Cargo.toml`
809    /// * `func`: closure called when internally building the library.
810    ///
811    /// It receives as argument the library name, and the minimum version required.
812    pub fn add_build_internal<F>(mut self, name: &str, func: F) -> Self
813    where
814        F: 'static + FnOnce(&str, &str) -> std::result::Result<Library, BuildInternalClosureError>,
815    {
816        let mut build_internals = self.build_internals;
817        build_internals.insert(name.to_string(), Box::new(func));
818
819        self.build_internals = build_internals;
820        self
821    }
822
823    /// Checks the map from packages to their provided prebuilt binaries locations, if available.
824    pub fn query_path(&self, _pkg: &str) -> Option<&'static Vec<PathBuf>> {
825        #[cfg(not(feature = "binary"))]
826        return None;
827
828        #[cfg(feature = "binary")]
829        self.env
830            .get(&EnvVariable::new_no_prebuilt(Some(_pkg)))
831            .or(self.env.get(&EnvVariable::new_no_prebuilt(None)))
832            .map_or_else(|| self.paths.get(_pkg), |_| None)
833    }
834
835    fn probe_full(mut self) -> Result<Dependencies, Error> {
836        let mut libraries = self.probe_pkg_config()?;
837        libraries.override_from_flags(&self.env);
838        Ok(libraries)
839    }
840
841    fn probe_pkg_config(&mut self) -> Result<Dependencies, Error> {
842        let dir = self
843            .env
844            .get("CARGO_MANIFEST_DIR")
845            .ok_or_else(|| Error::InvalidMetadata("$CARGO_MANIFEST_DIR not set".into()))?;
846        let mut path = PathBuf::from(dir);
847        path.push("Cargo.toml");
848
849        println!("cargo:rerun-if-changed={}", path.to_string_lossy());
850
851        let metadata = MetaData::from_file(&path)?;
852        let mut libraries = Dependencies::default();
853
854        // Collect keys that have an unconditional (non-cfg) dep so we can skip
855        // cfg-gated duplicates that only carry binary metadata.
856        let unconditional_keys: std::collections::HashSet<_> = metadata
857            .deps
858            .iter()
859            .filter(|d| d.cfg.is_none())
860            .map(|d| &d.key)
861            .collect();
862
863        for dep in metadata.deps.iter() {
864            if let Some(cfg) = &dep.cfg {
865                // Check if `cfg()` expression matches the target settings
866                if !self.check_cfg(cfg)? {
867                    continue;
868                }
869                // Skip cfg-gated deps whose key also has an unconditional entry.
870                // The cfg-gated entry typically only carries binary-related metadata
871                // (url, checksum, etc.) handled by system-deps-meta, not here.
872                if unconditional_keys.contains(&dep.key) {
873                    continue;
874                }
875            }
876
877            let mut enabled_feature_overrides = Vec::new();
878
879            for o in dep.version_overrides.iter() {
880                if self.has_feature(&o.key) {
881                    enabled_feature_overrides.push(o);
882                }
883            }
884
885            if let Some(feature) = dep.feature.as_ref() {
886                if !self.has_feature(feature) {
887                    continue;
888                }
889            }
890
891            // Pick the highest feature enabled version
892            let version;
893            let lib_name;
894            let fallback_lib_names;
895            let optional;
896            if enabled_feature_overrides.is_empty() {
897                version = dep.version.as_deref();
898                lib_name = dep.lib_name();
899                fallback_lib_names = dep.fallback_names.as_deref().unwrap_or(&[]);
900                optional = dep.optional;
901            } else {
902                enabled_feature_overrides.sort_by(|a, b| {
903                    fn min_version(r: metadata::VersionRange<'_>) -> &str {
904                        match r.start_bound() {
905                            std::ops::Bound::Unbounded => unreachable!(),
906                            std::ops::Bound::Excluded(_) => unreachable!(),
907                            std::ops::Bound::Included(b) => b,
908                        }
909                    }
910
911                    let a = min_version(metadata::parse_version(&a.version));
912                    let b = min_version(metadata::parse_version(&b.version));
913
914                    version_compare::compare(a, b)
915                        .expect("failed to compare versions")
916                        .ord()
917                        .expect("invalid version")
918                });
919                let highest = enabled_feature_overrides.into_iter().next_back().unwrap();
920
921                version = Some(highest.version.as_str());
922                lib_name = highest.name.as_deref().unwrap_or(dep.lib_name());
923                fallback_lib_names = highest
924                    .fallback_names
925                    .as_deref()
926                    .or(dep.fallback_names.as_deref())
927                    .unwrap_or(&[]);
928                optional = highest.optional.unwrap_or(dep.optional);
929            };
930
931            let version = version.ok_or_else(|| {
932                Error::InvalidMetadata(format!("No version defined for {}", dep.key))
933            })?;
934
935            let name = &dep.key;
936            let build_internal = self.get_build_internal_status(name)?;
937
938            // `Some` only for a dep served by a downloaded binary bundle.
939            // TODO: Pass package version here
940            let prebuilt_pkg_config_paths = self.query_path(name);
941
942            // should the lib be statically linked?
943            let statik = cfg!(feature = "binary")
944                || self
945                    .env
946                    .has_value(&EnvVariable::new_link(Some(name)), "static")
947                || self.env.has_value(&EnvVariable::new_link(None), "static");
948
949            let mut library = if self.env.contains(&EnvVariable::new_no_pkg_config(name)) {
950                Library::from_env_variables(name)
951            } else if build_internal == BuildInternal::Always {
952                self.call_build_internal(lib_name, version)?
953            } else {
954                let mut config = pkg_config::Config::new();
955                config
956                    .print_system_libs(false)
957                    .cargo_metadata(false)
958                    .range_version(metadata::parse_version(version))
959                    .statik(statik);
960
961                // Bundles ship pkg-config 0.29.2, whose `--define-prefix` (on by
962                // default on Windows) miscomputes `prefix` for nested layouts
963                // like `lib/gstreamer-1.0/pkgconfig`, inflating `${libdir}` to
964                // `<bundle>/lib/lib`. The bundled `.pc` carry correct
965                // `prefix=${pcfiledir}/...`, so disable it, but only when the
966                // tool accepts the flag (older ones lack the behaviour anyway).
967                #[cfg(windows)]
968                if prebuilt_pkg_config_paths.is_some() && pkg_config_accepts_dont_define_prefix() {
969                    config.arg("--dont-define-prefix");
970                }
971
972                let probe = Library::wrap_pkg_config(prebuilt_pkg_config_paths, || {
973                    Self::probe_with_fallback(&config, lib_name, fallback_lib_names)
974                });
975
976                match probe {
977                    Ok((lib_name, lib)) => Library::from_pkg_config(lib_name, lib),
978                    Err(e) => {
979                        if build_internal == BuildInternal::Auto {
980                            // Try building the lib internally as a fallback
981                            self.call_build_internal(name, version)?
982                        } else if optional {
983                            // If the dep is optional just skip it
984                            continue;
985                        } else {
986                            return Err(e.into());
987                        }
988                    }
989                }
990            };
991
992            library.statik = statik;
993
994            libraries.add(name, library);
995        }
996
997        Ok(libraries)
998    }
999
1000    fn probe_with_fallback<'a>(
1001        config: &'a pkg_config::Config,
1002        name: &'a str,
1003        fallback_names: &'a [String],
1004    ) -> Result<(&'a str, pkg_config::Library), pkg_config::Error> {
1005        let error = match config.probe(name) {
1006            Ok(x) => return Ok((name, x)),
1007            Err(e) => e,
1008        };
1009        for name in fallback_names {
1010            if let Ok(library) = config.probe(name) {
1011                return Ok((name, library));
1012            }
1013        }
1014        Err(error)
1015    }
1016
1017    fn get_build_internal_env_var(&self, var: EnvVariable) -> Result<Option<BuildInternal>, Error> {
1018        match self.env.get(&var).as_deref() {
1019            Some(s) => {
1020                let b = BuildInternal::from_str(s).map_err(|_| {
1021                    Error::BuildInternalInvalid(format!(
1022                        "Invalid value in {var}: {s} (allowed: 'auto', 'always', 'never')"
1023                    ))
1024                })?;
1025                Ok(Some(b))
1026            }
1027            None => Ok(None),
1028        }
1029    }
1030
1031    fn get_build_internal_status(&self, name: &str) -> Result<BuildInternal, Error> {
1032        match self.get_build_internal_env_var(EnvVariable::new_build_internal(Some(name)))? {
1033            Some(b) => Ok(b),
1034            None => Ok(self
1035                .get_build_internal_env_var(EnvVariable::new_build_internal(None))?
1036                .unwrap_or_default()),
1037        }
1038    }
1039
1040    fn call_build_internal(&mut self, name: &str, version_str: &str) -> Result<Library, Error> {
1041        let lib = match self.build_internals.remove(name) {
1042            Some(f) => f(name, version_str)
1043                .map_err(|e| Error::BuildInternalClosureError(name.into(), e))?,
1044            None => {
1045                return Err(Error::BuildInternalNoClosure(
1046                    name.into(),
1047                    version_str.into(),
1048                ))
1049            }
1050        };
1051
1052        // Check that the lib built internally matches the required version
1053        let version = metadata::parse_version(version_str);
1054        fn min_version(r: metadata::VersionRange<'_>) -> &str {
1055            match r.start_bound() {
1056                std::ops::Bound::Unbounded => unreachable!(),
1057                std::ops::Bound::Excluded(_) => unreachable!(),
1058                std::ops::Bound::Included(b) => b,
1059            }
1060        }
1061        fn max_version(r: metadata::VersionRange<'_>) -> Option<&str> {
1062            match r.end_bound() {
1063                std::ops::Bound::Included(_) => unreachable!(),
1064                std::ops::Bound::Unbounded => None,
1065                std::ops::Bound::Excluded(b) => Some(*b),
1066            }
1067        }
1068
1069        let min = min_version(version.clone());
1070        if version_compare::compare(&lib.version, min) == Ok(version_compare::Cmp::Lt) {
1071            return Err(Error::BuildInternalWrongVersion(
1072                name.into(),
1073                lib.version,
1074                version_str.into(),
1075            ));
1076        }
1077
1078        if let Some(max) = max_version(version) {
1079            if version_compare::compare(&lib.version, max) == Ok(version_compare::Cmp::Ge) {
1080                return Err(Error::BuildInternalWrongVersion(
1081                    name.into(),
1082                    lib.version,
1083                    version_str.into(),
1084                ));
1085            }
1086        }
1087
1088        Ok(lib)
1089    }
1090
1091    fn has_feature(&self, feature: &str) -> bool {
1092        let var: &str = &format!("CARGO_FEATURE_{}", feature.to_uppercase().replace('-', "_"));
1093        self.env.contains(var)
1094    }
1095
1096    fn check_cfg(&self, cfg: &cfg_expr::Expression) -> Result<bool, Error> {
1097        use cfg_expr::{targets::get_builtin_target_by_triple, Predicate};
1098
1099        let target = self
1100            .env
1101            .get("TARGET")
1102            .expect("no TARGET env variable defined");
1103
1104        let res = if let Some(target) = get_builtin_target_by_triple(&target) {
1105            cfg.eval(|pred| match pred {
1106                Predicate::Target(tp) => Some(tp.matches(target)),
1107                _ => None,
1108            })
1109        } else {
1110            // Attempt to parse the triple, the target is not an official builtin
1111            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));
1112
1113            cfg.eval(|pred| match pred {
1114                Predicate::Target(tp) => Some(tp.matches(&triple)),
1115                _ => None,
1116            })
1117        };
1118
1119        res.ok_or_else(|| Error::UnsupportedCfg(cfg.original().to_string()))
1120    }
1121}
1122
1123#[derive(Debug, PartialEq, Eq)]
1124/// From where the library settings have been retrieved
1125pub enum Source {
1126    /// Settings have been retrieved from `pkg-config`
1127    PkgConfig,
1128    /// Settings have been defined using user defined environment variables
1129    EnvVariables,
1130}
1131
1132#[derive(Debug, PartialEq, Eq)]
1133/// Internal library name and if a static library is available on the system
1134pub struct InternalLib {
1135    /// Name of the library
1136    pub name: String,
1137    /// Indicates if a static library is available on the system
1138    pub is_static_available: bool,
1139}
1140
1141impl InternalLib {
1142    const fn new(name: String, is_static_available: bool) -> Self {
1143        InternalLib {
1144            name,
1145            is_static_available,
1146        }
1147    }
1148}
1149
1150#[derive(Debug)]
1151/// A system dependency
1152pub struct Library {
1153    /// Name of the library
1154    pub name: String,
1155    /// From where the library settings have been retrieved
1156    pub source: Source,
1157    /// libraries the linker should link on
1158    pub libs: Vec<InternalLib>,
1159    /// directories where the compiler should look for libraries
1160    pub link_paths: Vec<PathBuf>,
1161    /// frameworks the linker should link on
1162    pub frameworks: Vec<String>,
1163    /// directories where the compiler should look for frameworks
1164    pub framework_paths: Vec<PathBuf>,
1165    /// directories where the compiler should look for header files
1166    pub include_paths: Vec<PathBuf>,
1167    /// flags that should be passed to the linker
1168    pub ld_args: Vec<Vec<String>>,
1169    /// macros that should be defined by the compiler
1170    pub defines: HashMap<String, Option<String>>,
1171    /// library version
1172    pub version: String,
1173    /// library is statically linked
1174    pub statik: bool,
1175}
1176
1177impl Library {
1178    fn from_pkg_config(name: &str, l: pkg_config::Library) -> Self {
1179        // taken from: https://github.com/rust-lang/pkg-config-rs/blob/54325785816695df031cef3b26b6a9a203bbc01b/src/lib.rs#L502
1180        let system_roots = if cfg!(target_os = "macos") {
1181            vec![PathBuf::from("/Library"), PathBuf::from("/System")]
1182        } else {
1183            let sysroot = env::var_os("PKG_CONFIG_SYSROOT_DIR")
1184                .or_else(|| env::var_os("SYSROOT"))
1185                .map(PathBuf::from);
1186
1187            if cfg!(target_os = "windows") {
1188                if let Some(sysroot) = sysroot {
1189                    vec![sysroot]
1190                } else {
1191                    vec![]
1192                }
1193            } else {
1194                vec![sysroot.unwrap_or_else(|| PathBuf::from("/usr"))]
1195            }
1196        };
1197
1198        let is_static_available = |name: &String| -> bool {
1199            // MSVC: don't claim a static archive is "available" even when
1200            // it is on disk. Emitting `cargo:rustc-link-lib=static=...`
1201            // makes rustc try to bundle the archive into the rlib, which
1202            // overflows `ar_archive_writer` (u32 limits) on archives the
1203            // size of gst-plugins-rs's `lib*.a`. Without the modifier,
1204            // MSVC `link.exe` resolves `name.lib` natively from the bundle.
1205            // Upstream issue:
1206            //   https://github.com/rust-lang/ar_archive_writer/issues/31
1207            if cfg!(all(target_os = "windows", target_env = "msvc")) {
1208                return false;
1209            }
1210            let libnames = {
1211                let mut names = vec![format!("lib{name}.a")];
1212                if cfg!(target_os = "windows") {
1213                    names.push(format!("{name}.lib"));
1214                }
1215                names
1216            };
1217
1218            l.link_paths.iter().any(|dir| {
1219                let library_exists = libnames.iter().any(|libname| dir.join(libname).exists());
1220                library_exists && !system_roots.iter().any(|sys| dir.starts_with(sys))
1221            })
1222        };
1223
1224        Self {
1225            name: name.to_string(),
1226            source: Source::PkgConfig,
1227            libs: l
1228                .libs
1229                .iter()
1230                .map(|lib| InternalLib::new(lib.to_owned(), is_static_available(lib)))
1231                .collect(),
1232            link_paths: l.link_paths,
1233            include_paths: l.include_paths,
1234            ld_args: l.ld_args,
1235            frameworks: l.frameworks,
1236            framework_paths: l.framework_paths,
1237            defines: l.defines,
1238            version: l.version,
1239            statik: false,
1240        }
1241    }
1242
1243    fn from_env_variables(name: &str) -> Self {
1244        Self {
1245            name: name.to_string(),
1246            source: Source::EnvVariables,
1247            libs: Vec::new(),
1248            link_paths: Vec::new(),
1249            include_paths: Vec::new(),
1250            ld_args: Vec::new(),
1251            frameworks: Vec::new(),
1252            framework_paths: Vec::new(),
1253            defines: HashMap::new(),
1254            version: String::new(),
1255            statik: false,
1256        }
1257    }
1258
1259    /// Calls a function changing the environment so that `pkg-config` will try to
1260    /// look first in the provided path.
1261    pub fn wrap_pkg_config<T, R>(
1262        pkg_config_paths: impl PathOrList<T>,
1263        f: impl FnOnce() -> Result<R, pkg_config::Error>,
1264    ) -> Result<R, pkg_config::Error> {
1265        // Save current PKG_CONFIG_PATH, so we can restore it
1266        let prev = env::var("PKG_CONFIG_PATH").ok();
1267
1268        let prev_paths = prev.iter().flat_map(env::split_paths).collect::<Vec<_>>();
1269        let joined_paths = pkg_config_paths.join_paths(prev_paths.as_slice());
1270
1271        // pkg-config 0.29.2 eats `\` while expanding `${pcfiledir}`, producing
1272        // corrupt flags (e.g. `C:Userslibpkgconfig`). Forward slashes work on
1273        // Windows and avoid this; there `\` is only ever a path separator.
1274        #[cfg(windows)]
1275        let joined_paths = OsString::from(joined_paths.to_string_lossy().replace('\\', "/"));
1276
1277        env::set_var("PKG_CONFIG_PATH", joined_paths);
1278
1279        let res = f();
1280
1281        if let Some(prev) = prev {
1282            env::set_var("PKG_CONFIG_PATH", prev);
1283        }
1284
1285        res
1286    }
1287
1288    /// Create a `Library` by probing `pkg-config` on an internal directory.
1289    /// This helper is meant to be used by `Config::add_build_internal` closures
1290    /// after having built the lib to return the library information to system-deps.
1291    ///
1292    /// This library will be statically linked.
1293    ///
1294    /// # Arguments
1295    ///
1296    /// * `pkg_config_dir`: the directory where the library `.pc` file is located
1297    /// * `lib`: the name of the library to look for
1298    /// * `version`: the minimum version of `lib` required
1299    ///
1300    /// # Examples
1301    ///
1302    /// ```
1303    /// let mut config = system_deps::Config::new();
1304    /// config.add_build_internal("mylib", |lib, version| {
1305    ///   // Actually build the library here that fulfills the passed in version requirements
1306    ///   system_deps::Library::from_internal_pkg_config("build-dir",
1307    ///       lib, "1.2.4")
1308    /// });
1309    /// ```
1310    pub fn from_internal_pkg_config<T>(
1311        pkg_config_paths: impl PathOrList<T>,
1312        lib: &str,
1313        version: &str,
1314    ) -> Result<Self, BuildInternalClosureError> {
1315        let pkg_lib = Self::wrap_pkg_config(pkg_config_paths, || {
1316            pkg_config::Config::new()
1317                .atleast_version(version)
1318                .print_system_libs(false)
1319                .cargo_metadata(false)
1320                .statik(true)
1321                .probe(lib)
1322        })?;
1323
1324        let mut lib = Self::from_pkg_config(lib, pkg_lib);
1325        lib.statik = true;
1326        Ok(lib)
1327    }
1328}
1329
1330/// Whether the resolved `pkg-config` accepts `--dont-define-prefix`. Cached.
1331#[cfg(windows)]
1332fn pkg_config_accepts_dont_define_prefix() -> bool {
1333    use std::sync::OnceLock;
1334    static SUPPORTED: OnceLock<bool> = OnceLock::new();
1335    *SUPPORTED.get_or_init(|| {
1336        let exe = env::var_os("PKG_CONFIG").unwrap_or_else(|| "pkg-config".into());
1337        std::process::Command::new(exe)
1338            .args(["--dont-define-prefix", "--version"])
1339            .output()
1340            .map(|out| out.status.success())
1341            .unwrap_or(false)
1342    })
1343}
1344
1345/// A trait that can represent both a reference to a Path like object or a list of paths.
1346/// Used in `Library::wrap_pkg_config` and `Library::from_internal_pkg_config` to specify
1347/// the list of `pkg-config` paths that should take priority.
1348pub trait PathOrList<T> {
1349    /// Creates an string of paths appropiately joined for an environment variable.
1350    /// The paths in `self` will go before the paths in `other`.
1351    fn join_paths(&self, other: impl AsRef<[PathBuf]>) -> OsString;
1352}
1353
1354impl<T: AsRef<Path>> PathOrList<T> for T {
1355    fn join_paths(&self, other: impl AsRef<[PathBuf]>) -> OsString {
1356        let other = other.as_ref().iter().map(|p| p.as_path());
1357        env::join_paths(iter::once(self.as_ref()).chain(other))
1358            .expect("Path contains invalid character")
1359    }
1360}
1361
1362impl<T: AsRef<Path>, S: Borrow<[T]>> PathOrList<(T, S)> for &S {
1363    fn join_paths(&self, other: impl AsRef<[PathBuf]>) -> OsString {
1364        let slice: &[T] = (*self).borrow();
1365        let other = other.as_ref().iter().map(|p| p.as_path());
1366        env::join_paths(slice.iter().map(|p| p.as_ref()).chain(other))
1367            .expect("Path contains invalid character")
1368    }
1369}
1370
1371impl<T: PathOrList<S>, S> PathOrList<(T, S)> for Option<T> {
1372    fn join_paths(&self, other: impl AsRef<[PathBuf]>) -> OsString {
1373        match self {
1374            Some(s) => s.join_paths(other),
1375            None => env::join_paths(other.as_ref().iter().map(|p| p.as_os_str()))
1376                .expect("Path contains invalid character"),
1377        }
1378    }
1379}
1380
1381#[derive(Debug)]
1382enum EnvVariables {
1383    Environment,
1384    #[cfg(test)]
1385    Mock(HashMap<&'static str, String>),
1386}
1387
1388trait EnvVariablesExt<T> {
1389    fn contains(&self, var: T) -> bool {
1390        self.get(var).is_some()
1391    }
1392
1393    fn get(&self, var: T) -> Option<String>;
1394
1395    fn has_value(&self, var: T, val: &str) -> bool {
1396        match self.get(var) {
1397            Some(v) => v == val,
1398            None => false,
1399        }
1400    }
1401}
1402
1403impl EnvVariablesExt<&str> for EnvVariables {
1404    fn get(&self, var: &str) -> Option<String> {
1405        match self {
1406            EnvVariables::Environment => env::var(var).ok(),
1407            #[cfg(test)]
1408            EnvVariables::Mock(vars) => vars.get(var).cloned(),
1409        }
1410    }
1411}
1412
1413impl EnvVariablesExt<&EnvVariable> for EnvVariables {
1414    fn get(&self, var: &EnvVariable) -> Option<String> {
1415        let s = var.to_string();
1416        let var: &str = s.as_ref();
1417        self.get(var)
1418    }
1419}
1420
1421#[derive(Debug, PartialEq)]
1422enum BuildFlag {
1423    Include(String),
1424    SearchNative(String),
1425    SearchFramework(String),
1426    Lib(String, bool), // true if static
1427    LibFramework(String),
1428    RerunIfEnvChanged(EnvVariable),
1429    LinkArg(Vec<String>),
1430}
1431
1432impl fmt::Display for BuildFlag {
1433    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1434        match self {
1435            BuildFlag::Include(paths) => write!(f, "include={paths}"),
1436            BuildFlag::SearchNative(lib) => write!(f, "rustc-link-search=native={lib}"),
1437            BuildFlag::SearchFramework(lib) => write!(f, "rustc-link-search=framework={lib}"),
1438            BuildFlag::Lib(lib, statik) => {
1439                if *statik {
1440                    write!(f, "rustc-link-lib=static={lib}")
1441                } else {
1442                    write!(f, "rustc-link-lib={lib}")
1443                }
1444            }
1445            BuildFlag::LibFramework(lib) => write!(f, "rustc-link-lib=framework={lib}"),
1446            BuildFlag::RerunIfEnvChanged(env) => write!(f, "rerun-if-env-changed={env}"),
1447            BuildFlag::LinkArg(ld_option) => {
1448                write!(f, "rustc-link-arg=-Wl,{}", ld_option.join(","))
1449            }
1450        }
1451    }
1452}
1453
1454#[derive(Debug, PartialEq)]
1455struct BuildFlags(Vec<BuildFlag>);
1456
1457impl BuildFlags {
1458    const fn new() -> Self {
1459        Self(Vec::new())
1460    }
1461
1462    fn add(&mut self, flag: BuildFlag) {
1463        self.0.push(flag);
1464    }
1465}
1466
1467impl fmt::Display for BuildFlags {
1468    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1469        for flag in self.0.iter() {
1470            writeln!(f, "cargo:{flag}")?;
1471        }
1472        Ok(())
1473    }
1474}
1475
1476fn split_paths(value: &str) -> Vec<PathBuf> {
1477    if !value.is_empty() {
1478        let paths = env::split_paths(&value);
1479        paths.map(|p| Path::new(&p).into()).collect()
1480    } else {
1481        Vec::new()
1482    }
1483}
1484
1485fn split_string(value: &str) -> Vec<String> {
1486    if !value.is_empty() {
1487        value.split(' ').map(|s| s.to_string()).collect()
1488    } else {
1489        Vec::new()
1490    }
1491}
1492
1493#[derive(Debug, PartialEq, Default)]
1494enum BuildInternal {
1495    Auto,
1496    Always,
1497    #[default]
1498    Never,
1499}
1500
1501impl FromStr for BuildInternal {
1502    type Err = ParseError;
1503
1504    fn from_str(s: &str) -> Result<Self, Self::Err> {
1505        match s {
1506            "auto" => Ok(Self::Auto),
1507            "always" => Ok(Self::Always),
1508            "never" => Ok(Self::Never),
1509            v => Err(ParseError::VariantNotFound(v.to_owned())),
1510        }
1511    }
1512}
1513
1514#[derive(Debug, PartialEq)]
1515enum ParseError {
1516    VariantNotFound(String),
1517}
1518
1519impl std::error::Error for ParseError {}
1520
1521impl fmt::Display for ParseError {
1522    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1523        match self {
1524            Self::VariantNotFound(v) => write!(f, "Unknown variant: `{v}`"),
1525        }
1526    }
1527}