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 | |