From 2e2ee12fcad656f33ca7cc96ec194861d6c4d583 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 5 Nov 2026 08:52:05 +0000 Subject: [PATCH 1/2] php: record why php-lua and php-tk are not on the extension list Asked directly, so written down rather than left to be re-asked. Neither is vendored and neither ships with php: external/lua and external/tk are the libraries, and php/ext has no lua or tk. Both would be PECL packages, and PECL extensions build as shared modules, which Plan 9 cannot load -- they would have to be linked statically and listed by hand. That part is mechanical and the port directory is already arranged for it; the problem is upstream. PECL tk is 0.1.1 beta dated 2004-11-14, and its own page says the package is unmaintained and invites a new owner. That is the PHP 4 extension API, predating zend_string, the PHP 7 zval rework, the arginfo requirements and the TSRMLS removal. Not an extension to enable: a rewrite, and a GUI binding additionally needs an event loop married to php's request lifecycle on top of our own tk/plan9 backend. PECL lua looks PHP 7-era; LuaSandbox is the one packaged for 8.0 through 8.5, which also means it is a different extension with a different API. Worth checking before any work whether it wants Lua 5.1, since it comes from MediaWiki's Scribunto and this tree ships 5.5.0. The note also states the distinction worth keeping: the bundled extensions this port wants all ship with 8.5, are tested by upstream against this version, and are turned on with a --with-X against a library we already have. The first is a flag; the second is a port. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01WGAwvvTwDg2yknFkmZ3qzs --- docs/notes/php.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/docs/notes/php.md b/docs/notes/php.md index 06f7527f6..2af204abe 100644 --- a/docs/notes/php.md +++ b/docs/notes/php.md @@ -295,11 +295,46 @@ Which maps to: `mbstring` (oniguruma), `openssl` (libressl), `curl`, `zlib`, `bz2`, `libxml`/`dom`/`simplexml`/`xml`/`xmlreader`/`xmlwriter`, `sqlite3` + `pdo_sqlite`, `zip`, `readline`, `gd` (png only), `intl`. +***THESE ARE A DIFFERENT RISK CLASS FROM ANYTHING ON PECL, AND THAT IS +THE DISTINCTION TO KEEP.*** Every one of them SHIPS WITH php 8.5, is +tested by upstream against this exact version, and is turned on with a +`--with-X` against a library this tree already has. A PECL package is +a separate source tree with its own maintenance state and its own +target php version. *The first is a flag; the second is a port.* + **`ext/pcre` bundles its own pcre2 and that is what the 29 objects are.** `--with-external-pcre` would use `sys/src/ape/lib/pcre2` instead; the bundled copy is the better starting point, because it is the version php was tested against and it removes a link dependency from the first build. +### php-lua and php-tk: a different risk class, and the answer is no for now + +Asked directly, so recorded rather than left to be re-asked. **Neither +is vendored and neither ships with php** -- `sys/src/external/lua` and +`external/tk` are the LIBRARIES, and `php/ext/` has no `lua` or `tk`. +Both would be PECL packages to bring in, and PECL extensions build as +shared modules by default, which Plan 9 cannot load at all; they would +have to be linked statically and listed in the mkfile and +`internal_functions_cli.c` by hand. *That part is mechanical and the +port directory is already arranged for it.* The problem is upstream. + +**`tk` is PECL 0.1.1 beta, dated 2004-11-14, and its own page says the +package is unmaintained and invites someone to take it over.** That is +the PHP 4 extension API: no `zend_string`, the PHP 7 zval rework, the +arginfo requirements and the TSRMLS removal all postdate it. This is +not an extension to enable, it is a rewrite -- *bigger than most of +what this tree has ported* -- and a GUI binding additionally needs an +event loop married to php's request lifecycle, on top of our own +`external/tk/plan9` backend. No. + +**`lua` is less clear and still not first.** The PECL `lua` package +looks PHP 7-era; **LuaSandbox** is the one actually packaged for PHP +8.0 through 8.5. *Which also means it is a different extension with a +different API*, and it is worth checking before any work whether it +wants Lua **5.1** -- it comes from MediaWiki's Scribunto, which +standardised there -- because this tree ships **5.5.0**. A version +mismatch would be its own port. + **No extension should be enabled in the first build.** The order that has worked in this tree is: get the thing to compile and print its own version, then add one library at a time. From 808a6db3f6e79fa4b0b037679fab77b10d32b3ca Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 5 Nov 2026 08:55:40 +0000 Subject: [PATCH 2/2] php: the mkfile's OFILES ended one line early; mkcont-sweep widened mk answered `mkfile:612: syntax error; expected one of :<='. The OFILES list ended at the last generated entry, and the internal_functions_cli.$O that was meant to be part of it sat tab-indented after a blank line and five comments -- an orphan logical line. It and the comments above it carry continuations now, and the comment says it is the one line in the list deliberately without one. mkcont-sweep reported 0 over that, before and after, because its check only fires when a COMMENT ends the continuation and here an ordinary entry did. The generalisation is the one mk itself uses and covers both: a logical line starting with a bare word is a syntax error wherever it came from, and an indented one is a syntax error unless it is a recipe -- which means the line above it had a `:'. Recipes are the whole difficulty, since every recipe body line is indented and has no `:' or `=', so a naive version reports the entire tree. The discriminator is what the last non-indented logical line was, and over all 253 mkfiles the widened sweep finds 0, which is the evidence that the discriminator is right. --control carries one of each shape and, in the second, a real recipe that must NOT be reported: a control without it would pass for a version that flags every mkfile in the tree. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01WGAwvvTwDg2yknFkmZ3qzs --- CLAUDE.md | 11 +++- sys/lib/tests/mkcont-sweep.py | 116 +++++++++++++++++++++++++++++++++- sys/src/ape/cmd/php/mkfile | 17 +++-- 3 files changed, 134 insertions(+), 10 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 02bea6d71..6cd359844 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -397,7 +397,16 @@ that the instrument notices.* nothing declares), `apehdr-sweep.py` (every `sys/include/ape` header standalone, plus a TOGETHER case for collisions only a combination can show), `rceq-sweep.py` (**gates**: a bare `=` in an mkfile recipe cannot -parse), `mkcont-sweep.py` (**gates**: a comment ending a continuation), +parse), `mkcont-sweep.py` (**gates**: a logical line mk cannot parse. TWO +shapes, and *the second was added after the first reported 0 over +it*: a comment ending a continuation, and an INDENTED line that is +not a recipe — `cmd/php/mkfile` ended its `OFILES` at an ordinary +entry and left a tab-indented one orphaned five comment lines later. +**Recipes are the whole difficulty**, since every recipe body is +indented and has no `:` or `=`, so the discriminator is what the last +non-indented logical line was; `--control` carries one of each shape +*and a real recipe*, because a control without one would pass for a +version that reports every mkfile), `ishadow-sweep.py` (does NOT gate — most hits are deliberate), `hosthelper-sweep.py` (**does NOT gate, on purpose**: which mkfile recipes run a binary the same mkfile builds, and whether it was built diff --git a/sys/lib/tests/mkcont-sweep.py b/sys/lib/tests/mkcont-sweep.py index 641acedd6..ba6357b8e 100644 --- a/sys/lib/tests/mkcont-sweep.py +++ b/sys/lib/tests/mkcont-sweep.py @@ -1,7 +1,9 @@ #!/usr/bin/env python3 """ -mkcont-sweep -- which mkfile has a COMMENT that silently ends a -continued assignment, leaving the rest of the list as a bare word? +mkcont-sweep -- which mkfile has a logical line mk cannot parse, +because a continuation ended where nobody meant it to? + +Two shapes, and the second was added after the first missed it. python3 sys/lib/tests/mkcont-sweep.py @@ -41,6 +43,28 @@ because mk accepts it. It is still wrong -- the dropped entries are silently not built -- but it has no symptom this can see, so this is a LOWER BOUND like every other sweep here. + +------------------------------------------------------------------ +SHAPE TWO: AN ORPHAN INDENTED LINE, AND THIS SWEEP REPORTED 0 OVER IT. + +`cmd/php/mkfile' ended its `OFILES' at the last generated entry and +then had a blank line, five comment lines and a tab-indented +`internal_functions_cli.$O' that was meant to be part of the list. mk +answered `mkfile:612: syntax error; expected one of :<=' -- and **the +check above never fired, because the continuation was ended by an +ordinary entry rather than by a comment.** + +The generalisation is the one mk itself uses, and it covers both: a +logical line that starts with a bare word -- no `:', `=' or leading +`<' -- is a syntax error WHEREVER it came from. An INDENTED one is a +syntax error unless it is a recipe, which means the rule above it had +a `:'. So this now also reports an indented logical line that follows +an ASSIGNMENT rather than a rule. + +*Recipes are the whole difficulty*: every recipe body line is indented +and has no `:' or `=', so a naive version of this check reports the +entire tree. The discriminator is what the last non-indented logical +line was. """ import os @@ -53,6 +77,37 @@ "report 0 findings from the wrong tree" % ROOT) +def logical(lines): + """yield (start_line_index, joined_text, first_raw) per logical line""" + i = 0 + while i < len(lines): + start = i + parts = [lines[i].rstrip()] + while parts[-1].endswith("\\") and i + 1 < len(lines): + i += 1 + parts.append(lines[i].rstrip()) + yield start, "\n".join(parts), lines[start] + i += 1 + + +def orphans(path): + """shape two: an indented logical line that is not a recipe""" + lines = open(path, encoding="latin1").read().split("\n") + out = [] + in_recipe = False + for start, text, raw in logical(lines): + bare = raw.strip() + if not bare or bare.startswith("#"): + continue # blank and comment end nothing, deliberately + if raw[:1] in (" ", "\t"): + if not in_recipe: + out.append((start + 1, bare)) + continue + # a non-indented logical line: does a recipe follow it? + in_recipe = ":" in text.split("\n")[0] + return out + + def check(path): lines = open(path, encoding="latin1").read().split("\n") out = [] @@ -73,7 +128,59 @@ def check(path): return out +CONTROL_ONE = """\ +OFILES=\\ +\ta.$O\\ +# a comment that forgets its backslash +\tb.$O\\ +\tc.$O +""" + +CONTROL_TWO = """\ +OFILES=\\ +\ta.$O\\ +\tb.$O + +# a comment block, and then an entry meant to be part of the list +\tc.$O + +install:V: +\techo this recipe must NOT be reported +""" + + +def control(): + """Both shapes, because a sweep that only proves it can say one + thing has not been shown to say the other. CONTROL_TWO carries a + real recipe as well: the discriminator between an orphan and a + recipe body is the whole difficulty, so a control that omits the + recipe would pass for a version that reports every mkfile.""" + import tempfile + ok = True + with tempfile.TemporaryDirectory() as d: + for name, body, fn, want in ( + ("one", CONTROL_ONE, lambda p: check(p), 1), + ("two", CONTROL_TWO, lambda p: orphans(p), 1)): + p = os.path.join(d, "mkfile") + open(p, "w").write(body) + got = len(fn(p)) + print("control %s: %d finding(s), want %d -- %s" + % (name, got, want, "ok" if got == want else "FAILED")) + if got != want: + ok = False + # and the recipe in CONTROL_TWO must not be among them + p = os.path.join(d, "mkfile") + open(p, "w").write(CONTROL_TWO) + for (ln, text) in orphans(p): + if "echo" in text: + print("control two: REPORTED A RECIPE BODY -- FAILED") + ok = False + return 0 if ok else 1 + + def main(): + if "--control" in sys.argv: + return control() findings = 0 for base in ("sys/src/ape", "sys/src/cmd", "sys/src/cmd2"): top = os.path.join(ROOT, base) @@ -90,6 +197,11 @@ def main(): print(" %s" % text.strip()[:68]) print(" :%d is then a bare word mk cannot parse:" % nl) print(" %s" % nxt.strip()[:68]) + for (ln, text) in orphans(p): + findings += 1 + print("%s:%d indented line that is not a recipe:" % + (os.path.relpath(p, ROOT), ln)) + print(" %s" % text[:68]) print() print("%d broken continuation(s)." % findings) if findings: diff --git a/sys/src/ape/cmd/php/mkfile b/sys/src/ape/cmd/php/mkfile index 2215169da..02073c71e 100644 --- a/sys/src/ape/cmd/php/mkfile +++ b/sys/src/ape/cmd/php/mkfile @@ -601,13 +601,16 @@ OFILES=\ sapi/cli/php_cli_process_title.$O\ sapi/cli/php_cli_server.$O\ sapi/cli/php_http_parser.$O\ - sapi/cli/ps_title.$O - -# `internal_functions_cli.c' is generated by configure -- it is the -# table of built-in extension entries -- and ours is beside this -# mkfile rather than in `external/php', so the vendored tree stays -# untouched. Same for `php_config.h', `zend_config.h', -# `timelib_config.h' and `build-defs.h'. + sapi/cli/ps_title.$O\ +# `internal_functions_cli.c' is generated by configure -- it is the\ +# table of built-in extension entries -- and ours is beside this\ +# mkfile rather than in `external/php', so the vendored tree stays\ +# untouched. Same for `php_config.h', `zend_config.h',\ +# `timelib_config.h' and `build-defs.h'. ***IT IS ALSO THE LAST\ +# ENTRY, AND THE ONLY LINE IN THIS LIST WITHOUT A TRAILING\ +# BACKSLASH*** -- the first version ended the list one line above and\ +# left this tab-indented line orphaned, which mk answered with\ +# `mkfile:612: syntax error; expected one of :<='.\ internal_functions_cli.$O # ***WITHOUT THIS LINE, EDITING `php_config.h' REBUILDS NOTHING.***