Skip to content

Commit

Permalink
+windows
Browse files Browse the repository at this point in the history
  • Loading branch information
mxcl committed Mar 11, 2025
1 parent c5c40a2 commit 77f9f88
Show file tree
Hide file tree
Showing 14 changed files with 247 additions and 69 deletions.
1 change: 1 addition & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
source ~/.cargo/env
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
clippy:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -63,7 +63,7 @@ jobs:
needs: fmt
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ Python 2.7.18
- `x86_64` & `arm64`

> [!TIP]
>
> We have gone to good lengths to make `pkgx` (and the packages it installs)
> work with almost nothing else installed, making it ideal for tiny containers.
Expand Down
2 changes: 1 addition & 1 deletion crates/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "pkgx"
description = "Run anything"
authors = ["Max Howell <mxcl@me.com>", "Jacob Heider <jacob@pkgx.dev>"]
license = "Apache-2.0"
version = "2.3.2"
version = "2.4.0
edition = "2021"
repository = "https://github.com/pkgxdev/pkgx"

Expand Down
26 changes: 25 additions & 1 deletion crates/cli/src/execve.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
#[cfg(unix)]
use nix::unistd::execve as nix_execve;
#[cfg(unix)]
use std::ffi::CString;

use libpkgx::env::PlatformCaseAwareEnvKey;
use std::{collections::HashMap, error::Error};

#[cfg(unix)]
pub fn execve(
cmd: String,
mut args: Vec<String>,
env: HashMap<String, String>,
env: HashMap<PlatformCaseAwareEnvKey, String>,
) -> Result<(), Box<dyn Error>> {
// Convert the command to a CString

let c_command = CString::new(cmd.clone())
.map_err(|e| format!("Failed to convert command to CString: {}", e))?;

Expand Down Expand Up @@ -47,3 +53,21 @@ pub fn execve(

Ok(())
}

#[cfg(windows)]
use std::process::{exit, Command};

#[cfg(windows)]
pub fn execve(
cmd: String,
args: Vec<String>,
env: HashMap<PlatformCaseAwareEnvKey, String>,
) -> Result<(), Box<dyn Error>> {
let status = Command::new(cmd)
.args(args)
.envs(env.iter().map(|(k, v)| (&k.0, v)))
.spawn()?
.wait()?;

exit(status.code().unwrap_or(1));
}
40 changes: 33 additions & 7 deletions crates/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@ use std::{collections::HashMap, error::Error, fmt::Write, sync::Arc, time::Durat
use execve::execve;
use indicatif::{ProgressBar, ProgressState, ProgressStyle};
use libpkgx::{
config::Config, env, hydrate::hydrate, install_multi, pantry_db, resolve::resolve, sync,
types::PackageReq, utils,
config::Config,
env::{self, construct_platform_case_aware_env_key},
hydrate::hydrate,
install_multi, pantry_db,
resolve::resolve,
sync,
types::PackageReq,
utils,
};
use regex::Regex;
use rusqlite::Connection;
Expand Down Expand Up @@ -195,9 +201,13 @@ async fn main() -> Result<(), Box<dyn Error>> {
paths.append(&mut pkgpaths.clone());
}
if let Ok(syspaths) = std::env::var("PATH") {
#[cfg(windows)]
let sep = ";";
#[cfg(not(windows))]
let sep = ":";
paths.extend(
syspaths
.split(':')
.split(sep)
.map(|x| x.to_string())
.collect::<Vec<String>>(),
);
Expand All @@ -209,21 +219,29 @@ async fn main() -> Result<(), Box<dyn Error>> {

let re = Regex::new(r"^\$\{\w+:-([^}]+)\}$").unwrap();

#[cfg(unix)]
let sep = ":";
#[cfg(windows)]
let sep = ";";

for (key, value) in env.clone() {
if let Some(caps) = re.captures(&value) {
env.insert(key, caps.get(1).unwrap().as_str().to_string());
} else {
let cleaned_value = value
.replace(&format!(":${}", key), "")
.replace(&format!("${}:", key), "")
.replace(&format!("{}${}", sep, key), "")
.replace(&format!("${}{}", key, sep), "")
.replace(&format!("; ${}", key), "") // one pantry instance of this
.replace(&format!("${}", key), "");
env.insert(key, cleaned_value);
}
}

// fork bomb protection
env.insert("PKGX_LVL".to_string(), pkgx_lvl.to_string());
env.insert(
construct_platform_case_aware_env_key("PKGX_LVL".to_string()),
pkgx_lvl.to_string(),
);

clear_progress_bar();

Expand All @@ -237,7 +255,15 @@ async fn main() -> Result<(), Box<dyn Error>> {
clear_progress_bar();

if !flags.json {
let env = env.iter().map(|(k, v)| (k.clone(), v.join(":"))).collect();
let env = env
.iter()
.map(|(k, v)| {
(
construct_platform_case_aware_env_key(k.clone()),
v.join(":"),
)
})
.collect();
let env = env::mix_runtime(&env, &installations, &conn)?;
for (key, value) in env {
println!(
Expand Down
2 changes: 1 addition & 1 deletion crates/lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "libpkgx"
description = "Install and run `pkgx` packages"
authors = ["Max Howell <mxcl@me.com>", "Jacob Heider <jacob@pkgx.dev>"]
license = "Apache-2.0"
version = "0.3.3"
version = "0.4.0"
edition = "2021"
repository = "https://github.com/pkgxdev/pkgx"

Expand Down
5 changes: 4 additions & 1 deletion crates/lib/build.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
fn main() {
let dist_url = option_env!("PKGX_DIST_URL").unwrap_or("https://dist.tea.xyz");
#[cfg(unix)]
let dist_url = option_env!("PKGX_DIST_URL").unwrap_or("https://dist.pkgx.dev");
#[cfg(windows)]
let dist_url = option_env!("PKGX_DIST_URL").unwrap_or("https://dist.pkgx.dev/v2");
let default_pantry_tarball_filename = "pantry.tgz";
let pantry_url =
option_env!("PKGX_PANTRY_TARBALL_FILENAME").unwrap_or(default_pantry_tarball_filename);
Expand Down
112 changes: 100 additions & 12 deletions crates/lib/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,74 @@ use std::{
collections::{HashMap, HashSet},
error::Error,
path::PathBuf,
str::FromStr,
};

#[cfg(unix)]
use std::str::FromStr;

#[cfg(windows)]
use std::{
fmt,
hash::{Hash, Hasher},
};

#[cfg(windows)]
#[derive(Clone)]
pub struct CaseInsensitiveKey(pub String);

#[cfg(windows)]
impl PartialEq for CaseInsensitiveKey {
fn eq(&self, other: &Self) -> bool {
self.0.eq_ignore_ascii_case(&other.0)
}
}

#[cfg(windows)]
impl fmt::Display for CaseInsensitiveKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}

#[cfg(windows)]
impl Eq for CaseInsensitiveKey {}

#[cfg(windows)]
impl Hash for CaseInsensitiveKey {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.to_lowercase().hash(state);
}
}

#[cfg(windows)]
impl fmt::Debug for CaseInsensitiveKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.0)
}
}

#[cfg(windows)]
pub type PlatformCaseAwareEnvKey = CaseInsensitiveKey;
#[cfg(not(windows))]
pub type PlatformCaseAwareEnvKey = String;

#[cfg(windows)]
pub fn construct_platform_case_aware_env_key(key: String) -> PlatformCaseAwareEnvKey {
CaseInsensitiveKey(key)
}

#[cfg(not(windows))]
pub fn construct_platform_case_aware_env_key(key: String) -> PlatformCaseAwareEnvKey {
key
}

use crate::types::Installation;

#[cfg(unix)]
const SEP: &str = ":";
#[cfg(windows)]
const SEP: &str = ";";

pub fn map(installations: &Vec<Installation>) -> HashMap<String, Vec<String>> {
let mut vars: HashMap<EnvKey, OrderedSet<PathBuf>> = HashMap::new();

Expand Down Expand Up @@ -37,12 +100,15 @@ pub fn map(installations: &Vec<Installation>) -> HashMap<String, Vec<String>> {
}

// don’t break `man`
#[cfg(unix)]
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
#[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());
Expand Down Expand Up @@ -71,17 +137,25 @@ 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> {
Expand Down Expand Up @@ -111,44 +185,58 @@ fn suffixes(key: &EnvKey) -> Option<Vec<&'static str>> {
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::Ldflags | EnvKey::PkgxDir => None,
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<String, String> {
let mut rv = HashMap::from_iter(std::env::vars());
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(":"), values));
rv.insert(key.clone(), format!("{}{}{}", value.join(SEP), SEP, values));
} else {
rv.insert(key.clone(), value.join(":"));
rv.insert(key.clone(), value.join(SEP));
}
}

rv
}

pub fn mix_runtime(
input: &HashMap<String, String>,
input: &HashMap<PlatformCaseAwareEnvKey, String>,
installations: &Vec<Installation>,
conn: &Connection,
) -> Result<HashMap<String, String>, Box<dyn Error>> {
let mut output: HashMap<String, String> = input
) -> Result<HashMap<PlatformCaseAwareEnvKey, String>, Box<dyn Error>> {
let mut output: HashMap<PlatformCaseAwareEnvKey, String> = input
.iter()
.map(|(k, v)| (k.clone(), format!("{}:${}", v, k)))
.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 new_value = if let Some(curr_value) = output.get(&key) {
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 {
Expand All @@ -161,7 +249,7 @@ pub fn mix_runtime(
} else {
format!("${{{}:-{}}}", key, runtime_value)
};
output.insert(key, new_value);
output.insert(insert_key, new_value);
}
}

Expand Down
Loading

0 comments on commit 77f9f88

Please sign in to comment.