Lean 4 on FreeBSD 14
Table of Contents
- 1. Overview
- 2. System Information
- 3. Problem Statement
- 4. Root Cause Analysis
- 5. Solution
- 6. Validation
- 7. Test Case: Building a Lean Project
- 8. Complete Setup Script
- 9. Notes for Future Reference
- 10. Related Links
- 11. Appendix: Full Diagnostic Commands
- 12. Addendum: FreeBSD 14.4, no
lean4package, Linux compat layer instead - 13. Addendum 2: the 14.4 "no package" was an empty catalog, not an absent port
1. Overview
A running log of getting Lean 4 working on FreeBSD 14, kept as dated entries
rather than rewritten in place: the body below is the original 4.23.0 /
14.3-RELEASE investigation, followed by addenda as the situation changed.
Each addendum carries its own :VERIFIED_AT: so a later entry can correct an
earlier one without either disappearing.
Formerly published at /research/lean4-4.23.0-freebsd-14.3/; that path now
redirects here.
1.1. Reading the version string
lean --version reports the triple it was built for, which is not
necessarily the host you are running on:
$ uname -r 14.4-RELEASE-p6 $ lean --version Lean (version 4.29.0, x86_64-unknown-freebsd14.3, Release)
The freebsd14.3 there is the build host baked into the target triple, not a
mismatch and not a problem — FreeBSD keeps ABI compatibility across a major
version. Worth knowing before it looks alarming in a bug report.
2. System Information
uname -srm
FreeBSD 14.3-RELEASE amd64
3. Problem Statement
The FreeBSD lean4 package (version 4.23.0) fails to run with the error:
$ lean --version error: failed to locate application
The lake build tool also fails with:
$ lake build error: could not detect the configuration of the Lake installation
4. Root Cause Analysis
Using truss to trace system calls reveals the issue:
truss lean --version 2>&1 | grep -E "(readlink|error)"
Output:
readlink("/proc/80630/file",0x82076b5e0,1024) ERR#2 'No such file or directory'
error: failed to locate application
4.1. The Problem
Lean 4 uses /proc/<pid>/file to determine its own executable path at runtime. This is a Linux-style procfs feature that FreeBSD supports, but only when procfs is mounted.
By default, FreeBSD does not mount procfs at /proc.
5. Solution
5.1. Step 1: Check current procfs status
mount | grep proc
linprocfs on /compat/linux/proc (linprocfs, local) procfs on /proc (procfs, local)
5.2. Step 2: Add procfs to /etc/fstab (if not present)
# Check if procfs line exists in fstab
if ! grep -q "^proc /proc procfs" /etc/fstab; then
echo "Adding procfs to /etc/fstab..."
echo 'proc /proc procfs rw 0 0' | sudo tee -a /etc/fstab
else
echo "procfs already in /etc/fstab"
fi
5.3. Step 3: Mount procfs (if not mounted)
# Mount procfs if not already mounted
if ! mount | grep -q "^procfs on /proc"; then
echo "Mounting procfs..."
sudo mount /proc
else
echo "procfs already mounted"
fi
5.4. Step 4: Verify procfs is working
ls -la /proc/$$ | head -5
total 0 dr-xr-xr-x 1 jwalsh jwalsh 0 Jan 1 19:30 . dr-xr-xr-x 1 root wheel 0 Jan 1 19:04 .. -r--r--r-- 1 jwalsh jwalsh 0 Jan 1 19:30 cmdline -rw------- 1 jwalsh jwalsh 0 Jan 1 19:30 dbregs
6. Validation
6.1. Lean Version
lean --version
Lean (version 4.23.0, x86_64-unknown-freebsd14.3, Release)
6.2. Lake Version
lake --version
Lake version 5.0.0-src (Lean version 4.23.0)
6.3. Package Information
pkg info lean4 | grep -E "Name|Version|Flat size"
Name : lean4 Version : 4.23.0 Flat size : 1.90GiB
7. Test Case: Building a Lean Project
7.1. Create a simple Lean file
-- Simple Lean 4 test
def hello := "Hello from Lean 4 on FreeBSD!"
#eval IO.println hello
-- Basic proof
theorem add_comm (a b : Nat) : a + b = b + a := Nat.add_comm a b
#check add_comm
7.2. Build and run
# Create test directory
mkdir -p /tmp/lean-test
cd /tmp/lean-test
# Create lakefile
cat > lakefile.lean << 'EOF'
import Lake
open Lake DSL
package «lean-test» where
version := v!"0.1.0"
@[default_target]
lean_exe «test» where
root := `test
EOF
# Create lean-toolchain
echo "leanprover/lean4:v4.23.0" > lean-toolchain
# Create test.lean
cat > test.lean << 'EOF'
def main : IO Unit := do
IO.println "Hello from Lean 4 on FreeBSD!"
IO.println s!"1 + 1 = {1 + 1}"
EOF
# Build
lake build 2>&1 | tail -3
# Run if build succeeded
if [ -f .lake/build/bin/test ]; then
.lake/build/bin/test
fi
8. Complete Setup Script
This script can be tangled and executed to set up Lean 4 on FreeBSD:
#!/bin/sh
# Lean 4 FreeBSD Setup Script
# Ensures procfs is mounted for Lean 4 to work
set -e
echo "=== Lean 4 FreeBSD Setup ==="
echo ""
# Check if running as root for fstab modification
check_root() {
if [ "$(id -u)" -ne 0 ]; then
echo "Note: Run with sudo for fstab modification"
return 1
fi
return 0
}
# Add procfs to fstab if needed
setup_fstab() {
if ! grep -q "^proc /proc procfs" /etc/fstab 2>/dev/null; then
if check_root; then
echo "Adding procfs to /etc/fstab..."
echo 'proc /proc procfs rw 0 0' >> /etc/fstab
echo "Done."
else
echo "Add this line to /etc/fstab:"
echo " proc /proc procfs rw 0 0"
fi
else
echo "procfs already in /etc/fstab"
fi
}
# Mount procfs if needed
mount_procfs() {
if ! mount | grep -q "^procfs on /proc"; then
if check_root; then
echo "Mounting procfs..."
mount /proc
echo "Done."
else
echo "Run: sudo mount /proc"
fi
else
echo "procfs already mounted"
fi
}
# Verify Lean works
verify_lean() {
echo ""
echo "=== Verification ==="
if command -v lean >/dev/null 2>&1; then
echo "Lean version:"
lean --version
echo ""
echo "Lake version:"
lake --version
else
echo "Lean not found. Install with: pkg install lean4"
fi
}
# Main
setup_fstab
mount_procfs
verify_lean
echo ""
echo "=== Setup Complete ==="
9. Notes for Future Reference
- procfs is required: Lean 4 will not work without
/procmounted - This is not a packaging bug: The FreeBSD port is correctly built; the runtime dependency on procfs is undocumented
- Persists across reboots: Adding to /etc/fstab ensures procfs is mounted at boot
- elan not available: The elan version manager does not have FreeBSD binaries; use the pkg instead
10. Related Links
- Veil DSL Workshop - Where this was discovered
- FreeBSD lean4 port
- Lean 4 Official Site
- POPL 2026 Veil Tutorial
11. Appendix: Full Diagnostic Commands
echo "=== Full System Info ==="
echo "OS: $(uname -srm)"
echo "Lean: $(lean --version 2>&1)"
echo "Lake: $(lake --version 2>&1)"
echo "procfs: $(mount | grep 'procfs on /proc' || echo 'NOT MOUNTED')"
echo "pkg: $(pkg info lean4 2>&1 | grep Version || echo 'NOT INSTALLED')"
=== Full System Info === OS: FreeBSD 14.3-RELEASE amd64 Lean: Lean (version 4.23.0, x86_64-unknown-freebsd14.3, Release) Lake: Lake version 5.0.0-src (Lean version 4.23.0) procfs: procfs on /proc (procfs, local) pkg: Version : 4.23.0
12. Addendum: FreeBSD 14.4, no lean4 package, Linux compat layer instead
On a second host (FreeBSD 14.4-RELEASE-p6, not the 14.3 box above), the
fix in this note no longer applies directly: pkg search lean returns
nothing Lean-related at all, so there is no lean4 package to install
and mount procfs for. Both dead ends are real and this note's own
"elan not available" line (Notes for Future Reference, item 4) still
holds – but there's a third option that works instead of falling back
to elan or giving up: if the host already runs FreeBSD's Linux
compatibility layer (Linuxulator) for unrelated reasons, install the
Linux build of elan and run it through that layer. A Linux-compiled
elan correctly detects itself as Linux instead of hitting elan's
missing-FreeBSD-platform panic, downloads Linux Lean binaries, and
those binaries' own need for /proc/<pid>/file is already satisfied
by linprocfs at /compat/linux/proc – the same underlying need
this whole page is about, solved by infrastructure that was already
there for other reasons.
kldstat -v | grep linux # linux.ko loaded, linuxelf registered
pkg info | grep linux_base # e.g. linux_base-rl9-9.7
mount | grep linprocfs # linprocfs on /compat/linux/proc
12 1 0xffffffff830ee000 30b08 linux.ko (/boot/kernel/linux.ko)
520 linuxelf
linux_base-rl9-9.7 Base set of packages needed in Linux mode (Rocky Linux 9.7)
linprocfs on /compat/linux/proc (linprocfs, local)
/compat/linux/bin/bash -c '
export ELAN_HOME=$HOME/.elan-linux CARGO_HOME=$HOME/.elan-linux
curl -sSf https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh -o /tmp/elan-init.sh
/compat/linux/bin/bash /tmp/elan-init.sh -y --no-modify-path --default-toolchain none
'
A trap worth naming: a bare bash /tmp/elan-init.sh inside that outer
/compat/linux/bin/bash -c '...' does not inherit the Linux
personality – it resolves via $PATH like any other command, which
still points at native FreeBSD bash unless every nested invocation
pins the full /compat/linux/bin/bash path explicitly. Getting this
wrong reproduces the exact failure this approach exists to avoid: the
platform detection silently runs under real FreeBSD again and hits
elan-x86_64-unknown-freebsd.tar.gz 404.
/compat/linux/bin/bash -c 'export PATH=$HOME/.elan-linux/bin:$PATH; lean --version; lake --version'
Lean (version 4.23.0, x86_64-unknown-linux-gnu, commit 50aaf682e9b74ab92880292a25c68baa1cc81c87, Release) Lake version 5.0.0-src+50aaf68 (Lean version 4.23.0)
Verified past --version: a full downstream project's test suite
(gmake verify in jwalsh/lean4-workshop – every example, exercise,
solution and scratch file) passed completely this way, exit 0. Wrapped
for reuse as bin/l4w-freebsd-linux in that repo, with the fuller
writeup at docs/lean-on-freebsd.org.
This only helps when the target host already has the Linux compat layer set up for something else – standing one up from nothing is its own yak-shave, at which point GitHub Codespaces (as used for the 4.26.0/FreeBSD 15.0 case) is probably still less effort.
13. Addendum 2: the 14.4 "no package" was an empty catalog, not an absent port
Same host as Addendum 1 (hydra, FreeBSD 14.4-RELEASE-p6 amd64). That
addendum concluded pkg search lean "returns nothing Lean-related at all,
so there is no lean4 package to install and mount procfs for", and
proceeded to the Linux compat layer on that basis. The conclusion was
sound given what the machine reported; the machine was reporting from a
catalog that had not been updated in seven and a half months.
git -C /usr/ports log -1 --date=short --format='%h %ad' # was: 2026-02-01
ls /var/db/pkg/repo-*.sqlite # was: no such file
The ports tree was pinned at 2026-02-01 and no binary catalogue had ever
been fetched, so pkg search had nothing to search. After
sudo git -C /usr/ports pull && sudo pkg update (tree to 2026-09-16,
37 837 + 38 238 packages catalogued), lean4 is present and installs
normally.
pkg info lean4 | head -1
lean --version
make -C /usr/ports/math/lean4 -V DISTVERSION
Lean (version 4.29.0, x86_64-unknown-freebsd14.3, Release) 4.34.0
So on 14.4 the original fix on this page applies after all. The native
port works, needing exactly what the body describes: procfs on /proc,
added to /etc/fstab and mounted. The Linux compat route in Addendum 1 is
still correct and still useful, but it is no longer the only option here,
and it was never required for the reason given.
Worth separating: the freebsd14.3 in that version string is the build
host baked into the target triple, not a mismatch with the 14.4 runtime.
13.1. Three corrections to the elan account
"elan not available" (Notes for Future Reference, item 4) is narrower than
stated. Upstream publishes no FreeBSD artifact – that is what produces the
elan-x86_64-unknown-freebsd.tar.gz 404 and the manifestation.rs panic,
since elan's platform detection has no FreeBSD branch. But a locally built
native elan runs fine and executes already-installed toolchains without
complaint; it panics only when it must download one. The precise statement
is: upstream ships no FreeBSD artifact; a locally built elan runs installed
toolchains but panics on any download.
The wrapper leaks toolchains into the wrong home. The install snippet above
sets ELAN_HOME=$HOME/.elan-linux, but the run path in
bin/l4w-freebsd-linux exports only PATH. With no ELAN_HOME at run time
the Linux elan falls back to the default ~/.elan and downloads there;
combined with --default-toolchain none at install, ~/.elan-linux/toolchains
is never created at all, so the tree looks abandoned while being the thing
that did the work. Timestamps settle it – ~/.elan-linux/ and
~/.elan/toolchains/...v4.23.0/ share a creation minute, and the native elan
cannot download.
This turns out to be a useful bug on this host: because the toolchains land
in ~/.elan, the native elan shim on PATH finds and execs them, which is
why lean works at all here.
The panic is old. ~/.elan/toolchains/ holds a stale
leanprover--lean4---v4.26.lock dated 2026-02-06 containing a dead pid, with
no toolchain directory ever created – the same failure, seven months before
either addendum.
13.2. Mathlib does not have to be built
The 4.27.0/FreeBSD 15.0 note puts Mathlib4 at "~30-60 min to build". Via the
Linux compat route it need not be built at all: lake exe cache get fetches
prebuilt Linux oleans, and they load correctly under the Linuxulator.
/compat/linux/bin/bash -c 'export PATH=$HOME/.elan-linux/bin:$PATH
elan toolchain install leanprover/lean4:v4.35.0-rc2'
cd ~/lean/mathlib-probe && lake init probe math && lake exe cache get
8 555 oleans decompressed, import Mathlib elaborates, and Monotone,
Finset (whose ≤ is definitionally ⊆, confirmed by rfl) and
Nat.Partition all resolve. Minutes, not an hour, and no compilation.
Two practical notes. The toolchain fetch 504'd once from the release CDN and
succeeded on retry – transient, not a platform problem. And this is the case
that genuinely needs elan rather than the port: mathlib pins its own
toolchain (v4.35.0-rc2 here), and the port gives exactly one version with
no way to match a pin.