AtlatestRepositorycore-channel
core-channel / tree / src / core / packagesterminal.sgl
1
;;; Terminal libraries and terminal programs.2
;;;3
;;; ncurses is the wide-character terminal library every terminal editor in4
;;; this channel links against. The install phase must pin `ticdir` because5
;;; ncurses' own install rule otherwise writes the compiled terminfo database6
;;; outside DESTDIR; see the note beside the `install` phase below.7
;;;8
;;; tmux is the first package here that has BOTH kinds of input at once: a9
;;; dependency SET (ncurses and libevent, linked into the output) and a BUILD10
;;; INPUT (byacc, which only has to exist on PATH while configure runs). It is11
;;; NOT the channel's first multi-dependency package -- emacs has had five12
;;; since it landed, `(list ncurses zlib libxml2 sqlite tree-sitter)` in13
;;; (core packages editors). ncurses gained a build input of its own in the14
;;; same change, for an unrelated reason; see the note beside its15
;;; `build-inputs:` below.16
(define-library (core packages terminal)17
(import (sigil core)18
(core build-environments)19
(core packages event)20
(core packages ffi)21
(core packages xml)22
(core packages compression)23
(core packages graphics)24
(core packages datastructures)25
(core packages fonts)26
(core packages wayland)27
(core packages input)28
(core packages python)29
(core packages build-tools)30
(sigil env package)31
(sigil build external))32
(export ncurses tmux foot)33
(begin34
(define ncurses35
(package36
name: "ncurses"37
version: "6.6"38
source: (external-source39
location: "https://ftp.gnu.org/gnu/ncurses/ncurses-6.6.tar.gz"40
sha256: "355b4cbbed880b0381a04c46617b7656e362585d52e9cf84a67e2009b749ff11")41
buildenv: core-buildenv42
;; ncurses with `--enable-widec` installs its headers ONLY into43
;; `include/ncursesw/`; `include/` itself holds no headers at all44
;; (measured on both built store items). A consumer compiling45
;; `#include <curses.h>` therefore needs that directory on its include46
;; path.47
;;48
;; This declaration is what replaced a hardcode. The generic builder49
;; used to emit `-I<dep>/include/ncursesw` for EVERY dependency of50
;; EVERY package: one package's directory layout baked into the builder51
;; for all packages alike. Now ncurses says it itself, and only52
;; ncurses' consumers hear it.53
;;54
;; CPATH rather than a `-I` flag because a search path is a path list,55
;; and measured: zig cc 0.16.0 honours CPATH in both directions56
;; (without it the compile fails `fatal error: 'curses.h' file not57
;; found`; with it, it succeeds).58
search-paths: (list (cons "CPATH" "include/ncursesw"))59
;; `file(1)` IS NOT IN THE SEED, and ncurses' configure uses it to60
;; decide whether the linker honours `-dynamic`. With the probe absent61
;; the ABSENCE is read as "yes", ncurses links its own `tic` with a62
;; trailing `-dynamic`, the binary acquires an INTERP segment naming a63
;; loader the sandbox does not have, and `make install` dies with64
;; `tic: not found` -- ncurses unable to run the program it just built.65
;; Declaring the tool is what makes the probe answer honestly; see66
;; (core packages build-tools) for the matched pair and for why a patch67
;; to ncurses' configure was rejected in favour of this.68
;;69
;; A build input, not a dependency: nothing `file` produces is linked70
;; into ncurses. It contributes its `bin/` to PATH and nothing else.71
build-inputs: (list file)72
phases: (list73
(external-phase74
kind: 'configure75
args: (list "--prefix=${out}"76
"--without-ada"77
"--without-tests"78
"--without-cxx"79
"--without-cxx-binding"80
"--without-debug"81
"--disable-stripping"82
"--without-shared"83
"--with-normal"84
"--enable-widec"85
"--with-default-terminfo-dir=${out}/share/terminfo"86
"--with-terminfo-dirs=${out}/share/terminfo"87
;; INSTALL .pc FILES, and this is load-bearing88
;; rather than tidy. ncurses is widec-only89
;; here, so the only library it installs is90
;; `libncursesw.a`. A consumer that cannot see91
;; ncurses through pkg-config falls back to92
;; probing, and tmux's fallback is:93
;;94
;; AC_SEARCH_LIBS(setupterm,95
;; [tinfow tinfo terminfo ncursesw ncurses])96
;; AC_CHECK_HEADER(ncurses.h,97
;; LIBS="$LIBS -lncurses", ...)98
;;99
;; It finds `setupterm` in `-lncursesw`, then100
;; finds `ncurses.h` (our `search-paths:` CPATH101
;; export puts it there) and appends102
;; `-lncurses` -- A LIBRARY THIS PACKAGE DOES103
;; NOT INSTALL.104
;;105
;; What that costs is far worse than one bad106
;; flag. `LIBS` is threaded through every107
;; later autoconf link test, so from that point108
;; on EVERY link test fails for a reason that109
;; has nothing to do with what it is testing,110
;; and each one records "no". Measured on111
;; tmux 3.7c: `forkpty` was reported absent,112
;; tmux compiled its own replacement, and the113
;; build died on a prototype conflict with114
;; musl's `pty.h`. forkpty is in musl and115
;; links fine -- probed four ways, all rc 0.116
;; Configure still exited 0.117
;;118
;; With a pkg-config-visible `ncursesw` the119
;; same configure reports `ncursesw... yes`,120
;; `forkpty... none required`, and a final121
;; LIBS carrying `-lncursesw` and no122
;; `-lncurses`.123
;;124
;; TWO CONSEQUENCES OF GENERATING THESE FILES,125
;; both measured on the installed item, because126
;; a `.pc` is a consumer-facing interface and127
;; these are what consumers will see:128
;;129
;; * `Libs:` comes out as130
;; `-L<out>/lib -s -lncursesw`. The `-s`131
;; is the build environment's own LDFLAGS132
;; leaking into a published interface, so133
;; every pkg-config consumer is silently134
;; force-stripped -- and this package135
;; passes `--disable-stripping` two lines136
;; down. Not fixed here; ncurses'137
;; gen-pkgconfig substitutes $LDFLAGS and138
;; removing it needs a patch.139
;;140
;; * `Requires.private:` and `Libs.private:`141
;; are emitted EMPTY -- the exact shape142
;; src/patches/libxml2-pc-drop-empty-143
;; requires.patch exists to remove. It is144
;; safe only because the seed's pkgconf145
;; carries a SOURCE PATCH for the146
;; underflow; see seed/scripts/147
;; build-seed.sh, which measured the same148
;; defective line in 1.9.5 and 2.4.3 and149
;; states that no pkgconf release fixes it.150
;; Verified against the seed's own binary151
;; on all four generated files: rc 0, with152
;; an absent-package control still rc 1.153
"--enable-pc-files"154
"--with-pkg-config-libdir=${out}/lib/pkgconfig"))155
(external-phase kind: 'make)156
;; ncurses' own install rule otherwise writes the compiled157
;; terminfo database outside DESTDIR, so `ticdir` has to be158
;; pinned. This was spelled `/tmp/sigil/build/out/...` under159
;; the old model; `${out}` expands to exactly the same string160
;; (the builder rewrote both `/out` and `/tmp/sigil/build/out`161
;; to the package's own prefix), so this is a rename, not a162
;; behaviour change.163
(external-phase164
kind: 'install165
args: (list "ticdir=${out}/share/terminfo")))))167
;; TERMINFO IS THE INTERESTING PART OF THIS PACKAGE, not the link.168
;;169
;; tmux is a terminal multiplexer: it allocates a pty, and a tmux CLIENT170
;; reads a terminfo entry to know what to write into it. `tmux -V` cannot171
;; see any of that -- it proves the binary links and nothing else. Nor can172
;; a detached `new-session -d`: no client attaches, so no terminfo entry is173
;; ever loaded and every arm of such a test passes, including the ones174
;; meant to fail.175
;;176
;; WHERE THE TERMINFO ACTUALLY COMES FROM, stated exactly, because the177
;; comfortable version of this is wrong. ncurses is linked statically, so178
;; its `--with-default-terminfo-dir=${out}/share/terminfo` IS carried into179
;; the tmux binary -- `grep -a` finds `/sigil/store/<ncurses-item>/share/180
;; terminfo` in it, and that is the only store path the binary contains.181
;; But `/sigil/store` is the SANDBOX's view of the store and exists only182
;; during a build, so OUTSIDE the sandbox that compiled-in default resolves183
;; to nothing:184
;;185
;; TERMINFO_DIRS = the ncurses store item -> session runs, output captured186
;; TERMINFO_DIRS = an empty directory -> "missing or unsuitable terminal"187
;; TERMINFO_DIRS = a nonexistent path -> "can't find terminfo database"188
;; TERMINFO_DIRS unset (compiled-in only) -> "can't find terminfo database"189
;; TERMINFO_DIRS = the host's guix profile-> session runs, output captured190
;;191
;; So the store's terminfo works and is load-bearing, and tmux CANNOT reach192
;; it on its own: something must hand it the path. Today `sigil env shell`193
;; hands it the HOST's, because it exports a hardcoded search-path list194
;; applied to the lock's ROOT packages only and never consults a package's195
;; own `search-paths:` -- so ncurses' terminfo, a DEPENDENCY's, is not on196
;; the list at all. That is a `sigil env` defect, not a packaging one, and197
;; it is tracked outside this repository. Recorded here so the next reader198
;; does not conclude from a working tmux that the store is what fed it.199
(define tmux200
(package201
name: "tmux"202
version: "3.7c"203
source: (external-source204
location: "https://github.com/tmux/tmux/releases/download/3.7c/tmux-3.7c.tar.gz"205
sha256: "7c60cae9a0e25288e2e24750aafc9e8800fc7fd4555e447e1b29ee4201cfb3bf")206
buildenv: core-buildenv207
;; Nothing here says how either dependency is laid out: ncurses exports208
;; `CPATH=include/ncursesw` itself, libevent needs no export because209
;; its headers sit directly in `include/`, and the builder contributes210
;; `-I`, `-L` and `PKG_CONFIG_PATH` for both. Order is not significant211
;; -- the engine sorts dependencies by name before sealing them, which212
;; is visible in any objects/<id>.sgl.213
dependencies: (list ncurses libevent)214
;; byacc is here and NOT in `dependencies:` because nothing it produces215
;; is linked into tmux. configure demands a program called `yacc` on216
;; PATH and then never needs it, since the release tarball already217
;; ships `cmd-parse.c`; see (core packages build-tools) for the218
;; measurement.219
build-inputs: (list byacc)220
phases: (list221
(external-phase222
kind: 'configure223
args: (list "--prefix=${out}"224
;; every other package in the channel is225
;; static. It puts `-static` on LDFLAGS and226
;; makes configure call pkg-config with227
;; `--static`. The pkg-config half buys228
;; nothing TODAY -- all four of libevent's229
;; `.pc` files have an empty `Libs.private:`,230
;; and `pkgconf --static --libs libevent_core`231
;; returns exactly what the non-static call232
;; does -- so `-static` is the whole of it.233
"--enable-static"234
;; the default already, stated so the build235
;; does not depend on what configure happens236
;; to find probeable237
"--with-TERM=screen"238
;; None of these are in the channel. Stated239
;; explicitly rather than left to the default,240
;; but be clear about what that buys: read241
;; against tmux 3.7c's configure.ac, ALL FIVE242
;; are opt-in and already off when unset243
;; (utf8proc and jemalloc probe automatically244
;; only on darwin), and each ERRORS rather245
;; than silently absorbing the host when asked246
;; for and missing. So these flags are247
;; documentation, not a hermeticity fix. The248
;; probe that genuinely DID leak the build249
;; environment into the output is the lock250
;; command, below.251
"--disable-utf8proc"252
"--disable-utempter"253
"--disable-jemalloc"254
"--disable-systemd"255
"--disable-fuzzing"256
;; A REAL HOST LEAK, and it is the only one257
;; tmux's configure leaves open here.258
;;259
;; DEFAULT_LOCK_CMD="lock -np"260
;; AC_CHECK_PROG(found_vlock, vlock, yes, no)261
;; if found_vlock; then DEFAULT_LOCK_CMD="vlock"262
;;263
;; Measured: without this override configure264
;; reports `lock-command... vlock`, because the265
;; SEED ships a busybox `vlock` applet, and266
;; `-DTMUX_LOCK_CMD='"vlock"'` lands on every267
;; compile line. That is a value chosen by268
;; what happened to be visible in the build269
;; environment rather than by anything this270
;; package declares -- and `vlock` is not in271
;; tmux's runtime closure, so `lock-server`272
;; would shell out to a program that is not273
;; there.274
;;275
;; `ac_cv_prog_found_vlock=no` is autoconf's276
;; own documented override (AC_CHECK_PROG IS277
;; cached; `ac_cv_prog_found_vlock` appears in278
;; the generated configure, against a control279
;; showing 89 `ac_cv_prog_` variables there).280
;; Measured: it flips the result to281
;; `lock-command... lock -np`.282
;;283
;; HONEST ABOUT WHAT THIS FIXES: `lock -np` is284
;; not in the closure either. Neither default285
;; is runnable, and `lock-command` is a runtime286
;; server option anyone can set. What changes287
;; is that the compiled default now comes from288
;; tmux's own portable choice instead of from289
;; the seed's applet list.290
"ac_cv_prog_found_vlock=no"))291
(external-phase kind: 'make)292
(external-phase kind: 'install))))294
;; foot: a Wayland terminal emulator.295
;;296
;; THE INTERESTING PROPERTY, and the reason this package is the point of297
;; the exercise rather than another consumer: FOOT SHIPS ITS OWN TERMINFO.298
;; `tic` -- from `core/ncurses`, as a BUILD INPUT -- compiles `foot.info`299
;; into `${out}/share/terminfo` during the build.300
;;301
;; That makes foot a WEAK test of terminfo resolution and a STRONG one, in302
;; two different configurations, and confusing them is how you get a green303
;; that proves nothing:304
;;305
;; AS A ROOT, foot's own terminfo is exported by even the oldest,306
;; broken search-path behaviour, because that behaviour composes paths307
;; over the lock's ROOT packages. A foot that starts and draws in that308
;; configuration would have started and drawn before any of the runtime309
;; search-path work existed.310
;;311
;; THROUGH A DEPENDENCY is where it breaks. Run `tmux` inside foot and312
;; tmux needs the `screen` entry, which comes from `core/ncurses` -- a313
;; DEPENDENCY of tmux, not a root. That is the path that was absent.314
;;315
;; The acceptance evidence for this package is built around the second316
;; case, deliberately. See [[tasks/foot-packaging]].317
;;318
;; WHAT FOOT NEEDS FROM OUTSIDE THE STORE, stated because a reader will319
;; otherwise mistake it for a defect:320
;;321
;; - A Wayland compositor. Supplied by the user's session over a socket;322
;; no library or version has to match, because Wayland is a wire323
;; protocol. See the note in `(core packages wayland)`.324
;; - FONTS. fontconfig searches the directories ITS configuration names,325
;; and the user's font files are wherever their system put them. This326
;; is the one genuine host coupling in foot's runtime and nothing in327
;; this channel closes it.328
;; - A cursor theme, through wayland-cursor, with the same shape.329
;;330
;; `-Dcustom-terminfo-install-location` is deliberately NOT set. Setting331
;; it makes foot export `TERMINFO` into its child processes, pointing at332
;; its own store item -- which would be convenient and would also mask the333
;; dependency-path question this package exists to exercise. If it is ever334
;; set, the acceptance test above stops measuring what it claims to.335
(define foot336
(package337
name: "foot"338
version: "1.27.0"339
source: (external-source340
location: "https://codeberg.org/dnkl/foot/releases/download/1.27.0/foot-1.27.0.tar.gz"341
sha256: "d1e1d04cd7a3a1615b4d8dbf9645b9a35cdc3f36fba4e1c0fd252ad0bd6651c8")342
buildenv: core-buildenv343
;; freetype and zlib are not named by foot's own meson.build. They are344
;; reached through the `Requires:` chain of fcft.pc and fontconfig.pc,345
;; and this channel composes PKG_CONFIG_PATH from DIRECT dependencies346
;; only -- see the long note on `fontconfig`.347
dependencies: (list fcft tllist pixman wayland wayland-protocols348
libffi expat349
libxkbcommon fontconfig freetype zlib)350
;; ncurses is a BUILD INPUT and not a dependency: `tic` runs during the351
;; build and nothing from ncurses is linked into foot or referenced by352
;; it at runtime. Declaring it as a dependency would put ncurses in353
;; foot's runtime closure and would ALSO quietly make the terminfo354
;; acceptance test pass for the wrong reason.355
build-inputs: (list meson samurai python ncurses)356
phases: (list357
(external-phase358
kind: 'run359
args: (list "sh" "-c"360
(string-append361
"set -eu\n"362
"meson setup build"363
" --prefix=${out}"364
" --buildtype=plain"365
" --default-library=static"366
;; `-Dprefer_static=true` MAKES MESON ASK367
;; PKG-CONFIG THE STATIC QUESTION, and without it368
;; a fully static link fails at the last step.369
;;370
;; A `.pc` file splits its link line: `Libs:` is371
;; what a SHARED consumer needs, `Libs.private:`372
;; and `Requires.private:` are the extra373
;; transitive libraries a STATIC consumer needs.374
;; fontconfig lists expat under375
;; `Requires.private:`, so a default376
;; `dependency('fontconfig')` emits no `-lexpat`377
;; -- correct for a shared link, fatal here:378
;;379
;; ld.lld: error: undefined symbol: XML_ErrorString380
;; >>> referenced by fcxml.c381
;; >>> fcxml.o in archive libfontconfig.a382
;;383
;; The symbol is missing from the LINK, not from384
;; the closure: expat is declared, built, and385
;; present. Only the query was the wrong one.386
;; This is the same `Requires.private:` line that387
;; had to be satisfied at CONFIGURE time; it bites388
;; twice, in two different ways, and fixing the389
;; first does not fix the second.390
" -Dprefer_static=true"391
" -Ddocs=disabled"392
;; Matches fcft: no harfbuzz, no utf8proc. The393
;; two must agree -- grapheme clustering in foot394
;; without shaping in fcft is, in foot's own395
;; words, not useful.396
" -Dgrapheme-clustering=disabled"397
;; utmp/wtmp accounting would want a setuid398
;; helper this channel has no way to install.399
" -Dutmp-backend=none"400
" -Dtests=false\n"401
"echo FOOT-CONFIGURED\n")))402
(external-phase403
kind: 'run404
args: (list "sh" "-c"405
(string-append406
"set -eu\n"407
"meson compile -C build\n"408
"echo FOOT-COMPILED\n")))409
(external-phase410
kind: 'run411
args: (list "sh" "-c"412
(string-append413
"set -eu\n"414
"d=\"$(dirname \"$PWD\")/install-root\"\n"415
"DESTDIR=\"$d\" meson install -C build --no-rebuild\n"416
"test -x \"$d${out}/bin/foot\"\n"417
"test -x \"$d${out}/bin/footclient\"\n"418
;; THE TERMINFO ENTRIES ARE ASSERTED, not419
;; assumed. `-Dterminfo` defaults to enabled but420
;; `find_program('tic')` is `required:` on that421
;; option -- so if the build input ever went422
;; missing, meson would DISABLE the feature and423
;; still succeed, and foot would install with no424
;; terminfo at all. That is a silent425
;; feature-loss of exactly the kind this channel426
;; keeps finding, so it fails here instead.427
;;428
;; `find -L` because terminfo directories are429
;; symlink farms and plain `find -type f`430
;; returns 0 on them.431
"n=$(find -L \"$d${out}/share/terminfo\" -type f | wc -l)\n"432
"echo \"FOOT-TERMINFO-FILES=$n\"\n"433
"test \"$n\" -ge 2\n"434
"test -f \"$d${out}/share/terminfo/f/foot\"\n"435
"test -f \"$d${out}/share/terminfo/f/foot-direct\"\n"436
"echo FOOT-INSTALLED\n"))))))))