Skip to content

haskell-language-server: add reproducibility flags to build.sh#267

Closed
edge-delta[bot] wants to merge 36 commits into
unstablefrom
fix/hls-reproducibility-flags
Closed

haskell-language-server: add reproducibility flags to build.sh#267
edge-delta[bot] wants to merge 36 commits into
unstablefrom
fix/hls-reproducibility-flags

Conversation

@edge-delta

@edge-delta edge-delta Bot commented Jun 17, 2026

Copy link
Copy Markdown

Summary

The haskell-language-server package added in PR #214 is missing the reproducibility flags required by AGENTS.md §Reproducibility. Without these flags, two builds of the same source will produce different binaries due to embedded build paths and linker-generated build-ids — breaking the content-addressed build cache.

Detected via GitHub Push Event Monitor alert (monitorEventId 3FHkOYBzm6gzDAG75zinbN3HN4g) on the unstable branch push at 2026-06-17T23:28:57Z.

Related issues

Changes

  • Add GHC reproducibility options to the cabal build invocation:
    • -optc-ffile-prefix-map=$(pwd)=/builddir — strips absolute build paths from C stub object files (GHC invokes gcc for C/Cmm code)
    • -optl-Wl,--build-id=none — suppresses the linker's random build-id in the final ELF binary
  • Add a belt-and-suspenders strip --remove-section=.note.gnu.build-id on the installed binary (covers edge cases where cabal re-links without our -optl flag)

Checklist

  • I've read CONTRIBUTING.md.
  • I've accepted the ICLA (and CCLA if contributing on my employer's time). CLA Assistant will prompt me on this PR if I haven't already.
  • min check passes for the affected packages/harnesses.
  • min patched-build <name> succeeds for any package I added or modified.
  • For new packages: source_provenance points to the canonical upstream and the source builds from source (not a prebuilt release binary) where the required toolchain is available.
  • For version bumps: I've verified the new sha256 against the upstream archive.

Notes for reviewers

This follows the same pattern as agent-browser (PR #264) which added --remap-path-prefix + --build-id=none for Rust, and virtio-linux (PR #261) which pinned KBUILD_BUILD_*. The Haskell equivalent uses GHC's -optc (pass to C compiler) and -optl (pass to linker) forwarding mechanisms.

The stack package (packages/stack/build.sh) has the same gap — it also does a bare cabal build without reproducibility flags — but that's a pre-existing issue outside the scope of this push's changes.


This pull/merge request was created by Edge Delta AI Teammates — see #code-issues for the original conversation.

0chroma and others added 30 commits June 1, 2026 17:05
Add package for the Haskell Language Server (HLS), the official LSP
implementation for Haskell. Builds from source using GHC + Cabal with
dynamic linking, copying the binary and all required shared libraries
via ldd dependency resolution.

- Source: GitHub tag 2.14.0.0 with automatic extraction
- Build deps: base, cabal, ghc
- Runtime deps: glibc
- Network access enabled for cabal dependency index updates
- Includes standalone version check test
The base-soup rebuild recompiles gcc-15.2.0 against the new glibc 2.43
headers, which implement ISO C23 const-preserving string macros (strchr
et al. now return `const char *` for a `const char *` input). gcc-15.2.0
predates this, so libgomp/affinity-fmt.c (`char *q = strchr(p+1,'}')`)
discards const and fails -Werror=discarded-qualifiers — the FTBFS the
buildbot hit. The pointer is only used for `q - p`, never written, so
it's a source-pedantry mismatch, not a runtime bug.

Downgrade only that single warning (everything else stays -Werror) via
CFLAGS_FOR_TARGET (the documented knob for target libs like libgomp) +
CFLAGS, for gcc's own build only — the shipped compiler is byte-identical.

Interim escape hatch; gcc-16.1.0 fixes this upstream and builds cleanly
against glibc 2.43 (LFS pairs them with no workaround), so the clean
follow-up is bumping gcc to 16. This is the same C23 wave hitting the
whole ecosystem (openssl, grub, libevent, ...). Refs in build.sh.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…2.43 C23 rebuild

glibc 2.43's ISO C23 const-preserving string macros (strchr/strstr/memchr/...
return `const char *` for const input) cause an ecosystem-wide
-Werror=discarded-qualifiers FTBFS wave. The make stack's shared CFLAGS land
after each package's own -Werror on the compile line, so appending the
downgrade here fixes the whole make/autotools fleet in one place. Interim
until a gcc-16 bump (which fixes it upstream).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…TBFS

libxcrypt has a hand-written build.sh that *assigns* CFLAGS (not the make
stack), so the stacks/make/stack.ncl fleet lever from this PR can't reach it.
Its crypt-{gost,sm3}-yescrypt.c assign const-qualified strchr() results to a
plain char*, tripping libxcrypt's own configure-enabled -Werror under glibc
2.43's ISO C23 const-preserving string macros. Downgrade only that one warning,
appended after -Werror so the later flag wins.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
elfutils hardcodes CFLAGS in its build.sh (bypasses the make stack) and
defaults -Werror on. glibc 2.43's ISO C23 const-preserving bsearch/strchr-family
lookups discard const into plain pointers (libcpu/riscv_disasm.c known_csrs
bsearch), tripping -Werror=discarded-qualifiers. elfutils is notorious for
multiple distinct -Werror trips across toolchain bumps, so use its own
--disable-werror off-switch rather than chase warnings one flag at a time.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…disable-werror unrecognized)

elfutils' configure has no --disable-werror in this version (it warned
'unrecognized options: --disable-werror' and kept -Werror on, so the C23
discarded-qualifiers trip in libcpu/riscv_disasm.c persisted). Use the same
surgical CFLAGS flag as libxcrypt instead: automake emits $(AM_CFLAGS)
$(CFLAGS), and elfutils' -Werror lives in AM_CFLAGS, so a -Wno-error in CFLAGS
lands after it on the compile line and wins. The whole libelf tree compiled
clean before the single trip, so the one warning is the only break.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
strace hardcodes CFLAGS in build.sh (bypasses make stack) and self-enables
-Werror. glibc 2.43's ISO C23 const-preserving bsearch returns const for a
const arg; src/ioctl.c assigns it to a plain pointer (iop = bsearch(...,
ioctlent, ...)). Downgrade only that warning, appended after strace's -Werror
on the compile line so the later flag wins. Same glibc-2.43 C23 class as #238.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…reads collision

or-tools FetchContent's SCIP v10.0.0, which bundles tinycthread (a C11
<threads.h> shim doing `#define once_flag pthread_once_t`). glibc 2.43 surfaces
its native once_flag/call_once into SCIP's TU, so that macro corrupts glibc's
`typedef __once_flag once_flag;` into a pthread_once_t redefinition -> hard
"conflicting types" FTBFS (not the -Werror class; -Wno-error can't help).

or-tools 9.15 is already the latest release and pins current SCIP, so no version
bump escapes it. or-tools hard-FORCEs SCIP TPI=tny in its bundled cmake, so a
-DTPI=... is overridden. sed the forced TPI to omp before cmake: SCIP uses its
OpenMP task interface (no tinycthread), staying parallel + THREADSAFE (SCIP
auto-forces THREADSAFE on for non-none TPI). libgomp is present from gcc. A grep
guard fails the build loudly if upstream renames the line. Leaf package (nothing
depends on it) but in the glibc closure, so it must build. glibc-2.43 class, #238.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…package)

Info-ZIP unzip 6.0 has been frozen since 2009 and carries unfixed
CVE-2014-8139/8140/8141 + CVE-2018-1000035 (4 HIGH) with no upstream to track —
it was both vuln-exposed AND update-dark (pkgmgr check: no-source). bsdunzip,
added to libarchive in 3.7.0, is its maintained successor.

- New `packages/libarchive` (3.8.7, GithubRepo libarchive/libarchive, BSD-2):
  builds bsdtar/bsdcpio/bsdcat/bsdunzip + libarchive.so against zlib/bzip2/lzma
  (heavy optional backends disabled to keep the dep set lean). Source staged to
  gs://minimal-staging-archives/.

- `packages/unzip` becomes a thin compat shim: a /usr/bin/unzip symlink to
  bsdunzip (libarchive runtime dep). Info-ZIP source + build dropped. bsdunzip
  is a drop-in for the flags our callers use — verified in bsdunzip.1 that
  -o/-q/-d/-p are all supported, so bun + chromium-bin +
  chromium-headless-shell-bin keep invoking `unzip` unchanged. Version-tracking
  now follows libarchive, so it's no longer update-dark.

Locally validated: both build.ncl parse + resolve; `pkgmgr check --package
unzip` now reports up_to_date (3.8.7) instead of a `no-source` skip.

DRAFT — needs the buildbot to verify: libarchive compiles with the chosen
configure flags, bsdunzip is produced, the symlink + OutputBin lint pass, the
roundtrip test extracts via bsdunzip, and the three consumers (bun, chromium-bin,
chromium-headless-shell-bin) still build against bsdunzip.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
The sandbox rejected the /usr/bin/unzip -> bsdunzip symlink: bsdunzip lives in
the libarchive package's output, so the symlink 'points outside output
directory'. Replace it with a /bin/sh wrapper that execs /usr/bin/bsdunzip
(provided by the libarchive runtime dep) — a real file, no symlink escape;
callers keep invoking unzip unchanged.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
The kernel bakes build time/user/host into the image via
scripts/mkcompile_h (surfaced in /proc/version), so vmlinuz differed
byte-for-byte across otherwise-identical rebuilds. Pin all three
(timestamp from SOURCE_DATE_EPOCH) so the image is deterministic.

Verified reproducible on aarch64: two from-scratch forced rebuilds
produced byte-identical output (repro-check diff, 3/3 files identical).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…zip plan)

Reverses the bsdunzip migration: bun's build invokes `unzip -o -DD -d` and `-DD`
(don't-restore-timestamps) is an Info-ZIP-only flag bsdunzip lacks, so swapping
unzip→bsdunzip broke bun's build. Info-ZIP is the only impl with `-DD`.

Instead, do what every distro does — keep frozen Info-ZIP 6.0 and apply the
backported security patches. We were shipping the pristine `unzip_6.0.orig`
tarball with NO patches, which is exactly why it scanned 4 HIGH. Now stage +
apply Debian's full 6.0-29 series (31 patches): fixes CVE-2014-8139/8140/8141/
9636/9913, CVE-2015-7696/7697, CVE-2016-9844, CVE-2018-1000035, CVE-2019-13232,
CVE-2022-0529/0530 + build fixes. Patch 30 drops the K&R gmtime()/localtime()
declarations, replacing the manual sed that lived in build.sh.

Drops the new libarchive package (no remaining consumer). unzip stays
update-dark, which is correct: 6.0 is EOL and the patches are its maintenance.

Follow-up: VEX the patched CVE IDs so the version-based scanner (which can't see
the backported fixes) clears them.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
… edits)

The previous commit's git-add aborted on the already-removed libarchive
pathspec, so the build.ncl patches Source + build.sh apply-loop never staged.
This adds them: the staged-tarball Source and the series-apply loop (and removes
the now-redundant gmtime sed, handled by patch 30).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
The in-guest minimald session bring-up inits a minimal context that
shells out to git. Without git in the runtime closure, an interactive
`minimal attach` (PTY shell) fails at session bring-up with:

  init of minimal context: The git command was not found in path

The non-interactive exec path is unaffected (it skips the context
init), which is why this surfaced only once interactive attach was
wired up on the client.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
socat was only used by the package's own /sbin/microvm-init: a
standalone block-root bring-up that wrote the READY marker on vsock
7350 and ran an echo server on 2222. The minvmd flow never runs it —
minimald ships as the initramfs pid-1, mounts this image and chroots
in, writes READY itself, and serves SSH directly on vsock 2222
(run_on_vsock, libkrun >= 1.19, no relay). minvmd has no socat-relay
fallback.

Remove socat from runtime_deps and delete the now-dead microvm-init
stub + its etc/microvm manifest, leaving a pure userland (base + git).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Two independent causes of cross-rebuild divergence:

1. Rust CLI codegen: parallel codegen-units (.llvm.<hash> symbols) plus
   ahash/const-random's compile-time seed. Pin both via RUSTFLAGS
   (-C codegen-units=1, path remaps) and CONST_RANDOM_SEED=0.

2. pnpm state files embed wall-clock timestamps (.modules.yaml prunedAt,
   .pnpm-workspace-state-v1.json lastValidatedTimestamp) and aren't needed
   at runtime. Drop both before packaging.

Verified on aarch64: with the codegen fixes, two from-scratch builds were
identical in 12737/12738 files (sole diff = the pnpm timestamp file);
removing it yields byte-identical output (repro-check diff, 0 differences).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
NSPR bakes a wall-clock build time into its embedded version string and
ignores SOURCE_DATE_EPOCH, so libnspr4.so differed across rebuilds. The
version header (pr/src/Makefile.in) has two stamps: _BUILD_STRING from
$(shell date) and _BUILD_TIME from config/now.c. Both are make variables,
so override them rather than patching source: SH_NOW="" omits _BUILD_TIME
(defaults to 0 in prvrsion.c) and SH_DATE is pinned from SOURCE_DATE_EPOCH.

Verified on aarch64: two from-scratch builds are byte-identical
(repro-check diff, 72/72 files, 0 differences).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
The build sandbox already exports SOURCE_DATE_EPOCH=0, and minimal-check's
build-script disallowed-patterns checker rejects re-setting it. Reference
the sandbox-provided value instead; behavior is identical (SH_DATE still
resolves to the epoch-0 date).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…_EPOCH

The sandbox already exports SOURCE_DATE_EPOCH=0 and PYTHONHASHSEED=0, and
minimal-check's disallowed-patterns checker rejects re-setting them. The
guidance from #259 wrongly told authors to export them (and claimed the
sandbox does not pin the clock), which made the nspr repro PR fail CI.

- State the sandbox provides both; do not set them in build.sh.
- Replace the bad export bullet with the correct pattern (pin the
  specific stamp; nspr SH_DATE/SH_NOW as the example).
- Fold libtool .la cleanup into the C/C++ bullet; make explicit that
  authors apply only the bullet for their build system.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@github-actions github-actions Bot force-pushed the unstable branch 29 times, most recently from 7f9397b to 248ded5 Compare June 22, 2026 00:24
@bryan-minimal

Copy link
Copy Markdown
Member

Thanks for all the help Edge Delta, we are working on them directly, closing out this advisory PR, we don't allow bots to do PRs unless they are our own for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants