| 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 = "2.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.gstreamer_1_0] |
| 115 | //! name = "gstreamer-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.gst_gl] |
| 126 | //! name = "gstreamer-gl-1.0" |
| 127 | //! version = "1.14" |
| 128 | //! v1_18 = { version = "1.18", name = "gstreamer-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 | #![deny (missing_docs)] |
| 207 | |
| 208 | #[cfg (test)] |
| 209 | #[macro_use ] |
| 210 | extern crate lazy_static; |
| 211 | |
| 212 | #[cfg (test)] |
| 213 | mod test; |
| 214 | |
| 215 | use heck::{ToShoutySnakeCase, ToSnakeCase}; |
| 216 | use std::collections::HashMap; |
| 217 | use std::env; |
| 218 | use std::fmt; |
| 219 | use std::ops::RangeBounds; |
| 220 | use std::path::{Path, PathBuf}; |
| 221 | use std::str::FromStr; |
| 222 | |
| 223 | mod metadata; |
| 224 | use metadata::MetaData; |
| 225 | |
| 226 | /// system-deps errors |
| 227 | #[derive (Debug)] |
| 228 | pub enum Error { |
| 229 | /// pkg-config error |
| 230 | PkgConfig(pkg_config::Error), |
| 231 | /// One of the `Config::add_build_internal` closures failed |
| 232 | BuildInternalClosureError(String, BuildInternalClosureError), |
| 233 | /// Failed to read `Cargo.toml` |
| 234 | FailToRead(String, std::io::Error), |
| 235 | /// Raised when an error is detected in the metadata defined in `Cargo.toml` |
| 236 | InvalidMetadata(String), |
| 237 | /// Raised when dependency defined manually using `SYSTEM_DEPS_$NAME_NO_PKG_CONFIG` |
| 238 | /// did not define at least one lib using `SYSTEM_DEPS_$NAME_LIB` or |
| 239 | /// `SYSTEM_DEPS_$NAME_LIB_FRAMEWORK` |
| 240 | MissingLib(String), |
| 241 | /// An environment variable in the form of `SYSTEM_DEPS_$NAME_BUILD_INTERNAL` |
| 242 | /// contained an invalid value (allowed: `auto`, `always`, `never`) |
| 243 | BuildInternalInvalid(String), |
| 244 | /// system-deps has been asked to internally build a lib, through |
| 245 | /// `SYSTEM_DEPS_$NAME_BUILD_INTERNAL=always' or `SYSTEM_DEPS_$NAME_BUILD_INTERNAL=auto', |
| 246 | /// but not closure has been defined using `Config::add_build_internal` to build |
| 247 | /// this lib |
| 248 | BuildInternalNoClosure(String, String), |
| 249 | /// The library which has been build internally does not match the |
| 250 | /// required version defined in `Cargo.toml` |
| 251 | BuildInternalWrongVersion(String, String, String), |
| 252 | /// The `cfg()` expression used in `Cargo.toml` is currently not supported |
| 253 | UnsupportedCfg(String), |
| 254 | } |
| 255 | |
| 256 | impl From<pkg_config::Error> for Error { |
| 257 | fn from(err: pkg_config::Error) -> Self { |
| 258 | Self::PkgConfig(err) |
| 259 | } |
| 260 | } |
| 261 | |
| 262 | impl std::error::Error for Error { |
| 263 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { |
| 264 | match self { |
| 265 | Self::PkgConfig(e: &Error) => Some(e), |
| 266 | Self::BuildInternalClosureError(_, e: &BuildInternalClosureError) => Some(e), |
| 267 | Self::FailToRead(_, e: &Error) => Some(e), |
| 268 | _ => None, |
| 269 | } |
| 270 | } |
| 271 | } |
| 272 | |
| 273 | impl fmt::Display for Error { |
| 274 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 275 | match self { |
| 276 | Self::PkgConfig(e) => write!(f, " {}" , e), |
| 277 | Self::BuildInternalClosureError(s, e) => write!(f, "Failed to build {}: {}" , s, e), |
| 278 | Self::FailToRead(s, _) => write!(f, " {}" , s), |
| 279 | Self::InvalidMetadata(s) => write!(f, " {}" , s), |
| 280 | Self::MissingLib(s) => write!( |
| 281 | f, |
| 282 | "You should define at least one lib using {} or {}" , |
| 283 | EnvVariable::new_lib(s), |
| 284 | EnvVariable::new_lib_framework(s), |
| 285 | ), |
| 286 | Self::BuildInternalInvalid(s) => write!(f, " {}" , s), |
| 287 | Self::BuildInternalNoClosure(s1, s2) => write!( |
| 288 | f, |
| 289 | "Missing build internal closure for {} (version {})" , |
| 290 | s1, s2 |
| 291 | ), |
| 292 | Self::BuildInternalWrongVersion(s1, s2, s3) => write!( |
| 293 | f, |
| 294 | "Internally built {} {} but minimum required version is {}" , |
| 295 | s1, s2, s3 |
| 296 | ), |
| 297 | Self::UnsupportedCfg(s) => write!(f, "Unsupported cfg() expression: {}" , s), |
| 298 | } |
| 299 | } |
| 300 | } |
| 301 | |
| 302 | #[derive (Debug, Default)] |
| 303 | /// All the system dependencies retrieved by [Config::probe]. |
| 304 | pub struct Dependencies { |
| 305 | libs: HashMap<String, Library>, |
| 306 | } |
| 307 | |
| 308 | impl Dependencies { |
| 309 | /// Retrieve details about a system dependency. |
| 310 | /// |
| 311 | /// # Arguments |
| 312 | /// |
| 313 | /// * `name`: the name of the `toml` key defining the dependency in `Cargo.toml` |
| 314 | pub fn get_by_name(&self, name: &str) -> Option<&Library> { |
| 315 | self.libs.get(name) |
| 316 | } |
| 317 | |
| 318 | /// A vector listing all system dependencies in sorted (for build reproducibility) order. |
| 319 | /// The first element of the tuple is the name of the `toml` key defining the |
| 320 | /// dependency in `Cargo.toml`. |
| 321 | pub fn iter(&self) -> Vec<(&str, &Library)> { |
| 322 | let mut v = self |
| 323 | .libs |
| 324 | .iter() |
| 325 | .map(|(k, v)| (k.as_str(), v)) |
| 326 | .collect::<Vec<_>>(); |
| 327 | v.sort_by_key(|x| x.0); |
| 328 | v |
| 329 | } |
| 330 | |
| 331 | fn aggregate_str<F: Fn(&Library) -> &Vec<String>>(&self, getter: F) -> Vec<&str> { |
| 332 | let mut v = self |
| 333 | .libs |
| 334 | .values() |
| 335 | .flat_map(getter) |
| 336 | .map(|s| s.as_str()) |
| 337 | .collect::<Vec<_>>(); |
| 338 | v.sort_unstable(); |
| 339 | v.dedup(); |
| 340 | v |
| 341 | } |
| 342 | |
| 343 | fn aggregate_path_buf<F: Fn(&Library) -> &Vec<PathBuf>>(&self, getter: F) -> Vec<&PathBuf> { |
| 344 | let mut v = self.libs.values().flat_map(getter).collect::<Vec<_>>(); |
| 345 | v.sort(); |
| 346 | v.dedup(); |
| 347 | v |
| 348 | } |
| 349 | |
| 350 | /// Returns a vector of [Library::libs] of each library, removing duplicates. |
| 351 | pub fn all_libs(&self) -> Vec<&str> { |
| 352 | let mut v = self |
| 353 | .libs |
| 354 | .values() |
| 355 | .flat_map(|l| l.libs.iter().map(|lib| lib.name.as_str())) |
| 356 | .collect::<Vec<_>>(); |
| 357 | v.sort_unstable(); |
| 358 | v.dedup(); |
| 359 | v |
| 360 | } |
| 361 | |
| 362 | /// Returns a vector of [Library::link_paths] of each library, removing duplicates. |
| 363 | pub fn all_link_paths(&self) -> Vec<&PathBuf> { |
| 364 | self.aggregate_path_buf(|l| &l.link_paths) |
| 365 | } |
| 366 | |
| 367 | /// Returns a vector of [Library::frameworks] of each library, removing duplicates. |
| 368 | pub fn all_frameworks(&self) -> Vec<&str> { |
| 369 | self.aggregate_str(|l| &l.frameworks) |
| 370 | } |
| 371 | |
| 372 | /// Returns a vector of [Library::framework_paths] of each library, removing duplicates. |
| 373 | pub fn all_framework_paths(&self) -> Vec<&PathBuf> { |
| 374 | self.aggregate_path_buf(|l| &l.framework_paths) |
| 375 | } |
| 376 | |
| 377 | /// Returns a vector of [Library::include_paths] of each library, removing duplicates. |
| 378 | pub fn all_include_paths(&self) -> Vec<&PathBuf> { |
| 379 | self.aggregate_path_buf(|l| &l.include_paths) |
| 380 | } |
| 381 | |
| 382 | /// Returns a vector of [Library::ld_args] of each library, removing duplicates. |
| 383 | pub fn all_linker_args(&self) -> Vec<&Vec<String>> { |
| 384 | let mut v = self |
| 385 | .libs |
| 386 | .values() |
| 387 | .flat_map(|l| &l.ld_args) |
| 388 | .collect::<Vec<_>>(); |
| 389 | v.sort_unstable(); |
| 390 | v.dedup(); |
| 391 | v |
| 392 | } |
| 393 | |
| 394 | /// Returns a vector of [Library::defines] of each library, removing duplicates. |
| 395 | pub fn all_defines(&self) -> Vec<(&str, &Option<String>)> { |
| 396 | let mut v = self |
| 397 | .libs |
| 398 | .values() |
| 399 | .flat_map(|l| l.defines.iter()) |
| 400 | .map(|(k, v)| (k.as_str(), v)) |
| 401 | .collect::<Vec<_>>(); |
| 402 | v.sort(); |
| 403 | v.dedup(); |
| 404 | v |
| 405 | } |
| 406 | |
| 407 | fn add(&mut self, name: &str, lib: Library) { |
| 408 | self.libs.insert(name.to_string(), lib); |
| 409 | } |
| 410 | |
| 411 | fn override_from_flags(&mut self, env: &EnvVariables) { |
| 412 | for (name, lib) in self.libs.iter_mut() { |
| 413 | if let Some(value) = env.get(&EnvVariable::new_search_native(name)) { |
| 414 | lib.link_paths = split_paths(&value); |
| 415 | } |
| 416 | if let Some(value) = env.get(&EnvVariable::new_search_framework(name)) { |
| 417 | lib.framework_paths = split_paths(&value); |
| 418 | } |
| 419 | if let Some(value) = env.get(&EnvVariable::new_lib(name)) { |
| 420 | let should_be_linked_statically = env |
| 421 | .has_value(&EnvVariable::new_link(Some(name)), "static" ) |
| 422 | || env.has_value(&EnvVariable::new_link(None), "static" ); |
| 423 | |
| 424 | // If somebody manually mandates static linking, that is a |
| 425 | // clear intent. Let's just assume that a static lib is |
| 426 | // available and let the linking fail if the user is wrong. |
| 427 | let is_static_lib_available = should_be_linked_statically; |
| 428 | |
| 429 | lib.libs = split_string(&value) |
| 430 | .into_iter() |
| 431 | .map(|l| InternalLib::new(l, is_static_lib_available)) |
| 432 | .collect(); |
| 433 | } |
| 434 | if let Some(value) = env.get(&EnvVariable::new_lib_framework(name)) { |
| 435 | lib.frameworks = split_string(&value); |
| 436 | } |
| 437 | if let Some(value) = env.get(&EnvVariable::new_include(name)) { |
| 438 | lib.include_paths = split_paths(&value); |
| 439 | } |
| 440 | if let Some(value) = env.get(&EnvVariable::new_linker_args(name)) { |
| 441 | lib.ld_args = split_string(&value) |
| 442 | .into_iter() |
| 443 | .map(|l| l.split(',' ).map(|l| l.to_string()).collect()) |
| 444 | .collect(); |
| 445 | } |
| 446 | } |
| 447 | } |
| 448 | |
| 449 | fn gen_flags(&self) -> Result<BuildFlags, Error> { |
| 450 | let mut flags = BuildFlags::new(); |
| 451 | let mut include_paths = Vec::new(); |
| 452 | |
| 453 | for (name, lib) in self.iter() { |
| 454 | include_paths.extend(lib.include_paths.clone()); |
| 455 | |
| 456 | if lib.source == Source::EnvVariables |
| 457 | && lib.libs.is_empty() |
| 458 | && lib.frameworks.is_empty() |
| 459 | { |
| 460 | return Err(Error::MissingLib(name.to_string())); |
| 461 | } |
| 462 | |
| 463 | lib.link_paths |
| 464 | .iter() |
| 465 | .for_each(|l| flags.add(BuildFlag::SearchNative(l.to_string_lossy().to_string()))); |
| 466 | lib.framework_paths.iter().for_each(|f| { |
| 467 | flags.add(BuildFlag::SearchFramework(f.to_string_lossy().to_string())) |
| 468 | }); |
| 469 | lib.libs.iter().for_each(|l| { |
| 470 | flags.add(BuildFlag::Lib( |
| 471 | l.name.clone(), |
| 472 | lib.statik && l.is_static_available, |
| 473 | )) |
| 474 | }); |
| 475 | lib.frameworks |
| 476 | .iter() |
| 477 | .for_each(|f| flags.add(BuildFlag::LibFramework(f.clone()))); |
| 478 | lib.ld_args |
| 479 | .iter() |
| 480 | .for_each(|f| flags.add(BuildFlag::LinkArg(f.clone()))) |
| 481 | } |
| 482 | |
| 483 | // Export DEP_$CRATE_INCLUDE env variable with the headers paths, |
| 484 | // see https://kornel.ski/rust-sys-crate#headers |
| 485 | if !include_paths.is_empty() { |
| 486 | if let Ok(paths) = std::env::join_paths(include_paths) { |
| 487 | flags.add(BuildFlag::Include(paths.to_string_lossy().to_string())); |
| 488 | } |
| 489 | } |
| 490 | |
| 491 | // Export cargo:rerun-if-env-changed instructions for all env variables affecting system-deps behaviour |
| 492 | flags.add(BuildFlag::RerunIfEnvChanged( |
| 493 | EnvVariable::new_build_internal(None), |
| 494 | )); |
| 495 | flags.add(BuildFlag::RerunIfEnvChanged(EnvVariable::new_link(None))); |
| 496 | |
| 497 | for (name, _lib) in self.libs.iter() { |
| 498 | EnvVariable::set_rerun_if_changed_for_all_variants(&mut flags, name); |
| 499 | } |
| 500 | |
| 501 | Ok(flags) |
| 502 | } |
| 503 | } |
| 504 | |
| 505 | #[derive (Debug)] |
| 506 | /// Error used in return value of `Config::add_build_internal` closures |
| 507 | pub enum BuildInternalClosureError { |
| 508 | /// `pkg-config` error |
| 509 | PkgConfig(pkg_config::Error), |
| 510 | /// General failure |
| 511 | Failed(String), |
| 512 | } |
| 513 | |
| 514 | impl From<pkg_config::Error> for BuildInternalClosureError { |
| 515 | fn from(err: pkg_config::Error) -> Self { |
| 516 | Self::PkgConfig(err) |
| 517 | } |
| 518 | } |
| 519 | |
| 520 | impl BuildInternalClosureError { |
| 521 | /// Create a new `BuildInternalClosureError::Failed` representing a general |
| 522 | /// failure. |
| 523 | /// |
| 524 | /// # Arguments |
| 525 | /// |
| 526 | /// * `details`: human-readable details about the failure |
| 527 | pub fn failed(details: &str) -> Self { |
| 528 | Self::Failed(details.to_string()) |
| 529 | } |
| 530 | } |
| 531 | |
| 532 | impl std::error::Error for BuildInternalClosureError { |
| 533 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { |
| 534 | match self { |
| 535 | Self::PkgConfig(e: &Error) => Some(e), |
| 536 | _ => None, |
| 537 | } |
| 538 | } |
| 539 | } |
| 540 | |
| 541 | impl fmt::Display for BuildInternalClosureError { |
| 542 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 543 | match self { |
| 544 | Self::PkgConfig(e: &Error) => write!(f, " {}" , e), |
| 545 | Self::Failed(s: &String) => write!(f, " {}" , s), |
| 546 | } |
| 547 | } |
| 548 | } |
| 549 | |
| 550 | // Enum representing the environment variables user can define to tune system-deps. |
| 551 | #[derive (Debug, PartialEq)] |
| 552 | enum EnvVariable { |
| 553 | Lib(String), |
| 554 | LibFramework(String), |
| 555 | SearchNative(String), |
| 556 | SearchFramework(String), |
| 557 | Include(String), |
| 558 | NoPkgConfig(String), |
| 559 | BuildInternal(Option<String>), |
| 560 | Link(Option<String>), |
| 561 | LinkerArgs(String), |
| 562 | } |
| 563 | |
| 564 | impl EnvVariable { |
| 565 | fn new_lib(lib: &str) -> Self { |
| 566 | Self::Lib(lib.to_string()) |
| 567 | } |
| 568 | |
| 569 | fn new_lib_framework(lib: &str) -> Self { |
| 570 | Self::LibFramework(lib.to_string()) |
| 571 | } |
| 572 | |
| 573 | fn new_search_native(lib: &str) -> Self { |
| 574 | Self::SearchNative(lib.to_string()) |
| 575 | } |
| 576 | |
| 577 | fn new_search_framework(lib: &str) -> Self { |
| 578 | Self::SearchFramework(lib.to_string()) |
| 579 | } |
| 580 | |
| 581 | fn new_include(lib: &str) -> Self { |
| 582 | Self::Include(lib.to_string()) |
| 583 | } |
| 584 | |
| 585 | fn new_linker_args(lib: &str) -> Self { |
| 586 | Self::LinkerArgs(lib.to_string()) |
| 587 | } |
| 588 | |
| 589 | fn new_no_pkg_config(lib: &str) -> Self { |
| 590 | Self::NoPkgConfig(lib.to_string()) |
| 591 | } |
| 592 | |
| 593 | fn new_build_internal(lib: Option<&str>) -> Self { |
| 594 | Self::BuildInternal(lib.map(|l| l.to_string())) |
| 595 | } |
| 596 | |
| 597 | fn new_link(lib: Option<&str>) -> Self { |
| 598 | Self::Link(lib.map(|l| l.to_string())) |
| 599 | } |
| 600 | |
| 601 | fn suffix(&self) -> &'static str { |
| 602 | match self { |
| 603 | EnvVariable::Lib(_) => "LIB" , |
| 604 | EnvVariable::LibFramework(_) => "LIB_FRAMEWORK" , |
| 605 | EnvVariable::SearchNative(_) => "SEARCH_NATIVE" , |
| 606 | EnvVariable::SearchFramework(_) => "SEARCH_FRAMEWORK" , |
| 607 | EnvVariable::Include(_) => "INCLUDE" , |
| 608 | EnvVariable::NoPkgConfig(_) => "NO_PKG_CONFIG" , |
| 609 | EnvVariable::BuildInternal(_) => "BUILD_INTERNAL" , |
| 610 | EnvVariable::Link(_) => "LINK" , |
| 611 | EnvVariable::LinkerArgs(_) => "LDFLAGS" , |
| 612 | } |
| 613 | } |
| 614 | |
| 615 | fn set_rerun_if_changed_for_all_variants(flags: &mut BuildFlags, name: &str) { |
| 616 | #[inline ] |
| 617 | fn add_to_flags(flags: &mut BuildFlags, var: EnvVariable) { |
| 618 | flags.add(BuildFlag::RerunIfEnvChanged(var)); |
| 619 | } |
| 620 | add_to_flags(flags, EnvVariable::new_lib(name)); |
| 621 | add_to_flags(flags, EnvVariable::new_lib_framework(name)); |
| 622 | add_to_flags(flags, EnvVariable::new_search_native(name)); |
| 623 | add_to_flags(flags, EnvVariable::new_search_framework(name)); |
| 624 | add_to_flags(flags, EnvVariable::new_include(name)); |
| 625 | add_to_flags(flags, EnvVariable::new_linker_args(name)); |
| 626 | add_to_flags(flags, EnvVariable::new_no_pkg_config(name)); |
| 627 | add_to_flags(flags, EnvVariable::new_build_internal(Some(name))); |
| 628 | add_to_flags(flags, EnvVariable::new_link(Some(name))); |
| 629 | } |
| 630 | } |
| 631 | |
| 632 | impl fmt::Display for EnvVariable { |
| 633 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 634 | let suffix: String = match self { |
| 635 | EnvVariable::Lib(lib: &String) |
| 636 | | EnvVariable::LibFramework(lib: &String) |
| 637 | | EnvVariable::SearchNative(lib: &String) |
| 638 | | EnvVariable::SearchFramework(lib: &String) |
| 639 | | EnvVariable::Include(lib: &String) |
| 640 | | EnvVariable::LinkerArgs(lib: &String) |
| 641 | | EnvVariable::NoPkgConfig(lib: &String) |
| 642 | | EnvVariable::BuildInternal(Some(lib: &String)) |
| 643 | | EnvVariable::Link(Some(lib: &String)) => { |
| 644 | format!(" {}_ {}" , lib.to_shouty_snake_case(), self.suffix()) |
| 645 | } |
| 646 | EnvVariable::BuildInternal(None) | EnvVariable::Link(None) => self.suffix().to_string(), |
| 647 | }; |
| 648 | write!(f, "SYSTEM_DEPS_ {}" , suffix) |
| 649 | } |
| 650 | } |
| 651 | |
| 652 | type FnBuildInternal = |
| 653 | dyn FnOnce(&str, &str) -> std::result::Result<Library, BuildInternalClosureError>; |
| 654 | |
| 655 | /// Structure used to configure `metadata` before starting to probe for dependencies |
| 656 | pub struct Config { |
| 657 | env: EnvVariables, |
| 658 | build_internals: HashMap<String, Box<FnBuildInternal>>, |
| 659 | } |
| 660 | |
| 661 | impl Default for Config { |
| 662 | fn default() -> Self { |
| 663 | Self::new_with_env(EnvVariables::Environment) |
| 664 | } |
| 665 | } |
| 666 | |
| 667 | impl Config { |
| 668 | /// Create a new set of configuration |
| 669 | pub fn new() -> Self { |
| 670 | Self::default() |
| 671 | } |
| 672 | |
| 673 | fn new_with_env(env: EnvVariables) -> Self { |
| 674 | Self { |
| 675 | env, |
| 676 | build_internals: HashMap::new(), |
| 677 | } |
| 678 | } |
| 679 | |
| 680 | /// Probe all libraries configured in the Cargo.toml |
| 681 | /// `[package.metadata.system-deps]` section. |
| 682 | /// |
| 683 | /// The returned hash is using the `toml` key defining the dependency as key. |
| 684 | pub fn probe(self) -> Result<Dependencies, Error> { |
| 685 | let libraries = self.probe_full()?; |
| 686 | let flags = libraries.gen_flags()?; |
| 687 | |
| 688 | // Output cargo flags |
| 689 | println!(" {}" , flags); |
| 690 | |
| 691 | for (name, _) in libraries.iter() { |
| 692 | println!("cargo:rustc-cfg=system_deps_have_ {}" , name.to_snake_case()); |
| 693 | } |
| 694 | |
| 695 | Ok(libraries) |
| 696 | } |
| 697 | |
| 698 | /// Add hook so system-deps can internally build library `name` if requested by user. |
| 699 | /// |
| 700 | /// It will only be triggered if the environment variable |
| 701 | /// `SYSTEM_DEPS_$NAME_BUILD_INTERNAL` is defined with either `always` or |
| 702 | /// `auto` as value. In the latter case, `func` is called only if the requested |
| 703 | /// version of the library was not found on the system. |
| 704 | /// |
| 705 | /// # Arguments |
| 706 | /// * `name`: the name of the library, as defined in `Cargo.toml` |
| 707 | /// * `func`: closure called when internally building the library. |
| 708 | /// |
| 709 | /// It receives as argument the library name, and the minimum version required. |
| 710 | pub fn add_build_internal<F>(self, name: &str, func: F) -> Self |
| 711 | where |
| 712 | F: 'static + FnOnce(&str, &str) -> std::result::Result<Library, BuildInternalClosureError>, |
| 713 | { |
| 714 | let mut build_internals = self.build_internals; |
| 715 | build_internals.insert(name.to_string(), Box::new(func)); |
| 716 | |
| 717 | Self { |
| 718 | env: self.env, |
| 719 | build_internals, |
| 720 | } |
| 721 | } |
| 722 | |
| 723 | fn probe_full(mut self) -> Result<Dependencies, Error> { |
| 724 | let mut libraries = self.probe_pkg_config()?; |
| 725 | libraries.override_from_flags(&self.env); |
| 726 | |
| 727 | Ok(libraries) |
| 728 | } |
| 729 | |
| 730 | fn probe_pkg_config(&mut self) -> Result<Dependencies, Error> { |
| 731 | let dir = self |
| 732 | .env |
| 733 | .get("CARGO_MANIFEST_DIR" ) |
| 734 | .ok_or_else(|| Error::InvalidMetadata("$CARGO_MANIFEST_DIR not set" .into()))?; |
| 735 | let mut path = PathBuf::from(dir); |
| 736 | path.push("Cargo.toml" ); |
| 737 | |
| 738 | println!("cargo:rerun-if-changed= {}" , &path.to_string_lossy()); |
| 739 | |
| 740 | let metadata = MetaData::from_file(&path)?; |
| 741 | |
| 742 | let mut libraries = Dependencies::default(); |
| 743 | |
| 744 | for dep in metadata.deps.iter() { |
| 745 | if let Some(cfg) = &dep.cfg { |
| 746 | // Check if `cfg()` expression matches the target settings |
| 747 | if !self.check_cfg(cfg)? { |
| 748 | continue; |
| 749 | } |
| 750 | } |
| 751 | |
| 752 | let mut enabled_feature_overrides = Vec::new(); |
| 753 | |
| 754 | for o in dep.version_overrides.iter() { |
| 755 | if self.has_feature(&o.key) { |
| 756 | enabled_feature_overrides.push(o); |
| 757 | } |
| 758 | } |
| 759 | |
| 760 | if let Some(feature) = dep.feature.as_ref() { |
| 761 | if !self.has_feature(feature) { |
| 762 | continue; |
| 763 | } |
| 764 | } |
| 765 | |
| 766 | // Pick the highest feature enabled version |
| 767 | let version; |
| 768 | let lib_name; |
| 769 | let fallback_lib_names; |
| 770 | let optional; |
| 771 | if enabled_feature_overrides.is_empty() { |
| 772 | version = dep.version.as_deref(); |
| 773 | lib_name = dep.lib_name(); |
| 774 | fallback_lib_names = dep.fallback_names.as_deref().unwrap_or(&[]); |
| 775 | optional = dep.optional; |
| 776 | } else { |
| 777 | enabled_feature_overrides.sort_by(|a, b| { |
| 778 | fn min_version(r: metadata::VersionRange) -> &str { |
| 779 | match r.start_bound() { |
| 780 | std::ops::Bound::Unbounded => unreachable!(), |
| 781 | std::ops::Bound::Excluded(_) => unreachable!(), |
| 782 | std::ops::Bound::Included(b) => b, |
| 783 | } |
| 784 | } |
| 785 | |
| 786 | let a = min_version(metadata::parse_version(&a.version)); |
| 787 | let b = min_version(metadata::parse_version(&b.version)); |
| 788 | |
| 789 | version_compare::compare(a, b) |
| 790 | .expect("failed to compare versions" ) |
| 791 | .ord() |
| 792 | .expect("invalid version" ) |
| 793 | }); |
| 794 | let highest = enabled_feature_overrides.into_iter().last().unwrap(); |
| 795 | |
| 796 | version = Some(highest.version.as_str()); |
| 797 | lib_name = highest.name.as_deref().unwrap_or(dep.lib_name()); |
| 798 | fallback_lib_names = highest |
| 799 | .fallback_names |
| 800 | .as_deref() |
| 801 | .or(dep.fallback_names.as_deref()) |
| 802 | .unwrap_or(&[]); |
| 803 | optional = highest.optional.unwrap_or(dep.optional); |
| 804 | }; |
| 805 | |
| 806 | let version = version.ok_or_else(|| { |
| 807 | Error::InvalidMetadata(format!("No version defined for {}" , dep.key)) |
| 808 | })?; |
| 809 | |
| 810 | let name = &dep.key; |
| 811 | let build_internal = self.get_build_internal_status(name)?; |
| 812 | |
| 813 | // should the lib be statically linked? |
| 814 | let statik = self |
| 815 | .env |
| 816 | .has_value(&EnvVariable::new_link(Some(name)), "static" ) |
| 817 | || self.env.has_value(&EnvVariable::new_link(None), "static" ); |
| 818 | |
| 819 | let mut library = if self.env.contains(&EnvVariable::new_no_pkg_config(name)) { |
| 820 | Library::from_env_variables(name) |
| 821 | } else if build_internal == BuildInternal::Always { |
| 822 | self.call_build_internal(lib_name, version)? |
| 823 | } else { |
| 824 | let mut config = pkg_config::Config::new(); |
| 825 | config |
| 826 | .print_system_libs(false) |
| 827 | .cargo_metadata(false) |
| 828 | .range_version(metadata::parse_version(version)) |
| 829 | .statik(statik); |
| 830 | |
| 831 | match Self::probe_with_fallback(config, lib_name, fallback_lib_names) { |
| 832 | Ok((lib_name, lib)) => Library::from_pkg_config(lib_name, lib), |
| 833 | Err(e) => { |
| 834 | if build_internal == BuildInternal::Auto { |
| 835 | // Try building the lib internally as a fallback |
| 836 | self.call_build_internal(name, version)? |
| 837 | } else if optional { |
| 838 | // If the dep is optional just skip it |
| 839 | continue; |
| 840 | } else { |
| 841 | return Err(e.into()); |
| 842 | } |
| 843 | } |
| 844 | } |
| 845 | }; |
| 846 | |
| 847 | library.statik = statik; |
| 848 | |
| 849 | libraries.add(name, library); |
| 850 | } |
| 851 | Ok(libraries) |
| 852 | } |
| 853 | |
| 854 | fn probe_with_fallback<'a>( |
| 855 | config: pkg_config::Config, |
| 856 | name: &'a str, |
| 857 | fallback_names: &'a [String], |
| 858 | ) -> Result<(&'a str, pkg_config::Library), pkg_config::Error> { |
| 859 | let error = match config.probe(name) { |
| 860 | Ok(x) => return Ok((name, x)), |
| 861 | Err(e) => e, |
| 862 | }; |
| 863 | for name in fallback_names { |
| 864 | if let Ok(library) = config.probe(name) { |
| 865 | return Ok((name, library)); |
| 866 | } |
| 867 | } |
| 868 | Err(error) |
| 869 | } |
| 870 | |
| 871 | fn get_build_internal_env_var(&self, var: EnvVariable) -> Result<Option<BuildInternal>, Error> { |
| 872 | match self.env.get(&var).as_deref() { |
| 873 | Some(s) => { |
| 874 | let b = BuildInternal::from_str(s).map_err(|_| { |
| 875 | Error::BuildInternalInvalid(format!( |
| 876 | "Invalid value in {}: {} (allowed: 'auto', 'always', 'never')" , |
| 877 | var, s |
| 878 | )) |
| 879 | })?; |
| 880 | Ok(Some(b)) |
| 881 | } |
| 882 | None => Ok(None), |
| 883 | } |
| 884 | } |
| 885 | |
| 886 | fn get_build_internal_status(&self, name: &str) -> Result<BuildInternal, Error> { |
| 887 | match self.get_build_internal_env_var(EnvVariable::new_build_internal(Some(name)))? { |
| 888 | Some(b) => Ok(b), |
| 889 | None => Ok(self |
| 890 | .get_build_internal_env_var(EnvVariable::new_build_internal(None))? |
| 891 | .unwrap_or_default()), |
| 892 | } |
| 893 | } |
| 894 | |
| 895 | fn call_build_internal(&mut self, name: &str, version_str: &str) -> Result<Library, Error> { |
| 896 | let lib = match self.build_internals.remove(name) { |
| 897 | Some(f) => f(name, version_str) |
| 898 | .map_err(|e| Error::BuildInternalClosureError(name.into(), e))?, |
| 899 | None => { |
| 900 | return Err(Error::BuildInternalNoClosure( |
| 901 | name.into(), |
| 902 | version_str.into(), |
| 903 | )) |
| 904 | } |
| 905 | }; |
| 906 | |
| 907 | // Check that the lib built internally matches the required version |
| 908 | let version = metadata::parse_version(version_str); |
| 909 | fn min_version(r: metadata::VersionRange) -> &str { |
| 910 | match r.start_bound() { |
| 911 | std::ops::Bound::Unbounded => unreachable!(), |
| 912 | std::ops::Bound::Excluded(_) => unreachable!(), |
| 913 | std::ops::Bound::Included(b) => b, |
| 914 | } |
| 915 | } |
| 916 | fn max_version(r: metadata::VersionRange) -> Option<&str> { |
| 917 | match r.end_bound() { |
| 918 | std::ops::Bound::Included(_) => unreachable!(), |
| 919 | std::ops::Bound::Unbounded => None, |
| 920 | std::ops::Bound::Excluded(b) => Some(*b), |
| 921 | } |
| 922 | } |
| 923 | |
| 924 | let min = min_version(version.clone()); |
| 925 | if version_compare::compare(&lib.version, min) == Ok(version_compare::Cmp::Lt) { |
| 926 | return Err(Error::BuildInternalWrongVersion( |
| 927 | name.into(), |
| 928 | lib.version, |
| 929 | version_str.into(), |
| 930 | )); |
| 931 | } |
| 932 | |
| 933 | if let Some(max) = max_version(version) { |
| 934 | if version_compare::compare(&lib.version, max) == Ok(version_compare::Cmp::Ge) { |
| 935 | return Err(Error::BuildInternalWrongVersion( |
| 936 | name.into(), |
| 937 | lib.version, |
| 938 | version_str.into(), |
| 939 | )); |
| 940 | } |
| 941 | } |
| 942 | |
| 943 | Ok(lib) |
| 944 | } |
| 945 | |
| 946 | fn has_feature(&self, feature: &str) -> bool { |
| 947 | let var: &str = &format!("CARGO_FEATURE_ {}" , feature.to_uppercase().replace('-' , "_" )); |
| 948 | self.env.contains(var) |
| 949 | } |
| 950 | |
| 951 | fn check_cfg(&self, cfg: &cfg_expr::Expression) -> Result<bool, Error> { |
| 952 | use cfg_expr::{targets::get_builtin_target_by_triple, Predicate}; |
| 953 | |
| 954 | let target = self |
| 955 | .env |
| 956 | .get("TARGET" ) |
| 957 | .expect("no TARGET env variable defined" ); |
| 958 | |
| 959 | let res = if let Some(target) = get_builtin_target_by_triple(&target) { |
| 960 | cfg.eval(|pred| match pred { |
| 961 | Predicate::Target(tp) => Some(tp.matches(target)), |
| 962 | _ => None, |
| 963 | }) |
| 964 | } else { |
| 965 | // Attempt to parse the triple, the target is not an official builtin |
| 966 | 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)); |
| 967 | |
| 968 | cfg.eval(|pred| match pred { |
| 969 | Predicate::Target(tp) => Some(tp.matches(&triple)), |
| 970 | _ => None, |
| 971 | }) |
| 972 | }; |
| 973 | |
| 974 | res.ok_or_else(|| Error::UnsupportedCfg(cfg.original().to_string())) |
| 975 | } |
| 976 | } |
| 977 | |
| 978 | #[derive (Debug, PartialEq, Eq)] |
| 979 | /// From where the library settings have been retrieved |
| 980 | pub enum Source { |
| 981 | /// Settings have been retrieved from `pkg-config` |
| 982 | PkgConfig, |
| 983 | /// Settings have been defined using user defined environment variables |
| 984 | EnvVariables, |
| 985 | } |
| 986 | |
| 987 | #[derive (Debug, PartialEq, Eq)] |
| 988 | /// Internal library name and if a static library is available on the system |
| 989 | pub struct InternalLib { |
| 990 | /// Name of the library |
| 991 | pub name: String, |
| 992 | /// Indicates if a static library is available on the system |
| 993 | pub is_static_available: bool, |
| 994 | } |
| 995 | |
| 996 | impl InternalLib { |
| 997 | fn new(name: String, is_static_available: bool) -> Self { |
| 998 | InternalLib { |
| 999 | name, |
| 1000 | is_static_available, |
| 1001 | } |
| 1002 | } |
| 1003 | } |
| 1004 | |
| 1005 | #[derive (Debug)] |
| 1006 | /// A system dependency |
| 1007 | pub struct Library { |
| 1008 | /// Name of the library |
| 1009 | pub name: String, |
| 1010 | /// From where the library settings have been retrieved |
| 1011 | pub source: Source, |
| 1012 | /// libraries the linker should link on |
| 1013 | pub libs: Vec<InternalLib>, |
| 1014 | /// directories where the compiler should look for libraries |
| 1015 | pub link_paths: Vec<PathBuf>, |
| 1016 | /// frameworks the linker should link on |
| 1017 | pub frameworks: Vec<String>, |
| 1018 | /// directories where the compiler should look for frameworks |
| 1019 | pub framework_paths: Vec<PathBuf>, |
| 1020 | /// directories where the compiler should look for header files |
| 1021 | pub include_paths: Vec<PathBuf>, |
| 1022 | /// flags that should be passed to the linker |
| 1023 | pub ld_args: Vec<Vec<String>>, |
| 1024 | /// macros that should be defined by the compiler |
| 1025 | pub defines: HashMap<String, Option<String>>, |
| 1026 | /// library version |
| 1027 | pub version: String, |
| 1028 | /// library is statically linked |
| 1029 | pub statik: bool, |
| 1030 | } |
| 1031 | |
| 1032 | impl Library { |
| 1033 | fn from_pkg_config(name: &str, l: pkg_config::Library) -> Self { |
| 1034 | // taken from: https://github.com/rust-lang/pkg-config-rs/blob/54325785816695df031cef3b26b6a9a203bbc01b/src/lib.rs#L502 |
| 1035 | let system_roots = if cfg!(target_os = "macos" ) { |
| 1036 | vec![PathBuf::from("/Library" ), PathBuf::from("/System" )] |
| 1037 | } else { |
| 1038 | let sysroot = env::var_os("PKG_CONFIG_SYSROOT_DIR" ) |
| 1039 | .or_else(|| env::var_os("SYSROOT" )) |
| 1040 | .map(PathBuf::from); |
| 1041 | |
| 1042 | if cfg!(target_os = "windows" ) { |
| 1043 | if let Some(sysroot) = sysroot { |
| 1044 | vec![sysroot] |
| 1045 | } else { |
| 1046 | vec![] |
| 1047 | } |
| 1048 | } else { |
| 1049 | vec![sysroot.unwrap_or_else(|| PathBuf::from("/usr" ))] |
| 1050 | } |
| 1051 | }; |
| 1052 | |
| 1053 | let is_static_available = |name: &String| -> bool { |
| 1054 | let libnames = { |
| 1055 | let mut names = vec![format!("lib {}.a" , name)]; |
| 1056 | |
| 1057 | if cfg!(target_os = "windows" ) { |
| 1058 | names.push(format!(" {}.lib" , name)); |
| 1059 | } |
| 1060 | |
| 1061 | names |
| 1062 | }; |
| 1063 | |
| 1064 | l.link_paths.iter().any(|dir| { |
| 1065 | let library_exists = libnames.iter().any(|libname| dir.join(libname).exists()); |
| 1066 | library_exists && !system_roots.iter().any(|sys| dir.starts_with(sys)) |
| 1067 | }) |
| 1068 | }; |
| 1069 | |
| 1070 | Self { |
| 1071 | name: name.to_string(), |
| 1072 | source: Source::PkgConfig, |
| 1073 | libs: l |
| 1074 | .libs |
| 1075 | .iter() |
| 1076 | .map(|lib| InternalLib::new(lib.to_owned(), is_static_available(lib))) |
| 1077 | .collect(), |
| 1078 | link_paths: l.link_paths, |
| 1079 | include_paths: l.include_paths, |
| 1080 | ld_args: l.ld_args, |
| 1081 | frameworks: l.frameworks, |
| 1082 | framework_paths: l.framework_paths, |
| 1083 | defines: l.defines, |
| 1084 | version: l.version, |
| 1085 | statik: false, |
| 1086 | } |
| 1087 | } |
| 1088 | |
| 1089 | fn from_env_variables(name: &str) -> Self { |
| 1090 | Self { |
| 1091 | name: name.to_string(), |
| 1092 | source: Source::EnvVariables, |
| 1093 | libs: Vec::new(), |
| 1094 | link_paths: Vec::new(), |
| 1095 | include_paths: Vec::new(), |
| 1096 | ld_args: Vec::new(), |
| 1097 | frameworks: Vec::new(), |
| 1098 | framework_paths: Vec::new(), |
| 1099 | defines: HashMap::new(), |
| 1100 | version: String::new(), |
| 1101 | statik: false, |
| 1102 | } |
| 1103 | } |
| 1104 | |
| 1105 | /// Create a `Library` by probing `pkg-config` on an internal directory. |
| 1106 | /// This helper is meant to be used by `Config::add_build_internal` closures |
| 1107 | /// after having built the lib to return the library information to system-deps. |
| 1108 | /// |
| 1109 | /// This library will be statically linked. |
| 1110 | /// |
| 1111 | /// # Arguments |
| 1112 | /// |
| 1113 | /// * `pkg_config_dir`: the directory where the library `.pc` file is located |
| 1114 | /// * `lib`: the name of the library to look for |
| 1115 | /// * `version`: the minimum version of `lib` required |
| 1116 | /// |
| 1117 | /// # Examples |
| 1118 | /// |
| 1119 | /// ``` |
| 1120 | /// let mut config = system_deps::Config::new(); |
| 1121 | /// config.add_build_internal("mylib" , |lib, version| { |
| 1122 | /// // Actually build the library here that fulfills the passed in version requirements |
| 1123 | /// system_deps::Library::from_internal_pkg_config("build-dir" , |
| 1124 | /// lib, "1.2.4" ) |
| 1125 | /// }); |
| 1126 | /// ``` |
| 1127 | pub fn from_internal_pkg_config<P>( |
| 1128 | pkg_config_dir: P, |
| 1129 | lib: &str, |
| 1130 | version: &str, |
| 1131 | ) -> Result<Self, BuildInternalClosureError> |
| 1132 | where |
| 1133 | P: AsRef<Path>, |
| 1134 | { |
| 1135 | // save current PKG_CONFIG_PATH, so we can restore it |
| 1136 | let old = env::var("PKG_CONFIG_PATH" ); |
| 1137 | |
| 1138 | match old { |
| 1139 | Ok(ref s) => { |
| 1140 | let mut paths = env::split_paths(s).collect::<Vec<_>>(); |
| 1141 | paths.push(PathBuf::from(pkg_config_dir.as_ref())); |
| 1142 | let paths = env::join_paths(paths).unwrap(); |
| 1143 | env::set_var("PKG_CONFIG_PATH" , paths) |
| 1144 | } |
| 1145 | Err(_) => env::set_var("PKG_CONFIG_PATH" , pkg_config_dir.as_ref()), |
| 1146 | } |
| 1147 | |
| 1148 | let pkg_lib = pkg_config::Config::new() |
| 1149 | .atleast_version(version) |
| 1150 | .print_system_libs(false) |
| 1151 | .cargo_metadata(false) |
| 1152 | .statik(true) |
| 1153 | .probe(lib); |
| 1154 | |
| 1155 | env::set_var("PKG_CONFIG_PATH" , old.unwrap_or_else(|_| "" .into())); |
| 1156 | |
| 1157 | match pkg_lib { |
| 1158 | Ok(pkg_lib) => { |
| 1159 | let mut lib = Self::from_pkg_config(lib, pkg_lib); |
| 1160 | lib.statik = true; |
| 1161 | Ok(lib) |
| 1162 | } |
| 1163 | Err(e) => Err(e.into()), |
| 1164 | } |
| 1165 | } |
| 1166 | } |
| 1167 | |
| 1168 | #[derive (Debug)] |
| 1169 | enum EnvVariables { |
| 1170 | Environment, |
| 1171 | #[cfg (test)] |
| 1172 | Mock(HashMap<&'static str, String>), |
| 1173 | } |
| 1174 | |
| 1175 | trait EnvVariablesExt<T> { |
| 1176 | fn contains(&self, var: T) -> bool { |
| 1177 | self.get(var).is_some() |
| 1178 | } |
| 1179 | |
| 1180 | fn get(&self, var: T) -> Option<String>; |
| 1181 | |
| 1182 | fn has_value(&self, var: T, val: &str) -> bool { |
| 1183 | match self.get(var) { |
| 1184 | Some(v: String) => v == val, |
| 1185 | None => false, |
| 1186 | } |
| 1187 | } |
| 1188 | } |
| 1189 | |
| 1190 | impl EnvVariablesExt<&str> for EnvVariables { |
| 1191 | fn get(&self, var: &str) -> Option<String> { |
| 1192 | match self { |
| 1193 | EnvVariables::Environment => env::var(key:var).ok(), |
| 1194 | #[cfg (test)] |
| 1195 | EnvVariables::Mock(vars) => vars.get(var).cloned(), |
| 1196 | } |
| 1197 | } |
| 1198 | } |
| 1199 | |
| 1200 | impl EnvVariablesExt<&EnvVariable> for EnvVariables { |
| 1201 | fn get(&self, var: &EnvVariable) -> Option<String> { |
| 1202 | let s: String = var.to_string(); |
| 1203 | let var: &str = s.as_ref(); |
| 1204 | self.get(var) |
| 1205 | } |
| 1206 | } |
| 1207 | |
| 1208 | #[derive (Debug, PartialEq)] |
| 1209 | enum BuildFlag { |
| 1210 | Include(String), |
| 1211 | SearchNative(String), |
| 1212 | SearchFramework(String), |
| 1213 | Lib(String, bool), // true if static |
| 1214 | LibFramework(String), |
| 1215 | RerunIfEnvChanged(EnvVariable), |
| 1216 | LinkArg(Vec<String>), |
| 1217 | } |
| 1218 | |
| 1219 | impl fmt::Display for BuildFlag { |
| 1220 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 1221 | match self { |
| 1222 | BuildFlag::Include(paths: &String) => write!(f, "include= {}" , paths), |
| 1223 | BuildFlag::SearchNative(lib: &String) => write!(f, "rustc-link-search=native= {}" , lib), |
| 1224 | BuildFlag::SearchFramework(lib: &String) => write!(f, "rustc-link-search=framework= {}" , lib), |
| 1225 | BuildFlag::Lib(lib: &String, statik: &bool) => { |
| 1226 | if *statik { |
| 1227 | write!(f, "rustc-link-lib=static= {}" , lib) |
| 1228 | } else { |
| 1229 | write!(f, "rustc-link-lib= {}" , lib) |
| 1230 | } |
| 1231 | } |
| 1232 | BuildFlag::LibFramework(lib: &String) => write!(f, "rustc-link-lib=framework= {}" , lib), |
| 1233 | BuildFlag::RerunIfEnvChanged(env: &EnvVariable) => write!(f, "rerun-if-env-changed= {}" , env), |
| 1234 | BuildFlag::LinkArg(ld_option: &Vec) => { |
| 1235 | write!(f, "rustc-link-arg=-Wl, {}" , ld_option.join("," )) |
| 1236 | } |
| 1237 | } |
| 1238 | } |
| 1239 | } |
| 1240 | |
| 1241 | #[derive (Debug, PartialEq)] |
| 1242 | struct BuildFlags(Vec<BuildFlag>); |
| 1243 | |
| 1244 | impl BuildFlags { |
| 1245 | fn new() -> Self { |
| 1246 | Self(Vec::new()) |
| 1247 | } |
| 1248 | |
| 1249 | fn add(&mut self, flag: BuildFlag) { |
| 1250 | self.0.push(flag); |
| 1251 | } |
| 1252 | } |
| 1253 | |
| 1254 | impl fmt::Display for BuildFlags { |
| 1255 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 1256 | for flag: &BuildFlag in self.0.iter() { |
| 1257 | writeln!(f, "cargo: {}" , flag)?; |
| 1258 | } |
| 1259 | Ok(()) |
| 1260 | } |
| 1261 | } |
| 1262 | |
| 1263 | fn split_paths(value: &str) -> Vec<PathBuf> { |
| 1264 | if !value.is_empty() { |
| 1265 | let paths: SplitPaths<'_> = env::split_paths(&value); |
| 1266 | paths.map(|p: PathBuf| Path::new(&p).into()).collect() |
| 1267 | } else { |
| 1268 | Vec::new() |
| 1269 | } |
| 1270 | } |
| 1271 | |
| 1272 | fn split_string(value: &str) -> Vec<String> { |
| 1273 | if !value.is_empty() { |
| 1274 | value.split(' ' ).map(|s: &str| s.to_string()).collect() |
| 1275 | } else { |
| 1276 | Vec::new() |
| 1277 | } |
| 1278 | } |
| 1279 | |
| 1280 | #[derive (Debug, PartialEq)] |
| 1281 | enum BuildInternal { |
| 1282 | Auto, |
| 1283 | Always, |
| 1284 | Never, |
| 1285 | } |
| 1286 | |
| 1287 | impl Default for BuildInternal { |
| 1288 | fn default() -> Self { |
| 1289 | Self::Never |
| 1290 | } |
| 1291 | } |
| 1292 | |
| 1293 | impl FromStr for BuildInternal { |
| 1294 | type Err = ParseError; |
| 1295 | |
| 1296 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
| 1297 | match s { |
| 1298 | "auto" => Ok(Self::Auto), |
| 1299 | "always" => Ok(Self::Always), |
| 1300 | "never" => Ok(Self::Never), |
| 1301 | v: &str => Err(ParseError::VariantNotFound(v.to_owned())), |
| 1302 | } |
| 1303 | } |
| 1304 | } |
| 1305 | |
| 1306 | #[derive (Debug, PartialEq)] |
| 1307 | enum ParseError { |
| 1308 | VariantNotFound(String), |
| 1309 | } |
| 1310 | |
| 1311 | impl std::error::Error for ParseError {} |
| 1312 | |
| 1313 | impl fmt::Display for ParseError { |
| 1314 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 1315 | match self { |
| 1316 | Self::VariantNotFound(v: &String) => write!(f, "Unknown variant: ` {}`" , v), |
| 1317 | } |
| 1318 | } |
| 1319 | } |
| 1320 | |