Validate local Guile fix on FreeBSD

This commit is contained in:
2026-04-01 08:52:10 +02:00
parent 27916cbb78
commit 02f7a7f57b
4 changed files with 283 additions and 10 deletions

View File

@@ -66,13 +66,63 @@ Current assessment:
- the next practical step is to validate a workaround or patch in Guile so subprocess helpers stop crashing - the next practical step is to validate a workaround or patch in Guile so subprocess helpers stop crashing
- after that, continue with Phase 1.2 (minimal native build environment / GNU Hello) - after that, continue with Phase 1.2 (minimal native build environment / GNU Hello)
## 2026-04-01 — Phase 1.1 follow-up: local Guile build validated the fix
Completed work:
- installed the additional build tooling needed for a local Guile checkout build:
- `autoconf`
- `automake`
- `libtool`
- `gettext-tools`
- `texinfo`
- `help2man`
- `gperf`
- `pkgconf`
- confirmed a FreeBSD-specific bootstrap quirk:
- Guile `autogen.sh` needs GNU `m4`
- FreeBSD base `/usr/bin/m4` is not sufficient
- `M4=gm4 ./autogen.sh` works
- built a disposable validation copy from `~/repos/guile`
- confirmed `~/repos/guile` already contains upstream commit:
- `eb828801f621d3e130b6fe88cfc4acaa69b98a03`
- `Don't use posix_spawn_file_actions_addclosefrom_np with glib posix_spawn`
- updated the local test harnesses so they can test non-system Guile builds:
- `tests/guile/run-phase1-verification.sh`
- `tests/guile/run-subprocess-diagnostics.sh`
- both now accept `GUILE_BIN`
- both now prepend the sibling `../lib` directory to `LD_LIBRARY_PATH` when a matching local `libguile-3.0.so.1` exists
- subprocess diagnostics now supports `EXPECT_GUILE_SUBPROCESS_CRASH=0` for fixed builds
- validated that the packaged Guile still reproduces the crash
- validated that the locally built Guile succeeds for:
- `system*`
- `spawn`
- `open-pipe*`
- re-ran the broader Phase 1.1 Scheme verification suite successfully against the local Guile build
- wrote the results to `docs/reports/phase1-guile-local-build-validation.md`
Important findings:
- the local Guile executable initially still crashed until it was forced to load its matching local `libguile-3.0.so.1`
- once `LD_LIBRARY_PATH` pointed at the local install lib directory, subprocess helpers worked correctly
- this strongly supports the earlier diagnosis and shows that the upstream Guile fix resolves the problem in practice
- the local `~/repos/bdwgc` checkout was not needed for this step; packaged `boehm-gc-threaded` was sufficient so far
Current assessment:
- Phase 1.1 now has both a root-cause analysis and a working validated fix path on amd64
- no source changes were needed in `~/repos/guile` because the local checkout already contains the relevant upstream fix
- no source changes were needed in `~/repos/bdwgc` yet, but the earlier FreeBSD warning keeps it on the watch list
- the project can now move on to Phase 1.2 with a known-good local Guile fallback
Recent commits: Recent commits:
- `e380e88``Add FreeBSD Guile verification harness` - `e380e88``Add FreeBSD Guile verification harness`
- `cd721b1``Update progress after Guile verification` - `cd721b1``Update progress after Guile verification`
- `27916cb``Diagnose Guile subprocess crash on FreeBSD`
Next recommended step: Next recommended step:
1. patch or locally validate a fix for the `addclosefrom_np` / `REPLACE_POSIX_SPAWN` mismatch 1. begin Phase 1.2 by creating a minimal native FreeBSD build environment exercise (for example, GNU Hello or an even smaller autotools package)
2. re-run both Guile test harnesses after the fix 2. use the local fixed Guile build when subprocess helpers are required
3. once subprocess behavior is stable, continue with Phase 1.2 (minimal native build environment / GNU Hello) 3. keep `bdwgc` in reserve if later FreeBSD-specific GC/thread issues appear

View File

@@ -0,0 +1,171 @@
# Phase 1.1 follow-up: local Guile build validates the FreeBSD subprocess fix
Date: 2026-04-01
## Summary
A local build of `~/repos/guile` was used to validate the previously identified FreeBSD subprocess crash.
Result:
- the packaged FreeBSD Guile (`/usr/local/bin/guile3`) still reproduces the crash
- a locally built Guile from `~/repos/guile` works correctly for:
- `system*`
- `spawn`
- `open-pipe*`
- no local source patch to `~/repos/guile` was required because the local checkout already contains the upstream fix commit
The relevant upstream commit already present in `~/repos/guile` is:
- `eb828801f621d3e130b6fe88cfc4acaa69b98a03`
- subject: `Don't use posix_spawn_file_actions_addclosefrom_np with glib posix_spawn`
The tested local checkout revision was:
- `bbf2baa10f6cc8dfdd9e4ea14b503d748287a03d`
## Additional build tooling installed on FreeBSD
To build Guile from the local checkout, these packages were installed:
```sh
sudo pkg install -y autoconf automake libtool gettext-tools texinfo help2man gperf pkgconf
```
An extra FreeBSD-specific detail also mattered:
- Guile's `autogen.sh` expects GNU `m4`
- FreeBSD base `/usr/bin/m4` is not GNU `m4`
- the installed GNU version is available as `gm4`
- `autogen.sh` therefore had to be run with `M4=gm4`
## Build procedure used
A disposable build copy was created from the local repo to avoid polluting `~/repos/guile`:
```sh
git clone --shared ~/repos/guile /tmp/guile-freebsd-validate-src
```
Autotools bootstrap:
```sh
cd /tmp/guile-freebsd-validate-src
M4=gm4 ./autogen.sh
```
Configure:
```sh
cd /tmp/guile-freebsd-validate-build
env \
M4=gm4 \
MAKE=gmake \
PKG_CONFIG=pkg-config \
PKG_CONFIG_PATH=/usr/local/libdata/pkgconfig:/usr/local/lib/pkgconfig \
CPPFLAGS='-I/usr/local/include' \
LDFLAGS='-L/usr/local/lib -Wl,-rpath,/usr/local/lib' \
/tmp/guile-freebsd-validate-src/configure \
--prefix=/tmp/guile-freebsd-validate-install \
--with-bdw-gc=bdw-gc-threaded \
--with-libgmp-prefix=/usr/local \
--with-libunistring-prefix=/usr/local \
--with-libiconv-prefix=/usr/local \
--with-libintl-prefix=/usr/local
```
Build and install:
```sh
cd /tmp/guile-freebsd-validate-build
gmake -j4
gmake install
```
## Important validation detail: the executable must load the matching local libguile
Immediately after installation, running `/tmp/guile-freebsd-validate-install/bin/guile` without adjusting the dynamic linker path still loaded the system `libguile-3.0.so.1` from `/usr/local/lib`, so it still crashed.
This was confirmed with:
```sh
ldd /tmp/guile-freebsd-validate-install/bin/guile
```
To test the local build correctly, the process must use:
```sh
LD_LIBRARY_PATH=/tmp/guile-freebsd-validate-install/lib
```
The project test harnesses were updated accordingly so that when `GUILE_BIN` points to a non-system installation, the sibling `../lib` directory is automatically prepended to `LD_LIBRARY_PATH`.
## Validation results
### 1. Packaged Guile still reproduces the problem
```sh
./tests/guile/run-subprocess-diagnostics.sh
```
Observed result:
- `system*` exits `139`
- `spawn` exits `139`
- `open-pipe*` exits `139`
### 2. Local Guile succeeds when using the matching local libguile
```sh
EXPECT_GUILE_SUBPROCESS_CRASH=0 \
GUILE_BIN=/tmp/guile-freebsd-validate-install/bin/guile \
./tests/guile/run-subprocess-diagnostics.sh
```
Observed result:
```text
system-star exit=0
spawn exit=0
open-pipe-star exit=0
Guile subprocess helpers succeeded as expected
```
### 3. The broader Phase 1.1 verification suite also passes with the local build
```sh
GUILE_BIN=/tmp/guile-freebsd-validate-install/bin/guile \
./tests/guile/run-phase1-verification.sh
```
Observed result:
- module loading: pass
- deterministic output: pass
- file I/O: pass
- process fork/wait: pass
- loopback sockets: pass
- FFI: pass
- `(guix build make-bootstrap)` import and `copy-linux-headers`: pass
## boehm-gc note
The user noted a package warning that Boehm GC is unmaintained on FreeBSD. For this specific validation step:
- the local `~/repos/bdwgc` checkout was not needed
- the packaged `boehm-gc-threaded` installation was sufficient to build and run the fixed local Guile
- no local bdwgc source patch was necessary yet
That said, the warning is worth keeping in mind as a likely future brittleness point if later Guile or Guix behavior exposes GC- or thread-related FreeBSD issues.
## Conclusion
The local build validates the earlier root-cause analysis in practice:
- the packaged Guile is broken on FreeBSD for subprocess helpers
- a newer local Guile checkout that already contains upstream commit `eb828801f...` no longer crashes
- the next Guix-on-FreeBSD work can proceed using either:
1. a locally built fixed Guile, or
2. a backported packaging patch carrying the same upstream fix
This clears the way to proceed into Phase 1.2 while keeping a documented workaround available on the current host.

View File

@@ -10,15 +10,32 @@ if [ ! -d "$guix_source_dir/guix" ]; then
exit 1 exit 1
fi fi
if command -v guile3 >/dev/null 2>&1; then if [ -n "${GUILE_BIN:-}" ]; then
guile_bin=$GUILE_BIN
elif command -v guile3 >/dev/null 2>&1; then
guile_bin=$(command -v guile3) guile_bin=$(command -v guile3)
elif command -v guile-3.0 >/dev/null 2>&1; then elif command -v guile-3.0 >/dev/null 2>&1; then
guile_bin=$(command -v guile-3.0) guile_bin=$(command -v guile-3.0)
else else
echo "Unable to find guile3 or guile-3.0 in PATH" >&2 echo "Unable to find GUILE_BIN, guile3, or guile-3.0 in PATH" >&2
exit 1 exit 1
fi fi
if [ ! -x "$guile_bin" ]; then
echo "Guile binary is not executable: $guile_bin" >&2
exit 1
fi
guile_prefix=$(CDPATH= cd -- "$(dirname "$guile_bin")/.." && pwd)
guile_lib_dir=$guile_prefix/lib
if [ -e "$guile_lib_dir/libguile-3.0.so.1" ]; then
if [ -n "${LD_LIBRARY_PATH:-}" ]; then
export LD_LIBRARY_PATH="$guile_lib_dir:$LD_LIBRARY_PATH"
else
export LD_LIBRARY_PATH="$guile_lib_dir"
fi
fi
export GUIX_SOURCE_DIR="$guix_source_dir" export GUIX_SOURCE_DIR="$guix_source_dir"
export GUILE_AUTO_COMPILE=${GUILE_AUTO_COMPILE:-0} export GUILE_AUTO_COMPILE=${GUILE_AUTO_COMPILE:-0}
if [ -n "${GUILE_LOAD_PATH:-}" ]; then if [ -n "${GUILE_LOAD_PATH:-}" ]; then
@@ -27,4 +44,6 @@ else
export GUILE_LOAD_PATH="$repo_root/tests/guile/modules:$guix_source_dir" export GUILE_LOAD_PATH="$repo_root/tests/guile/modules:$guix_source_dir"
fi fi
echo "Using Guile: $guile_bin" >&2
echo "Using LD_LIBRARY_PATH: ${LD_LIBRARY_PATH:-<unset>}" >&2
exec "$guile_bin" -s "$repo_root/tests/guile/verify-phase1.scm" "$@" exec "$guile_bin" -s "$repo_root/tests/guile/verify-phase1.scm" "$@"

View File

@@ -6,23 +6,44 @@ repo_root=$(CDPATH= cd -- "$script_dir/../.." && pwd)
workdir=$(mktemp -d /tmp/fruix-guile-subprocess.XXXXXX) workdir=$(mktemp -d /tmp/fruix-guile-subprocess.XXXXXX)
trap 'rm -rf "$workdir"' EXIT INT TERM trap 'rm -rf "$workdir"' EXIT INT TERM
if command -v guile3 >/dev/null 2>&1; then if [ -n "${GUILE_BIN:-}" ]; then
guile_bin=$GUILE_BIN
elif command -v guile3 >/dev/null 2>&1; then
guile_bin=$(command -v guile3) guile_bin=$(command -v guile3)
elif command -v guile-3.0 >/dev/null 2>&1; then elif command -v guile-3.0 >/dev/null 2>&1; then
guile_bin=$(command -v guile-3.0) guile_bin=$(command -v guile-3.0)
else else
echo "Unable to find guile3 or guile-3.0 in PATH" >&2 echo "Unable to find GUILE_BIN, guile3, or guile-3.0 in PATH" >&2
exit 1 exit 1
fi fi
if [ ! -x "$guile_bin" ]; then
echo "Guile binary is not executable: $guile_bin" >&2
exit 1
fi
guile_prefix=$(CDPATH= cd -- "$(dirname "$guile_bin")/.." && pwd)
guile_lib_dir=$guile_prefix/lib
if [ -e "$guile_lib_dir/libguile-3.0.so.1" ]; then
if [ -n "${LD_LIBRARY_PATH:-}" ]; then
export LD_LIBRARY_PATH="$guile_lib_dir:$LD_LIBRARY_PATH"
else
export LD_LIBRARY_PATH="$guile_lib_dir"
fi
fi
ulimit -c 0 || true ulimit -c 0 || true
cc -Wall -Wextra -O2 "$repo_root/tests/guile/posix-spawn-freebsd-diagnostics.c" \ cc -Wall -Wextra -O2 "$repo_root/tests/guile/posix-spawn-freebsd-diagnostics.c" \
-o "$workdir/posix-spawn-freebsd-diagnostics" -o "$workdir/posix-spawn-freebsd-diagnostics"
printf 'Using Guile: %s\n' "$guile_bin"
printf 'Using LD_LIBRARY_PATH: %s\n' "${LD_LIBRARY_PATH:-<unset>}"
printf '== Native posix_spawn diagnostics ==\n' printf '== Native posix_spawn diagnostics ==\n'
"$workdir/posix-spawn-freebsd-diagnostics" "$workdir/posix-spawn-freebsd-diagnostics"
expect_crash=${EXPECT_GUILE_SUBPROCESS_CRASH:-1}
run_guile_case() { run_guile_case() {
name=$1 name=$1
code=$2 code=$2
@@ -31,12 +52,24 @@ run_guile_case() {
rc=$? rc=$?
set -e set -e
printf '%s exit=%s\n' "$name" "$rc" printf '%s exit=%s\n' "$name" "$rc"
if [ "$expect_crash" -eq 1 ]; then
[ "$rc" -eq 139 ] [ "$rc" -eq 139 ]
else
[ "$rc" -eq 0 ]
fi
} }
printf '== Guile subprocess crash repro ==\n' if [ "$expect_crash" -eq 1 ]; then
printf '== Guile subprocess crash repro ==\n'
else
printf '== Guile subprocess validation (expect success) ==\n'
fi
run_guile_case system-star '(system* "/usr/bin/true")' run_guile_case system-star '(system* "/usr/bin/true")'
run_guile_case spawn '(spawn "/usr/bin/true" (list "/usr/bin/true"))' run_guile_case spawn '(spawn "/usr/bin/true" (list "/usr/bin/true"))'
run_guile_case open-pipe-star '(use-modules (ice-9 popen)) (open-pipe* OPEN_READ "/usr/bin/true")' run_guile_case open-pipe-star '(use-modules (ice-9 popen)) (open-pipe* OPEN_READ "/usr/bin/true")'
printf 'known FreeBSD Guile subprocess crash profile reproduced\n' if [ "$expect_crash" -eq 1 ]; then
printf 'known FreeBSD Guile subprocess crash profile reproduced\n'
else
printf 'Guile subprocess helpers succeeded as expected\n'
fi