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.10 because at this point rust-openssl's
90 // openssl-sys is backward compatible that far. (Actually, the oldest release
91 // crate openssl version 0.10 seems to build against is now Rust 1.24.1?)
92 #![allow(deprecated)]
93 #![allow(warnings)]
94 
95 #[cfg(test)]
96 #[macro_use]
97 extern crate lazy_static;
98 
99 #[allow(unused_imports)]
100 use std::ascii::AsciiExt;
101 
102 use std::collections::BTreeMap;
103 use std::env;
104 use std::error;
105 use std::ffi::OsStr;
106 use std::fmt;
107 use std::fs::{self, File};
108 use std::io::{BufRead, BufReader};
109 use std::path::{Path, PathBuf};
110 
111 /// Configuration options for finding packages, setting up the tree and emitting metadata to cargo
112 #[derive(Default)]
113 pub struct Config {
114     /// should the cargo metadata actually be emitted
115     cargo_metadata: bool,
116 
117     /// should cargo:include= metadata be emitted (defaults to false)
118     emit_includes: bool,
119 
120     /// .lib/.a files that must be be found for probing to be considered successful
121     required_libs: Vec<String>,
122 
123     /// .dlls that must be be found for probing to be considered successful
124     required_dlls: Vec<String>,
125 
126     /// should DLLs be copied to OUT_DIR?
127     copy_dlls: bool,
128 
129     /// override VCPKG_ROOT environment variable
130     vcpkg_root: Option<PathBuf>,
131 
132     target: Option<TargetTriplet>,
133 }
134 
135 /// Details of a package that was found
136 #[derive(Debug)]
137 pub struct Library {
138     /// Paths for the linker to search for static or import libraries
139     pub link_paths: Vec<PathBuf>,
140 
141     /// Paths to search at runtme to find DLLs
142     pub dll_paths: Vec<PathBuf>,
143 
144     /// Paths to include files
145     pub include_paths: Vec<PathBuf>,
146 
147     /// cargo: metadata lines
148     pub cargo_metadata: Vec<String>,
149 
150     /// libraries found are static
151     pub is_static: bool,
152 
153     /// DLLs found
154     pub found_dlls: Vec<PathBuf>,
155 
156     /// static libs or import libs found
157     pub found_libs: Vec<PathBuf>,
158 
159     /// link name of libraries found, this is useful to emit linker commands
160     pub found_names: Vec<String>,
161 
162     /// ports that are providing the libraries to link to, in port link order
163     pub ports: Vec<String>,
164 
165     /// the vcpkg triplet that has been selected
166     pub vcpkg_triplet: String,
167 }
168 
169 #[derive(Clone)]
170 struct TargetTriplet {
171     triplet: String,
172     is_static: bool,
173     lib_suffix: String,
174     strip_lib_prefix: bool,
175 }
176 
177 impl<S: AsRef<str>> From<S> for TargetTriplet {
from(triplet: S) -> TargetTriplet178     fn from(triplet: S) -> TargetTriplet {
179         let triplet = triplet.as_ref();
180         if triplet.contains("windows") {
181             TargetTriplet {
182                 triplet: triplet.into(),
183                 is_static: triplet.contains("-static"),
184                 lib_suffix: "lib".into(),
185                 strip_lib_prefix: false,
186             }
187         } else {
188             TargetTriplet {
189                 triplet: triplet.into(),
190                 is_static: true,
191                 lib_suffix: "a".into(),
192                 strip_lib_prefix: true,
193             }
194         }
195     }
196 }
197 
198 #[derive(Debug)] // need Display?
199 pub enum Error {
200     /// Aborted because of a `VCPKGRS_NO_*` environment variable.
201     ///
202     /// Contains the name of the responsible environment variable.
203     DisabledByEnv(String),
204 
205     /// Aborted because a required environment variable was not set.
206     RequiredEnvMissing(String),
207 
208     /// On Windows, only MSVC ABI is supported
209     NotMSVC,
210 
211     /// Can't find a vcpkg tree
212     VcpkgNotFound(String),
213 
214     /// Library not found in vcpkg tree
215     LibNotFound(String),
216 
217     /// Could not understand vcpkg installation
218     VcpkgInstallation(String),
219 
220     #[doc(hidden)]
221     __Nonexhaustive,
222 }
223 
224 impl error::Error for Error {
description(&self) -> &str225     fn description(&self) -> &str {
226         match *self {
227             Error::DisabledByEnv(_) => "vcpkg-rs requested to be aborted",
228             Error::RequiredEnvMissing(_) => "a required env setting is missing",
229             Error::NotMSVC => "vcpkg-rs only can only find libraries for MSVC ABI builds",
230             Error::VcpkgNotFound(_) => "could not find Vcpkg tree",
231             Error::LibNotFound(_) => "could not find library in Vcpkg tree",
232             Error::VcpkgInstallation(_) => "could not look up details of packages in vcpkg tree",
233             Error::__Nonexhaustive => panic!(),
234         }
235     }
236 
cause(&self) -> Option<&error::Error>237     fn cause(&self) -> Option<&error::Error> {
238         match *self {
239             // Error::Command { ref cause, .. } => Some(cause),
240             _ => None,
241         }
242     }
243 }
244 
245 impl fmt::Display for Error {
fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>246     fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
247         match *self {
248             Error::DisabledByEnv(ref name) => write!(f, "Aborted because {} is set", name),
249             Error::RequiredEnvMissing(ref name) => write!(f, "Aborted because {} is not set", name),
250             Error::NotMSVC => write!(
251                 f,
252                 "the vcpkg-rs Vcpkg build helper can only find libraries built for the MSVC ABI."
253             ),
254             Error::VcpkgNotFound(ref detail) => write!(f, "Could not find Vcpkg tree: {}", detail),
255             Error::LibNotFound(ref detail) => {
256                 write!(f, "Could not find library in Vcpkg tree {}", detail)
257             }
258             Error::VcpkgInstallation(ref detail) => write!(
259                 f,
260                 "Could not look up details of packages in vcpkg tree {}",
261                 detail
262             ),
263             Error::__Nonexhaustive => panic!(),
264         }
265     }
266 }
267 
268 /// Deprecated in favor of the find_package function
269 #[doc(hidden)]
probe_package(name: &str) -> Result<Library, Error>270 pub fn probe_package(name: &str) -> Result<Library, Error> {
271     Config::new().probe(name)
272 }
273 
274 /// Find the package `package` in a Vcpkg tree.
275 ///
276 /// Emits cargo metadata to link to libraries provided by the Vcpkg package/port
277 /// named, and any (non-system) libraries that they depend on.
278 ///
279 /// This will select the architecture and linkage based on environment
280 /// variables and build flags as described in the module docs.
find_package(package: &str) -> Result<Library, Error>281 pub fn find_package(package: &str) -> Result<Library, Error> {
282     Config::new().find_package(package)
283 }
284 
285 /// Find the vcpkg root
286 #[doc(hidden)]
find_vcpkg_root(cfg: &Config) -> Result<PathBuf, Error>287 pub fn find_vcpkg_root(cfg: &Config) -> Result<PathBuf, Error> {
288     // prefer the setting from the use if there is one
289     if let &Some(ref path) = &cfg.vcpkg_root {
290         return Ok(path.clone());
291     }
292 
293     // otherwise, use the setting from the environment
294     if let Some(path) = env::var_os("VCPKG_ROOT") {
295         return Ok(PathBuf::from(path));
296     }
297 
298     // see if there is a per-user vcpkg tree that has been integrated into msbuild
299     // using `vcpkg integrate install`
300     if let Ok(ref local_app_data) = env::var("LOCALAPPDATA") {
301         let vcpkg_user_targets_path = Path::new(local_app_data.as_str())
302             .join("vcpkg")
303             .join("vcpkg.user.targets");
304 
305         if let Ok(file) = File::open(vcpkg_user_targets_path.clone()) {
306             let file = BufReader::new(&file);
307 
308             for line in file.lines() {
309                 let line = try!(line.map_err(|_| Error::VcpkgNotFound(format!(
310                     "Parsing of {} failed.",
311                     vcpkg_user_targets_path.to_string_lossy().to_owned()
312                 ))));
313                 let mut split = line.split("Project=\"");
314                 split.next(); // eat anything before Project="
315                 if let Some(found) = split.next() {
316                     // " is illegal in a Windows pathname
317                     if let Some(found) = found.split_terminator('"').next() {
318                         let mut vcpkg_root = PathBuf::from(found);
319                         if !(vcpkg_root.pop()
320                             && vcpkg_root.pop()
321                             && vcpkg_root.pop()
322                             && vcpkg_root.pop())
323                         {
324                             return Err(Error::VcpkgNotFound(format!(
325                                 "Could not find vcpkg root above {}",
326                                 found
327                             )));
328                         }
329                         return Ok(vcpkg_root);
330                     }
331                 }
332             }
333 
334             // return Err(Error::VcpkgNotFound(format!(
335             //     "Project location not found parsing {}.",
336             //     vcpkg_user_targets_path.to_string_lossy().to_owned()
337             // )));
338         }
339     }
340 
341     // walk up the directory structure and see if it is there
342     if let Some(path) = env::var_os("OUT_DIR") {
343         // path.ancestors() is supported from Rust 1.28
344         let mut path = PathBuf::from(path);
345         while path.pop() {
346             let mut try_root = path.clone();
347             try_root.push("vcpkg");
348             try_root.push(".vcpkg-root");
349             if try_root.exists() {
350                 try_root.pop();
351 
352                 // this could walk up beyond the target directory and find a vcpkg installation
353                 // that would not have been found by previous versions of vcpkg-rs, so this
354                 // checks that the vcpkg tree was created by cargo-vcpkg and ignores it if not.
355                 let mut cv_cfg = try_root.clone();
356                 cv_cfg.push("downloads");
357                 cv_cfg.push("cargo-vcpkg.toml");
358                 if cv_cfg.exists() {
359                     return Ok(try_root);
360                 }
361             }
362         }
363     }
364 
365     Err(Error::VcpkgNotFound(
366         "No vcpkg installation found. Set the VCPKG_ROOT environment \
367              variable or run 'vcpkg integrate install'"
368             .to_string(),
369     ))
370 }
371 
validate_vcpkg_root(path: &PathBuf) -> Result<(), Error>372 fn validate_vcpkg_root(path: &PathBuf) -> Result<(), Error> {
373     let mut vcpkg_root_path = path.clone();
374     vcpkg_root_path.push(".vcpkg-root");
375 
376     if vcpkg_root_path.exists() {
377         Ok(())
378     } else {
379         Err(Error::VcpkgNotFound(format!(
380             "Could not find Vcpkg root at {}",
381             vcpkg_root_path.to_string_lossy()
382         )))
383     }
384 }
385 
find_vcpkg_target(cfg: &Config, target_triplet: &TargetTriplet) -> Result<VcpkgTarget, Error>386 fn find_vcpkg_target(cfg: &Config, target_triplet: &TargetTriplet) -> Result<VcpkgTarget, Error> {
387     let vcpkg_root = try!(find_vcpkg_root(&cfg));
388     try!(validate_vcpkg_root(&vcpkg_root));
389 
390     let mut base = vcpkg_root;
391     base.push("installed");
392     let status_path = base.join("vcpkg");
393 
394     base.push(&target_triplet.triplet);
395 
396     let lib_path = base.join("lib");
397     let bin_path = base.join("bin");
398     let include_path = base.join("include");
399 
400     Ok(VcpkgTarget {
401         vcpkg_triplet: target_triplet.triplet.to_owned(),
402         lib_path: lib_path,
403         bin_path: bin_path,
404         include_path: include_path,
405         is_static: target_triplet.is_static,
406         status_path: status_path,
407         lib_suffix: target_triplet.lib_suffix.to_owned(),
408         strip_lib_prefix: target_triplet.strip_lib_prefix,
409     })
410 }
411 
412 #[derive(Clone, Debug)]
413 struct Port {
414     // dlls if any
415     dlls: Vec<String>,
416 
417     // libs (static or import)
418     libs: Vec<String>,
419 
420     // ports that this port depends on
421     deps: Vec<String>,
422 }
423 
load_port_manifest( path: &PathBuf, port: &str, version: &str, vcpkg_target: &VcpkgTarget, ) -> Result<(Vec<String>, Vec<String>), Error>424 fn load_port_manifest(
425     path: &PathBuf,
426     port: &str,
427     version: &str,
428     vcpkg_target: &VcpkgTarget,
429 ) -> Result<(Vec<String>, Vec<String>), Error> {
430     let manifest_file = path.join("info").join(format!(
431         "{}_{}_{}.list",
432         port, version, vcpkg_target.vcpkg_triplet
433     ));
434 
435     let mut dlls = Vec::new();
436     let mut libs = Vec::new();
437 
438     let f = try!(
439         File::open(&manifest_file).map_err(|_| Error::VcpkgInstallation(format!(
440             "Could not open port manifest file {}",
441             manifest_file.display()
442         )))
443     );
444 
445     let file = BufReader::new(&f);
446 
447     let dll_prefix = Path::new(&vcpkg_target.vcpkg_triplet).join("bin");
448     let lib_prefix = Path::new(&vcpkg_target.vcpkg_triplet).join("lib");
449 
450     for line in file.lines() {
451         let line = line.unwrap();
452 
453         let file_path = Path::new(&line);
454 
455         if let Ok(dll) = file_path.strip_prefix(&dll_prefix) {
456             if dll.extension() == Some(OsStr::new("dll"))
457                 && dll.components().collect::<Vec<_>>().len() == 1
458             {
459                 // match "mylib.dll" but not "debug/mylib.dll" or "manual_link/mylib.dll"
460 
461                 dll.to_str().map(|s| dlls.push(s.to_owned()));
462             }
463         } else if let Ok(lib) = file_path.strip_prefix(&lib_prefix) {
464             if lib.extension() == Some(OsStr::new(&vcpkg_target.lib_suffix))
465                 && lib.components().collect::<Vec<_>>().len() == 1
466             {
467                 if let Some(lib) = vcpkg_target.link_name_for_lib(lib) {
468                     libs.push(lib);
469                 }
470             }
471         }
472     }
473 
474     Ok((dlls, libs))
475 }
476 
477 // 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>478 fn load_port_file(
479     filename: &PathBuf,
480     port_info: &mut Vec<BTreeMap<String, String>>,
481 ) -> Result<(), Error> {
482     let f = try!(
483         File::open(&filename).map_err(|e| Error::VcpkgInstallation(format!(
484             "Could not open status file at {}: {}",
485             filename.display(),
486             e
487         )))
488     );
489     let file = BufReader::new(&f);
490     let mut current: BTreeMap<String, String> = BTreeMap::new();
491     for line in file.lines() {
492         let line = line.unwrap();
493         let parts = line.splitn(2, ": ").clone().collect::<Vec<_>>();
494         if parts.len() == 2 {
495             // a key: value line
496             current.insert(parts[0].trim().into(), parts[1].trim().into());
497         } else if line.len() == 0 {
498             // end of section
499             port_info.push(current.clone());
500             current.clear();
501         } else {
502             // ignore all extension lines of the form
503             //
504             // Description: a package with a
505             //   very long description
506             //
507             // the description key is not used so this is harmless but
508             // this will eat extension lines for any multiline key which
509             // could become an issue in future
510         }
511     }
512 
513     if !current.is_empty() {
514         port_info.push(current);
515     }
516 
517     Ok(())
518 }
519 
load_ports(target: &VcpkgTarget) -> Result<BTreeMap<String, Port>, Error>520 fn load_ports(target: &VcpkgTarget) -> Result<BTreeMap<String, Port>, Error> {
521     let mut ports: BTreeMap<String, Port> = BTreeMap::new();
522 
523     let mut port_info: Vec<BTreeMap<String, String>> = Vec::new();
524 
525     // load the main status file. It is not an error if this file does not
526     // exist. If the only command that has been run in a Vcpkg installation
527     // is a single `vcpkg install package` then there will likely be no
528     // status file, only incremental updates. This is the typical case when
529     // running in a CI environment.
530     let status_filename = target.status_path.join("status");
531     load_port_file(&status_filename, &mut port_info).ok();
532 
533     // load updates to the status file that have yet to be normalized
534     let status_update_dir = target.status_path.join("updates");
535 
536     let paths = try!(
537         fs::read_dir(status_update_dir).map_err(|e| Error::VcpkgInstallation(format!(
538             "could not read status file updates dir: {}",
539             e
540         )))
541     );
542 
543     // get all of the paths of the update files into a Vec<PathBuf>
544     let mut paths = try!(paths
545         .map(|rde| rde.map(|de| de.path())) // Result<DirEntry, io::Error> -> Result<PathBuf, io::Error>
546         .collect::<Result<Vec<_>, _>>() // collect into Result<Vec<PathBuf>, io::Error>
547         .map_err(|e| {
548             Error::VcpkgInstallation(format!(
549                 "could not read status file update filenames: {}",
550                 e
551             ))
552         }));
553 
554     // Sort the paths and read them. This could be done directly from the iterator if
555     // read_dir() guarantees that the files will be read in alpha order but that appears
556     // to be unspecified as the underlying operating system calls used are unspecified
557     // https://doc.rust-lang.org/nightly/std/fs/fn.read_dir.html#platform-specific-behavior
558     paths.sort();
559     for path in paths {
560         //       println!("Name: {}", path.display());
561         try!(load_port_file(&path, &mut port_info));
562     }
563     //println!("{:#?}", port_info);
564 
565     let mut seen_names = BTreeMap::new();
566     for current in &port_info {
567         // store them by name and arch, clobbering older details
568         match (
569             current.get("Package"),
570             current.get("Architecture"),
571             current.get("Feature"),
572         ) {
573             (Some(pkg), Some(arch), feature) => {
574                 seen_names.insert((pkg, arch, feature), current);
575             }
576             _ => {}
577         }
578     }
579 
580     for (&(name, arch, feature), current) in &seen_names {
581         if **arch == target.vcpkg_triplet {
582             let mut deps = if let Some(deps) = current.get("Depends") {
583                 deps.split(", ").map(|x| x.to_owned()).collect()
584             } else {
585                 Vec::new()
586             };
587 
588             if current
589                 .get("Status")
590                 .unwrap_or(&String::new())
591                 .ends_with(" installed")
592             {
593                 match (current.get("Version"), feature) {
594                     (Some(version), _) => {
595                         // this failing here and bailing out causes everything to fail
596                         let lib_info = try!(load_port_manifest(
597                             &target.status_path,
598                             &name,
599                             version,
600                             &target
601                         ));
602                         let port = Port {
603                             dlls: lib_info.0,
604                             libs: lib_info.1,
605                             deps: deps,
606                         };
607 
608                         ports.insert(name.to_string(), port);
609                     }
610                     (_, Some(_feature)) => match ports.get_mut(name) {
611                         Some(ref mut port) => {
612                             port.deps.append(&mut deps);
613                         }
614                         _ => {
615                             println!("found a feature that had no corresponding port :-");
616                             println!("current {:+?}", current);
617                             continue;
618                         }
619                     },
620                     (_, _) => {
621                         println!("didn't know how to deal with status file entry :-");
622                         println!("{:+?}", current);
623                         continue;
624                     }
625                 }
626             }
627         }
628     }
629 
630     Ok(ports)
631 }
632 
633 /// paths and triple for the chosen target
634 struct VcpkgTarget {
635     vcpkg_triplet: String,
636     lib_path: PathBuf,
637     bin_path: PathBuf,
638     include_path: PathBuf,
639 
640     // directory containing the status file
641     status_path: PathBuf,
642 
643     is_static: bool,
644     lib_suffix: String,
645 
646     /// strip 'lib' from library names in linker args?
647     strip_lib_prefix: bool,
648 }
649 
650 impl VcpkgTarget {
link_name_for_lib(&self, filename: &std::path::Path) -> Option<String>651     fn link_name_for_lib(&self, filename: &std::path::Path) -> Option<String> {
652         if self.strip_lib_prefix {
653             filename.to_str().map(|s| s.to_owned())
654         // filename
655         //     .to_str()
656         //     .map(|s| s.trim_left_matches("lib").to_owned())
657         } else {
658             filename.to_str().map(|s| s.to_owned())
659         }
660     }
661 }
662 
663 impl Config {
new() -> Config664     pub fn new() -> Config {
665         Config {
666             cargo_metadata: true,
667             copy_dlls: true,
668             ..Default::default()
669         }
670     }
671 
get_target_triplet(&mut self) -> Result<TargetTriplet, Error>672     fn get_target_triplet(&mut self) -> Result<TargetTriplet, Error> {
673         if self.target.is_none() {
674             let target = if let Ok(triplet_str) = env::var("VCPKGRS_TRIPLET") {
675                 triplet_str.into()
676             } else {
677                 try!(msvc_target())
678             };
679             self.target = Some(target);
680         }
681 
682         Ok(self.target.as_ref().unwrap().clone())
683     }
684 
685     /// Find the package `port_name` in a Vcpkg tree.
686     ///
687     /// Emits cargo metadata to link to libraries provided by the Vcpkg package/port
688     /// named, and any (non-system) libraries that they depend on.
689     ///
690     /// This will select the architecture and linkage based on environment
691     /// variables and build flags as described in the module docs, and any configuration
692     /// set on the builder.
find_package(&mut self, port_name: &str) -> Result<Library, Error>693     pub fn find_package(&mut self, port_name: &str) -> Result<Library, Error> {
694         // determine the target type, bailing out if it is not some
695         // kind of msvc
696         let msvc_target = try!(self.get_target_triplet());
697 
698         // bail out if requested to not try at all
699         if env::var_os("VCPKGRS_DISABLE").is_some() {
700             return Err(Error::DisabledByEnv("VCPKGRS_DISABLE".to_owned()));
701         }
702 
703         // bail out if requested to not try at all (old)
704         if env::var_os("NO_VCPKG").is_some() {
705             return Err(Error::DisabledByEnv("NO_VCPKG".to_owned()));
706         }
707 
708         // bail out if requested to skip this package
709         let abort_var_name = format!("VCPKGRS_NO_{}", envify(port_name));
710         if env::var_os(&abort_var_name).is_some() {
711             return Err(Error::DisabledByEnv(abort_var_name));
712         }
713 
714         // bail out if requested to skip this package (old)
715         let abort_var_name = format!("{}_NO_VCPKG", envify(port_name));
716         if env::var_os(&abort_var_name).is_some() {
717             return Err(Error::DisabledByEnv(abort_var_name));
718         }
719 
720         let vcpkg_target = try!(find_vcpkg_target(&self, &msvc_target));
721         let mut required_port_order = Vec::new();
722 
723         // if no overrides have been selected, then the Vcpkg port name
724         // is the the .lib name and the .dll name
725         if self.required_libs.is_empty() {
726             let ports = try!(load_ports(&vcpkg_target));
727 
728             if ports.get(&port_name.to_owned()).is_none() {
729                 return Err(Error::LibNotFound(format!(
730                     "package {} is not installed for vcpkg triplet {}",
731                     port_name.to_owned(),
732                     vcpkg_target.vcpkg_triplet
733                 )));
734             }
735 
736             // the complete set of ports required
737             let mut required_ports: BTreeMap<String, Port> = BTreeMap::new();
738             // working of ports that we need to include
739             //        let mut ports_to_scan: BTreeSet<String> = BTreeSet::new();
740             //        ports_to_scan.insert(port_name.to_owned());
741             let mut ports_to_scan = vec![port_name.to_owned()]; //: Vec<String> = BTreeSet::new();
742 
743             while !ports_to_scan.is_empty() {
744                 let port_name = ports_to_scan.pop().unwrap();
745 
746                 if required_ports.contains_key(&port_name) {
747                     continue;
748                 }
749 
750                 if let Some(port) = ports.get(&port_name) {
751                     for dep in &port.deps {
752                         ports_to_scan.push(dep.clone());
753                     }
754                     required_ports.insert(port_name.clone(), (*port).clone());
755                     remove_item(&mut required_port_order, &port_name);
756                     required_port_order.push(port_name);
757                 } else {
758                     // what?
759                 }
760             }
761 
762             // for port in ports {
763             //     println!("port {:?}", port);
764             // }
765             // println!("== Looking for port {}", port_name);
766             // for port in &required_port_order {
767             //     println!("ordered required port {:?}", port);
768             // }
769             // println!("=============================");
770             // for port in &required_ports {
771             //     println!("required port {:?}", port);
772             // }
773 
774             // if no overrides have been selected, then the Vcpkg port name
775             // is the the .lib name and the .dll name
776             if self.required_libs.is_empty() {
777                 for port_name in &required_port_order {
778                     let port = required_ports.get(port_name).unwrap();
779                     self.required_libs.extend(port.libs.iter().map(|s| {
780                         Path::new(&s)
781                             .file_stem()
782                             .unwrap()
783                             .to_string_lossy()
784                             .into_owned()
785                     }));
786                     self.required_dlls
787                         .extend(port.dlls.iter().cloned().map(|s| {
788                             Path::new(&s)
789                                 .file_stem()
790                                 .unwrap()
791                                 .to_string_lossy()
792                                 .into_owned()
793                         }));
794                 }
795             }
796         }
797         // require explicit opt-in before using dynamically linked
798         // variants, otherwise cargo install of various things will
799         // stop working if Vcpkg is installed.
800         if !vcpkg_target.is_static && !env::var_os("VCPKGRS_DYNAMIC").is_some() {
801             return Err(Error::RequiredEnvMissing("VCPKGRS_DYNAMIC".to_owned()));
802         }
803 
804         let mut lib = Library::new(vcpkg_target.is_static, &vcpkg_target.vcpkg_triplet);
805 
806         if self.emit_includes {
807             lib.cargo_metadata.push(format!(
808                 "cargo:include={}",
809                 vcpkg_target.include_path.display()
810             ));
811         }
812         lib.include_paths.push(vcpkg_target.include_path.clone());
813 
814         lib.cargo_metadata.push(format!(
815             "cargo:rustc-link-search=native={}",
816             vcpkg_target
817                 .lib_path
818                 .to_str()
819                 .expect("failed to convert string type")
820         ));
821         lib.link_paths.push(vcpkg_target.lib_path.clone());
822         if !vcpkg_target.is_static {
823             lib.cargo_metadata.push(format!(
824                 "cargo:rustc-link-search=native={}",
825                 vcpkg_target
826                     .bin_path
827                     .to_str()
828                     .expect("failed to convert string type")
829             ));
830             // this path is dropped by recent versions of cargo hence the copies to OUT_DIR below
831             lib.dll_paths.push(vcpkg_target.bin_path.clone());
832         }
833 
834         lib.ports = required_port_order;
835 
836         try!(self.emit_libs(&mut lib, &vcpkg_target));
837 
838         if self.copy_dlls {
839             try!(self.do_dll_copy(&mut lib));
840         }
841 
842         if self.cargo_metadata {
843             for line in &lib.cargo_metadata {
844                 println!("{}", line);
845             }
846         }
847         Ok(lib)
848     }
849 
850     /// Define whether metadata should be emitted for cargo allowing it to
851     /// automatically link the binary. Defaults to `true`.
cargo_metadata(&mut self, cargo_metadata: bool) -> &mut Config852     pub fn cargo_metadata(&mut self, cargo_metadata: bool) -> &mut Config {
853         self.cargo_metadata = cargo_metadata;
854         self
855     }
856 
857     /// Define cargo:include= metadata should be emitted. Defaults to `false`.
emit_includes(&mut self, emit_includes: bool) -> &mut Config858     pub fn emit_includes(&mut self, emit_includes: bool) -> &mut Config {
859         self.emit_includes = emit_includes;
860         self
861     }
862 
863     /// Should DLLs be copied to OUT_DIR?
864     /// Defaults to `true`.
copy_dlls(&mut self, copy_dlls: bool) -> &mut Config865     pub fn copy_dlls(&mut self, copy_dlls: bool) -> &mut Config {
866         self.copy_dlls = copy_dlls;
867         self
868     }
869 
870     /// Define which path to use as vcpkg root overriding the VCPKG_ROOT environment variable
871     /// Default to `None`, which means use VCPKG_ROOT or try to find out automatically
vcpkg_root(&mut self, vcpkg_root: PathBuf) -> &mut Config872     pub fn vcpkg_root(&mut self, vcpkg_root: PathBuf) -> &mut Config {
873         self.vcpkg_root = Some(vcpkg_root);
874         self
875     }
876 
877     /// Specify target triplet. When triplet is not specified, inferred triplet from rust target is used.
878     ///
879     /// Specifying a triplet using `target_triplet` will override the default triplet for this crate. This
880     /// cannot change the choice of triplet made by other crates, so a safer choice will be to set
881     /// `VCPKGRS_TRIPLET` in the environment which will allow all crates to use a consistent set of
882     /// external dependencies.
target_triplet<S: AsRef<str>>(&mut self, triplet: S) -> &mut Config883     pub fn target_triplet<S: AsRef<str>>(&mut self, triplet: S) -> &mut Config {
884         self.target = Some(triplet.into());
885         self
886     }
887 
888     /// Find the library `port_name` in a Vcpkg tree.
889     ///
890     /// This will use all configuration previously set to select the
891     /// architecture and linkage.
892     /// Deprecated in favor of the find_package function
893     #[doc(hidden)]
probe(&mut self, port_name: &str) -> Result<Library, Error>894     pub fn probe(&mut self, port_name: &str) -> Result<Library, Error> {
895         // determine the target type, bailing out if it is not some
896         // kind of msvc
897         let msvc_target = try!(self.get_target_triplet());
898 
899         // bail out if requested to not try at all
900         if env::var_os("VCPKGRS_DISABLE").is_some() {
901             return Err(Error::DisabledByEnv("VCPKGRS_DISABLE".to_owned()));
902         }
903 
904         // bail out if requested to not try at all (old)
905         if env::var_os("NO_VCPKG").is_some() {
906             return Err(Error::DisabledByEnv("NO_VCPKG".to_owned()));
907         }
908 
909         // bail out if requested to skip this package
910         let abort_var_name = format!("VCPKGRS_NO_{}", envify(port_name));
911         if env::var_os(&abort_var_name).is_some() {
912             return Err(Error::DisabledByEnv(abort_var_name));
913         }
914 
915         // bail out if requested to skip this package (old)
916         let abort_var_name = format!("{}_NO_VCPKG", envify(port_name));
917         if env::var_os(&abort_var_name).is_some() {
918             return Err(Error::DisabledByEnv(abort_var_name));
919         }
920 
921         // if no overrides have been selected, then the Vcpkg port name
922         // is the the .lib name and the .dll name
923         if self.required_libs.is_empty() {
924             self.required_libs.push(port_name.to_owned());
925             self.required_dlls.push(port_name.to_owned());
926         }
927 
928         let vcpkg_target = try!(find_vcpkg_target(&self, &msvc_target));
929 
930         // require explicit opt-in before using dynamically linked
931         // variants, otherwise cargo install of various things will
932         // stop working if Vcpkg is installed.
933         if !vcpkg_target.is_static && !env::var_os("VCPKGRS_DYNAMIC").is_some() {
934             return Err(Error::RequiredEnvMissing("VCPKGRS_DYNAMIC".to_owned()));
935         }
936 
937         let mut lib = Library::new(vcpkg_target.is_static, &vcpkg_target.vcpkg_triplet);
938 
939         if self.emit_includes {
940             lib.cargo_metadata.push(format!(
941                 "cargo:include={}",
942                 vcpkg_target.include_path.display()
943             ));
944         }
945         lib.include_paths.push(vcpkg_target.include_path.clone());
946 
947         lib.cargo_metadata.push(format!(
948             "cargo:rustc-link-search=native={}",
949             vcpkg_target
950                 .lib_path
951                 .to_str()
952                 .expect("failed to convert string type")
953         ));
954         lib.link_paths.push(vcpkg_target.lib_path.clone());
955         if !vcpkg_target.is_static {
956             lib.cargo_metadata.push(format!(
957                 "cargo:rustc-link-search=native={}",
958                 vcpkg_target
959                     .bin_path
960                     .to_str()
961                     .expect("failed to convert string type")
962             ));
963             // this path is dropped by recent versions of cargo hence the copies to OUT_DIR below
964             lib.dll_paths.push(vcpkg_target.bin_path.clone());
965         }
966 
967         try!(self.emit_libs(&mut lib, &vcpkg_target));
968 
969         if self.copy_dlls {
970             try!(self.do_dll_copy(&mut lib));
971         }
972 
973         if self.cargo_metadata {
974             for line in &lib.cargo_metadata {
975                 println!("{}", line);
976             }
977         }
978         Ok(lib)
979     }
980 
emit_libs(&mut self, lib: &mut Library, vcpkg_target: &VcpkgTarget) -> Result<(), Error>981     fn emit_libs(&mut self, lib: &mut Library, vcpkg_target: &VcpkgTarget) -> Result<(), Error> {
982         for required_lib in &self.required_libs {
983             // this could use static-nobundle= for static libraries but it is apparently
984             // not necessary to make the distinction for windows-msvc.
985 
986             let link_name = match vcpkg_target.strip_lib_prefix {
987                 true => required_lib.trim_left_matches("lib"),
988                 false => required_lib,
989             };
990 
991             lib.cargo_metadata
992                 .push(format!("cargo:rustc-link-lib={}", link_name));
993 
994             lib.found_names.push(String::from(link_name));
995 
996             // verify that the library exists
997             let mut lib_location = vcpkg_target.lib_path.clone();
998             lib_location.push(required_lib.clone() + "." + &vcpkg_target.lib_suffix);
999 
1000             if !lib_location.exists() {
1001                 return Err(Error::LibNotFound(lib_location.display().to_string()));
1002             }
1003             lib.found_libs.push(lib_location);
1004         }
1005 
1006         if !vcpkg_target.is_static {
1007             for required_dll in &self.required_dlls {
1008                 let mut dll_location = vcpkg_target.bin_path.clone();
1009                 dll_location.push(required_dll.clone() + ".dll");
1010 
1011                 // verify that the DLL exists
1012                 if !dll_location.exists() {
1013                     return Err(Error::LibNotFound(dll_location.display().to_string()));
1014                 }
1015                 lib.found_dlls.push(dll_location);
1016             }
1017         }
1018 
1019         Ok(())
1020     }
1021 
do_dll_copy(&mut self, lib: &mut Library) -> Result<(), Error>1022     fn do_dll_copy(&mut self, lib: &mut Library) -> Result<(), Error> {
1023         if let Some(target_dir) = env::var_os("OUT_DIR") {
1024             if !lib.found_dlls.is_empty() {
1025                 for file in &lib.found_dlls {
1026                     let mut dest_path = Path::new(target_dir.as_os_str()).to_path_buf();
1027                     dest_path.push(Path::new(file.file_name().unwrap()));
1028                     try!(
1029                         fs::copy(file, &dest_path).map_err(|_| Error::LibNotFound(format!(
1030                             "Can't copy file {} to {}",
1031                             file.to_string_lossy(),
1032                             dest_path.to_string_lossy()
1033                         )))
1034                     );
1035                     println!(
1036                         "vcpkg build helper copied {} to {}",
1037                         file.to_string_lossy(),
1038                         dest_path.to_string_lossy()
1039                     );
1040                 }
1041                 lib.cargo_metadata.push(format!(
1042                     "cargo:rustc-link-search=native={}",
1043                     env::var("OUT_DIR").unwrap()
1044                 ));
1045                 // work around https://github.com/rust-lang/cargo/issues/3957
1046                 lib.cargo_metadata.push(format!(
1047                     "cargo:rustc-link-search={}",
1048                     env::var("OUT_DIR").unwrap()
1049                 ));
1050             }
1051         } else {
1052             return Err(Error::LibNotFound("Unable to get OUT_DIR".to_owned()));
1053         }
1054         Ok(())
1055     }
1056 
1057     /// Override the name of the library to look for if it differs from the package name.
1058     ///
1059     /// It should not be necessary to use `lib_name` anymore. Calling `find_package` with a package name
1060     /// will result in the correct library names.
1061     /// This may be called more than once if multiple libs are required.
1062     /// All libs must be found for the probe to succeed. `.probe()` must
1063     /// be run with a different configuration to look for libraries under one of several names.
1064     /// `.libname("ssleay32")` will look for ssleay32.lib and also ssleay32.dll if
1065     /// dynamic linking is selected.
lib_name(&mut self, lib_stem: &str) -> &mut Config1066     pub fn lib_name(&mut self, lib_stem: &str) -> &mut Config {
1067         self.required_libs.push(lib_stem.to_owned());
1068         self.required_dlls.push(lib_stem.to_owned());
1069         self
1070     }
1071 
1072     /// Override the name of the library to look for if it differs from the package name.
1073     ///
1074     /// It should not be necessary to use `lib_names` anymore. Calling `find_package` with a package name
1075     /// will result in the correct library names.
1076     /// This may be called more than once if multiple libs are required.
1077     /// All libs must be found for the probe to succeed. `.probe()` must
1078     /// be run with a different configuration to look for libraries under one of several names.
1079     /// `.lib_names("libcurl_imp","curl")` will look for libcurl_imp.lib and also curl.dll if
1080     /// dynamic linking is selected.
lib_names(&mut self, lib_stem: &str, dll_stem: &str) -> &mut Config1081     pub fn lib_names(&mut self, lib_stem: &str, dll_stem: &str) -> &mut Config {
1082         self.required_libs.push(lib_stem.to_owned());
1083         self.required_dlls.push(dll_stem.to_owned());
1084         self
1085     }
1086 }
1087 
remove_item(cont: &mut Vec<String>, item: &String) -> Option<String>1088 fn remove_item(cont: &mut Vec<String>, item: &String) -> Option<String> {
1089     match cont.iter().position(|x| *x == *item) {
1090         Some(pos) => Some(cont.remove(pos)),
1091         None => None,
1092     }
1093 }
1094 
1095 impl Library {
new(is_static: bool, vcpkg_triplet: &str) -> Library1096     fn new(is_static: bool, vcpkg_triplet: &str) -> Library {
1097         Library {
1098             link_paths: Vec::new(),
1099             dll_paths: Vec::new(),
1100             include_paths: Vec::new(),
1101             cargo_metadata: Vec::new(),
1102             is_static: is_static,
1103             found_dlls: Vec::new(),
1104             found_libs: Vec::new(),
1105             found_names: Vec::new(),
1106             ports: Vec::new(),
1107             vcpkg_triplet: vcpkg_triplet.to_string(),
1108         }
1109     }
1110 }
1111 
envify(name: &str) -> String1112 fn envify(name: &str) -> String {
1113     name.chars()
1114         .map(|c| c.to_ascii_uppercase())
1115         .map(|c| if c == '-' { '_' } else { c })
1116         .collect()
1117 }
1118 
msvc_target() -> Result<TargetTriplet, Error>1119 fn msvc_target() -> Result<TargetTriplet, Error> {
1120     let is_definitely_dynamic = env::var("VCPKGRS_DYNAMIC").is_ok();
1121     let target = env::var("TARGET").unwrap_or(String::new());
1122     let is_static = env::var("CARGO_CFG_TARGET_FEATURE")
1123         .unwrap_or(String::new()) // rustc 1.10
1124         .contains("crt-static");
1125     if target == "x86_64-apple-darwin" {
1126         Ok(TargetTriplet {
1127             triplet: "x64-osx".into(),
1128             is_static: true,
1129             lib_suffix: "a".into(),
1130             strip_lib_prefix: true,
1131         })
1132     } else if target == "aarch64-apple-darwin" {
1133         Ok(TargetTriplet {
1134             triplet: "arm64-osx".into(),
1135             is_static: true,
1136             lib_suffix: "a".into(),
1137             strip_lib_prefix: true,
1138         })
1139     } else if target == "x86_64-unknown-linux-gnu" {
1140         Ok(TargetTriplet {
1141             triplet: "x64-linux".into(),
1142             is_static: true,
1143             lib_suffix: "a".into(),
1144             strip_lib_prefix: true,
1145         })
1146     } else if target == "aarch64-apple-ios" {
1147             Ok(TargetTriplet {
1148                 triplet: "arm64-ios".into(),
1149                 is_static: true,
1150                 lib_suffix: "a".into(),
1151                 strip_lib_prefix: true,
1152             })
1153     } else if !target.contains("-pc-windows-msvc") {
1154         Err(Error::NotMSVC)
1155     } else if target.starts_with("x86_64-") {
1156         if is_static {
1157             Ok(TargetTriplet {
1158                 triplet: "x64-windows-static".into(),
1159                 is_static: true,
1160                 lib_suffix: "lib".into(),
1161                 strip_lib_prefix: false,
1162             })
1163         } else if is_definitely_dynamic {
1164             Ok(TargetTriplet {
1165                 triplet: "x64-windows".into(),
1166                 is_static: false,
1167                 lib_suffix: "lib".into(),
1168                 strip_lib_prefix: false,
1169             })
1170         } else {
1171             Ok(TargetTriplet {
1172                 triplet: "x64-windows-static-md".into(),
1173                 is_static: true,
1174                 lib_suffix: "lib".into(),
1175                 strip_lib_prefix: false,
1176             })
1177         }
1178     } else {
1179         // everything else is x86
1180         if is_static {
1181             Ok(TargetTriplet {
1182                 triplet: "x86-windows-static".into(),
1183                 is_static: true,
1184                 lib_suffix: "lib".into(),
1185                 strip_lib_prefix: false,
1186             })
1187         } else if is_definitely_dynamic {
1188             Ok(TargetTriplet {
1189                 triplet: "x86-windows".into(),
1190                 is_static: false,
1191                 lib_suffix: "lib".into(),
1192                 strip_lib_prefix: false,
1193             })
1194         } else {
1195             Ok(TargetTriplet {
1196                 triplet: "x86-windows-static-md".into(),
1197                 is_static: true,
1198                 lib_suffix: "lib".into(),
1199                 strip_lib_prefix: false,
1200             })
1201         }
1202     }
1203 }
1204 
1205 #[cfg(test)]
1206 mod tests {
1207 
1208     extern crate tempdir;
1209 
1210     use super::*;
1211     use std::env;
1212     use std::sync::Mutex;
1213 
1214     lazy_static! {
1215         static ref LOCK: Mutex<()> = Mutex::new(());
1216     }
1217 
1218     #[test]
do_nothing_for_unsupported_target()1219     fn do_nothing_for_unsupported_target() {
1220         let _g = LOCK.lock();
1221         env::set_var("VCPKG_ROOT", "/");
1222         env::set_var("TARGET", "x86_64-pc-windows-gnu");
1223         assert!(match ::probe_package("foo") {
1224             Err(Error::NotMSVC) => true,
1225             _ => false,
1226         });
1227 
1228         env::set_var("TARGET", "x86_64-pc-windows-gnu");
1229         assert_eq!(env::var("TARGET"), Ok("x86_64-pc-windows-gnu".to_string()));
1230         assert!(match ::probe_package("foo") {
1231             Err(Error::NotMSVC) => true,
1232             _ => false,
1233         });
1234         env::remove_var("TARGET");
1235         env::remove_var("VCPKG_ROOT");
1236     }
1237 
1238     #[test]
do_nothing_for_bailout_variables_set()1239     fn do_nothing_for_bailout_variables_set() {
1240         let _g = LOCK.lock();
1241         env::set_var("VCPKG_ROOT", "/");
1242         env::set_var("TARGET", "x86_64-pc-windows-msvc");
1243 
1244         for &var in &[
1245             "VCPKGRS_DISABLE",
1246             "VCPKGRS_NO_FOO",
1247             "FOO_NO_VCPKG",
1248             "NO_VCPKG",
1249         ] {
1250             env::set_var(var, "1");
1251             assert!(match ::probe_package("foo") {
1252                 Err(Error::DisabledByEnv(ref v)) if v == var => true,
1253                 _ => false,
1254             });
1255             env::remove_var(var);
1256         }
1257         env::remove_var("TARGET");
1258         env::remove_var("VCPKG_ROOT");
1259     }
1260 
1261     // these tests are good but are leaning on a real vcpkg installation
1262 
1263     // #[test]
1264     // fn default_build_refuses_dynamic() {
1265     //     let _g = LOCK.lock();
1266     //     clean_env();
1267     //     env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("no-status"));
1268     //     env::set_var("TARGET", "x86_64-pc-windows-msvc");
1269     //     println!("Result is {:?}", ::find_package("libmysql"));
1270     //     assert!(match ::find_package("libmysql") {
1271     //         Err(Error::RequiredEnvMissing(ref v)) if v == "VCPKGRS_DYNAMIC" => true,
1272     //         _ => false,
1273     //     });
1274     //     clean_env();
1275     // }
1276 
1277     #[test]
static_build_finds_lib()1278     fn static_build_finds_lib() {
1279         let _g = LOCK.lock();
1280         clean_env();
1281         env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("normalized"));
1282         env::set_var("TARGET", "x86_64-pc-windows-msvc");
1283         let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap();
1284         env::set_var("OUT_DIR", tmp_dir.path());
1285 
1286         // CARGO_CFG_TARGET_FEATURE is set in response to
1287         // RUSTFLAGS=-Ctarget-feature=+crt-static. It would
1288         //  be nice to test that also.
1289         env::set_var("CARGO_CFG_TARGET_FEATURE", "crt-static");
1290         println!("Result is {:?}", ::find_package("libmysql"));
1291         assert!(match ::find_package("libmysql") {
1292             Ok(_) => true,
1293             _ => false,
1294         });
1295         clean_env();
1296     }
1297 
1298     #[test]
dynamic_build_finds_lib()1299     fn dynamic_build_finds_lib() {
1300         let _g = LOCK.lock();
1301         clean_env();
1302         env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("no-status"));
1303         env::set_var("TARGET", "x86_64-pc-windows-msvc");
1304         env::set_var("VCPKGRS_DYNAMIC", "1");
1305         let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap();
1306         env::set_var("OUT_DIR", tmp_dir.path());
1307 
1308         println!("Result is {:?}", ::find_package("libmysql"));
1309         assert!(match ::find_package("libmysql") {
1310             Ok(_) => true,
1311             _ => false,
1312         });
1313         clean_env();
1314     }
1315 
1316     #[test]
handle_multiline_description()1317     fn handle_multiline_description() {
1318         let _g = LOCK.lock();
1319         clean_env();
1320         env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("multiline-description"));
1321         env::set_var("TARGET", "i686-pc-windows-msvc");
1322         env::set_var("VCPKGRS_DYNAMIC", "1");
1323         let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap();
1324         env::set_var("OUT_DIR", tmp_dir.path());
1325 
1326         println!("Result is {:?}", ::find_package("graphite2"));
1327         assert!(match ::find_package("graphite2") {
1328             Ok(_) => true,
1329             _ => false,
1330         });
1331         clean_env();
1332     }
1333 
1334     #[test]
link_libs_required_by_optional_features()1335     fn link_libs_required_by_optional_features() {
1336         let _g = LOCK.lock();
1337         clean_env();
1338         env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("normalized"));
1339         env::set_var("TARGET", "i686-pc-windows-msvc");
1340         env::set_var("VCPKGRS_DYNAMIC", "1");
1341         let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap();
1342         env::set_var("OUT_DIR", tmp_dir.path());
1343 
1344         println!("Result is {:?}", ::find_package("harfbuzz"));
1345         assert!(match ::find_package("harfbuzz") {
1346             Ok(lib) => lib
1347                 .cargo_metadata
1348                 .iter()
1349                 .find(|&x| x == "cargo:rustc-link-lib=icuuc")
1350                 .is_some(),
1351             _ => false,
1352         });
1353         clean_env();
1354     }
1355 
1356     #[test]
link_lib_name_is_correct()1357     fn link_lib_name_is_correct() {
1358         let _g = LOCK.lock();
1359 
1360         for target in &[
1361             "x86_64-apple-darwin",
1362             "i686-pc-windows-msvc",
1363             //      "x86_64-pc-windows-msvc",
1364             //    "x86_64-unknown-linux-gnu",
1365         ] {
1366             clean_env();
1367             env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("normalized"));
1368             env::set_var("TARGET", target);
1369             env::set_var("VCPKGRS_DYNAMIC", "1");
1370             let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap();
1371             env::set_var("OUT_DIR", tmp_dir.path());
1372 
1373             println!("Result is {:?}", ::find_package("harfbuzz"));
1374             assert!(match ::find_package("harfbuzz") {
1375                 Ok(lib) => lib
1376                     .cargo_metadata
1377                     .iter()
1378                     .find(|&x| x == "cargo:rustc-link-lib=harfbuzz")
1379                     .is_some(),
1380                 _ => false,
1381             });
1382             clean_env();
1383         }
1384     }
1385 
1386     #[test]
link_dependencies_after_port()1387     fn link_dependencies_after_port() {
1388         let _g = LOCK.lock();
1389         clean_env();
1390         env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("normalized"));
1391         env::set_var("TARGET", "i686-pc-windows-msvc");
1392         env::set_var("VCPKGRS_DYNAMIC", "1");
1393         let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap();
1394         env::set_var("OUT_DIR", tmp_dir.path());
1395 
1396         let lib = ::find_package("harfbuzz").unwrap();
1397 
1398         check_before(&lib, "freetype", "zlib");
1399         check_before(&lib, "freetype", "bzip2");
1400         check_before(&lib, "freetype", "libpng");
1401         check_before(&lib, "harfbuzz", "freetype");
1402         check_before(&lib, "harfbuzz", "ragel");
1403         check_before(&lib, "libpng", "zlib");
1404 
1405         clean_env();
1406 
1407         fn check_before(lib: &Library, earlier: &str, later: &str) {
1408             match (
1409                 lib.ports.iter().position(|x| *x == *earlier),
1410                 lib.ports.iter().position(|x| *x == *later),
1411             ) {
1412                 (Some(earlier_pos), Some(later_pos)) if earlier_pos < later_pos => {
1413                     // ok
1414                 }
1415                 _ => {
1416                     println!(
1417                         "earlier: {}, later: {}\nLibrary found: {:#?}",
1418                         earlier, later, lib
1419                     );
1420                     panic!();
1421                 }
1422             }
1423         }
1424     }
1425 
1426     #[test]
custom_target_triplet_in_config()1427     fn custom_target_triplet_in_config() {
1428         let _g = LOCK.lock();
1429 
1430         clean_env();
1431         env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("normalized"));
1432         env::set_var("TARGET", "aarch64-apple-ios");
1433         env::set_var("VCPKGRS_DYNAMIC", "1");
1434         let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap();
1435         env::set_var("OUT_DIR", tmp_dir.path());
1436 
1437         let harfbuzz = ::Config::new()
1438             // For the sake of testing, force this build to try to
1439             // link to the arm64-osx libraries in preference to the
1440             // default of arm64-ios.
1441             .target_triplet("x64-osx")
1442             .find_package("harfbuzz");
1443         println!("Result with specifying target triplet is {:?}", &harfbuzz);
1444         let harfbuzz = harfbuzz.unwrap();
1445         assert_eq!(harfbuzz.vcpkg_triplet, "x64-osx");
1446         clean_env();
1447     }
1448 
1449     #[test]
custom_target_triplet_by_env_no_default()1450     fn custom_target_triplet_by_env_no_default() {
1451         let _g = LOCK.lock();
1452 
1453         clean_env();
1454         env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("normalized"));
1455         env::set_var("TARGET", "aarch64-apple-doesnotexist");
1456         env::set_var("VCPKGRS_DYNAMIC", "1");
1457         let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap();
1458         env::set_var("OUT_DIR", tmp_dir.path());
1459 
1460         let harfbuzz = ::find_package("harfbuzz");
1461         println!("Result with inference is {:?}", &harfbuzz);
1462         assert!(harfbuzz.is_err());
1463 
1464         env::set_var("VCPKGRS_TRIPLET", "x64-osx");
1465         let harfbuzz = ::find_package("harfbuzz").unwrap();
1466         println!("Result with setting VCPKGRS_TRIPLET is {:?}", &harfbuzz);
1467         assert_eq!(harfbuzz.vcpkg_triplet, "x64-osx");
1468         clean_env();
1469     }
1470 
1471     #[test]
custom_target_triplet_by_env_with_default()1472     fn custom_target_triplet_by_env_with_default() {
1473         let _g = LOCK.lock();
1474 
1475         clean_env();
1476         env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("normalized"));
1477         env::set_var("TARGET", "aarch64-apple-ios");
1478         env::set_var("VCPKGRS_DYNAMIC", "1");
1479         let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap();
1480         env::set_var("OUT_DIR", tmp_dir.path());
1481 
1482         let harfbuzz = ::find_package("harfbuzz").unwrap();
1483         println!("Result with inference is {:?}", &harfbuzz);
1484         assert_eq!(harfbuzz.vcpkg_triplet, "arm64-ios");
1485 
1486         env::set_var("VCPKGRS_TRIPLET", "x64-osx");
1487         let harfbuzz = ::find_package("harfbuzz").unwrap();
1488         println!("Result with setting VCPKGRS_TRIPLET is {:?}", &harfbuzz);
1489         assert_eq!(harfbuzz.vcpkg_triplet, "x64-osx");
1490         clean_env();
1491     }
1492 
1493     // #[test]
1494     // fn dynamic_build_package_specific_bailout() {
1495     //     clean_env();
1496     //     env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("no-status"));
1497     //     env::set_var("TARGET", "x86_64-pc-windows-msvc");
1498     //     env::set_var("VCPKGRS_DYNAMIC", "1");
1499     //     env::set_var("VCPKGRS_NO_LIBMYSQL", "1");
1500 
1501     //     println!("Result is {:?}", ::find_package("libmysql"));
1502     //     assert!(match ::find_package("libmysql") {
1503     //         Err(Error::DisabledByEnv(ref v)) if v == "VCPKGRS_NO_LIBMYSQL" => true,
1504     //         _ => false,
1505     //     });
1506     //     clean_env();
1507     // }
1508 
1509     // #[test]
1510     // fn dynamic_build_global_bailout() {
1511     //     clean_env();
1512     //     env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("no-status"));
1513     //     env::set_var("TARGET", "x86_64-pc-windows-msvc");
1514     //     env::set_var("VCPKGRS_DYNAMIC", "1");
1515     //     env::set_var("VCPKGRS_DISABLE", "1");
1516 
1517     //     println!("Result is {:?}", ::find_package("libmysql"));
1518     //     assert!(match ::find_package("libmysql") {
1519     //         Err(Error::DisabledByEnv(ref v)) if v == "VCPKGRS_DISABLE" => true,
1520     //         _ => false,
1521     //     });
1522     //     clean_env();
1523     // }
1524 
clean_env()1525     fn clean_env() {
1526         env::remove_var("TARGET");
1527         env::remove_var("VCPKG_ROOT");
1528         env::remove_var("VCPKGRS_DYNAMIC");
1529         env::remove_var("RUSTFLAGS");
1530         env::remove_var("CARGO_CFG_TARGET_FEATURE");
1531         env::remove_var("VCPKGRS_DISABLE");
1532         env::remove_var("VCPKGRS_NO_LIBMYSQL");
1533         env::remove_var("VCPKGRS_TRIPLET");
1534     }
1535 
1536     // path to a to vcpkg installation to test against
vcpkg_test_tree_loc(name: &str) -> PathBuf1537     fn vcpkg_test_tree_loc(name: &str) -> PathBuf {
1538         let mut path = PathBuf::new();
1539         path.push(env::var("CARGO_MANIFEST_DIR").unwrap());
1540         path.pop();
1541         path.push("test-data");
1542         path.push(name);
1543         path
1544     }
1545 }
1546