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