1 //! A build dependency for Cargo libraries to find libraries in a
2 //! [Vcpkg](https://github.com/microsoft/vcpkg) tree
3 //!
4 //! From a Vcpkg package name
5 //! this build helper will emit cargo metadata to link it and it's dependencies
6 //! (excluding system libraries, which it does not determine).
7 //!
8 //! The simplest possible usage looks like this :-
9 //!
10 //! ```rust,no_run
11 //! // build.rs
12 //! vcpkg::find_package("libssh2").unwrap();
13 //! ```
14 //!
15 //! The cargo metadata that is emitted can be changed like this :-
16 //!
17 //! ```rust,no_run
18 //! // build.rs
19 //! vcpkg::Config::new()
20 //!     .emit_includes(true)
21 //!     .find_package("zlib").unwrap();
22 //! ```
23 //!
24 //! If the search was successful all appropriate Cargo metadata will be printed
25 //! to stdout.
26 //!
27 //! # Static vs. dynamic linking
28 //! ## Linux and Mac
29 //! At this time, vcpkg has a single triplet on macOS and Linux, which builds
30 //! static link versions of libraries. This triplet works well with Rust. It is also possible
31 //! to select a custom triplet using the `VCPKGRS_TRIPLET` environment variable.
32 //! ## Windows
33 //! On Windows there are three
34 //! configurations that are supported for 64-bit builds and another three for 32-bit.
35 //! The default 64-bit configuration is `x64-windows-static-md` which is a
36 //! [community supported](https://github.com/microsoft/vcpkg/blob/master/docs/users/triplets.md#community-triplets)
37 //! configuration that is a good match for Rust - dynamically linking to the C runtime,
38 //! and statically linking to the packages in vcpkg.
39 //!
40 //! Another option is to build a fully static
41 //! binary using `RUSTFLAGS=-Ctarget-feature=+crt-static`. This will link to libraries built
42 //! with vcpkg triplet `x64-windows-static`.
43 //!
44 //! For dynamic linking, set `VCPKGRS_DYNAMIC=1` in the
45 //! environment. This will link to libraries built with vcpkg triplet `x64-windows`. If `VCPKGRS_DYNAMIC` is set, `cargo install` will
46 //! generate dynamically linked binaries, in which case you will have to arrange for
47 //! dlls from your Vcpkg installation to be available in your path.
48 //!
49 //! # Environment variables
50 //!
51 //! A number of environment variables are available to globally configure which
52 //! libraries are selected.
53 //!
54 //! * `VCPKG_ROOT` - Set the directory to look in for a vcpkg installation. If
55 //! it is not set, vcpkg will use the user-wide installation if one has been
56 //! set up with `vcpkg integrate install`, and check the crate source and target
57 //! to see if a vcpkg tree has been created by [cargo-vcpkg](https://crates.io/crates/cargo-vcpkg).
58 //!
59 //! * `VCPKGRS_TRIPLET` - Use this to override vcpkg-rs' default triplet selection with your own.
60 //! This is how to select a custom vcpkg triplet.
61 //!
62 //! * `VCPKGRS_NO_FOO` - if set, vcpkg-rs will not attempt to find the
63 //! library named `foo`.
64 //!
65 //! * `VCPKGRS_DISABLE` - if set, vcpkg-rs will not attempt to find any libraries.
66 //!
67 //! * `VCPKGRS_DYNAMIC` - if set, vcpkg-rs will link to DLL builds of ports.
68 //! # Related tools
69 //! ## cargo vcpkg
70 //! [`cargo vcpkg`](https://crates.io/crates/cargo-vcpkg) can fetch and build a vcpkg installation of
71 //! required packages from scratch. It merges package requirements specified in the `Cargo.toml` of
72 //! crates in the dependency tree.
73 //! ## vcpkg_cli
74 //! There is also a rudimentary companion crate, `vcpkg_cli` that allows testing of environment
75 //! and flag combinations.
76 //!
77 //! ```Batchfile
78 //! C:\src> vcpkg_cli probe -l static mysqlclient
79 //! Found library mysqlclient
80 //! Include paths:
81 //!         C:\src\[..]\vcpkg\installed\x64-windows-static\include
82 //! Library paths:
83 //!         C:\src\[..]\vcpkg\installed\x64-windows-static\lib
84 //! Cargo metadata:
85 //!         cargo:rustc-link-search=native=C:\src\[..]\vcpkg\installed\x64-windows-static\lib
86 //!         cargo:rustc-link-lib=static=mysqlclient
87 //! ```
88 
89 // The CI will test vcpkg-rs on 1.12 because that is how far back vcpkg-rs 0.2 tries to be
90 // compatible (was actually 1.10 see #29).  This was originally based on how far back
91 // rust-openssl's openssl-sys was backward compatible when this crate originally released.
92 //
93 // This will likely get bumped by the next major release.
94 #![allow(deprecated)]
95 #![allow(warnings)]
96 
97 #[cfg(test)]
98 #[macro_use]
99 extern crate lazy_static;
100 
101 #[allow(unused_imports)]
102 use std::ascii::AsciiExt;
103 
104 use std::collections::BTreeMap;
105 use std::collections::HashMap;
106 use std::env;
107 use std::error;
108 use std::ffi::OsStr;
109 use std::fmt;
110 use std::fs::{self, File};
111 use std::io::{BufRead, BufReader, Read};
112 use std::path::{Path, PathBuf};
113 
114 /// Configuration options for finding packages, setting up the tree and emitting metadata to cargo
115 #[derive(Default)]
116 pub struct Config {
117     /// should the cargo metadata actually be emitted
118     cargo_metadata: bool,
119 
120     /// should cargo:include= metadata be emitted (defaults to false)
121     emit_includes: bool,
122 
123     /// .lib/.a files that must be be found for probing to be considered successful
124     required_libs: Vec<String>,
125 
126     /// .dlls that must be be found for probing to be considered successful
127     required_dlls: Vec<String>,
128 
129     /// should DLLs be copied to OUT_DIR?
130     copy_dlls: bool,
131 
132     /// override VCPKG_ROOT environment variable
133     vcpkg_root: Option<PathBuf>,
134 
135     target: Option<TargetTriplet>,
136 }
137 
138 /// Details of a package that was found
139 #[derive(Debug)]
140 pub struct Library {
141     /// Paths for the linker to search for static or import libraries
142     pub link_paths: Vec<PathBuf>,
143 
144     /// Paths to search at runtme to find DLLs
145     pub dll_paths: Vec<PathBuf>,
146 
147     /// Paths to include files
148     pub include_paths: Vec<PathBuf>,
149 
150     /// cargo: metadata lines
151     pub cargo_metadata: Vec<String>,
152 
153     /// libraries found are static
154     pub is_static: bool,
155 
156     /// DLLs found
157     pub found_dlls: Vec<PathBuf>,
158 
159     /// static libs or import libs found
160     pub found_libs: Vec<PathBuf>,
161 
162     /// link name of libraries found, this is useful to emit linker commands
163     pub found_names: Vec<String>,
164 
165     /// ports that are providing the libraries to link to, in port link order
166     pub ports: Vec<String>,
167 
168     /// the vcpkg triplet that has been selected
169     pub vcpkg_triplet: String,
170 }
171 
172 #[derive(Clone)]
173 struct TargetTriplet {
174     triplet: String,
175     is_static: bool,
176     lib_suffix: String,
177     strip_lib_prefix: bool,
178 }
179 
180 impl<S: AsRef<str>> From<S> for TargetTriplet {
from(triplet: S) -> TargetTriplet181     fn from(triplet: S) -> TargetTriplet {
182         let triplet = triplet.as_ref();
183         if triplet.contains("windows") {
184             TargetTriplet {
185                 triplet: triplet.into(),
186                 is_static: triplet.contains("-static"),
187                 lib_suffix: "lib".into(),
188                 strip_lib_prefix: false,
189             }
190         } else {
191             TargetTriplet {
192                 triplet: triplet.into(),
193                 is_static: true,
194                 lib_suffix: "a".into(),
195                 strip_lib_prefix: true,
196             }
197         }
198     }
199 }
200 
201 #[derive(Debug)] // need Display?
202 pub enum Error {
203     /// Aborted because of a `VCPKGRS_NO_*` environment variable.
204     ///
205     /// Contains the name of the responsible environment variable.
206     DisabledByEnv(String),
207 
208     /// Aborted because a required environment variable was not set.
209     RequiredEnvMissing(String),
210 
211     /// On Windows, only MSVC ABI is supported
212     NotMSVC,
213 
214     /// Can't find a vcpkg tree
215     VcpkgNotFound(String),
216 
217     /// Library not found in vcpkg tree
218     LibNotFound(String),
219 
220     /// Could not understand vcpkg installation
221     VcpkgInstallation(String),
222 
223     #[doc(hidden)]
224     __Nonexhaustive,
225 }
226 
227 impl error::Error for Error {
description(&self) -> &str228     fn description(&self) -> &str {
229         match *self {
230             Error::DisabledByEnv(_) => "vcpkg-rs requested to be aborted",
231             Error::RequiredEnvMissing(_) => "a required env setting is missing",
232             Error::NotMSVC => "vcpkg-rs only can only find libraries for MSVC ABI builds",
233             Error::VcpkgNotFound(_) => "could not find Vcpkg tree",
234             Error::LibNotFound(_) => "could not find library in Vcpkg tree",
235             Error::VcpkgInstallation(_) => "could not look up details of packages in vcpkg tree",
236             Error::__Nonexhaustive => panic!(),
237         }
238     }
239 
cause(&self) -> Option<&error::Error>240     fn cause(&self) -> Option<&error::Error> {
241         match *self {
242             // Error::Command { ref cause, .. } => Some(cause),
243             _ => None,
244         }
245     }
246 }
247 
248 impl fmt::Display for Error {
fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>249     fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
250         match *self {
251             Error::DisabledByEnv(ref name) => write!(f, "Aborted because {} is set", name),
252             Error::RequiredEnvMissing(ref name) => write!(f, "Aborted because {} is not set", name),
253             Error::NotMSVC => write!(
254                 f,
255                 "the vcpkg-rs Vcpkg build helper can only find libraries built for the MSVC ABI."
256             ),
257             Error::VcpkgNotFound(ref detail) => write!(f, "Could not find Vcpkg tree: {}", detail),
258             Error::LibNotFound(ref detail) => {
259                 write!(f, "Could not find library in Vcpkg tree {}", detail)
260             }
261             Error::VcpkgInstallation(ref detail) => write!(
262                 f,
263                 "Could not look up details of packages in vcpkg tree {}",
264                 detail
265             ),
266             Error::__Nonexhaustive => panic!(),
267         }
268     }
269 }
270 
271 /// Deprecated in favor of the find_package function
272 #[doc(hidden)]
probe_package(name: &str) -> Result<Library, Error>273 pub fn probe_package(name: &str) -> Result<Library, Error> {
274     Config::new().probe(name)
275 }
276 
277 /// Find the package `package` in a Vcpkg tree.
278 ///
279 /// Emits cargo metadata to link to libraries provided by the Vcpkg package/port
280 /// named, and any (non-system) libraries that they depend on.
281 ///
282 /// This will select the architecture and linkage based on environment
283 /// variables and build flags as described in the module docs.
find_package(package: &str) -> Result<Library, Error>284 pub fn find_package(package: &str) -> Result<Library, Error> {
285     Config::new().find_package(package)
286 }
287 
288 /// Find the vcpkg root
289 #[doc(hidden)]
find_vcpkg_root(cfg: &Config) -> Result<PathBuf, Error>290 pub fn find_vcpkg_root(cfg: &Config) -> Result<PathBuf, Error> {
291     // prefer the setting from the use if there is one
292     if let &Some(ref path) = &cfg.vcpkg_root {
293         return Ok(path.clone());
294     }
295 
296     // otherwise, use the setting from the environment
297     if let Some(path) = env::var_os("VCPKG_ROOT") {
298         return Ok(PathBuf::from(path));
299     }
300 
301     // see if there is a per-user vcpkg tree that has been integrated into msbuild
302     // using `vcpkg integrate install`
303     if let Ok(ref local_app_data) = env::var("LOCALAPPDATA") {
304         let vcpkg_user_targets_path = Path::new(local_app_data.as_str())
305             .join("vcpkg")
306             .join("vcpkg.user.targets");
307 
308         if let Ok(file) = File::open(vcpkg_user_targets_path.clone()) {
309             let file = BufReader::new(&file);
310 
311             for line in file.lines() {
312                 let line = try!(line.map_err(|_| Error::VcpkgNotFound(format!(
313                     "Parsing of {} failed.",
314                     vcpkg_user_targets_path.to_string_lossy().to_owned()
315                 ))));
316                 let mut split = line.split("Project=\"");
317                 split.next(); // eat anything before Project="
318                 if let Some(found) = split.next() {
319                     // " is illegal in a Windows pathname
320                     if let Some(found) = found.split_terminator('"').next() {
321                         let mut vcpkg_root = PathBuf::from(found);
322                         if !(vcpkg_root.pop()
323                             && vcpkg_root.pop()
324                             && vcpkg_root.pop()
325                             && vcpkg_root.pop())
326                         {
327                             return Err(Error::VcpkgNotFound(format!(
328                                 "Could not find vcpkg root above {}",
329                                 found
330                             )));
331                         }
332                         return Ok(vcpkg_root);
333                     }
334                 }
335             }
336 
337             // return Err(Error::VcpkgNotFound(format!(
338             //     "Project location not found parsing {}.",
339             //     vcpkg_user_targets_path.to_string_lossy().to_owned()
340             // )));
341         }
342     }
343 
344     // walk up the directory structure and see if it is there
345     if let Some(path) = env::var_os("OUT_DIR") {
346         // path.ancestors() is supported from Rust 1.28
347         let mut path = PathBuf::from(path);
348         while path.pop() {
349             let mut try_root = path.clone();
350             try_root.push("vcpkg");
351             try_root.push(".vcpkg-root");
352             if try_root.exists() {
353                 try_root.pop();
354 
355                 // this could walk up beyond the target directory and find a vcpkg installation
356                 // that would not have been found by previous versions of vcpkg-rs, so this
357                 // checks that the vcpkg tree was created by cargo-vcpkg and ignores it if not.
358                 let mut cv_cfg = try_root.clone();
359                 cv_cfg.push("downloads");
360                 cv_cfg.push("cargo-vcpkg.toml");
361                 if cv_cfg.exists() {
362                     return Ok(try_root);
363                 }
364             }
365         }
366     }
367 
368     Err(Error::VcpkgNotFound(
369         "No vcpkg installation found. Set the VCPKG_ROOT environment \
370              variable or run 'vcpkg integrate install'"
371             .to_string(),
372     ))
373 }
374 
validate_vcpkg_root(path: &PathBuf) -> Result<(), Error>375 fn validate_vcpkg_root(path: &PathBuf) -> Result<(), Error> {
376     let mut vcpkg_root_path = path.clone();
377     vcpkg_root_path.push(".vcpkg-root");
378 
379     if vcpkg_root_path.exists() {
380         Ok(())
381     } else {
382         Err(Error::VcpkgNotFound(format!(
383             "Could not find Vcpkg root at {}",
384             vcpkg_root_path.to_string_lossy()
385         )))
386     }
387 }
388 
find_vcpkg_target(cfg: &Config, target_triplet: &TargetTriplet) -> Result<VcpkgTarget, Error>389 fn find_vcpkg_target(cfg: &Config, target_triplet: &TargetTriplet) -> Result<VcpkgTarget, Error> {
390     let vcpkg_root = try!(find_vcpkg_root(&cfg));
391     try!(validate_vcpkg_root(&vcpkg_root));
392 
393     let mut base = vcpkg_root.clone();
394     base.push("installed");
395     let status_path = base.join("vcpkg");
396 
397     base.push(&target_triplet.triplet);
398 
399     let lib_path = base.join("lib");
400     let bin_path = base.join("bin");
401     let include_path = base.join("include");
402     let packages_path = vcpkg_root.join("packages");
403 
404     Ok(VcpkgTarget {
405         lib_path: lib_path,
406         bin_path: bin_path,
407         include_path: include_path,
408         status_path: status_path,
409         packages_path: packages_path,
410         target_triplet: target_triplet.clone(),
411     })
412 }
413 
414 /// Parsed knowledge from a .pc file.
415 #[derive(Debug)]
416 struct PcFile {
417     /// The pkg-config name of this library.
418     id: String,
419     /// List of libraries found as '-l', translated to a given vcpkg_target. e.g. libbrotlicommon.a
420     libs: Vec<String>,
421     /// List of pkgconfig dependencies, e.g. PcFile::id.
422     deps: Vec<String>,
423 }
424 impl PcFile {
parse_pc_file(vcpkg_target: &VcpkgTarget, path: &Path) -> Result<Self, Error>425     fn parse_pc_file(vcpkg_target: &VcpkgTarget, path: &Path) -> Result<Self, Error> {
426         // Extract the pkg-config name.
427         let id = try!(path
428             .file_stem()
429             .ok_or_else(|| Error::VcpkgInstallation(format!(
430                 "pkg-config file {} has bogus name",
431                 path.to_string_lossy()
432             ))))
433         .to_string_lossy();
434         // Read through the file and gather what we want.
435         let mut file = try!(File::open(path)
436             .map_err(|_| Error::VcpkgInstallation(format!("Couldn't open {}", path.display()))));
437         let mut pc_file_contents = String::new();
438 
439         try!(file
440             .read_to_string(&mut pc_file_contents)
441             .map_err(|_| Error::VcpkgInstallation(format!("Couldn't read {}", path.display()))));
442         PcFile::from_str(&id, &pc_file_contents, &vcpkg_target.target_triplet)
443     }
from_str(id: &str, s: &str, target_triplet: &TargetTriplet) -> Result<Self, Error>444     fn from_str(id: &str, s: &str, target_triplet: &TargetTriplet) -> Result<Self, Error> {
445         let mut libs = Vec::new();
446         let mut deps = Vec::new();
447 
448         for line in s.lines() {
449             // We could collect a lot of stuff here, but we only care about Requires and Libs for the moment.
450             if line.starts_with("Requires:") {
451                 let mut requires_args = line
452                     .split(":")
453                     .skip(1)
454                     .next()
455                     .unwrap_or("")
456                     .split_whitespace()
457                     .flat_map(|e| e.split(","))
458                     .filter(|s| *s != "");
459                 while let Some(dep) = requires_args.next() {
460                     // Drop any versioning requirements, we only care about library order and rely upon
461                     // port dependencies to resolve versioning.
462                     if let Some(_) = dep.find(|c| c == '=' || c == '<' || c == '>') {
463                         requires_args.next();
464                         continue;
465                     }
466                     deps.push(dep.to_owned());
467                 }
468             } else if line.starts_with("Libs:") {
469                 let lib_flags = line
470                     .split(":")
471                     .skip(1)
472                     .next()
473                     .unwrap_or("")
474                     .split_whitespace();
475                 for lib_flag in lib_flags {
476                     if lib_flag.starts_with("-l") {
477                         // reconstruct the library name.
478                         let lib = format!(
479                             "{}{}.{}",
480                             if target_triplet.strip_lib_prefix {
481                                 "lib"
482                             } else {
483                                 ""
484                             },
485                             lib_flag.trim_left_matches("-l"),
486                             target_triplet.lib_suffix
487                         );
488                         libs.push(lib);
489                     }
490                 }
491             }
492         }
493 
494         Ok(PcFile {
495             id: id.to_string(),
496             libs: libs,
497             deps: deps,
498         })
499     }
500 }
501 
502 /// Collection of PcFile.  Can be built and queried as a set of .pc files.
503 #[derive(Debug)]
504 struct PcFiles {
505     files: HashMap<String, PcFile>,
506 }
507 impl PcFiles {
load_pkgconfig_dir(vcpkg_target: &VcpkgTarget, path: &PathBuf) -> Result<Self, Error>508     fn load_pkgconfig_dir(vcpkg_target: &VcpkgTarget, path: &PathBuf) -> Result<Self, Error> {
509         let mut files = HashMap::new();
510         for dir_entry in try!(path.read_dir().map_err(|e| {
511             Error::VcpkgInstallation(format!(
512                 "Missing pkgconfig directory {}: {}",
513                 path.to_string_lossy(),
514                 e
515             ))
516         })) {
517             let dir_entry = try!(dir_entry.map_err(|e| {
518                 Error::VcpkgInstallation(format!(
519                     "Troubling reading pkgconfig dir {}: {}",
520                     path.to_string_lossy(),
521                     e
522                 ))
523             }));
524             // Only look at .pc files.
525             if dir_entry.path().extension() != Some(OsStr::new("pc")) {
526                 continue;
527             }
528             let pc_file = try!(PcFile::parse_pc_file(vcpkg_target, &dir_entry.path()));
529             files.insert(pc_file.id.to_owned(), pc_file);
530         }
531         Ok(PcFiles { files: files })
532     }
533     /// Use the .pc files as a hint to the library sort order.
fix_ordering(&self, mut libs: Vec<String>) -> Vec<String>534     fn fix_ordering(&self, mut libs: Vec<String>) -> Vec<String> {
535         // Overall heuristic: for each library given as input, identify which PcFile declared it.
536         // Then, looking at that PcFile, check its Requires: (deps), and if the pc file for that
537         // dep is in our set, check if its libraries are in our set of libs.  If so, move it to the
538         // end to ensure it gets linked afterwards.
539 
540         // We may need to do this a few times to properly handle the case where A -> (depends on) B
541         // -> C -> D and libraries were originally sorted D, C, B, A.  Avoid recursion so we don't
542         // have to detect potential cycles.
543         for _iter in 0..3 {
544             let mut required_lib_order: Vec<String> = Vec::new();
545             for lib in &libs {
546                 required_lib_order.push(lib.to_owned());
547                 if let Some(pc_file) = self.locate_pc_file_by_lib(lib) {
548                     // Consider its requirements:
549                     for dep in &pc_file.deps {
550                         // Only consider pkgconfig dependencies we know about.
551                         if let Some(dep_pc_file) = self.files.get(dep) {
552                             // Intra-port library ordering found, pivot any already seen dep_lib to the
553                             // end of the list.
554                             for dep_lib in &dep_pc_file.libs {
555                                 if let Some(removed) = remove_item(&mut required_lib_order, dep_lib)
556                                 {
557                                     required_lib_order.push(removed);
558                                 }
559                             }
560                         }
561                     }
562                 }
563             }
564             // We should always end up with the same number of libraries, only their order should
565             // change.
566             assert_eq!(libs.len(), required_lib_order.len());
567             // Termination:
568             if required_lib_order == libs {
569                 // Nothing changed, we're done here.
570                 return libs;
571             }
572             libs = required_lib_order;
573         }
574         println!("cargo:warning=vcpkg gave up trying to resolve pkg-config ordering.");
575         libs
576     }
577     /// Locate which PcFile contains this library, if any.
locate_pc_file_by_lib(&self, lib: &str) -> Option<&PcFile>578     fn locate_pc_file_by_lib(&self, lib: &str) -> Option<&PcFile> {
579         for (id, pc_file) in &self.files {
580             if pc_file.libs.contains(&lib.to_owned()) {
581                 return Some(pc_file);
582             }
583         }
584         None
585     }
586 }
587 
588 #[derive(Clone, Debug)]
589 struct Port {
590     // dlls if any
591     dlls: Vec<String>,
592 
593     // libs (static or import)
594     libs: Vec<String>,
595 
596     // ports that this port depends on
597     deps: Vec<String>,
598 }
599 
load_port_manifest( path: &PathBuf, port: &str, version: &str, vcpkg_target: &VcpkgTarget, ) -> Result<(Vec<String>, Vec<String>), Error>600 fn load_port_manifest(
601     path: &PathBuf,
602     port: &str,
603     version: &str,
604     vcpkg_target: &VcpkgTarget,
605 ) -> Result<(Vec<String>, Vec<String>), Error> {
606     let manifest_file = path.join("info").join(format!(
607         "{}_{}_{}.list",
608         port, version, vcpkg_target.target_triplet.triplet
609     ));
610 
611     let mut dlls = Vec::new();
612     let mut libs = Vec::new();
613 
614     let f = try!(
615         File::open(&manifest_file).map_err(|_| Error::VcpkgInstallation(format!(
616             "Could not open port manifest file {}",
617             manifest_file.display()
618         )))
619     );
620 
621     let file = BufReader::new(&f);
622 
623     let dll_prefix = Path::new(&vcpkg_target.target_triplet.triplet).join("bin");
624     let lib_prefix = Path::new(&vcpkg_target.target_triplet.triplet).join("lib");
625 
626     for line in file.lines() {
627         let line = line.unwrap();
628 
629         let file_path = Path::new(&line);
630 
631         if let Ok(dll) = file_path.strip_prefix(&dll_prefix) {
632             if dll.extension() == Some(OsStr::new("dll"))
633                 && dll.components().collect::<Vec<_>>().len() == 1
634             {
635                 // match "mylib.dll" but not "debug/mylib.dll" or "manual_link/mylib.dll"
636 
637                 dll.to_str().map(|s| dlls.push(s.to_owned()));
638             }
639         } else if let Ok(lib) = file_path.strip_prefix(&lib_prefix) {
640             if lib.extension() == Some(OsStr::new(&vcpkg_target.target_triplet.lib_suffix))
641                 && lib.components().collect::<Vec<_>>().len() == 1
642             {
643                 if let Some(lib) = vcpkg_target.link_name_for_lib(lib) {
644                     libs.push(lib);
645                 }
646             }
647         }
648     }
649 
650     // Load .pc files for hints about intra-port library ordering.
651     let pkg_config_prefix = vcpkg_target
652         .packages_path
653         .join(format!("{}_{}", port, vcpkg_target.target_triplet.triplet))
654         .join("lib")
655         .join("pkgconfig");
656     // Try loading the pc files, if they are present. Not all ports have pkgconfig.
657     if let Ok(pc_files) = PcFiles::load_pkgconfig_dir(vcpkg_target, &pkg_config_prefix) {
658         // Use the .pc file data to potentially sort the libs to the correct order.
659         libs = pc_files.fix_ordering(libs);
660     }
661 
662     Ok((dlls, libs))
663 }
664 
665 // load ports from the status file or one of the incremental updates
load_port_file( filename: &PathBuf, port_info: &mut Vec<BTreeMap<String, String>>, ) -> Result<(), Error>666 fn load_port_file(
667     filename: &PathBuf,
668     port_info: &mut Vec<BTreeMap<String, String>>,
669 ) -> Result<(), Error> {
670     let f = try!(
671         File::open(&filename).map_err(|e| Error::VcpkgInstallation(format!(
672             "Could not open status file at {}: {}",
673             filename.display(),
674             e
675         )))
676     );
677     let file = BufReader::new(&f);
678     let mut current: BTreeMap<String, String> = BTreeMap::new();
679     for line in file.lines() {
680         let line = line.unwrap();
681         let parts = line.splitn(2, ": ").clone().collect::<Vec<_>>();
682         if parts.len() == 2 {
683             // a key: value line
684             current.insert(parts[0].trim().into(), parts[1].trim().into());
685         } else if line.len() == 0 {
686             // end of section
687             port_info.push(current.clone());
688             current.clear();
689         } else {
690             // ignore all extension lines of the form
691             //
692             // Description: a package with a
693             //   very long description
694             //
695             // the description key is not used so this is harmless but
696             // this will eat extension lines for any multiline key which
697             // could become an issue in future
698         }
699     }
700 
701     if !current.is_empty() {
702         port_info.push(current);
703     }
704 
705     Ok(())
706 }
707 
load_ports(target: &VcpkgTarget) -> Result<BTreeMap<String, Port>, Error>708 fn load_ports(target: &VcpkgTarget) -> Result<BTreeMap<String, Port>, Error> {
709     let mut ports: BTreeMap<String, Port> = BTreeMap::new();
710 
711     let mut port_info: Vec<BTreeMap<String, String>> = Vec::new();
712 
713     // load the main status file. It is not an error if this file does not
714     // exist. If the only command that has been run in a Vcpkg installation
715     // is a single `vcpkg install package` then there will likely be no
716     // status file, only incremental updates. This is the typical case when
717     // running in a CI environment.
718     let status_filename = target.status_path.join("status");
719     load_port_file(&status_filename, &mut port_info).ok();
720 
721     // load updates to the status file that have yet to be normalized
722     let status_update_dir = target.status_path.join("updates");
723 
724     let paths = try!(
725         fs::read_dir(status_update_dir).map_err(|e| Error::VcpkgInstallation(format!(
726             "could not read status file updates dir: {}",
727             e
728         )))
729     );
730 
731     // get all of the paths of the update files into a Vec<PathBuf>
732     let mut paths = try!(paths
733         .map(|rde| rde.map(|de| de.path())) // Result<DirEntry, io::Error> -> Result<PathBuf, io::Error>
734         .collect::<Result<Vec<_>, _>>() // collect into Result<Vec<PathBuf>, io::Error>
735         .map_err(|e| {
736             Error::VcpkgInstallation(format!(
737                 "could not read status file update filenames: {}",
738                 e
739             ))
740         }));
741 
742     // Sort the paths and read them. This could be done directly from the iterator if
743     // read_dir() guarantees that the files will be read in alpha order but that appears
744     // to be unspecified as the underlying operating system calls used are unspecified
745     // https://doc.rust-lang.org/nightly/std/fs/fn.read_dir.html#platform-specific-behavior
746     paths.sort();
747     for path in paths {
748         //       println!("Name: {}", path.display());
749         try!(load_port_file(&path, &mut port_info));
750     }
751     //println!("{:#?}", port_info);
752 
753     let mut seen_names = BTreeMap::new();
754     for current in &port_info {
755         // store them by name and arch, clobbering older details
756         match (
757             current.get("Package"),
758             current.get("Architecture"),
759             current.get("Feature"),
760         ) {
761             (Some(pkg), Some(arch), feature) => {
762                 seen_names.insert((pkg, arch, feature), current);
763             }
764             _ => {}
765         }
766     }
767 
768     for (&(name, arch, feature), current) in &seen_names {
769         if **arch == target.target_triplet.triplet {
770             let mut deps = if let Some(deps) = current.get("Depends") {
771                 deps.split(", ").map(|x| x.to_owned()).collect()
772             } else {
773                 Vec::new()
774             };
775 
776             if current
777                 .get("Status")
778                 .unwrap_or(&String::new())
779                 .ends_with(" installed")
780             {
781                 match (current.get("Version"), feature) {
782                     (Some(version), _) => {
783                         // this failing here and bailing out causes everything to fail
784                         let lib_info = try!(load_port_manifest(
785                             &target.status_path,
786                             &name,
787                             version,
788                             &target
789                         ));
790                         let port = Port {
791                             dlls: lib_info.0,
792                             libs: lib_info.1,
793                             deps: deps,
794                         };
795 
796                         ports.insert(name.to_string(), port);
797                     }
798                     (_, Some(_feature)) => match ports.get_mut(name) {
799                         Some(ref mut port) => {
800                             port.deps.append(&mut deps);
801                         }
802                         _ => {
803                             println!("found a feature that had no corresponding port :-");
804                             println!("current {:+?}", current);
805                             continue;
806                         }
807                     },
808                     (_, _) => {
809                         println!("didn't know how to deal with status file entry :-");
810                         println!("{:+?}", current);
811                         continue;
812                     }
813                 }
814             }
815         }
816     }
817 
818     Ok(ports)
819 }
820 
821 /// paths and triple for the chosen target
822 struct VcpkgTarget {
823     lib_path: PathBuf,
824     bin_path: PathBuf,
825     include_path: PathBuf,
826 
827     // directory containing the status file
828     status_path: PathBuf,
829     // directory containing the install files per port.
830     packages_path: PathBuf,
831 
832     // target-specific settings.
833     target_triplet: TargetTriplet,
834 }
835 
836 impl VcpkgTarget {
link_name_for_lib(&self, filename: &std::path::Path) -> Option<String>837     fn link_name_for_lib(&self, filename: &std::path::Path) -> Option<String> {
838         if self.target_triplet.strip_lib_prefix {
839             filename.to_str().map(|s| s.to_owned())
840         // filename
841         //     .to_str()
842         //     .map(|s| s.trim_left_matches("lib").to_owned())
843         } else {
844             filename.to_str().map(|s| s.to_owned())
845         }
846     }
847 }
848 
849 impl Config {
new() -> Config850     pub fn new() -> Config {
851         Config {
852             cargo_metadata: true,
853             copy_dlls: true,
854             ..Default::default()
855         }
856     }
857 
get_target_triplet(&mut self) -> Result<TargetTriplet, Error>858     fn get_target_triplet(&mut self) -> Result<TargetTriplet, Error> {
859         if self.target.is_none() {
860             let target = if let Ok(triplet_str) = env::var("VCPKGRS_TRIPLET") {
861                 triplet_str.into()
862             } else {
863                 try!(msvc_target())
864             };
865             self.target = Some(target);
866         }
867 
868         Ok(self.target.as_ref().unwrap().clone())
869     }
870 
871     /// Find the package `port_name` in a Vcpkg tree.
872     ///
873     /// Emits cargo metadata to link to libraries provided by the Vcpkg package/port
874     /// named, and any (non-system) libraries that they depend on.
875     ///
876     /// This will select the architecture and linkage based on environment
877     /// variables and build flags as described in the module docs, and any configuration
878     /// set on the builder.
find_package(&mut self, port_name: &str) -> Result<Library, Error>879     pub fn find_package(&mut self, port_name: &str) -> Result<Library, Error> {
880         // determine the target type, bailing out if it is not some
881         // kind of msvc
882         let msvc_target = try!(self.get_target_triplet());
883 
884         // bail out if requested to not try at all
885         if env::var_os("VCPKGRS_DISABLE").is_some() {
886             return Err(Error::DisabledByEnv("VCPKGRS_DISABLE".to_owned()));
887         }
888 
889         // bail out if requested to not try at all (old)
890         if env::var_os("NO_VCPKG").is_some() {
891             return Err(Error::DisabledByEnv("NO_VCPKG".to_owned()));
892         }
893 
894         // bail out if requested to skip this package
895         let abort_var_name = format!("VCPKGRS_NO_{}", envify(port_name));
896         if env::var_os(&abort_var_name).is_some() {
897             return Err(Error::DisabledByEnv(abort_var_name));
898         }
899 
900         // bail out if requested to skip this package (old)
901         let abort_var_name = format!("{}_NO_VCPKG", envify(port_name));
902         if env::var_os(&abort_var_name).is_some() {
903             return Err(Error::DisabledByEnv(abort_var_name));
904         }
905 
906         let vcpkg_target = try!(find_vcpkg_target(&self, &msvc_target));
907         let mut required_port_order = Vec::new();
908 
909         // if no overrides have been selected, then the Vcpkg port name
910         // is the the .lib name and the .dll name
911         if self.required_libs.is_empty() {
912             let ports = try!(load_ports(&vcpkg_target));
913 
914             if ports.get(&port_name.to_owned()).is_none() {
915                 return Err(Error::LibNotFound(format!(
916                     "package {} is not installed for vcpkg triplet {}",
917                     port_name.to_owned(),
918                     vcpkg_target.target_triplet.triplet
919                 )));
920             }
921 
922             // the complete set of ports required
923             let mut required_ports: BTreeMap<String, Port> = BTreeMap::new();
924             // working of ports that we need to include
925             //        let mut ports_to_scan: BTreeSet<String> = BTreeSet::new();
926             //        ports_to_scan.insert(port_name.to_owned());
927             let mut ports_to_scan = vec![port_name.to_owned()]; //: Vec<String> = BTreeSet::new();
928 
929             while !ports_to_scan.is_empty() {
930                 let port_name = ports_to_scan.pop().unwrap();
931 
932                 if required_ports.contains_key(&port_name) {
933                     continue;
934                 }
935 
936                 if let Some(port) = ports.get(&port_name) {
937                     for dep in &port.deps {
938                         ports_to_scan.push(dep.clone());
939                     }
940                     required_ports.insert(port_name.clone(), (*port).clone());
941                     remove_item(&mut required_port_order, &port_name);
942                     required_port_order.push(port_name);
943                 } else {
944                     // what?
945                 }
946             }
947 
948             // for port in ports {
949             //     println!("port {:?}", port);
950             // }
951             // println!("== Looking for port {}", port_name);
952             // for port in &required_port_order {
953             //     println!("ordered required port {:?}", port);
954             // }
955             // println!("=============================");
956             // for port in &required_ports {
957             //     println!("required port {:?}", port);
958             // }
959 
960             // if no overrides have been selected, then the Vcpkg port name
961             // is the the .lib name and the .dll name
962             if self.required_libs.is_empty() {
963                 for port_name in &required_port_order {
964                     let port = required_ports.get(port_name).unwrap();
965                     self.required_libs.extend(port.libs.iter().map(|s| {
966                         Path::new(&s)
967                             .file_stem()
968                             .unwrap()
969                             .to_string_lossy()
970                             .into_owned()
971                     }));
972                     self.required_dlls
973                         .extend(port.dlls.iter().cloned().map(|s| {
974                             Path::new(&s)
975                                 .file_stem()
976                                 .unwrap()
977                                 .to_string_lossy()
978                                 .into_owned()
979                         }));
980                 }
981             }
982         }
983         // require explicit opt-in before using dynamically linked
984         // variants, otherwise cargo install of various things will
985         // stop working if Vcpkg is installed.
986         if !vcpkg_target.target_triplet.is_static && !env::var_os("VCPKGRS_DYNAMIC").is_some() {
987             return Err(Error::RequiredEnvMissing("VCPKGRS_DYNAMIC".to_owned()));
988         }
989 
990         let mut lib = Library::new(
991             vcpkg_target.target_triplet.is_static,
992             &vcpkg_target.target_triplet.triplet,
993         );
994 
995         if self.emit_includes {
996             lib.cargo_metadata.push(format!(
997                 "cargo:include={}",
998                 vcpkg_target.include_path.display()
999             ));
1000         }
1001         lib.include_paths.push(vcpkg_target.include_path.clone());
1002 
1003         lib.cargo_metadata.push(format!(
1004             "cargo:rustc-link-search=native={}",
1005             vcpkg_target
1006                 .lib_path
1007                 .to_str()
1008                 .expect("failed to convert string type")
1009         ));
1010         lib.link_paths.push(vcpkg_target.lib_path.clone());
1011         if !vcpkg_target.target_triplet.is_static {
1012             lib.cargo_metadata.push(format!(
1013                 "cargo:rustc-link-search=native={}",
1014                 vcpkg_target
1015                     .bin_path
1016                     .to_str()
1017                     .expect("failed to convert string type")
1018             ));
1019             // this path is dropped by recent versions of cargo hence the copies to OUT_DIR below
1020             lib.dll_paths.push(vcpkg_target.bin_path.clone());
1021         }
1022 
1023         lib.ports = required_port_order;
1024 
1025         try!(self.emit_libs(&mut lib, &vcpkg_target));
1026 
1027         if self.copy_dlls {
1028             try!(self.do_dll_copy(&mut lib));
1029         }
1030 
1031         if self.cargo_metadata {
1032             for line in &lib.cargo_metadata {
1033                 println!("{}", line);
1034             }
1035         }
1036         Ok(lib)
1037     }
1038 
1039     /// Define whether metadata should be emitted for cargo allowing it to
1040     /// automatically link the binary. Defaults to `true`.
cargo_metadata(&mut self, cargo_metadata: bool) -> &mut Config1041     pub fn cargo_metadata(&mut self, cargo_metadata: bool) -> &mut Config {
1042         self.cargo_metadata = cargo_metadata;
1043         self
1044     }
1045 
1046     /// Define cargo:include= metadata should be emitted. Defaults to `false`.
emit_includes(&mut self, emit_includes: bool) -> &mut Config1047     pub fn emit_includes(&mut self, emit_includes: bool) -> &mut Config {
1048         self.emit_includes = emit_includes;
1049         self
1050     }
1051 
1052     /// Should DLLs be copied to OUT_DIR?
1053     /// Defaults to `true`.
copy_dlls(&mut self, copy_dlls: bool) -> &mut Config1054     pub fn copy_dlls(&mut self, copy_dlls: bool) -> &mut Config {
1055         self.copy_dlls = copy_dlls;
1056         self
1057     }
1058 
1059     /// Define which path to use as vcpkg root overriding the VCPKG_ROOT environment variable
1060     /// Default to `None`, which means use VCPKG_ROOT or try to find out automatically
vcpkg_root(&mut self, vcpkg_root: PathBuf) -> &mut Config1061     pub fn vcpkg_root(&mut self, vcpkg_root: PathBuf) -> &mut Config {
1062         self.vcpkg_root = Some(vcpkg_root);
1063         self
1064     }
1065 
1066     /// Specify target triplet. When triplet is not specified, inferred triplet from rust target is used.
1067     ///
1068     /// Specifying a triplet using `target_triplet` will override the default triplet for this crate. This
1069     /// cannot change the choice of triplet made by other crates, so a safer choice will be to set
1070     /// `VCPKGRS_TRIPLET` in the environment which will allow all crates to use a consistent set of
1071     /// external dependencies.
target_triplet<S: AsRef<str>>(&mut self, triplet: S) -> &mut Config1072     pub fn target_triplet<S: AsRef<str>>(&mut self, triplet: S) -> &mut Config {
1073         self.target = Some(triplet.into());
1074         self
1075     }
1076 
1077     /// Find the library `port_name` in a Vcpkg tree.
1078     ///
1079     /// This will use all configuration previously set to select the
1080     /// architecture and linkage.
1081     /// Deprecated in favor of the find_package function
1082     #[doc(hidden)]
probe(&mut self, port_name: &str) -> Result<Library, Error>1083     pub fn probe(&mut self, port_name: &str) -> Result<Library, Error> {
1084         // determine the target type, bailing out if it is not some
1085         // kind of msvc
1086         let msvc_target = try!(self.get_target_triplet());
1087 
1088         // bail out if requested to not try at all
1089         if env::var_os("VCPKGRS_DISABLE").is_some() {
1090             return Err(Error::DisabledByEnv("VCPKGRS_DISABLE".to_owned()));
1091         }
1092 
1093         // bail out if requested to not try at all (old)
1094         if env::var_os("NO_VCPKG").is_some() {
1095             return Err(Error::DisabledByEnv("NO_VCPKG".to_owned()));
1096         }
1097 
1098         // bail out if requested to skip this package
1099         let abort_var_name = format!("VCPKGRS_NO_{}", envify(port_name));
1100         if env::var_os(&abort_var_name).is_some() {
1101             return Err(Error::DisabledByEnv(abort_var_name));
1102         }
1103 
1104         // bail out if requested to skip this package (old)
1105         let abort_var_name = format!("{}_NO_VCPKG", envify(port_name));
1106         if env::var_os(&abort_var_name).is_some() {
1107             return Err(Error::DisabledByEnv(abort_var_name));
1108         }
1109 
1110         // if no overrides have been selected, then the Vcpkg port name
1111         // is the the .lib name and the .dll name
1112         if self.required_libs.is_empty() {
1113             self.required_libs.push(port_name.to_owned());
1114             self.required_dlls.push(port_name.to_owned());
1115         }
1116 
1117         let vcpkg_target = try!(find_vcpkg_target(&self, &msvc_target));
1118 
1119         // require explicit opt-in before using dynamically linked
1120         // variants, otherwise cargo install of various things will
1121         // stop working if Vcpkg is installed.
1122         if !vcpkg_target.target_triplet.is_static && !env::var_os("VCPKGRS_DYNAMIC").is_some() {
1123             return Err(Error::RequiredEnvMissing("VCPKGRS_DYNAMIC".to_owned()));
1124         }
1125 
1126         let mut lib = Library::new(
1127             vcpkg_target.target_triplet.is_static,
1128             &vcpkg_target.target_triplet.triplet,
1129         );
1130 
1131         if self.emit_includes {
1132             lib.cargo_metadata.push(format!(
1133                 "cargo:include={}",
1134                 vcpkg_target.include_path.display()
1135             ));
1136         }
1137         lib.include_paths.push(vcpkg_target.include_path.clone());
1138 
1139         lib.cargo_metadata.push(format!(
1140             "cargo:rustc-link-search=native={}",
1141             vcpkg_target
1142                 .lib_path
1143                 .to_str()
1144                 .expect("failed to convert string type")
1145         ));
1146         lib.link_paths.push(vcpkg_target.lib_path.clone());
1147         if !vcpkg_target.target_triplet.is_static {
1148             lib.cargo_metadata.push(format!(
1149                 "cargo:rustc-link-search=native={}",
1150                 vcpkg_target
1151                     .bin_path
1152                     .to_str()
1153                     .expect("failed to convert string type")
1154             ));
1155             // this path is dropped by recent versions of cargo hence the copies to OUT_DIR below
1156             lib.dll_paths.push(vcpkg_target.bin_path.clone());
1157         }
1158 
1159         try!(self.emit_libs(&mut lib, &vcpkg_target));
1160 
1161         if self.copy_dlls {
1162             try!(self.do_dll_copy(&mut lib));
1163         }
1164 
1165         if self.cargo_metadata {
1166             for line in &lib.cargo_metadata {
1167                 println!("{}", line);
1168             }
1169         }
1170         Ok(lib)
1171     }
1172 
emit_libs(&mut self, lib: &mut Library, vcpkg_target: &VcpkgTarget) -> Result<(), Error>1173     fn emit_libs(&mut self, lib: &mut Library, vcpkg_target: &VcpkgTarget) -> Result<(), Error> {
1174         for required_lib in &self.required_libs {
1175             // this could use static-nobundle= for static libraries but it is apparently
1176             // not necessary to make the distinction for windows-msvc.
1177 
1178             let link_name = match vcpkg_target.target_triplet.strip_lib_prefix {
1179                 true => required_lib.trim_left_matches("lib"),
1180                 false => required_lib,
1181             };
1182 
1183             lib.cargo_metadata
1184                 .push(format!("cargo:rustc-link-lib={}", link_name));
1185 
1186             lib.found_names.push(String::from(link_name));
1187 
1188             // verify that the library exists
1189             let mut lib_location = vcpkg_target.lib_path.clone();
1190             lib_location.push(required_lib.clone() + "." + &vcpkg_target.target_triplet.lib_suffix);
1191 
1192             if !lib_location.exists() {
1193                 return Err(Error::LibNotFound(lib_location.display().to_string()));
1194             }
1195             lib.found_libs.push(lib_location);
1196         }
1197 
1198         if !vcpkg_target.target_triplet.is_static {
1199             for required_dll in &self.required_dlls {
1200                 let mut dll_location = vcpkg_target.bin_path.clone();
1201                 dll_location.push(required_dll.clone() + ".dll");
1202 
1203                 // verify that the DLL exists
1204                 if !dll_location.exists() {
1205                     return Err(Error::LibNotFound(dll_location.display().to_string()));
1206                 }
1207                 lib.found_dlls.push(dll_location);
1208             }
1209         }
1210 
1211         Ok(())
1212     }
1213 
do_dll_copy(&mut self, lib: &mut Library) -> Result<(), Error>1214     fn do_dll_copy(&mut self, lib: &mut Library) -> Result<(), Error> {
1215         if let Some(target_dir) = env::var_os("OUT_DIR") {
1216             if !lib.found_dlls.is_empty() {
1217                 for file in &lib.found_dlls {
1218                     let mut dest_path = Path::new(target_dir.as_os_str()).to_path_buf();
1219                     dest_path.push(Path::new(file.file_name().unwrap()));
1220                     try!(
1221                         fs::copy(file, &dest_path).map_err(|_| Error::LibNotFound(format!(
1222                             "Can't copy file {} to {}",
1223                             file.to_string_lossy(),
1224                             dest_path.to_string_lossy()
1225                         )))
1226                     );
1227                     println!(
1228                         "vcpkg build helper copied {} to {}",
1229                         file.to_string_lossy(),
1230                         dest_path.to_string_lossy()
1231                     );
1232                 }
1233                 lib.cargo_metadata.push(format!(
1234                     "cargo:rustc-link-search=native={}",
1235                     env::var("OUT_DIR").unwrap()
1236                 ));
1237                 // work around https://github.com/rust-lang/cargo/issues/3957
1238                 lib.cargo_metadata.push(format!(
1239                     "cargo:rustc-link-search={}",
1240                     env::var("OUT_DIR").unwrap()
1241                 ));
1242             }
1243         } else {
1244             return Err(Error::LibNotFound("Unable to get OUT_DIR".to_owned()));
1245         }
1246         Ok(())
1247     }
1248 
1249     /// Override the name of the library to look for if it differs from the package name.
1250     ///
1251     /// It should not be necessary to use `lib_name` anymore. Calling `find_package` with a package name
1252     /// will result in the correct library names.
1253     /// This may be called more than once if multiple libs are required.
1254     /// All libs must be found for the probe to succeed. `.probe()` must
1255     /// be run with a different configuration to look for libraries under one of several names.
1256     /// `.libname("ssleay32")` will look for ssleay32.lib and also ssleay32.dll if
1257     /// dynamic linking is selected.
lib_name(&mut self, lib_stem: &str) -> &mut Config1258     pub fn lib_name(&mut self, lib_stem: &str) -> &mut Config {
1259         self.required_libs.push(lib_stem.to_owned());
1260         self.required_dlls.push(lib_stem.to_owned());
1261         self
1262     }
1263 
1264     /// Override the name of the library to look for if it differs from the package name.
1265     ///
1266     /// It should not be necessary to use `lib_names` anymore. Calling `find_package` with a package name
1267     /// will result in the correct library names.
1268     /// This may be called more than once if multiple libs are required.
1269     /// All libs must be found for the probe to succeed. `.probe()` must
1270     /// be run with a different configuration to look for libraries under one of several names.
1271     /// `.lib_names("libcurl_imp","curl")` will look for libcurl_imp.lib and also curl.dll if
1272     /// dynamic linking is selected.
lib_names(&mut self, lib_stem: &str, dll_stem: &str) -> &mut Config1273     pub fn lib_names(&mut self, lib_stem: &str, dll_stem: &str) -> &mut Config {
1274         self.required_libs.push(lib_stem.to_owned());
1275         self.required_dlls.push(dll_stem.to_owned());
1276         self
1277     }
1278 }
1279 
remove_item(cont: &mut Vec<String>, item: &String) -> Option<String>1280 fn remove_item(cont: &mut Vec<String>, item: &String) -> Option<String> {
1281     match cont.iter().position(|x| *x == *item) {
1282         Some(pos) => Some(cont.remove(pos)),
1283         None => None,
1284     }
1285 }
1286 
1287 impl Library {
new(is_static: bool, vcpkg_triplet: &str) -> Library1288     fn new(is_static: bool, vcpkg_triplet: &str) -> Library {
1289         Library {
1290             link_paths: Vec::new(),
1291             dll_paths: Vec::new(),
1292             include_paths: Vec::new(),
1293             cargo_metadata: Vec::new(),
1294             is_static: is_static,
1295             found_dlls: Vec::new(),
1296             found_libs: Vec::new(),
1297             found_names: Vec::new(),
1298             ports: Vec::new(),
1299             vcpkg_triplet: vcpkg_triplet.to_string(),
1300         }
1301     }
1302 }
1303 
envify(name: &str) -> String1304 fn envify(name: &str) -> String {
1305     name.chars()
1306         .map(|c| c.to_ascii_uppercase())
1307         .map(|c| if c == '-' { '_' } else { c })
1308         .collect()
1309 }
1310 
msvc_target() -> Result<TargetTriplet, Error>1311 fn msvc_target() -> Result<TargetTriplet, Error> {
1312     let is_definitely_dynamic = env::var("VCPKGRS_DYNAMIC").is_ok();
1313     let target = env::var("TARGET").unwrap_or(String::new());
1314     let is_static = env::var("CARGO_CFG_TARGET_FEATURE")
1315         .unwrap_or(String::new()) // rustc 1.10
1316         .contains("crt-static");
1317     if target == "x86_64-apple-darwin" {
1318         Ok(TargetTriplet {
1319             triplet: "x64-osx".into(),
1320             is_static: true,
1321             lib_suffix: "a".into(),
1322             strip_lib_prefix: true,
1323         })
1324     } else if target == "aarch64-apple-darwin" {
1325         Ok(TargetTriplet {
1326             triplet: "arm64-osx".into(),
1327             is_static: true,
1328             lib_suffix: "a".into(),
1329             strip_lib_prefix: true,
1330         })
1331     } else if target == "x86_64-unknown-linux-gnu" {
1332         Ok(TargetTriplet {
1333             triplet: "x64-linux".into(),
1334             is_static: true,
1335             lib_suffix: "a".into(),
1336             strip_lib_prefix: true,
1337         })
1338     } else if target == "aarch64-apple-ios" {
1339         Ok(TargetTriplet {
1340             triplet: "arm64-ios".into(),
1341             is_static: true,
1342             lib_suffix: "a".into(),
1343             strip_lib_prefix: true,
1344         })
1345     } else if !target.contains("-pc-windows-msvc") {
1346         Err(Error::NotMSVC)
1347     } else if target.starts_with("x86_64-") {
1348         if is_static {
1349             Ok(TargetTriplet {
1350                 triplet: "x64-windows-static".into(),
1351                 is_static: true,
1352                 lib_suffix: "lib".into(),
1353                 strip_lib_prefix: false,
1354             })
1355         } else if is_definitely_dynamic {
1356             Ok(TargetTriplet {
1357                 triplet: "x64-windows".into(),
1358                 is_static: false,
1359                 lib_suffix: "lib".into(),
1360                 strip_lib_prefix: false,
1361             })
1362         } else {
1363             Ok(TargetTriplet {
1364                 triplet: "x64-windows-static-md".into(),
1365                 is_static: true,
1366                 lib_suffix: "lib".into(),
1367                 strip_lib_prefix: false,
1368             })
1369         }
1370     } else if target.starts_with("aarch64") {
1371         if is_static {
1372             Ok(TargetTriplet {
1373                 triplet: "arm64-windows-static".into(),
1374                 is_static: true,
1375                 lib_suffix: "lib".into(),
1376                 strip_lib_prefix: false,
1377             })
1378         } else if is_definitely_dynamic {
1379             Ok(TargetTriplet {
1380                 triplet: "arm64-windows".into(),
1381                 is_static: false,
1382                 lib_suffix: "lib".into(),
1383                 strip_lib_prefix: false,
1384             })
1385         } else {
1386             Ok(TargetTriplet {
1387                 triplet: "arm64-windows-static-md".into(),
1388                 is_static: true,
1389                 lib_suffix: "lib".into(),
1390                 strip_lib_prefix: false,
1391             })
1392         }
1393     } else {
1394         // everything else is x86
1395         if is_static {
1396             Ok(TargetTriplet {
1397                 triplet: "x86-windows-static".into(),
1398                 is_static: true,
1399                 lib_suffix: "lib".into(),
1400                 strip_lib_prefix: false,
1401             })
1402         } else if is_definitely_dynamic {
1403             Ok(TargetTriplet {
1404                 triplet: "x86-windows".into(),
1405                 is_static: false,
1406                 lib_suffix: "lib".into(),
1407                 strip_lib_prefix: false,
1408             })
1409         } else {
1410             Ok(TargetTriplet {
1411                 triplet: "x86-windows-static-md".into(),
1412                 is_static: true,
1413                 lib_suffix: "lib".into(),
1414                 strip_lib_prefix: false,
1415             })
1416         }
1417     }
1418 }
1419 
1420 #[cfg(test)]
1421 mod tests {
1422 
1423     extern crate tempdir;
1424 
1425     use super::*;
1426     use std::env;
1427     use std::sync::Mutex;
1428 
1429     lazy_static! {
1430         static ref LOCK: Mutex<()> = Mutex::new(());
1431     }
1432 
1433     #[test]
do_nothing_for_unsupported_target()1434     fn do_nothing_for_unsupported_target() {
1435         let _g = LOCK.lock();
1436         env::set_var("VCPKG_ROOT", "/");
1437         env::set_var("TARGET", "x86_64-pc-windows-gnu");
1438         assert!(match ::probe_package("foo") {
1439             Err(Error::NotMSVC) => true,
1440             _ => false,
1441         });
1442 
1443         env::set_var("TARGET", "x86_64-pc-windows-gnu");
1444         assert_eq!(env::var("TARGET"), Ok("x86_64-pc-windows-gnu".to_string()));
1445         assert!(match ::probe_package("foo") {
1446             Err(Error::NotMSVC) => true,
1447             _ => false,
1448         });
1449         env::remove_var("TARGET");
1450         env::remove_var("VCPKG_ROOT");
1451     }
1452 
1453     #[test]
do_nothing_for_bailout_variables_set()1454     fn do_nothing_for_bailout_variables_set() {
1455         let _g = LOCK.lock();
1456         env::set_var("VCPKG_ROOT", "/");
1457         env::set_var("TARGET", "x86_64-pc-windows-msvc");
1458 
1459         for &var in &[
1460             "VCPKGRS_DISABLE",
1461             "VCPKGRS_NO_FOO",
1462             "FOO_NO_VCPKG",
1463             "NO_VCPKG",
1464         ] {
1465             env::set_var(var, "1");
1466             assert!(match ::probe_package("foo") {
1467                 Err(Error::DisabledByEnv(ref v)) if v == var => true,
1468                 _ => false,
1469             });
1470             env::remove_var(var);
1471         }
1472         env::remove_var("TARGET");
1473         env::remove_var("VCPKG_ROOT");
1474     }
1475 
1476     // these tests are good but are leaning on a real vcpkg installation
1477 
1478     // #[test]
1479     // fn default_build_refuses_dynamic() {
1480     //     let _g = LOCK.lock();
1481     //     clean_env();
1482     //     env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("no-status"));
1483     //     env::set_var("TARGET", "x86_64-pc-windows-msvc");
1484     //     println!("Result is {:?}", ::find_package("libmysql"));
1485     //     assert!(match ::find_package("libmysql") {
1486     //         Err(Error::RequiredEnvMissing(ref v)) if v == "VCPKGRS_DYNAMIC" => true,
1487     //         _ => false,
1488     //     });
1489     //     clean_env();
1490     // }
1491 
1492     #[test]
static_build_finds_lib()1493     fn static_build_finds_lib() {
1494         let _g = LOCK.lock();
1495         clean_env();
1496         env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("normalized"));
1497         env::set_var("TARGET", "x86_64-pc-windows-msvc");
1498         let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap();
1499         env::set_var("OUT_DIR", tmp_dir.path());
1500 
1501         // CARGO_CFG_TARGET_FEATURE is set in response to
1502         // RUSTFLAGS=-Ctarget-feature=+crt-static. It would
1503         //  be nice to test that also.
1504         env::set_var("CARGO_CFG_TARGET_FEATURE", "crt-static");
1505         println!("Result is {:?}", ::find_package("libmysql"));
1506         assert!(match ::find_package("libmysql") {
1507             Ok(_) => true,
1508             _ => false,
1509         });
1510         clean_env();
1511     }
1512 
1513     #[test]
dynamic_build_finds_lib()1514     fn dynamic_build_finds_lib() {
1515         let _g = LOCK.lock();
1516         clean_env();
1517         env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("no-status"));
1518         env::set_var("TARGET", "x86_64-pc-windows-msvc");
1519         env::set_var("VCPKGRS_DYNAMIC", "1");
1520         let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap();
1521         env::set_var("OUT_DIR", tmp_dir.path());
1522 
1523         println!("Result is {:?}", ::find_package("libmysql"));
1524         assert!(match ::find_package("libmysql") {
1525             Ok(_) => true,
1526             _ => false,
1527         });
1528         clean_env();
1529     }
1530 
1531     #[test]
handle_multiline_description()1532     fn handle_multiline_description() {
1533         let _g = LOCK.lock();
1534         clean_env();
1535         env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("multiline-description"));
1536         env::set_var("TARGET", "i686-pc-windows-msvc");
1537         env::set_var("VCPKGRS_DYNAMIC", "1");
1538         let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap();
1539         env::set_var("OUT_DIR", tmp_dir.path());
1540 
1541         println!("Result is {:?}", ::find_package("graphite2"));
1542         assert!(match ::find_package("graphite2") {
1543             Ok(_) => true,
1544             _ => false,
1545         });
1546         clean_env();
1547     }
1548 
1549     #[test]
link_libs_required_by_optional_features()1550     fn link_libs_required_by_optional_features() {
1551         let _g = LOCK.lock();
1552         clean_env();
1553         env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("normalized"));
1554         env::set_var("TARGET", "i686-pc-windows-msvc");
1555         env::set_var("VCPKGRS_DYNAMIC", "1");
1556         let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap();
1557         env::set_var("OUT_DIR", tmp_dir.path());
1558 
1559         println!("Result is {:?}", ::find_package("harfbuzz"));
1560         assert!(match ::find_package("harfbuzz") {
1561             Ok(lib) => lib
1562                 .cargo_metadata
1563                 .iter()
1564                 .find(|&x| x == "cargo:rustc-link-lib=icuuc")
1565                 .is_some(),
1566             _ => false,
1567         });
1568         clean_env();
1569     }
1570 
1571     #[test]
link_lib_name_is_correct()1572     fn link_lib_name_is_correct() {
1573         let _g = LOCK.lock();
1574 
1575         for target in &[
1576             "x86_64-apple-darwin",
1577             "i686-pc-windows-msvc",
1578             //      "x86_64-pc-windows-msvc",
1579             //    "x86_64-unknown-linux-gnu",
1580         ] {
1581             clean_env();
1582             env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("normalized"));
1583             env::set_var("TARGET", target);
1584             env::set_var("VCPKGRS_DYNAMIC", "1");
1585             let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap();
1586             env::set_var("OUT_DIR", tmp_dir.path());
1587 
1588             println!("Result is {:?}", ::find_package("harfbuzz"));
1589             assert!(match ::find_package("harfbuzz") {
1590                 Ok(lib) => lib
1591                     .cargo_metadata
1592                     .iter()
1593                     .find(|&x| x == "cargo:rustc-link-lib=harfbuzz")
1594                     .is_some(),
1595                 _ => false,
1596             });
1597             clean_env();
1598         }
1599     }
1600 
1601     #[test]
link_dependencies_after_port()1602     fn link_dependencies_after_port() {
1603         let _g = LOCK.lock();
1604         clean_env();
1605         env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("normalized"));
1606         env::set_var("TARGET", "i686-pc-windows-msvc");
1607         env::set_var("VCPKGRS_DYNAMIC", "1");
1608         let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap();
1609         env::set_var("OUT_DIR", tmp_dir.path());
1610 
1611         let lib = ::find_package("harfbuzz").unwrap();
1612 
1613         check_before(&lib, "freetype", "zlib");
1614         check_before(&lib, "freetype", "bzip2");
1615         check_before(&lib, "freetype", "libpng");
1616         check_before(&lib, "harfbuzz", "freetype");
1617         check_before(&lib, "harfbuzz", "ragel");
1618         check_before(&lib, "libpng", "zlib");
1619 
1620         clean_env();
1621 
1622         fn check_before(lib: &Library, earlier: &str, later: &str) {
1623             match (
1624                 lib.ports.iter().position(|x| *x == *earlier),
1625                 lib.ports.iter().position(|x| *x == *later),
1626             ) {
1627                 (Some(earlier_pos), Some(later_pos)) if earlier_pos < later_pos => {
1628                     // ok
1629                 }
1630                 _ => {
1631                     println!(
1632                         "earlier: {}, later: {}\nLibrary found: {:#?}",
1633                         earlier, later, lib
1634                     );
1635                     panic!();
1636                 }
1637             }
1638         }
1639     }
1640 
1641     #[test]
custom_target_triplet_in_config()1642     fn custom_target_triplet_in_config() {
1643         let _g = LOCK.lock();
1644 
1645         clean_env();
1646         env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("normalized"));
1647         env::set_var("TARGET", "aarch64-apple-ios");
1648         env::set_var("VCPKGRS_DYNAMIC", "1");
1649         let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap();
1650         env::set_var("OUT_DIR", tmp_dir.path());
1651 
1652         let harfbuzz = ::Config::new()
1653             // For the sake of testing, force this build to try to
1654             // link to the arm64-osx libraries in preference to the
1655             // default of arm64-ios.
1656             .target_triplet("x64-osx")
1657             .find_package("harfbuzz");
1658         println!("Result with specifying target triplet is {:?}", &harfbuzz);
1659         let harfbuzz = harfbuzz.unwrap();
1660         assert_eq!(harfbuzz.vcpkg_triplet, "x64-osx");
1661         clean_env();
1662     }
1663 
1664     #[test]
custom_target_triplet_by_env_no_default()1665     fn custom_target_triplet_by_env_no_default() {
1666         let _g = LOCK.lock();
1667 
1668         clean_env();
1669         env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("normalized"));
1670         env::set_var("TARGET", "aarch64-apple-doesnotexist");
1671         env::set_var("VCPKGRS_DYNAMIC", "1");
1672         let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap();
1673         env::set_var("OUT_DIR", tmp_dir.path());
1674 
1675         let harfbuzz = ::find_package("harfbuzz");
1676         println!("Result with inference is {:?}", &harfbuzz);
1677         assert!(harfbuzz.is_err());
1678 
1679         env::set_var("VCPKGRS_TRIPLET", "x64-osx");
1680         let harfbuzz = ::find_package("harfbuzz").unwrap();
1681         println!("Result with setting VCPKGRS_TRIPLET is {:?}", &harfbuzz);
1682         assert_eq!(harfbuzz.vcpkg_triplet, "x64-osx");
1683         clean_env();
1684     }
1685 
1686     #[test]
custom_target_triplet_by_env_with_default()1687     fn custom_target_triplet_by_env_with_default() {
1688         let _g = LOCK.lock();
1689 
1690         clean_env();
1691         env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("normalized"));
1692         env::set_var("TARGET", "aarch64-apple-ios");
1693         env::set_var("VCPKGRS_DYNAMIC", "1");
1694         let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap();
1695         env::set_var("OUT_DIR", tmp_dir.path());
1696 
1697         let harfbuzz = ::find_package("harfbuzz").unwrap();
1698         println!("Result with inference is {:?}", &harfbuzz);
1699         assert_eq!(harfbuzz.vcpkg_triplet, "arm64-ios");
1700 
1701         env::set_var("VCPKGRS_TRIPLET", "x64-osx");
1702         let harfbuzz = ::find_package("harfbuzz").unwrap();
1703         println!("Result with setting VCPKGRS_TRIPLET is {:?}", &harfbuzz);
1704         assert_eq!(harfbuzz.vcpkg_triplet, "x64-osx");
1705         clean_env();
1706     }
1707 
1708     // #[test]
1709     // fn dynamic_build_package_specific_bailout() {
1710     //     clean_env();
1711     //     env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("no-status"));
1712     //     env::set_var("TARGET", "x86_64-pc-windows-msvc");
1713     //     env::set_var("VCPKGRS_DYNAMIC", "1");
1714     //     env::set_var("VCPKGRS_NO_LIBMYSQL", "1");
1715 
1716     //     println!("Result is {:?}", ::find_package("libmysql"));
1717     //     assert!(match ::find_package("libmysql") {
1718     //         Err(Error::DisabledByEnv(ref v)) if v == "VCPKGRS_NO_LIBMYSQL" => true,
1719     //         _ => false,
1720     //     });
1721     //     clean_env();
1722     // }
1723 
1724     // #[test]
1725     // fn dynamic_build_global_bailout() {
1726     //     clean_env();
1727     //     env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("no-status"));
1728     //     env::set_var("TARGET", "x86_64-pc-windows-msvc");
1729     //     env::set_var("VCPKGRS_DYNAMIC", "1");
1730     //     env::set_var("VCPKGRS_DISABLE", "1");
1731 
1732     //     println!("Result is {:?}", ::find_package("libmysql"));
1733     //     assert!(match ::find_package("libmysql") {
1734     //         Err(Error::DisabledByEnv(ref v)) if v == "VCPKGRS_DISABLE" => true,
1735     //         _ => false,
1736     //     });
1737     //     clean_env();
1738     // }
1739 
1740     #[test]
pc_files_reordering()1741     fn pc_files_reordering() {
1742         let _g = LOCK.lock();
1743         clean_env();
1744         env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("normalized"));
1745         env::set_var("TARGET", "x86_64-unknown-linux-gnu");
1746         // env::set_var("VCPKGRS_DYNAMIC", "1");
1747         let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap();
1748         env::set_var("OUT_DIR", tmp_dir.path());
1749 
1750         let target_triplet = msvc_target().unwrap();
1751 
1752         // The brotli use-case.
1753         {
1754             let mut pc_files = PcFiles {
1755                 files: HashMap::new(),
1756             };
1757             pc_files.files.insert(
1758                 "libbrotlicommon".to_owned(),
1759                 PcFile::from_str(
1760                     "libbrotlicommon",
1761                     "Libs: -lbrotlicommon-static\nRequires:",
1762                     &target_triplet,
1763                 )
1764                 .unwrap(),
1765             );
1766             pc_files.files.insert(
1767                 "libbrotlienc".to_owned(),
1768                 PcFile::from_str(
1769                     "libbrotlienc",
1770                     "Libs: -lbrotlienc-static\nRequires: libbrotlicommon",
1771                     &target_triplet,
1772                 )
1773                 .unwrap(),
1774             );
1775             pc_files.files.insert(
1776                 "libbrotlidec".to_owned(),
1777                 PcFile::from_str(
1778                     "brotlidec",
1779                     "Libs: -lbrotlidec-static\nRequires: libbrotlicommon >= 1.0.9",
1780                     &target_triplet,
1781                 )
1782                 .unwrap(),
1783             );
1784             // Note that the input is alphabetically sorted.
1785             let input_libs = vec![
1786                 "libbrotlicommon-static.a".to_owned(),
1787                 "libbrotlidec-static.a".to_owned(),
1788                 "libbrotlienc-static.a".to_owned(),
1789             ];
1790             let output_libs = pc_files.fix_ordering(input_libs);
1791             assert_eq!(output_libs[0], "libbrotlidec-static.a");
1792             assert_eq!(output_libs[1], "libbrotlienc-static.a");
1793             assert_eq!(output_libs[2], "libbrotlicommon-static.a");
1794         }
1795 
1796         // Concoct elaborate dependency graph, try all variations of input sort.
1797         // Throw some (ignored) version dependencies as well as extra libs not represented in the
1798         // pc_files dataset.
1799         {
1800             let mut pc_files = PcFiles {
1801                 files: HashMap::new(),
1802             };
1803             pc_files.files.insert(
1804                 "libA".to_owned(),
1805                 PcFile::from_str(
1806                     "libA",
1807                     "Libs: -lA\n\
1808                      Requires:",
1809                     &target_triplet,
1810                 )
1811                 .unwrap(),
1812             );
1813             pc_files.files.insert(
1814                 "libB".to_owned(),
1815                 PcFile::from_str(
1816                     "libB",
1817                     "Libs:  -lB -lm -pthread \n\
1818                      Requires: libA",
1819                     &target_triplet,
1820                 )
1821                 .unwrap(),
1822             );
1823             pc_files.files.insert(
1824                 "libC".to_owned(),
1825                 PcFile::from_str(
1826                     "libC",
1827                     "Libs: -lC -L${libdir}\n\
1828                      Requires: libB <=1.0 , libmysql-client = 0.9, ",
1829                     &target_triplet,
1830                 )
1831                 .unwrap(),
1832             );
1833             pc_files.files.insert(
1834                 "libD".to_owned(),
1835                 PcFile::from_str(
1836                     "libD",
1837                     "Libs: -Lpath/to/libs -Rplugins -lD\n\
1838                      Requires: libpostgres libC",
1839                     &target_triplet,
1840                 )
1841                 .unwrap(),
1842             );
1843             let permutations: Vec<Vec<&str>> = vec![
1844                 vec!["libA.a", "libB.a", "libC.a", "libD.a"],
1845                 vec!["libA.a", "libB.a", "libD.a", "libC.a"],
1846                 vec!["libA.a", "libC.a", "libB.a", "libD.a"],
1847                 vec!["libA.a", "libC.a", "libD.a", "libB.a"],
1848                 vec!["libA.a", "libD.a", "libB.a", "libC.a"],
1849                 vec!["libA.a", "libD.a", "libC.a", "libB.a"],
1850                 //
1851                 vec!["libB.a", "libA.a", "libC.a", "libD.a"],
1852                 vec!["libB.a", "libA.a", "libD.a", "libC.a"],
1853                 vec!["libB.a", "libC.a", "libA.a", "libD.a"],
1854                 vec!["libB.a", "libC.a", "libD.a", "libA.a"],
1855                 vec!["libB.a", "libD.a", "libA.a", "libC.a"],
1856                 vec!["libB.a", "libD.a", "libC.a", "libA.a"],
1857                 //
1858                 vec!["libC.a", "libA.a", "libB.a", "libD.a"],
1859                 vec!["libC.a", "libA.a", "libD.a", "libB.a"],
1860                 vec!["libC.a", "libB.a", "libA.a", "libD.a"],
1861                 vec!["libC.a", "libB.a", "libD.a", "libA.a"],
1862                 vec!["libC.a", "libD.a", "libA.a", "libB.a"],
1863                 vec!["libC.a", "libD.a", "libB.a", "libA.a"],
1864                 //
1865                 vec!["libD.a", "libA.a", "libB.a", "libC.a"],
1866                 vec!["libD.a", "libA.a", "libC.a", "libB.a"],
1867                 vec!["libD.a", "libB.a", "libA.a", "libC.a"],
1868                 vec!["libD.a", "libB.a", "libC.a", "libA.a"],
1869                 vec!["libD.a", "libC.a", "libA.a", "libB.a"],
1870                 vec!["libD.a", "libC.a", "libB.a", "libA.a"],
1871             ];
1872             for permutation in permutations {
1873                 let input_libs = vec![
1874                     permutation[0].to_owned(),
1875                     permutation[1].to_owned(),
1876                     permutation[2].to_owned(),
1877                     permutation[3].to_owned(),
1878                 ];
1879                 let output_libs = pc_files.fix_ordering(input_libs);
1880                 assert_eq!(output_libs.len(), 4);
1881                 assert_eq!(output_libs[0], "libD.a");
1882                 assert_eq!(output_libs[1], "libC.a");
1883                 assert_eq!(output_libs[2], "libB.a");
1884                 assert_eq!(output_libs[3], "libA.a");
1885             }
1886         }
1887 
1888         // Test parsing of a couple different Requires: lines.
1889         {
1890             let pc_file = PcFile::from_str(
1891                 "test",
1892                 "Libs: -ltest\n\
1893                  Requires: cairo libpng",
1894                 &target_triplet,
1895             )
1896             .unwrap();
1897             assert_eq!(pc_file.deps, vec!["cairo", "libpng"]);
1898             let pc_file = PcFile::from_str(
1899                 "test",
1900                 "Libs: -ltest\n\
1901                  Requires: cairo xcb >= 1.6 xcb-render >= 1.6",
1902                 &target_triplet,
1903             )
1904             .unwrap();
1905             assert_eq!(pc_file.deps, vec!["cairo", "xcb", "xcb-render"]);
1906             let pc_file = PcFile::from_str(
1907                 "test",
1908                 "Libs: -ltest\n\
1909                  Requires: glib-2.0, gobject-2.0",
1910                 &target_triplet,
1911             )
1912             .unwrap();
1913             assert_eq!(pc_file.deps, vec!["glib-2.0", "gobject-2.0"]);
1914             let pc_file = PcFile::from_str(
1915                 "test",
1916                 "Libs: -ltest\n\
1917                  Requires: glib-2.0 >=  2.58.0, gobject-2.0 >=  2.58.0",
1918                 &target_triplet,
1919             )
1920             .unwrap();
1921             assert_eq!(pc_file.deps, vec!["glib-2.0", "gobject-2.0"]);
1922         }
1923 
1924         clean_env();
1925     }
1926 
clean_env()1927     fn clean_env() {
1928         env::remove_var("TARGET");
1929         env::remove_var("VCPKG_ROOT");
1930         env::remove_var("VCPKGRS_DYNAMIC");
1931         env::remove_var("RUSTFLAGS");
1932         env::remove_var("CARGO_CFG_TARGET_FEATURE");
1933         env::remove_var("VCPKGRS_DISABLE");
1934         env::remove_var("VCPKGRS_NO_LIBMYSQL");
1935         env::remove_var("VCPKGRS_TRIPLET");
1936     }
1937 
1938     // path to a to vcpkg installation to test against
vcpkg_test_tree_loc(name: &str) -> PathBuf1939     fn vcpkg_test_tree_loc(name: &str) -> PathBuf {
1940         let mut path = PathBuf::new();
1941         path.push(env::var("CARGO_MANIFEST_DIR").unwrap());
1942         path.push("test-data");
1943         path.push(name);
1944         path
1945     }
1946 }
1947