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 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 first package here that has BOTH kinds of input at once: a
9;;; dependency SET (ncurses and libevent, linked into the output) and a BUILD
10;;; INPUT (byacc, which only has to exist on PATH while configure runs). It is
11;;; NOT the channel's first multi-dependency package -- emacs has had five
12;;; since it landed, `(list ncurses zlib libxml2 sqlite tree-sitter)` in
13;;; (core packages editors). ncurses gained a build input of its own in the
14;;; same change, for an unrelated reason; see the note beside its
15;;; `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 (begin
34 (define ncurses
35 (package
36 name: "ncurses"
37 version: "6.6"
38 source: (external-source
39 location: "https://ftp.gnu.org/gnu/ncurses/ncurses-6.6.tar.gz"
40 sha256: "355b4cbbed880b0381a04c46617b7656e362585d52e9cf84a67e2009b749ff11")
41 buildenv: core-buildenv
42 ;; ncurses with `--enable-widec` installs its headers ONLY into
43 ;; `include/ncursesw/`; `include/` itself holds no headers at all
44 ;; (measured on both built store items). A consumer compiling
45 ;; `#include <curses.h>` therefore needs that directory on its include
46 ;; path.
47 ;;
48 ;; This declaration is what replaced a hardcode. The generic builder
49 ;; used to emit `-I<dep>/include/ncursesw` for EVERY dependency of
50 ;; EVERY package: one package's directory layout baked into the builder
51 ;; for all packages alike. Now ncurses says it itself, and only
52 ;; 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 directions
56 ;; (without it the compile fails `fatal error: 'curses.h' file not
57 ;; 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 to
60 ;; decide whether the linker honours `-dynamic`. With the probe absent
61 ;; the ABSENCE is read as "yes", ncurses links its own `tic` with a
62 ;; trailing `-dynamic`, the binary acquires an INTERP segment naming a
63 ;; loader the sandbox does not have, and `make install` dies with
64 ;; `tic: not found` -- ncurses unable to run the program it just built.
65 ;; Declaring the tool is what makes the probe answer honestly; see
66 ;; (core packages build-tools) for the matched pair and for why a patch
67 ;; to ncurses' configure was rejected in favour of this.
68 ;;
69 ;; A build input, not a dependency: nothing `file` produces is linked
70 ;; into ncurses. It contributes its `bin/` to PATH and nothing else.
71 build-inputs: (list file)
72 phases: (list
73 (external-phase
74 kind: 'configure
75 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-bearing
88 ;; rather than tidy. ncurses is widec-only
89 ;; here, so the only library it installs is
90 ;; `libncursesw.a`. A consumer that cannot see
91 ;; ncurses through pkg-config falls back to
92 ;; 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`, then
100 ;; finds `ncurses.h` (our `search-paths:` CPATH
101 ;; export puts it there) and appends
102 ;; `-lncurses` -- A LIBRARY THIS PACKAGE DOES
103 ;; NOT INSTALL.
104 ;;
105 ;; What that costs is far worse than one bad
106 ;; flag. `LIBS` is threaded through every
107 ;; later autoconf link test, so from that point
108 ;; on EVERY link test fails for a reason that
109 ;; has nothing to do with what it is testing,
110 ;; and each one records "no". Measured on
111 ;; tmux 3.7c: `forkpty` was reported absent,
112 ;; tmux compiled its own replacement, and the
113 ;; build died on a prototype conflict with
114 ;; musl's `pty.h`. forkpty is in musl and
115 ;; links fine -- probed four ways, all rc 0.
116 ;; Configure still exited 0.
117 ;;
118 ;; With a pkg-config-visible `ncursesw` the
119 ;; same configure reports `ncursesw... yes`,
120 ;; `forkpty... none required`, and a final
121 ;; LIBS carrying `-lncursesw` and no
122 ;; `-lncurses`.
123 ;;
124 ;; TWO CONSEQUENCES OF GENERATING THESE FILES,
125 ;; both measured on the installed item, because
126 ;; a `.pc` is a consumer-facing interface and
127 ;; these are what consumers will see:
128 ;;
129 ;; * `Libs:` comes out as
130 ;; `-L<out>/lib -s -lncursesw`. The `-s`
131 ;; is the build environment's own LDFLAGS
132 ;; leaking into a published interface, so
133 ;; every pkg-config consumer is silently
134 ;; force-stripped -- and this package
135 ;; passes `--disable-stripping` two lines
136 ;; down. Not fixed here; ncurses'
137 ;; gen-pkgconfig substitutes $LDFLAGS and
138 ;; removing it needs a patch.
139 ;;
140 ;; * `Requires.private:` and `Libs.private:`
141 ;; are emitted EMPTY -- the exact shape
142 ;; src/patches/libxml2-pc-drop-empty-
143 ;; requires.patch exists to remove. It is
144 ;; safe only because the seed's pkgconf
145 ;; carries a SOURCE PATCH for the
146 ;; underflow; see seed/scripts/
147 ;; build-seed.sh, which measured the same
148 ;; defective line in 1.9.5 and 2.4.3 and
149 ;; states that no pkgconf release fixes it.
150 ;; Verified against the seed's own binary
151 ;; on all four generated files: rc 0, with
152 ;; 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 compiled
157 ;; terminfo database outside DESTDIR, so `ticdir` has to be
158 ;; pinned. This was spelled `/tmp/sigil/build/out/...` under
159 ;; the old model; `${out}` expands to exactly the same string
160 ;; (the builder rewrote both `/out` and `/tmp/sigil/build/out`
161 ;; to the package's own prefix), so this is a rename, not a
162 ;; behaviour change.
163 (external-phase
164 kind: 'install
165 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 CLIENT
170 ;; reads a terminfo entry to know what to write into it. `tmux -V` cannot
171 ;; see any of that -- it proves the binary links and nothing else. Nor can
172 ;; a detached `new-session -d`: no client attaches, so no terminfo entry is
173 ;; ever loaded and every arm of such a test passes, including the ones
174 ;; meant to fail.
175 ;;
176 ;; WHERE THE TERMINFO ACTUALLY COMES FROM, stated exactly, because the
177 ;; comfortable version of this is wrong. ncurses is linked statically, so
178 ;; its `--with-default-terminfo-dir=${out}/share/terminfo` IS carried into
179 ;; 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 only
182 ;; during a build, so OUTSIDE the sandbox that compiled-in default resolves
183 ;; to nothing:
184 ;;
185 ;; TERMINFO_DIRS = the ncurses store item -> session runs, output captured
186 ;; 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 captured
190 ;;
191 ;; So the store's terminfo works and is load-bearing, and tmux CANNOT reach
192 ;; 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 list
194 ;; applied to the lock's ROOT packages only and never consults a package's
195 ;; own `search-paths:` -- so ncurses' terminfo, a DEPENDENCY's, is not on
196 ;; the list at all. That is a `sigil env` defect, not a packaging one, and
197 ;; it is tracked outside this repository. Recorded here so the next reader
198 ;; does not conclude from a working tmux that the store is what fed it.
199 (define tmux
200 (package
201 name: "tmux"
202 version: "3.7c"
203 source: (external-source
204 location: "https://github.com/tmux/tmux/releases/download/3.7c/tmux-3.7c.tar.gz"
205 sha256: "7c60cae9a0e25288e2e24750aafc9e8800fc7fd4555e447e1b29ee4201cfb3bf")
206 buildenv: core-buildenv
207 ;; Nothing here says how either dependency is laid out: ncurses exports
208 ;; `CPATH=include/ncursesw` itself, libevent needs no export because
209 ;; its headers sit directly in `include/`, and the builder contributes
210 ;; `-I`, `-L` and `PKG_CONFIG_PATH` for both. Order is not significant
211 ;; -- the engine sorts dependencies by name before sealing them, which
212 ;; is visible in any objects/<id>.sgl.
213 dependencies: (list ncurses libevent)
214 ;; byacc is here and NOT in `dependencies:` because nothing it produces
215 ;; is linked into tmux. configure demands a program called `yacc` on
216 ;; PATH and then never needs it, since the release tarball already
217 ;; ships `cmd-parse.c`; see (core packages build-tools) for the
218 ;; measurement.
219 build-inputs: (list byacc)
220 phases: (list
221 (external-phase
222 kind: 'configure
223 args: (list "--prefix=${out}"
224 ;; every other package in the channel is
225 ;; static. It puts `-static` on LDFLAGS and
226 ;; makes configure call pkg-config with
227 ;; `--static`. The pkg-config half buys
228 ;; nothing TODAY -- all four of libevent's
229 ;; `.pc` files have an empty `Libs.private:`,
230 ;; and `pkgconf --static --libs libevent_core`
231 ;; returns exactly what the non-static call
232 ;; does -- so `-static` is the whole of it.
233 "--enable-static"
234 ;; the default already, stated so the build
235 ;; does not depend on what configure happens
236 ;; to find probeable
237 "--with-TERM=screen"
238 ;; None of these are in the channel. Stated
239 ;; explicitly rather than left to the default,
240 ;; but be clear about what that buys: read
241 ;; against tmux 3.7c's configure.ac, ALL FIVE
242 ;; are opt-in and already off when unset
243 ;; (utf8proc and jemalloc probe automatically
244 ;; only on darwin), and each ERRORS rather
245 ;; than silently absorbing the host when asked
246 ;; for and missing. So these flags are
247 ;; documentation, not a hermeticity fix. The
248 ;; probe that genuinely DID leak the build
249 ;; environment into the output is the lock
250 ;; 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 one
257 ;; 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 configure
264 ;; reports `lock-command... vlock`, because the
265 ;; SEED ships a busybox `vlock` applet, and
266 ;; `-DTMUX_LOCK_CMD='"vlock"'` lands on every
267 ;; compile line. That is a value chosen by
268 ;; what happened to be visible in the build
269 ;; environment rather than by anything this
270 ;; package declares -- and `vlock` is not in
271 ;; tmux's runtime closure, so `lock-server`
272 ;; would shell out to a program that is not
273 ;; there.
274 ;;
275 ;; `ac_cv_prog_found_vlock=no` is autoconf's
276 ;; own documented override (AC_CHECK_PROG IS
277 ;; cached; `ac_cv_prog_found_vlock` appears in
278 ;; the generated configure, against a control
279 ;; showing 89 `ac_cv_prog_` variables there).
280 ;; Measured: it flips the result to
281 ;; `lock-command... lock -np`.
282 ;;
283 ;; HONEST ABOUT WHAT THIS FIXES: `lock -np` is
284 ;; not in the closure either. Neither default
285 ;; is runnable, and `lock-command` is a runtime
286 ;; server option anyone can set. What changes
287 ;; is that the compiled default now comes from
288 ;; tmux's own portable choice instead of from
289 ;; 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 of
297 ;; 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, in
302 ;; two different configurations, and confusing them is how you get a green
303 ;; 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 paths
307 ;; over the lock's ROOT packages. A foot that starts and draws in that
308 ;; configuration would have started and drawn before any of the runtime
309 ;; search-path work existed.
310 ;;
311 ;; THROUGH A DEPENDENCY is where it breaks. Run `tmux` inside foot and
312 ;; tmux needs the `screen` entry, which comes from `core/ncurses` -- a
313 ;; 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 second
316 ;; case, deliberately. See [[tasks/foot-packaging]].
317 ;;
318 ;; WHAT FOOT NEEDS FROM OUTSIDE THE STORE, stated because a reader will
319 ;; 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 wire
323 ;; 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. This
326 ;; is the one genuine host coupling in foot's runtime and nothing in
327 ;; 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. Setting
331 ;; it makes foot export `TERMINFO` into its child processes, pointing at
332 ;; its own store item -- which would be convenient and would also mask the
333 ;; dependency-path question this package exists to exercise. If it is ever
334 ;; set, the acceptance test above stops measuring what it claims to.
335 (define foot
336 (package
337 name: "foot"
338 version: "1.27.0"
339 source: (external-source
340 location: "https://codeberg.org/dnkl/foot/releases/download/1.27.0/foot-1.27.0.tar.gz"
341 sha256: "d1e1d04cd7a3a1615b4d8dbf9645b9a35cdc3f36fba4e1c0fd252ad0bd6651c8")
342 buildenv: core-buildenv
343 ;; freetype and zlib are not named by foot's own meson.build. They are
344 ;; reached through the `Requires:` chain of fcft.pc and fontconfig.pc,
345 ;; and this channel composes PKG_CONFIG_PATH from DIRECT dependencies
346 ;; only -- see the long note on `fontconfig`.
347 dependencies: (list fcft tllist pixman wayland wayland-protocols
348 libffi expat
349 libxkbcommon fontconfig freetype zlib)
350 ;; ncurses is a BUILD INPUT and not a dependency: `tic` runs during the
351 ;; build and nothing from ncurses is linked into foot or referenced by
352 ;; it at runtime. Declaring it as a dependency would put ncurses in
353 ;; foot's runtime closure and would ALSO quietly make the terminfo
354 ;; acceptance test pass for the wrong reason.
355 build-inputs: (list meson samurai python ncurses)
356 phases: (list
357 (external-phase
358 kind: 'run
359 args: (list "sh" "-c"
360 (string-append
361 "set -eu\n"
362 "meson setup build"
363 " --prefix=${out}"
364 " --buildtype=plain"
365 " --default-library=static"
366 ;; `-Dprefer_static=true` MAKES MESON ASK
367 ;; PKG-CONFIG THE STATIC QUESTION, and without it
368 ;; a fully static link fails at the last step.
369 ;;
370 ;; A `.pc` file splits its link line: `Libs:` is
371 ;; what a SHARED consumer needs, `Libs.private:`
372 ;; and `Requires.private:` are the extra
373 ;; transitive libraries a STATIC consumer needs.
374 ;; fontconfig lists expat under
375 ;; `Requires.private:`, so a default
376 ;; `dependency('fontconfig')` emits no `-lexpat`
377 ;; -- correct for a shared link, fatal here:
378 ;;
379 ;; ld.lld: error: undefined symbol: XML_ErrorString
380 ;; >>> referenced by fcxml.c
381 ;; >>> fcxml.o in archive libfontconfig.a
382 ;;
383 ;; The symbol is missing from the LINK, not from
384 ;; the closure: expat is declared, built, and
385 ;; present. Only the query was the wrong one.
386 ;; This is the same `Requires.private:` line that
387 ;; had to be satisfied at CONFIGURE time; it bites
388 ;; twice, in two different ways, and fixing the
389 ;; first does not fix the second.
390 " -Dprefer_static=true"
391 " -Ddocs=disabled"
392 ;; Matches fcft: no harfbuzz, no utf8proc. The
393 ;; two must agree -- grapheme clustering in foot
394 ;; without shaping in fcft is, in foot's own
395 ;; words, not useful.
396 " -Dgrapheme-clustering=disabled"
397 ;; utmp/wtmp accounting would want a setuid
398 ;; helper this channel has no way to install.
399 " -Dutmp-backend=none"
400 " -Dtests=false\n"
401 "echo FOOT-CONFIGURED\n")))
402 (external-phase
403 kind: 'run
404 args: (list "sh" "-c"
405 (string-append
406 "set -eu\n"
407 "meson compile -C build\n"
408 "echo FOOT-COMPILED\n")))
409 (external-phase
410 kind: 'run
411 args: (list "sh" "-c"
412 (string-append
413 "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, not
419 ;; assumed. `-Dterminfo` defaults to enabled but
420 ;; `find_program('tic')` is `required:` on that
421 ;; option -- so if the build input ever went
422 ;; missing, meson would DISABLE the feature and
423 ;; still succeed, and foot would install with no
424 ;; terminfo at all. That is a silent
425 ;; feature-loss of exactly the kind this channel
426 ;; keeps finding, so it fails here instead.
427 ;;
428 ;; `find -L` because terminfo directories are
429 ;; 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"))))))))