Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions fixtures/projects/curl.se/package.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
provides:
- bin/curl
4 changes: 4 additions & 0 deletions fixtures/projects/gnu.org/glibc/package.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
provides:
- bin/getconf
platforms:
- linux
36 changes: 35 additions & 1 deletion src/hooks/useShellEnv.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { assertEquals } from "@std/assert"
import { assertEquals, assert } from "@std/assert"
import { useTestConfig } from "./useTestConfig.ts"
import useShellEnv from "./useShellEnv.ts"
import SemVer from "../utils/semver.ts"
Expand All @@ -24,3 +24,37 @@ Deno.test("useShellEnv", async () => {

assertEquals(env.PATH, `${installations[0].path.join("bin")}${SEP}${installations[1].path.join("bin")}`)
})

Deno.test("useShellEnv: gnu.org/glibc does not export lib/ to LIBRARY_PATH", async () => {
// Regression test for libc-style bottles polluting consumers'
// LD_LIBRARY_PATH and breaking the host's coreutils.
const { map } = useShellEnv()
const { prefix } = useTestConfig()

const glibc_install = {
pkg: { project: 'gnu.org/glibc', version: new SemVer('2.43.0') },
path: prefix.join("gnu.org/glibc/v2.43.0"),
}
const curl_install = {
pkg: { project: 'curl.se', version: new SemVer('8.13.0') },
path: prefix.join("curl.se/v8.13.0"),
}

// Both bottles have a populated lib/ directory.
glibc_install.path.join("lib").mkdir('p')
curl_install.path.join("lib").mkdir('p')

const vars = await map({ installations: [glibc_install, curl_install] })

// curl's lib/ is still exposed (existing behaviour).
const lib_paths = vars.LIBRARY_PATH ?? []
assert(
lib_paths.some(p => p.endsWith("curl.se/v8.13.0/lib")),
`expected curl's lib/ in LIBRARY_PATH, got: ${lib_paths.join(", ")}`,
)
// glibc's lib/ is NOT — that's the new opt-out behaviour.
assert(
!lib_paths.some(p => p.endsWith("gnu.org/glibc/v2.43.0/lib")),
`glibc's lib/ should not be in LIBRARY_PATH, got: ${lib_paths.join(", ")}`,
)
})
24 changes: 23 additions & 1 deletion src/hooks/useShellEnv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,21 @@ export const EnvKeys = [
] as const
export type EnvKey = typeof EnvKeys[number]

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

interface Options {
installations: Installation[]
}
Expand Down Expand Up @@ -55,7 +70,14 @@ async function map({installations}: Options): Promise<Record<string, string[]>>
}
}

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