tinymux/docs/building.md
Stephen Dennis b05539f86a fix(#2212): PCRE2 comes from vcpkg, like grpc
The Windows build took PCRE2 from a hand-built out-of-tree directory
reached through $(Pcre2Dir).  engine.vcxproj honoured that property for
its headers but hardcoded ..\..\src\pcre2\{Release,Debug} for its
libraries, so -p:Pcre2Dir= applied to one half and not the other and the
link failed with LNK1104 in any tree where PCRE2 was not at the default.

Declare pcre2 in mux/vcpkg.json instead, alongside grpc and
nlohmann-json, and point netmux, libmux and engine at $(VcpkgDir).  All
three now read include and library paths from the same place.  Note
vcpkg's layout is lib/ for release and debug/lib/ for debug, not
Release/ and Debug/.

PCRE2's JIT is required -- funceval2.cpp calls pcre2_jit_compile -- and
comes automatically: the port's platform-default-features pulls in jit
everywhere except emscripten and iOS.

Ship mux/vcpkg.json in both source TOCs.  It was in neither, so the
source distribution carried no statement of its own dependencies; with
PCRE2 moving to vcpkg, the file that declares it has to travel with the
source or an unpacking reader is worse off than before.

Unix is unaffected -- configure finds system PCRE2 via pkg-config.

Verified on hatsuhara: Release and Debug both build clean with no
hand-built PCRE2 present anywhere on the box.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 20:39:31 -06:00

8.5 KiB

Building TinyMUX — platform notes

CLAUDE.md carries the short version: configure, make install from the repo root, make test. This file is for when that does not just work, and for the platform-specific traps that cost time.

The short version

cd mux && ./configure --enable-jit --enable-realitylvls --enable-wodrealms
cd .. && make install
make test

As of the PCRE2/OpenSSL discovery fix, that line needs no CPPFLAGS or LDFLAGS on any supported platform for the default build. If you are carrying such flags in muscle memory or a shell alias, you can drop them; they still work and are still needed for --enable-nls on macOS (below).

Prerequisites

Debian / Ubuntu

sudo apt install build-essential autoconf automake libtool pkg-config \
                 libpcre2-dev libssl-dev

Optional, only if you regenerate things:

Package Needed for
ragel regenerating *_scan.cpp, color_ops.c, muxescape.cpp from .rl
bison the omega converter (see reference_omega_build)

gettext for --enable-nls comes with libc6-dev — glibc provides it, so there is nothing extra to install and nothing extra to pass.

macOS (Homebrew)

brew install pcre2 openssl@3 pkg-config

Optional: gettext (only for --enable-nls), ragel and bison (only to regenerate), autoconf automake libtool (only to regenerate configure).

Homebrew's prefix is /opt/homebrew on Apple Silicon and /usr/local on Intel. Use $(brew --prefix) in scripts rather than hardcoding either.

The thing worth understanding about macOS: clang does not search Homebrew's prefix by default. /opt/homebrew/include is not on the default include path, so a library being "installed" is not the same as the compiler being able to find it. PCRE2 and OpenSSL are located through pkg-config, which knows the real paths, which is why the default build needs no flags. Anything without pkg-config metadata has to be pointed at by hand.

Windows

MSVC via the .vcxproj files; there is no autotools path. See CLAUDE.md for the GANL test harness invocation. Note issue #1499 (/utf-8).

Third-party dependencies come from vcpkg (#2212). mux/vcpkg.json declares them — currently grpc, nlohmann-json and pcre2 — pinned to a builtin-baseline commit:

git clone https://github.com/microsoft/vcpkg C:\vcpkg
C:\vcpkg\bootstrap-vcpkg.bat
cd mux && C:\vcpkg\vcpkg.exe install --triplet x64-windows

That materialises mux/vcpkg_installed/x64-windows/ (gitignored), which every project reads via $(VcpkgDir). Release libraries land in lib/ and debug in debug/lib/ — not Release/ and Debug/, which is what the old hand-built PCRE2 layout used.

Two things that will waste your afternoon otherwise:

  • Do not git clone --depth 1. The pinned baseline commit is not in a shallow clone and resolution fails with failed to git show versions/baseline.json. Recover with git fetch --depth 1 origin <baseline-sha>.
  • First run is slow — grpc dominates, roughly an hour and ~11 GB. It is a one-time cost: vcpkg's binary cache (%LOCALAPPDATA%\vcpkg\archives) makes any later tree a few seconds.

PCRE2's JIT is required (funceval2.cpp calls pcre2_jit_compile) and arrives automatically — the port's platform-default-features pulls in jit on every platform except emscripten and iOS, so a plain "pcre2" dependency is enough.

When you still need flags

--enable-nls on macOS. GNU gettext ships no .pc file, so unlike PCRE2 and OpenSSL it cannot be discovered:

cd mux && ./configure --enable-jit --enable-realitylvls --enable-wodrealms \
    --enable-nls \
    CPPFLAGS="-I$(brew --prefix)/include" LDFLAGS="-L$(brew --prefix)/lib"

libintl.h is symlinked into the main prefix (gettext is not keg-only), so the plain prefix is enough — no gettext-specific opt/gettext paths are required.

On Linux, --enable-nls needs no flags at all.

Anything installed outside pkg-config's view. Point PKG_CONFIG_PATH at the directory holding the .pc file rather than reaching for CPPFLAGS; that keeps the include and library paths consistent with each other.

Regenerating build system files — read before running autotools

The committed configure and Makefile.in files are checked in, and the tool versions that produced them matter. Using a different version rewrites the whole file and produces a diff that the next contributor, on the version you did not have, will churn straight back.

File Generated by Regenerate with
mux/configure autoconf 2.73 cd mux && autoconf
mux/*/Makefile.in automake 1.16.5 avoid — see below

Prefer not to touch Makefile.am. If a change can be expressed in configure.ac instead, do it there: configure.ac needs only autoconf, and 2.73 is what the tree already uses. Adding an include path is the common case, and folding it into CPPFLAGS in configure.ac reaches every subdirectory without regenerating a single Makefile.in.

autoconf will warn that aclocal.m4 was generated for 2.71. That warning is expected and the output is correct; do not "fix" it by running autoreconf --force --install, which rewrites the aux scripts for no benefit.

Related history: #1477, where a configure regenerated on one autoconf version blocked every agent on the other.

Generated source files

See generated-files.md for the full map. Two behaviours that look like bugs and are not:

  • Ragel output churns #line numbers. Regenerating color_ops.c with a different Ragel than the one that produced the committed copy yields a diff that is entirely #line directives — no code change. Revert it rather than committing it. A pre-commit hook blocks generated output committed without its source.

  • make clean removes committed generated files. mux/lib/color_ops.c is checked in and deleted by make clean, so the tree reads as dirty-by- deletion until the next build regenerates it. git checkout -- restores it.

Failure modes and what they actually mean

Symptom Cause
pcre2.h header not found PCRE2 dev files missing, or installed where pkg-config cannot see them. Install libpcre2-dev / brew install pcre2, or set PKG_CONFIG_PATH.
openssl/ssl.h file not found during make OpenSSL include path not reaching a subdirectory. Should not happen since the discovery fix; if it does, the subdirectory's AM_CPPFLAGS is the place to look.
#error Need EVP_MD_CTX_new() or EVP_MD_CTX_create() configure's OpenSSL function probes answered "no". They probe by linking, so this means OpenSSL was not in scope at configure time — re-run configure and check checking for EVP_MD_CTX_new... yes.
--enable-nls requires libintl.h macOS without the Homebrew prefix passed in. See above.
ASan reports "Interceptors are not working" A partially-built tree: some objects instrumented, some not. make clean and rebuild the whole thing. Do not debug the report itself.
Coherent wrong behaviour after a header edit; same commit green on another box Objects present without sibling .d files — make has no header edges for them and under-rebuilds. #2118. Engine and libmux drop such orphans automatically; if the smell remains, make clean.

That first partial-tree row deserves emphasis, because it wastes an afternoon every time. A partially-built tree lies. After changing configure flags — sanitizers, --enable-nls, JIT — run make clean before make install. A stale muxscript against a fresh engine.so produces failures that describe nothing real. The same class of lie is a header edit that rebuilds only some of its dependents when .d files are missing (#2118, closed the false #2107 chase): the binary is internally inconsistent but fails in a way that looks like a real defect. Prefer treating same-commit green on another host as a build-hygiene signal before inventing a platform bug.

Verifying a build

make test          # builds, installs, runs the smoke suite

A healthy run ends with a count and a dispatch tally, both of which matter:

=== Smoke: ALL 1505 TESTS PASSED ===
  Dispatched: 316 / 316 generated

The dispatch line is the completeness gate (#1391). A suite that passes everything it ran while quietly running less than it generated is not green. Trust the harness's own counters over a grep -c of the log; verdict phrasing varies and hand-rolled greps undercount.