• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pkgxdev / pkgx / 13863219990

14 Mar 2025 06:39PM UTC coverage: 91.235%. First build
13863219990

Pull #1151

github

web-flow
Merge e39b8e6c8 into 35f6bbe1d
Pull Request #1151: --query,-Q

35 of 35 new or added lines in 4 files covered. (100.0%)

1374 of 1506 relevant lines covered (91.24%)

18538506.28 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.0
/crates/lib/src/env.rs
1
use std::{
2
    collections::{HashMap, HashSet},
3
    error::Error,
4
    path::PathBuf,
5
};
6

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 {
149✔
63
    key
149✔
64
}
149✔
65

66
use crate::types::Installation;
67

68
#[cfg(unix)]
69
const SEP: &str = ":";
70
#[cfg(windows)]
71
const SEP: &str = ";";
72

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

24✔
76
    let projects: HashSet<&str> = installations
24✔
77
        .iter()
24✔
78
        .map(|i| i.pkg.project.as_str())
140✔
79
        .collect();
24✔
80

81
    for installation in installations {
164✔
82
        for key in EnvKey::iter() {
1,890✔
83
            if let Some(suffixes) = suffixes(&key) {
1,750✔
84
                for suffix in suffixes {
3,150✔
85
                    let path = installation.path.join(suffix);
1,960✔
86
                    if path.is_dir() {
1,960✔
87
                        vars.entry(key.clone())
800✔
88
                            .or_insert_with(OrderedSet::new)
800✔
89
                            .add(path);
800✔
90
                    }
1,160✔
91
                }
92
            }
560✔
93
        }
94

95
        if projects.contains("cmake.org") {
140✔
96
            vars.entry(EnvKey::CmakePrefixPath)
×
97
                .or_insert_with(OrderedSet::new)
98
                .add(installation.path.clone());
×
99
        }
140✔
100
    }
101

102
    // don’t break `man`
103
    #[cfg(unix)]
104
    if vars.contains_key(&EnvKey::Manpath) {
24✔
105
        vars.get_mut(&EnvKey::Manpath)
20✔
106
            .unwrap()
20✔
107
            .add(PathBuf::from_str("/usr/share/man").unwrap());
20✔
108
    }
20✔
109

110
    // https://github.com/pkgxdev/libpkgx/issues/70
111
    #[cfg(unix)]
112
    if vars.contains_key(&EnvKey::XdgDataDirs) {
24✔
113
        let set = vars.get_mut(&EnvKey::XdgDataDirs).unwrap();
20✔
114
        set.add(PathBuf::from_str("/usr/local/share").unwrap());
20✔
115
        set.add(PathBuf::from_str("/usr/share").unwrap());
20✔
116
    }
20✔
117

118
    let mut rv: HashMap<String, Vec<String>> = HashMap::new();
24✔
119
    for (key, set) in vars {
192✔
120
        let set = set
168✔
121
            .items
168✔
122
            .iter()
168✔
123
            .map(|p| p.to_string_lossy().to_string())
860✔
124
            .collect();
168✔
125
        rv.insert(key.as_ref().to_string(), set);
168✔
126
    }
168✔
127
    rv
24✔
128
}
24✔
129

130
use rusqlite::Connection;
131
use strum::IntoEnumIterator;
132
use strum_macros::{AsRefStr, EnumIter, EnumString};
133

134
#[derive(Debug, EnumString, AsRefStr, PartialEq, Eq, Hash, Clone, EnumIter)]
2,030✔
135
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
136
enum EnvKey {
137
    Path,
138
    Manpath,
139
    PkgConfigPath,
140
    #[cfg(unix)]
141
    LibraryPath,
142
    #[cfg(unix)]
143
    LdLibraryPath,
144
    #[cfg(unix)]
145
    Cpath,
146
    XdgDataDirs,
147
    CmakePrefixPath,
148
    #[cfg(target_os = "macos")]
149
    DyldFallbackLibraryPath,
150
    SslCertFile,
151
    #[cfg(unix)]
152
    Ldflags,
153
    PkgxDir,
154
    AclocalPath,
155
    #[cfg(windows)]
156
    Lib,
157
    #[cfg(windows)]
158
    Include,
159
}
160

161
struct OrderedSet<T: Eq + std::hash::Hash + Clone> {
162
    items: Vec<T>,
163
    set: HashSet<T>,
164
}
165

166
impl<T: Eq + std::hash::Hash + Clone> OrderedSet<T> {
167
    fn new() -> Self {
168✔
168
        OrderedSet {
168✔
169
            items: Vec::new(),
168✔
170
            set: HashSet::new(),
168✔
171
        }
168✔
172
    }
168✔
173

174
    fn add(&mut self, item: T) {
860✔
175
        if self.set.insert(item.clone()) {
860✔
176
            self.items.push(item);
860✔
177
        }
860✔
178
    }
860✔
179
}
180

181
fn suffixes(key: &EnvKey) -> Option<Vec<&'static str>> {
1,750✔
182
    match key {
1,750✔
183
        EnvKey::Path => Some(vec!["bin", "sbin"]),
140✔
184
        EnvKey::Manpath => Some(vec!["man", "share/man"]),
140✔
185
        EnvKey::PkgConfigPath => Some(vec!["share/pkgconfig", "lib/pkgconfig"]),
140✔
186
        EnvKey::XdgDataDirs => Some(vec!["share"]),
140✔
187
        EnvKey::AclocalPath => Some(vec!["share/aclocal"]),
140✔
188
        #[cfg(unix)]
189
        EnvKey::LibraryPath | EnvKey::LdLibraryPath => Some(vec!["lib", "lib64"]),
280✔
190
        #[cfg(target_os = "macos")]
191
        EnvKey::DyldFallbackLibraryPath => Some(vec!["lib", "lib64"]),
70✔
192
        #[cfg(unix)]
193
        EnvKey::Cpath => Some(vec!["include"]),
140✔
194
        EnvKey::CmakePrefixPath | EnvKey::SslCertFile | EnvKey::PkgxDir => None,
420✔
195
        #[cfg(unix)]
196
        EnvKey::Ldflags => None,
140✔
197
        #[cfg(windows)]
198
        EnvKey::Lib => Some(vec!["lib"]),
199
        #[cfg(windows)]
200
        EnvKey::Include => Some(vec!["include"]),
201
    }
202
}
1,750✔
203

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
    }
210

211
    for (key, value) in input.iter() {
×
212
        let key = &construct_platform_case_aware_env_key(key.clone());
×
213
        if let Some(values) = rv.get(key) {
×
214
            rv.insert(key.clone(), format!("{}{}{}", value.join(SEP), SEP, values));
×
215
        } else {
216
            rv.insert(key.clone(), value.join(SEP));
×
217
        }
218
    }
219

220
    rv
×
221
}
222

223
pub fn mix_runtime(
16✔
224
    input: &HashMap<PlatformCaseAwareEnvKey, String>,
16✔
225
    installations: &Vec<Installation>,
16✔
226
    conn: &Connection,
16✔
227
) -> Result<HashMap<PlatformCaseAwareEnvKey, String>, Box<dyn Error>> {
16✔
228
    let mut output: HashMap<PlatformCaseAwareEnvKey, String> = input
16✔
229
        .iter()
16✔
230
        .map(|(k, v)| (k.clone(), format!("{}{}${}", v, SEP, k)))
117✔
231
        .collect();
16✔
232

233
    for installation in installations.clone() {
80✔
234
        let runtime_env =
80✔
235
            crate::pantry_db::runtime_env_for_project(&installation.pkg.project, conn)?;
80✔
236
        for (key, runtime_value) in runtime_env {
112✔
237
            let runtime_value = expand_moustaches(&runtime_value, &installation, installations);
32✔
238
            let insert_key = construct_platform_case_aware_env_key(key.clone());
32✔
239
            let new_value = if let Some(curr_value) = output.get(&insert_key) {
32✔
240
                if runtime_value.contains(&format!("${}", key)) {
×
241
                    runtime_value.replace(&format!("${}", key), curr_value)
×
242
                } else {
243
                    // parent env overrides runtime env if the runtime env
244
                    // has no capacity to include the parent env
245
                    curr_value.clone()
×
246
                }
247
            } else if runtime_value.contains(&format!("${}", key)) {
32✔
248
                runtime_value
12✔
249
            } else {
250
                format!("${{{}:-{}}}", key, runtime_value)
20✔
251
            };
252
            output.insert(insert_key, new_value);
32✔
253
        }
254
    }
255

256
    Ok(output)
16✔
257
}
16✔
258

259
pub fn expand_moustaches(input: &str, pkg: &Installation, deps: &Vec<Installation>) -> String {
32✔
260
    let mut output = input.to_string();
32✔
261

32✔
262
    if output.starts_with("${{") {
32✔
263
        output.replace_range(..1, "");
20✔
264
    }
20✔
265

266
    output = output.replace("{{prefix}}", &pkg.path.to_string_lossy());
32✔
267
    output = output.replace("{{version}}", &format!("{}", &pkg.pkg.version));
32✔
268
    output = output.replace("{{version.major}}", &format!("{}", pkg.pkg.version.major));
32✔
269
    output = output.replace("{{version.minor}}", &format!("{}", pkg.pkg.version.minor));
32✔
270
    output = output.replace("{{version.patch}}", &format!("{}", pkg.pkg.version.patch));
32✔
271
    output = output.replace(
32✔
272
        "{{version.marketing}}",
32✔
273
        &format!("{}.{}", pkg.pkg.version.major, pkg.pkg.version.minor),
32✔
274
    );
32✔
275

276
    for dep in deps {
286✔
277
        let prefix = format!("deps.{}", dep.pkg.project);
254✔
278
        output = output.replace(
254✔
279
            &format!("{{{{{}.prefix}}}}", prefix),
254✔
280
            &dep.path.to_string_lossy(),
254✔
281
        );
254✔
282
        output = output.replace(
254✔
283
            &format!("{{{{{}.version}}}}", prefix),
254✔
284
            &format!("{}", &dep.pkg.version),
254✔
285
        );
254✔
286
        output = output.replace(
254✔
287
            &format!("{{{{{}.version.major}}}}", prefix),
254✔
288
            &format!("{}", dep.pkg.version.major),
254✔
289
        );
254✔
290
        output = output.replace(
254✔
291
            &format!("{{{{{}.version.minor}}}}", prefix),
254✔
292
            &format!("{}", dep.pkg.version.minor),
254✔
293
        );
254✔
294
        output = output.replace(
254✔
295
            &format!("{{{{{}.version.patch}}}}", prefix),
254✔
296
            &format!("{}", dep.pkg.version.patch),
254✔
297
        );
254✔
298
        output = output.replace(
254✔
299
            &format!("{{{{{}.version.marketing}}}}", prefix),
254✔
300
            &format!("{}.{}", dep.pkg.version.major, dep.pkg.version.minor),
254✔
301
        );
254✔
302
    }
254✔
303

304
    output
32✔
305
}
32✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc