Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

handle env edgecases & fill out runtime env #8

Merged
merged 2 commits into from
Jan 3, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
212 changes: 129 additions & 83 deletions src/env.rs
Original file line number Diff line number Diff line change
@@ -1,98 +1,76 @@
use std::collections::{HashMap, HashSet};
use std::{
collections::{HashMap, HashSet},
error::Error,
path::PathBuf,
str::FromStr,
};

use crate::types::Installation;

pub fn map(installations: Vec<Installation>) -> HashMap<String, Vec<String>> {
let mut vars: HashMap<&str, OrderedSet<String>> = HashMap::new();
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();

let has_cmake = projects.contains("cmake.org");
let archaic = true;

let mut rv: HashMap<String, Vec<String>> = HashMap::new();

for installation in installations {
for key in &[
EnvKey::Path,
EnvKey::Manpath,
EnvKey::PkgConfigPath,
EnvKey::LibraryPath,
EnvKey::LdLibraryPath,
EnvKey::Cpath,
EnvKey::XdgDataDirs,
EnvKey::CmakePrefixPath,
EnvKey::DyldFallbackLibraryPath,
EnvKey::SslCertFile,
EnvKey::Ldflags,
EnvKey::PkgxDir,
EnvKey::AclocalPath,
] {
if let Some(suffixes) = suffixes(key) {
for key in EnvKey::iter() {
if let Some(suffixes) = suffixes(&key) {
for suffix in suffixes {
let path = installation.path.join(suffix).to_string_lossy().to_string();
if !path.is_empty() {
vars.entry(key.as_ref())
let path = installation.path.join(suffix);
if path.is_dir() {
vars.entry(key.clone())
.or_insert_with(OrderedSet::new)
.add(path);
}
}
}
}

if archaic {
let lib_path = installation.path.join("lib").to_string_lossy().to_string();
vars.entry(EnvKey::LibraryPath.as_ref())
.or_insert_with(OrderedSet::new)
.add(lib_path);

let include_path = installation
.path
.join("include")
.to_string_lossy()
.to_string();
vars.entry(EnvKey::Cpath.as_ref())
.or_insert_with(OrderedSet::new)
.add(include_path);
}

if has_cmake {
vars.entry(EnvKey::CmakePrefixPath.as_ref())
if projects.contains("cmake.org") {
vars.entry(EnvKey::CmakePrefixPath)
.or_insert_with(OrderedSet::new)
.add(installation.path.to_string_lossy().to_string());
.add(installation.path.clone());
}
}

if let Some(library_path) = vars.get(EnvKey::LibraryPath.as_ref()) {
let paths = library_path.to_vec();
vars.entry(EnvKey::LdLibraryPath.as_ref())
.or_insert_with(OrderedSet::new)
.items
.extend(paths.clone());
#[cfg(not(target_os = "macos"))]
vars.remove(&EnvKey::DyldFallbackLibraryPath);

mxcl marked this conversation as resolved.
Show resolved Hide resolved
// We only need to set DYLD_FALLBACK_LIBRARY_PATH on macOS
#[cfg(target_os = "macos")]
vars.entry(EnvKey::DyldFallbackLibraryPath.as_ref())
.or_insert_with(OrderedSet::new)
.items
.extend(paths);
// don’t break `man`
if vars.contains_key(&EnvKey::Manpath) {
vars.get_mut(&EnvKey::Manpath)
.unwrap()
.add(PathBuf::from_str("/usr/share/man").unwrap());
}
// https://github.com/pkgxdev/libpkgx/issues/70
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());
}

for (key, set) in &vars {
if !set.is_empty() {
rv.insert(key.to_string(), set.to_vec());
}
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);
}
mxcl marked this conversation as resolved.
Show resolved Hide resolved

rv
}

use strum_macros::{AsRefStr, EnumString};
use rusqlite::Connection;
use strum::IntoEnumIterator;
use strum_macros::{AsRefStr, EnumIter, EnumString};

#[derive(Debug, EnumString, AsRefStr, PartialEq, Eq, Hash, Clone)]
#[derive(Debug, EnumString, AsRefStr, PartialEq, Eq, Hash, Clone, EnumIter)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
enum EnvKey {
Path,
Expand All @@ -110,6 +88,7 @@ enum EnvKey {
AclocalPath,
}

//FIXME surely there's a stdlib type that does this?
pub struct OrderedSet<T: Eq + std::hash::Hash + Clone> {
mxcl marked this conversation as resolved.
Show resolved Hide resolved
items: Vec<T>,
set: HashSet<T>,
Expand All @@ -128,38 +107,26 @@ impl<T: Eq + std::hash::Hash + Clone> OrderedSet<T> {
self.items.push(item);
}
}

pub fn to_vec(&self) -> Vec<T> {
self.items.clone()
}

pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
}

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::LibraryPath
| EnvKey::LdLibraryPath
| EnvKey::DyldFallbackLibraryPath
| EnvKey::Cpath
| EnvKey::CmakePrefixPath
| EnvKey::SslCertFile
| EnvKey::Ldflags
| EnvKey::PkgxDir
| EnvKey::AclocalPath => None,
EnvKey::AclocalPath => Some(vec!["share/aclocal"]),
EnvKey::LibraryPath | EnvKey::LdLibraryPath | EnvKey::DyldFallbackLibraryPath => {
Some(vec!["lib", "lib64"])
}
EnvKey::Cpath => Some(vec!["include"]),
EnvKey::CmakePrefixPath | EnvKey::SslCertFile | EnvKey::Ldflags | EnvKey::PkgxDir => None,
}
}

pub fn mix(input: HashMap<String, Vec<String>>) -> HashMap<String, String> {
let mut rv = HashMap::new();

//TODO handle empty values etc.

for (key, mut value) in std::env::vars() {
if let Some(injected_values) = input.get(&key) {
value = format!("{}:{}", injected_values.join(":"), value);
Expand All @@ -169,3 +136,82 @@ pub fn mix(input: HashMap<String, Vec<String>>) -> HashMap<String, String> {

rv
}

pub fn mix_runtime(
input: &HashMap<String, String>,
installations: &Vec<Installation>,
conn: &Connection,
) -> Result<HashMap<String, String>, Box<dyn Error>> {
let mut output = input.clone();

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 new_value = match output.get(&key) {
Some(curr_value) => runtime_value.replace(&format!("${}", key), curr_value),
None => {
//TODO need to remove any $FOO, aware of `:` delimiters
runtime_value
.replace(&format!(":${}", key), "")
.replace(&format!("${}:", key), "")
.replace(&format!("${}", key), "")
}
};
output.insert(key, new_value);
}
}

Ok(output)
}

pub fn expand_moustaches(input: &str, pkg: &Installation, deps: &Vec<Installation>) -> String {
let prefix = pkg.path.to_string_lossy();
let mut output = input.to_string();
if output.starts_with("${{prefix}}") {
output.replace_range(..11, &prefix);
}
output = output.replace("{{prefix}}", &pkg.path.to_string_lossy());
mxcl marked this conversation as resolved.
Show resolved Hide resolved
output = output.replace(
"{{version}}",
&crate::types::semverator_version_to_string(&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),
&crate::types::semverator_version_to_string(&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
mxcl marked this conversation as resolved.
Show resolved Hide resolved
}
17 changes: 10 additions & 7 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use types::PackageReq;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let args::Args {
mut plus,
plus,
mut args,
mode,
flags,
Expand Down Expand Up @@ -54,9 +54,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {

let mut pkgs = vec![];
for arg in plus {
let arg = PackageReq::parse(&arg)?;
let pkg = pantry_db::resolve(arg, &conn)?;
pkgs.push(pkg);
let arg = PackageReq::parse(&arg)?;
let pkg = pantry_db::resolve(arg, &conn)?;
pkgs.push(pkg);
}

if find_program {
Expand All @@ -79,11 +79,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
installations.extend(installed);
}

let env = env::map(installations);
let env = env::map(&installations);

if args.is_empty() {
for (key, values) in env {
println!("{}={}", key, values.join(":"));
let env = env.iter().map(|(k, v)| (k.clone(), v.join(":"))).collect();
let env = env::mix_runtime(&env, &installations, &conn)?;
for (key, value) in env {
println!("{}=\"{}${{{}:+:${}}}\"", key, value, key, key);
}
Ok(())
} else {
Expand Down Expand Up @@ -111,6 +113,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
utils::find_program(&args.remove(0), &paths, &config).await?
};
let env = env::mix(env);
let env = env::mix_runtime(&env, &installations, &conn)?;
execve(cmd, args, env)
}
}
20 changes: 18 additions & 2 deletions src/pantry_db.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{error::Error, fs};
use std::{collections::HashMap, error::Error, fs};

use rusqlite::{params, Connection};

Expand Down Expand Up @@ -91,7 +91,7 @@ pub fn deps_for_project(
let mut stmt = conn.prepare("SELECT pkgspec FROM dependencies WHERE project = ?1")?;

let rv = stmt.query_map(params![project], |row| {
let pkgspec = row.get(0)?;
let pkgspec: String = row.get(0)?;
let pkgrq = PackageReq::parse(&pkgspec).unwrap(); //FIXME unwrap()
mxcl marked this conversation as resolved.
Show resolved Hide resolved
Ok(pkgrq)
})?;
Expand All @@ -108,6 +108,22 @@ pub fn which(cmd: &String, conn: &Connection) -> Result<String, Box<dyn Error>>
}
}

pub fn runtime_env_for_project(
project: &String,
conn: &Connection,
) -> Result<HashMap<String, String>, Box<dyn Error>> {
let sql = "SELECT envline FROM runtime_env WHERE project = ?1";
let mut stmt = conn.prepare(sql)?;
let mut rows = stmt.query(params![project])?;
mxcl marked this conversation as resolved.
Show resolved Hide resolved
let mut env = HashMap::new();
while let Some(row) = rows.next()? {
let envline: String = row.get(0)?;
let (key, value) = envline.split_once('=').unwrap();
env.insert(key.to_string(), value.to_string());
}
Ok(env)
}

// given pkgspec of program@version or project@version return PackageReq
pub fn resolve(mut pkg: PackageReq, conn: &Connection) -> Result<PackageReq, Box<dyn Error>> {
if let Ok(project) = which(&pkg.project, conn) {
Expand Down
4 changes: 2 additions & 2 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub struct PackageReq {
use regex::Regex;

impl PackageReq {
pub fn parse(pkgspec: &String) -> Result<Self, Box<dyn Error>> {
pub fn parse(pkgspec: &str) -> Result<Self, Box<dyn Error>> {
let input = pkgspec.trim();
let captures = PACKAGE_REGEX
.captures(input)
Expand All @@ -55,7 +55,7 @@ impl PackageReq {
}
}

fn semverator_version_to_string(version: &Version) -> String {
pub fn semverator_version_to_string(version: &Version) -> String {
version
.components
.iter()
Expand Down
Loading