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

pkgxdev / libpkgx / 29955076666

22 Jul 2026 08:25PM UTC coverage: 82.164% (-0.1%) from 82.301%
29955076666

Pull #89

github

web-flow
Merge dc53b90a8 into bf926ece3
Pull Request #89: chore: publish libpkgx to JSR as @pkgx/libpkgx

586 of 781 branches covered (75.03%)

Branch coverage included in aggregate %.

92 of 94 new or added lines in 20 files covered. (97.87%)

3 existing lines in 2 files now uncovered.

2565 of 3054 relevant lines covered (83.99%)

3115.86 hits per line

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

79.36
/src/hooks/useShellEnv.ts
1
import { Installation } from "../types.ts"
2
import usePantry from "./usePantry.ts"
36✔
3
import host from "../utils/host.ts"
36✔
4

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

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

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

41
export default function() {
36✔
42
  return {
50✔
43
    map,
50✔
44
    expand,
50✔
45
    flatten
50✔
46
  }
50✔
47
}
50✔
48

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

54
  const projects = new Set(installations.map(x => x.pkg.project))
50✔
55
  const has_cmake = projects.has('cmake.org')
50✔
56
  const archaic = true
50✔
57

58
  const rv: Record<string, string[]> = {}
50✔
59
  const seen = new Set<string>()
50✔
60

61
  for (const installation of installations) {
50✔
62

63
    if (!seen.insert(installation.pkg.project).inserted) {
×
64
      console.warn("pkgx: env is being duped:", installation.pkg.project)
×
65
    }
×
66

67
    for (const key of EnvKeys) {
78✔
68
      for (const suffix of suffixes(key)!) {
442✔
69
        vars[key] = compact_add(vars[key], installation.path.join(suffix).chuzzle()?.string)
638✔
70
      }
638✔
71
    }
442✔
72

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

85
    if (has_cmake) {
×
86
      vars.CMAKE_PREFIX_PATH = compact_add(vars.CMAKE_PREFIX_PATH, installation.path.string)
×
87
    }
×
88

89
    if (projects.has('gnu.org/autoconf')) {
×
90
      vars.ACLOCAL_PATH = compact_add(vars.ACLOCAL_PATH, installation.path.join("share/aclocal").chuzzle()?.string)
×
91
    }
×
92

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

103
    // pantry configured runtime environment
78✔
104
    const runtime = await usePantry().project(installation.pkg).runtime.env(installation.pkg.version, installations)
78✔
105
    for (const key in runtime) {
78✔
106
      rv[key] ??= []
88✔
107
      rv[key].push(runtime[key])
88✔
108
    }
88✔
109
  }
78✔
110

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

123
  for (const key of EnvKeys) {
50✔
124
    //FIXME where is this `undefined` __happening__?
232✔
125
    if (vars[key] === undefined || vars[key]!.isEmpty()) continue
232✔
126
    rv[key] = vars[key]!.toArray()
249✔
127
  }
249✔
128

129
  // don’t break `man` lol
50✔
130
  rv["MANPATH"]?.push("/usr/share/man")
×
131
  // https://github.com/pkgxdev/libpkgx/issues/70
50✔
132
  rv['XDG_DATA_DIRS']?.push('/usr/local/share:/usr/share')
×
133

134
  return rv
50✔
135
}
50✔
136

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

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

172
export function flatten(env: Record<string, string[]>) {
36✔
173
  const SEP = Deno.build.os == 'windows' ? ';' : ':'
×
174
  const rv: Record<string, string> = {}
48✔
175
  for (const [key, value] of Object.entries(env)) {
48✔
176
    rv[key] = value.join(SEP)
1,150✔
177
  }
1,150✔
178
  return rv
48✔
179
}
48✔
180

181
function compact_add<T>(set: OrderedSet<T> | undefined, item: T | null | undefined): OrderedSet<T> {
284✔
182
  if (!set) set = new OrderedSet<T>()
284✔
183
  if (item) set.add(item)
284✔
184

185
  return set
284✔
186
}
284✔
187

188
class OrderedSet<T> {
36✔
189
  private items: T[];
36✔
190
  private set: Set<T>;
36✔
191

192
  constructor() {
36✔
193
    this.items = [];
120✔
194
    this.set = new Set();
120✔
195
  }
120✔
196

197
  add(item: T): void {
36✔
198
    if (!this.set.has(item)) {
52✔
199
      this.items.push(item);
52✔
200
      this.set.add(item);
52✔
201
    }
52✔
202
  }
52✔
203

204
  toArray(): T[] {
36✔
205
    return [...this.items];
159✔
206
  }
53✔
207

208
  isEmpty(): boolean {
36✔
209
    return this.items.length == 0
141✔
210
  }
141✔
211
}
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