1 //! A build dependency for Cargo libraries to find libraries in a
2 //! [Vcpkg](https://github.com/Microsoft/vcpkg) tree. From a Vcpkg package name
3 //! this build helper will emit cargo metadata to link it and it's dependencies
4 //! (excluding system libraries, which it cannot derive).
5 //!
6 //! **Note:** You must set one of `RUSTFLAGS=-Ctarget-feature=+crt-static` or
7 //! `VCPKGRS_DYNAMIC=1` in your environment or the vcpkg-rs helper
8 //! will not find any libraries. If `VCPKGRS_DYNAMIC` is set, `cargo install` will
9 //! generate dynamically linked binaries, in which case you will have to arrange for
10 //! dlls from your Vcpkg installation to be available in your path.
11 //!
12 //! The simplest possible usage looks like this :-
13 //!
14 //! ```rust,no_run
15 //! vcpkg::find_package("libssh2").unwrap();
16 //! ```
17 //!
18 //! The cargo metadata that is emitted can be changed like this :-
19 //!
20 //! ```rust,no_run
21 //! vcpkg::Config::new()
22 //! .emit_includes(true)
23 //! .find_package("zlib").unwrap();
24 //! ```
25 //!
26 //! If the search was successful all appropriate Cargo metadata will be printed
27 //! to stdout.
28 //!
29 //! The decision to choose static variants of libraries is driven by adding
30 //! `RUSTFLAGS=-Ctarget-feature=+crt-static` to the environment. This requires
31 //! at least Rust 1.19.
32 //!
33 //! A number of environment variables are available to globally configure which
34 //! libraries are selected.
35 //!
36 //! * `VCPKG_ROOT` - Set the directory to look in for a vcpkg installation. If
37 //! it is not set, vcpkg will use the user-wide installation if one has been
38 //! set up with `vcpkg integrate install`
39 //!
40 //! * `VCPKGRS_NO_FOO` - if set, vcpkg-rs will not attempt to find the
41 //! library named `foo`.
42 //!
43 //! * `VCPKGRS_DISABLE` - if set, vcpkg-rs will not attempt to find any libraries.
44 //!
45 //! * `VCPKGRS_DYNAMIC` - if set, vcpkg-rs will link to DLL builds of ports.
46 //!
47 //! There is a companion crate `vcpkg_cli` that allows testing of environment
48 //! and flag combinations.
49 //!
50 //! ```Batchfile
51 //! C:\src> vcpkg_cli probe -l static mysqlclient
52 //! Found library mysqlclient
53 //! Include paths:
54 //! C:\src\[..]\vcpkg\installed\x64-windows-static\include
55 //! Library paths:
56 //! C:\src\[..]\vcpkg\installed\x64-windows-static\lib
57 //! Cargo metadata:
58 //! cargo:rustc-link-search=native=C:\src\[..]\vcpkg\installed\x64-windows-static\lib
59 //! cargo:rustc-link-lib=static=mysqlclient
60 //! ```
61
62 // The CI will test vcpkg-rs on 1.10 because at this point rust-openssl's
63 // openssl-sys is backward compatible that far. (Actually, the oldest release
64 // crate openssl version 0.10 seems to build against is now Rust 1.24.1?)
65 #![allow(deprecated)]
66
67 #[cfg(test)]
68 #[macro_use]
69 extern crate lazy_static;
70
71 #[allow(unused_imports)]
72 use std::ascii::AsciiExt;
73
74 use std::collections::BTreeMap;
75 use std::env;
76 use std::error;
77 use std::ffi::OsStr;
78 use std::fmt;
79 use std::fs::{self, File};
80 use std::io::{BufRead, BufReader};
81 use std::path::{Path, PathBuf};
82
83 #[derive(Default)]
84 pub struct Config {
85 /// should the cargo metadata actually be emitted
86 cargo_metadata: bool,
87
88 /// should cargo:include= metadata be emitted (defaults to false)
89 emit_includes: bool,
90
91 /// .lib/.a files that must be be found for probing to be considered successful
92 required_libs: Vec<String>,
93
94 /// .dlls that must be be found for probing to be considered successful
95 required_dlls: Vec<String>,
96
97 /// should DLLs be copies to OUT_DIR?
98 copy_dlls: bool,
99 }
100
101 /// Details of a package that was found
102 #[derive(Debug)]
103 pub struct Library {
104 /// Paths for the linker to search for static or import libraries
105 pub link_paths: Vec<PathBuf>,
106
107 /// Paths to search at runtme to find DLLs
108 pub dll_paths: Vec<PathBuf>,
109
110 /// Paths to search for
111 pub include_paths: Vec<PathBuf>,
112
113 /// cargo: metadata lines
114 pub cargo_metadata: Vec<String>,
115
116 /// libraries found are static
117 pub is_static: bool,
118
119 /// DLLs found
120 pub found_dlls: Vec<PathBuf>,
121
122 /// static libs or import libs found
123 pub found_libs: Vec<PathBuf>,
124
125 /// ports that are providing the libraries to link to, in port link order
126 pub ports: Vec<String>,
127 }
128
129 enum MSVCTarget {
130 X86Windows,
131 X64Windows,
132 X64Linux,
133 X64MacOS,
134 }
135
136 impl fmt::Display for MSVCTarget {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result137 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
138 match *self {
139 MSVCTarget::X86Windows => write!(f, "x86-windows"),
140 MSVCTarget::X64Windows => write!(f, "x64-windows"),
141 MSVCTarget::X64Linux => write!(f, "x64-linux"),
142 MSVCTarget::X64MacOS => write!(f, "x64-osx"),
143 }
144 }
145 }
146
147 #[derive(Debug)] // need Display?
148 pub enum Error {
149 /// Aborted because of a `VCPKGRS_NO_*` environment variable.
150 ///
151 /// Contains the name of the responsible environment variable.
152 DisabledByEnv(String),
153
154 /// Aborted because a required environment variable was not set.
155 RequiredEnvMissing(String),
156
157 /// On Windows, only MSVC ABI is supported
158 NotMSVC,
159
160 /// Can't find a vcpkg tree
161 VcpkgNotFound(String),
162
163 /// Library not found in vcpkg tree
164 LibNotFound(String),
165
166 /// Could not understand vcpkg installation
167 VcpkgInstallation(String),
168
169 #[doc(hidden)]
170 __Nonexhaustive,
171 }
172
173 impl error::Error for Error {
description(&self) -> &str174 fn description(&self) -> &str {
175 match *self {
176 Error::DisabledByEnv(_) => "vcpkg-rs requested to be aborted",
177 Error::RequiredEnvMissing(_) => "a required env setting is missing",
178 Error::NotMSVC => "vcpkg-rs only can only find libraries for MSVC ABI 64 bit builds",
179 Error::VcpkgNotFound(_) => "could not find Vcpkg tree",
180 Error::LibNotFound(_) => "could not find library in Vcpkg tree",
181 Error::VcpkgInstallation(_) => "could not look up details of packages in vcpkg tree",
182 Error::__Nonexhaustive => panic!(),
183 }
184 }
185
cause(&self) -> Option<&error::Error>186 fn cause(&self) -> Option<&error::Error> {
187 match *self {
188 // Error::Command { ref cause, .. } => Some(cause),
189 _ => None,
190 }
191 }
192 }
193
194 impl fmt::Display for Error {
fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>195 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
196 match *self {
197 Error::DisabledByEnv(ref name) => write!(f, "Aborted because {} is set", name),
198 Error::RequiredEnvMissing(ref name) => write!(f, "Aborted because {} is not set", name),
199 Error::NotMSVC => write!(
200 f,
201 "the vcpkg-rs Vcpkg build helper can only find libraries built for the MSVC ABI."
202 ),
203 Error::VcpkgNotFound(ref detail) => write!(f, "Could not find Vcpkg tree: {}", detail),
204 Error::LibNotFound(ref detail) => {
205 write!(f, "Could not find library in Vcpkg tree {}", detail)
206 }
207 Error::VcpkgInstallation(ref detail) => write!(
208 f,
209 "Could not look up details of packages in vcpkg tree {}",
210 detail
211 ),
212 Error::__Nonexhaustive => panic!(),
213 }
214 }
215 }
216
217 /// Deprecated in favor of the find_package function
218 #[doc(hidden)]
probe_package(name: &str) -> Result<Library, Error>219 pub fn probe_package(name: &str) -> Result<Library, Error> {
220 Config::new().probe(name)
221 }
222
223 /// Find the package `package` in a Vcpkg tree.
224 ///
225 /// Emits cargo metadata to link to libraries provided by the Vcpkg package/port
226 /// named, and any (non-system) libraries that they depend on.
227 ///
228 /// This will select the architecture and linkage based on environment
229 /// variables and build flags as described in the module docs.
find_package(package: &str) -> Result<Library, Error>230 pub fn find_package(package: &str) -> Result<Library, Error> {
231 Config::new().find_package(package)
232 }
233
find_vcpkg_root() -> Result<PathBuf, Error>234 fn find_vcpkg_root() -> Result<PathBuf, Error> {
235 // prefer the setting from the environment is there is one
236 if let Some(path) = env::var_os("VCPKG_ROOT") {
237 return Ok(PathBuf::from(path));
238 }
239
240 // see if there is a per-user vcpkg tree that has been integrated into msbuild
241 // using `vcpkg integrate install`
242 let local_app_data = try!(env::var("LOCALAPPDATA").map_err(|_| Error::VcpkgNotFound(
243 "Failed to read either VCPKG_ROOT or LOCALAPPDATA environment variables".to_string()
244 ))); // not present or can't utf8
245 let vcpkg_user_targets_path = Path::new(local_app_data.as_str())
246 .join("vcpkg")
247 .join("vcpkg.user.targets");
248
249 let file = try!(File::open(vcpkg_user_targets_path.clone()).map_err(|_| {
250 Error::VcpkgNotFound(
251 "No vcpkg.user.targets found. Set the VCPKG_ROOT environment \
252 variable or run 'vcpkg integrate install'"
253 .to_string(),
254 )
255 }));
256 let file = BufReader::new(&file);
257
258 for line in file.lines() {
259 let line = try!(line.map_err(|_| Error::VcpkgNotFound(format!(
260 "Parsing of {} failed.",
261 vcpkg_user_targets_path.to_string_lossy().to_owned()
262 ))));
263 let mut split = line.split("Project=\"");
264 split.next(); // eat anything before Project="
265 if let Some(found) = split.next() {
266 // " is illegal in a Windows pathname
267 if let Some(found) = found.split_terminator('"').next() {
268 let mut vcpkg_root = PathBuf::from(found);
269 if !(vcpkg_root.pop() && vcpkg_root.pop() && vcpkg_root.pop() && vcpkg_root.pop()) {
270 return Err(Error::VcpkgNotFound(format!(
271 "Could not find vcpkg root above {}",
272 found
273 )));
274 }
275 return Ok(vcpkg_root);
276 }
277 }
278 }
279
280 Err(Error::VcpkgNotFound(format!(
281 "Project location not found parsing {}.",
282 vcpkg_user_targets_path.to_string_lossy().to_owned()
283 )))
284 }
285
validate_vcpkg_root(path: &PathBuf) -> Result<(), Error>286 fn validate_vcpkg_root(path: &PathBuf) -> Result<(), Error> {
287 let mut vcpkg_root_path = path.clone();
288 vcpkg_root_path.push(".vcpkg-root");
289
290 if vcpkg_root_path.exists() {
291 Ok(())
292 } else {
293 Err(Error::VcpkgNotFound(format!(
294 "Could not find Vcpkg root at {}",
295 vcpkg_root_path.to_string_lossy()
296 )))
297 }
298 }
299
find_vcpkg_target(msvc_target: &MSVCTarget) -> Result<VcpkgTarget, Error>300 fn find_vcpkg_target(msvc_target: &MSVCTarget) -> Result<VcpkgTarget, Error> {
301 let vcpkg_root = try!(find_vcpkg_root());
302 try!(validate_vcpkg_root(&vcpkg_root));
303
304 let (static_lib, static_appendage, lib_suffix, strip_lib_prefix) = match msvc_target {
305 &MSVCTarget::X64Windows | &MSVCTarget::X86Windows => {
306 let static_lib = env::var("CARGO_CFG_TARGET_FEATURE")
307 .unwrap_or(String::new()) // rustc 1.10
308 .contains("crt-static");
309 let static_appendage = if static_lib { "-static" } else { "" };
310 (static_lib, static_appendage, "lib", false)
311 }
312 _ => (true, "", "a", true),
313 };
314
315 let mut base = vcpkg_root;
316 base.push("installed");
317 let status_path = base.join("vcpkg");
318
319 let vcpkg_triple = format!("{}{}", msvc_target.to_string(), static_appendage);
320 base.push(&vcpkg_triple);
321
322 let lib_path = base.join("lib");
323 let bin_path = base.join("bin");
324 let include_path = base.join("include");
325
326 Ok(VcpkgTarget {
327 vcpkg_triple: vcpkg_triple,
328 lib_path: lib_path,
329 bin_path: bin_path,
330 include_path: include_path,
331 is_static: static_lib,
332 status_path: status_path,
333 lib_suffix: lib_suffix.to_owned(),
334 strip_lib_prefix: strip_lib_prefix,
335 })
336 }
337
338 #[derive(Clone, Debug)]
339 struct Port {
340 // dlls if any
341 dlls: Vec<String>,
342
343 // libs (static or import)
344 libs: Vec<String>,
345
346 // ports that this port depends on
347 deps: Vec<String>,
348 }
349
load_port_manifest( path: &PathBuf, port: &str, version: &str, vcpkg_target: &VcpkgTarget, ) -> Result<(Vec<String>, Vec<String>), Error>350 fn load_port_manifest(
351 path: &PathBuf,
352 port: &str,
353 version: &str,
354 vcpkg_target: &VcpkgTarget,
355 ) -> Result<(Vec<String>, Vec<String>), Error> {
356 let manifest_file = path.join("info").join(format!(
357 "{}_{}_{}.list",
358 port, version, vcpkg_target.vcpkg_triple
359 ));
360
361 let mut dlls = Vec::new();
362 let mut libs = Vec::new();
363
364 let f = try!(
365 File::open(&manifest_file).map_err(|_| Error::VcpkgInstallation(format!(
366 "Could not open port manifest file {}",
367 manifest_file.display()
368 )))
369 );
370
371 let file = BufReader::new(&f);
372
373 let dll_prefix = Path::new(&vcpkg_target.vcpkg_triple).join("bin");
374 let lib_prefix = Path::new(&vcpkg_target.vcpkg_triple).join("lib");
375
376 for line in file.lines() {
377 let line = line.unwrap();
378
379 let file_path = Path::new(&line);
380
381 if let Ok(dll) = file_path.strip_prefix(&dll_prefix) {
382 if dll.extension() == Some(OsStr::new("dll"))
383 && dll.components().collect::<Vec<_>>().len() == 1
384 {
385 // match "mylib.dll" but not "debug/mylib.dll" or "manual_link/mylib.dll"
386
387 dll.to_str().map(|s| dlls.push(s.to_owned()));
388 }
389 } else if let Ok(lib) = file_path.strip_prefix(&lib_prefix) {
390 if lib.extension() == Some(OsStr::new(&vcpkg_target.lib_suffix))
391 && lib.components().collect::<Vec<_>>().len() == 1
392 {
393 if let Some(lib) = vcpkg_target.link_name_for_lib(lib) {
394 libs.push(lib);
395 }
396 }
397 }
398 }
399
400 Ok((dlls, libs))
401 }
402
403 // 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>404 fn load_port_file(
405 filename: &PathBuf,
406 port_info: &mut Vec<BTreeMap<String, String>>,
407 ) -> Result<(), Error> {
408 let f = try!(
409 File::open(&filename).map_err(|e| Error::VcpkgInstallation(format!(
410 "Could not open status file at {}: {}",
411 filename.display(),
412 e
413 )))
414 );
415 let file = BufReader::new(&f);
416 let mut current: BTreeMap<String, String> = BTreeMap::new();
417 for line in file.lines() {
418 let line = line.unwrap();
419 let parts = line.splitn(2, ": ").clone().collect::<Vec<_>>();
420 if parts.len() == 2 {
421 // a key: value line
422 current.insert(parts[0].trim().into(), parts[1].trim().into());
423 } else if line.len() == 0 {
424 // end of section
425 port_info.push(current.clone());
426 current.clear();
427 } else {
428 // ignore all extension lines of the form
429 //
430 // Description: a package with a
431 // very long description
432 //
433 // the description key is not used so this is harmless but
434 // this will eat extension lines for any multiline key which
435 // could become an issue in future
436 }
437 }
438
439 if !current.is_empty() {
440 port_info.push(current);
441 }
442
443 Ok(())
444 }
445
load_ports(target: &VcpkgTarget) -> Result<BTreeMap<String, Port>, Error>446 fn load_ports(target: &VcpkgTarget) -> Result<BTreeMap<String, Port>, Error> {
447 let mut ports: BTreeMap<String, Port> = BTreeMap::new();
448
449 let mut port_info: Vec<BTreeMap<String, String>> = Vec::new();
450
451 // load the main status file. It is not an error if this file does not
452 // exist. If the only command that has been run in a Vcpkg installation
453 // is a single `vcpkg install package` then there will likely be no
454 // status file, only incremental updates. This is the typical case when
455 // running in a CI environment.
456 let status_filename = target.status_path.join("status");
457 load_port_file(&status_filename, &mut port_info).ok();
458
459 // load updates to the status file that have yet to be normalized
460 let status_update_dir = target.status_path.join("updates");
461
462 let paths = try!(
463 fs::read_dir(status_update_dir).map_err(|e| Error::VcpkgInstallation(format!(
464 "could not read status file updates dir: {}",
465 e
466 )))
467 );
468
469 // get all of the paths of the update files into a Vec<PathBuf>
470 let mut paths = try!(paths
471 .map(|rde| rde.map(|de| de.path())) // Result<DirEntry, io::Error> -> Result<PathBuf, io::Error>
472 .collect::<Result<Vec<_>, _>>() // collect into Result<Vec<PathBuf>, io::Error>
473 .map_err(|e| {
474 Error::VcpkgInstallation(format!(
475 "could not read status file update filenames: {}",
476 e
477 ))
478 }));
479
480 // Sort the paths and read them. This could be done directly from the iterator if
481 // read_dir() guarantees that the files will be read in alpha order but that appears
482 // to be unspecified as the underlying operating system calls used are unspecified
483 // https://doc.rust-lang.org/nightly/std/fs/fn.read_dir.html#platform-specific-behavior
484 paths.sort();
485 for path in paths {
486 // println!("Name: {}", path.display());
487 try!(load_port_file(&path, &mut port_info));
488 }
489 //println!("{:#?}", port_info);
490
491 let mut seen_names = BTreeMap::new();
492 for current in &port_info {
493 // store them by name and arch, clobbering older details
494 match (
495 current.get("Package"),
496 current.get("Architecture"),
497 current.get("Feature"),
498 ) {
499 (Some(pkg), Some(arch), feature) => {
500 seen_names.insert((pkg, arch, feature), current);
501 }
502 _ => {}
503 }
504 }
505
506 for (&(name, arch, feature), current) in &seen_names {
507 if **arch == target.vcpkg_triple {
508 let mut deps = if let Some(deps) = current.get("Depends") {
509 deps.split(", ").map(|x| x.to_owned()).collect()
510 } else {
511 Vec::new()
512 };
513
514 if current
515 .get("Status")
516 .unwrap_or(&String::new())
517 .ends_with(" installed")
518 {
519 match (current.get("Version"), feature) {
520 (Some(version), _) => {
521 // this failing here and bailing out causes everything to fail
522 let lib_info = try!(load_port_manifest(
523 &target.status_path,
524 &name,
525 version,
526 &target
527 ));
528 let port = Port {
529 dlls: lib_info.0,
530 libs: lib_info.1,
531 deps: deps,
532 };
533
534 ports.insert(name.to_string(), port);
535 }
536 (_, Some(_feature)) => match ports.get_mut(name) {
537 Some(ref mut port) => {
538 port.deps.append(&mut deps);
539 }
540 _ => {
541 println!("found a feature that had no corresponding port :-");
542 println!("current {:+?}", current);
543 continue;
544 }
545 },
546 (_, _) => {
547 println!("didn't know how to deal with status file entry :-");
548 println!("{:+?}", current);
549 continue;
550 }
551 }
552 }
553 }
554 }
555
556 Ok(ports)
557 }
558
559 /// paths and triple for the chosen target
560 struct VcpkgTarget {
561 vcpkg_triple: String,
562 lib_path: PathBuf,
563 bin_path: PathBuf,
564 include_path: PathBuf,
565
566 // directory containing the status file
567 status_path: PathBuf,
568
569 is_static: bool,
570 lib_suffix: String,
571
572 /// strip 'lib' from library names in linker args?
573 strip_lib_prefix: bool,
574 }
575
576 impl VcpkgTarget {
link_name_for_lib(&self, filename: &std::path::Path) -> Option<String>577 fn link_name_for_lib(&self, filename: &std::path::Path) -> Option<String> {
578 if self.strip_lib_prefix {
579 filename.to_str().map(|s| s.to_owned())
580 // filename
581 // .to_str()
582 // .map(|s| s.trim_left_matches("lib").to_owned())
583 } else {
584 filename.to_str().map(|s| s.to_owned())
585 }
586 }
587 }
588
589 impl Config {
new() -> Config590 pub fn new() -> Config {
591 Config {
592 cargo_metadata: true,
593 copy_dlls: true,
594 ..Default::default()
595 // emit_includes: false,
596 // required_libs: Vec::new(),
597 }
598 }
599
600 /// Override the name of the library to look for if it differs from the package name.
601 ///
602 /// This may be called more than once if multiple libs are required.
603 /// All libs must be found for the probe to succeed. `.probe()` must
604 /// be run with a different configuration to look for libraries under one of several names.
605 /// `.libname("ssleay32")` will look for ssleay32.lib and also ssleay32.dll if
606 /// dynamic linking is selected.
lib_name(&mut self, lib_stem: &str) -> &mut Config607 pub fn lib_name(&mut self, lib_stem: &str) -> &mut Config {
608 self.required_libs.push(lib_stem.to_owned());
609 self.required_dlls.push(lib_stem.to_owned());
610 self
611 }
612
613 /// Override the name of the library to look for if it differs from the package name.
614 ///
615 /// This may be called more than once if multiple libs are required.
616 /// All libs must be found for the probe to succeed. `.probe()` must
617 /// be run with a different configuration to look for libraries under one of several names.
618 /// `.lib_names("libcurl_imp","curl")` will look for libcurl_imp.lib and also curl.dll if
619 /// dynamic linking is selected.
lib_names(&mut self, lib_stem: &str, dll_stem: &str) -> &mut Config620 pub fn lib_names(&mut self, lib_stem: &str, dll_stem: &str) -> &mut Config {
621 self.required_libs.push(lib_stem.to_owned());
622 self.required_dlls.push(dll_stem.to_owned());
623 self
624 }
625
626 /// Define whether metadata should be emitted for cargo allowing it to
627 /// automatically link the binary. Defaults to `true`.
cargo_metadata(&mut self, cargo_metadata: bool) -> &mut Config628 pub fn cargo_metadata(&mut self, cargo_metadata: bool) -> &mut Config {
629 self.cargo_metadata = cargo_metadata;
630 self
631 }
632
633 /// Define cargo:include= metadata should be emitted. Defaults to `false`.
emit_includes(&mut self, emit_includes: bool) -> &mut Config634 pub fn emit_includes(&mut self, emit_includes: bool) -> &mut Config {
635 self.emit_includes = emit_includes;
636 self
637 }
638
639 /// Should DLLs be copied to OUT_DIR?
640 /// Defaults to `true`.
copy_dlls(&mut self, copy_dlls: bool) -> &mut Config641 pub fn copy_dlls(&mut self, copy_dlls: bool) -> &mut Config {
642 self.copy_dlls = copy_dlls;
643 self
644 }
645
646 /// Find the library `port_name` in a Vcpkg tree.
647 ///
648 /// This will use all configuration previously set to select the
649 /// architecture and linkage.
650 /// Deprecated in favor of the find_package function
651 #[doc(hidden)]
probe(&mut self, port_name: &str) -> Result<Library, Error>652 pub fn probe(&mut self, port_name: &str) -> Result<Library, Error> {
653 // determine the target type, bailing out if it is not some
654 // kind of msvc
655 let msvc_target = try!(msvc_target());
656
657 // bail out if requested to not try at all
658 if env::var_os("VCPKGRS_DISABLE").is_some() {
659 return Err(Error::DisabledByEnv("VCPKGRS_DISABLE".to_owned()));
660 }
661
662 // bail out if requested to not try at all (old)
663 if env::var_os("NO_VCPKG").is_some() {
664 return Err(Error::DisabledByEnv("NO_VCPKG".to_owned()));
665 }
666
667 // bail out if requested to skip this package
668 let abort_var_name = format!("VCPKGRS_NO_{}", envify(port_name));
669 if env::var_os(&abort_var_name).is_some() {
670 return Err(Error::DisabledByEnv(abort_var_name));
671 }
672
673 // bail out if requested to skip this package (old)
674 let abort_var_name = format!("{}_NO_VCPKG", envify(port_name));
675 if env::var_os(&abort_var_name).is_some() {
676 return Err(Error::DisabledByEnv(abort_var_name));
677 }
678
679 // if no overrides have been selected, then the Vcpkg port name
680 // is the the .lib name and the .dll name
681 if self.required_libs.is_empty() {
682 self.required_libs.push(port_name.to_owned());
683 self.required_dlls.push(port_name.to_owned());
684 }
685
686 let vcpkg_target = try!(find_vcpkg_target(&msvc_target));
687
688 // require explicit opt-in before using dynamically linked
689 // variants, otherwise cargo install of various things will
690 // stop working if Vcpkg is installed.
691 if !vcpkg_target.is_static && !env::var_os("VCPKGRS_DYNAMIC").is_some() {
692 return Err(Error::RequiredEnvMissing("VCPKGRS_DYNAMIC".to_owned()));
693 }
694
695 let mut lib = Library::new(vcpkg_target.is_static);
696
697 if self.emit_includes {
698 lib.cargo_metadata.push(format!(
699 "cargo:include={}",
700 vcpkg_target.include_path.display()
701 ));
702 }
703 lib.include_paths.push(vcpkg_target.include_path.clone());
704
705 lib.cargo_metadata.push(format!(
706 "cargo:rustc-link-search=native={}",
707 vcpkg_target
708 .lib_path
709 .to_str()
710 .expect("failed to convert string type")
711 ));
712 lib.link_paths.push(vcpkg_target.lib_path.clone());
713 if !vcpkg_target.is_static {
714 lib.cargo_metadata.push(format!(
715 "cargo:rustc-link-search=native={}",
716 vcpkg_target
717 .bin_path
718 .to_str()
719 .expect("failed to convert string type")
720 ));
721 // this path is dropped by recent versions of cargo hence the copies to OUT_DIR below
722 lib.dll_paths.push(vcpkg_target.bin_path.clone());
723 }
724
725 try!(self.emit_libs(&mut lib, &vcpkg_target));
726
727 if self.copy_dlls {
728 try!(self.do_dll_copy(&mut lib));
729 }
730
731 if self.cargo_metadata {
732 for line in &lib.cargo_metadata {
733 println!("{}", line);
734 }
735 }
736 Ok(lib)
737 }
738
emit_libs(&mut self, lib: &mut Library, vcpkg_target: &VcpkgTarget) -> Result<(), Error>739 fn emit_libs(&mut self, lib: &mut Library, vcpkg_target: &VcpkgTarget) -> Result<(), Error> {
740 for required_lib in &self.required_libs {
741 // this could use static-nobundle= for static libraries but it is apparently
742 // not necessary to make the distinction for windows-msvc.
743
744 let link_name = match vcpkg_target.strip_lib_prefix {
745 true => required_lib.trim_left_matches("lib"),
746 false => required_lib,
747 };
748
749 lib.cargo_metadata
750 .push(format!("cargo:rustc-link-lib={}", link_name));
751
752 // verify that the library exists
753 let mut lib_location = vcpkg_target.lib_path.clone();
754 lib_location.push(required_lib.clone() + "." + &vcpkg_target.lib_suffix);
755
756 if !lib_location.exists() {
757 return Err(Error::LibNotFound(lib_location.display().to_string()));
758 }
759 lib.found_libs.push(lib_location);
760 }
761
762 if !vcpkg_target.is_static {
763 for required_dll in &self.required_dlls {
764 let mut dll_location = vcpkg_target.bin_path.clone();
765 dll_location.push(required_dll.clone() + ".dll");
766
767 // verify that the DLL exists
768 if !dll_location.exists() {
769 return Err(Error::LibNotFound(dll_location.display().to_string()));
770 }
771 lib.found_dlls.push(dll_location);
772 }
773 }
774
775 Ok(())
776 }
777
do_dll_copy(&mut self, lib: &mut Library) -> Result<(), Error>778 fn do_dll_copy(&mut self, lib: &mut Library) -> Result<(), Error> {
779 if let Some(target_dir) = env::var_os("OUT_DIR") {
780 if !lib.found_dlls.is_empty() {
781 for file in &lib.found_dlls {
782 let mut dest_path = Path::new(target_dir.as_os_str()).to_path_buf();
783 dest_path.push(Path::new(file.file_name().unwrap()));
784 try!(
785 fs::copy(file, &dest_path).map_err(|_| Error::LibNotFound(format!(
786 "Can't copy file {} to {}",
787 file.to_string_lossy(),
788 dest_path.to_string_lossy()
789 )))
790 );
791 println!(
792 "vcpkg build helper copied {} to {}",
793 file.to_string_lossy(),
794 dest_path.to_string_lossy()
795 );
796 }
797 lib.cargo_metadata.push(format!(
798 "cargo:rustc-link-search=native={}",
799 env::var("OUT_DIR").unwrap()
800 ));
801 // work around https://github.com/rust-lang/cargo/issues/3957
802 lib.cargo_metadata.push(format!(
803 "cargo:rustc-link-search={}",
804 env::var("OUT_DIR").unwrap()
805 ));
806 }
807 } else {
808 return Err(Error::LibNotFound("Unable to get OUT_DIR".to_owned()));
809 }
810 Ok(())
811 }
812
813 /// Find the package `port_name` in a Vcpkg tree.
814 ///
815 /// Emits cargo metadata to link to libraries provided by the Vcpkg package/port
816 /// named, and any (non-system) libraries that they depend on.
817 ///
818 /// This will select the architecture and linkage based on environment
819 /// variables and build flags as described in the module docs, and any configuration
820 /// set on the builder.
find_package(&mut self, port_name: &str) -> Result<Library, Error>821 pub fn find_package(&mut self, port_name: &str) -> Result<Library, Error> {
822 // determine the target type, bailing out if it is not some
823 // kind of msvc
824 let msvc_target = try!(msvc_target());
825
826 // bail out if requested to not try at all
827 if env::var_os("VCPKGRS_DISABLE").is_some() {
828 return Err(Error::DisabledByEnv("VCPKGRS_DISABLE".to_owned()));
829 }
830
831 // bail out if requested to not try at all (old)
832 if env::var_os("NO_VCPKG").is_some() {
833 return Err(Error::DisabledByEnv("NO_VCPKG".to_owned()));
834 }
835
836 // bail out if requested to skip this package
837 let abort_var_name = format!("VCPKGRS_NO_{}", envify(port_name));
838 if env::var_os(&abort_var_name).is_some() {
839 return Err(Error::DisabledByEnv(abort_var_name));
840 }
841
842 // bail out if requested to skip this package (old)
843 let abort_var_name = format!("{}_NO_VCPKG", envify(port_name));
844 if env::var_os(&abort_var_name).is_some() {
845 return Err(Error::DisabledByEnv(abort_var_name));
846 }
847
848 let vcpkg_target = try!(find_vcpkg_target(&msvc_target));
849
850 let mut required_port_order = Vec::new();
851
852 // if no overrides have been selected, then the Vcpkg port name
853 // is the the .lib name and the .dll name
854 if self.required_libs.is_empty() {
855 let ports = try!(load_ports(&vcpkg_target));
856
857 if ports.get(&port_name.to_owned()).is_none() {
858 return Err(Error::LibNotFound(port_name.to_owned()));
859 }
860
861 // the complete set of ports required
862 let mut required_ports: BTreeMap<String, Port> = BTreeMap::new();
863 // working of ports that we need to include
864 // let mut ports_to_scan: BTreeSet<String> = BTreeSet::new();
865 // ports_to_scan.insert(port_name.to_owned());
866 let mut ports_to_scan = vec![port_name.to_owned()]; //: Vec<String> = BTreeSet::new();
867
868 while !ports_to_scan.is_empty() {
869 let port_name = ports_to_scan.pop().unwrap();
870
871 if required_ports.contains_key(&port_name) {
872 continue;
873 }
874
875 if let Some(port) = ports.get(&port_name) {
876 for dep in &port.deps {
877 ports_to_scan.push(dep.clone());
878 }
879 required_ports.insert(port_name.clone(), (*port).clone());
880 remove_item(&mut required_port_order, &port_name);
881 required_port_order.push(port_name);
882 } else {
883 // what?
884 }
885 }
886
887 // for port in ports {
888 // println!("port {:?}", port);
889 // }
890 // println!("== Looking for port {}", port_name);
891 // for port in &required_port_order {
892 // println!("ordered required port {:?}", port);
893 // }
894 // println!("=============================");
895 // for port in &required_ports {
896 // println!("required port {:?}", port);
897 // }
898
899 // if no overrides have been selected, then the Vcpkg port name
900 // is the the .lib name and the .dll name
901 if self.required_libs.is_empty() {
902 for port_name in &required_port_order {
903 let port = required_ports.get(port_name).unwrap();
904 self.required_libs.extend(port.libs.iter().map(|s| {
905 Path::new(&s)
906 .file_stem()
907 .unwrap()
908 .to_string_lossy()
909 .into_owned()
910 }));
911 self.required_dlls
912 .extend(port.dlls.iter().cloned().map(|s| {
913 Path::new(&s)
914 .file_stem()
915 .unwrap()
916 .to_string_lossy()
917 .into_owned()
918 }));
919 }
920 }
921 }
922 // require explicit opt-in before using dynamically linked
923 // variants, otherwise cargo install of various things will
924 // stop working if Vcpkg is installed.
925 if !vcpkg_target.is_static && !env::var_os("VCPKGRS_DYNAMIC").is_some() {
926 return Err(Error::RequiredEnvMissing("VCPKGRS_DYNAMIC".to_owned()));
927 }
928
929 let mut lib = Library::new(vcpkg_target.is_static);
930
931 if self.emit_includes {
932 lib.cargo_metadata.push(format!(
933 "cargo:include={}",
934 vcpkg_target.include_path.display()
935 ));
936 }
937 lib.include_paths.push(vcpkg_target.include_path.clone());
938
939 lib.cargo_metadata.push(format!(
940 "cargo:rustc-link-search=native={}",
941 vcpkg_target
942 .lib_path
943 .to_str()
944 .expect("failed to convert string type")
945 ));
946 lib.link_paths.push(vcpkg_target.lib_path.clone());
947 if !vcpkg_target.is_static {
948 lib.cargo_metadata.push(format!(
949 "cargo:rustc-link-search=native={}",
950 vcpkg_target
951 .bin_path
952 .to_str()
953 .expect("failed to convert string type")
954 ));
955 // this path is dropped by recent versions of cargo hence the copies to OUT_DIR below
956 lib.dll_paths.push(vcpkg_target.bin_path.clone());
957 }
958
959 lib.ports = required_port_order;
960
961 try!(self.emit_libs(&mut lib, &vcpkg_target));
962
963 if self.copy_dlls {
964 try!(self.do_dll_copy(&mut lib));
965 }
966
967 if self.cargo_metadata {
968 for line in &lib.cargo_metadata {
969 println!("{}", line);
970 }
971 }
972 Ok(lib)
973 }
974 }
975
remove_item(cont: &mut Vec<String>, item: &String) -> Option<String>976 fn remove_item(cont: &mut Vec<String>, item: &String) -> Option<String> {
977 match cont.iter().position(|x| *x == *item) {
978 Some(pos) => Some(cont.remove(pos)),
979 None => None,
980 }
981 }
982
983 impl Library {
new(is_static: bool) -> Library984 fn new(is_static: bool) -> Library {
985 Library {
986 link_paths: Vec::new(),
987 dll_paths: Vec::new(),
988 include_paths: Vec::new(),
989 cargo_metadata: Vec::new(),
990 is_static: is_static,
991 found_dlls: Vec::new(),
992 found_libs: Vec::new(),
993 ports: Vec::new(),
994 }
995 }
996 }
997
envify(name: &str) -> String998 fn envify(name: &str) -> String {
999 name.chars()
1000 .map(|c| c.to_ascii_uppercase())
1001 .map(|c| if c == '-' { '_' } else { c })
1002 .collect()
1003 }
1004
msvc_target() -> Result<MSVCTarget, Error>1005 fn msvc_target() -> Result<MSVCTarget, Error> {
1006 let target = env::var("TARGET").unwrap_or(String::new());
1007 if target == "x86_64-apple-darwin" {
1008 Ok(MSVCTarget::X64MacOS)
1009 } else if target == "x86_64-unknown-linux-gnu" {
1010 Ok(MSVCTarget::X64Linux)
1011 } else if !target.contains("-pc-windows-msvc") {
1012 Err(Error::NotMSVC)
1013 } else if target.starts_with("x86_64-") {
1014 Ok(MSVCTarget::X64Windows)
1015 } else {
1016 // everything else is x86
1017 Ok(MSVCTarget::X86Windows)
1018 }
1019 }
1020
1021 #[cfg(test)]
1022 mod tests {
1023
1024 extern crate tempdir;
1025
1026 use super::*;
1027 use std::env;
1028 use std::sync::Mutex;
1029
1030 lazy_static! {
1031 static ref LOCK: Mutex<()> = Mutex::new(());
1032 }
1033
1034 #[test]
do_nothing_for_unsupported_target()1035 fn do_nothing_for_unsupported_target() {
1036 let _g = LOCK.lock();
1037 env::set_var("VCPKG_ROOT", "/");
1038 env::set_var("TARGET", "x86_64-pc-windows-gnu");
1039 assert!(match ::probe_package("foo") {
1040 Err(Error::NotMSVC) => true,
1041 _ => false,
1042 });
1043
1044 env::set_var("TARGET", "x86_64-pc-windows-gnu");
1045 assert_eq!(env::var("TARGET"), Ok("x86_64-pc-windows-gnu".to_string()));
1046 assert!(match ::probe_package("foo") {
1047 Err(Error::NotMSVC) => true,
1048 _ => false,
1049 });
1050 env::remove_var("TARGET");
1051 env::remove_var("VCPKG_ROOT");
1052 }
1053
1054 #[test]
do_nothing_for_bailout_variables_set()1055 fn do_nothing_for_bailout_variables_set() {
1056 let _g = LOCK.lock();
1057 env::set_var("VCPKG_ROOT", "/");
1058 env::set_var("TARGET", "x86_64-pc-windows-msvc");
1059
1060 for &var in &[
1061 "VCPKGRS_DISABLE",
1062 "VCPKGRS_NO_FOO",
1063 "FOO_NO_VCPKG",
1064 "NO_VCPKG",
1065 ] {
1066 env::set_var(var, "1");
1067 assert!(match ::probe_package("foo") {
1068 Err(Error::DisabledByEnv(ref v)) if v == var => true,
1069 _ => false,
1070 });
1071 env::remove_var(var);
1072 }
1073 env::remove_var("TARGET");
1074 env::remove_var("VCPKG_ROOT");
1075 }
1076
1077 // these tests are good but are leaning on a real vcpkg installation
1078
1079 #[test]
default_build_refuses_dynamic()1080 fn default_build_refuses_dynamic() {
1081 let _g = LOCK.lock();
1082 clean_env();
1083 env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("no-status"));
1084 env::set_var("TARGET", "x86_64-pc-windows-msvc");
1085 println!("Result is {:?}", ::find_package("libmysql"));
1086 assert!(match ::find_package("libmysql") {
1087 Err(Error::RequiredEnvMissing(ref v)) if v == "VCPKGRS_DYNAMIC" => true,
1088 _ => false,
1089 });
1090 clean_env();
1091 }
1092
1093 #[test]
static_build_finds_lib()1094 fn static_build_finds_lib() {
1095 let _g = LOCK.lock();
1096 clean_env();
1097 env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("normalized"));
1098 env::set_var("TARGET", "x86_64-pc-windows-msvc");
1099 let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap();
1100 env::set_var("OUT_DIR", tmp_dir.path());
1101
1102 // CARGO_CFG_TARGET_FEATURE is set in response to
1103 // RUSTFLAGS=-Ctarget-feature=+crt-static. It would
1104 // be nice to test that also.
1105 env::set_var("CARGO_CFG_TARGET_FEATURE", "crt-static");
1106 println!("Result is {:?}", ::find_package("libmysql"));
1107 assert!(match ::find_package("libmysql") {
1108 Ok(_) => true,
1109 _ => false,
1110 });
1111 clean_env();
1112 }
1113
1114 #[test]
dynamic_build_finds_lib()1115 fn dynamic_build_finds_lib() {
1116 let _g = LOCK.lock();
1117 clean_env();
1118 env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("no-status"));
1119 env::set_var("TARGET", "x86_64-pc-windows-msvc");
1120 env::set_var("VCPKGRS_DYNAMIC", "1");
1121 let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap();
1122 env::set_var("OUT_DIR", tmp_dir.path());
1123
1124 println!("Result is {:?}", ::find_package("libmysql"));
1125 assert!(match ::find_package("libmysql") {
1126 Ok(_) => true,
1127 _ => false,
1128 });
1129 clean_env();
1130 }
1131
1132 #[test]
handle_multiline_description()1133 fn handle_multiline_description() {
1134 let _g = LOCK.lock();
1135 clean_env();
1136 env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("multiline-description"));
1137 env::set_var("TARGET", "i686-pc-windows-msvc");
1138 env::set_var("VCPKGRS_DYNAMIC", "1");
1139 let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap();
1140 env::set_var("OUT_DIR", tmp_dir.path());
1141
1142 println!("Result is {:?}", ::find_package("graphite2"));
1143 assert!(match ::find_package("graphite2") {
1144 Ok(_) => true,
1145 _ => false,
1146 });
1147 clean_env();
1148 }
1149
1150 #[test]
link_libs_required_by_optional_features()1151 fn link_libs_required_by_optional_features() {
1152 let _g = LOCK.lock();
1153 clean_env();
1154 env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("normalized"));
1155 env::set_var("TARGET", "i686-pc-windows-msvc");
1156 env::set_var("VCPKGRS_DYNAMIC", "1");
1157 let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap();
1158 env::set_var("OUT_DIR", tmp_dir.path());
1159
1160 println!("Result is {:?}", ::find_package("harfbuzz"));
1161 assert!(match ::find_package("harfbuzz") {
1162 Ok(lib) => lib
1163 .cargo_metadata
1164 .iter()
1165 .find(|&x| x == "cargo:rustc-link-lib=icuuc")
1166 .is_some(),
1167 _ => false,
1168 });
1169 clean_env();
1170 }
1171
1172 #[test]
link_lib_name_is_correct()1173 fn link_lib_name_is_correct() {
1174 let _g = LOCK.lock();
1175
1176 for target in &[
1177 "x86_64-apple-darwin",
1178 "i686-pc-windows-msvc",
1179 // "x86_64-pc-windows-msvc",
1180 // "x86_64-unknown-linux-gnu",
1181 ] {
1182 clean_env();
1183 env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("normalized"));
1184 env::set_var("TARGET", target);
1185 env::set_var("VCPKGRS_DYNAMIC", "1");
1186 let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap();
1187 env::set_var("OUT_DIR", tmp_dir.path());
1188
1189 println!("Result is {:?}", ::find_package("harfbuzz"));
1190 assert!(match ::find_package("harfbuzz") {
1191 Ok(lib) => lib
1192 .cargo_metadata
1193 .iter()
1194 .find(|&x| x == "cargo:rustc-link-lib=harfbuzz")
1195 .is_some(),
1196 _ => false,
1197 });
1198 clean_env();
1199 }
1200 }
1201
1202 #[test]
link_dependencies_after_port()1203 fn link_dependencies_after_port() {
1204 let _g = LOCK.lock();
1205 clean_env();
1206 env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("normalized"));
1207 env::set_var("TARGET", "i686-pc-windows-msvc");
1208 env::set_var("VCPKGRS_DYNAMIC", "1");
1209 let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap();
1210 env::set_var("OUT_DIR", tmp_dir.path());
1211
1212 let lib = ::find_package("harfbuzz").unwrap();
1213
1214 check_before(&lib, "freetype", "zlib");
1215 check_before(&lib, "freetype", "bzip2");
1216 check_before(&lib, "freetype", "libpng");
1217 check_before(&lib, "harfbuzz", "freetype");
1218 check_before(&lib, "harfbuzz", "ragel");
1219 check_before(&lib, "libpng", "zlib");
1220
1221 clean_env();
1222
1223 fn check_before(lib: &Library, earlier: &str, later: &str) {
1224 match (
1225 lib.ports.iter().position(|x| *x == *earlier),
1226 lib.ports.iter().position(|x| *x == *later),
1227 ) {
1228 (Some(earlier_pos), Some(later_pos)) if earlier_pos < later_pos => {
1229 // ok
1230 }
1231 _ => {
1232 println!(
1233 "earlier: {}, later: {}\nLibrary found: {:#?}",
1234 earlier, later, lib
1235 );
1236 panic!();
1237 }
1238 }
1239 }
1240 }
1241 // #[test]
1242 // fn dynamic_build_package_specific_bailout() {
1243 // clean_env();
1244 // env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("no-status"));
1245 // env::set_var("TARGET", "x86_64-pc-windows-msvc");
1246 // env::set_var("VCPKGRS_DYNAMIC", "1");
1247 // env::set_var("VCPKGRS_NO_LIBMYSQL", "1");
1248
1249 // println!("Result is {:?}", ::find_package("libmysql"));
1250 // assert!(match ::find_package("libmysql") {
1251 // Err(Error::DisabledByEnv(ref v)) if v == "VCPKGRS_NO_LIBMYSQL" => true,
1252 // _ => false,
1253 // });
1254 // clean_env();
1255 // }
1256
1257 // #[test]
1258 // fn dynamic_build_global_bailout() {
1259 // clean_env();
1260 // env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("no-status"));
1261 // env::set_var("TARGET", "x86_64-pc-windows-msvc");
1262 // env::set_var("VCPKGRS_DYNAMIC", "1");
1263 // env::set_var("VCPKGRS_DISABLE", "1");
1264
1265 // println!("Result is {:?}", ::find_package("libmysql"));
1266 // assert!(match ::find_package("libmysql") {
1267 // Err(Error::DisabledByEnv(ref v)) if v == "VCPKGRS_DISABLE" => true,
1268 // _ => false,
1269 // });
1270 // clean_env();
1271 // }
1272
clean_env()1273 fn clean_env() {
1274 env::remove_var("TARGET");
1275 env::remove_var("VCPKG_ROOT");
1276 env::remove_var("VCPKGRS_DYNAMIC");
1277 env::remove_var("RUSTFLAGS");
1278 env::remove_var("CARGO_CFG_TARGET_FEATURE");
1279 env::remove_var("VCPKGRS_DISABLE");
1280 env::remove_var("VCPKGRS_NO_LIBMYSQL");
1281 }
1282
1283 // path to a to vcpkg installation to test against
vcpkg_test_tree_loc(name: &str) -> PathBuf1284 fn vcpkg_test_tree_loc(name: &str) -> PathBuf {
1285 let mut path = PathBuf::new();
1286 path.push(env::var("CARGO_MANIFEST_DIR").unwrap());
1287 path.pop();
1288 path.push("test-data");
1289 path.push(name);
1290 path
1291 }
1292 }
1293