Skip to content

Commit 77f9f88

Browse files
committed
+windows
1 parent c5c40a2 commit 77f9f88

File tree

14 files changed

+247
-69
lines changed

14 files changed

+247
-69
lines changed

.envrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
source ~/.cargo/env

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ jobs:
3535
clippy:
3636
strategy:
3737
matrix:
38-
os: [ubuntu-latest, macos-latest]
38+
os: [ubuntu-latest, macos-latest, windows-latest]
3939
runs-on: ${{ matrix.os }}
4040
steps:
4141
- uses: actions/checkout@v4
@@ -63,7 +63,7 @@ jobs:
6363
needs: fmt
6464
strategy:
6565
matrix:
66-
os: [ubuntu-latest, macos-latest]
66+
os: [ubuntu-latest, macos-latest, windows-latest]
6767
runs-on: ${{ matrix.os }}
6868
steps:
6969
- uses: actions/checkout@v4

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ Python 2.7.18
5555
- `x86_64` & `arm64`
5656

5757
> [!TIP]
58+
>
5859
> We have gone to good lengths to make `pkgx` (and the packages it installs)
5960
> work with almost nothing else installed, making it ideal for tiny containers.
6061

crates/cli/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "pkgx"
33
description = "Run anything"
44
authors = ["Max Howell <mxcl@me.com>", "Jacob Heider <jacob@pkgx.dev>"]
55
license = "Apache-2.0"
6-
version = "2.3.2"
6+
version = "2.4.0
77
edition = "2021"
88
repository = "https://github.com/pkgxdev/pkgx"
99

crates/cli/src/execve.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
1+
#[cfg(unix)]
12
use nix::unistd::execve as nix_execve;
3+
#[cfg(unix)]
24
use std::ffi::CString;
5+
6+
use libpkgx::env::PlatformCaseAwareEnvKey;
37
use std::{collections::HashMap, error::Error};
48

9+
#[cfg(unix)]
510
pub fn execve(
611
cmd: String,
712
mut args: Vec<String>,
8-
env: HashMap<String, String>,
13+
env: HashMap<PlatformCaseAwareEnvKey, String>,
914
) -> Result<(), Box<dyn Error>> {
1015
// Convert the command to a CString
16+
1117
let c_command = CString::new(cmd.clone())
1218
.map_err(|e| format!("Failed to convert command to CString: {}", e))?;
1319

@@ -47,3 +53,21 @@ pub fn execve(
4753

4854
Ok(())
4955
}
56+
57+
#[cfg(windows)]
58+
use std::process::{exit, Command};
59+
60+
#[cfg(windows)]
61+
pub fn execve(
62+
cmd: String,
63+
args: Vec<String>,
64+
env: HashMap<PlatformCaseAwareEnvKey, String>,
65+
) -> Result<(), Box<dyn Error>> {
66+
let status = Command::new(cmd)
67+
.args(args)
68+
.envs(env.iter().map(|(k, v)| (&k.0, v)))
69+
.spawn()?
70+
.wait()?;
71+
72+
exit(status.code().unwrap_or(1));
73+
}

crates/cli/src/main.rs

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,14 @@ use std::{collections::HashMap, error::Error, fmt::Write, sync::Arc, time::Durat
99
use execve::execve;
1010
use indicatif::{ProgressBar, ProgressState, ProgressStyle};
1111
use libpkgx::{
12-
config::Config, env, hydrate::hydrate, install_multi, pantry_db, resolve::resolve, sync,
13-
types::PackageReq, utils,
12+
config::Config,
13+
env::{self, construct_platform_case_aware_env_key},
14+
hydrate::hydrate,
15+
install_multi, pantry_db,
16+
resolve::resolve,
17+
sync,
18+
types::PackageReq,
19+
utils,
1420
};
1521
use regex::Regex;
1622
use rusqlite::Connection;
@@ -195,9 +201,13 @@ async fn main() -> Result<(), Box<dyn Error>> {
195201
paths.append(&mut pkgpaths.clone());
196202
}
197203
if let Ok(syspaths) = std::env::var("PATH") {
204+
#[cfg(windows)]
205+
let sep = ";";
206+
#[cfg(not(windows))]
207+
let sep = ":";
198208
paths.extend(
199209
syspaths
200-
.split(':')
210+
.split(sep)
201211
.map(|x| x.to_string())
202212
.collect::<Vec<String>>(),
203213
);
@@ -209,21 +219,29 @@ async fn main() -> Result<(), Box<dyn Error>> {
209219

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

222+
#[cfg(unix)]
223+
let sep = ":";
224+
#[cfg(windows)]
225+
let sep = ";";
226+
212227
for (key, value) in env.clone() {
213228
if let Some(caps) = re.captures(&value) {
214229
env.insert(key, caps.get(1).unwrap().as_str().to_string());
215230
} else {
216231
let cleaned_value = value
217-
.replace(&format!(":${}", key), "")
218-
.replace(&format!("${}:", key), "")
232+
.replace(&format!("{}${}", sep, key), "")
233+
.replace(&format!("${}{}", key, sep), "")
219234
.replace(&format!("; ${}", key), "") // one pantry instance of this
220235
.replace(&format!("${}", key), "");
221236
env.insert(key, cleaned_value);
222237
}
223238
}
224239

225240
// fork bomb protection
226-
env.insert("PKGX_LVL".to_string(), pkgx_lvl.to_string());
241+
env.insert(
242+
construct_platform_case_aware_env_key("PKGX_LVL".to_string()),
243+
pkgx_lvl.to_string(),
244+
);
227245

228246
clear_progress_bar();
229247

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

239257
if !flags.json {
240-
let env = env.iter().map(|(k, v)| (k.clone(), v.join(":"))).collect();
258+
let env = env
259+
.iter()
260+
.map(|(k, v)| {
261+
(
262+
construct_platform_case_aware_env_key(k.clone()),
263+
v.join(":"),
264+
)
265+
})
266+
.collect();
241267
let env = env::mix_runtime(&env, &installations, &conn)?;
242268
for (key, value) in env {
243269
println!(

crates/lib/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "libpkgx"
33
description = "Install and run `pkgx` packages"
44
authors = ["Max Howell <mxcl@me.com>", "Jacob Heider <jacob@pkgx.dev>"]
55
license = "Apache-2.0"
6-
version = "0.3.3"
6+
version = "0.4.0"
77
edition = "2021"
88
repository = "https://github.com/pkgxdev/pkgx"
99

crates/lib/build.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
fn main() {
2-
let dist_url = option_env!("PKGX_DIST_URL").unwrap_or("https://dist.tea.xyz");
2+
#[cfg(unix)]
3+
let dist_url = option_env!("PKGX_DIST_URL").unwrap_or("https://dist.pkgx.dev");
4+
#[cfg(windows)]
5+
let dist_url = option_env!("PKGX_DIST_URL").unwrap_or("https://dist.pkgx.dev/v2");
36
let default_pantry_tarball_filename = "pantry.tgz";
47
let pantry_url =
58
option_env!("PKGX_PANTRY_TARBALL_FILENAME").unwrap_or(default_pantry_tarball_filename);

crates/lib/src/env.rs

Lines changed: 100 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,74 @@ use std::{
22
collections::{HashMap, HashSet},
33
error::Error,
44
path::PathBuf,
5-
str::FromStr,
65
};
76

7+
#[cfg(unix)]
8+
use std::str::FromStr;
9+
10+
#[cfg(windows)]
11+
use std::{
12+
fmt,
13+
hash::{Hash, Hasher},
14+
};
15+
16+
#[cfg(windows)]
17+
#[derive(Clone)]
18+
pub struct CaseInsensitiveKey(pub String);
19+
20+
#[cfg(windows)]
21+
impl PartialEq for CaseInsensitiveKey {
22+
fn eq(&self, other: &Self) -> bool {
23+
self.0.eq_ignore_ascii_case(&other.0)
24+
}
25+
}
26+
27+
#[cfg(windows)]
28+
impl fmt::Display for CaseInsensitiveKey {
29+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30+
write!(f, "{}", self.0)
31+
}
32+
}
33+
34+
#[cfg(windows)]
35+
impl Eq for CaseInsensitiveKey {}
36+
37+
#[cfg(windows)]
38+
impl Hash for CaseInsensitiveKey {
39+
fn hash<H: Hasher>(&self, state: &mut H) {
40+
self.0.to_lowercase().hash(state);
41+
}
42+
}
43+
44+
#[cfg(windows)]
45+
impl fmt::Debug for CaseInsensitiveKey {
46+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47+
write!(f, "{:?}", self.0)
48+
}
49+
}
50+
51+
#[cfg(windows)]
52+
pub type PlatformCaseAwareEnvKey = CaseInsensitiveKey;
53+
#[cfg(not(windows))]
54+
pub type PlatformCaseAwareEnvKey = String;
55+
56+
#[cfg(windows)]
57+
pub fn construct_platform_case_aware_env_key(key: String) -> PlatformCaseAwareEnvKey {
58+
CaseInsensitiveKey(key)
59+
}
60+
61+
#[cfg(not(windows))]
62+
pub fn construct_platform_case_aware_env_key(key: String) -> PlatformCaseAwareEnvKey {
63+
key
64+
}
65+
866
use crate::types::Installation;
967

68+
#[cfg(unix)]
69+
const SEP: &str = ":";
70+
#[cfg(windows)]
71+
const SEP: &str = ";";
72+
1073
pub fn map(installations: &Vec<Installation>) -> HashMap<String, Vec<String>> {
1174
let mut vars: HashMap<EnvKey, OrderedSet<PathBuf>> = HashMap::new();
1275

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

39102
// don’t break `man`
103+
#[cfg(unix)]
40104
if vars.contains_key(&EnvKey::Manpath) {
41105
vars.get_mut(&EnvKey::Manpath)
42106
.unwrap()
43107
.add(PathBuf::from_str("/usr/share/man").unwrap());
44108
}
109+
45110
// https://github.com/pkgxdev/libpkgx/issues/70
111+
#[cfg(unix)]
46112
if vars.contains_key(&EnvKey::XdgDataDirs) {
47113
let set = vars.get_mut(&EnvKey::XdgDataDirs).unwrap();
48114
set.add(PathBuf::from_str("/usr/local/share").unwrap());
@@ -71,17 +137,25 @@ enum EnvKey {
71137
Path,
72138
Manpath,
73139
PkgConfigPath,
140+
#[cfg(unix)]
74141
LibraryPath,
142+
#[cfg(unix)]
75143
LdLibraryPath,
144+
#[cfg(unix)]
76145
Cpath,
77146
XdgDataDirs,
78147
CmakePrefixPath,
79148
#[cfg(target_os = "macos")]
80149
DyldFallbackLibraryPath,
81150
SslCertFile,
151+
#[cfg(unix)]
82152
Ldflags,
83153
PkgxDir,
84154
AclocalPath,
155+
#[cfg(windows)]
156+
Lib,
157+
#[cfg(windows)]
158+
Include,
85159
}
86160

87161
struct OrderedSet<T: Eq + std::hash::Hash + Clone> {
@@ -111,44 +185,58 @@ fn suffixes(key: &EnvKey) -> Option<Vec<&'static str>> {
111185
EnvKey::PkgConfigPath => Some(vec!["share/pkgconfig", "lib/pkgconfig"]),
112186
EnvKey::XdgDataDirs => Some(vec!["share"]),
113187
EnvKey::AclocalPath => Some(vec!["share/aclocal"]),
188+
#[cfg(unix)]
114189
EnvKey::LibraryPath | EnvKey::LdLibraryPath => Some(vec!["lib", "lib64"]),
115190
#[cfg(target_os = "macos")]
116191
EnvKey::DyldFallbackLibraryPath => Some(vec!["lib", "lib64"]),
192+
#[cfg(unix)]
117193
EnvKey::Cpath => Some(vec!["include"]),
118-
EnvKey::CmakePrefixPath | EnvKey::SslCertFile | EnvKey::Ldflags | EnvKey::PkgxDir => None,
194+
EnvKey::CmakePrefixPath | EnvKey::SslCertFile | EnvKey::PkgxDir => None,
195+
#[cfg(unix)]
196+
EnvKey::Ldflags => None,
197+
#[cfg(windows)]
198+
EnvKey::Lib => Some(vec!["lib"]),
199+
#[cfg(windows)]
200+
EnvKey::Include => Some(vec!["include"]),
119201
}
120202
}
121203

122-
pub fn mix(input: HashMap<String, Vec<String>>) -> HashMap<String, String> {
123-
let mut rv = HashMap::from_iter(std::env::vars());
204+
pub fn mix(input: HashMap<String, Vec<String>>) -> HashMap<PlatformCaseAwareEnvKey, String> {
205+
let mut rv: HashMap<PlatformCaseAwareEnvKey, String> = HashMap::new();
206+
207+
for (key, value) in std::env::vars() {
208+
rv.insert(construct_platform_case_aware_env_key(key), value);
209+
}
124210

125211
for (key, value) in input.iter() {
212+
let key = &construct_platform_case_aware_env_key(key.clone());
126213
if let Some(values) = rv.get(key) {
127-
rv.insert(key.clone(), format!("{}:{}", value.join(":"), values));
214+
rv.insert(key.clone(), format!("{}{}{}", value.join(SEP), SEP, values));
128215
} else {
129-
rv.insert(key.clone(), value.join(":"));
216+
rv.insert(key.clone(), value.join(SEP));
130217
}
131218
}
132219

133220
rv
134221
}
135222

136223
pub fn mix_runtime(
137-
input: &HashMap<String, String>,
224+
input: &HashMap<PlatformCaseAwareEnvKey, String>,
138225
installations: &Vec<Installation>,
139226
conn: &Connection,
140-
) -> Result<HashMap<String, String>, Box<dyn Error>> {
141-
let mut output: HashMap<String, String> = input
227+
) -> Result<HashMap<PlatformCaseAwareEnvKey, String>, Box<dyn Error>> {
228+
let mut output: HashMap<PlatformCaseAwareEnvKey, String> = input
142229
.iter()
143-
.map(|(k, v)| (k.clone(), format!("{}:${}", v, k)))
230+
.map(|(k, v)| (k.clone(), format!("{}{}${}", v, SEP, k)))
144231
.collect();
145232

146233
for installation in installations.clone() {
147234
let runtime_env =
148235
crate::pantry_db::runtime_env_for_project(&installation.pkg.project, conn)?;
149236
for (key, runtime_value) in runtime_env {
150237
let runtime_value = expand_moustaches(&runtime_value, &installation, installations);
151-
let new_value = if let Some(curr_value) = output.get(&key) {
238+
let insert_key = construct_platform_case_aware_env_key(key.clone());
239+
let new_value = if let Some(curr_value) = output.get(&insert_key) {
152240
if runtime_value.contains(&format!("${}", key)) {
153241
runtime_value.replace(&format!("${}", key), curr_value)
154242
} else {
@@ -161,7 +249,7 @@ pub fn mix_runtime(
161249
} else {
162250
format!("${{{}:-{}}}", key, runtime_value)
163251
};
164-
output.insert(key, new_value);
252+
output.insert(insert_key, new_value);
165253
}
166254
}
167255

0 commit comments

Comments
 (0)