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