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

pkgxdev / pkgx / 10947024976

19 Sep 2024 06:28PM UTC coverage: 91.505% (-1.2%) from 92.742%
10947024976

push

github

mxcl
Pass `--unstable-process`

Fixes #1038

373 of 407 branches covered (91.65%)

1469 of 1606 relevant lines covered (91.47%)

81.85 hits per line

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

91.41
/src/prefab/construct-env.ts
1
import { Installation, Package, hooks, utils } from "pkgx"
6✔
2
const { usePantry } = hooks
6✔
3
const { host } = utils
6✔
4

5
export default async function(pkgenv: { installations: Installation[] }) {
6✔
6
  const rv = await mkenv({...pkgenv})
27✔
7

8
  // don’t break `man` lol
9✔
9
  //TODO don’t add if already there obv
9✔
10
  if (rv["MANPATH"]) {
9✔
11
    rv["MANPATH"] = `${rv["MANPATH"]}:/usr/share/man`
10✔
12
  }
10✔
13

14
  // makes libraries precise rather than having them use their rpaths
9✔
15
  //FIXME SIP on macOS prevents DYLD_FALLBACK_LIBRARY_PATH from propagating to grandchild processes
9✔
16
  if (rv.LIBRARY_PATH) {
9✔
17
    rv.LD_LIBRARY_PATH = rv.LIBRARY_PATH.replace('$LIBRARY_PATH', '${LD_LIBRARY_PATH}')
10✔
18
    if (host().platform == 'darwin') {
×
19
      // non FALLBACK variety causes strange issues in edge cases
×
20
      // where our symbols somehow override symbols from the macOS system
×
21
      rv.DYLD_FALLBACK_LIBRARY_PATH = rv.LIBRARY_PATH.replace('$LIBRARY_PATH', '${DYLD_FALLBACK_LIBRARY_PATH}')
×
22
    }
×
23
  }
10✔
24

25
  for (const key in rv) {
9✔
26
    rv[key] = rv[key].replaceAll(new RegExp(`\\$${key}\\b`, 'g'), `\${${key}}`)
16✔
27
    // don’t end with a trailing `:` since that is sometimes interpreted as CWD and can break things
16✔
28
    // instead of `foo:${PATH}` we end up with `foo${PATH:+:PATH}` which is not POSIX but works
16✔
29
    // with all the shells that we support shellcode for and avoids a trailing `:`
16✔
30
    // NOTE this may not work with FISH though.
16✔
31
    rv[key] = rv[key].replaceAll(new RegExp(`:+\\$\{${key}}$`, 'g'), `\${${key}:+:$${key}}`)
16✔
32
  }
16✔
33

34
  return rv
9✔
35
}
9✔
36

37
///////////////////////// reworked from useShellEnv needs porting back to libpkgx
1✔
38
async function mkenv({installations}: {installations: Installation[]}) {
10✔
39
  const projects = new Set(installations.map(x => `${x.pkg.project}@${x.pkg.version}`))
10✔
40

41
  // we need to do this as well, since we allow multiple versions of e.g. unicode.org.
10✔
42
  // however, if we *just* use this as `projects`, then tests below like `projects.has('cmake.org')` will fail
10✔
43
  const projects_with_versions = new Set(installations.map(x => `${x.pkg.project}@${x.pkg.version}`))
10✔
44
  console.assert(projects_with_versions.size == installations.length, "pkgx: env is being duped")
10✔
45

46
  const common_vars: Record<string, OrderedSet<string>> = {}
10✔
47
  const common_keys = new Set<string>()
10✔
48

49
  for (const { path } of installations) {
10✔
50

51
    const test = (part: string, key: string) => {
16✔
52
      const d = path.join(part).isDirectory()
70✔
53
      if (!d) return
70✔
54
      if (!common_vars[key]) common_vars[key] = new OrderedSet()
74✔
55
      common_vars[key].add(d.string)
74✔
56
      common_keys.add(key)
74✔
57
    }
16✔
58

59
    test("bin", 'PATH')
16✔
60
    test("include", 'CPATH')
16✔
61
    test("lib", 'LIBRARY_PATH')
16✔
62
    test('lib/pkgconfig', 'PKG_CONFIG_PATH')
16✔
63
    test("man", "MANPATH")
16✔
64
    test("sbin", 'PATH')
16✔
65
    test('share', 'XDG_DATA_DIRS')
16✔
66
    test("share/man", "MANPATH")
16✔
67
    test('share/pkgconfig', 'PKG_CONFIG_PATH')
16✔
68

69
    if (projects.has('cmake.org')) {
×
70
      test('', 'CMAKE_PREFIX_PATH')
×
71
    }
×
72

73
    if (projects.has('gnu.org/autoconf')) {
×
74
      test("share/aclocal", 'ACLOCAL_PATH')
×
75
    }
×
76
  }
16✔
77

78
  const rv: Record<string, string> = {}
10✔
79

80
  for (const { pkg } of installations) {
10✔
81
    const runtime_env = await _internals.runtime_env(pkg, installations)
16✔
82
    for (const key in runtime_env) {
16✔
83
      const value = runtime_env[key]
32✔
84

85
      if (common_keys.has(key)) {
32✔
86
        // if the package has specific env for a key we handle ourselves we treat it differently
40✔
87

88
        const new_set = new OrderedSet<string>()
40✔
89
        let superkey_present = false
40✔
90
        for (const part of value.split(":")) {
40✔
91
          if (part == `$${key}`) {
52✔
92
            common_vars[key].toArray().forEach(x => new_set.add(x))
56✔
93
            superkey_present = true
56✔
94
          } else {
52✔
95
            new_set.add(part)
60✔
96
          }
60✔
97
        }
52✔
98
        // we don’t care if the package author didn’t include the superkey
40✔
99
        // we are not going to throw away all the other env lol!
40✔
100
        if (!superkey_present) {
40✔
101
          new_set.add_all(common_vars[key])
44✔
102
        }
44✔
103

104
        common_vars[key] = new_set
40✔
105

106
      } else {
40✔
107
        const rx = new RegExp(`(\\$${key})(\\b)`, 'g')
40✔
108
        if (rx.test(value)) {
40✔
109
          if (rv[key]) {
44✔
110
            // replace eg. `foo:$FOO:bar` with `foo:${existing}:$FOO:bar`
47✔
111
            rv[key] = value.replaceAll(rx, (_, a, b) => `${b}${rv[key]}${b}${a}`)
47✔
112
          } else {
44✔
113
            rv[key] = value
45✔
114
          }
45✔
115
        } else {
44✔
116
          //NOTE this means we may replace user-specified env
44✔
117
          //TODO show warning!
44✔
118
          rv[key] = value
44✔
119
        }
44✔
120
      }
40✔
121
    }
32✔
122
  }
16✔
123

124
  for (const key of common_keys) {
10✔
125
    if (!common_vars[key].isEmpty()) {
14✔
126
      rv[key] = [...common_vars[key].toArray(), `$${key}`].join(':')
56✔
127
    }
14✔
128
  }
14✔
129

130
  return rv
10✔
131
}
10✔
132

133
////////////////////////////////////////////////////////////////////////// utils
1✔
134
class OrderedSet<T> {
6✔
135
  private items: T[];
18✔
136
  private set: Set<T>;
18✔
137

138
  constructor(items: T[] = []) {
18✔
139
    this.items = items;
30✔
140
    this.set = new Set();
30✔
141
  }
30✔
142

143
  add(item: T): void {
18✔
144
    if (!this.set.has(item)) {
44✔
145
      this.items.push(item);
64✔
146
      this.set.add(item);
64✔
147
    }
64✔
148
  }
44✔
149

150
  add_all(items: OrderedSet<T>) {
18✔
151
    for (const item of items.items) {
22✔
152
      this.add(item)
29✔
153
    }
29✔
154
  }
22✔
155

156
  toArray(): T[] {
18✔
157
    return [...this.items];
78✔
158
  }
26✔
159

160
  isEmpty(): boolean {
18✔
161
    return this.items.length == 0
22✔
162
  }
22✔
163
}
18✔
164

165
////////////////////////////////////////////////////////////////////// internals
1✔
166
export const _internals = {
6✔
167
  runtime_env: (pkg: Package, installations: Installation[]) => usePantry().project(pkg.project).runtime.env(pkg.version, installations),
6✔
168
  mkenv
6✔
169
}
6✔
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

© 2026 Coveralls, Inc