use std::{
collections::{HashMap, HashSet},
error::Error,
path::PathBuf,
};
#[cfg(unix)]
use std::str::FromStr;
use crate::{
platform_case_aware_env_key::{construct_platform_case_aware_env_key, PlatformCaseAwareEnvKey},
types::Installation,
};
#[cfg(unix)]
pub const SEP: &str = ":";
#[cfg(windows)]
pub const SEP: &str = ";";
pub fn map(installations: &Vec<Installation>) -> HashMap<String, Vec<String>> {
let mut vars: HashMap<EnvKey, OrderedSet<PathBuf>> = HashMap::new();
let projects: HashSet<&str> = installations
.iter()
.map(|i| i.pkg.project.as_str())
.collect();
for installation in installations {
for key in EnvKey::iter() {
if let Some(suffixes) = suffixes(&key) {
for suffix in suffixes {
let path = installation.path.join(suffix);
if path.is_dir() {
vars.entry(key.clone())
.or_insert_with(OrderedSet::new)
.add(path);
}
}
}
}
if projects.contains("cmake.org") {
vars.entry(EnvKey::CmakePrefixPath)
.or_insert_with(OrderedSet::new)
.add(installation.path.clone());
}
}
#[cfg(unix)]
if vars.contains_key(&EnvKey::Manpath) {
vars.get_mut(&EnvKey::Manpath)
.unwrap()
.add(PathBuf::from_str("/usr/share/man").unwrap());
}
#[cfg(unix)]
if vars.contains_key(&EnvKey::XdgDataDirs) {
let set = vars.get_mut(&EnvKey::XdgDataDirs).unwrap();
set.add(PathBuf::from_str("/usr/local/share").unwrap());
set.add(PathBuf::from_str("/usr/share").unwrap());
}
let mut rv: HashMap<String, Vec<String>> = HashMap::new();
for (key, set) in vars {
let set = set
.items
.iter()
.map(|p| p.to_string_lossy().to_string())
.collect();
rv.insert(key.as_ref().to_string(), set);
}
rv
}
use rusqlite::Connection;
use strum::IntoEnumIterator;
use strum_macros::{AsRefStr, EnumIter, EnumString};
#[derive(Debug, EnumString, AsRefStr, PartialEq, Eq, Hash, Clone, EnumIter)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
enum EnvKey {
Path,
Manpath,
PkgConfigPath,
#[cfg(unix)]
LibraryPath,
#[cfg(unix)]
LdLibraryPath,
#[cfg(unix)]
Cpath,
XdgDataDirs,
CmakePrefixPath,
#[cfg(target_os = "macos")]
DyldFallbackLibraryPath,
SslCertFile,
#[cfg(unix)]
Ldflags,
PkgxDir,
AclocalPath,
#[cfg(windows)]
Lib,
#[cfg(windows)]
Include,
}
struct OrderedSet<T: Eq + std::hash::Hash + Clone> {
items: Vec<T>,
set: HashSet<T>,
}
impl<T: Eq + std::hash::Hash + Clone> OrderedSet<T> {
fn new() -> Self {
OrderedSet {
items: Vec::new(),
set: HashSet::new(),
}
}
fn add(&mut self, item: T) {
if self.set.insert(item.clone()) {
self.items.push(item);
}
}
}
fn suffixes(key: &EnvKey) -> Option<Vec<&'static str>> {
match key {
EnvKey::Path => Some(vec!["bin", "sbin"]),
EnvKey::Manpath => Some(vec!["man", "share/man"]),
EnvKey::PkgConfigPath => Some(vec!["share/pkgconfig", "lib/pkgconfig"]),
EnvKey::XdgDataDirs => Some(vec!["share"]),
EnvKey::AclocalPath => Some(vec!["share/aclocal"]),
#[cfg(unix)]
EnvKey::LibraryPath | EnvKey::LdLibraryPath => Some(vec!["lib", "lib64"]),
#[cfg(target_os = "macos")]
EnvKey::DyldFallbackLibraryPath => Some(vec!["lib", "lib64"]),
#[cfg(unix)]
EnvKey::Cpath => Some(vec!["include"]),
EnvKey::CmakePrefixPath | EnvKey::SslCertFile | EnvKey::PkgxDir => None,
#[cfg(unix)]
EnvKey::Ldflags => None,
#[cfg(windows)]
EnvKey::Lib => Some(vec!["lib"]),
#[cfg(windows)]
EnvKey::Include => Some(vec!["include"]),
}
}
pub fn mix(input: HashMap<String, Vec<String>>) -> HashMap<PlatformCaseAwareEnvKey, String> {
let mut rv: HashMap<PlatformCaseAwareEnvKey, String> = HashMap::new();
for (key, value) in std::env::vars() {
rv.insert(construct_platform_case_aware_env_key(key), value);
}
for (key, value) in input.iter() {
let key = &construct_platform_case_aware_env_key(key.clone());
if let Some(values) = rv.get(key) {
rv.insert(key.clone(), format!("{}{}{}", value.join(SEP), SEP, values));
} else {
rv.insert(key.clone(), value.join(SEP));
}
}
rv
}
pub fn mix_runtime(
input: &HashMap<PlatformCaseAwareEnvKey, String>,
installations: &Vec<Installation>,
conn: &Connection,
) -> Result<HashMap<PlatformCaseAwareEnvKey, String>, Box<dyn Error>> {
let mut output: HashMap<PlatformCaseAwareEnvKey, String> = input
.iter()
.map(|(k, v)| (k.clone(), format!("{}{}${}", v, SEP, k)))
.collect();
for installation in installations.clone() {
let runtime_env =
crate::pantry_db::runtime_env_for_project(&installation.pkg.project, conn)?;
for (key, runtime_value) in runtime_env {
let runtime_value = expand_moustaches(&runtime_value, &installation, installations);
let insert_key = construct_platform_case_aware_env_key(key.clone());
let new_value = if let Some(curr_value) = output.get(&insert_key) {
if runtime_value.contains(&format!("${}", key)) {
runtime_value.replace(&format!("${}", key), curr_value)
} else {
curr_value.clone()
}
} else if runtime_value.contains(&format!("${}", key)) {
runtime_value
} else {
format!("${{{}:-{}}}", key, runtime_value)
};
output.insert(insert_key, new_value);
}
}
Ok(output)
}
pub fn expand_moustaches(input: &str, pkg: &Installation, deps: &Vec<Installation>) -> String {
let mut output = input.to_string();
if output.starts_with("${{") {
output.replace_range(..1, "");
}
output = output.replace("{{prefix}}", &pkg.path.to_string_lossy());
output = output.replace("{{version}}", &format!("{}", &pkg.pkg.version));
output = output.replace("{{version.major}}", &format!("{}", pkg.pkg.version.major));
output = output.replace("{{version.minor}}", &format!("{}", pkg.pkg.version.minor));
output = output.replace("{{version.patch}}", &format!("{}", pkg.pkg.version.patch));
output = output.replace(
"{{version.marketing}}",
&format!("{}.{}", pkg.pkg.version.major, pkg.pkg.version.minor),
);
for dep in deps {
let prefix = format!("deps.{}", dep.pkg.project);
output = output.replace(
&format!("{{{{{}.prefix}}}}", prefix),
&dep.path.to_string_lossy(),
);
output = output.replace(
&format!("{{{{{}.version}}}}", prefix),
&format!("{}", &dep.pkg.version),
);
output = output.replace(
&format!("{{{{{}.version.major}}}}", prefix),
&format!("{}", dep.pkg.version.major),
);
output = output.replace(
&format!("{{{{{}.version.minor}}}}", prefix),
&format!("{}", dep.pkg.version.minor),
);
output = output.replace(
&format!("{{{{{}.version.patch}}}}", prefix),
&format!("{}", dep.pkg.version.patch),
);
output = output.replace(
&format!("{{{{{}.version.marketing}}}}", prefix),
&format!("{}.{}", dep.pkg.version.major, dep.pkg.version.minor),
);
}
output
}