#![allow(clippy::needless_doctest_main)]
#![deny(missing_docs)]
#[cfg(test)]
#[macro_use]
extern crate lazy_static;
#[cfg(test)]
mod test;
use heck::{ToShoutySnakeCase, ToSnakeCase};
use std::collections::HashMap;
use std::env;
use std::fmt;
use std::path::{Path, PathBuf};
use std::str::FromStr;
mod metadata;
use metadata::MetaData;
#[derive(Debug)]
pub enum Error {
PkgConfig(pkg_config::Error),
BuildInternalClosureError(String, BuildInternalClosureError),
FailToRead(String, std::io::Error),
InvalidMetadata(String),
MissingLib(String),
BuildInternalInvalid(String),
BuildInternalNoClosure(String, String),
BuildInternalWrongVersion(String, String, String),
UnsupportedCfg(String),
}
impl From<pkg_config::Error> for Error {
fn from(err: pkg_config::Error) -> Self {
Self::PkgConfig(err)
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::PkgConfig(e) => Some(e),
Self::BuildInternalClosureError(_, e) => Some(e),
Self::FailToRead(_, e) => Some(e),
_ => None,
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::PkgConfig(e) => write!(f, "{}", e),
Self::BuildInternalClosureError(s, e) => write!(f, "Failed to build {}: {}", s, e),
Self::FailToRead(s, _) => write!(f, "{}", s),
Self::InvalidMetadata(s) => write!(f, "{}", s),
Self::MissingLib(s) => write!(
f,
"You should define at least one lib using {} or {}",
EnvVariable::new_lib(s),
EnvVariable::new_lib_framework(s),
),
Self::BuildInternalInvalid(s) => write!(f, "{}", s),
Self::BuildInternalNoClosure(s1, s2) => write!(
f,
"Missing build internal closure for {} (version {})",
s1, s2
),
Self::BuildInternalWrongVersion(s1, s2, s3) => write!(
f,
"Internally built {} {} but minimum required version is {}",
s1, s2, s3
),
Self::UnsupportedCfg(s) => write!(f, "Unsupported cfg() expression: {}", s),
}
}
}
#[derive(Debug, Default)]
pub struct Dependencies {
libs: HashMap<String, Library>,
}
impl Dependencies {
pub fn get_by_name(&self, name: &str) -> Option<&Library> {
self.libs.get(name)
}
pub fn iter(&self) -> Vec<(&str, &Library)> {
let mut v = self
.libs
.iter()
.map(|(k, v)| (k.as_str(), v))
.collect::<Vec<_>>();
v.sort_by_key(|x| x.0);
v
}
fn aggregate_str<F: Fn(&Library) -> &Vec<String>>(&self, getter: F) -> Vec<&str> {
let mut v = self
.libs
.values()
.flat_map(getter)
.map(|s| s.as_str())
.collect::<Vec<_>>();
v.sort_unstable();
v.dedup();
v
}
fn aggregate_path_buf<F: Fn(&Library) -> &Vec<PathBuf>>(&self, getter: F) -> Vec<&PathBuf> {
let mut v = self.libs.values().flat_map(getter).collect::<Vec<_>>();
v.sort();
v.dedup();
v
}
pub fn all_libs(&self) -> Vec<&str> {
let mut v = self
.libs
.values()
.flat_map(|l| l.libs.iter().map(|lib| lib.name.as_str()))
.collect::<Vec<_>>();
v.sort_unstable();
v.dedup();
v
}
pub fn all_link_paths(&self) -> Vec<&PathBuf> {
self.aggregate_path_buf(|l| &l.link_paths)
}
pub fn all_frameworks(&self) -> Vec<&str> {
self.aggregate_str(|l| &l.frameworks)
}
pub fn all_framework_paths(&self) -> Vec<&PathBuf> {
self.aggregate_path_buf(|l| &l.framework_paths)
}
pub fn all_include_paths(&self) -> Vec<&PathBuf> {
self.aggregate_path_buf(|l| &l.include_paths)
}
pub fn all_defines(&self) -> Vec<(&str, &Option<String>)> {
let mut v = self
.libs
.values()
.flat_map(|l| l.defines.iter())
.map(|(k, v)| (k.as_str(), v))
.collect::<Vec<_>>();
v.sort();
v.dedup();
v
}
fn add(&mut self, name: &str, lib: Library) {
self.libs.insert(name.to_string(), lib);
}
fn override_from_flags(&mut self, env: &EnvVariables) {
for (name, lib) in self.libs.iter_mut() {
if let Some(value) = env.get(&EnvVariable::new_search_native(name)) {
lib.link_paths = split_paths(&value);
}
if let Some(value) = env.get(&EnvVariable::new_search_framework(name)) {
lib.framework_paths = split_paths(&value);
}
if let Some(value) = env.get(&EnvVariable::new_lib(name)) {
lib.libs = split_string(&value)
.into_iter()
.map(|l| InternalLib::new(l, false))
.collect();
}
if let Some(value) = env.get(&EnvVariable::new_lib_framework(name)) {
lib.frameworks = split_string(&value);
}
if let Some(value) = env.get(&EnvVariable::new_include(name)) {
lib.include_paths = split_paths(&value);
}
}
}
fn gen_flags(&self) -> Result<BuildFlags, Error> {
let mut flags = BuildFlags::new();
let mut include_paths = Vec::new();
for (name, lib) in self.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.to_string()));
}
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.name.clone(),
lib.statik && l.is_static_available,
))
});
lib.frameworks
.iter()
.for_each(|f| flags.add(BuildFlag::LibFramework(f.clone())));
}
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()));
}
}
flags.add(BuildFlag::RerunIfEnvChanged(
EnvVariable::new_build_internal(None),
));
flags.add(BuildFlag::RerunIfEnvChanged(EnvVariable::new_link(None)));
for (name, _lib) in self.libs.iter() {
EnvVariable::set_rerun_if_changed_for_all_variants(&mut flags, name);
}
Ok(flags)
}
}
#[derive(Debug)]
pub enum BuildInternalClosureError {
PkgConfig(pkg_config::Error),
Failed(String),
}
impl From<pkg_config::Error> for BuildInternalClosureError {
fn from(err: pkg_config::Error) -> Self {
Self::PkgConfig(err)
}
}
impl BuildInternalClosureError {
pub fn failed(details: &str) -> Self {
Self::Failed(details.to_string())
}
}
impl std::error::Error for BuildInternalClosureError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::PkgConfig(e) => Some(e),
_ => None,
}
}
}
impl fmt::Display for BuildInternalClosureError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::PkgConfig(e) => write!(f, "{}", e),
Self::Failed(s) => write!(f, "{}", s),
}
}
}
#[derive(Debug, PartialEq)]
enum EnvVariable {
Lib(String),
LibFramework(String),
SearchNative(String),
SearchFramework(String),
Include(String),
NoPkgConfig(String),
BuildInternal(Option<String>),
Link(Option<String>),
}
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 new_link(lib: Option<&str>) -> Self {
Self::Link(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",
EnvVariable::Link(_) => "LINK",
}
}
fn set_rerun_if_changed_for_all_variants(flags: &mut BuildFlags, name: &str) {
#[inline]
fn add_to_flags(flags: &mut BuildFlags, var: EnvVariable) {
flags.add(BuildFlag::RerunIfEnvChanged(var));
}
add_to_flags(flags, EnvVariable::new_lib(name));
add_to_flags(flags, EnvVariable::new_lib_framework(name));
add_to_flags(flags, EnvVariable::new_search_native(name));
add_to_flags(flags, EnvVariable::new_search_framework(name));
add_to_flags(flags, EnvVariable::new_include(name));
add_to_flags(flags, EnvVariable::new_no_pkg_config(name));
add_to_flags(flags, EnvVariable::new_build_internal(Some(name)));
add_to_flags(flags, EnvVariable::new_link(Some(name)));
}
}
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))
| EnvVariable::Link(Some(lib)) => {
format!("{}_{}", lib.to_shouty_snake_case(), self.suffix())
}
EnvVariable::BuildInternal(None) | EnvVariable::Link(None) => self.suffix().to_string(),
};
write!(f, "SYSTEM_DEPS_{}", suffix)
}
}
type FnBuildInternal =
dyn FnOnce(&str, &str) -> std::result::Result<Library, BuildInternalClosureError>;
pub struct Config {
env: EnvVariables,
build_internals: HashMap<String, Box<FnBuildInternal>>,
}
impl Default for Config {
fn default() -> Self {
Self::new_with_env(EnvVariables::Environnement)
}
}
impl Config {
pub fn new() -> Self {
Self::default()
}
fn new_with_env(env: EnvVariables) -> Self {
Self {
env,
build_internals: HashMap::new(),
}
}
pub fn probe(self) -> Result<Dependencies, Error> {
let libraries = self.probe_full()?;
let flags = libraries.gen_flags()?;
println!("{}", flags);
for (name, _) in libraries.iter() {
println!("cargo:rustc-cfg=system_deps_have_{}", name.to_snake_case());
}
Ok(libraries)
}
pub fn add_build_internal<F>(self, name: &str, func: F) -> Self
where
F: 'static + FnOnce(&str, &str) -> std::result::Result<Library, BuildInternalClosureError>,
{
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<Dependencies, Error> {
let mut libraries = self.probe_pkg_config()?;
libraries.override_from_flags(&self.env);
Ok(libraries)
}
fn probe_pkg_config(&mut self) -> Result<Dependencies, 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 metadata = MetaData::from_file(&path)?;
let mut libraries = Dependencies::default();
for dep in metadata.deps.iter() {
if let Some(cfg) = &dep.cfg {
if !self.check_cfg(cfg)? {
continue;
}
}
let mut enabled_feature_overrides = Vec::new();
for o in dep.version_overrides.iter() {
if self.has_feature(&o.key) {
enabled_feature_overrides.push(o);
}
}
if let Some(feature) = dep.feature.as_ref() {
if !self.has_feature(feature) {
continue;
}
}
let (version, lib_name, optional) = {
if !enabled_feature_overrides.is_empty() {
enabled_feature_overrides.sort_by(|a, b| {
version_compare::compare(&a.version, &b.version)
.expect("failed to compare versions")
.ord()
.expect("invalid version")
});
let highest = enabled_feature_overrides.into_iter().last().unwrap();
(
Some(&highest.version),
highest.name.clone().unwrap_or_else(|| dep.lib_name()),
highest.optional.unwrap_or(dep.optional),
)
} else {
(dep.version.as_ref(), dep.lib_name(), dep.optional)
}
};
let version = version.ok_or_else(|| {
Error::InvalidMetadata(format!("No version defined for {}", dep.key))
})?;
let name = &dep.key;
let build_internal = self.get_build_internal_status(name)?;
let statik = self
.env
.has_value(&EnvVariable::new_link(Some(name)), "static")
|| self.env.has_value(&EnvVariable::new_link(None), "static");
let mut library = if self.env.contains(&EnvVariable::new_no_pkg_config(name)) {
Library::from_env_variables(name)
} 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)
.statik(statik)
.probe(&lib_name)
{
Ok(lib) => Library::from_pkg_config(&lib_name, lib),
Err(e) => {
if build_internal == BuildInternal::Auto {
self.call_build_internal(name, version)?
} else if optional {
continue;
} else {
return Err(e.into());
}
}
}
};
library.statik = statik;
libraries.add(name, library);
}
Ok(libraries)
}
fn get_build_internal_env_var(&self, var: EnvVariable) -> Result<Option<BuildInternal>, 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))
}
None => Ok(None),
}
}
fn get_build_internal_status(&self, name: &str) -> Result<BuildInternal, Error> {
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<Library, Error> {
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())),
};
match version_compare::compare(&lib.version, version) {
Ok(version_compare::Cmp::Lt) => Err(Error::BuildInternalWrongVersion(
name.into(),
lib.version,
version.into(),
)),
_ => Ok(lib),
}
}
fn has_feature(&self, feature: &str) -> bool {
let var: &str = &format!("CARGO_FEATURE_{}", feature.to_uppercase().replace('-', "_"));
self.env.contains(var)
}
fn check_cfg(&self, cfg: &cfg_expr::Expression) -> Result<bool, Error> {
use cfg_expr::{targets::get_builtin_target_by_triple, Predicate};
let target = self
.env
.get("TARGET")
.expect("no TARGET env variable defined");
let target = get_builtin_target_by_triple(&target)
.unwrap_or_else(|| panic!("Invalid TARGET: {}", target));
let res = cfg.eval(|pred| match pred {
Predicate::Target(tp) => Some(tp.matches(target)),
_ => None,
});
res.ok_or_else(|| Error::UnsupportedCfg(cfg.original().to_string()))
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum Source {
PkgConfig,
EnvVariables,
}
#[derive(Debug, PartialEq, Eq)]
pub struct InternalLib {
pub name: String,
pub is_static_available: bool,
}
impl InternalLib {
fn new(name: String, is_static_available: bool) -> Self {
InternalLib {
name,
is_static_available,
}
}
}
#[derive(Debug)]
pub struct Library {
pub name: String,
pub source: Source,
pub libs: Vec<InternalLib>,
pub link_paths: Vec<PathBuf>,
pub frameworks: Vec<String>,
pub framework_paths: Vec<PathBuf>,
pub include_paths: Vec<PathBuf>,
pub defines: HashMap<String, Option<String>>,
pub version: String,
pub statik: bool,
}
impl Library {
fn from_pkg_config(name: &str, l: pkg_config::Library) -> Self {
let system_roots = if cfg!(target_os = "macos") {
vec![PathBuf::from("/Library"), PathBuf::from("/System")]
} else {
let sysroot = env::var_os("PKG_CONFIG_SYSROOT_DIR")
.or_else(|| env::var_os("SYSROOT"))
.map(PathBuf::from);
if cfg!(target_os = "windows") {
if let Some(sysroot) = sysroot {
vec![sysroot]
} else {
vec![]
}
} else {
vec![sysroot.unwrap_or_else(|| PathBuf::from("/usr"))]
}
};
let is_static_available = |name: &String| -> bool {
let libname = format!("lib{}.a", name);
l.link_paths.iter().any(|dir| {
!system_roots.iter().any(|sys| dir.starts_with(sys)) && dir.join(&libname).exists()
})
};
Self {
name: name.to_string(),
source: Source::PkgConfig,
libs: l
.libs
.iter()
.map(|lib| InternalLib::new(lib.to_owned(), is_static_available(lib)))
.collect(),
link_paths: l.link_paths,
include_paths: l.include_paths,
frameworks: l.frameworks,
framework_paths: l.framework_paths,
defines: l.defines,
version: l.version,
statik: false,
}
}
fn from_env_variables(name: &str) -> Self {
Self {
name: name.to_string(),
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(),
statik: false,
}
}
pub fn from_internal_pkg_config<P>(
pkg_config_dir: P,
lib: &str,
version: &str,
) -> Result<Self, BuildInternalClosureError>
where
P: AsRef<Path>,
{
let old = env::var("PKG_CONFIG_PATH");
match old {
Ok(ref s) => {
let mut paths = env::split_paths(s).collect::<Vec<_>>();
paths.push(PathBuf::from(pkg_config_dir.as_ref()));
let paths = env::join_paths(paths).unwrap();
env::set_var("PKG_CONFIG_PATH", paths)
}
Err(_) => env::set_var("PKG_CONFIG_PATH", pkg_config_dir.as_ref()),
}
let pkg_lib = pkg_config::Config::new()
.atleast_version(version)
.print_system_libs(false)
.cargo_metadata(false)
.statik(true)
.probe(lib);
env::set_var("PKG_CONFIG_PATH", &old.unwrap_or_else(|_| "".into()));
match pkg_lib {
Ok(pkg_lib) => {
let mut lib = Self::from_pkg_config(lib, pkg_lib);
lib.statik = true;
Ok(lib)
}
Err(e) => Err(e.into()),
}
}
}
#[derive(Debug)]
enum EnvVariables {
Environnement,
#[cfg(test)]
Mock(HashMap<&'static str, String>),
}
trait EnvVariablesExt<T> {
fn contains(&self, var: T) -> bool {
self.get(var).is_some()
}
fn get(&self, var: T) -> Option<String>;
fn has_value(&self, var: T, val: &str) -> bool {
match self.get(var) {
Some(v) => v == val,
None => false,
}
}
}
impl EnvVariablesExt<&str> for EnvVariables {
fn get(&self, var: &str) -> Option<String> {
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<String> {
let s = var.to_string();
let var: &str = s.as_ref();
self.get(var)
}
}
#[derive(Debug, PartialEq)]
enum BuildFlag {
Include(String),
SearchNative(String),
SearchFramework(String),
Lib(String, bool), 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, statik) => {
if *statik {
write!(f, "rustc-link-lib=static={}", lib)
} else {
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<BuildFlag>);
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<PathBuf> {
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<String> {
if !value.is_empty() {
value.split(' ').map(|s| s.to_string()).collect()
} else {
Vec::new()
}
}
#[derive(Debug, PartialEq)]
enum BuildInternal {
Auto,
Always,
Never,
}
impl Default for BuildInternal {
fn default() -> Self {
Self::Never
}
}
impl FromStr for BuildInternal {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"auto" => Ok(Self::Auto),
"always" => Ok(Self::Always),
"never" => Ok(Self::Never),
v => Err(ParseError::VariantNotFound(v.to_owned())),
}
}
}
#[derive(Debug, PartialEq)]
enum ParseError {
VariantNotFound(String),
}
impl std::error::Error for ParseError {}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::VariantNotFound(v) => write!(f, "Unknown variant: `{}`", v),
}
}
}