//! A build dependency for Cargo libraries to find libraries in a //! [Vcpkg](https://github.com/microsoft/vcpkg) tree //! //! From a Vcpkg package name //! this build helper will emit cargo metadata to link it and it's dependencies //! (excluding system libraries, which it does not determine). //! //! The simplest possible usage looks like this :- //! //! ```rust,no_run //! // build.rs //! vcpkg::find_package("libssh2").unwrap(); //! ``` //! //! The cargo metadata that is emitted can be changed like this :- //! //! ```rust,no_run //! // build.rs //! vcpkg::Config::new() //! .emit_includes(true) //! .find_package("zlib").unwrap(); //! ``` //! //! If the search was successful all appropriate Cargo metadata will be printed //! to stdout. //! //! # Static vs. dynamic linking //! ## Linux and Mac //! At this time, vcpkg has a single triplet on macOS and Linux, which builds //! static link versions of libraries. This triplet works well with Rust. It is also possible //! to select a custom triplet using the `VCPKGRS_TRIPLET` environment variable. //! ## Windows //! On Windows there are three //! configurations that are supported for 64-bit builds and another three for 32-bit. //! The default 64-bit configuration is `x64-windows-static-md` which is a //! [community supported](https://github.com/microsoft/vcpkg/blob/master/docs/users/triplets.md#community-triplets) //! configuration that is a good match for Rust - dynamically linking to the C runtime, //! and statically linking to the packages in vcpkg. //! //! Another option is to build a fully static //! binary using `RUSTFLAGS=-Ctarget-feature=+crt-static`. This will link to libraries built //! with vcpkg triplet `x64-windows-static`. //! //! For dynamic linking, set `VCPKGRS_DYNAMIC=1` in the //! environment. This will link to libraries built with vcpkg triplet `x64-windows`. If `VCPKGRS_DYNAMIC` is set, `cargo install` will //! generate dynamically linked binaries, in which case you will have to arrange for //! dlls from your Vcpkg installation to be available in your path. //! //! # Environment variables //! //! A number of environment variables are available to globally configure which //! libraries are selected. //! //! * `VCPKG_ROOT` - Set the directory to look in for a vcpkg installation. If //! it is not set, vcpkg will use the user-wide installation if one has been //! set up with `vcpkg integrate install`, and check the crate source and target //! to see if a vcpkg tree has been created by [cargo-vcpkg](https://crates.io/crates/cargo-vcpkg). //! //! * `VCPKGRS_TRIPLET` - Use this to override vcpkg-rs' default triplet selection with your own. //! This is how to select a custom vcpkg triplet. //! //! * `VCPKGRS_NO_FOO` - if set, vcpkg-rs will not attempt to find the //! library named `foo`. //! //! * `VCPKGRS_DISABLE` - if set, vcpkg-rs will not attempt to find any libraries. //! //! * `VCPKGRS_DYNAMIC` - if set, vcpkg-rs will link to DLL builds of ports. //! # Related tools //! ## cargo vcpkg //! [`cargo vcpkg`](https://crates.io/crates/cargo-vcpkg) can fetch and build a vcpkg installation of //! required packages from scratch. It merges package requirements specified in the `Cargo.toml` of //! crates in the dependency tree. //! ## vcpkg_cli //! There is also a rudimentary companion crate, `vcpkg_cli` that allows testing of environment //! and flag combinations. //! //! ```Batchfile //! C:\src> vcpkg_cli probe -l static mysqlclient //! Found library mysqlclient //! Include paths: //! C:\src\[..]\vcpkg\installed\x64-windows-static\include //! Library paths: //! C:\src\[..]\vcpkg\installed\x64-windows-static\lib //! Cargo metadata: //! cargo:rustc-link-search=native=C:\src\[..]\vcpkg\installed\x64-windows-static\lib //! cargo:rustc-link-lib=static=mysqlclient //! ``` // The CI will test vcpkg-rs on 1.12 because that is how far back vcpkg-rs 0.2 tries to be // compatible (was actually 1.10 see #29). This was originally based on how far back // rust-openssl's openssl-sys was backward compatible when this crate originally released. // // This will likely get bumped by the next major release. #![allow(deprecated)] #![allow(warnings)] #[cfg(test)] #[macro_use] extern crate lazy_static; #[allow(unused_imports)] use std::ascii::AsciiExt; use std::collections::BTreeMap; use std::collections::HashMap; use std::env; use std::error; use std::ffi::OsStr; use std::fmt; use std::fs::{self, File}; use std::io::{BufRead, BufReader, Read}; use std::path::{Path, PathBuf}; /// Configuration options for finding packages, setting up the tree and emitting metadata to cargo #[derive(Default)] pub struct Config { /// should the cargo metadata actually be emitted cargo_metadata: bool, /// should cargo:include= metadata be emitted (defaults to false) emit_includes: bool, /// .lib/.a files that must be be found for probing to be considered successful required_libs: Vec, /// .dlls that must be be found for probing to be considered successful required_dlls: Vec, /// should DLLs be copied to OUT_DIR? copy_dlls: bool, /// override VCPKG_ROOT environment variable vcpkg_root: Option, target: Option, } /// Details of a package that was found #[derive(Debug)] pub struct Library { /// Paths for the linker to search for static or import libraries pub link_paths: Vec, /// Paths to search at runtme to find DLLs pub dll_paths: Vec, /// Paths to include files pub include_paths: Vec, /// cargo: metadata lines pub cargo_metadata: Vec, /// libraries found are static pub is_static: bool, /// DLLs found pub found_dlls: Vec, /// static libs or import libs found pub found_libs: Vec, /// link name of libraries found, this is useful to emit linker commands pub found_names: Vec, /// ports that are providing the libraries to link to, in port link order pub ports: Vec, /// the vcpkg triplet that has been selected pub vcpkg_triplet: String, } #[derive(Clone)] struct TargetTriplet { triplet: String, is_static: bool, lib_suffix: String, strip_lib_prefix: bool, } impl> From for TargetTriplet { fn from(triplet: S) -> TargetTriplet { let triplet = triplet.as_ref(); if triplet.contains("windows") { TargetTriplet { triplet: triplet.into(), is_static: triplet.contains("-static"), lib_suffix: "lib".into(), strip_lib_prefix: false, } } else { TargetTriplet { triplet: triplet.into(), is_static: true, lib_suffix: "a".into(), strip_lib_prefix: true, } } } } #[derive(Debug)] // need Display? pub enum Error { /// Aborted because of a `VCPKGRS_NO_*` environment variable. /// /// Contains the name of the responsible environment variable. DisabledByEnv(String), /// Aborted because a required environment variable was not set. RequiredEnvMissing(String), /// On Windows, only MSVC ABI is supported NotMSVC, /// Can't find a vcpkg tree VcpkgNotFound(String), /// Library not found in vcpkg tree LibNotFound(String), /// Could not understand vcpkg installation VcpkgInstallation(String), #[doc(hidden)] __Nonexhaustive, } impl error::Error for Error { fn description(&self) -> &str { match *self { Error::DisabledByEnv(_) => "vcpkg-rs requested to be aborted", Error::RequiredEnvMissing(_) => "a required env setting is missing", Error::NotMSVC => "vcpkg-rs only can only find libraries for MSVC ABI builds", Error::VcpkgNotFound(_) => "could not find Vcpkg tree", Error::LibNotFound(_) => "could not find library in Vcpkg tree", Error::VcpkgInstallation(_) => "could not look up details of packages in vcpkg tree", Error::__Nonexhaustive => panic!(), } } fn cause(&self) -> Option<&error::Error> { match *self { // Error::Command { ref cause, .. } => Some(cause), _ => None, } } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { match *self { Error::DisabledByEnv(ref name) => write!(f, "Aborted because {} is set", name), Error::RequiredEnvMissing(ref name) => write!(f, "Aborted because {} is not set", name), Error::NotMSVC => write!( f, "the vcpkg-rs Vcpkg build helper can only find libraries built for the MSVC ABI." ), Error::VcpkgNotFound(ref detail) => write!(f, "Could not find Vcpkg tree: {}", detail), Error::LibNotFound(ref detail) => { write!(f, "Could not find library in Vcpkg tree {}", detail) } Error::VcpkgInstallation(ref detail) => write!( f, "Could not look up details of packages in vcpkg tree {}", detail ), Error::__Nonexhaustive => panic!(), } } } /// Deprecated in favor of the find_package function #[doc(hidden)] pub fn probe_package(name: &str) -> Result { Config::new().probe(name) } /// Find the package `package` in a Vcpkg tree. /// /// Emits cargo metadata to link to libraries provided by the Vcpkg package/port /// named, and any (non-system) libraries that they depend on. /// /// This will select the architecture and linkage based on environment /// variables and build flags as described in the module docs. pub fn find_package(package: &str) -> Result { Config::new().find_package(package) } /// Find the vcpkg root #[doc(hidden)] pub fn find_vcpkg_root(cfg: &Config) -> Result { // prefer the setting from the use if there is one if let &Some(ref path) = &cfg.vcpkg_root { return Ok(path.clone()); } // otherwise, use the setting from the environment if let Some(path) = env::var_os("VCPKG_ROOT") { return Ok(PathBuf::from(path)); } // see if there is a per-user vcpkg tree that has been integrated into msbuild // using `vcpkg integrate install` if let Ok(ref local_app_data) = env::var("LOCALAPPDATA") { let vcpkg_user_targets_path = Path::new(local_app_data.as_str()) .join("vcpkg") .join("vcpkg.user.targets"); if let Ok(file) = File::open(vcpkg_user_targets_path.clone()) { let file = BufReader::new(&file); for line in file.lines() { let line = try!(line.map_err(|_| Error::VcpkgNotFound(format!( "Parsing of {} failed.", vcpkg_user_targets_path.to_string_lossy().to_owned() )))); let mut split = line.split("Project=\""); split.next(); // eat anything before Project=" if let Some(found) = split.next() { // " is illegal in a Windows pathname if let Some(found) = found.split_terminator('"').next() { let mut vcpkg_root = PathBuf::from(found); if !(vcpkg_root.pop() && vcpkg_root.pop() && vcpkg_root.pop() && vcpkg_root.pop()) { return Err(Error::VcpkgNotFound(format!( "Could not find vcpkg root above {}", found ))); } return Ok(vcpkg_root); } } } // return Err(Error::VcpkgNotFound(format!( // "Project location not found parsing {}.", // vcpkg_user_targets_path.to_string_lossy().to_owned() // ))); } } // walk up the directory structure and see if it is there if let Some(path) = env::var_os("OUT_DIR") { // path.ancestors() is supported from Rust 1.28 let mut path = PathBuf::from(path); while path.pop() { let mut try_root = path.clone(); try_root.push("vcpkg"); try_root.push(".vcpkg-root"); if try_root.exists() { try_root.pop(); // this could walk up beyond the target directory and find a vcpkg installation // that would not have been found by previous versions of vcpkg-rs, so this // checks that the vcpkg tree was created by cargo-vcpkg and ignores it if not. let mut cv_cfg = try_root.clone(); cv_cfg.push("downloads"); cv_cfg.push("cargo-vcpkg.toml"); if cv_cfg.exists() { return Ok(try_root); } } } } Err(Error::VcpkgNotFound( "No vcpkg installation found. Set the VCPKG_ROOT environment \ variable or run 'vcpkg integrate install'" .to_string(), )) } fn validate_vcpkg_root(path: &PathBuf) -> Result<(), Error> { let mut vcpkg_root_path = path.clone(); vcpkg_root_path.push(".vcpkg-root"); if vcpkg_root_path.exists() { Ok(()) } else { Err(Error::VcpkgNotFound(format!( "Could not find Vcpkg root at {}", vcpkg_root_path.to_string_lossy() ))) } } fn find_vcpkg_target(cfg: &Config, target_triplet: &TargetTriplet) -> Result { let vcpkg_root = try!(find_vcpkg_root(&cfg)); try!(validate_vcpkg_root(&vcpkg_root)); let mut base = vcpkg_root.clone(); base.push("installed"); let status_path = base.join("vcpkg"); base.push(&target_triplet.triplet); let lib_path = base.join("lib"); let bin_path = base.join("bin"); let include_path = base.join("include"); let packages_path = vcpkg_root.join("packages"); Ok(VcpkgTarget { lib_path: lib_path, bin_path: bin_path, include_path: include_path, status_path: status_path, packages_path: packages_path, target_triplet: target_triplet.clone(), }) } /// Parsed knowledge from a .pc file. #[derive(Debug)] struct PcFile { /// The pkg-config name of this library. id: String, /// List of libraries found as '-l', translated to a given vcpkg_target. e.g. libbrotlicommon.a libs: Vec, /// List of pkgconfig dependencies, e.g. PcFile::id. deps: Vec, } impl PcFile { fn parse_pc_file(vcpkg_target: &VcpkgTarget, path: &Path) -> Result { // Extract the pkg-config name. let id = try!(path .file_stem() .ok_or_else(|| Error::VcpkgInstallation(format!( "pkg-config file {} has bogus name", path.to_string_lossy() )))) .to_string_lossy(); // Read through the file and gather what we want. let mut file = try!(File::open(path) .map_err(|_| Error::VcpkgInstallation(format!("Couldn't open {}", path.display())))); let mut pc_file_contents = String::new(); try!(file .read_to_string(&mut pc_file_contents) .map_err(|_| Error::VcpkgInstallation(format!("Couldn't read {}", path.display())))); PcFile::from_str(&id, &pc_file_contents, &vcpkg_target.target_triplet) } fn from_str(id: &str, s: &str, target_triplet: &TargetTriplet) -> Result { let mut libs = Vec::new(); let mut deps = Vec::new(); for line in s.lines() { // We could collect a lot of stuff here, but we only care about Requires and Libs for the moment. if line.starts_with("Requires:") { let mut requires_args = line .split(":") .skip(1) .next() .unwrap_or("") .split_whitespace() .flat_map(|e| e.split(",")) .filter(|s| *s != ""); while let Some(dep) = requires_args.next() { // Drop any versioning requirements, we only care about library order and rely upon // port dependencies to resolve versioning. if let Some(_) = dep.find(|c| c == '=' || c == '<' || c == '>') { requires_args.next(); continue; } deps.push(dep.to_owned()); } } else if line.starts_with("Libs:") { let lib_flags = line .split(":") .skip(1) .next() .unwrap_or("") .split_whitespace(); for lib_flag in lib_flags { if lib_flag.starts_with("-l") { // reconstruct the library name. let lib = format!( "{}{}.{}", if target_triplet.strip_lib_prefix { "lib" } else { "" }, lib_flag.trim_left_matches("-l"), target_triplet.lib_suffix ); libs.push(lib); } } } } Ok(PcFile { id: id.to_string(), libs: libs, deps: deps, }) } } /// Collection of PcFile. Can be built and queried as a set of .pc files. #[derive(Debug)] struct PcFiles { files: HashMap, } impl PcFiles { fn load_pkgconfig_dir(vcpkg_target: &VcpkgTarget, path: &PathBuf) -> Result { let mut files = HashMap::new(); for dir_entry in try!(path.read_dir().map_err(|e| { Error::VcpkgInstallation(format!( "Missing pkgconfig directory {}: {}", path.to_string_lossy(), e )) })) { let dir_entry = try!(dir_entry.map_err(|e| { Error::VcpkgInstallation(format!( "Troubling reading pkgconfig dir {}: {}", path.to_string_lossy(), e )) })); // Only look at .pc files. if dir_entry.path().extension() != Some(OsStr::new("pc")) { continue; } let pc_file = try!(PcFile::parse_pc_file(vcpkg_target, &dir_entry.path())); files.insert(pc_file.id.to_owned(), pc_file); } Ok(PcFiles { files: files }) } /// Use the .pc files as a hint to the library sort order. fn fix_ordering(&self, mut libs: Vec) -> Vec { // Overall heuristic: for each library given as input, identify which PcFile declared it. // Then, looking at that PcFile, check its Requires: (deps), and if the pc file for that // dep is in our set, check if its libraries are in our set of libs. If so, move it to the // end to ensure it gets linked afterwards. // We may need to do this a few times to properly handle the case where A -> (depends on) B // -> C -> D and libraries were originally sorted D, C, B, A. Avoid recursion so we don't // have to detect potential cycles. for _iter in 0..3 { let mut required_lib_order: Vec = Vec::new(); for lib in &libs { required_lib_order.push(lib.to_owned()); if let Some(pc_file) = self.locate_pc_file_by_lib(lib) { // Consider its requirements: for dep in &pc_file.deps { // Only consider pkgconfig dependencies we know about. if let Some(dep_pc_file) = self.files.get(dep) { // Intra-port library ordering found, pivot any already seen dep_lib to the // end of the list. for dep_lib in &dep_pc_file.libs { if let Some(removed) = remove_item(&mut required_lib_order, dep_lib) { required_lib_order.push(removed); } } } } } } // We should always end up with the same number of libraries, only their order should // change. assert_eq!(libs.len(), required_lib_order.len()); // Termination: if required_lib_order == libs { // Nothing changed, we're done here. return libs; } libs = required_lib_order; } println!("cargo:warning=vcpkg gave up trying to resolve pkg-config ordering."); libs } /// Locate which PcFile contains this library, if any. fn locate_pc_file_by_lib(&self, lib: &str) -> Option<&PcFile> { for (id, pc_file) in &self.files { if pc_file.libs.contains(&lib.to_owned()) { return Some(pc_file); } } None } } #[derive(Clone, Debug)] struct Port { // dlls if any dlls: Vec, // libs (static or import) libs: Vec, // ports that this port depends on deps: Vec, } fn load_port_manifest( path: &PathBuf, port: &str, version: &str, vcpkg_target: &VcpkgTarget, ) -> Result<(Vec, Vec), Error> { let manifest_file = path.join("info").join(format!( "{}_{}_{}.list", port, version, vcpkg_target.target_triplet.triplet )); let mut dlls = Vec::new(); let mut libs = Vec::new(); let f = try!( File::open(&manifest_file).map_err(|_| Error::VcpkgInstallation(format!( "Could not open port manifest file {}", manifest_file.display() ))) ); let file = BufReader::new(&f); let dll_prefix = Path::new(&vcpkg_target.target_triplet.triplet).join("bin"); let lib_prefix = Path::new(&vcpkg_target.target_triplet.triplet).join("lib"); for line in file.lines() { let line = line.unwrap(); let file_path = Path::new(&line); if let Ok(dll) = file_path.strip_prefix(&dll_prefix) { if dll.extension() == Some(OsStr::new("dll")) && dll.components().collect::>().len() == 1 { // match "mylib.dll" but not "debug/mylib.dll" or "manual_link/mylib.dll" dll.to_str().map(|s| dlls.push(s.to_owned())); } } else if let Ok(lib) = file_path.strip_prefix(&lib_prefix) { if lib.extension() == Some(OsStr::new(&vcpkg_target.target_triplet.lib_suffix)) && lib.components().collect::>().len() == 1 { if let Some(lib) = vcpkg_target.link_name_for_lib(lib) { libs.push(lib); } } } } // Load .pc files for hints about intra-port library ordering. let pkg_config_prefix = vcpkg_target .packages_path .join(format!("{}_{}", port, vcpkg_target.target_triplet.triplet)) .join("lib") .join("pkgconfig"); // Try loading the pc files, if they are present. Not all ports have pkgconfig. if let Ok(pc_files) = PcFiles::load_pkgconfig_dir(vcpkg_target, &pkg_config_prefix) { // Use the .pc file data to potentially sort the libs to the correct order. libs = pc_files.fix_ordering(libs); } Ok((dlls, libs)) } // load ports from the status file or one of the incremental updates fn load_port_file( filename: &PathBuf, port_info: &mut Vec>, ) -> Result<(), Error> { let f = try!( File::open(&filename).map_err(|e| Error::VcpkgInstallation(format!( "Could not open status file at {}: {}", filename.display(), e ))) ); let file = BufReader::new(&f); let mut current: BTreeMap = BTreeMap::new(); for line in file.lines() { let line = line.unwrap(); let parts = line.splitn(2, ": ").clone().collect::>(); if parts.len() == 2 { // a key: value line current.insert(parts[0].trim().into(), parts[1].trim().into()); } else if line.len() == 0 { // end of section port_info.push(current.clone()); current.clear(); } else { // ignore all extension lines of the form // // Description: a package with a // very long description // // the description key is not used so this is harmless but // this will eat extension lines for any multiline key which // could become an issue in future } } if !current.is_empty() { port_info.push(current); } Ok(()) } fn load_ports(target: &VcpkgTarget) -> Result, Error> { let mut ports: BTreeMap = BTreeMap::new(); let mut port_info: Vec> = Vec::new(); // load the main status file. It is not an error if this file does not // exist. If the only command that has been run in a Vcpkg installation // is a single `vcpkg install package` then there will likely be no // status file, only incremental updates. This is the typical case when // running in a CI environment. let status_filename = target.status_path.join("status"); load_port_file(&status_filename, &mut port_info).ok(); // load updates to the status file that have yet to be normalized let status_update_dir = target.status_path.join("updates"); let paths = try!( fs::read_dir(status_update_dir).map_err(|e| Error::VcpkgInstallation(format!( "could not read status file updates dir: {}", e ))) ); // get all of the paths of the update files into a Vec let mut paths = try!(paths .map(|rde| rde.map(|de| de.path())) // Result -> Result .collect::, _>>() // collect into Result, io::Error> .map_err(|e| { Error::VcpkgInstallation(format!( "could not read status file update filenames: {}", e )) })); // Sort the paths and read them. This could be done directly from the iterator if // read_dir() guarantees that the files will be read in alpha order but that appears // to be unspecified as the underlying operating system calls used are unspecified // https://doc.rust-lang.org/nightly/std/fs/fn.read_dir.html#platform-specific-behavior paths.sort(); for path in paths { // println!("Name: {}", path.display()); try!(load_port_file(&path, &mut port_info)); } //println!("{:#?}", port_info); let mut seen_names = BTreeMap::new(); for current in &port_info { // store them by name and arch, clobbering older details match ( current.get("Package"), current.get("Architecture"), current.get("Feature"), ) { (Some(pkg), Some(arch), feature) => { seen_names.insert((pkg, arch, feature), current); } _ => {} } } for (&(name, arch, feature), current) in &seen_names { if **arch == target.target_triplet.triplet { let mut deps = if let Some(deps) = current.get("Depends") { deps.split(", ").map(|x| x.to_owned()).collect() } else { Vec::new() }; if current .get("Status") .unwrap_or(&String::new()) .ends_with(" installed") { match (current.get("Version"), feature) { (Some(version), _) => { // this failing here and bailing out causes everything to fail let lib_info = try!(load_port_manifest( &target.status_path, &name, version, &target )); let port = Port { dlls: lib_info.0, libs: lib_info.1, deps: deps, }; ports.insert(name.to_string(), port); } (_, Some(_feature)) => match ports.get_mut(name) { Some(ref mut port) => { port.deps.append(&mut deps); } _ => { println!("found a feature that had no corresponding port :-"); println!("current {:+?}", current); continue; } }, (_, _) => { println!("didn't know how to deal with status file entry :-"); println!("{:+?}", current); continue; } } } } } Ok(ports) } /// paths and triple for the chosen target struct VcpkgTarget { lib_path: PathBuf, bin_path: PathBuf, include_path: PathBuf, // directory containing the status file status_path: PathBuf, // directory containing the install files per port. packages_path: PathBuf, // target-specific settings. target_triplet: TargetTriplet, } impl VcpkgTarget { fn link_name_for_lib(&self, filename: &std::path::Path) -> Option { if self.target_triplet.strip_lib_prefix { filename.to_str().map(|s| s.to_owned()) // filename // .to_str() // .map(|s| s.trim_left_matches("lib").to_owned()) } else { filename.to_str().map(|s| s.to_owned()) } } } impl Config { pub fn new() -> Config { Config { cargo_metadata: true, copy_dlls: true, ..Default::default() } } fn get_target_triplet(&mut self) -> Result { if self.target.is_none() { let target = if let Ok(triplet_str) = env::var("VCPKGRS_TRIPLET") { triplet_str.into() } else { try!(msvc_target()) }; self.target = Some(target); } Ok(self.target.as_ref().unwrap().clone()) } /// Find the package `port_name` in a Vcpkg tree. /// /// Emits cargo metadata to link to libraries provided by the Vcpkg package/port /// named, and any (non-system) libraries that they depend on. /// /// This will select the architecture and linkage based on environment /// variables and build flags as described in the module docs, and any configuration /// set on the builder. pub fn find_package(&mut self, port_name: &str) -> Result { // determine the target type, bailing out if it is not some // kind of msvc let msvc_target = try!(self.get_target_triplet()); // bail out if requested to not try at all if env::var_os("VCPKGRS_DISABLE").is_some() { return Err(Error::DisabledByEnv("VCPKGRS_DISABLE".to_owned())); } // bail out if requested to not try at all (old) if env::var_os("NO_VCPKG").is_some() { return Err(Error::DisabledByEnv("NO_VCPKG".to_owned())); } // bail out if requested to skip this package let abort_var_name = format!("VCPKGRS_NO_{}", envify(port_name)); if env::var_os(&abort_var_name).is_some() { return Err(Error::DisabledByEnv(abort_var_name)); } // bail out if requested to skip this package (old) let abort_var_name = format!("{}_NO_VCPKG", envify(port_name)); if env::var_os(&abort_var_name).is_some() { return Err(Error::DisabledByEnv(abort_var_name)); } let vcpkg_target = try!(find_vcpkg_target(&self, &msvc_target)); let mut required_port_order = Vec::new(); // if no overrides have been selected, then the Vcpkg port name // is the the .lib name and the .dll name if self.required_libs.is_empty() { let ports = try!(load_ports(&vcpkg_target)); if ports.get(&port_name.to_owned()).is_none() { return Err(Error::LibNotFound(format!( "package {} is not installed for vcpkg triplet {}", port_name.to_owned(), vcpkg_target.target_triplet.triplet ))); } // the complete set of ports required let mut required_ports: BTreeMap = BTreeMap::new(); // working of ports that we need to include // let mut ports_to_scan: BTreeSet = BTreeSet::new(); // ports_to_scan.insert(port_name.to_owned()); let mut ports_to_scan = vec![port_name.to_owned()]; //: Vec = BTreeSet::new(); while !ports_to_scan.is_empty() { let port_name = ports_to_scan.pop().unwrap(); if required_ports.contains_key(&port_name) { continue; } if let Some(port) = ports.get(&port_name) { for dep in &port.deps { ports_to_scan.push(dep.clone()); } required_ports.insert(port_name.clone(), (*port).clone()); remove_item(&mut required_port_order, &port_name); required_port_order.push(port_name); } else { // what? } } // for port in ports { // println!("port {:?}", port); // } // println!("== Looking for port {}", port_name); // for port in &required_port_order { // println!("ordered required port {:?}", port); // } // println!("============================="); // for port in &required_ports { // println!("required port {:?}", port); // } // if no overrides have been selected, then the Vcpkg port name // is the the .lib name and the .dll name if self.required_libs.is_empty() { for port_name in &required_port_order { let port = required_ports.get(port_name).unwrap(); self.required_libs.extend(port.libs.iter().map(|s| { Path::new(&s) .file_stem() .unwrap() .to_string_lossy() .into_owned() })); self.required_dlls .extend(port.dlls.iter().cloned().map(|s| { Path::new(&s) .file_stem() .unwrap() .to_string_lossy() .into_owned() })); } } } // require explicit opt-in before using dynamically linked // variants, otherwise cargo install of various things will // stop working if Vcpkg is installed. if !vcpkg_target.target_triplet.is_static && !env::var_os("VCPKGRS_DYNAMIC").is_some() { return Err(Error::RequiredEnvMissing("VCPKGRS_DYNAMIC".to_owned())); } let mut lib = Library::new( vcpkg_target.target_triplet.is_static, &vcpkg_target.target_triplet.triplet, ); if self.emit_includes { lib.cargo_metadata.push(format!( "cargo:include={}", vcpkg_target.include_path.display() )); } lib.include_paths.push(vcpkg_target.include_path.clone()); lib.cargo_metadata.push(format!( "cargo:rustc-link-search=native={}", vcpkg_target .lib_path .to_str() .expect("failed to convert string type") )); lib.link_paths.push(vcpkg_target.lib_path.clone()); if !vcpkg_target.target_triplet.is_static { lib.cargo_metadata.push(format!( "cargo:rustc-link-search=native={}", vcpkg_target .bin_path .to_str() .expect("failed to convert string type") )); // this path is dropped by recent versions of cargo hence the copies to OUT_DIR below lib.dll_paths.push(vcpkg_target.bin_path.clone()); } lib.ports = required_port_order; try!(self.emit_libs(&mut lib, &vcpkg_target)); if self.copy_dlls { try!(self.do_dll_copy(&mut lib)); } if self.cargo_metadata { for line in &lib.cargo_metadata { println!("{}", line); } } Ok(lib) } /// Define whether metadata should be emitted for cargo allowing it to /// automatically link the binary. Defaults to `true`. pub fn cargo_metadata(&mut self, cargo_metadata: bool) -> &mut Config { self.cargo_metadata = cargo_metadata; self } /// Define cargo:include= metadata should be emitted. Defaults to `false`. pub fn emit_includes(&mut self, emit_includes: bool) -> &mut Config { self.emit_includes = emit_includes; self } /// Should DLLs be copied to OUT_DIR? /// Defaults to `true`. pub fn copy_dlls(&mut self, copy_dlls: bool) -> &mut Config { self.copy_dlls = copy_dlls; self } /// Define which path to use as vcpkg root overriding the VCPKG_ROOT environment variable /// Default to `None`, which means use VCPKG_ROOT or try to find out automatically pub fn vcpkg_root(&mut self, vcpkg_root: PathBuf) -> &mut Config { self.vcpkg_root = Some(vcpkg_root); self } /// Specify target triplet. When triplet is not specified, inferred triplet from rust target is used. /// /// Specifying a triplet using `target_triplet` will override the default triplet for this crate. This /// cannot change the choice of triplet made by other crates, so a safer choice will be to set /// `VCPKGRS_TRIPLET` in the environment which will allow all crates to use a consistent set of /// external dependencies. pub fn target_triplet>(&mut self, triplet: S) -> &mut Config { self.target = Some(triplet.into()); self } /// Find the library `port_name` in a Vcpkg tree. /// /// This will use all configuration previously set to select the /// architecture and linkage. /// Deprecated in favor of the find_package function #[doc(hidden)] pub fn probe(&mut self, port_name: &str) -> Result { // determine the target type, bailing out if it is not some // kind of msvc let msvc_target = try!(self.get_target_triplet()); // bail out if requested to not try at all if env::var_os("VCPKGRS_DISABLE").is_some() { return Err(Error::DisabledByEnv("VCPKGRS_DISABLE".to_owned())); } // bail out if requested to not try at all (old) if env::var_os("NO_VCPKG").is_some() { return Err(Error::DisabledByEnv("NO_VCPKG".to_owned())); } // bail out if requested to skip this package let abort_var_name = format!("VCPKGRS_NO_{}", envify(port_name)); if env::var_os(&abort_var_name).is_some() { return Err(Error::DisabledByEnv(abort_var_name)); } // bail out if requested to skip this package (old) let abort_var_name = format!("{}_NO_VCPKG", envify(port_name)); if env::var_os(&abort_var_name).is_some() { return Err(Error::DisabledByEnv(abort_var_name)); } // if no overrides have been selected, then the Vcpkg port name // is the the .lib name and the .dll name if self.required_libs.is_empty() { self.required_libs.push(port_name.to_owned()); self.required_dlls.push(port_name.to_owned()); } let vcpkg_target = try!(find_vcpkg_target(&self, &msvc_target)); // require explicit opt-in before using dynamically linked // variants, otherwise cargo install of various things will // stop working if Vcpkg is installed. if !vcpkg_target.target_triplet.is_static && !env::var_os("VCPKGRS_DYNAMIC").is_some() { return Err(Error::RequiredEnvMissing("VCPKGRS_DYNAMIC".to_owned())); } let mut lib = Library::new( vcpkg_target.target_triplet.is_static, &vcpkg_target.target_triplet.triplet, ); if self.emit_includes { lib.cargo_metadata.push(format!( "cargo:include={}", vcpkg_target.include_path.display() )); } lib.include_paths.push(vcpkg_target.include_path.clone()); lib.cargo_metadata.push(format!( "cargo:rustc-link-search=native={}", vcpkg_target .lib_path .to_str() .expect("failed to convert string type") )); lib.link_paths.push(vcpkg_target.lib_path.clone()); if !vcpkg_target.target_triplet.is_static { lib.cargo_metadata.push(format!( "cargo:rustc-link-search=native={}", vcpkg_target .bin_path .to_str() .expect("failed to convert string type") )); // this path is dropped by recent versions of cargo hence the copies to OUT_DIR below lib.dll_paths.push(vcpkg_target.bin_path.clone()); } try!(self.emit_libs(&mut lib, &vcpkg_target)); if self.copy_dlls { try!(self.do_dll_copy(&mut lib)); } if self.cargo_metadata { for line in &lib.cargo_metadata { println!("{}", line); } } Ok(lib) } fn emit_libs(&mut self, lib: &mut Library, vcpkg_target: &VcpkgTarget) -> Result<(), Error> { for required_lib in &self.required_libs { // this could use static-nobundle= for static libraries but it is apparently // not necessary to make the distinction for windows-msvc. let link_name = match vcpkg_target.target_triplet.strip_lib_prefix { true => required_lib.trim_left_matches("lib"), false => required_lib, }; lib.cargo_metadata .push(format!("cargo:rustc-link-lib={}", link_name)); lib.found_names.push(String::from(link_name)); // verify that the library exists let mut lib_location = vcpkg_target.lib_path.clone(); lib_location.push(required_lib.clone() + "." + &vcpkg_target.target_triplet.lib_suffix); if !lib_location.exists() { return Err(Error::LibNotFound(lib_location.display().to_string())); } lib.found_libs.push(lib_location); } if !vcpkg_target.target_triplet.is_static { for required_dll in &self.required_dlls { let mut dll_location = vcpkg_target.bin_path.clone(); dll_location.push(required_dll.clone() + ".dll"); // verify that the DLL exists if !dll_location.exists() { return Err(Error::LibNotFound(dll_location.display().to_string())); } lib.found_dlls.push(dll_location); } } Ok(()) } fn do_dll_copy(&mut self, lib: &mut Library) -> Result<(), Error> { if let Some(target_dir) = env::var_os("OUT_DIR") { if !lib.found_dlls.is_empty() { for file in &lib.found_dlls { let mut dest_path = Path::new(target_dir.as_os_str()).to_path_buf(); dest_path.push(Path::new(file.file_name().unwrap())); try!( fs::copy(file, &dest_path).map_err(|_| Error::LibNotFound(format!( "Can't copy file {} to {}", file.to_string_lossy(), dest_path.to_string_lossy() ))) ); println!( "vcpkg build helper copied {} to {}", file.to_string_lossy(), dest_path.to_string_lossy() ); } lib.cargo_metadata.push(format!( "cargo:rustc-link-search=native={}", env::var("OUT_DIR").unwrap() )); // work around https://github.com/rust-lang/cargo/issues/3957 lib.cargo_metadata.push(format!( "cargo:rustc-link-search={}", env::var("OUT_DIR").unwrap() )); } } else { return Err(Error::LibNotFound("Unable to get OUT_DIR".to_owned())); } Ok(()) } /// Override the name of the library to look for if it differs from the package name. /// /// It should not be necessary to use `lib_name` anymore. Calling `find_package` with a package name /// will result in the correct library names. /// This may be called more than once if multiple libs are required. /// All libs must be found for the probe to succeed. `.probe()` must /// be run with a different configuration to look for libraries under one of several names. /// `.libname("ssleay32")` will look for ssleay32.lib and also ssleay32.dll if /// dynamic linking is selected. pub fn lib_name(&mut self, lib_stem: &str) -> &mut Config { self.required_libs.push(lib_stem.to_owned()); self.required_dlls.push(lib_stem.to_owned()); self } /// Override the name of the library to look for if it differs from the package name. /// /// It should not be necessary to use `lib_names` anymore. Calling `find_package` with a package name /// will result in the correct library names. /// This may be called more than once if multiple libs are required. /// All libs must be found for the probe to succeed. `.probe()` must /// be run with a different configuration to look for libraries under one of several names. /// `.lib_names("libcurl_imp","curl")` will look for libcurl_imp.lib and also curl.dll if /// dynamic linking is selected. pub fn lib_names(&mut self, lib_stem: &str, dll_stem: &str) -> &mut Config { self.required_libs.push(lib_stem.to_owned()); self.required_dlls.push(dll_stem.to_owned()); self } } fn remove_item(cont: &mut Vec, item: &String) -> Option { match cont.iter().position(|x| *x == *item) { Some(pos) => Some(cont.remove(pos)), None => None, } } impl Library { fn new(is_static: bool, vcpkg_triplet: &str) -> Library { Library { link_paths: Vec::new(), dll_paths: Vec::new(), include_paths: Vec::new(), cargo_metadata: Vec::new(), is_static: is_static, found_dlls: Vec::new(), found_libs: Vec::new(), found_names: Vec::new(), ports: Vec::new(), vcpkg_triplet: vcpkg_triplet.to_string(), } } } fn envify(name: &str) -> String { name.chars() .map(|c| c.to_ascii_uppercase()) .map(|c| if c == '-' { '_' } else { c }) .collect() } fn msvc_target() -> Result { let is_definitely_dynamic = env::var("VCPKGRS_DYNAMIC").is_ok(); let target = env::var("TARGET").unwrap_or(String::new()); let is_static = env::var("CARGO_CFG_TARGET_FEATURE") .unwrap_or(String::new()) // rustc 1.10 .contains("crt-static"); if target == "x86_64-apple-darwin" { Ok(TargetTriplet { triplet: "x64-osx".into(), is_static: true, lib_suffix: "a".into(), strip_lib_prefix: true, }) } else if target == "aarch64-apple-darwin" { Ok(TargetTriplet { triplet: "arm64-osx".into(), is_static: true, lib_suffix: "a".into(), strip_lib_prefix: true, }) } else if target == "x86_64-unknown-linux-gnu" { Ok(TargetTriplet { triplet: "x64-linux".into(), is_static: true, lib_suffix: "a".into(), strip_lib_prefix: true, }) } else if target == "aarch64-apple-ios" { Ok(TargetTriplet { triplet: "arm64-ios".into(), is_static: true, lib_suffix: "a".into(), strip_lib_prefix: true, }) } else if !target.contains("-pc-windows-msvc") { Err(Error::NotMSVC) } else if target.starts_with("x86_64-") { if is_static { Ok(TargetTriplet { triplet: "x64-windows-static".into(), is_static: true, lib_suffix: "lib".into(), strip_lib_prefix: false, }) } else if is_definitely_dynamic { Ok(TargetTriplet { triplet: "x64-windows".into(), is_static: false, lib_suffix: "lib".into(), strip_lib_prefix: false, }) } else { Ok(TargetTriplet { triplet: "x64-windows-static-md".into(), is_static: true, lib_suffix: "lib".into(), strip_lib_prefix: false, }) } } else if target.starts_with("aarch64") { if is_static { Ok(TargetTriplet { triplet: "arm64-windows-static".into(), is_static: true, lib_suffix: "lib".into(), strip_lib_prefix: false, }) } else if is_definitely_dynamic { Ok(TargetTriplet { triplet: "arm64-windows".into(), is_static: false, lib_suffix: "lib".into(), strip_lib_prefix: false, }) } else { Ok(TargetTriplet { triplet: "arm64-windows-static-md".into(), is_static: true, lib_suffix: "lib".into(), strip_lib_prefix: false, }) } } else { // everything else is x86 if is_static { Ok(TargetTriplet { triplet: "x86-windows-static".into(), is_static: true, lib_suffix: "lib".into(), strip_lib_prefix: false, }) } else if is_definitely_dynamic { Ok(TargetTriplet { triplet: "x86-windows".into(), is_static: false, lib_suffix: "lib".into(), strip_lib_prefix: false, }) } else { Ok(TargetTriplet { triplet: "x86-windows-static-md".into(), is_static: true, lib_suffix: "lib".into(), strip_lib_prefix: false, }) } } } #[cfg(test)] mod tests { extern crate tempdir; use super::*; use std::env; use std::sync::Mutex; lazy_static! { static ref LOCK: Mutex<()> = Mutex::new(()); } #[test] fn do_nothing_for_unsupported_target() { let _g = LOCK.lock(); env::set_var("VCPKG_ROOT", "/"); env::set_var("TARGET", "x86_64-pc-windows-gnu"); assert!(match ::probe_package("foo") { Err(Error::NotMSVC) => true, _ => false, }); env::set_var("TARGET", "x86_64-pc-windows-gnu"); assert_eq!(env::var("TARGET"), Ok("x86_64-pc-windows-gnu".to_string())); assert!(match ::probe_package("foo") { Err(Error::NotMSVC) => true, _ => false, }); env::remove_var("TARGET"); env::remove_var("VCPKG_ROOT"); } #[test] fn do_nothing_for_bailout_variables_set() { let _g = LOCK.lock(); env::set_var("VCPKG_ROOT", "/"); env::set_var("TARGET", "x86_64-pc-windows-msvc"); for &var in &[ "VCPKGRS_DISABLE", "VCPKGRS_NO_FOO", "FOO_NO_VCPKG", "NO_VCPKG", ] { env::set_var(var, "1"); assert!(match ::probe_package("foo") { Err(Error::DisabledByEnv(ref v)) if v == var => true, _ => false, }); env::remove_var(var); } env::remove_var("TARGET"); env::remove_var("VCPKG_ROOT"); } // these tests are good but are leaning on a real vcpkg installation // #[test] // fn default_build_refuses_dynamic() { // let _g = LOCK.lock(); // clean_env(); // env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("no-status")); // env::set_var("TARGET", "x86_64-pc-windows-msvc"); // println!("Result is {:?}", ::find_package("libmysql")); // assert!(match ::find_package("libmysql") { // Err(Error::RequiredEnvMissing(ref v)) if v == "VCPKGRS_DYNAMIC" => true, // _ => false, // }); // clean_env(); // } #[test] fn static_build_finds_lib() { let _g = LOCK.lock(); clean_env(); env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("normalized")); env::set_var("TARGET", "x86_64-pc-windows-msvc"); let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap(); env::set_var("OUT_DIR", tmp_dir.path()); // CARGO_CFG_TARGET_FEATURE is set in response to // RUSTFLAGS=-Ctarget-feature=+crt-static. It would // be nice to test that also. env::set_var("CARGO_CFG_TARGET_FEATURE", "crt-static"); println!("Result is {:?}", ::find_package("libmysql")); assert!(match ::find_package("libmysql") { Ok(_) => true, _ => false, }); clean_env(); } #[test] fn dynamic_build_finds_lib() { let _g = LOCK.lock(); clean_env(); env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("no-status")); env::set_var("TARGET", "x86_64-pc-windows-msvc"); env::set_var("VCPKGRS_DYNAMIC", "1"); let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap(); env::set_var("OUT_DIR", tmp_dir.path()); println!("Result is {:?}", ::find_package("libmysql")); assert!(match ::find_package("libmysql") { Ok(_) => true, _ => false, }); clean_env(); } #[test] fn handle_multiline_description() { let _g = LOCK.lock(); clean_env(); env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("multiline-description")); env::set_var("TARGET", "i686-pc-windows-msvc"); env::set_var("VCPKGRS_DYNAMIC", "1"); let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap(); env::set_var("OUT_DIR", tmp_dir.path()); println!("Result is {:?}", ::find_package("graphite2")); assert!(match ::find_package("graphite2") { Ok(_) => true, _ => false, }); clean_env(); } #[test] fn link_libs_required_by_optional_features() { let _g = LOCK.lock(); clean_env(); env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("normalized")); env::set_var("TARGET", "i686-pc-windows-msvc"); env::set_var("VCPKGRS_DYNAMIC", "1"); let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap(); env::set_var("OUT_DIR", tmp_dir.path()); println!("Result is {:?}", ::find_package("harfbuzz")); assert!(match ::find_package("harfbuzz") { Ok(lib) => lib .cargo_metadata .iter() .find(|&x| x == "cargo:rustc-link-lib=icuuc") .is_some(), _ => false, }); clean_env(); } #[test] fn link_lib_name_is_correct() { let _g = LOCK.lock(); for target in &[ "x86_64-apple-darwin", "i686-pc-windows-msvc", // "x86_64-pc-windows-msvc", // "x86_64-unknown-linux-gnu", ] { clean_env(); env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("normalized")); env::set_var("TARGET", target); env::set_var("VCPKGRS_DYNAMIC", "1"); let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap(); env::set_var("OUT_DIR", tmp_dir.path()); println!("Result is {:?}", ::find_package("harfbuzz")); assert!(match ::find_package("harfbuzz") { Ok(lib) => lib .cargo_metadata .iter() .find(|&x| x == "cargo:rustc-link-lib=harfbuzz") .is_some(), _ => false, }); clean_env(); } } #[test] fn link_dependencies_after_port() { let _g = LOCK.lock(); clean_env(); env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("normalized")); env::set_var("TARGET", "i686-pc-windows-msvc"); env::set_var("VCPKGRS_DYNAMIC", "1"); let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap(); env::set_var("OUT_DIR", tmp_dir.path()); let lib = ::find_package("harfbuzz").unwrap(); check_before(&lib, "freetype", "zlib"); check_before(&lib, "freetype", "bzip2"); check_before(&lib, "freetype", "libpng"); check_before(&lib, "harfbuzz", "freetype"); check_before(&lib, "harfbuzz", "ragel"); check_before(&lib, "libpng", "zlib"); clean_env(); fn check_before(lib: &Library, earlier: &str, later: &str) { match ( lib.ports.iter().position(|x| *x == *earlier), lib.ports.iter().position(|x| *x == *later), ) { (Some(earlier_pos), Some(later_pos)) if earlier_pos < later_pos => { // ok } _ => { println!( "earlier: {}, later: {}\nLibrary found: {:#?}", earlier, later, lib ); panic!(); } } } } #[test] fn custom_target_triplet_in_config() { let _g = LOCK.lock(); clean_env(); env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("normalized")); env::set_var("TARGET", "aarch64-apple-ios"); env::set_var("VCPKGRS_DYNAMIC", "1"); let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap(); env::set_var("OUT_DIR", tmp_dir.path()); let harfbuzz = ::Config::new() // For the sake of testing, force this build to try to // link to the arm64-osx libraries in preference to the // default of arm64-ios. .target_triplet("x64-osx") .find_package("harfbuzz"); println!("Result with specifying target triplet is {:?}", &harfbuzz); let harfbuzz = harfbuzz.unwrap(); assert_eq!(harfbuzz.vcpkg_triplet, "x64-osx"); clean_env(); } #[test] fn custom_target_triplet_by_env_no_default() { let _g = LOCK.lock(); clean_env(); env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("normalized")); env::set_var("TARGET", "aarch64-apple-doesnotexist"); env::set_var("VCPKGRS_DYNAMIC", "1"); let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap(); env::set_var("OUT_DIR", tmp_dir.path()); let harfbuzz = ::find_package("harfbuzz"); println!("Result with inference is {:?}", &harfbuzz); assert!(harfbuzz.is_err()); env::set_var("VCPKGRS_TRIPLET", "x64-osx"); let harfbuzz = ::find_package("harfbuzz").unwrap(); println!("Result with setting VCPKGRS_TRIPLET is {:?}", &harfbuzz); assert_eq!(harfbuzz.vcpkg_triplet, "x64-osx"); clean_env(); } #[test] fn custom_target_triplet_by_env_with_default() { let _g = LOCK.lock(); clean_env(); env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("normalized")); env::set_var("TARGET", "aarch64-apple-ios"); env::set_var("VCPKGRS_DYNAMIC", "1"); let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap(); env::set_var("OUT_DIR", tmp_dir.path()); let harfbuzz = ::find_package("harfbuzz").unwrap(); println!("Result with inference is {:?}", &harfbuzz); assert_eq!(harfbuzz.vcpkg_triplet, "arm64-ios"); env::set_var("VCPKGRS_TRIPLET", "x64-osx"); let harfbuzz = ::find_package("harfbuzz").unwrap(); println!("Result with setting VCPKGRS_TRIPLET is {:?}", &harfbuzz); assert_eq!(harfbuzz.vcpkg_triplet, "x64-osx"); clean_env(); } // #[test] // fn dynamic_build_package_specific_bailout() { // clean_env(); // env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("no-status")); // env::set_var("TARGET", "x86_64-pc-windows-msvc"); // env::set_var("VCPKGRS_DYNAMIC", "1"); // env::set_var("VCPKGRS_NO_LIBMYSQL", "1"); // println!("Result is {:?}", ::find_package("libmysql")); // assert!(match ::find_package("libmysql") { // Err(Error::DisabledByEnv(ref v)) if v == "VCPKGRS_NO_LIBMYSQL" => true, // _ => false, // }); // clean_env(); // } // #[test] // fn dynamic_build_global_bailout() { // clean_env(); // env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("no-status")); // env::set_var("TARGET", "x86_64-pc-windows-msvc"); // env::set_var("VCPKGRS_DYNAMIC", "1"); // env::set_var("VCPKGRS_DISABLE", "1"); // println!("Result is {:?}", ::find_package("libmysql")); // assert!(match ::find_package("libmysql") { // Err(Error::DisabledByEnv(ref v)) if v == "VCPKGRS_DISABLE" => true, // _ => false, // }); // clean_env(); // } #[test] fn pc_files_reordering() { let _g = LOCK.lock(); clean_env(); env::set_var("VCPKG_ROOT", vcpkg_test_tree_loc("normalized")); env::set_var("TARGET", "x86_64-unknown-linux-gnu"); // env::set_var("VCPKGRS_DYNAMIC", "1"); let tmp_dir = tempdir::TempDir::new("vcpkg_tests").unwrap(); env::set_var("OUT_DIR", tmp_dir.path()); let target_triplet = msvc_target().unwrap(); // The brotli use-case. { let mut pc_files = PcFiles { files: HashMap::new(), }; pc_files.files.insert( "libbrotlicommon".to_owned(), PcFile::from_str( "libbrotlicommon", "Libs: -lbrotlicommon-static\nRequires:", &target_triplet, ) .unwrap(), ); pc_files.files.insert( "libbrotlienc".to_owned(), PcFile::from_str( "libbrotlienc", "Libs: -lbrotlienc-static\nRequires: libbrotlicommon", &target_triplet, ) .unwrap(), ); pc_files.files.insert( "libbrotlidec".to_owned(), PcFile::from_str( "brotlidec", "Libs: -lbrotlidec-static\nRequires: libbrotlicommon >= 1.0.9", &target_triplet, ) .unwrap(), ); // Note that the input is alphabetically sorted. let input_libs = vec![ "libbrotlicommon-static.a".to_owned(), "libbrotlidec-static.a".to_owned(), "libbrotlienc-static.a".to_owned(), ]; let output_libs = pc_files.fix_ordering(input_libs); assert_eq!(output_libs[0], "libbrotlidec-static.a"); assert_eq!(output_libs[1], "libbrotlienc-static.a"); assert_eq!(output_libs[2], "libbrotlicommon-static.a"); } // Concoct elaborate dependency graph, try all variations of input sort. // Throw some (ignored) version dependencies as well as extra libs not represented in the // pc_files dataset. { let mut pc_files = PcFiles { files: HashMap::new(), }; pc_files.files.insert( "libA".to_owned(), PcFile::from_str( "libA", "Libs: -lA\n\ Requires:", &target_triplet, ) .unwrap(), ); pc_files.files.insert( "libB".to_owned(), PcFile::from_str( "libB", "Libs: -lB -lm -pthread \n\ Requires: libA", &target_triplet, ) .unwrap(), ); pc_files.files.insert( "libC".to_owned(), PcFile::from_str( "libC", "Libs: -lC -L${libdir}\n\ Requires: libB <=1.0 , libmysql-client = 0.9, ", &target_triplet, ) .unwrap(), ); pc_files.files.insert( "libD".to_owned(), PcFile::from_str( "libD", "Libs: -Lpath/to/libs -Rplugins -lD\n\ Requires: libpostgres libC", &target_triplet, ) .unwrap(), ); let permutations: Vec> = vec![ vec!["libA.a", "libB.a", "libC.a", "libD.a"], vec!["libA.a", "libB.a", "libD.a", "libC.a"], vec!["libA.a", "libC.a", "libB.a", "libD.a"], vec!["libA.a", "libC.a", "libD.a", "libB.a"], vec!["libA.a", "libD.a", "libB.a", "libC.a"], vec!["libA.a", "libD.a", "libC.a", "libB.a"], // vec!["libB.a", "libA.a", "libC.a", "libD.a"], vec!["libB.a", "libA.a", "libD.a", "libC.a"], vec!["libB.a", "libC.a", "libA.a", "libD.a"], vec!["libB.a", "libC.a", "libD.a", "libA.a"], vec!["libB.a", "libD.a", "libA.a", "libC.a"], vec!["libB.a", "libD.a", "libC.a", "libA.a"], // vec!["libC.a", "libA.a", "libB.a", "libD.a"], vec!["libC.a", "libA.a", "libD.a", "libB.a"], vec!["libC.a", "libB.a", "libA.a", "libD.a"], vec!["libC.a", "libB.a", "libD.a", "libA.a"], vec!["libC.a", "libD.a", "libA.a", "libB.a"], vec!["libC.a", "libD.a", "libB.a", "libA.a"], // vec!["libD.a", "libA.a", "libB.a", "libC.a"], vec!["libD.a", "libA.a", "libC.a", "libB.a"], vec!["libD.a", "libB.a", "libA.a", "libC.a"], vec!["libD.a", "libB.a", "libC.a", "libA.a"], vec!["libD.a", "libC.a", "libA.a", "libB.a"], vec!["libD.a", "libC.a", "libB.a", "libA.a"], ]; for permutation in permutations { let input_libs = vec![ permutation[0].to_owned(), permutation[1].to_owned(), permutation[2].to_owned(), permutation[3].to_owned(), ]; let output_libs = pc_files.fix_ordering(input_libs); assert_eq!(output_libs.len(), 4); assert_eq!(output_libs[0], "libD.a"); assert_eq!(output_libs[1], "libC.a"); assert_eq!(output_libs[2], "libB.a"); assert_eq!(output_libs[3], "libA.a"); } } // Test parsing of a couple different Requires: lines. { let pc_file = PcFile::from_str( "test", "Libs: -ltest\n\ Requires: cairo libpng", &target_triplet, ) .unwrap(); assert_eq!(pc_file.deps, vec!["cairo", "libpng"]); let pc_file = PcFile::from_str( "test", "Libs: -ltest\n\ Requires: cairo xcb >= 1.6 xcb-render >= 1.6", &target_triplet, ) .unwrap(); assert_eq!(pc_file.deps, vec!["cairo", "xcb", "xcb-render"]); let pc_file = PcFile::from_str( "test", "Libs: -ltest\n\ Requires: glib-2.0, gobject-2.0", &target_triplet, ) .unwrap(); assert_eq!(pc_file.deps, vec!["glib-2.0", "gobject-2.0"]); let pc_file = PcFile::from_str( "test", "Libs: -ltest\n\ Requires: glib-2.0 >= 2.58.0, gobject-2.0 >= 2.58.0", &target_triplet, ) .unwrap(); assert_eq!(pc_file.deps, vec!["glib-2.0", "gobject-2.0"]); } clean_env(); } fn clean_env() { env::remove_var("TARGET"); env::remove_var("VCPKG_ROOT"); env::remove_var("VCPKGRS_DYNAMIC"); env::remove_var("RUSTFLAGS"); env::remove_var("CARGO_CFG_TARGET_FEATURE"); env::remove_var("VCPKGRS_DISABLE"); env::remove_var("VCPKGRS_NO_LIBMYSQL"); env::remove_var("VCPKGRS_TRIPLET"); } // path to a to vcpkg installation to test against fn vcpkg_test_tree_loc(name: &str) -> PathBuf { let mut path = PathBuf::new(); path.push(env::var("CARGO_MANIFEST_DIR").unwrap()); path.push("test-data"); path.push(name); path } }