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]
99extern crate lazy_static;
100
101#[allow(unused_imports)]
102use std::ascii::AsciiExt;
103
104use std::collections::BTreeMap;
105use std::collections::HashMap;
106use std::env;
107use std::error;
108use std::ffi::OsStr;
109use std::fmt;
110use std::fs::{self, File};
111use std::io::{BufRead, BufReader, Read};
112use std::path::{Path, PathBuf};
113
114/// Configuration options for finding packages, setting up the tree and emitting metadata to cargo
115#[derive(Default)]
116pub 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)]
140pub 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)]
173struct TargetTriplet {
174 triplet: String,
175 is_static: bool,
176 lib_suffix: String,
177 strip_lib_prefix: bool,
178}
179
180impl<S: AsRef<str>> From<S> for TargetTriplet {
181 fn from(triplet: S) -> TargetTriplet {
182 let triplet: &str = 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?
202pub 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
227impl error::Error for Error {
228 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
240 fn cause(&self) -> Option<&error::Error> {
241 match *self {
242 // Error::Command { ref cause, .. } => Some(cause),
243 _ => None,
244 }
245 }
246}
247
248impl fmt::Display for Error {
249 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
250 match *self {
251 Error::DisabledByEnv(ref name: &String) => write!(f, "Aborted because {} is set", name),
252 Error::RequiredEnvMissing(ref name: &String) => 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: &String) => write!(f, "Could not find Vcpkg tree: {}", detail),
258 Error::LibNotFound(ref detail: &String) => {
259 write!(f, "Could not find library in Vcpkg tree {}", detail)
260 }
261 Error::VcpkgInstallation(ref detail: &String) => 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)]
273pub fn probe_package(name: &str) -> Result<Library, Error> {
274 Config::new().probe(port_name: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.
284pub fn find_package(package: &str) -> Result<Library, Error> {
285 Config::new().find_package(port_name:package)
286}
287
288/// Find the vcpkg root
289#[doc(hidden)]
290pub 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
375fn validate_vcpkg_root(path: &PathBuf) -> Result<(), Error> {
376 let mut vcpkg_root_path: PathBuf = path.clone();
377 vcpkg_root_path.push(path:".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
389fn find_vcpkg_target(cfg: &Config, target_triplet: &TargetTriplet) -> Result<VcpkgTarget, Error> {
390 let vcpkg_root: PathBuf = try!(find_vcpkg_root(&cfg));
391 try!(validate_vcpkg_root(&vcpkg_root));
392
393 let mut base: PathBuf = vcpkg_root.clone();
394 base.push(path:"installed");
395 let status_path: PathBuf = base.join(path:"vcpkg");
396
397 base.push(&target_triplet.triplet);
398
399 let lib_path: PathBuf = base.join(path:"lib");
400 let bin_path: PathBuf = base.join(path:"bin");
401 let include_path: PathBuf = base.join(path:"include");
402 let packages_path: PathBuf = vcpkg_root.join(path:"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)]
416struct 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}
424impl PcFile {
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 }
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)]
504struct PcFiles {
505 files: HashMap<String, PcFile>,
506}
507impl PcFiles {
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.
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.
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)]
589struct 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
600fn 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
666fn 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
708fn 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
822struct 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
836impl VcpkgTarget {
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: &str| 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: &str| s.to_owned())
845 }
846 }
847}
848
849impl Config {
850 pub fn new() -> Config {
851 Config {
852 cargo_metadata: true,
853 copy_dlls: true,
854 ..Default::default()
855 }
856 }
857
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.
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`.
1041 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`.
1047 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`.
1054 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
1061 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.
1072 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)]
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
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
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.
1258 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.
1273 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
1280fn remove_item(cont: &mut Vec<String>, item: &String) -> Option<String> {
1281 match cont.iter().position(|x: &String| *x == *item) {
1282 Some(pos: usize) => Some(cont.remove(index:pos)),
1283 None => None,
1284 }
1285}
1286
1287impl Library {
1288 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
1304fn envify(name: &str) -> String {
1305 nameimpl Iterator.chars()
1306 .map(|c: char| c.to_ascii_uppercase())
1307 .map(|c: char| if c == '-' { '_' } else { c })
1308 .collect()
1309}
1310
1311fn 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)]
1421mod 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]
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]
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]
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]
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]
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]
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]
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]
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]
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]
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]
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]
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
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
1939 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