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

pkgxdev / pkgx / 10740094409

06 Sep 2024 02:23PM UTC coverage: 92.292% (-1.1%) from 93.36%
10740094409

push

github

mxcl
deduped cdk.json

373 of 404 branches covered (92.33%)

1435 of 1555 relevant lines covered (92.28%)

79.73 hits per line

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

91.25
/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
  console.assert(projects.size == installations.length, "pkgx: env is being duped")
10✔
41

42
  const common_vars: Record<string, OrderedSet<string>> = {}
10✔
43
  const common_keys = new Set<string>()
10✔
44

45
  for (const { path } of installations) {
10✔
46

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

55
    test("bin", 'PATH')
16✔
56
    test("include", 'CPATH')
16✔
57
    test("lib", 'LIBRARY_PATH')
16✔
58
    test('lib/pkgconfig', 'PKG_CONFIG_PATH')
16✔
59
    test("man", "MANPATH")
16✔
60
    test("sbin", 'PATH')
16✔
61
    test('share', 'XDG_DATA_DIRS')
16✔
62
    test("share/man", "MANPATH")
16✔
63
    test('share/pkgconfig', 'PKG_CONFIG_PATH')
16✔
64

65
    if (projects.has('cmake.org')) {
×
66
      test('', 'CMAKE_PREFIX_PATH')
×
67
    }
×
68

69
    if (projects.has('gnu.org/autoconf')) {
×
70
      test("share/aclocal", 'ACLOCAL_PATH')
×
71
    }
×
72
  }
16✔
73

74
  const rv: Record<string, string> = {}
10✔
75

76
  for (const { pkg } of installations) {
10✔
77
    const runtime_env = await _internals.runtime_env(pkg, installations)
16✔
78
    for (const key in runtime_env) {
16✔
79
      const value = runtime_env[key]
32✔
80

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

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

100
        common_vars[key] = new_set
40✔
101

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

120
  for (const key of common_keys) {
10✔
121
    if (!common_vars[key].isEmpty()) {
14✔
122
      rv[key] = [...common_vars[key].toArray(), `$${key}`].join(':')
56✔
123
    }
14✔
124
  }
14✔
125

126
  return rv
10✔
127
}
10✔
128

129
////////////////////////////////////////////////////////////////////////// utils
1✔
130
class OrderedSet<T> {
6✔
131
  private items: T[];
18✔
132
  private set: Set<T>;
18✔
133

134
  constructor(items: T[] = []) {
18✔
135
    this.items = items;
30✔
136
    this.set = new Set();
30✔
137
  }
30✔
138

139
  add(item: T): void {
18✔
140
    if (!this.set.has(item)) {
44✔
141
      this.items.push(item);
64✔
142
      this.set.add(item);
64✔
143
    }
64✔
144
  }
44✔
145

146
  add_all(items: OrderedSet<T>) {
18✔
147
    for (const item of items.items) {
22✔
148
      this.add(item)
29✔
149
    }
29✔
150
  }
22✔
151

152
  toArray(): T[] {
18✔
153
    return [...this.items];
78✔
154
  }
26✔
155

156
  isEmpty(): boolean {
18✔
157
    return this.items.length == 0
22✔
158
  }
22✔
159
}
18✔
160

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