Commitaa2a9e95Recorded24 Aug 2026Repositorycore-channel

Add tmux, libevent and the channel's first build tools

Message

tmux is the channel's first package with a dependency SET (ncurses and libevent) rather than a single edge, and the first to need a build-time tool that is not linked into its output.

libevent goes in its own module rather than beside ncurses: the channel names single-package modules for a DOMAIN, and libevent's domain is event loops, not terminals. It needs no search-paths: declaration -- measured on the built store item, include/ holds five headers at the top level plus event2/, so the builder's own -I<dep>/include reaches <event.h> and <event2/event.h> alike. ncurses is the contrasting case: 0 headers at the top level, everything under include/ncursesw/.

byacc exists because tmux's configure refuses to proceed without a program called yacc, even though the release tarball ships cmd-parse.c pre-generated. Measured against a PATH stripped of yacc/bison/byacc; the seed carries none of the three.

file(1) exists because ncurses' configure uses it to decide whether the linker honours -dynamic, and the seed has no file(1). With the probe absent, its ABSENCE is read as "yes": ncurses then links its own tic as -static -lncursesw -lncursesw -dynamic, which yields an ELF with an INTERP segment naming a loader the build sandbox does not have, and make install dies with tic: not found. Declaring the tool is what makes the probe answer honestly. A patch to ncurses' configure was written and tested and deliberately not used -- it would encode this channel's deficiency into a third party's source. The seed gap itself stays open as t-46a82e.

ncurses also gains --enable-pc-files. Without a .pc file, tmux takes a fallback that tests a HEADER and appends a LIBRARY -- -lncurses, which a widec-only ncurses does not install. autoconf threads LIBS through every later link test, so from that point every one of them fails for an unrelated reason and records "no". forkpty was the first casualty and the build died on a prototype conflict with musl's pty.h. Configure still exited 0. vim and emacs were checked and are NOT affected: neither reaches a probe of that shape.

Changed
 channel.sgl                       |   6 ++++++
 src/core/packages/build-tools.sgl | 122 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/core/packages/event.sgl       |  64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/core/packages/terminal.sgl    | 129 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 4 files changed, 317 insertions(+), 4 deletions(-)
Diff
channel.sglmodified
@@ -2,6 +2,8 @@
2
(core packages demo)
3
(core packages compression)
4
(core packages libc)
+5
(core packages event)
+6
(core packages build-tools)
7
(core packages terminal)
8
(core packages xml)
9
(core packages databases)
@@ -12,12 +14,16 @@
14
stream: "core-stable"
15
bindings: (list
16
(channel-binding name: "core/binaryen" value: binaryen)
+17
(channel-binding name: "core/byacc" value: byacc)
+18
(channel-binding name: "core/file" value: file)
19
(channel-binding name: "core/hello" value: hello)
20
(channel-binding name: "core/zlib" value: zlib)
+21
(channel-binding name: "core/libevent" value: libevent)
22
(channel-binding name: "core/musl" value: musl)
23
(channel-binding name: "core/ncurses" value: ncurses)
24
(channel-binding name: "core/libxml2" value: libxml2)
25
(channel-binding name: "core/sqlite" value: sqlite)
26
(channel-binding name: "core/tree-sitter" value: tree-sitter)
+27
(channel-binding name: "core/tmux" value: tmux)
28
(channel-binding name: "core/vim" value: vim)
29
(channel-binding name: "core/emacs" value: emacs)))
src/core/packages/build-tools.sgladded
@@ -0,0 +1,122 @@
+1
;;; Tools that run during a build and are never linked into its output.
+2
;;;
+3
;;; This is the channel's first `build-inputs:` module, and the distinction it
+4
;;; exists to hold is not cosmetic. A `dependencies:` entry contributes
+5
;;; `-I<dep>/include`, `-L<dep>/lib` and `<dep>/lib/pkgconfig`, and its store
+6
;;; path can legitimately end up inside the built artifact. A `build-inputs:`
+7
;;; entry contributes its `bin/` to PATH (first, ahead of the seed) and nothing
+8
;;; else -- deliberately not `${name}` interpolation, because a baked tool path
+9
;;; would be an undeclared RUNTIME reference to something that is only a
+10
;;; build-time input.
+11
(define-library (core packages build-tools)
+12
(import (sigil core)
+13
(core build-environments)
+14
(sigil env package)
+15
(sigil build external))
+16
(export byacc file)
+17
(begin
+18
;; WHY THE CHANNEL NEEDS A YACC AT ALL, since nothing here has a grammar of
+19
;; its own: tmux's configure REFUSES to proceed without one.
+20
;;
+21
;; configure: error: "yacc not found"
+22
;;
+23
;; It fires even though the tmux release tarball ships `cmd-parse.c`
+24
;; pre-generated from `cmd-parse.y`, because the check is
+25
;; `AC_CHECK_PROG(found_yacc, $YACC, yes, no)` followed by an
+26
;; unconditional AC_MSG_ERROR -- configure asks whether the program exists,
+27
;; not whether the build will need it. Measured, not inferred: the real
+28
;; configure was run against a PATH stripped of yacc/bison/byacc, and the
+29
;; seed's `bin/` was enumerated at 0 of those 3.
+30
;;
+31
;; Patching that check out would have been shorter and would have hidden a
+32
;; true statement about the channel: it had no build tools.
+33
;;
+34
;; byacc rather than bison because byacc is a leaf -- plain autotools, no
+35
;; dependencies, no m4 and no gettext. Its makefile installs the program
+36
;; as `bin/yacc` (makefile.in `THIS = yacc`), which is the name configure
+37
;; looks for.
+38
;;
+39
;; Dickey's byacc is a dated rolling release; the archive at
+40
;; invisible-mirror.net keeps every dated tarball, so the URL is stable
+41
;; even though "the latest byacc" is not.
+42
(define byacc
+43
(package
+44
name: "byacc"
+45
version: "20260126"
+46
source: (external-source
+47
location: "https://invisible-mirror.net/archives/byacc/byacc-20260126.tgz"
+48
sha256: "b618c5fb44c2f5f048843db90f7d1b24f78f47b07913c8c7ba8c942d3eb24b00")
+49
buildenv: core-buildenv
+50
phases: (list
+51
(external-phase
+52
kind: 'configure
+53
args: (list "--prefix=${out}"
+54
;; btyacc's backtracking extension is not
+55
;; wanted; tmux uses plain yacc input
+56
"--disable-btyacc"))
+57
(external-phase kind: 'make)
+58
(external-phase kind: 'install))))
+59
+60
;; WHY A `file` PACKAGE EXISTS IN A CHANNEL THAT NEVER RUNS `file`.
+61
;;
+62
;; ncurses decides whether the linker honours `-dynamic` by running
+63
;; `file(1)` on a conftest and looking for "static" in the output. The
+64
;; seed has no `file(1)`, so the probe produces nothing, the case statement
+65
;; falls to its default branch, and the ABSENCE OF THE TOOL IS READ AS
+66
;; "yes, switching works". ncurses then links its own `tic` as
+67
;; `-static -lncursesw -lncursesw -dynamic`, which with this toolchain
+68
;; produces an ELF carrying an INTERP segment naming
+69
;; `/lib/ld-musl-x86_64.so.1`. That loader is not a declared sandbox
+70
;; input, exec fails ENOENT, and the seed's shell reports it as
+71
;; `run_tic.sh: line 159: tic: not found`. ncurses cannot run the program
+72
;; it just built and `install.data` fails.
+73
;;
+74
;; Measured, matched pair, same source and same musl wrapper, only PATH
+75
;; differing:
+76
;;
+77
;; file ON PATH -> "switching between static/dynamic... no"
+78
;; LIBS_TIC = -L../lib -lncursesw -lncursesw
+79
;; file ABSENT -> "switching between static/dynamic... yes"
+80
;; LIBS_TIC = -L../lib -static -lncursesw -lncursesw
+81
;; -dynamic
+82
;;
+83
;; So the fix is to make the tool PRESENT rather than to teach ncurses'
+84
;; configure what to conclude when it is missing. A patch to that case
+85
;; statement was written and tested and is NOT used here: it works, but it
+86
;; encodes this channel's deficiency into a third party's source, and it
+87
;; would need retiring the moment the seed gains `file(1)`. Declaring the
+88
;; tool is the same answer the rest of this channel gives -- a package
+89
;; states what it needs instead of the environment being assumed.
+90
;;
+91
;; THIS DOES NOT CLOSE THE SEED GAP (t-46a82e). The seed still has no
+92
;; `file(1)`, which still matters for the seed's own bootstrap and for
+93
;; anything probing before its build inputs are available. This fixes
+94
;; ncurses, not the class.
+95
;;
+96
;; No compression backends: each is a `[default=auto]` probe that would
+97
;; make the output depend on what happens to be visible, and none of them
+98
;; is what ncurses' configure asks this program.
+99
(define file
+100
(package
+101
name: "file"
+102
version: "5.48"
+103
source: (external-source
+104
location: "https://astron.com/pub/file/file-5.48.tar.gz"
+105
sha256: "ed14656883b23a364b4057c05595d93252da9bc473d30106519519d0da141283")
+106
buildenv: core-buildenv
+107
phases: (list
+108
(external-phase
+109
kind: 'configure
+110
args: (list "--prefix=${out}"
+111
"--disable-shared"
+112
"--enable-static"
+113
"--disable-zlib"
+114
"--disable-bzlib"
+115
"--disable-xzlib"
+116
"--disable-zstdlib"
+117
"--disable-lzlib"
+118
"--disable-lrziplib"
+119
"--disable-libseccomp"
+120
"--disable-landlock"))
+121
(external-phase kind: 'make)
+122
(external-phase kind: 'install))))))
src/core/packages/event.sgladded
@@ -0,0 +1,64 @@
+1
;;; Asynchronous event notification.
+2
;;;
+3
;;; libevent lands in its own module rather than beside ncurses in
+4
;;; (core packages terminal), even though tmux is the only thing in the channel
+5
;;; that wants it today. Two reasons, and the second is the one that matters:
+6
;;;
+7
;;; * it follows the channel's existing shape. compression/, databases/,
+8
;;; parsing/ and xml/ are each a single-package module named for a DOMAIN,
+9
;;; not for a consumer. libevent's domain is event loops, not terminals.
+10
;;;
+11
;;; * filing it under `terminal` would encode "libevent exists to serve tmux"
+12
;;; in the module layout, and the next consumer of libevent will not be a
+13
;;; terminal program. That is the same mistake in a different place as the
+14
;;; builder hardcoding `include/ncursesw` for every package -- one
+15
;;; consumer's situation written down somewhere general.
+16
(define-library (core packages event)
+17
(import (sigil core)
+18
(core build-environments)
+19
(sigil env package)
+20
(sigil build external))
+21
(export libevent)
+22
(begin
+23
;; NO `search-paths:` DECLARATION, and that is an answered question rather
+24
;; than an omission. ncurses needs one because `--enable-widec` puts every
+25
;; header under `include/ncursesw/` and leaves `include/` empty, so the
+26
;; builder's own `-I<dep>/include` reaches nothing. libevent installs
+27
;; `event.h`, `evutil.h` and friends directly in `include/`, with the
+28
;; modern API under `include/event2/` -- and `#include <event2/event.h>`,
+29
;; which is what tmux compiles, resolves relative to `include/`. The
+30
;; builder's own flag is therefore sufficient; measured on the built store
+31
;; item, see the task note.
+32
;;
+33
;; The `.pc` files matter more than the headers here: tmux finds libevent
+34
;; through PKG_CHECK_MODULES, and the builder puts every dependency's
+35
;; `lib/pkgconfig` on PKG_CONFIG_PATH already. Each of libevent's four
+36
;; `.pc` files carries an empty `Requires:` and an empty `Conflicts:`,
+37
;; which is exactly the shape that made the OLD seed's pkgconf 1.9.5 abort
+38
;; with SIGABRT. Measured against the seed this channel actually pins
+39
;; (pkgconf 2.4.3, seed-v2): accepted, exit 0, with an absent-package
+40
;; control still exiting 1.
+41
(define libevent
+42
(package
+43
name: "libevent"
+44
version: "2.1.13-stable"
+45
source: (external-source
+46
location: "https://github.com/libevent/libevent/releases/download/release-2.1.13-stable/libevent-2.1.13-stable.tar.gz"
+47
sha256: "f7e9383b8c0baa81b687e5b5eecc01beefaf1b19b64151d95ed61647fe7a315c")
+48
buildenv: core-buildenv
+49
phases: (list
+50
(external-phase
+51
kind: 'configure
+52
args: (list "--prefix=${out}"
+53
;; static only, consistent with the channel
+54
"--disable-shared"
+55
"--enable-static"
+56
;; openssl is not in the channel
+57
"--disable-openssl"
+58
;; nothing here is installed, and both build
+59
;; test programs we would only throw away
+60
"--disable-samples"
+61
"--disable-libevent-regress"
+62
"--disable-doxygen-html"))
+63
(external-phase kind: 'make)
+64
(external-phase kind: 'install))))))
src/core/packages/terminal.sglmodified
@@ -1,15 +1,24 @@
−1
;;; Terminal libraries.
+1
;;; Terminal libraries and terminal programs.
2
;;;
3
;;; ncurses is the wide-character terminal library every terminal editor in
4
;;; this channel links against. The install phase must pin `ticdir` because
5
;;; ncurses' own install rule otherwise writes the compiled terminfo database
6
;;; outside DESTDIR; see the note beside the `install` phase below.
+7
;;;
+8
;;; tmux is the channel's first package with a dependency SET rather than a
+9
;;; single edge, and it carries a build input as well. Those are two different
+10
;;; relationships and tmux has one of each: ncurses and libevent are linked
+11
;;; into the output, byacc only has to exist on PATH while configure runs.
+12
;;; ncurses gained a build input of its own for the same reason -- see the
+13
;;; note beside its `build-inputs:` below.
14
(define-library (core packages terminal)
15
(import (sigil core)
16
(core build-environments)
+17
(core packages event)
+18
(core packages build-tools)
19
(sigil env package)
20
(sigil build external))
−12
(export ncurses)
+21
(export ncurses tmux)
22
(begin
23
(define ncurses
24
(package
@@ -36,6 +45,19 @@
45
;; (without it the compile fails `fatal error: 'curses.h' file not
46
;; found`; with it, it succeeds).
47
search-paths: (list (cons "CPATH" "include/ncursesw"))
+48
;; `file(1)` IS NOT IN THE SEED, and ncurses' configure uses it to
+49
;; decide whether the linker honours `-dynamic`. With the probe absent
+50
;; the ABSENCE is read as "yes", ncurses links its own `tic` with a
+51
;; trailing `-dynamic`, the binary acquires an INTERP segment naming a
+52
;; loader the sandbox does not have, and `make install` dies with
+53
;; `tic: not found` -- ncurses unable to run the program it just built.
+54
;; Declaring the tool is what makes the probe answer honestly; see
+55
;; (core packages build-tools) for the matched pair and for why a patch
+56
;; to ncurses' configure was rejected in favour of this.
+57
;;
+58
;; A build input, not a dependency: nothing `file` produces is linked
+59
;; into ncurses. It contributes its `bin/` to PATH and nothing else.
+60
build-inputs: (list file)
61
phases: (list
62
(external-phase
63
kind: 'configure
@@ -50,7 +72,45 @@
72
"--with-normal"
73
"--enable-widec"
74
"--with-default-terminfo-dir=${out}/share/terminfo"
−53
"--with-terminfo-dirs=${out}/share/terminfo"))
+75
"--with-terminfo-dirs=${out}/share/terminfo"
+76
;; INSTALL .pc FILES, and this is load-bearing
+77
;; rather than tidy. ncurses is widec-only
+78
;; here, so the only library it installs is
+79
;; `libncursesw.a`. A consumer that cannot see
+80
;; ncurses through pkg-config falls back to
+81
;; probing, and tmux's fallback is:
+82
;;
+83
;; AC_SEARCH_LIBS(setupterm,
+84
;; [tinfow tinfo terminfo ncursesw ncurses])
+85
;; AC_CHECK_HEADER(ncurses.h,
+86
;; LIBS="$LIBS -lncurses", ...)
+87
;;
+88
;; It finds `setupterm` in `-lncursesw`, then
+89
;; finds `ncurses.h` (our `search-paths:` CPATH
+90
;; export puts it there) and appends
+91
;; `-lncurses` -- A LIBRARY THIS PACKAGE DOES
+92
;; NOT INSTALL.
+93
;;
+94
;; What that costs is far worse than one bad
+95
;; flag. `LIBS` is threaded through every
+96
;; later autoconf link test, so from that point
+97
;; on EVERY link test fails for a reason that
+98
;; has nothing to do with what it is testing,
+99
;; and each one records "no". Measured on
+100
;; tmux 3.7c: `forkpty` was reported absent,
+101
;; tmux compiled its own replacement, and the
+102
;; build died on a prototype conflict with
+103
;; musl's `pty.h`. forkpty is in musl and
+104
;; links fine -- probed four ways, all rc 0.
+105
;; Configure still exited 0.
+106
;;
+107
;; With a pkg-config-visible `ncursesw` the
+108
;; same configure reports `ncursesw... yes`,
+109
;; `forkpty... none required`, and a final
+110
;; LIBS carrying `-lncursesw` and no
+111
;; `-lncurses`.
+112
"--enable-pc-files"
+113
"--with-pkg-config-libdir=${out}/lib/pkgconfig"))
114
(external-phase kind: 'make)
115
;; ncurses' own install rule otherwise writes the compiled
116
;; terminfo database outside DESTDIR, so `ticdir` has to be
@@ -61,4 +121,65 @@
121
;; behaviour change.
122
(external-phase
123
kind: 'install
−64
args: (list "ticdir=${out}/share/terminfo")))))))
+124
args: (list "ticdir=${out}/share/terminfo")))))
+125
+126
;; TERMINFO IS THE INTERESTING PART OF THIS PACKAGE, not the link.
+127
;;
+128
;; tmux is a terminal multiplexer: it allocates a pty, and it reads a
+129
;; terminfo entry to know what to write into it. A tmux that starts only
+130
;; because the HOST happens to have `/usr/share/terminfo` is the exact
+131
;; false green this channel exists to prevent, and `tmux -V` cannot tell
+132
;; the two apart -- it proves the binary links and nothing else.
+133
;;
+134
;; What makes the store copy authoritative is ncurses' own
+135
;; `--with-default-terminfo-dir=${out}/share/terminfo`, above. That path
+136
;; is compiled into `libncursesw.a`, so linking it statically carries
+137
;; ncurses' store item into this binary as its terminfo root. ncurses is
+138
;; a declared dependency, so that is a true reference rather than a
+139
;; smuggled one.
+140
(define tmux
+141
(package
+142
name: "tmux"
+143
version: "3.7c"
+144
source: (external-source
+145
location: "https://github.com/tmux/tmux/releases/download/3.7c/tmux-3.7c.tar.gz"
+146
sha256: "7c60cae9a0e25288e2e24750aafc9e8800fc7fd4555e447e1b29ee4201cfb3bf")
+147
buildenv: core-buildenv
+148
;; The channel's first dependency SET. Nothing here says how either
+149
;; dependency is laid out: ncurses exports `CPATH=include/ncursesw`
+150
;; itself, libevent needs no export because its headers sit directly
+151
;; in `include/`, and the builder contributes `-I`, `-L` and
+152
;; `PKG_CONFIG_PATH` for both. Order is not significant -- the engine
+153
;; sorts dependencies by name before sealing them.
+154
dependencies: (list ncurses libevent)
+155
;; byacc is here and NOT in `dependencies:` because nothing it produces
+156
;; is linked into tmux. configure demands a program called `yacc` on
+157
;; PATH and then never needs it, since the release tarball already
+158
;; ships `cmd-parse.c`; see (core packages build-tools) for the
+159
;; measurement.
+160
build-inputs: (list byacc)
+161
phases: (list
+162
(external-phase
+163
kind: 'configure
+164
args: (list "--prefix=${out}"
+165
;; every other package in the channel is
+166
;; static, and this also makes configure ask
+167
;; pkg-config for `--static`, which is what
+168
;; pulls libevent's `Libs.private` onto the
+169
;; link line
+170
"--enable-static"
+171
;; the default already, stated so the build
+172
;; does not depend on what configure happens
+173
;; to find probeable
+174
"--with-TERM=screen"
+175
;; none of these are in the channel, and each
+176
;; is a `use X if it is installed` probe that
+177
;; would otherwise make the output depend on
+178
;; the host
+179
"--disable-utf8proc"
+180
"--disable-utempter"
+181
"--disable-jemalloc"
+182
"--disable-systemd"
+183
"--disable-fuzzing"))
+184
(external-phase kind: 'make)
+185
(external-phase kind: 'install))))))