diff --git a/Cargo.toml b/Cargo.toml index 8e5328e..eddce60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,16 +1,24 @@ [package] -name = "metadeps" -version = "1.1.2" -authors = ["Josh Triplett "] +name = "system-deps" +version = "1.3.2" +authors = ["Guillaume Desmottes ", "Josh Triplett "] license = "MIT/Apache-2.0" -repository = "https://github.com/joshtriplett/metadeps" -description = "Run pkg-config from declarative dependencies in Cargo.toml" +repository = "https://github.com/gdesmott/system-deps" +description = "Discover and configure system dependencies from declarative dependencies in Cargo.toml" keywords = ["pkg-config", "build-dependencies", "build-depends", "manifest", "metadata"] +edition = "2018" +documentation = "https://docs.rs/system-deps/" +readme = "README.md" [dependencies] -error-chain = { version = "0.10", default-features = false } -pkg-config = "0.3.8" -toml = { version = "0.2", default-features = false } +pkg-config = "0.3" +toml = { version = "0.5", default-features = false } +version-compare = "0.0.11" +heck = "0.3" +strum = "0.19" +strum_macros = "0.19" +thiserror = "1" [dev-dependencies] lazy_static = "1" +itertools = "0.9" diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..16fe87b --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..31aa793 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 26db2d6..f211815 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,46 @@ -metadeps lets you write `pkg-config` dependencies in `Cargo.toml` metadata, -rather than programmatically in `build.rs`. This makes those dependencies +# system-deps [![](https://img.shields.io/crates/v/system-deps.svg)](https://crates.io/crates/system-deps) [![](https://docs.rs/system-deps/badge.svg)](https://docs.rs/system-deps) + +`system-deps` lets you write system dependencies in `Cargo.toml` metadata, +rather than programmatically in `build.rs`. This makes those dependencies declarative, so other tools can read them as well. -# Usage +For now only `pkg-config` dependencies are supported but we are planning to +[expand it](https://github.com/gdesmott/system-deps/issues/3) at some point. + +Users can override dependency flags using environment variables if needed. +`system-deps` also allows `-sys` crates to optionally internally build and +static link the required system library. + +`system-deps` has been started as a fork of the +[metadeps](https://github.com/joshtriplett/metadeps) project. + +## Documentation -In your `Cargo.toml`, add the following to your `[build-dependencies]`: +See the [crate documentation](https://docs.rs/system-deps/). + +## Usage + +In your `Cargo.toml`: ```toml -metadeps = "1.1" +[build-dependencies] +system-deps = "1.3" ``` -Then, to declare a dependency on `testlib >= 1.2`, and a conditional dependency -on `testdata >= 4.5`, add the following section: +Then, to declare a dependency on `testlib >= 1.2` +add the following section: ```toml -[package.metadata.pkg-config] +[package.metadata.system-deps] testlib = "1.2" -testdata = { version = "4.5", feature = "use-testdata" } ``` -In your `build.rs`, add: +Finally, in your `build.rs`, add: ```rust -extern crate metadeps; - fn main() { - metadeps::probe().unwrap(); + system_deps::Config::new().probe().unwrap(); } ``` + +See the [crate documentation](https://docs.rs/system-deps/) for more advanced features. diff --git a/src/lib.rs b/src/lib.rs index 699afce..6f7967d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,84 +1,872 @@ -//! metadeps lets you write `pkg-config` dependencies in `Cargo.toml` metadata, -//! rather than programmatically in `build.rs`. This makes those dependencies +#![allow(clippy::needless_doctest_main)] +//!`system-deps` lets you write system dependencies in `Cargo.toml` metadata, +//! rather than programmatically in `build.rs`. This makes those dependencies //! declarative, so other tools can read them as well. //! -//! metadeps parses metadata like this in `Cargo.toml`: +//! # Usage +//! In your `Cargo.toml`: //! //! ```toml -//! [package.metadata.pkg-config] +//! [build-dependencies] +//! system-deps = "1.3" +//! ``` +//! +//! Then, to declare a dependency on `testlib >= 1.2` +//! add the following section: +//! +//! ```toml +//! [package.metadata.system-deps] //! testlib = "1.2" -//! testdata = { version = "4.5", feature = "some-feature" } //! ``` +//! +//! Finally, in your `build.rs`, add: +//! +//! ```should_panic +//! fn main() { +//! system_deps::Config::new().probe().unwrap(); +//! } +//! ``` +//! +//! # Optional dependency +//! You can easily declare an optional system dependency by associating it with a feature: +//! +//! ```toml +//! [package.metadata.system-deps] +//! testdata = { version = "4.5", feature = "use-testdata" } +//! ``` +//! +//! # Overriding library name +//! `toml` keys cannot contain dot characters so if your library name does you can define it using the `name` field: +//! +//! ```toml +//! [package.metadata.system-deps] +//! glib = { name = "glib-2.0", version = "2.64" } +//! ``` +//! # Feature versions +//! `-sys` crates willing to support various versions of their underlying system libraries +//! can use features to control the version of the dependency required. +//! `system-deps` will pick the highest version among enabled features. +//! Such version features all have to start with "v". +//! +//! ```toml +//! [features] +//! v1_2 = [] +//! v1_4 = ["v1_2"] +//! v1_6 = ["v1_4"] +//! +//! [package.metadata.system-deps.gstreamer_1_0] +//! name = "gstreamer-1.0" +//! version = "1.0" +//! +//! [package.metadata.system-deps.gstreamer_1_0.v1_2] +//! version = "1.2" +//! [package.metadata.system-deps.gstreamer_1_0.v1_4] +//! version = "1.4" +//! [package.metadata.system-deps.gstreamer_1_0.v1_6] +//! version = "1.6" +//! ``` +//! +//! The same mechanism can be used to require a different library name depending on the version: +//! +//! ```toml +//! [package.metadata.system-deps.gst_gl] +//! name = "gstreamer-gl-1.0" +//! version = "1.14" +//! +//! [package.metadata.system-deps.gst_gl.v1_18] +//! version = "1.18" +//! name = "gstreamer-gl-egl-1.0" +//! ``` +//! +//! # Overriding build flags +//! By default `system-deps` automatically defines the required build flags for each dependency using the information fetched from `pkg-config`. +//! These flags can be overriden using environment variables if needed: +//! - `SYSTEM_DEPS_$NAME_SEARCH_NATIVE` to override the [`cargo:rustc-link-search=native`](https://doc.rust-lang.org/cargo/reference/build-scripts.html#cargorustc-link-searchkindpath) flag; +//! - `SYSTEM_DEPS_$NAME_SEARCH_FRAMEWORK` to override the [`cargo:rustc-link-search=framework`](https://doc.rust-lang.org/cargo/reference/build-scripts.html#cargorustc-link-searchkindpath) flag; +//! - `SYSTEM_DEPS_$NAME_LIB` to override the [`cargo:rustc-link-lib`](https://doc.rust-lang.org/cargo/reference/build-scripts.html#rustc-link-lib) flag; +//! - `SYSTEM_DEPS_$NAME_LIB_FRAMEWORK` to override the [`cargo:rustc-link-lib=framework`](https://doc.rust-lang.org/cargo/reference/build-scripts.html#rustc-link-lib) flag; +//! - `SYSTEM_DEPS_$NAME_INCLUDE` to override the [`cargo:include`](https://kornel.ski/rust-sys-crate#headers) flag. +//! +//! With `$NAME` being the upper case name of the key defining the dependency in `Cargo.toml`. +//! For example `SYSTEM_DEPS_TESTLIB_SEARCH_NATIVE=/opt/lib` could be used to override a dependency named `testlib`. +//! +//! One can also define the environment variable `SYSTEM_DEPS_$NAME_NO_PKG_CONFIG` to fully disable `pkg-config` lookup +//! for the given dependency. In this case at least SYSTEM_DEPS_$NAME_LIB or SYSTEM_DEPS_$NAME_LIB_FRAMEWORK should be defined as well. +//! +//! # Statically build system library +//! `-sys` crates can provide support for building and statically link their underlying system libray as part of their build process. +//! Here is how to do this in your `build.rs`: +//! ```should_panic +//! fn main() { +//! system_deps::Config::new() +//! .add_build_internal("testlib", |lib, version| { +//! // Actually build the library here +//! system_deps::Library::from_internal_pkg_config("build/path-to-pc-file", lib, version) +//! }) +//! .probe() +//! .unwrap(); +//! } +//! ``` +//! +//! This feature can be controlled using the `SYSTEM_DEPS_$NAME_BUILD_INTERNAL` environment variable +//! which can have the following values: +//! - `auto`: build the dependency only if the required version has not been found by `pkg-config`; +//! - `always`: always build the dependency, ignoring any version which may be installed on the system; +//! - `never`: (default) never build the dependency, `system-deps` will fail if the required version is not found on the system. +//! +//! You can also use the `SYSTEM_DEPS_BUILD_INTERNAL` environment variable with the same values +//! defining the behavior for all the dependencies which don't have `SYSTEM_DEPS_$NAME_BUILD_INTERNAL` defined. -#![deny(missing_docs, warnings)] +#![deny(missing_docs)] +#[cfg(test)] #[macro_use] -extern crate error_chain; -extern crate pkg_config; -extern crate toml; +extern crate lazy_static; +#[cfg(test)] +mod test; + +use heck::ShoutySnakeCase; use std::collections::HashMap; use std::env; +use std::fmt; use std::fs; use std::io::Read; -use std::path::PathBuf; -use pkg_config::{Config, Library}; - -error_chain! { - foreign_links { - PkgConfig(pkg_config::Error) #[doc="pkg-config error"]; - } -} - -/// Probe all libraries configured in the Cargo.toml -/// `[package.metadata.pkg-config]` section. -pub fn probe() -> Result> { - let dir = try!(env::var_os("CARGO_MANIFEST_DIR").ok_or("$CARGO_MANIFEST_DIR not set")); - let mut path = PathBuf::from(dir); - path.push("Cargo.toml"); - let mut manifest = try!(fs::File::open(&path).chain_err(|| - format!("Error opening {}", path.display()) - )); - let mut manifest_str = String::new(); - try!(manifest.read_to_string(&mut manifest_str).chain_err(|| - format!("Error reading {}", path.display()) - )); - let toml = try!(manifest_str.parse::().map_err(|e| - format!("Error parsing TOML from {}: {:?}", path.display(), e) - )); - let key = "package.metadata.pkg-config"; - let meta = try!(toml.lookup(key).ok_or( - format!("No {} in {}", key, path.display()) - )); - let table = try!(meta.as_table().ok_or( - format!("{} not a table in {}", key, path.display()) - )); - let mut libraries = HashMap::new(); - for (name, value) in table { - let ref version = match value { - &toml::Value::String(ref s) => s, - &toml::Value::Table(ref t) => { - let mut feature = None; - let mut version = None; - for (tname, tvalue) in t { - match (tname.as_str(), tvalue) { - ("feature", &toml::Value::String(ref s)) => { feature = Some(s); } - ("version", &toml::Value::String(ref s)) => { version = Some(s); } - _ => bail!("Unexpected key {}.{}.{} type {}", key, name, tname, tvalue.type_str()), +use std::path::{Path, PathBuf}; +use std::str::FromStr; +use strum::IntoEnumIterator; +use strum_macros::{EnumIter, EnumString}; +use thiserror::Error; +use version_compare::VersionCompare; + +/// system-deps errors +#[derive(Error, Debug)] +pub enum Error { + /// pkg-config error + #[error(transparent)] + PkgConfig(#[from] pkg_config::Error), + /// One of the `Config::add_build_internal` closures failed + #[error("Failed to build {0}: {1}")] + BuildInternalClosureError(String, #[source] BuildInternalClosureError), + /// Failed to read `Cargo.toml` + #[error("{0}")] + FailToRead(String, #[source] std::io::Error), + /// Raised when an error is detected in the metadata defined in `Cargo.toml` + #[error("{0}")] + InvalidMetadata(String), + /// Raised when dependency defined manually using `SYSTEM_DEPS_$NAME_NO_PKG_CONFIG` + /// did not define at least one lib using `SYSTEM_DEPS_$NAME_LIB` or + /// `SYSTEM_DEPS_$NAME_LIB_FRAMEWORK` + #[error("You should define at least one lib using {} or {}", EnvVariable::new_lib(.0).to_string(), EnvVariable::new_lib_framework(.0))] + MissingLib(String), + /// An environment variable in the form of `SYSTEM_DEPS_$NAME_BUILD_INTERNAL` + /// contained an invalid value (allowed: `auto`, `always`, `never`) + #[error("{0}")] + BuildInternalInvalid(String), + /// system-deps has been asked to internally build a lib, through + /// `SYSTEM_DEPS_$NAME_BUILD_INTERNAL=always' or `SYSTEM_DEPS_$NAME_BUILD_INTERNAL=auto', + /// but not closure has been defined using `Config::add_build_internal` to build + /// this lib + #[error("Missing build internal closure for {0} (version {1})")] + BuildInternalNoClosure(String, String), + /// The library which has been build internally does not match the + /// required version defined in `Cargo.toml` + #[error("Internally built {0} {1} but minimum required version is {2}")] + BuildInternalWrongVersion(String, String, String), +} + +#[derive(Error, Debug)] +/// Error used in return value of `Config::add_build_internal` closures +pub enum BuildInternalClosureError { + /// `pkg-config` error + #[error(transparent)] + PkgConfig(#[from] pkg_config::Error), + /// General failure + #[error("{0}")] + Failed(String), +} + +impl BuildInternalClosureError { + /// Create a new `BuildInternalClosureError::Failed` representing a general + /// failure. + /// + /// # Arguments + /// + /// * `details`: human-readable details about the failure + pub fn failed(details: &str) -> Self { + Self::Failed(details.to_string()) + } +} + +// enums representing the environment variables user can define to tune system-deps +#[derive(Debug, PartialEq, EnumIter)] +enum EnvVariable { + Lib(String), + LibFramework(String), + SearchNative(String), + SearchFramework(String), + Include(String), + NoPkgConfig(String), + BuildInternal(Option), +} + +struct FeatureOverride { + version: String, + name: Option, +} + +impl FeatureOverride { + fn new(version: &str, name: Option<&String>) -> Self { + Self { + version: version.to_string(), + name: name.cloned(), + } + } +} + +impl EnvVariable { + fn new_lib(lib: &str) -> Self { + Self::Lib(lib.to_string()) + } + + fn new_lib_framework(lib: &str) -> Self { + Self::LibFramework(lib.to_string()) + } + + fn new_search_native(lib: &str) -> Self { + Self::SearchNative(lib.to_string()) + } + + fn new_search_framework(lib: &str) -> Self { + Self::SearchFramework(lib.to_string()) + } + + fn new_include(lib: &str) -> Self { + Self::Include(lib.to_string()) + } + + fn new_no_pkg_config(lib: &str) -> Self { + Self::NoPkgConfig(lib.to_string()) + } + + fn new_build_internal(lib: Option<&str>) -> Self { + Self::BuildInternal(lib.map(|l| l.to_string())) + } + + fn suffix(&self) -> &'static str { + match self { + EnvVariable::Lib(_) => "LIB", + EnvVariable::LibFramework(_) => "LIB_FRAMEWORK", + EnvVariable::SearchNative(_) => "SEARCH_NATIVE", + EnvVariable::SearchFramework(_) => "SEARCH_FRAMEWORK", + EnvVariable::Include(_) => "INCLUDE", + EnvVariable::NoPkgConfig(_) => "NO_PKG_CONFIG", + EnvVariable::BuildInternal(_) => "BUILD_INTERNAL", + } + } +} + +impl fmt::Display for EnvVariable { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let suffix = match self { + EnvVariable::Lib(lib) + | EnvVariable::LibFramework(lib) + | EnvVariable::SearchNative(lib) + | EnvVariable::SearchFramework(lib) + | EnvVariable::Include(lib) + | EnvVariable::NoPkgConfig(lib) + | EnvVariable::BuildInternal(Some(lib)) => { + format!("{}_{}", lib.to_shouty_snake_case(), self.suffix()) + } + EnvVariable::BuildInternal(None) => self.suffix().to_string(), + }; + write!(f, "SYSTEM_DEPS_{}", suffix) + } +} + +type FnBuildInternal = + dyn FnOnce(&str, &str) -> std::result::Result; + +/// Structure used to configure `metadata` before starting to probe for dependencies +pub struct Config { + env: EnvVariables, + build_internals: HashMap>, +} + +impl Default for Config { + fn default() -> Self { + Self::new_with_env(EnvVariables::Environnement) + } +} + +impl Config { + /// Create a new set of configuration + pub fn new() -> Self { + Self::default() + } + + fn new_with_env(env: EnvVariables) -> Self { + Self { + env, + build_internals: HashMap::new(), + } + } + + /// Probe all libraries configured in the Cargo.toml + /// `[package.metadata.system-deps]` section. + pub fn probe(self) -> Result, Error> { + let (libraries, flags) = self.probe_full()?; + + // Output cargo flags + println!("{}", flags); + + Ok(libraries) + } + + /// Add hook so system-deps can internally build library `name` if requested by user. + /// + /// It will only be triggered if the environment variable + /// `SYSTEM_DEPS_$NAME_BUILD_INTERNAL` is defined with either `always` or + /// `auto` as value. In the latter case, `func` is called only if the requested + /// version of the library was not found on the system. + /// + /// # Arguments + /// * `name`: the name of the library, as defined in `Cargo.toml` + /// * `func`: closure called when internally building the library. + /// It receives as argument the library name and the minimum version required. + pub fn add_build_internal(self, name: &str, func: F) -> Self + where + F: 'static + FnOnce(&str, &str) -> std::result::Result, + { + let mut build_internals = self.build_internals; + build_internals.insert(name.to_string(), Box::new(func)); + + Self { + env: self.env, + build_internals, + } + } + + fn probe_full(mut self) -> Result<(HashMap, BuildFlags), Error> { + let mut libraries = self.probe_pkg_config()?; + self.override_from_flags(&mut libraries); + let flags = self.gen_flags(&libraries)?; + + Ok((libraries, flags)) + } + + fn probe_pkg_config(&mut self) -> Result, Error> { + let dir = self + .env + .get("CARGO_MANIFEST_DIR") + .ok_or_else(|| Error::InvalidMetadata("$CARGO_MANIFEST_DIR not set".into()))?; + let mut path = PathBuf::from(dir); + path.push("Cargo.toml"); + let mut manifest = fs::File::open(&path) + .map_err(|e| Error::FailToRead(format!("Error opening {}", path.display()), e))?; + let mut manifest_str = String::new(); + manifest + .read_to_string(&mut manifest_str) + .map_err(|e| Error::FailToRead(format!("Error reading {}", path.display()), e))?; + let toml = manifest_str.parse::().map_err(|e| { + Error::InvalidMetadata(format!( + "Error parsing TOML from {}: {:?}", + path.display(), + e + )) + })?; + let key = "package.metadata.system-deps"; + let meta = toml + .get("package") + .and_then(|v| v.get("metadata")) + .and_then(|v| v.get("system-deps")) + .ok_or_else(|| Error::InvalidMetadata(format!("No {} in {}", key, path.display())))?; + let table = meta.as_table().ok_or_else(|| { + Error::InvalidMetadata(format!("{} not a table in {}", key, path.display())) + })?; + let mut libraries = HashMap::new(); + for (name, value) in table { + let (lib_name, version) = match value { + toml::Value::String(ref s) => (name.to_string(), s.to_string()), + toml::Value::Table(ref t) => { + let mut feature = None; + let mut version = None; + let mut lib_name = None; + let mut enabled_feature_overrides = Vec::new(); + for (tname, tvalue) in t { + match (tname.as_str(), tvalue) { + ("feature", &toml::Value::String(ref s)) => { + feature = Some(s); + } + ("version", &toml::Value::String(ref s)) => { + version = Some(s); + } + ("name", &toml::Value::String(ref s)) => { + lib_name = Some(s); + } + (version_feature, &toml::Value::Table(ref version_settings)) + if version_feature.starts_with("v") => + { + let mut override_version = None; + let mut override_name = None; + + for (k, v) in version_settings { + match (k.as_str(), v) { + ("version", &toml::Value::String(ref feat_vers)) => { + override_version = Some(feat_vers); + } + ("name", &toml::Value::String(ref feat_name)) => { + override_name = Some(feat_name); + } + _ => { + return Err(Error::InvalidMetadata(format!( + "Unexpected version settings key: {}.{}.{}.{} type: {}", + key, + name, + tname, + k, + v.type_str() + ))) + } + } + } + + let override_version = override_version.ok_or_else(|| { + Error::InvalidMetadata(format!( + "Missing version field for {}.{}.{}", + key, name, tvalue + )) + })?; + + if self.has_feature(&version_feature) { + let f_override = + FeatureOverride::new(&override_version, override_name); + enabled_feature_overrides.push(f_override); + } + } + _ => { + return Err(Error::InvalidMetadata(format!( + "Unexpected key {}.{}.{} type {}", + key, + name, + tname, + tvalue.type_str() + ))) + } + } + } + if let Some(feature) = feature { + if !self.has_feature(feature) { + continue; + } } + + let (version, lib_name) = { + // Pick the highest feature enabled version + if !enabled_feature_overrides.is_empty() { + enabled_feature_overrides.sort_by(|a, b| { + VersionCompare::compare(&b.version, &a.version) + .expect("failed to compare versions") + .ord() + .expect("invalid version") + }); + let o = &enabled_feature_overrides[0]; + (Some(o.version.clone()), o.name.clone()) + } else { + (version.cloned(), lib_name.cloned()) + } + }; + + ( + lib_name.unwrap_or(name.to_string()), + version.ok_or_else(|| { + Error::InvalidMetadata(format!("No version in {}.{}", key, name)) + })?, + ) + } + _ => { + return Err(Error::InvalidMetadata(format!( + "{}.{} not a string or table", + key, name + ))) } - if let Some(feature) = feature { - let var = format!("CARGO_FEATURE_{}", feature.to_uppercase().replace('-', "_")); - if env::var_os(var).is_none() { - continue; + }; + + let build_internal = self.get_build_internal_status(name)?; + + let library = if self.env.contains(&EnvVariable::new_no_pkg_config(name)) { + Library::from_env_variables() + } else if build_internal == BuildInternal::Always { + self.call_build_internal(&lib_name, &version)? + } else { + match pkg_config::Config::new() + .atleast_version(&version) + .print_system_libs(false) + .cargo_metadata(false) + .probe(&lib_name) + { + Ok(lib) => Library::from_pkg_config(lib), + Err(e) => { + if build_internal == BuildInternal::Auto { + // Try building the lib internally as a fallback + self.call_build_internal(name, &version)? + } else { + return Err(e.into()); + } } } - try!(version.ok_or(format!("No version in {}.{}", key, name))) + }; + + libraries.insert(name.clone(), library); + } + Ok(libraries) + } + + fn get_build_internal_env_var(&self, var: EnvVariable) -> Result, Error> { + match self.env.get(&var).as_deref() { + Some(s) => { + let b = BuildInternal::from_str(s).map_err(|_| { + Error::BuildInternalInvalid(format!( + "Invalid value in {}: {} (allowed: 'auto', 'always', 'never')", + var, s + )) + })?; + Ok(Some(b)) } - _ => bail!("{}.{} not a string or table", key, name), + None => Ok(None), + } + } + + fn get_build_internal_status(&self, name: &str) -> Result { + match self.get_build_internal_env_var(EnvVariable::new_build_internal(Some(name)))? { + Some(b) => Ok(b), + None => Ok(self + .get_build_internal_env_var(EnvVariable::new_build_internal(None))? + .unwrap_or_default()), + } + } + + fn call_build_internal(&mut self, name: &str, version: &str) -> Result { + let lib = match self.build_internals.remove(name) { + Some(f) => { + f(name, version).map_err(|e| Error::BuildInternalClosureError(name.into(), e))? + } + None => return Err(Error::BuildInternalNoClosure(name.into(), version.into())), }; - let library = try!(Config::new().atleast_version(&version).probe(name)); - libraries.insert(name.clone(), library); + + // Check that the lib built internally matches the required version + match VersionCompare::compare(&lib.version, version) { + Ok(version_compare::CompOp::Lt) => Err(Error::BuildInternalWrongVersion( + name.into(), + lib.version.clone(), + version.into(), + )), + _ => Ok(lib), + } + } + + fn override_from_flags(&self, libraries: &mut HashMap) { + for (name, lib) in libraries.iter_mut() { + if let Some(value) = self.env.get(&EnvVariable::new_search_native(name)) { + lib.link_paths = split_paths(&value); + } + if let Some(value) = self.env.get(&EnvVariable::new_search_framework(name)) { + lib.framework_paths = split_paths(&value); + } + if let Some(value) = self.env.get(&EnvVariable::new_lib(name)) { + lib.libs = split_string(&value); + } + if let Some(value) = self.env.get(&EnvVariable::new_lib_framework(name)) { + lib.frameworks = split_string(&value); + } + if let Some(value) = self.env.get(&EnvVariable::new_include(name)) { + lib.include_paths = split_paths(&value); + } + } + } + + fn gen_flags(&self, libraries: &HashMap) -> Result { + let mut flags = BuildFlags::new(); + let mut include_paths = Vec::new(); + + for (name, lib) in libraries.iter() { + include_paths.extend(lib.include_paths.clone()); + + if lib.source == Source::EnvVariables + && lib.libs.is_empty() + && lib.frameworks.is_empty() + { + return Err(Error::MissingLib(name.clone())); + } + + lib.link_paths + .iter() + .for_each(|l| flags.add(BuildFlag::SearchNative(l.to_string_lossy().to_string()))); + lib.framework_paths.iter().for_each(|f| { + flags.add(BuildFlag::SearchFramework(f.to_string_lossy().to_string())) + }); + lib.libs + .iter() + .for_each(|l| flags.add(BuildFlag::Lib(l.clone()))); + lib.frameworks + .iter() + .for_each(|f| flags.add(BuildFlag::LibFramework(f.clone()))); + } + + // Export DEP_$CRATE_INCLUDE env variable with the headers paths, + // see https://kornel.ski/rust-sys-crate#headers + if !include_paths.is_empty() { + if let Ok(paths) = std::env::join_paths(include_paths) { + flags.add(BuildFlag::Include(paths.to_string_lossy().to_string())); + } + } + + // Export cargo:rerun-if-env-changed instructions for all env variables affecting system-deps behaviour + flags.add(BuildFlag::RerunIfEnvChanged( + EnvVariable::new_build_internal(None), + )); + + for (name, _lib) in libraries.iter() { + for var in EnvVariable::iter() { + let var = match var { + EnvVariable::Lib(_) => EnvVariable::new_lib(name), + EnvVariable::LibFramework(_) => EnvVariable::new_lib_framework(name), + EnvVariable::SearchNative(_) => EnvVariable::new_search_native(name), + EnvVariable::SearchFramework(_) => EnvVariable::new_search_framework(name), + EnvVariable::Include(_) => EnvVariable::new_include(name), + EnvVariable::NoPkgConfig(_) => EnvVariable::new_no_pkg_config(name), + EnvVariable::BuildInternal(_) => EnvVariable::new_build_internal(Some(name)), + }; + flags.add(BuildFlag::RerunIfEnvChanged(var)); + } + } + + Ok(flags) + } + + fn has_feature(&self, feature: &str) -> bool { + let var: &str = &format!("CARGO_FEATURE_{}", feature.to_uppercase().replace('-', "_")); + self.env.contains(var) + } +} + +#[derive(Debug, PartialEq)] +/// From where the library settings have been retrieved +pub enum Source { + /// Settings have been retrieved from `pkg-config` + PkgConfig, + /// Settings have been defined using user defined environment variables + EnvVariables, +} + +#[derive(Debug)] +/// A system dependency +pub struct Library { + /// From where the library settings have been retrieved + pub source: Source, + /// libraries the linker should link on + pub libs: Vec, + /// directories where the compiler should look for libraries + pub link_paths: Vec, + /// frameworks the linker should link on + pub frameworks: Vec, + /// directories where the compiler should look for frameworks + pub framework_paths: Vec, + /// directories where the compiler should look for header files + pub include_paths: Vec, + /// macros that should be defined by the compiler + pub defines: HashMap>, + /// library version + pub version: String, +} + +impl Library { + fn from_pkg_config(l: pkg_config::Library) -> Self { + Self { + source: Source::PkgConfig, + libs: l.libs, + link_paths: l.link_paths, + include_paths: l.include_paths, + frameworks: l.frameworks, + framework_paths: l.framework_paths, + defines: l.defines, + version: l.version, + } + } + + fn from_env_variables() -> Self { + Self { + source: Source::EnvVariables, + libs: Vec::new(), + link_paths: Vec::new(), + include_paths: Vec::new(), + frameworks: Vec::new(), + framework_paths: Vec::new(), + defines: HashMap::new(), + version: String::new(), + } + } + + /// Create a `Library` by probing `pkg-config` on an internal directory. + /// This helper is meant to be used by `Config::add_build_internal` closures + /// after having built the lib to return the library information to system-deps. + /// + /// # Arguments + /// + /// * `pkg_config_dir`: the directory where the library `.pc` file is located + /// * `lib`: the name of the library to look for + /// * `version`: the minimum version of `lib` required + /// + /// # Examples + /// + /// ``` + /// let mut config = system_deps::Config::new(); + /// config.add_build_internal("mylib", |lib, version| { + /// // Actually build the library here + /// system_deps::Library::from_internal_pkg_config("build-dir", + /// lib, version) + /// }); + /// ``` + pub fn from_internal_pkg_config

( + pkg_config_dir: P, + lib: &str, + version: &str, + ) -> Result + where + P: AsRef, + { + // save current PKG_CONFIG_PATH so we can restore it + let old = env::var("PKG_CONFIG_PATH"); + + match old { + Ok(ref s) => { + let paths = [s, &pkg_config_dir.as_ref().to_string_lossy().to_string()]; + let paths = env::join_paths(paths.iter()).unwrap(); + env::set_var("PKG_CONFIG_PATH", paths) + } + Err(_) => env::set_var("PKG_CONFIG_PATH", pkg_config_dir.as_ref()), + } + + let lib = pkg_config::Config::new() + .atleast_version(&version) + .print_system_libs(false) + .cargo_metadata(false) + .probe(lib); + + env::set_var("PKG_CONFIG_PATH", &old.unwrap_or_else(|_| "".into())); + + match lib { + Ok(lib) => Ok(Self::from_pkg_config(lib)), + Err(e) => Err(e.into()), + } + } +} + +#[derive(Debug)] +enum EnvVariables { + Environnement, + #[cfg(test)] + Mock(HashMap<&'static str, String>), +} + +trait EnvVariablesExt { + fn contains(&self, var: T) -> bool { + self.get(var).is_some() + } + fn get(&self, var: T) -> Option; +} + +impl EnvVariablesExt<&str> for EnvVariables { + fn get(&self, var: &str) -> Option { + match self { + EnvVariables::Environnement => env::var(var).ok(), + #[cfg(test)] + EnvVariables::Mock(vars) => vars.get(var).cloned(), + } + } +} + +impl EnvVariablesExt<&EnvVariable> for EnvVariables { + fn get(&self, var: &EnvVariable) -> Option { + let s = var.to_string(); + let var: &str = s.as_ref(); + self.get(var) + } +} + +// TODO: add support for "rustc-link-lib=static=" ? +#[derive(Debug, PartialEq)] +enum BuildFlag { + Include(String), + SearchNative(String), + SearchFramework(String), + Lib(String), + LibFramework(String), + RerunIfEnvChanged(EnvVariable), +} + +impl fmt::Display for BuildFlag { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + BuildFlag::Include(paths) => write!(f, "include={}", paths), + BuildFlag::SearchNative(lib) => write!(f, "rustc-link-search=native={}", lib), + BuildFlag::SearchFramework(lib) => write!(f, "rustc-link-search=framework={}", lib), + BuildFlag::Lib(lib) => write!(f, "rustc-link-lib={}", lib), + BuildFlag::LibFramework(lib) => write!(f, "rustc-link-lib=framework={}", lib), + BuildFlag::RerunIfEnvChanged(env) => write!(f, "rerun-if-env-changed={}", env), + } + } +} + +#[derive(Debug, PartialEq)] +struct BuildFlags(Vec); + +impl BuildFlags { + fn new() -> Self { + Self(Vec::new()) + } + + fn add(&mut self, flag: BuildFlag) { + self.0.push(flag); + } +} + +impl fmt::Display for BuildFlags { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for flag in self.0.iter() { + writeln!(f, "cargo:{}", flag)?; + } + Ok(()) + } +} + +fn split_paths(value: &str) -> Vec { + if !value.is_empty() { + let paths = env::split_paths(&value); + paths.map(|p| Path::new(&p).into()).collect() + } else { + Vec::new() + } +} + +fn split_string(value: &str) -> Vec { + if !value.is_empty() { + value.split(' ').map(|s| s.to_string()).collect() + } else { + Vec::new() + } +} + +#[derive(Debug, PartialEq, EnumString)] +#[strum(serialize_all = "snake_case")] +enum BuildInternal { + Auto, + Always, + Never, +} + +impl Default for BuildInternal { + fn default() -> Self { + BuildInternal::Never } - Ok(libraries) } diff --git a/src/test.rs b/src/test.rs new file mode 100644 index 0000000..3c14187 --- /dev/null +++ b/src/test.rs @@ -0,0 +1,740 @@ +use itertools::Itertools; +use pkg_config; +use std::cell::Cell; +use std::collections::HashMap; +use std::env; +use std::path::{Path, PathBuf}; +use std::rc::Rc; +use std::sync::Mutex; + +use super::{BuildFlags, BuildInternalClosureError, Config, EnvVariables, Error, Library}; + +lazy_static! { + static ref LOCK: Mutex<()> = Mutex::new(()); +} + +fn create_config(path: &str, env: Vec<(&'static str, &'static str)>) -> Config { + { + // PKG_CONFIG_PATH is read by pkg-config so we need to actually change the env + let _l = LOCK.lock(); + env::set_var( + "PKG_CONFIG_PATH", + &env::current_dir().unwrap().join("src").join("tests"), + ); + } + + let mut hash = HashMap::new(); + hash.insert( + "CARGO_MANIFEST_DIR", + env::current_dir() + .unwrap() + .join("src") + .join("tests") + .join(path) + .to_string_lossy() + .to_string(), + ); + + hash.insert("CARGO_FEATURE_TEST_FEATURE", "".to_string()); + env.iter().for_each(|(k, v)| { + hash.insert(k, v.to_string()); + }); + + Config::new_with_env(EnvVariables::Mock(hash)) +} + +fn toml( + path: &str, + env: Vec<(&'static str, &'static str)>, +) -> Result<(std::collections::HashMap, BuildFlags), Error> { + create_config(path, env).probe_full() +} + +fn assert_flags(flags: BuildFlags, expected: &str) { + // flags ordering isn't guaranteed so sort them out before comparing + let flags = flags.to_string().split("\n").sorted().join("\n"); + let expected = expected.to_string().split("\n").sorted().join("\n"); + assert_eq!(flags, expected); +} + +#[test] +fn good() { + let (libraries, flags) = toml("toml-good", vec![]).unwrap(); + let testlib = libraries.get("testlib").unwrap(); + assert_eq!(testlib.version, "1.2.3"); + let testdata = libraries.get("testdata").unwrap(); + assert_eq!(testdata.version, "4.5.6"); + assert!(libraries.get("testmore").is_none()); + + assert_flags( + flags, + r#"cargo:rustc-link-search=native=/usr/lib/x86_64-linux-gnu +cargo:rustc-link-search=framework=/usr/lib/x86_64-linux-gnu +cargo:rustc-link-lib=test +cargo:rustc-link-lib=framework=someframework +cargo:include=/usr/include/testlib +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_INCLUDE +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_LIB +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_LIB_FRAMEWORK +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_NO_PKG_CONFIG +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_SEARCH_FRAMEWORK +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_SEARCH_NATIVE +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_INCLUDE +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_LIB +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_LIB_FRAMEWORK +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_NO_PKG_CONFIG +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_SEARCH_FRAMEWORK +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_SEARCH_NATIVE +cargo:rerun-if-env-changed=SYSTEM_DEPS_BUILD_INTERNAL +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_BUILD_INTERNAL +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_BUILD_INTERNAL +"#, + ); +} + +fn toml_err(path: &str, err_starts_with: &str) { + let err = toml(path, vec![]).unwrap_err(); + if !err.to_string().starts_with(err_starts_with) { + panic!( + "Expected error to start with: {:?}\nGot error: {:?}", + err_starts_with, err + ); + } +} + +// Assert a PkgConfig error because requested lib version cannot be found +fn toml_pkg_config_err_version( + path: &str, + expected_version: &str, + env_vars: Vec<(&'static str, &'static str)>, +) { + let err = toml(path, env_vars).unwrap_err(); + match err { + Error::PkgConfig(e) => match e { + pkg_config::Error::Failure { + command: cmd, + output: _, + } => { + let s = format!(">= {}\"", expected_version); + assert!(cmd.ends_with(&s)); + } + _ => panic!("Wrong pkg-config error type"), + }, + _ => panic!("Wrong error type"), + } +} + +#[test] +fn missing_file() { + toml_err("toml-missing-file", "Error opening"); +} + +#[test] +fn missing_key() { + toml_err("toml-missing-key", "No package.metadata.system-deps in"); +} + +#[test] +fn not_table() { + toml_err( + "toml-not-table", + "package.metadata.system-deps not a table in", + ); +} + +#[test] +fn version_missing() { + toml_err( + "toml-version-missing", + "No version in package.metadata.system-deps.testlib", + ); +} + +#[test] +fn version_not_string() { + toml_err( + "toml-version-not-string", + "package.metadata.system-deps.testlib not a string or table", + ); +} + +#[test] +fn version_in_table_not_string() { + toml_err( + "toml-version-in-table-not-string", + "Unexpected key package.metadata.system-deps.testlib.version type integer", + ); +} + +#[test] +fn feature_not_string() { + toml_err( + "toml-feature-not-string", + "Unexpected key package.metadata.system-deps.testlib.feature type integer", + ); +} + +#[test] +fn unexpected_key() { + toml_err( + "toml-unexpected-key", + "Unexpected key package.metadata.system-deps.testlib.color type string", + ); +} + +#[test] +fn override_name() { + let (libraries, _) = toml("toml-override-name", vec![]).unwrap(); + let testlib = libraries.get("testlib").unwrap(); + assert_eq!(testlib.version, "2.0.0"); +} + +#[test] +fn feature_versions() { + let (libraries, _) = toml("toml-feature-versions", vec![]).unwrap(); + let testdata = libraries.get("testdata").unwrap(); + assert_eq!(testdata.version, "4.5.6"); + + // version 5 is not available + env::set_var("CARGO_FEATURE_V5", ""); + toml_pkg_config_err_version("toml-feature-versions", "5", vec![("CARGO_FEATURE_V5", "")]); + + // We check the highest version enabled by features + env::set_var("CARGO_FEATURE_V6", ""); + toml_pkg_config_err_version("toml-feature-versions", "6", vec![("CARGO_FEATURE_V6", "")]); + + let (libraries, _) = toml("toml-version-names", vec![]).unwrap(); + let testlib = libraries.get("testlib").unwrap(); + assert_eq!(testlib.version, "1.2.3"); + + // Enable feature v2 + let (libraries, _) = toml("toml-version-names", vec![("CARGO_FEATURE_V2", "")]).unwrap(); + let testlib = libraries.get("testlib").unwrap(); + assert_eq!(testlib.version, "2.0.0"); +} + +#[test] +fn override_search_native() { + let (libraries, flags) = toml( + "toml-good", + vec![( + "SYSTEM_DEPS_TESTLIB_SEARCH_NATIVE", + "/custom/path:/other/path", + )], + ) + .unwrap(); + let testlib = libraries.get("testlib").unwrap(); + assert_eq!( + testlib.link_paths, + vec![Path::new("/custom/path"), Path::new("/other/path")] + ); + + assert_flags( + flags, + r#"cargo:rustc-link-search=native=/custom/path +cargo:rustc-link-search=native=/other/path +cargo:rustc-link-search=framework=/usr/lib/x86_64-linux-gnu +cargo:rustc-link-lib=test +cargo:rustc-link-lib=framework=someframework +cargo:include=/usr/include/testlib +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_INCLUDE +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_LIB +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_LIB_FRAMEWORK +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_NO_PKG_CONFIG +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_SEARCH_FRAMEWORK +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_SEARCH_NATIVE +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_INCLUDE +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_LIB +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_LIB_FRAMEWORK +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_NO_PKG_CONFIG +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_SEARCH_FRAMEWORK +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_SEARCH_NATIVE +cargo:rerun-if-env-changed=SYSTEM_DEPS_BUILD_INTERNAL +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_BUILD_INTERNAL +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_BUILD_INTERNAL +"#, + ); +} + +#[test] +fn override_search_framework() { + let (libraries, flags) = toml( + "toml-good", + vec![("SYSTEM_DEPS_TESTLIB_SEARCH_FRAMEWORK", "/custom/path")], + ) + .unwrap(); + let testlib = libraries.get("testlib").unwrap(); + assert_eq!(testlib.framework_paths, vec![Path::new("/custom/path")]); + + assert_flags( + flags, + r#"cargo:rustc-link-search=native=/usr/lib/x86_64-linux-gnu +cargo:rustc-link-search=framework=/custom/path +cargo:rustc-link-lib=test +cargo:rustc-link-lib=framework=someframework +cargo:include=/usr/include/testlib +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_INCLUDE +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_LIB +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_LIB_FRAMEWORK +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_NO_PKG_CONFIG +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_SEARCH_FRAMEWORK +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_SEARCH_NATIVE +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_INCLUDE +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_LIB +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_LIB_FRAMEWORK +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_NO_PKG_CONFIG +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_SEARCH_FRAMEWORK +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_SEARCH_NATIVE +cargo:rerun-if-env-changed=SYSTEM_DEPS_BUILD_INTERNAL +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_BUILD_INTERNAL +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_BUILD_INTERNAL +"#, + ); +} + +#[test] +fn override_lib() { + let (libraries, flags) = toml( + "toml-good", + vec![("SYSTEM_DEPS_TESTLIB_LIB", "overrided-test other-test")], + ) + .unwrap(); + let testlib = libraries.get("testlib").unwrap(); + assert_eq!(testlib.libs, vec!["overrided-test", "other-test"]); + + assert_flags( + flags, + r#"cargo:rustc-link-search=native=/usr/lib/x86_64-linux-gnu +cargo:rustc-link-search=framework=/usr/lib/x86_64-linux-gnu +cargo:rustc-link-lib=overrided-test +cargo:rustc-link-lib=other-test +cargo:rustc-link-lib=framework=someframework +cargo:include=/usr/include/testlib +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_INCLUDE +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_LIB +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_LIB_FRAMEWORK +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_NO_PKG_CONFIG +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_SEARCH_FRAMEWORK +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_SEARCH_NATIVE +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_INCLUDE +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_LIB +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_LIB_FRAMEWORK +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_NO_PKG_CONFIG +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_SEARCH_FRAMEWORK +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_SEARCH_NATIVE +cargo:rerun-if-env-changed=SYSTEM_DEPS_BUILD_INTERNAL +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_BUILD_INTERNAL +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_BUILD_INTERNAL +"#, + ); +} + +#[test] +fn override_framework() { + let (libraries, flags) = toml( + "toml-good", + vec![("SYSTEM_DEPS_TESTLIB_LIB_FRAMEWORK", "overrided-framework")], + ) + .unwrap(); + let testlib = libraries.get("testlib").unwrap(); + assert_eq!(testlib.frameworks, vec!["overrided-framework"]); + + assert_flags( + flags, + r#"cargo:rustc-link-search=native=/usr/lib/x86_64-linux-gnu +cargo:rustc-link-search=framework=/usr/lib/x86_64-linux-gnu +cargo:rustc-link-lib=test +cargo:rustc-link-lib=framework=overrided-framework +cargo:include=/usr/include/testlib +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_INCLUDE +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_LIB +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_LIB_FRAMEWORK +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_NO_PKG_CONFIG +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_SEARCH_FRAMEWORK +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_SEARCH_NATIVE +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_INCLUDE +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_LIB +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_LIB_FRAMEWORK +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_NO_PKG_CONFIG +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_SEARCH_FRAMEWORK +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_SEARCH_NATIVE +cargo:rerun-if-env-changed=SYSTEM_DEPS_BUILD_INTERNAL +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_BUILD_INTERNAL +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_BUILD_INTERNAL +"#, + ); +} + +#[test] +fn override_include() { + let (libraries, flags) = toml( + "toml-good", + vec![("SYSTEM_DEPS_TESTLIB_INCLUDE", "/other/include")], + ) + .unwrap(); + let testlib = libraries.get("testlib").unwrap(); + assert_eq!(testlib.include_paths, vec![Path::new("/other/include")]); + + assert_flags( + flags, + r#"cargo:rustc-link-search=native=/usr/lib/x86_64-linux-gnu +cargo:rustc-link-search=framework=/usr/lib/x86_64-linux-gnu +cargo:rustc-link-lib=test +cargo:rustc-link-lib=framework=someframework +cargo:include=/other/include +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_INCLUDE +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_LIB +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_LIB_FRAMEWORK +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_NO_PKG_CONFIG +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_SEARCH_FRAMEWORK +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_SEARCH_NATIVE +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_INCLUDE +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_LIB +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_LIB_FRAMEWORK +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_NO_PKG_CONFIG +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_SEARCH_FRAMEWORK +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_SEARCH_NATIVE +cargo:rerun-if-env-changed=SYSTEM_DEPS_BUILD_INTERNAL +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_BUILD_INTERNAL +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_BUILD_INTERNAL +"#, + ); +} + +#[test] +fn override_unset() { + let (libraries, flags) = toml( + "toml-good", + vec![ + ("SYSTEM_DEPS_TESTLIB_SEARCH_NATIVE", ""), + ("SYSTEM_DEPS_TESTLIB_SEARCH_FRAMEWORK", ""), + ("SYSTEM_DEPS_TESTLIB_LIB", ""), + ("SYSTEM_DEPS_TESTLIB_LIB_FRAMEWORK", ""), + ("SYSTEM_DEPS_TESTLIB_INCLUDE", ""), + ], + ) + .unwrap(); + let testlib = libraries.get("testlib").unwrap(); + assert_eq!(testlib.link_paths, Vec::::new()); + assert_eq!(testlib.framework_paths, Vec::::new()); + assert_eq!(testlib.libs, Vec::::new()); + assert_eq!(testlib.frameworks, Vec::::new()); + assert_eq!(testlib.include_paths, Vec::::new()); + + assert_flags( + flags, + r"cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_INCLUDE +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_LIB +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_LIB_FRAMEWORK +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_NO_PKG_CONFIG +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_SEARCH_FRAMEWORK +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_SEARCH_NATIVE +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_INCLUDE +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_LIB +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_LIB_FRAMEWORK +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_NO_PKG_CONFIG +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_SEARCH_FRAMEWORK +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_SEARCH_NATIVE +cargo:rerun-if-env-changed=SYSTEM_DEPS_BUILD_INTERNAL +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_BUILD_INTERNAL +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_BUILD_INTERNAL +", + ); +} + +#[test] +fn override_no_pkg_config() { + let (libraries, flags) = toml( + "toml-good", + vec![ + ("SYSTEM_DEPS_TESTLIB_NO_PKG_CONFIG", "1"), + ("SYSTEM_DEPS_TESTLIB_LIB", "custom-lib"), + ], + ) + .unwrap(); + let testlib = libraries.get("testlib").unwrap(); + assert_eq!(testlib.link_paths, Vec::::new()); + assert_eq!(testlib.framework_paths, Vec::::new()); + assert_eq!(testlib.libs, vec!["custom-lib"]); + assert_eq!(testlib.frameworks, Vec::::new()); + assert_eq!(testlib.include_paths, Vec::::new()); + + assert_flags( + flags, + r"cargo:rustc-link-lib=custom-lib +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_INCLUDE +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_LIB +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_LIB_FRAMEWORK +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_NO_PKG_CONFIG +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_SEARCH_FRAMEWORK +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_SEARCH_NATIVE +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_INCLUDE +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_LIB +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_LIB_FRAMEWORK +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_NO_PKG_CONFIG +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_SEARCH_FRAMEWORK +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_SEARCH_NATIVE +cargo:rerun-if-env-changed=SYSTEM_DEPS_BUILD_INTERNAL +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTLIB_BUILD_INTERNAL +cargo:rerun-if-env-changed=SYSTEM_DEPS_TESTDATA_BUILD_INTERNAL +", + ); +} + +#[test] +fn override_no_pkg_config_error() { + let err = toml( + "toml-good", + vec![("SYSTEM_DEPS_TESTLIB_NO_PKG_CONFIG", "1")], + ) + .unwrap_err(); + assert_eq!( + err.to_string(), + "You should define at least one lib using SYSTEM_DEPS_TESTLIB_LIB or SYSTEM_DEPS_TESTLIB_LIB_FRAMEWORK" + ); +} + +fn test_build_internal( + path: &'static str, + env: Vec<(&'static str, &'static str)>, + expected_lib: &'static str, +) -> Result<(HashMap, bool), (Error, bool)> { + let called = Rc::new(Cell::new(false)); + let called_clone = called.clone(); + let config = create_config(path, env).add_build_internal(expected_lib, move |lib, version| { + called_clone.replace(true); + assert_eq!(lib, expected_lib); + let mut lib = pkg_config::Config::new() + .print_system_libs(false) + .cargo_metadata(false) + .probe(lib) + .unwrap(); + lib.version = version.to_string(); + Ok(Library::from_pkg_config(lib)) + }); + + match config.probe_full() { + Ok((libraries, _flags)) => Ok((libraries, called.get())), + Err(e) => Err((e, called.get())), + } +} + +#[test] +fn build_internal_always() { + let (libraries, called) = test_build_internal( + "toml-good", + vec![("SYSTEM_DEPS_TESTLIB_BUILD_INTERNAL", "always")], + "testlib", + ) + .unwrap(); + + assert_eq!(called, true); + assert!(libraries.get("testlib").is_some()); +} + +#[test] +fn build_internal_auto_not_called() { + // No need to build the lib as the existing version is new enough + let (libraries, called) = test_build_internal( + "toml-good", + vec![("SYSTEM_DEPS_TESTLIB_BUILD_INTERNAL", "auto")], + "testlib", + ) + .unwrap(); + + assert_eq!(called, false); + assert!(libraries.get("testlib").is_some()); +} + +#[test] +fn build_internal_auto_called() { + // Version 5 is not available so we should try building + let (libraries, called) = test_build_internal( + "toml-feature-versions", + vec![ + ("SYSTEM_DEPS_TESTDATA_BUILD_INTERNAL", "auto"), + ("CARGO_FEATURE_V5", ""), + ], + "testdata", + ) + .unwrap(); + + assert_eq!(called, true); + assert!(libraries.get("testdata").is_some()); +} + +#[test] +fn build_internal_auto_never() { + // Version 5 is not available but we forbid to build the lib + let (err, called) = test_build_internal( + "toml-feature-versions", + vec![ + ("SYSTEM_DEPS_TESTDATA_BUILD_INTERNAL", "never"), + ("CARGO_FEATURE_V5", ""), + ], + "testdata", + ) + .unwrap_err(); + + assert!(matches!(err, Error::PkgConfig(..))); + assert_eq!(called, false); +} + +#[test] +fn build_internal_always_no_closure() { + let config = create_config( + "toml-good", + vec![("SYSTEM_DEPS_TESTLIB_BUILD_INTERNAL", "always")], + ); + + let err = config.probe_full().unwrap_err(); + assert!(matches!(err, Error::BuildInternalNoClosure(..))); +} + +#[test] +fn build_internal_invalid() { + let config = create_config( + "toml-good", + vec![("SYSTEM_DEPS_TESTLIB_BUILD_INTERNAL", "badger")], + ); + + let err = config.probe_full().unwrap_err(); + assert!(matches!(err, Error::BuildInternalInvalid(..))); +} + +#[test] +fn build_internal_wrong_version() { + // Require version 5 + let called = Rc::new(Cell::new(false)); + let called_clone = called.clone(); + let config = create_config( + "toml-feature-versions", + vec![ + ("SYSTEM_DEPS_TESTDATA_BUILD_INTERNAL", "auto"), + ("CARGO_FEATURE_V5", ""), + ], + ) + .add_build_internal("testdata", move |lib, _version| { + called_clone.replace(true); + assert_eq!(lib, "testdata"); + let lib = pkg_config::Config::new() + .print_system_libs(false) + .cargo_metadata(false) + .probe(lib) + .unwrap(); + Ok(Library::from_pkg_config(lib)) + }); + + let err = config.probe_full().unwrap_err(); + assert!(matches!(err, Error::BuildInternalWrongVersion(..))); + assert_eq!(called.get(), true); +} + +#[test] +fn build_internal_fail() { + let called = Rc::new(Cell::new(false)); + let called_clone = called.clone(); + let config = create_config( + "toml-good", + vec![("SYSTEM_DEPS_TESTLIB_BUILD_INTERNAL", "always")], + ) + .add_build_internal("testlib", move |lib, _version| { + called_clone.replace(true); + assert_eq!(lib, "testlib"); + Err(BuildInternalClosureError::failed("Something went wrong")) + }); + + let err = config.probe_full().unwrap_err(); + assert!(matches!(err, Error::BuildInternalClosureError(..))); + assert_eq!(called.get(), true); +} + +#[test] +fn build_internal_always_gobal() { + let called = Rc::new(Cell::new((false, false))); + let called_clone = called.clone(); + let called_clone2 = called.clone(); + let config = create_config("toml-good", vec![("SYSTEM_DEPS_BUILD_INTERNAL", "always")]) + .add_build_internal("testlib", move |lib, version| { + let (_, b) = called_clone.get(); + called_clone.replace((true, b)); + let mut lib = pkg_config::Config::new() + .print_system_libs(false) + .cargo_metadata(false) + .probe(lib) + .unwrap(); + lib.version = version.to_string(); + Ok(Library::from_pkg_config(lib)) + }) + .add_build_internal("testdata", move |lib, version| { + let (a, _) = called_clone2.get(); + called_clone2.replace((a, true)); + let mut lib = pkg_config::Config::new() + .print_system_libs(false) + .cargo_metadata(false) + .probe(lib) + .unwrap(); + lib.version = version.to_string(); + Ok(Library::from_pkg_config(lib)) + }); + + let (libraries, _flags) = config.probe_full().unwrap(); + assert_eq!(called.get(), (true, true)); + assert!(libraries.get("testlib").is_some()); + assert!(libraries.get("testdata").is_some()); +} + +#[test] +fn build_internal_gobal_override() { + // Request to build all libs using global var but disable it for a specific one + let called = Rc::new(Cell::new((false, false))); + let called_clone = called.clone(); + let called_clone2 = called.clone(); + let config = create_config( + "toml-good", + vec![ + ("SYSTEM_DEPS_BUILD_INTERNAL", "always"), + ("SYSTEM_DEPS_TESTLIB_BUILD_INTERNAL", "never"), + ], + ) + .add_build_internal("testlib", move |lib, version| { + let (_, b) = called_clone.get(); + called_clone.replace((true, b)); + let mut lib = pkg_config::Config::new() + .print_system_libs(false) + .cargo_metadata(false) + .probe(lib) + .unwrap(); + lib.version = version.to_string(); + Ok(Library::from_pkg_config(lib)) + }) + .add_build_internal("testdata", move |lib, version| { + let (a, _) = called_clone2.get(); + called_clone2.replace((a, true)); + let mut lib = pkg_config::Config::new() + .print_system_libs(false) + .cargo_metadata(false) + .probe(lib) + .unwrap(); + lib.version = version.to_string(); + Ok(Library::from_pkg_config(lib)) + }); + + let (libraries, _flags) = config.probe_full().unwrap(); + assert_eq!(called.get(), (false, true)); + assert!(libraries.get("testlib").is_some()); + assert!(libraries.get("testdata").is_some()); +} + +#[test] +fn build_internal_override_name() { + let (libraries, called) = test_build_internal( + "toml-override-name", + vec![("SYSTEM_DEPS_BUILD_INTERNAL", "always")], + "testlib-2.0", + ) + .unwrap(); + + assert_eq!(called, true); + assert!(libraries.get("testlib").is_some()); +} diff --git a/tests/testdata.pc b/src/tests/testdata.pc similarity index 100% rename from tests/testdata.pc rename to src/tests/testdata.pc diff --git a/src/tests/testlib-2.0.pc b/src/tests/testlib-2.0.pc new file mode 100644 index 0000000..7866541 --- /dev/null +++ b/src/tests/testlib-2.0.pc @@ -0,0 +1,10 @@ +prefix=/usr +exec_prefix=${prefix} +libdir=${exec_prefix}/lib/x86_64-linux-gnu +includedir=${prefix}/include/testlib + +Name: Test Library +Description: A fake library to test pkg-config. +Version: 2.0.0 +Libs: L${libdir} -ltest +Cflags: -I${includedir} diff --git a/tests/testlib.pc b/src/tests/testlib.pc similarity index 78% rename from tests/testlib.pc rename to src/tests/testlib.pc index d330eef..4e1986f 100644 --- a/tests/testlib.pc +++ b/src/tests/testlib.pc @@ -6,5 +6,5 @@ includedir=${prefix}/include/testlib Name: Test Library Description: A fake library to test pkg-config. Version: 1.2.3 -Libs: L${libdir} -ltest +Libs: -L${libdir} -ltest -F${libdir} -framework someframework Cflags: -I${includedir} diff --git a/tests/toml-feature-not-string/Cargo.toml b/src/tests/toml-feature-not-string/Cargo.toml similarity index 56% rename from tests/toml-feature-not-string/Cargo.toml rename to src/tests/toml-feature-not-string/Cargo.toml index 5d2d7f3..e794ddd 100644 --- a/tests/toml-feature-not-string/Cargo.toml +++ b/src/tests/toml-feature-not-string/Cargo.toml @@ -1,2 +1,2 @@ -[package.metadata.pkg-config] +[package.metadata.system-deps] testlib = { version = "1", feature = 2 } diff --git a/src/tests/toml-feature-versions/Cargo.toml b/src/tests/toml-feature-versions/Cargo.toml new file mode 100644 index 0000000..c4ade2e --- /dev/null +++ b/src/tests/toml-feature-versions/Cargo.toml @@ -0,0 +1,2 @@ +[package.metadata.system-deps] +testdata = { version = "4", v5 = { version = "5" }, v6 = { version = "6" }} diff --git a/tests/toml-good/Cargo.toml b/src/tests/toml-good/Cargo.toml similarity index 80% rename from tests/toml-good/Cargo.toml rename to src/tests/toml-good/Cargo.toml index af324de..a77ad9f 100644 --- a/tests/toml-good/Cargo.toml +++ b/src/tests/toml-good/Cargo.toml @@ -1,4 +1,4 @@ -[package.metadata.pkg-config] +[package.metadata.system-deps] testdata = "4" testlib = { version = "1", feature = "test-feature" } testmore = { version = "2", feature = "another-test-feature" } diff --git a/tests/toml-missing-file/no-cargo-toml-here b/src/tests/toml-missing-file/no-cargo-toml-here similarity index 100% rename from tests/toml-missing-file/no-cargo-toml-here rename to src/tests/toml-missing-file/no-cargo-toml-here diff --git a/tests/toml-missing-key/Cargo.toml b/src/tests/toml-missing-key/Cargo.toml similarity index 100% rename from tests/toml-missing-key/Cargo.toml rename to src/tests/toml-missing-key/Cargo.toml diff --git a/src/tests/toml-not-table/Cargo.toml b/src/tests/toml-not-table/Cargo.toml new file mode 100644 index 0000000..d309622 --- /dev/null +++ b/src/tests/toml-not-table/Cargo.toml @@ -0,0 +1,2 @@ +[package.metadata] +system-deps = "not a table" diff --git a/src/tests/toml-override-name/Cargo.toml b/src/tests/toml-override-name/Cargo.toml new file mode 100644 index 0000000..a89d8c8 --- /dev/null +++ b/src/tests/toml-override-name/Cargo.toml @@ -0,0 +1,2 @@ +[package.metadata.system-deps] +testlib = { name = "testlib-2.0", version = "2" } diff --git a/tests/toml-unexpected-key/Cargo.toml b/src/tests/toml-unexpected-key/Cargo.toml similarity index 58% rename from tests/toml-unexpected-key/Cargo.toml rename to src/tests/toml-unexpected-key/Cargo.toml index c79e7cb..6d05bfa 100644 --- a/tests/toml-unexpected-key/Cargo.toml +++ b/src/tests/toml-unexpected-key/Cargo.toml @@ -1,2 +1,2 @@ -[package.metadata.pkg-config] +[package.metadata.system-deps] testlib = { version = "1", color = "blue" } diff --git a/src/tests/toml-version-in-table-not-string/Cargo.toml b/src/tests/toml-version-in-table-not-string/Cargo.toml new file mode 100644 index 0000000..79ed920 --- /dev/null +++ b/src/tests/toml-version-in-table-not-string/Cargo.toml @@ -0,0 +1,2 @@ +[package.metadata.system-deps] +testlib = { version = 1 } diff --git a/tests/toml-version-missing/Cargo.toml b/src/tests/toml-version-missing/Cargo.toml similarity index 55% rename from tests/toml-version-missing/Cargo.toml rename to src/tests/toml-version-missing/Cargo.toml index 35a1088..0b6c924 100644 --- a/tests/toml-version-missing/Cargo.toml +++ b/src/tests/toml-version-missing/Cargo.toml @@ -1,2 +1,2 @@ -[package.metadata.pkg-config] +[package.metadata.system-deps] testlib = { feature = "test-feature" } diff --git a/src/tests/toml-version-names/Cargo.toml b/src/tests/toml-version-names/Cargo.toml new file mode 100644 index 0000000..ec1b800 --- /dev/null +++ b/src/tests/toml-version-names/Cargo.toml @@ -0,0 +1,2 @@ +[package.metadata.system-deps] +testlib = { version = "1.2", v2 = { version = "2.0", name = "testlib-2.0"}} \ No newline at end of file diff --git a/src/tests/toml-version-not-string/Cargo.toml b/src/tests/toml-version-not-string/Cargo.toml new file mode 100644 index 0000000..1dcf8ed --- /dev/null +++ b/src/tests/toml-version-not-string/Cargo.toml @@ -0,0 +1,2 @@ +[package.metadata.system-deps] +testlib = 1 diff --git a/tests/test.rs b/tests/test.rs deleted file mode 100644 index b31c4f5..0000000 --- a/tests/test.rs +++ /dev/null @@ -1,76 +0,0 @@ -#[macro_use] -extern crate lazy_static; -extern crate metadeps; -extern crate pkg_config; - -use std::env; -use std::sync::Mutex; - -lazy_static! { - static ref LOCK: Mutex<()> = Mutex::new(()); -} - -fn toml(path: &str) -> metadeps::Result> { - let _l = LOCK.lock(); - env::set_var("PKG_CONFIG_PATH", &env::current_dir().unwrap().join("tests")); - env::set_var("CARGO_MANIFEST_DIR", &env::current_dir().unwrap().join("tests").join(path)); - env::set_var("CARGO_FEATURE_TEST_FEATURE", ""); - metadeps::probe() -} - -#[test] -fn good() { - let libraries = toml("toml-good").unwrap(); - let testlib = libraries.get("testlib").unwrap(); - assert_eq!(testlib.version, "1.2.3"); - let testdata = libraries.get("testdata").unwrap(); - assert_eq!(testdata.version, "4.5.6"); - assert!(libraries.get("testmore").is_none()); -} - -fn toml_err(path: &str, err_starts_with: &str) { - let err = toml(path).unwrap_err(); - if !err.description().starts_with(err_starts_with) { - panic!("Expected error to start with: {:?}\nGot error: {:?}", err_starts_with, err); - } -} - -#[test] -fn missing_file() { - toml_err("toml-missing-file", "Error opening"); -} - -#[test] -fn missing_key() { - toml_err("toml-missing-key", "No package.metadata.pkg-config in"); -} - -#[test] -fn not_table() { - toml_err("toml-not-table", "package.metadata.pkg-config not a table in"); -} - -#[test] -fn version_missing() { - toml_err("toml-version-missing", "No version in package.metadata.pkg-config.testlib"); -} - -#[test] -fn version_not_string() { - toml_err("toml-version-not-string", "package.metadata.pkg-config.testlib not a string or table"); -} - -#[test] -fn version_in_table_not_string() { - toml_err("toml-version-in-table-not-string", "Unexpected key package.metadata.pkg-config.testlib.version type integer"); -} - -#[test] -fn feature_not_string() { - toml_err("toml-feature-not-string", "Unexpected key package.metadata.pkg-config.testlib.feature type integer"); -} - -#[test] -fn unexpected_key() { - toml_err("toml-unexpected-key", "Unexpected key package.metadata.pkg-config.testlib.color type string"); -} diff --git a/tests/toml-not-table/Cargo.toml b/tests/toml-not-table/Cargo.toml deleted file mode 100644 index 290f07c..0000000 --- a/tests/toml-not-table/Cargo.toml +++ /dev/null @@ -1,2 +0,0 @@ -[package.metadata] -pkg-config = "not a table" diff --git a/tests/toml-version-in-table-not-string/Cargo.toml b/tests/toml-version-in-table-not-string/Cargo.toml deleted file mode 100644 index a0e3293..0000000 --- a/tests/toml-version-in-table-not-string/Cargo.toml +++ /dev/null @@ -1,2 +0,0 @@ -[package.metadata.pkg-config] -testlib = { version = 1 } diff --git a/tests/toml-version-not-string/Cargo.toml b/tests/toml-version-not-string/Cargo.toml deleted file mode 100644 index 5ae3f6e..0000000 --- a/tests/toml-version-not-string/Cargo.toml +++ /dev/null @@ -1,2 +0,0 @@ -[package.metadata.pkg-config] -testlib = 1