From d917798090a3ce4630ee230a28d1ead3bc0a9ed8 Mon Sep 17 00:00:00 2001 From: Nexoniarz Date: Sat, 12 Sep 2026 21:34:36 +0200 Subject: [PATCH] ports: ncurses, so zsh's line editor has a terminal to drive zsh's configure will happily build without a terminal library -- the probe is AC_SEARCH_LIBS, which is soft -- and produce a shell whose cursor cannot move and whose arrow keys do nothing. ZLE is the reason to want zsh, so this comes first. MIT, so it combines with Apache-2.0 without conditions. Built static, widec (which is what zsh looks for first, and the console's UTF-8 decoder can already take), and --disable-database --with-fallbacks so seven terminal descriptions are compiled into the library. SchOS has no business shipping /usr/share/terminfo, and with the fallbacks the .a is self-contained: verified that no terminfo path strings survive in the binary. Nothing reaches sysroot/ -- a static library is not a file the OS ships -- so `install` means into $(PREFIX). port.mk gains NCURSES beside MUSL for the same reason MUSL is there. tinfotest.c is the acceptance test, and asks the question the build cannot: not "did it link" but "does terminfo work on a SchOS console". It reports TIOCGWINSZ, initialises from the compiled-in fallback, and names every capability ZLE binds. Anything MISSING there is a missing feature in the shell later. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2 --- ports/Makefile | 7 ++- ports/mk/port.mk | 5 ++ ports/ncurses/Makefile | 115 ++++++++++++++++++++++++++++++++++++ ports/ncurses/tinfotest.c | 121 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 246 insertions(+), 2 deletions(-) create mode 100644 ports/ncurses/Makefile create mode 100644 ports/ncurses/tinfotest.c diff --git a/ports/Makefile b/ports/Makefile index 9bf7d04..ef1781b 100644 --- a/ports/Makefile +++ b/ports/Makefile @@ -5,6 +5,7 @@ # ports/musl/ libc — installs headers/libs to build/ports/musl/prefix # ports/mksh/ MirBSD Korn Shell — installed as sysroot/bin/sh # ports/toybox/ core utilities — installed as sysroot/bin/ +# ports/ncurses/ terminfo + screen library — link-time only, ships nothing # # A port directory holds only a Makefile and optional patches/. Everything # generated lives in build/ports// — see mk/port.mk for the contract. @@ -24,8 +25,10 @@ PORTS := $(filter-out $(NOTPORTS), \ all: $(PORTS) -mksh: musl -toybox: musl +mksh: musl +toybox: musl +fastfetch: musl +ncurses: musl $(PORTS): $(MAKE) -C $@ diff --git a/ports/mk/port.mk b/ports/mk/port.mk index c13ddd2..d1ece15 100644 --- a/ports/mk/port.mk +++ b/ports/mk/port.mk @@ -39,6 +39,11 @@ PREFIX := $(PORTDIR)/prefix # fixed location so ports don't need to know where musl's own source lives. MUSL := $(BUILDROOT)/musl/prefix +# ncurses is the second library other ports link against, so its prefix is +# fixed for the same reason musl's is. Unlike every other port it installs +# nothing into sysroot/ -- a static library is not a file the OS ships. +NCURSES := $(BUILDROOT)/ncurses/prefix + CC := clang # FS register belongs to the kernel; never let the compiler touch %fs directly. CFLAGS_BT := -fno-stack-protector diff --git a/ports/ncurses/Makefile b/ports/ncurses/Makefile new file mode 100644 index 0000000..85f67fc --- /dev/null +++ b/ports/ncurses/Makefile @@ -0,0 +1,115 @@ +# SPDX-License-Identifier: Apache-2.0 +# ncurses -- terminal capability database and screen library, MIT: +# https://invisible-island.net/ncurses/ +# +# Here because zsh's line editor is the reason to want zsh, and the line editor +# drives the terminal through tigetstr/tputs. zsh's configure will happily build +# without a terminal library -- AC_SEARCH_LIBS is soft -- and produce a shell +# whose cursor cannot move and whose arrow keys do nothing. So this comes first. +# +# Unlike every other port, nothing here reaches sysroot/. ncurses is built +# static and consumed at link time by zsh; `install` means "into $(PREFIX)". +# +# The awkward part of ncurses is that it is a library whose data is a database. +# Normally that database is ~4000 compiled terminal descriptions under +# /usr/share/terminfo, which SchOS has no business shipping. --disable-database +# plus --with-fallbacks compiles a chosen handful directly into the library, so +# the resulting .a is self-contained and the filesystem stays clean. + +include ../mk/port.mk + +NC_VERSION := 6.4 +NC_URL := https://ftp.gnu.org/gnu/ncurses/ncurses-$(NC_VERSION).tar.gz +NC_SHA256 := 6931283d9ac87c5073f30b6290c4c75f21632bb4fc3603ac8100812bed248159 + +TARBALL := $(DISTDIR)/ncurses-$(NC_VERSION).tar.gz + +# The terminal types compiled into the library. `linux` is what SchOS's own +# console is closest to and what TERM is set to; the rest are here so a serial +# console or an ssh client from a real machine still gets a working editor. +# Every name added is bytes in the binary, so this list is deliberately short. +NC_FALLBACKS := linux,vt100,vt102,xterm,xterm-256color,screen,dumb + +# Cross-compiling ncurses needs tic and infocmp *on the host* to turn those +# names into C. Both exist here; the paths are looked up rather than written +# down because on NixOS they live in the store and move on every update. +HOST_TIC ?= $(shell command -v tic) +HOST_INFOCMP ?= $(shell command -v infocmp) + +# gcc, for the same reason fastfetch uses it: -print-libgcc-file-name. +HOSTCC_NC ?= gcc +GCC_INC := $(shell $(HOSTCC_NC) -print-file-name=include) +LIBGCC := $(shell $(HOSTCC_NC) -print-libgcc-file-name) + +# Split exactly as autoconf consumes them: it links a test as +# $CC $CFLAGS $LDFLAGS conftest.c $LIBS +# so crt1.o/crti.o must ride in LDFLAGS (before the object) and libc in LIBS +# (after it). libc twice -- libgcc reaches back into it, and a static link +# resolves left to right. +NC_CPPFLAGS := -nostdinc -isystem $(MUSL)/include -isystem $(GCC_INC) -D__SCHOS__ +NC_CFLAGS := -O2 $(CFLAGS_BT) +NC_LDFLAGS := -static -nostdlib -L$(MUSL)/lib $(MUSL)/lib/crt1.o $(MUSL)/lib/crti.o +NC_LIBS := $(MUSL)/lib/libc.a $(LIBGCC) $(MUSL)/lib/libc.a $(MUSL)/lib/crtn.o + +# ncurses builds make_hash and make_keys to run during the build, not on the +# target. In a cross build those must be compiled for the host, with the host's +# own libc -- hence a second, completely separate set of flags. +NC_BUILD_CC := $(HOSTCC_NC) + +NC_CONFIGURE := \ + --host=x86_64-linux-musl \ + --build=x86_64-pc-linux-gnu \ + --prefix=$(PREFIX) \ + --disable-database \ + --with-fallbacks=$(NC_FALLBACKS) \ + --with-tic-path=$(HOST_TIC) \ + --with-infocmp-path=$(HOST_INFOCMP) \ + --without-shared --with-normal \ + --enable-widec \ + --enable-overwrite \ + --without-cxx --without-cxx-binding \ + --without-ada --without-tests --without-progs --without-manpages \ + --without-debug --without-profile \ + --disable-stripping \ + --disable-home-terminfo \ + --disable-rpath-hack + +$(TARBALL): | $(DISTDIR) + curl -fsSL -o $@ $(NC_URL) + sh $(SHA256CHECK) "$(NC_SHA256)" "$@" + +download: $(TARBALL) + +$(SRCDIR): $(TARBALL) + mkdir -p $(PORTDIR) + tar -xzf $< -C $(PORTDIR) + mv $(PORTDIR)/ncurses-$(NC_VERSION) $@ + +extract: $(SRCDIR) + +$(BUILDDIR)/config.status: | $(SRCDIR) + @test -n "$(HOST_TIC)" -a -n "$(HOST_INFOCMP)" || { \ + echo "ncurses: host tic/infocmp not found -- install ncurses on the build host" >&2; \ + exit 1; } + mkdir -p $(BUILDDIR) + cd $(BUILDDIR) && \ + CC="$(HOSTCC_NC)" \ + CPPFLAGS="$(NC_CPPFLAGS)" \ + CFLAGS="$(NC_CFLAGS)" \ + LDFLAGS="$(NC_LDFLAGS)" \ + LIBS="$(NC_LIBS)" \ + BUILD_CC="$(NC_BUILD_CC)" \ + BUILD_CPPFLAGS= BUILD_CFLAGS= BUILD_LDFLAGS= BUILD_LIBS= \ + $(SRCDIR)/configure $(NC_CONFIGURE) + +$(PREFIX)/lib/libncursesw.a: $(BUILDDIR)/config.status + $(MAKE) -C $(BUILDDIR) + $(MAKE) -C $(BUILDDIR) install + +build install: $(PREFIX)/lib/libncursesw.a + +clean: + rm -rf $(PORTDIR) + +distclean: clean + rm -f $(TARBALL) diff --git a/ports/ncurses/tinfotest.c b/ports/ncurses/tinfotest.c new file mode 100644 index 0000000..f0c9812 --- /dev/null +++ b/ports/ncurses/tinfotest.c @@ -0,0 +1,121 @@ +/* SPDX-License-Identifier: Apache-2.0 + * + * ncurses acceptance test for SchOS. + * + * The question this answers is not "did the library compile" -- that much the + * build already proved -- but "does terminfo work on a SchOS console". Those + * are different questions, because ncurses can link, initialise, and then + * return null for every capability it is asked for, at which point a line + * editor draws nothing and blames itself. + * + * So: initialise against the compiled-in fallback, then ask for exactly the + * capabilities zsh's ZLE needs, and print what came back. Anything reported + * MISSING here will be a missing feature in the shell later. + */ +#include +#include +#include +#include +#include +#include +#include + +static int failures; + +/* Capability present and non-empty, or a named failure. The distinction + * matters: tigetstr returns (char *)-1 for "no such capability in this + * terminal's description" and NULL for "not in the database at all", and + * only the second means the port is broken. */ +static void +cap(const char *name, const char *what) +{ + char *v = tigetstr((char *)name); + if (v == (char *)-1) { + printf(" %-8s %-22s ABSENT (not in this terminal type)\n", name, what); + } else if (v == NULL) { + printf(" %-8s %-22s MISSING\n", name, what); + failures++; + } else { + printf(" %-8s %-22s ok (%d bytes)\n", name, what, (int)strlen(v)); + } +} + +int +main(void) +{ + struct winsize ws; + const char *term = getenv("TERM"); + int err = 0; + + printf("ncurses %s on SchOS\n\n", curses_version()); + + /* The kernel's idea of the screen, which is what everything lays out + * against. A hardcoded 80x24 here used to be a kernel bug; print the + * numbers so a regression is visible rather than merely wrong. */ + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0) + printf("TIOCGWINSZ %d cols x %d rows (%dx%d px)\n", + ws.ws_col, ws.ws_row, ws.ws_xpixel, ws.ws_ypixel); + else { + printf("TIOCGWINSZ FAILED\n"); + failures++; + } + + if (!term) { + printf("TERM unset -- falling back to \"linux\"\n"); + term = "linux"; + } else { + printf("TERM %s\n", term); + } + + /* The whole point of --disable-database: this must succeed with no + * /usr/share/terminfo anywhere on the system. */ + if (setupterm((char *)term, STDOUT_FILENO, &err) != OK) { + printf("\nsetupterm failed (err=%d) -- no compiled-in fallback for " + "\"%s\"\n", err, term); + return 1; + } + printf("setupterm ok, from compiled-in fallback (no terminfo files)\n"); + printf(" %s\n\n", longname()); + + printf("capabilities a line editor needs:\n"); + cap("cup", "absolute cursor move"); + cap("cuu1", "cursor up"); + cap("cud1", "cursor down"); + cap("cub1", "cursor left"); + cap("cuf1", "cursor right"); + cap("el", "erase to end of line"); + cap("ed", "erase to end of screen"); + cap("clear", "clear screen"); + cap("bold", "bold"); + cap("sgr0", "reset attributes"); + cap("setaf", "set foreground colour"); + + printf("\nkeys ZLE binds:\n"); + cap("kcuu1", "up arrow"); + cap("kcud1", "down arrow"); + cap("kcub1", "left arrow"); + cap("kcuf1", "right arrow"); + cap("khome", "home"); + cap("kend", "end"); + cap("kdch1", "delete"); + + printf("\nnumbers:\n"); + printf(" colors %d\n", tigetnum((char *)"colors")); + printf(" cols %d\n", tigetnum((char *)"cols")); + printf(" lines %d\n", tigetnum((char *)"lines")); + + /* Finally, actually drive the terminal. Everything above only read the + * description; this proves the escape sequences reach the console and + * that its ANSI parser accepts what ncurses emits. */ + printf("\noutput test: "); + fflush(stdout); + char *bold = tigetstr((char *)"bold"), *sgr0 = tigetstr((char *)"sgr0"); + if (bold && bold != (char *)-1 && sgr0 && sgr0 != (char *)-1) { + putp(bold); + printf("this should be bold"); + putp(sgr0); + fflush(stdout); + } + printf("\n\n%s\n", failures ? "FAIL" : "PASS"); + return failures ? 1 : 0; +}