#![allow(clippy::needless_doctest_main)]
#![allow(clippy::result_large_err)]
#![deny(missing_docs)]
#[cfg(test)]
mod test;
use heck::{ToShoutySnakeCase, ToSnakeCase};
use std::{
borrow::Borrow,
collections::{BTreeMap, HashMap},
env,
ffi::OsString,
fmt, iter,
ops::RangeBounds,
path::{Path, PathBuf},
str::FromStr,
};
mod metadata;
use metadata::MetaData;
#[cfg(all(test, feature = "binary"))]
mod test_binary;
#[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 {s1} (version {s2})")
}
Self::BuildInternalWrongVersion(s1, s2, s3) => write!(
f,
"Internally built {s1} {s2} but minimum required version is {s3}"
),
Self::UnsupportedCfg(s) => write!(f, "Unsupported cfg() expression: {s}"),
}
}
}
#[derive(Debug, Default)]
pub struct Dependencies {
libs: BTreeMap<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_linker_args(&self) -> Vec<&Vec<String>> {
let mut v = self
.libs
.values()
.flat_map(|l| &l.ld_args)
.collect::<Vec<_>>();
v.sort_unstable();
v.dedup();
v
}
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)) {
let should_be_linked_statically = env
.has_value(&EnvVariable::new_link(Some(name)), "static")
|| env.has_value(&EnvVariable::new_link(None), "static");
let is_static_lib_available = should_be_linked_statically;
lib.libs = split_string(&value)
.into_iter()
.map(|l| InternalLib::new(l, is_static_lib_available))
.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);
}
if let Some(value) = env.get(&EnvVariable::new_linker_args(name)) {
lib.ld_args = split_string(&value)
.into_iter()
.map(|l| l.split(',').map(|l| l.to_string()).collect())
.collect();
}
}
}
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())));
lib.ld_args
.iter()
.for_each(|f| flags.add(BuildFlag::LinkArg(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 in self.libs.keys() {
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>),
LinkerArgs(String),
NoPrebuilt(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_linker_args(lib: &str) -> Self {
Self::LinkerArgs(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 new_no_prebuilt(lib: Option<&str>) -> Self {
Self::NoPrebuilt(lib.map(|l| l.to_string()))
}
const 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",
EnvVariable::LinkerArgs(_) => "LDFLAGS",
EnvVariable::NoPrebuilt(_) => "NO_PREBUILT",
}
}
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_linker_args(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)));
add_to_flags(flags, EnvVariable::new_no_prebuilt(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::LinkerArgs(lib)
| EnvVariable::NoPkgConfig(lib)
| EnvVariable::BuildInternal(Some(lib))
| EnvVariable::Link(Some(lib))
| EnvVariable::NoPrebuilt(Some(lib)) => {
format!("{}_{}", lib.to_shouty_snake_case(), self.suffix())
}
EnvVariable::BuildInternal(None)
| EnvVariable::Link(None)
| EnvVariable::NoPrebuilt(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>>,
#[cfg(feature = "binary")]
paths: &'static system_deps_meta::binary::Paths,
}
impl Default for Config {
fn default() -> Self {
Self::new_with_env(EnvVariables::Environment)
}
}
impl Config {
pub fn new() -> Self {
Self::default()
}
fn new_with_env(env: EnvVariables) -> Self {
let me = Self {
env,
build_internals: HashMap::new(),
#[cfg(feature = "binary")]
paths: {
const CONTENT: &str = include_str!(env!("SYSTEM_DEPS_BINARY_PATHS"));
static PATHS: std::sync::OnceLock<system_deps_meta::binary::Paths> =
std::sync::OnceLock::new();
PATHS.get_or_init(|| {
toml::from_str(CONTENT)
.expect("The build script should output valid serialization")
})
},
};
#[cfg(feature = "binary")]
if env::var_os("PKG_CONFIG").is_none() {
if let Some(pc) = me.paths.pkg_config_binary() {
env::set_var("PKG_CONFIG", pc);
}
}
me
}
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>(mut 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.build_internals = build_internals;
self
}
pub fn query_path(&self, _pkg: &str) -> Option<&'static Vec<PathBuf>> {
#[cfg(not(feature = "binary"))]
return None;
#[cfg(feature = "binary")]
self.env
.get(&EnvVariable::new_no_prebuilt(Some(_pkg)))
.or(self.env.get(&EnvVariable::new_no_prebuilt(None)))
.map_or_else(|| self.paths.get(_pkg), |_| None)
}
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");
println!("cargo:rerun-if-changed={}", path.to_string_lossy());
let metadata = MetaData::from_file(&path)?;
let mut libraries = Dependencies::default();
let unconditional_keys: std::collections::HashSet<_> = metadata
.deps
.iter()
.filter(|d| d.cfg.is_none())
.map(|d| &d.key)
.collect();
for dep in metadata.deps.iter() {
if let Some(cfg) = &dep.cfg {
if !self.check_cfg(cfg)? {
continue;
}
if unconditional_keys.contains(&dep.key) {
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;
let lib_name;
let fallback_lib_names;
let optional;
if enabled_feature_overrides.is_empty() {
version = dep.version.as_deref();
lib_name = dep.lib_name();
fallback_lib_names = dep.fallback_names.as_deref().unwrap_or(&[]);
optional = dep.optional;
} else {
enabled_feature_overrides.sort_by(|a, b| {
fn min_version(r: metadata::VersionRange<'_>) -> &str {
match r.start_bound() {
std::ops::Bound::Unbounded => unreachable!(),
std::ops::Bound::Excluded(_) => unreachable!(),
std::ops::Bound::Included(b) => b,
}
}
let a = min_version(metadata::parse_version(&a.version));
let b = min_version(metadata::parse_version(&b.version));
version_compare::compare(a, b)
.expect("failed to compare versions")
.ord()
.expect("invalid version")
});
let highest = enabled_feature_overrides.into_iter().next_back().unwrap();
version = Some(highest.version.as_str());
lib_name = highest.name.as_deref().unwrap_or(dep.lib_name());
fallback_lib_names = highest
.fallback_names
.as_deref()
.or(dep.fallback_names.as_deref())
.unwrap_or(&[]);
optional = highest.optional.unwrap_or(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 prebuilt_pkg_config_paths = self.query_path(name);
let statik = cfg!(feature = "binary")
|| 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 {
let mut config = pkg_config::Config::new();
config
.print_system_libs(false)
.cargo_metadata(false)
.range_version(metadata::parse_version(version))
.statik(statik);
#[cfg(windows)]
if prebuilt_pkg_config_paths.is_some() && pkg_config_accepts_dont_define_prefix() {
config.arg("--dont-define-prefix");
}
let probe = Library::wrap_pkg_config(prebuilt_pkg_config_paths, || {
Self::probe_with_fallback(&config, lib_name, fallback_lib_names)
});
match probe {
Ok((lib_name, 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 probe_with_fallback<'a>(
config: &'a pkg_config::Config,
name: &'a str,
fallback_names: &'a [String],
) -> Result<(&'a str, pkg_config::Library), pkg_config::Error> {
let error = match config.probe(name) {
Ok(x) => return Ok((name, x)),
Err(e) => e,
};
for name in fallback_names {
if let Ok(library) = config.probe(name) {
return Ok((name, library));
}
}
Err(error)
}
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 {var}: {s} (allowed: 'auto', 'always', 'never')"
))
})?;
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: &str) -> Result<Library, Error> {
let lib = match self.build_internals.remove(name) {
Some(f) => f(name, version_str)
.map_err(|e| Error::BuildInternalClosureError(name.into(), e))?,
None => {
return Err(Error::BuildInternalNoClosure(
name.into(),
version_str.into(),
))
}
};
let version = metadata::parse_version(version_str);
fn min_version(r: metadata::VersionRange<'_>) -> &str {
match r.start_bound() {
std::ops::Bound::Unbounded => unreachable!(),
std::ops::Bound::Excluded(_) => unreachable!(),
std::ops::Bound::Included(b) => b,
}
}
fn max_version(r: metadata::VersionRange<'_>) -> Option<&str> {
match r.end_bound() {
std::ops::Bound::Included(_) => unreachable!(),
std::ops::Bound::Unbounded => None,
std::ops::Bound::Excluded(b) => Some(*b),
}
}
let min = min_version(version.clone());
if version_compare::compare(&lib.version, min) == Ok(version_compare::Cmp::Lt) {
return Err(Error::BuildInternalWrongVersion(
name.into(),
lib.version,
version_str.into(),
));
}
if let Some(max) = max_version(version) {
if version_compare::compare(&lib.version, max) == Ok(version_compare::Cmp::Ge) {
return Err(Error::BuildInternalWrongVersion(
name.into(),
lib.version,
version_str.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 res = if let Some(target) = get_builtin_target_by_triple(&target) {
cfg.eval(|pred| match pred {
Predicate::Target(tp) => Some(tp.matches(target)),
_ => None,
})
} else {
let triple: cfg_expr::target_lexicon::Triple = target.parse().unwrap_or_else(|e| panic!("TARGET {} is not a builtin target, and it could not be parsed as a valid triplet: {}", target, e));
cfg.eval(|pred| match pred {
Predicate::Target(tp) => Some(tp.matches(&triple)),
_ => 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 {
const 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 ld_args: Vec<Vec<String>>,
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 {
if cfg!(all(target_os = "windows", target_env = "msvc")) {
return false;
}
let libnames = {
let mut names = vec![format!("lib{name}.a")];
if cfg!(target_os = "windows") {
names.push(format!("{name}.lib"));
}
names
};
l.link_paths.iter().any(|dir| {
let library_exists = libnames.iter().any(|libname| dir.join(libname).exists());
library_exists && !system_roots.iter().any(|sys| dir.starts_with(sys))
})
};
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,
ld_args: l.ld_args,
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(),
ld_args: Vec::new(),
frameworks: Vec::new(),
framework_paths: Vec::new(),
defines: HashMap::new(),
version: String::new(),
statik: false,
}
}
pub fn wrap_pkg_config<T, R>(
pkg_config_paths: impl PathOrList<T>,
f: impl FnOnce() -> Result<R, pkg_config::Error>,
) -> Result<R, pkg_config::Error> {
let prev = env::var("PKG_CONFIG_PATH").ok();
let prev_paths = prev.iter().flat_map(env::split_paths).collect::<Vec<_>>();
let joined_paths = pkg_config_paths.join_paths(prev_paths.as_slice());
#[cfg(windows)]
let joined_paths = OsString::from(joined_paths.to_string_lossy().replace('\\', "/"));
env::set_var("PKG_CONFIG_PATH", joined_paths);
let res = f();
if let Some(prev) = prev {
env::set_var("PKG_CONFIG_PATH", prev);
}
res
}
pub fn from_internal_pkg_config<T>(
pkg_config_paths: impl PathOrList<T>,
lib: &str,
version: &str,
) -> Result<Self, BuildInternalClosureError> {
let pkg_lib = Self::wrap_pkg_config(pkg_config_paths, || {
pkg_config::Config::new()
.atleast_version(version)
.print_system_libs(false)
.cargo_metadata(false)
.statik(true)
.probe(lib)
})?;
let mut lib = Self::from_pkg_config(lib, pkg_lib);
lib.statik = true;
Ok(lib)
}
}
#[cfg(windows)]
fn pkg_config_accepts_dont_define_prefix() -> bool {
use std::sync::OnceLock;
static SUPPORTED: OnceLock<bool> = OnceLock::new();
*SUPPORTED.get_or_init(|| {
let exe = env::var_os("PKG_CONFIG").unwrap_or_else(|| "pkg-config".into());
std::process::Command::new(exe)
.args(["--dont-define-prefix", "--version"])
.output()
.map(|out| out.status.success())
.unwrap_or(false)
})
}
pub trait PathOrList<T> {
fn join_paths(&self, other: impl AsRef<[PathBuf]>) -> OsString;
}
impl<T: AsRef<Path>> PathOrList<T> for T {
fn join_paths(&self, other: impl AsRef<[PathBuf]>) -> OsString {
let other = other.as_ref().iter().map(|p| p.as_path());
env::join_paths(iter::once(self.as_ref()).chain(other))
.expect("Path contains invalid character")
}
}
impl<T: AsRef<Path>, S: Borrow<[T]>> PathOrList<(T, S)> for &S {
fn join_paths(&self, other: impl AsRef<[PathBuf]>) -> OsString {
let slice: &[T] = (*self).borrow();
let other = other.as_ref().iter().map(|p| p.as_path());
env::join_paths(slice.iter().map(|p| p.as_ref()).chain(other))
.expect("Path contains invalid character")
}
}
impl<T: PathOrList<S>, S> PathOrList<(T, S)> for Option<T> {
fn join_paths(&self, other: impl AsRef<[PathBuf]>) -> OsString {
match self {
Some(s) => s.join_paths(other),
None => env::join_paths(other.as_ref().iter().map(|p| p.as_os_str()))
.expect("Path contains invalid character"),
}
}
}
#[derive(Debug)]
enum EnvVariables {
Environment,
#[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::Environment => 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),
LinkArg(Vec<String>),
}
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}"),
BuildFlag::LinkArg(ld_option) => {
write!(f, "rustc-link-arg=-Wl,{}", ld_option.join(","))
}
}
}
}
#[derive(Debug, PartialEq)]
struct BuildFlags(Vec<BuildFlag>);
impl BuildFlags {
const 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, Default)]
enum BuildInternal {
Auto,
Always,
#[default]
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}`"),
}
}
}