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

pkgxdev / libpkgx / 29957096047

22 Jul 2026 08:54PM UTC coverage: 82.201%. First build
29957096047

Pull #90

github

web-flow
Merge 7ad7aae00 into 5aaf5ed3f
Pull Request #90: fix global declares (jsr doesn't allow them)

589 of 784 branches covered (75.13%)

Branch coverage included in aggregate %.

37 of 40 new or added lines in 10 files covered. (92.5%)

2570 of 3059 relevant lines covered (84.01%)

3107.62 hits per line

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

79.45
/src/hooks/useShellEnv.ts
1
import type { Installation } from "../types.ts"
2
import { insert } from "../utils/misc.ts"
36✔
3
import usePantry from "./usePantry.ts"
36✔
4
import host from "../utils/host.ts"
36✔
5

6
export const EnvKeys = [
36✔
7
  'PATH',
36✔
8
  'MANPATH',
36✔
9
  'PKG_CONFIG_PATH',
36✔
10
  'LIBRARY_PATH',
36✔
11
  'LD_LIBRARY_PATH',
36✔
12
  'CPATH',
36✔
13
  'XDG_DATA_DIRS',
36✔
14
  'CMAKE_PREFIX_PATH',
36✔
15
  'DYLD_FALLBACK_LIBRARY_PATH',
36✔
16
  'SSL_CERT_FILE',
36✔
17
  'LDFLAGS',
36✔
18
  'PKGX_DIR',
36✔
19
  'ACLOCAL_PATH'
36✔
20
] as const
36✔
21
export type EnvKey = typeof EnvKeys[number]
22

23
/// Projects whose `lib/` MUST NOT be auto-added to LIBRARY_PATH /
2✔
24
/// LD_LIBRARY_PATH for consumers, because their `lib/` *is* the libc
2✔
25
/// (libc.so.6, libm.so.6, ld-linux*.so) — adding it to consumers'
2✔
26
/// dynamic-linker search path forces every executable in the same
2✔
27
/// pkgx env to load this bottle's libc, which fails the moment the
2✔
28
/// host's own ld-linux is older than what the bottle's libc requires.
2✔
29
///
2✔
30
/// This set lists projects that legitimately ship libc itself. It is
2✔
31
/// intentionally small and hardcoded; long-term the per-package opt
2✔
32
/// should be expressed in the bottle's own metadata so the pantry
2✔
33
/// doesn't need a coordinated libpkgx release for new entries.
2✔
34
const NO_LIB_EXPORT: ReadonlySet<string> = new Set([
36✔
35
  'gnu.org/glibc',
36✔
36
])
36✔
37

38
interface Options {
39
  installations: Installation[]
40
}
41

42
export default function(): {
36✔
43
  map: (opts: Options) => Promise<Record<string, string[]>>
44
  expand: (env: Record<string, string[]>) => string
45
  flatten: (env: Record<string, string[]>) => Record<string, string>
46
} {
47
  return {
50✔
48
    map,
50✔
49
    expand,
50✔
50
    flatten
50✔
51
  }
50✔
52
}
50✔
53

54
/// returns an environment that supports the provided packages
2✔
55
async function map({installations}: Options): Promise<Record<string, string[]>> {
50✔
56
  const vars: Partial<Record<EnvKey, OrderedSet<string>>> = {}
50✔
57
  const isMac = host().platform == 'darwin'
50✔
58

59
  const projects = new Set(installations.map(x => x.pkg.project))
50✔
60
  const has_cmake = projects.has('cmake.org')
50✔
61
  const archaic = true
50✔
62

63
  const rv: Record<string, string[]> = {}
50✔
64
  const seen = new Set<string>()
50✔
65

66
  for (const installation of installations) {
50✔
67

NEW
68
    if (!insert(seen, installation.pkg.project).inserted) {
×
69
      console.warn("pkgx: env is being duped:", installation.pkg.project)
×
70
    }
×
71

72
    for (const key of EnvKeys) {
78✔
73
      for (const suffix of suffixes(key)!) {
442✔
74
        vars[key] = compact_add(vars[key], installation.path.join(suffix).chuzzle()?.string)
638✔
75
      }
638✔
76
    }
442✔
77

78
    // libc-style projects (see NO_LIB_EXPORT) MUST NOT have their
78✔
79
    // lib/ added to LIBRARY_PATH / LD_LIBRARY_PATH: adding a glibc
78✔
80
    // bottle's lib/ to LD_LIBRARY_PATH would force every executable
78✔
81
    // in the same pkgx env to load THIS bottle's libc.so.6, which
78✔
82
    // breaks on hosts whose own ld-linux is older than the bottle's
78✔
83
    // libc requires. Also skip the CPATH/include auto-add for the
78✔
84
    // same reason (compile-time vs runtime libc skew).
78✔
85
    if (archaic && !NO_LIB_EXPORT.has(installation.pkg.project)) {
78✔
86
      vars.LIBRARY_PATH = compact_add(vars.LIBRARY_PATH, installation.path.join("lib").chuzzle()?.string)
104✔
87
      vars.CPATH = compact_add(vars.CPATH, installation.path.join("include").chuzzle()?.string)
×
88
    }
104✔
89

90
    if (has_cmake) {
×
91
      vars.CMAKE_PREFIX_PATH = compact_add(vars.CMAKE_PREFIX_PATH, installation.path.string)
×
92
    }
×
93

94
    if (projects.has('gnu.org/autoconf')) {
×
95
      vars.ACLOCAL_PATH = compact_add(vars.ACLOCAL_PATH, installation.path.join("share/aclocal").chuzzle()?.string)
×
96
    }
×
97

98
    if (installation.pkg.project === 'openssl.org') {
×
99
      const certPath = installation.path.join("ssl/cert.pem").chuzzle()?.string
×
100
      // this is a single file, so we assume a
×
101
      // valid entry is correct
×
102
      if (certPath) {
×
103
        vars.SSL_CERT_FILE = new OrderedSet()
×
104
        vars.SSL_CERT_FILE.add(certPath)
×
105
      }
×
106
    }
×
107

108
    // pantry configured runtime environment
78✔
109
    const runtime = await usePantry().project(installation.pkg).runtime.env(installation.pkg.version, installations)
78✔
110
    for (const key in runtime) {
78✔
111
      rv[key] ??= []
88✔
112
      rv[key].push(runtime[key])
88✔
113
    }
88✔
114
  }
78✔
115

116
   // this is how we use precise versions of libraries
50✔
117
   // for your virtual environment
50✔
118
   //FIXME SIP on macOS prevents DYLD_FALLBACK_LIBRARY_PATH from propagating to grandchild processes
50✔
119
   if (vars.LIBRARY_PATH) {
50✔
120
    vars.LD_LIBRARY_PATH = vars.LIBRARY_PATH
50✔
121
    if (isMac) {
25!
122
      // non FALLBACK variety causes strange issues in edge cases
25✔
123
      // where our symbols somehow override symbols from the macOS system
25✔
124
      vars.DYLD_FALLBACK_LIBRARY_PATH = vars.LIBRARY_PATH
25✔
125
    }
25✔
126
  }
50✔
127

128
  for (const key of EnvKeys) {
50✔
129
    //FIXME where is this `undefined` __happening__?
232✔
130
    if (vars[key] === undefined || vars[key]!.isEmpty()) continue
232✔
131
    rv[key] = vars[key]!.toArray()
249✔
132
  }
249✔
133

134
  // don’t break `man` lol
50✔
135
  rv["MANPATH"]?.push("/usr/share/man")
×
136
  // https://github.com/pkgxdev/libpkgx/issues/70
50✔
137
  rv['XDG_DATA_DIRS']?.push('/usr/local/share:/usr/share')
×
138

139
  return rv
50✔
140
}
50✔
141

142
function suffixes(key: EnvKey) {
400✔
143
  switch (key) {
400✔
144
    case 'PATH':
400✔
145
      return ["bin", "sbin"]
1,712✔
146
    case 'MANPATH':
400✔
147
      return ["man", "share/man"]
1,712✔
148
    case 'PKG_CONFIG_PATH':
400✔
149
      return ['share/pkgconfig', 'lib/pkgconfig']
1,712✔
150
    case 'XDG_DATA_DIRS':
400✔
151
      return ['share']
1,284✔
152
    case 'LIBRARY_PATH':
400✔
153
    case 'LD_LIBRARY_PATH':
400✔
154
    case 'DYLD_FALLBACK_LIBRARY_PATH':
400✔
155
    case 'CPATH':
400✔
156
    case 'CMAKE_PREFIX_PATH':
400✔
157
    case 'SSL_CERT_FILE':
400✔
158
    case 'LDFLAGS':
400✔
159
    case 'PKGX_DIR':
400✔
160
    case 'ACLOCAL_PATH':
400✔
161
      return []  // we handle these specially
652✔
162
    default: {
×
163
      const exhaustiveness_check: never = key
×
164
      throw new Error(`unhandled id: ${exhaustiveness_check}`)
×
165
  }}
400✔
166
}
400✔
167

168
export function expand(env: Record<string, string[]>): string {
×
169
  let rv = ''
×
170
  for (const [key, value] of Object.entries(env)) {
×
171
    if (value.length == 0) continue
×
172
    rv += `export ${key}="${value.join(":")}"\n`
×
173
  }
×
174
  return rv
×
175
}
×
176

177
export function flatten(env: Record<string, string[]>): Record<string, string> {
36✔
178
  const SEP = Deno.build.os == 'windows' ? ';' : ':'
×
179
  const rv: Record<string, string> = {}
48✔
180
  for (const [key, value] of Object.entries(env)) {
48✔
181
    rv[key] = value.join(SEP)
1,160✔
182
  }
1,160✔
183
  return rv
48✔
184
}
48✔
185

186
function compact_add<T>(set: OrderedSet<T> | undefined, item: T | null | undefined): OrderedSet<T> {
284✔
187
  if (!set) set = new OrderedSet<T>()
284✔
188
  if (item) set.add(item)
284✔
189

190
  return set
284✔
191
}
284✔
192

193
class OrderedSet<T> {
36✔
194
  private items: T[];
36✔
195
  private set: Set<T>;
36✔
196

197
  constructor() {
36✔
198
    this.items = [];
120✔
199
    this.set = new Set();
120✔
200
  }
120✔
201

202
  add(item: T): void {
36✔
203
    if (!this.set.has(item)) {
52✔
204
      this.items.push(item);
52✔
205
      this.set.add(item);
52✔
206
    }
52✔
207
  }
52✔
208

209
  toArray(): T[] {
36✔
210
    return [...this.items];
159✔
211
  }
53✔
212

213
  isEmpty(): boolean {
36✔
214
    return this.items.length == 0
141✔
215
  }
141✔
216
}
36✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc