Compare commits
34 Commits
f3463f0b52
...
c0412a5660
103 changed files with 8379 additions and 2098 deletions
@ -1,46 +0,0 @@
|
||||
# G-106 powershell console-mode fallback: maintained raw-mode path for twapi-less runtimes |
||||
|
||||
Status: proposed |
||||
Scope: src/modules/punk/console-999999.0a1.0.tm (enableRaw_powershell/disableRaw_powershell + the persistent server lifecycle), scriptlib/utils/pwsh/ (consolemode_server_async.ps1 canonical + experiment-variant reconciliation), ps-script resolution/packaging across launch contexts (argv0-derived pstooldir; kits via the G-089 scriptlib-in-kits interplay), src/make.tcl shell (the verified launch context) |
||||
Goal: the powershell console-mode fallback - a persistent pwsh/powershell named-pipe server driven by punk::console when twapi is absent - is a MAINTAINED, dependable raw-mode path for twapi-less runtimes (the suite-built shells and kits, until/unless zig-built twapi lands): quiet in normal operation, reliable across the session lifecycle, and resolvable from every supported launch shape rather than only argv0-relative luck. |
||||
Acceptance: on a twapi-less suite runtime, raw enable/disable work via the fallback from (a) the make.tcl shell launch and (b) a kit / plain-tclsh repl launch, with no stderr noise in normal operation; the server lifecycle is verified - starts once per session, stays up for the session's raw transitions (the 2026-07-20 observed early-shutdown mode diagnosed and fixed), and shuts down with the session leaving no orphan pwsh processes; ps-script resolution no longer depends solely on argv0 parent-dir derivation (works from kits and unusual cwds, with the fallback-to-pwd branch replaced by something principled); scriptlib/utils/pwsh is reconciled to one canonical server script with the experiment variants retired or explicitly labelled; the fallback's role as the no-twapi contingency is documented where G-103's twapi investigation will find it, and a repeatable verification recipe is recorded in this file. |
||||
|
||||
## Context |
||||
|
||||
User verification 2026-07-20 on the freshly built tclsh90spr (piperepl |
||||
variant from G-096, achieved 2026-07-20): with `tcl::tm::add .../modules` + `package require repl; repl::init; |
||||
repl::start stdin`, basic punk features are functional on the bare suite runtime - |
||||
except raw mode, which is twapi-reliant. Launched instead via |
||||
`./tclsh90spr .../src/make.tcl shell`, the powershell fallback engaged and raw mode |
||||
WORKED - modulo consolemode_server_async.ps1 noise and an early server shutdown. |
||||
That working-but-rough state is what this goal hardens. User direction: "we would |
||||
like to look at maintaining this powershell fallback - especially if we can't get |
||||
zig-based twapi builds going." |
||||
|
||||
Mechanism today (punk::console): when twapi is absent, enableRaw/disableRaw are |
||||
aliased to *_powershell variants that talk over a named pipe to a persistent |
||||
`consolemode_server_async.ps1` process (templated per-console id; pwsh.exe probed |
||||
first, then powershell.exe). Known weak points at goal creation: |
||||
|
||||
- Resolution: pstooldir = argv0's grandparent + /scriptlib/utils/pwsh, else [pwd] - |
||||
works for `make.tcl shell` (argv0 = src/make.tcl -> repo root) and little else by |
||||
construction. Kits need the script carried/resolved properly (G-089 scriptlib in |
||||
kits). |
||||
- Lifecycle: observed early shutdown of the server mid-session; startup noise on |
||||
stderr ("twapi not present, using persistent powershell process: ..." plus ps1 |
||||
output). |
||||
- Hygiene: scriptlib/utils/pwsh holds several sibling experiments |
||||
(consolemode_server.ps1, *_async1.ps1, *_async.2ps1, consolemode.ps1, |
||||
consolemode_enableraw.ps1) with no marking of which is canonical. |
||||
|
||||
Relationships: G-103 (bi-kit twapi under the zig-only policy is |
||||
needs-investigation - THIS fallback is the mitigation; if zig-built twapi proves |
||||
impractical the fallback becomes the primary raw path on suite runtimes and this |
||||
goal's priority rises), G-013 (raw mode as the repl default ultimately requires raw |
||||
mode to be dependable on twapi-less runtimes), G-089 (scriptlib in kits - the |
||||
delivery vehicle for the ps script), G-061-era console-context findings (agent |
||||
console-context traps around attached consoles apply to verifying this). |
||||
|
||||
## Notes |
||||
|
||||
(none yet) |
||||
@ -0,0 +1,31 @@
|
||||
# G-113 make.tcl colour output is terminal-aware: SGR only on interactive tty, off when piped |
||||
|
||||
Status: proposed |
||||
Scope: src/make.tcl (startup colour policy, define_global_ansi), src/AGENTS.md (invocation guidance), src/tests/shell/testsuites/punkexe/ (piped-output characterization if a suite proves feasible); layout make.tcl copies via established sync channels |
||||
Goal: make.tcl emits ANSI SGR colour only when its output goes to an interactive terminal. Piped/redirected runs (agent harnesses, CI, log capture) produce SGR-free output with no caller action required; interactive terminal runs keep full colour by default; explicit env overrides work in both directions. |
||||
Acceptance: With NO_COLOR unset, representative informational and build subcommands (at minimum check, help, workflow, and one build command such as modules) run with stdout redirected to a file produce zero ESC (0x1B) bytes on stdout and stderr, verified on tclsh 9.x and a repo punk kit. The same commands on a real interactive terminal retain colour (manual verification, Windows console plus one unix tty). NO_COLOR=1 still suppresses all colour; a documented force-colour override re-enables colour on piped output. The Tcl 8.6 probe-fallback policy is documented and behaves as decided. The interim src/AGENTS.md NO_COLOR bullet is revised to describe the implemented behaviour. |
||||
|
||||
## Context |
||||
|
||||
Agents drive `tclsh src/make.tcl ...` with captured/piped output on every build-task closeout; today that output carries raw SGR sequences unless the caller remembered NO_COLOR=1, which no AGENTS doc mentioned before the interim bullet (see Notes). Humans at a terminal want the colour. The single existing switch (::punk::console::colour_disabled, set at make.tcl startup from NO_COLOR) is honoured dynamically by punk::ansi a+/a and by the define_global_ansi fallback branch, so auto-detection only needs to set that same variable from a tty probe at startup - no downstream changes. |
||||
|
||||
## Approach |
||||
|
||||
- Startup policy in make.tcl, evaluated before any output: NO_COLOR set -> colour off; force-colour env (name settled in the work; FORCE_COLOR is the de-facto standard) -> colour on; otherwise probe stdout and enable colour only when it is a terminal. |
||||
- Probe technique: channel-config key presence on stdout, mirroring the stdin_is_interactive precedent (chan configure key test; no package needed, core Tcl only). |
||||
- Key the decision on stdout (primary output channel); stderr colour follows the same switch for simplicity - agents capture both anyway, and the force override covers the user-redirecting-stdout case. |
||||
- Tcl 8.6 has no reliable probe (see make.tcl:316 comment); fail direction for colour is OFF (safe for agents), unlike stdin's ON. Document; reconsider only if 8.6 interactive colour matters in practice. |
||||
- Out of scope: the REPL launched by `make.tcl shell` (shell colour is the shell's own concern - colour on/off + NO_COLOR there). |
||||
|
||||
## Alternatives considered |
||||
|
||||
- Guidance-only (agents always set NO_COLOR=1) - rejected as the sole fix: relies on every harness remembering; kept as the documented explicit override and landed as interim guidance. |
||||
- Post-hoc SGR stripping in harnesses - rejected: pushes the burden onto every consumer. |
||||
- Per-channel colour policy (stderr coloured when it alone is a tty) - more faithful but more state; default is the stdout-keyed single switch, settled in the work. |
||||
|
||||
## Notes |
||||
|
||||
- 2026-07-22: interim src/AGENTS.md Work Guidance bullet landed (agents set NO_COLOR=1 for captured make.tcl runs) ahead of this goal's implementation; revise it to describe the auto-detection + overrides when this goal lands (tracked in Acceptance). |
||||
- Existing machinery: NO_COLOR -> colour_disabled (make.tcl:5-7); punk::ansi honours colour_disabled dynamically with a -forcecolour in-module override (src/modules/punk/ansi-999999.0a1.0.tm); PUNKBOOT_PLAIN is the env-switch precedent (make.tcl:1655); stdin probe precedent at make.tcl:319. |
||||
- Related: G-030 (achieved - prompt-free flags + the stdin probe this mirrors), G-011 (console stderr semantics - loose relation), G-104/G-112 (both touch make.tcl subcommand surfaces - coordinate if either goes active first). |
||||
- make.tcl interface is product surface (root AGENTS.md): this behaviour change ships with a punkproject.toml bump (patch) + CHANGELOG entry. |
||||
@ -0,0 +1,51 @@
|
||||
# G-114 Per-platform tm module roots: platform-segregated binary .tm via tcl::tm::path |
||||
|
||||
Status: proposed |
||||
Scope: src/vfs/_config/punk_main.tcl + src/vfs/_config/project_main.tcl + src/make.tcl (boot tm-path wiring), src/modules/punk/platform-999999.0a1.0.tm (canon names, as consumer), new per-platform tm root trees (layout/naming decision - sibling roots beside src/vendormodules_tclX / src/modules_tclX), src/project_layouts (layout seeding), modpod demonstration artifact, tree READMEs; coordinates with G-109 (manifest target-platform field stays that goal's item) |
||||
Goal: binary-bearing .tm modules (modpods) are distributed ONE PLATFORM PER FILE and installed under per-platform TM ROOTS that the boot registers only for the running platform (canonical punk::platform names) - so no consumer downloads other platforms' binaries, a user can drop in their platform's copy of a same-name-version module without same-version shadowing, and wrong-platform .tm files present on a shared or synced tree are never registered or loaded. |
||||
Acceptance: the root layout + naming decision is recorded in this file (including the structural constraint that platform dirs cannot nest inside an existing tm root - tm subdirectories are namespace components - so per-platform roots are sibling trees); the boot (punk_main.tcl, project_main.tcl, make.tcl) registers exactly the running platform's root(s), verified on the tcl9 kit and an 8.6 shell (or the 8.6 limitation recorded); a demonstration binary modpod .tm placed in the win32-x86_64 root loads via package require on windows while a same-name-version copy placed in a foreign platform's root is demonstrably not registered; the same-version drop-in-replacement scenario (the fat-tm objection) is demonstrated working across platform roots; generated-project layouts seed the structure; tree READMEs and punk::platform docs updated. |
||||
|
||||
## Context |
||||
|
||||
Drafted 2026-07-22 from the platform-canon survey discussion (user-approved |
||||
wording). Motivating evidence is the user's prior fat-tm experience, recorded |
||||
in G-109 Notes the same day: a multi-platform thread .tm proved unworkable - |
||||
(a) downloads far larger than any one consumer needs, and (b) same-version |
||||
shadowing: tm handling resolves one name-version to ONE file with no arch |
||||
awareness, so a user cannot drop in a replacement .tm carrying their platform |
||||
when a same-version copy is already on the path. Platform segregation for |
||||
binary .tm therefore has to ride tcl::tm::path - per-platform tm roots |
||||
selected by the boot for the running platform. |
||||
|
||||
Structural constraint driving the layout decision: platform directories |
||||
canNOT nest inside an existing tm root, because a subdirectory of a tm path |
||||
is a NAMESPACE component (foo/bar-1.0.tm = package foo::bar - see |
||||
src/vendormodules_tcl8/tdbc/sqlite3-1.1.5.tm = tdbc::sqlite3 for the |
||||
legitimate use of subdirs). Per-platform roots must therefore be SIBLING |
||||
trees registered separately (candidate shapes: vendormodules_tcl<N>-<platform>/ |
||||
siblings, or a byplatform/<platform>/ parent) - the naming decision is this |
||||
goal's first item. |
||||
|
||||
Division of labour with G-109 (libunknown manifest-declared multi-name |
||||
discovery): this goal owns the roots, the boot wiring and the demonstration; |
||||
G-109 owns the manifest format, where a declared target-platform field would |
||||
additionally let libunknown skip wrong-platform binary .tm even when someone |
||||
shares a path (defensive extra recorded in G-109 Notes 2026-07-22). |
||||
|
||||
Related: G-066 (modpod generation - the demonstration artifact's tooling; |
||||
self-mounting binary modpods characterized working there), G-110 (the binary |
||||
inside a .tm loads via the extraction path investigated there), G-034 (8.6 |
||||
modpod mount path - bears on the 8.6 leg of acceptance), punk::platform |
||||
(canonical platform names; 'help platforms'). |
||||
|
||||
Provenance boundary (2026-07-22 user framing, G-004): the binary .tm these |
||||
roots hold are NEVER checked into punkshell - they arrive via the G-067 |
||||
artifact channel (punkbin library-class artifacts, consent-gated) or as local |
||||
uncommitted drop-ins, and the same-version drop-in acceptance below is the |
||||
tm-side expression of G-004's binaries-must-still-operate-in-tree |
||||
requirement. Derived projects may commit them under their own binary policy. |
||||
|
||||
Nothing needs this until binary-bearing .tm distribution starts in earnest - |
||||
creating empty tm-root siblings ahead of that machinery would be speculative |
||||
structure (2026-07-22 assessment). The goal exists so the layout and boot |
||||
work land deliberately when G-109-era binary tms arrive, not ad hoc. |
||||
@ -0,0 +1,45 @@
|
||||
# G-115 Declarative .vfs composition: toml-defined kit payloads with drop-in preservation |
||||
|
||||
Status: proposed |
||||
Scope: src/make.tcl (vfs assembly), src/runtime/vendorlib_vfs.toml (existing per-package declaration surface - fold/supersede settled in the work), src/vfs/ (per-.vfs declaration files + README), punk::mix machinery as touched, src/project_layouts (seeding for derived projects); coordinates with G-067 (artifact sources), G-006 (consent), G-004 (binary-free committed tree) |
||||
Goal: a .vfs folder's payload can be DECLARED in a toml definition (sources: suite build products, punkbin runtime/library artifacts via the consent-gated channels, vendor trees) and materialized into the folder by the build - while the folder remains the operative assembly area: undeclared dropped-in files (binary libs/modules included) are preserved with documented precedence, so derived projects with permissive binary policies and gitignored test .vfs folders keep drop-in simplicity with no declaration required. |
||||
Acceptance: a punkshell kit .vfs (or demonstration .vfs) builds from a toml declaration reproducing its payload on a clean tree, with declared binary content arriving via consented retrieval or local build products; an undeclared dropped-in file survives re-materialization per the documented precedence; a .vfs with NO declaration builds exactly as today (pure drop-in mode unchanged); the declaration format and precedence rules are documented; the vendorlib_vfs.toml relationship is settled with rationale; the layout store seeds the convention for derived projects. |
||||
|
||||
## Context |
||||
|
||||
Drafted 2026-07-22 (user-approved wording) from the binary-policy framing |
||||
discussion: with G-004 removing checked-in binaries from punkshell, the kit |
||||
.vfs payloads' binary content must come from SOMEWHERE reproducible - suite |
||||
build products (G-103-class batteries and library builds), punkbin artifact |
||||
classes retrieved through the G-006/G-067 consent gates, or vendor trees. |
||||
A per-.vfs toml declaration is the glue that makes a kit payload buildable |
||||
on a clean binary-free tree, while the answer to "can we still just drop a |
||||
dll in?" must stay YES - the folder remains operative, declarations are |
||||
optional, and undeclared drop-ins are preserved (the operate-in-tree |
||||
requirement recorded in G-004 Notes 2026-07-22: local experimentation and |
||||
permissive derived projects are first-class forever). |
||||
|
||||
Precedents already in the tree that make this credible: |
||||
|
||||
- src/runtime/vendorlib_vfs.toml - per-package per-kit declarations ALREADY |
||||
drive vendorlib-to-vfs inclusion (with superseded-version removal); this |
||||
goal generalizes that surface (fold or supersede - settled in the work). |
||||
- layout_materialize (G-087, achieved) - the declarative-plus-folder hybrid |
||||
with overlay/anti mechanics is proven machinery in this codebase. |
||||
- tomlish is vendored, and toml is the accepted format for punkshell-context |
||||
configuration (the 2026-07-20 toml drop applies only to dependency-free |
||||
BUILDSUITE BOOTSTRAP configs - recorded in G-103 Context; achieved |
||||
2026-07-22, archived at goals/archive/G-103-runtime-kit-family.md). |
||||
|
||||
Relationship to the punkbin third-class question (G-004 Notes 2026-07-22): |
||||
declarative composition pulling per-package artifacts is the |
||||
preferred-assessment alternative to storing whole zipped binary .vfs |
||||
archives - if this goal lands, the third class likely stays unnecessary |
||||
except as a release-snapshot convenience. |
||||
|
||||
Precedence design (the drop-in guarantee) is the core design work: |
||||
materialization must never clobber an undeclared file, and the documented |
||||
rules must state what happens when a declaration and a drop-in collide on |
||||
the same path (drop-in wins vs declared wins vs error - decided in the work, |
||||
with punkcheck-style tracking of materialized content the likely mechanism |
||||
for telling the two apart). |
||||
@ -0,0 +1,43 @@
|
||||
# G-116 Suite-built tcltls with a zig-built crypto backend: bi-family battery, prebuilt replacement path |
||||
|
||||
Status: proposed |
||||
Scope: src/buildsuites/suite_tcl90/ (build_tcltls module + crypto-backend build, sources.config + build.zig.zon source records, kit-family bi payload + metadata extension, test_gate record step), src/vfs kit payloads carrying vendored tcltls binaries (current-state reference + recorded disposition only - removal stays G-004-era work), punkbin (as eventual artifact destination via the G-067 library class); consumers punk::imap4 / punk::netbox (package require tls) as verification context |
||||
Goal: suite_tcl90 builds tcltls (core.tcl-lang.org/tcltls fossil, the thread/tclvfs/tk fetch pattern) together with its crypto backend (LibreSSL-portable or no-asm OpenSSL - decided and recorded in this file) entirely under the zig-only toolchain policy, statically linked so the resulting tcl9 tls extension is self-contained; it joins the bi-family attached payload as the first battery beyond Tk, loads via package require tls from a bi family kit, and its own testsuite runs under the suite-built shell through the G-107 machinery - replacing the need for the vendored prebuilt tcltls binaries and advancing G-004. |
||||
Acceptance: the crypto-backend choice (LibreSSL-portable vs no-asm OpenSSL) is recorded here with rationale and pinned source records in BOTH suite flows (sources.config + build.zig.zon); a suite step builds backend + tcltls reproducibly and the extension loads in the suite shell AND in a rebuilt bi family kit (package require tls plus a functional handshake: a loopback connection to an ephemeral local TLS server using a generated self-signed certificate - no external network); tcltls's own tests/ suite runs as a record-mode test_gate step emitting the G-107 evidence summary (gate promotion per the standard disposition-plus-two-run-deterministic bar, recorded either way); the bi kit-family payload and artifact metadata carry the tls version and the backend name+version; the disposition of the existing vendored tcltls kit payloads (tcltls1.7.22/1.7.23, tls2.0b2, punk9linux tcltls.so) is recorded here - they REMAIN until user-directed removal, actual removal being G-004-era work outside this goal. |
||||
|
||||
## Context |
||||
|
||||
Drafted 2026-07-22 (user-approved wording) to give the tcltls bi-battery line a |
||||
live goal home after G-103's archive (achieved 2026-07-22 - see |
||||
goals/archive/G-103-runtime-kit-family.md, whose Notes carry the original |
||||
2026-07-21 material this goal formalizes): |
||||
|
||||
- DECISION 2026-07-21 (user, recorded in archived G-103): tcltls approved into |
||||
the bi-battery enumeration - deliberately NOT gating family progress; a |
||||
dedicated goal was named as the option "if the crypto build proves large" - |
||||
this is that goal, drafted at the natural point rather than under pressure. |
||||
- FEASIBILITY (the crux, unchanged from the archived analysis): tcltls itself |
||||
is a thin C extension (tls.c/tlsIO.c compile under zig cc like |
||||
thread/tclvfs/tk) but LINKS a crypto backend - upstream OpenSSL, LibreSSL |
||||
also supported. No crypto library is in the tree, so the real work is the |
||||
backend build under the zig-only policy: pragmatic paths are |
||||
LibreSSL-portable (autoconf/cmake, no perl Configure) or a no-asm OpenSSL, |
||||
compiled with zig cc, with tcltls linked STATIC against it (mirroring the |
||||
tcllibc/tk build shape). This is the largest new build dependency the family |
||||
takes on - hence its own goal with its own acceptance. |
||||
- TEST VALUE (archived G-103 observation): zig-built crypto + tcltls against a |
||||
static tclsh is a combination nobody upstream tests - the record-mode run is |
||||
evidence of exactly the kind G-107 (achieved - see |
||||
goals/archive/G-107-buildsuite-library-tests.md) was built to capture. |
||||
- INTERIM STATE: TLS ships today ONLY as vendored prebuilt binaries in kit |
||||
.vfs payloads (tcltls1.7.22/1.7.23 + tls2.0b2 dlls; punk9linux tcltls.so) - |
||||
committed binaries G-004 wants gone, tolerated pending their build story. |
||||
Real consumers exist now: punk::imap4 and punk::netbox both |
||||
'package require tls'. |
||||
|
||||
Relationships: G-004 (a suite-built tls is the prebuilts' replacement path), |
||||
G-067 (once built, per-platform tls artifacts are library-class punkbin |
||||
candidates), G-105 (tls joins the family x target matrix when cross-targets |
||||
land), G-107 (achieved - test machinery this goal's record step uses), |
||||
archived G-103 (family payload contract and metadata shape this battery |
||||
extends). |
||||
@ -0,0 +1,79 @@
|
||||
# G-117 Self-describing family runtimes: embedded artifact record + metadata schema v1 |
||||
|
||||
Status: proposed |
||||
Scope: src/buildsuites/suite_tcl90/ (kit-family staging embeds the record; family_artifacts.tcl schema v1 fields + emission ordering; -Doriginurl/-Dpackager options), tools/family_check.tcl (embedded-record verification), src/scriptapps/bin/punk-runtime.* + bin/punk-runtime.cmd via rewrap ('info' action; schema-tolerant parsing), punkbin AGENTS.md (record relationship + schema/field documentation) |
||||
Goal: every family runtime carries a copy of its artifact metadata record INSIDE the attached image (e.g /app/punkbin-artifact.toml, written at kit-family staging time - necessarily WITHOUT the final sha1, which is computed over the finished binary and lives only in the sidecar toml + sha1sums), so a runtime separated from its sidecar remains identifiable via a punk-runtime 'info' action (embedded record and sidecar shown side by side, disagreements flagged); AND the metadata record is hardened as SCHEMA v1: schema (format version), build_id (uuid stamped in embedded AND sidecar - the offline correlation key for renamed copies), origin (canonical artifact repo the artifact was built FOR - mirrors preserve it; -Doriginurl, default punkbin), packager (declared identity; -Dpackager > env > git identity > unrecorded), project/project_url, license summary, build_host_platform - so records circulating on punkbin-compatible repos from different packagers stay interpretable and attributable. |
||||
Acceptance: kit-family embeds the record in all three members with the embed-then-hash ordering documented (embedded copy carries the v1 fields but no self-sha1; the sidecar + sha1sums remain the integrity authority); family_check verifies the embedded record exists and its variant/patchlevel/battery fields MATCH the probed facts for each member; 'punk-runtime info <name>' reports identity for (a) a fetched artifact with its sidecar and (b) a bare RENAMED copy with no sidecar - the embedded-read mechanism is decided and documented in the work (zip-central-directory read of the exe-appended archive by the host payload where available vs a cooperative probe that executes the target with a generated script, execution caveat stated) - and flags embedded-vs-sidecar disagreement; the v1 field set is documented in punkbin AGENTS.md with each field's semantics (notably origin's built-for-not-published-on meaning and packager's declarative-not-verified status, with minisign sidecars noted as the verification complement); family_artifacts emits all v1 fields with build_id present and IDENTICAL in embedded and sidecar copies, and the emitted records carry brief '#' comment lines documenting the non-obvious fields (toml-spec comments, ignored by the existing line-based consumers); punk-runtime metadata parsing tolerates unknown fields and absent schema (pre-v1 r1 records keep working); both payloads in parity with the roundtrip pin green; the next family emission after this goal lands is r2 carrying the full v1 record (r1 stays immutable as published). |
||||
|
||||
## Context |
||||
|
||||
Drafted 2026-07-22 (user-approved wording) from two exchanges during the G-103 |
||||
publication session: |
||||
|
||||
MOTIVATION (embedding): the published family artifacts are metadata-carrying |
||||
via SIDECAR tomls only - nothing is stamped inside the executables (a |
||||
deliberate G-103 naming-decision property: the metadata record is |
||||
authoritative, the filename identifies, the binary stays unstamped). The |
||||
implication: an exe separated from its toml (out-of-band copy, rename) is |
||||
identifiable only by executing probes against it or sha1-matching it back to |
||||
a repo's sha1sums. Embedding a COPY of the record in the attached image makes |
||||
runtimes self-describing without changing the authority model - the sidecar + |
||||
sha1sums remain the integrity authority; the embedded copy is a convenience |
||||
duplicate that cannot contain its own final sha1 (embed at staging -> wrap -> |
||||
hash -> sidecar; no circularity). |
||||
|
||||
SCHEMA v1 (user-floated fields, analysed 2026-07-22): |
||||
|
||||
- origin: the user's "repository url it was first published on" idea, with a |
||||
semantic reframe dissolving the timing problem they spotted (emission |
||||
precedes publication; publish-time sidecar mutation would permanently |
||||
diverge embedded vs sidecar - the exact disagreement 'info' flags): origin |
||||
= the canonical artifact repo the artifact was BUILT FOR, known at emission |
||||
(-Doriginurl, default punkbin), truthful regardless of publish timing, and |
||||
preserved by mirrors - an artifact found on a third-party repo still |
||||
declares its home. Where-it-actually-lives is the hosting repo's own |
||||
business (a repo-level identity file beside platforms.txt/defaults.txt is a |
||||
separate later idea). |
||||
- packager: declared identity for multi-packager punkbin-compatible |
||||
ecosystems (-Dpackager > env > the building checkout's git identity > |
||||
"unrecorded"). DECLARATIVE, not proof - the verifiable layer is signing, |
||||
with punkbin's existing minisign practice (zig tool archives) as precedent; |
||||
per-artifact .minisig sidecars are the natural future complement, |
||||
deliberately outside the metadata file (a signature cannot live inside |
||||
what it signs). |
||||
- schema: format-version field - the most important future-proofing addition; |
||||
nearly free now, impossible to retrofit onto records already circulating. |
||||
- build_id: uuid stamped in BOTH embedded and sidecar copies - the offline |
||||
correlation key that re-joins a renamed stray exe to its record without |
||||
content hashing. |
||||
- license summary + project/project_url: distribution-platform eligibility |
||||
(the G-004 motivation) asks licensing first; the project pointer makes a |
||||
stray artifact self-explaining beyond its origin repo. Component licenses |
||||
ride inside the image (tcl_library etc). |
||||
- build_host_platform: near-free now, genuinely interesting once G-105 |
||||
cross-builds exist (built ON win32-x86_64 FOR linux-x86_64). |
||||
- REJECTED: lifecycle fields (superseded_by, expires) - repo-level curation |
||||
(defaults.txt and successors), not artifact facts; artifacts stay immutable |
||||
statements of what they are. |
||||
|
||||
Comment lines: emitted records gain brief '#' comments documenting non-obvious |
||||
fields (origin/packager semantics). Toml-spec comments; the existing |
||||
line-based consumers (punk-runtime ps1 regex / bash sed per-line matching, |
||||
the summary parsers from G-107, achieved - see |
||||
goals/archive/G-107-buildsuite-library-tests.md) ignore them by construction - family_artifacts already |
||||
emits a '#' header line today. |
||||
|
||||
Read-mechanism design fork (the main in-goal decision): exe-appended zips are |
||||
readable from the central directory by generic zip readers (.NET |
||||
System.IO.Compression for the ps1 payload, unzip for bash) WITHOUT executing |
||||
the target - preferred, since identifying an untrusted stray binary by |
||||
running it is what an identity mechanism should avoid; the cooperative probe |
||||
(execute the target with a generated script) is the documented fallback for |
||||
hosts without a zip reader. |
||||
|
||||
Relationships: archived G-103 (metadata shape + family staging this extends - |
||||
see goals/archive/G-103-runtime-kit-family.md), G-067 (library-class |
||||
artifacts should adopt the same schema + embedding pattern), G-105 (embedded |
||||
target field aids cross-platform staging hygiene alongside the sidecar |
||||
!TARGET-MISMATCH check), G-006/G-067 consent gates (unchanged - metadata |
||||
travels with artifacts through the existing channels). |
||||
@ -0,0 +1,209 @@
|
||||
# G-106 powershell console-mode fallback: maintained raw-mode path for twapi-less runtimes |
||||
|
||||
Status: achieved 2026-07-22 |
||||
Scope: src/modules/punk/console-999999.0a1.0.tm (enableRaw_powershell/disableRaw_powershell + the persistent server lifecycle), scriptlib/utils/pwsh/ (consolemode_server_async.ps1 canonical + experiment-variant reconciliation), ps-script resolution/packaging across launch contexts (argv0-derived pstooldir; kits via the G-089 scriptlib-in-kits interplay), src/make.tcl shell (the verified launch context) |
||||
Goal: the powershell console-mode fallback - a persistent pwsh/powershell named-pipe server driven by punk::console when twapi is absent - is a MAINTAINED, dependable raw-mode path for twapi-less runtimes (the suite-built shells and kits, until/unless zig-built twapi lands): quiet in normal operation, reliable across the session lifecycle, and resolvable from every supported launch shape rather than only argv0-relative luck. |
||||
Acceptance: on a twapi-less suite runtime, raw enable/disable work via the fallback from (a) the make.tcl shell launch and (b) a kit / plain-tclsh repl launch, with no stderr noise in normal operation; the server lifecycle is verified - starts once per session, stays up for the session's raw transitions (the 2026-07-20 observed early-shutdown mode diagnosed and fixed), and shuts down with the session leaving no orphan pwsh processes; ps-script resolution no longer depends solely on argv0 parent-dir derivation (works from kits and unusual cwds, with the fallback-to-pwd branch replaced by something principled); scriptlib/utils/pwsh is reconciled to one canonical server script with the experiment variants retired or explicitly labelled; the fallback's role as the no-twapi contingency is documented where G-103's twapi investigation will find it, and a repeatable verification recipe is recorded in this file. |
||||
|
||||
## Context |
||||
|
||||
User verification 2026-07-20 on the freshly built tclsh90spr (piperepl |
||||
variant from G-096, achieved 2026-07-20): with `tcl::tm::add .../modules` + `package require repl; repl::init; |
||||
repl::start stdin`, basic punk features are functional on the bare suite runtime - |
||||
except raw mode, which is twapi-reliant. Launched instead via |
||||
`./tclsh90spr .../src/make.tcl shell`, the powershell fallback engaged and raw mode |
||||
WORKED - modulo consolemode_server_async.ps1 noise and an early server shutdown. |
||||
That working-but-rough state is what this goal hardens. User direction: "we would |
||||
like to look at maintaining this powershell fallback - especially if we can't get |
||||
zig-based twapi builds going." |
||||
|
||||
Mechanism today (punk::console): when twapi is absent, enableRaw/disableRaw are |
||||
aliased to *_powershell variants that talk over a named pipe to a persistent |
||||
`consolemode_server_async.ps1` process (templated per-console id; pwsh.exe probed |
||||
first, then powershell.exe). Known weak points at goal creation: |
||||
|
||||
- Resolution: pstooldir = argv0's grandparent + /scriptlib/utils/pwsh, else [pwd] - |
||||
works for `make.tcl shell` (argv0 = src/make.tcl -> repo root) and little else by |
||||
construction. Kits need the script carried/resolved properly (G-089 scriptlib in |
||||
kits). |
||||
- Lifecycle: observed early shutdown of the server mid-session; startup noise on |
||||
stderr ("twapi not present, using persistent powershell process: ..." plus ps1 |
||||
output). |
||||
- Hygiene: scriptlib/utils/pwsh holds several sibling experiments |
||||
(consolemode_server.ps1, *_async1.ps1, *_async.2ps1, consolemode.ps1, |
||||
consolemode_enableraw.ps1) with no marking of which is canonical. |
||||
|
||||
Relationships: G-103 (bi-kit twapi under the zig-only policy is |
||||
needs-investigation - THIS fallback is the mitigation; if zig-built twapi proves |
||||
impractical the fallback becomes the primary raw path on suite runtimes and this |
||||
goal's priority rises), G-013 (raw mode as the repl default ultimately requires raw |
||||
mode to be dependable on twapi-less runtimes), G-089 (scriptlib in kits - the |
||||
delivery vehicle for the ps script), G-061-era console-context findings (agent |
||||
console-context traps around attached consoles apply to verifying this). |
||||
|
||||
## Approach |
||||
|
||||
Design decisions of the 2026-07-22 overhaul (punk::console 0.8.0): |
||||
|
||||
- Root cause of the 2026-07-20 early shutdown - two compounding defects in the old |
||||
server script: (1) a 20s keepalive killed the server when no ping arrived, but only |
||||
`enableraw` messages refreshed the ping and NO tcl-side pinger ever existed, so the |
||||
server always died ~20s after the last raw enable ("ping stale for pipe ... - |
||||
exiting" was this kill's console noise); (2) the per-connection |
||||
`StreamReader.Close()` disposed the underlying pipe stream, so the following |
||||
`Disconnect()/Dispose()` threw and killed the listener runspace after the FIRST |
||||
message - later toggles could not reach the server even while its process lived. |
||||
- Keepalive replaced by a parent-process watch: the tcl side templates its pid into |
||||
the script; the server holds a `System.Diagnostics.Process` object obtained once by |
||||
that pid (handle-based, immune to pid reuse) and exits when `HasExited` reports the |
||||
owner gone (<=5s poll cadence). Orphan prevention needs no cooperation from the tcl |
||||
side - hard kills included. |
||||
- Lazy singleton server: spawn moved from module load to first |
||||
enableRaw/disableRaw use, so loading punk::console in piped/non-console or |
||||
worker-thread contexts spawns nothing and prints nothing. Server identity lives in |
||||
tsv `punk_console` (`ps_server_pipename`/`ps_server_pid`/`ps_server_spawntime`) |
||||
under `tsv::lock`, so all interps/threads of a process share ONE server (the |
||||
codethread and the repl thread previously would each have spawned their own). |
||||
- Quiet by default: the server child is spawned `-nop -nol -noni` with stdout/stderr |
||||
redirected to NUL (stdin MUST stay inherited - the console input handle is how the |
||||
server reaches the console); all ps1 output is debug-gated. Env |
||||
`PUNK_PS_CONSOLEMODE_DEBUG=1` enables diagnostics on both sides (tcl spawn note on |
||||
stderr + unredirected ps1 write-host trail). Failure paths (server unreachable, |
||||
spawn failure) still emit one actionable stderr line - that is not normal |
||||
operation. |
||||
- Resolution chain (`punk::console::system::ps_consolemode_script_get`): env |
||||
`PUNK_PS_CONSOLEMODE_SCRIPT` override (used only when the file exists) -> |
||||
argv0-derived project root -> module-location-derived roots (2-up for built |
||||
`modules/`, 3-up for `src/modules/`; a zipfs kit module path just fails the probes) |
||||
-> embedded copy of the script carried in the module |
||||
(`ps_consolemode_script_embedded`). The `[pwd]` fallback is gone. The embedded copy |
||||
makes kits and arbitrary cwds resolution-proof with no scriptlib delivery |
||||
dependency (the G-089 interplay is thereby optional for THIS script: a resolvable |
||||
scriptlib file wins - dev-editable - and the embedded text is the floor). |
||||
- Delivery stays `-c <script text>` (not a dropped file): needs nothing on disk at |
||||
run time and is not subject to ExecutionPolicy restrictions that can block -File |
||||
runs. The ps1's semicolon-after-each-command convention supports that delivery. |
||||
- Protocol: one line per named-pipe connection - `enableraw | disableraw | ping | |
||||
exit`. The listener now forwards disableraw (previously swallowed - on tcl 8.6, |
||||
where `-inputmode` is unavailable, cooked mode could never be restored via the |
||||
fallback); a null-message connection (a probe) no longer shuts the server down; |
||||
`AutoResetEvent` replaces ManualResetEvent+late-Reset (lost-wakeup race). |
||||
- enableRaw returns only after the flip is observable where the runtime can read |
||||
live mode (tcl9 `-inputmode` poll, 750ms cap, result note carries |
||||
`confirmed 0|1|unknown`) - closes the fire-and-forget race between the pipe write |
||||
and the server applying the console flags. |
||||
- `ps_consolemode_send` retries connection to a deadline (15s within 15s of spawn - |
||||
powershell startup - else 2.5s), and on an unreachable recorded server clears the |
||||
recorded state and respawns once. `ps_consolemode_server_stop` is an explicit |
||||
best-effort shutdown for tests/tools (not needed for orphan prevention). |
||||
- stty remains the last-resort branch after the server path (unchanged semantics: |
||||
useful only in msys/mintty-without-winpty environments and when powershell is |
||||
entirely absent). |
||||
- posh-git MIT attribution for the NativeConsoleMethods C# snippet now rides in the |
||||
canonical script (and thus the embedded copy) - the third-party source copy |
||||
(consolemode.ps1) could then be retired with the other variants. |
||||
|
||||
## Notes |
||||
|
||||
- Verification recipe (repeatable): |
||||
1. Sync + resolution suite (any platform, no server spawned): |
||||
`tclsh90 src/tests/runtests.tcl -report compact -show-passes 0 -include-paths "modules/punk/console/***"` |
||||
- psfallback.test pins the embedded-copy/canonical-file sync, placeholder |
||||
presence, and the resolution chain (env override + fallthrough). |
||||
2. Functional lifecycle selftest on a twapi-less runtime. The selftest must run |
||||
with a REAL console that is not the driving shell's console: launch it via |
||||
`Start-Process <twapi-less tclsh> <selftest.tcl> <resultfile> <moduledir> -WindowStyle Hidden` |
||||
(Start-Process gives a console app a fresh console; Hidden keeps it invisible; |
||||
do NOT redirect stdin). Selftest body (write results to `<resultfile>`, tm-path |
||||
add `<moduledir>`, `package require punk::console`, assert `has_twapi=0`): |
||||
enableRaw -> assert `chan configure stdin -inputmode` reads `raw` (tcl9's live |
||||
GetConsoleMode read is the independent cross-check); disableRaw -> `normal`; |
||||
repeat; idle 25s (the old keepalive died at 20s) then another cycle; |
||||
`punk::console::system::ps_consolemode_server_stop` (expect `stopped 1`); |
||||
enableRaw again (respawn) + disableRaw; record |
||||
`tsv::get punk_console ps_server_pid` after each spawn. Driver afterwards: |
||||
wait >=12s past process exit and assert every recorded server pid is gone |
||||
(parent-watch reaping). Force the embedded resolution variant by setting |
||||
`set ::punk::console::system::module_dir C:/nonexistent` before the first |
||||
enableRaw with the selftest located outside any checkout, and assert |
||||
`ps_consolemode_script_get` reports `source=embedded`. |
||||
3. Live-repl spot check (the acceptance launch shapes): boot |
||||
`<twapi-less tclsh> src/make.tcl shell` (shape a) or |
||||
`<twapi-less kit> <script: tcl::tm::path add <repo>/modules; package require punk::repl; repl::init; repl::start stdin>` |
||||
(shape b) hidden as above with stderr redirected to a file; from a helper |
||||
process do FreeConsole + AttachConsole(<shell pid>) + open CONIN$ + |
||||
WriteConsoleInput to type `punk::console::enableRaw` + Enter at the live |
||||
prompt; GetConsoleMode(CONIN$) before/after shows ENABLE_LINE_INPUT|ECHO |
||||
cleared (observed 0x1E7 -> 0x1E1); a pwsh child of the shell with |
||||
`punkshell_ps_consolemode` in its command line confirms engagement; type |
||||
`punk::console::disableRaw` (mode returns, observed 0x1E1 -> 0x1E7); kill the |
||||
shell and assert the server exits <=12s; assert the stderr capture has no |
||||
fallback-related lines (grep: `twapi not present|pipename|Started named |
||||
pipe|consolemode_server|ping stale|persistent powershell`). |
||||
4. Debug knob when anything misbehaves: `PUNK_PS_CONSOLEMODE_DEBUG=1` in the tcl |
||||
process's environment (tcl-side spawn note + unredirected ps1 diagnostics). |
||||
- Residue - tcl 8.6 twapi-less: the no-`-inputmode` disableraw-via-server path is |
||||
now CODED (previously stty-or-error) but only tcl9-verified; verify when the G-099 |
||||
8.6 suite produces a twapi-less 8.6 runtime. |
||||
- Residue - punk kits with twapi (e.g punk905): unaffected by design (twapi branch |
||||
wins). Note their kit-stamped punk::console pre-registration satisfies a plain |
||||
`package require punk::console` in bare `tcl::tm::add` contexts, shadowing newer |
||||
built modules - the known kit-registration behaviour, relevant to verification |
||||
setups only. |
||||
- Residue - src/vfs/project.vfs's pinned punk::console 0.1.1 copy still carries the |
||||
old load-time spawn referencing the retired consolemode_server.ps1 (user-curated |
||||
vfs payload for generated projects; not touched by this goal). |
||||
- Repl integration note: raw engages on demand (lazy spawn) - the first |
||||
enableRaw of a twapi-less session pays the server spawn (~0.6-0.7s with pwsh 7 |
||||
observed; powershell.exe cold is slower), later toggles are ~1-30ms. An eager |
||||
warm-up at repl init on twapi-less interactive sessions is a possible future |
||||
nicety if the first-prompt delay is felt. |
||||
|
||||
## Progress |
||||
|
||||
### 2026-07-22: overhaul landed (punk::console 0.8.0) + full verification - acceptance met |
||||
|
||||
Landed: punk::console 0.8.0 (see console-buildversion.txt changelog for the |
||||
per-change list; design in ## Approach), canonical |
||||
scriptlib/utils/pwsh/consolemode_server_async.ps1 rewrite, five experiment variants |
||||
retired (consolemode_server.ps1, consolemode_server_async1.ps1, |
||||
consolemode_server_async.2ps1, consolemode.ps1, consolemode_enableraw.ps1) with |
||||
scriptlib/utils/pwsh/README.md labelling the canonical + echotest.ps1, new console |
||||
testsuite psfallback.test (embedded-sync + resolution pins), punk::repl stale |
||||
comment fix (no behaviour change), project 0.17.8. |
||||
|
||||
Verification evidence (all on suite build 9.0.5 runtimes, zig 0.16.0; driver |
||||
scripts per the ## Notes recipe): |
||||
|
||||
- Console testsuite: 76/76 pass incl the 5 new psfallback tests (native tclsh 9.0.3 |
||||
runner). |
||||
- Hidden-console lifecycle selftest, tclsh90spr (has_twapi=0), against src modules |
||||
AND built modules, plus zipfs kit tclsh90sprzip against built modules - all |
||||
SELFTEST-PASS: first enable ~0.6-0.7s incl server spawn with `confirmed 1` and |
||||
-inputmode reading `raw`; warm toggles ~30ms; survived 25s idle then toggled in |
||||
~30ms (old keepalive died at 20s); explicit stop `stopped 1`; respawn cycle ok; |
||||
both server generations reaped (explicit stop + parent-watch after process kill), |
||||
zero orphans. |
||||
- Embedded-resolution forced run (bogus module_dir, scratchpad argv0/cwd): |
||||
`source=embedded`, full enable/disable cycle + stop PASS - kits/unusual cwds need |
||||
no scriptlib on disk. |
||||
- Acceptance shape (a): `tclsh90spr src/make.tcl shell` live repl in a hidden |
||||
console; keystrokes injected via AttachConsole+WriteConsoleInput typing |
||||
`punk::console::enableRaw` / `punk::console::disableRaw` at the prompt; external |
||||
GetConsoleMode reads flipped 0x1E7 -> 0x1E1 -> 0x1E7 (LINE|ECHO cleared and |
||||
restored); server child engaged; hard-kill of the shell -> server gone <=12s; |
||||
stderr capture free of fallback noise (8 lines, all unrelated boot messages). |
||||
- Acceptance shape (b): tclsh90sprzip (zipfs kit) running the bare-runtime repl |
||||
formula (`tcl::tm::path add <repo>/modules; package require punk::repl; |
||||
repl::init; repl::start stdin`) - same injection sequence, same mode flips, |
||||
engagement, reaping and quiet stderr. |
||||
- punk905 (punk kit WITH twapi): twapi path unaffected - enableRaw flips modes via |
||||
twapi with no server spawned. |
||||
- ps1 standalone lifecycle smoke (pwsh -f, debug mode): start/ping/null-probe/bogus |
||||
message/multi-connection survival/exit-message shutdown + watched-parent-death |
||||
shutdown all pass; debug output only. |
||||
|
||||
Remaining manual item: none gating. The user's interactive feel of raw mode on the |
||||
suite runtimes (typing experience) is the natural follow-up confirmation; the |
||||
mechanical acceptance criteria are all verified above. 8.6-runtime verification is |
||||
recorded as residue against the G-099 arc (see ## Notes). |
||||
@ -1,4 +1,4 @@
|
||||
[project] |
||||
name = "punkshell" |
||||
version = "0.17.6" |
||||
version = "0.18.8" |
||||
license = "BSD-2-Clause" |
||||
|
||||
@ -0,0 +1,36 @@
|
||||
# scriptlib/utils/pwsh |
||||
|
||||
Powershell helper scripts for punkshell. |
||||
|
||||
## consolemode_server_async.ps1 (canonical - G-106) |
||||
|
||||
The powershell console-mode fallback server: a persistent named-pipe server that |
||||
punk::console launches when twapi is absent on windows, to flip the console's line/echo |
||||
input flags for raw mode. This file is the canonical maintained copy; the module |
||||
`src/modules/punk/console-999999.0a1.0.tm` carries an embedded copy |
||||
(`punk::console::system::ps_consolemode_script_embedded`) as the last-resort resolution |
||||
for kits and unusual cwds. Keep the two in sync - the console testsuite |
||||
`src/tests/modules/punk/console/testsuites/console/psfallback.test` fails when they |
||||
diverge. |
||||
|
||||
Key facts (details in the script header and `goals/archive/G-106-powershell-consolemode-fallback.md`): |
||||
|
||||
- Delivered to powershell via `-c <script text>` with `<punkshell_*>` placeholders |
||||
substituted (no script file needed at run time; not subject to ExecutionPolicy). |
||||
Semicolons after each command are required by that delivery convention. |
||||
- Protocol: one line per named-pipe connection - `enableraw | disableraw | ping | exit`. |
||||
- The server watches the owning tcl process's pid and exits with it (orphan prevention). |
||||
- Quiet by default; `PUNK_PS_CONSOLEMODE_DEBUG=1` in the launching tcl process's |
||||
environment enables diagnostics on both sides. |
||||
- The NativeConsoleMethods C# snippet derives from posh-git (MIT - attribution carried in |
||||
the script). |
||||
|
||||
Superseded experiment variants (consolemode_server.ps1, consolemode_server_async1.ps1, |
||||
consolemode_server_async.2ps1, consolemode.ps1 - the posh-git original this derives from - |
||||
and the one-shot consolemode_enableraw.ps1) were retired 2026-07-22 as part of G-106; see |
||||
VCS history to recover them. |
||||
|
||||
## echotest.ps1 |
||||
|
||||
Standalone one-line scratch script (`write-host "test"`) - not part of the console-mode |
||||
fallback machinery. |
||||
@ -1,201 +0,0 @@
|
||||
# from github.com/dahlbyk/posh-git |
||||
# ------------------------------------------------------------------------------------ |
||||
#Copyright (c) 2010-2018 Keith Dahlby, Keith Hill, and contributors |
||||
|
||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: |
||||
|
||||
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. |
||||
|
||||
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||||
# ------------------------------------------------------------------------------------ |
||||
|
||||
# Always skip setting the console mode on non-Windows platforms. |
||||
if (($PSVersionTable.PSVersion.Major -ge 6) -and !$IsWindows) { |
||||
function Set-ConsoleMode { |
||||
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] |
||||
param() |
||||
} |
||||
|
||||
return |
||||
} |
||||
|
||||
$consoleModeSource = @" |
||||
using System; |
||||
using System.Runtime.InteropServices; |
||||
|
||||
public class NativeConsoleMethods |
||||
{ |
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] |
||||
public static extern IntPtr GetStdHandle(int handleId); |
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] |
||||
public static extern bool GetConsoleMode(IntPtr hConsoleOutput, out uint dwMode); |
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] |
||||
public static extern bool SetConsoleMode(IntPtr hConsoleOutput, uint dwMode); |
||||
|
||||
public static uint GetConsoleMode(bool input = false) |
||||
{ |
||||
var handle = GetStdHandle(input ? -10 : -11); |
||||
uint mode; |
||||
if (GetConsoleMode(handle, out mode)) |
||||
{ |
||||
return mode; |
||||
} |
||||
return 0xffffffff; |
||||
} |
||||
|
||||
public static uint SetConsoleMode(bool input, uint mode) |
||||
{ |
||||
var handle = GetStdHandle(input ? -10 : -11); |
||||
if (SetConsoleMode(handle, mode)) |
||||
{ |
||||
return GetConsoleMode(input); |
||||
} |
||||
return 0xffffffff; |
||||
} |
||||
} |
||||
"@ |
||||
|
||||
[Flags()] |
||||
enum ConsoleModeInputFlags |
||||
{ |
||||
ENABLE_PROCESSED_INPUT = 0x0001 |
||||
ENABLE_LINE_INPUT = 0x0002 |
||||
ENABLE_ECHO_INPUT = 0x0004 |
||||
ENABLE_WINDOW_INPUT = 0x0008 |
||||
ENABLE_MOUSE_INPUT = 0x0010 |
||||
ENABLE_INSERT_MODE = 0x0020 |
||||
ENABLE_QUICK_EDIT_MODE = 0x0040 |
||||
ENABLE_EXTENDED_FLAGS = 0x0080 |
||||
ENABLE_AUTO_POSITION = 0x0100 |
||||
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0200 |
||||
} |
||||
|
||||
[Flags()] |
||||
enum ConsoleModeOutputFlags |
||||
{ |
||||
ENABLE_PROCESSED_OUTPUT = 0x0001 |
||||
ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002 |
||||
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 |
||||
} |
||||
|
||||
function Set-ConsoleMode |
||||
{ |
||||
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] |
||||
param( |
||||
[Parameter(ParameterSetName = "ANSI")] |
||||
[switch] |
||||
$ANSI, |
||||
|
||||
[Parameter(ParameterSetName = "Mode")] |
||||
[uint32] |
||||
$Mode, |
||||
|
||||
[switch] |
||||
$StandardInput |
||||
) |
||||
|
||||
begin { |
||||
# Module import is speeded up by deferring the Add-Type until the first time this function is called. |
||||
# Add the NativeConsoleMethods type but only once per session. |
||||
if (!('NativeConsoleMethods' -as [System.Type])) { |
||||
Add-Type $consoleModeSource |
||||
} |
||||
} |
||||
|
||||
end { |
||||
if ($ANSI) |
||||
{ |
||||
$outputMode = [NativeConsoleMethods]::GetConsoleMode($false) |
||||
$null = [NativeConsoleMethods]::SetConsoleMode($false, $outputMode -bor [ConsoleModeOutputFlags]::ENABLE_VIRTUAL_TERMINAL_PROCESSING) |
||||
|
||||
if ($StandardInput) |
||||
{ |
||||
$inputMode = [NativeConsoleMethods]::GetConsoleMode($true) |
||||
$null = [NativeConsoleMethods]::SetConsoleMode($true, $inputMode -bor [ConsoleModeInputFlags]::ENABLE_VIRTUAL_TERMINAL_PROCESSING) |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
[NativeConsoleMethods]::SetConsoleMode($StandardInput, $Mode) |
||||
} |
||||
} |
||||
} |
||||
function Get-ConsoleMode |
||||
{ |
||||
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] |
||||
param( |
||||
[switch] |
||||
$StandardInput |
||||
) |
||||
|
||||
begin { |
||||
# Module import is speeded up by deferring the Add-Type until the first time this function is called. |
||||
# Add the NativeConsoleMethods type but only once per session. |
||||
if (!('NativeConsoleMethods' -as [System.Type])) { |
||||
Add-Type $consoleModeSource |
||||
} |
||||
} |
||||
|
||||
end { |
||||
$mode = [NativeConsoleMethods]::GetConsoleMode($StandardInput) |
||||
write-Output $mode |
||||
return |
||||
} |
||||
} |
||||
function psmain { |
||||
param ( |
||||
[validateSet('enableRaw', 'disableRaw')] |
||||
[string]$Action |
||||
) |
||||
$inputflags = Get-ConsoleMode -StandardInput |
||||
$resultflags = $inputflags #default |
||||
if (($inputflags -band [ConsoleModeInputFlags]::ENABLE_LINE_INPUT) -eq [ConsoleModeInputFlags]::ENABLE_LINE_INPUT) { |
||||
#cooked mode |
||||
$initialstate = "cooked" |
||||
if ($action -eq "enableraw") { |
||||
#disable cooked flags |
||||
$disable = [uint32](-bnot [uint32][ConsoleModeInputFlags]::ENABLE_LINE_INPUT) -band ( -bnot [uint32][ConsoleModeInputFlags]::ENABLE_ECHO_INPUT) |
||||
$adjustedflags = $inputflags -band ($disable) |
||||
$resultflags = [NativeConsoleMethods]::SetConsoleMode($true,$adjustedflags) |
||||
} |
||||
} else { |
||||
#raw mode |
||||
$initialstate = "raw" |
||||
if ($action -eq "disableraw") { |
||||
#set cooked flags |
||||
$adjustedflags = $inputflags -bor [ConsoleModeInputFlags]::ENABLE_LINE_INPUT -bor [ConsoleModeInputFlags]::ENABLE_ECHO_INPUT |
||||
$resultflags = [NativeConsoleMethods]::SetConsoleMode($true,$adjustedflags) |
||||
} |
||||
} |
||||
#return in format that can act as a tcl dict |
||||
write-host "startflags: $inputflags initialstate: $initialstate action: $Action endflags: $resultflags" |
||||
} |
||||
psmain @args |
||||
|
||||
#write-host (Get-ConsoleMode) |
||||
#Set-ConsoleMode -ANSI -StandardInput |
||||
#write-host (Get-ConsoleMode) |
||||
|
||||
#test toggle |
||||
#if ((($inputflags -band [ConsoleModeInputFlags]::ENABLE_QUICK_EDIT_MODE)) -eq [ConsoleModeInputFlags]::ENABLE_QUICK_EDIT_MODE) { |
||||
# #quick edit is on |
||||
# write-host "quick edit is on" |
||||
# $adjustedflags = $inputflags -band (-bnot [uint32][ConsoleModeInputFlags]::ENABLE_QUICK_EDIT_MODE) |
||||
# $resultflags = [NativeConsoleMethods]::SetConsoleMode($true, $adjustedflags) |
||||
## |
||||
#} else { |
||||
# #quick edit is off |
||||
# write-host "quick edit is off" |
||||
# $resultflags = [NativeConsoleMethods]::SetConsoleMode($true, $inputflags -bor [ConsoleModeInputFlags]::ENABLE_QUICK_EDIT_MODE) |
||||
#} |
||||
|
||||
|
||||
#todo - parameters so it doesn't act as a toggle |
||||
#we want to be able to explicitly set raw vs cooked |
||||
|
||||
#multi |
||||
|
||||
|
||||
|
||||
@ -1,91 +0,0 @@
|
||||
#!SEMICOLONS must be placed after each command as scriptdata needs to be sent to powershell directly with the -c parameter! |
||||
; |
||||
|
||||
if ($PSVersionTable.PSVersion.Major -le 5) { |
||||
# For Windows PowerShell, we want to remove any PowerShell 7 paths from PSModulePath |
||||
#snipped from https://github.com/PowerShell/DSC/pull/777/commits/af9b99a4d38e0cf1e54c4bbd89cbb6a8a8598c4e |
||||
#Presumably users are supposed to know not to have custom paths for powershell desktop containing a 'powershell' subfolder?? |
||||
; |
||||
$env:PSModulePath = ($env:PSModulePath -split ';' | Where-Object { $_ -notlike '*\powershell\*' }) -join ';'; |
||||
}; |
||||
|
||||
$consoleModeSource = @" |
||||
using System; |
||||
using System.Runtime.InteropServices; |
||||
|
||||
public class NativeConsoleMethods |
||||
{ |
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] |
||||
public static extern IntPtr GetStdHandle(int handleId); |
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] |
||||
public static extern bool GetConsoleMode(IntPtr hConsoleOutput, out uint dwMode); |
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] |
||||
public static extern bool SetConsoleMode(IntPtr hConsoleOutput, uint dwMode); |
||||
|
||||
public static uint GetConsoleMode(bool input = false) |
||||
{ |
||||
var handle = GetStdHandle(input ? -10 : -11); |
||||
uint mode; |
||||
if (GetConsoleMode(handle, out mode)) |
||||
{ |
||||
return mode; |
||||
} |
||||
return 0xffffffff; |
||||
} |
||||
|
||||
public static uint SetConsoleMode(bool input, uint mode) |
||||
{ |
||||
var handle = GetStdHandle(input ? -10 : -11); |
||||
if (SetConsoleMode(handle, mode)) |
||||
{ |
||||
return GetConsoleMode(input); |
||||
} |
||||
return 0xffffffff; |
||||
} |
||||
} |
||||
"@ |
||||
; |
||||
[Flags()] |
||||
enum ConsoleModeInputFlags |
||||
{ |
||||
ENABLE_LINE_INPUT = 0x0002 |
||||
ENABLE_ECHO_INPUT = 0x0004 |
||||
}; |
||||
|
||||
|
||||
function psmain { |
||||
param ( |
||||
[validateSet('enableRaw', 'disableRaw')] |
||||
[string]$Action |
||||
); |
||||
# $inputflags = Get-ConsoleMode -StandardInput; |
||||
if (!('NativeConsoleMethods' -as [System.Type])) { |
||||
Add-Type $consoleModeSource |
||||
} |
||||
$inputFlags = [NativeConsoleMethods]::GetConsoleMode($true); |
||||
$resultflags = $inputflags; |
||||
if (($inputflags -band [ConsoleModeInputFlags]::ENABLE_LINE_INPUT) -eq [ConsoleModeInputFlags]::ENABLE_LINE_INPUT) { |
||||
#cooked mode |
||||
$initialstate = "cooked"; |
||||
if ($action -eq "enableraw") { |
||||
#disable cooked flags |
||||
$disable = [uint32](-bnot [uint32][ConsoleModeInputFlags]::ENABLE_LINE_INPUT) -band ( -bnot [uint32][ConsoleModeInputFlags]::ENABLE_ECHO_INPUT); |
||||
$adjustedflags = $inputflags -band ($disable); |
||||
$resultflags = [NativeConsoleMethods]::SetConsoleMode($true,$adjustedflags); |
||||
} |
||||
} else { |
||||
#raw mode |
||||
$initialstate = "raw"; |
||||
if ($action -eq "disableraw") { |
||||
#set cooked flags |
||||
$adjustedflags = $inputflags -bor [ConsoleModeInputFlags]::ENABLE_LINE_INPUT -bor [ConsoleModeInputFlags]::ENABLE_ECHO_INPUT; |
||||
$resultflags = [NativeConsoleMethods]::SetConsoleMode($true,$adjustedflags); |
||||
} |
||||
} |
||||
#return in format that can act as a tcl dict |
||||
#write-host "startflags: $inputflags initialstate: $initialstate action: $Action endflags: $resultflags"; |
||||
}; |
||||
psmain 'enableRaw'; |
||||
|
||||
@ -1,144 +0,0 @@
|
||||
#!SEMICOLONS must be placed after each command as scriptdata needs to be sent to powershell directly with the -c parameter! |
||||
; |
||||
|
||||
if ($PSVersionTable.PSVersion.Major -le 5) { |
||||
# For Windows PowerShell, we want to remove any PowerShell 7 paths from PSModulePath |
||||
#snipped from https://github.com/PowerShell/DSC/pull/777/commits/af9b99a4d38e0cf1e54c4bbd89cbb6a8a8598c4e |
||||
#Presumably users are supposed to know not to have custom paths for powershell desktop containing a 'powershell' subfolder?? |
||||
; |
||||
$env:PSModulePath = ($env:PSModulePath -split ';' | Where-Object { $_ -notlike '*\powershell\*' }) -join ';'; |
||||
}; |
||||
|
||||
$consoleModeSource = @" |
||||
using System; |
||||
using System.Runtime.InteropServices; |
||||
|
||||
public class NativeConsoleMethods |
||||
{ |
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] |
||||
public static extern IntPtr GetStdHandle(int handleId); |
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] |
||||
public static extern bool GetConsoleMode(IntPtr hConsoleOutput, out uint dwMode); |
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] |
||||
public static extern bool SetConsoleMode(IntPtr hConsoleOutput, uint dwMode); |
||||
|
||||
public static uint GetConsoleMode(bool input = false) |
||||
{ |
||||
var handle = GetStdHandle(input ? -10 : -11); |
||||
uint mode; |
||||
if (GetConsoleMode(handle, out mode)) |
||||
{ |
||||
return mode; |
||||
} |
||||
return 0xffffffff; |
||||
} |
||||
|
||||
public static uint SetConsoleMode(bool input, uint mode) |
||||
{ |
||||
var handle = GetStdHandle(input ? -10 : -11); |
||||
if (SetConsoleMode(handle, mode)) |
||||
{ |
||||
return GetConsoleMode(input); |
||||
} |
||||
return 0xffffffff; |
||||
} |
||||
} |
||||
"@ |
||||
; |
||||
[Flags()] |
||||
enum ConsoleModeInputFlags |
||||
{ |
||||
ENABLE_LINE_INPUT = 0x0002 |
||||
ENABLE_ECHO_INPUT = 0x0004 |
||||
}; |
||||
|
||||
if (!('NativeConsoleMethods' -as [System.Type])) { |
||||
Add-Type $consoleModeSource |
||||
} |
||||
|
||||
function psmain { |
||||
param ( |
||||
[validateSet('enableRaw', 'disableRaw')] |
||||
[string]$Action |
||||
); |
||||
# $inputflags = Get-ConsoleMode -StandardInput; |
||||
if (!('NativeConsoleMethods' -as [System.Type])) { |
||||
Add-Type $consoleModeSource |
||||
} |
||||
$inputFlags = [NativeConsoleMethods]::GetConsoleMode($true); |
||||
$resultflags = $inputflags; |
||||
if (($inputflags -band [ConsoleModeInputFlags]::ENABLE_LINE_INPUT) -eq [ConsoleModeInputFlags]::ENABLE_LINE_INPUT) { |
||||
#cooked mode |
||||
$initialstate = "cooked"; |
||||
if ($action -eq "enableraw") { |
||||
#disable cooked flags |
||||
$disable = [uint32](-bnot [uint32][ConsoleModeInputFlags]::ENABLE_LINE_INPUT) -band ( -bnot [uint32][ConsoleModeInputFlags]::ENABLE_ECHO_INPUT); |
||||
$adjustedflags = $inputflags -band ($disable); |
||||
$resultflags = [NativeConsoleMethods]::SetConsoleMode($true,$adjustedflags); |
||||
} |
||||
} else { |
||||
#raw mode |
||||
$initialstate = "raw"; |
||||
if ($action -eq "disableraw") { |
||||
#set cooked flags |
||||
$adjustedflags = $inputflags -bor [ConsoleModeInputFlags]::ENABLE_LINE_INPUT -bor [ConsoleModeInputFlags]::ENABLE_ECHO_INPUT; |
||||
$resultflags = [NativeConsoleMethods]::SetConsoleMode($true,$adjustedflags); |
||||
} |
||||
} |
||||
#return in format that can act as a tcl dict |
||||
#write-host "startflags: $inputflags initialstate: $initialstate action: $Action endflags: $resultflags"; |
||||
}; |
||||
# psmain 'enableRaw'; |
||||
|
||||
$consoleid = $args[0]; |
||||
if ([string]::IsNullOrEmpty($consoleid)) { |
||||
$consoleid= "<punkshell_consoleid>" |
||||
}; |
||||
$pipeName = "punkshell_ps_consolemode_$consoleid"; |
||||
"pipename: $pipeName" |
||||
$pipeServer = New-Object System.IO.Pipes.NamedPipeServerStream($pipeName); |
||||
try { |
||||
while ($true) { |
||||
#"Waiting for connection on '$pipeName'"; |
||||
|
||||
$pipeServer.WaitForConnection(); |
||||
#"Connection established"; |
||||
$pipeReader = New-Object System.IO.StreamReader($pipeServer); |
||||
#$pipeWriter = New-Object System.IO.StreamWriter($pipeServer); |
||||
#$pipeWriter.AutoFlush = $true; |
||||
$request = $pipeReader.ReadLine(); |
||||
# "Received request: $request"; |
||||
if ($request -eq "exit") { |
||||
"consolemode_server.ps1 Exiting"; |
||||
exit; |
||||
} elseif ($request -eq "") { |
||||
#"Empty input"; |
||||
$pipeServer.Disconnect(); |
||||
#"Disconnected"; |
||||
continue; |
||||
} elseif ($request -eq $none) { |
||||
"Remote disconnected before sending"; |
||||
$pipeServer.Disconnect(); |
||||
"Disconnected"; |
||||
continue; |
||||
} elseif ($request -eq "enableraw") { |
||||
#$result = psmain 'enableRaw'; |
||||
$null = psmain 'enableRaw' |
||||
# "Sending result: '$result'"; |
||||
#$pipeWriter.Write($result); |
||||
$pipeServer.Disconnect(); |
||||
continue; |
||||
} else { |
||||
"consolemode_server.ps1 ignoring request: $request"; |
||||
$pipeServer.Disconnect(); |
||||
continue; |
||||
} |
||||
} |
||||
} |
||||
finally { |
||||
$pipeServer.Dispose(); |
||||
}; |
||||
|
||||
|
||||
@ -1,244 +0,0 @@
|
||||
#!SEMICOLONS must be placed after each command as scriptdata needs to be sent to powershell directly with the -c parameter! |
||||
; |
||||
|
||||
if ($PSVersionTable.PSVersion.Major -le 5) { |
||||
# For Windows PowerShell, we want to remove any PowerShell 7 paths from PSModulePath |
||||
#snipped from https://github.com/PowerShell/DSC/pull/777/commits/af9b99a4d38e0cf1e54c4bbd89cbb6a8a8598c4e |
||||
#Presumably users are supposed to know not to have custom paths for powershell desktop containing a 'powershell' subfolder?? |
||||
; |
||||
$env:PSModulePath = ($env:PSModulePath -split ';' | Where-Object { $_ -notlike '*\powershell\*' }) -join ';'; |
||||
}; |
||||
|
||||
$helper = @' |
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System.Linq.Expressions; |
||||
using System.Management.Automation.Runspaces; |
||||
|
||||
public class RunspacedDelegateFactory |
||||
{ |
||||
public static Delegate NewRunspacedDelegate(Delegate _delegate, Runspace runspace) |
||||
{ |
||||
Action setRunspace = () => Runspace.DefaultRunspace = runspace; |
||||
return ConcatActionToDelegate(setRunspace, _delegate); |
||||
} |
||||
|
||||
private static Expression ExpressionInvoke(Delegate _delegate, params Expression[] arguments) |
||||
{ |
||||
var invokeMethod = _delegate.GetType().GetMethod("Invoke"); |
||||
return Expression.Call(Expression.Constant(_delegate), invokeMethod, arguments); |
||||
} |
||||
|
||||
public static Delegate ConcatActionToDelegate(Action a, Delegate d) |
||||
{ |
||||
var parameters = |
||||
d.GetType().GetMethod("Invoke").GetParameters() |
||||
.Select(p => Expression.Parameter(p.ParameterType, p.Name)) |
||||
.ToArray(); |
||||
|
||||
Expression body = Expression.Block(ExpressionInvoke(a), ExpressionInvoke(d, parameters)); |
||||
var lambda = Expression.Lambda(d.GetType(), body, parameters); |
||||
var compiled = lambda.Compile(); |
||||
return compiled; |
||||
} |
||||
} |
||||
'@ |
||||
add-type -TypeDefinition $helper |
||||
|
||||
#region console |
||||
|
||||
$consoleModeSource = @" |
||||
using System; |
||||
using System.Runtime.InteropServices; |
||||
|
||||
public class NativeConsoleMethods |
||||
{ |
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] |
||||
public static extern IntPtr GetStdHandle(int handleId); |
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] |
||||
public static extern bool GetConsoleMode(IntPtr hConsoleOutput, out uint dwMode); |
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] |
||||
public static extern bool SetConsoleMode(IntPtr hConsoleOutput, uint dwMode); |
||||
|
||||
public static uint GetConsoleMode(bool input = false) |
||||
{ |
||||
var handle = GetStdHandle(input ? -10 : -11); |
||||
uint mode; |
||||
if (GetConsoleMode(handle, out mode)) |
||||
{ |
||||
return mode; |
||||
} |
||||
return 0xffffffff; |
||||
} |
||||
|
||||
public static uint SetConsoleMode(bool input, uint mode) |
||||
{ |
||||
var handle = GetStdHandle(input ? -10 : -11); |
||||
if (SetConsoleMode(handle, mode)) |
||||
{ |
||||
return GetConsoleMode(input); |
||||
} |
||||
return 0xffffffff; |
||||
} |
||||
} |
||||
"@ |
||||
; |
||||
[Flags()] |
||||
enum ConsoleModeInputFlags |
||||
{ |
||||
ENABLE_LINE_INPUT = 0x0002 |
||||
ENABLE_ECHO_INPUT = 0x0004 |
||||
}; |
||||
|
||||
if (!('NativeConsoleMethods' -as [System.Type])) { |
||||
Add-Type $consoleModeSource |
||||
} |
||||
|
||||
function psmain { |
||||
param ( |
||||
[validateSet('enableRaw', 'disableRaw')] |
||||
[string]$Action |
||||
); |
||||
# $inputflags = Get-ConsoleMode -StandardInput; |
||||
if (!('NativeConsoleMethods' -as [System.Type])) { |
||||
Add-Type $consoleModeSource |
||||
} |
||||
$inputFlags = [NativeConsoleMethods]::GetConsoleMode($true); |
||||
$resultflags = $inputflags; |
||||
if (($inputflags -band [ConsoleModeInputFlags]::ENABLE_LINE_INPUT) -eq [ConsoleModeInputFlags]::ENABLE_LINE_INPUT) { |
||||
#cooked mode |
||||
$initialstate = "cooked"; |
||||
if ($action -eq "enableraw") { |
||||
#disable cooked flags |
||||
$disable = [uint32](-bnot [uint32][ConsoleModeInputFlags]::ENABLE_LINE_INPUT) -band ( -bnot [uint32][ConsoleModeInputFlags]::ENABLE_ECHO_INPUT); |
||||
$adjustedflags = $inputflags -band ($disable); |
||||
$resultflags = [NativeConsoleMethods]::SetConsoleMode($true,$adjustedflags); |
||||
} |
||||
} else { |
||||
#raw mode |
||||
$initialstate = "raw"; |
||||
if ($action -eq "disableraw") { |
||||
#set cooked flags |
||||
$adjustedflags = $inputflags -bor [ConsoleModeInputFlags]::ENABLE_LINE_INPUT -bor [ConsoleModeInputFlags]::ENABLE_ECHO_INPUT; |
||||
$resultflags = [NativeConsoleMethods]::SetConsoleMode($true,$adjustedflags); |
||||
} |
||||
} |
||||
#return in format that can act as a tcl dict |
||||
#write-host "startflags: $inputflags initialstate: $initialstate action: $Action endflags: $resultflags"; |
||||
}; |
||||
# psmain 'enableRaw'; |
||||
#endregion console |
||||
|
||||
|
||||
$consoleid = $args[0]; |
||||
if ([string]::IsNullOrEmpty($consoleid)) { |
||||
$consoleid= "<punkshell_consoleid>" |
||||
}; |
||||
$pipeName = "punkshell_ps_consolemode_$consoleid"; |
||||
"pipename: $pipeName"; |
||||
|
||||
$pipeServer = New-Object System.IO.Pipes.NamedPipeServerStream( |
||||
$pipeName, |
||||
[System.IO.Pipes.PipeDirection]::In, |
||||
1, |
||||
[System.IO.Pipes.PipeTransmissionMode]::Byte, |
||||
[System.IO.Pipes.PipeOptions]::Asynchronous |
||||
); |
||||
#$pipeServer = New-Object System.IO.Pipes.NamedPipeServerStream($pipeName); |
||||
; |
||||
|
||||
# Define the callback function for when a client connects |
||||
$callback = [System.AsyncCallback]{ |
||||
param($asyncResult); |
||||
|
||||
$se = $asyncResult.AsyncState.Sync; |
||||
$ps = $asyncResult.AsyncState.Pipeserver; |
||||
$pn = $asyncResult.AsyncState.Pipename; |
||||
Write-Host "Client connected - $pn"; |
||||
|
||||
# End the asynchronous wait operation |
||||
; |
||||
$ps.EndWaitForConnection($asyncResult); |
||||
#?? |
||||
; |
||||
# You can now perform read/write operations with the client |
||||
# For example, create a StreamReader and StreamWriter |
||||
; |
||||
$streamReader = New-Object System.IO.StreamReader($ps); |
||||
#$streamWriter = New-Object System.IO.StreamWriter($pipeServer); |
||||
#$streamWriter.AutoFlush = $true; |
||||
try { |
||||
$message = $streamReader.ReadLine(); |
||||
Write-Host "Received: $message"; |
||||
; |
||||
#$asyncResult.Message = $message; |
||||
; |
||||
} catch { |
||||
Write-Error "Error during communication: $($_.Exception.Message)"; |
||||
} finally { |
||||
# sever connection with client but keep the named pipe |
||||
if ($streamReader -ne $null) { |
||||
Write-Host "streamreader closing"; |
||||
$streamReader.Close(); |
||||
Write-Host "streamreader closed"; |
||||
} |
||||
Write-Host "Client disconnecting. $pn"; |
||||
$ps.Disconnect(); |
||||
Write-Host "Client disconnected. $pn"; |
||||
#$ps.Disconnect(); |
||||
#[System.Console]::Out.Flush(); |
||||
; |
||||
}; |
||||
write-host "HERE"; |
||||
|
||||
$se.set(); |
||||
|
||||
# Optionally, you can call BeginWaitForConnection again to listen for another client |
||||
# if your server is designed for multiple connections over time. |
||||
# $pipeServer.BeginWaitForConnection($callback, $null) |
||||
$ps.BeginWaitForConnection($callback, $null) |
||||
; |
||||
}; |
||||
|
||||
$syncEvent = New-Object System.Threading.ManualResetEvent($false); |
||||
|
||||
$runspacedDelegate = [RunspacedDelegateFactory]::NewRunspacedDelegate($callback, [Runspace]::DefaultRunspace); |
||||
|
||||
$loop = 0; |
||||
while ($loop -lt 15) { |
||||
Write-Host "Waiting for client connection on pipe '$pipeName'..."; |
||||
# Begin the asynchronous wait for a client connection |
||||
; |
||||
$state = [PSCustomObject]@{ |
||||
Loop = $loop |
||||
Sync = $SyncEvent |
||||
Pipeserver = $pipeServer |
||||
Pipename = $pipename |
||||
Message = "" |
||||
}; |
||||
$x = $pipeServer.BeginWaitForConnection($runspacedDelegate, $state ); |
||||
$syncEvent.WaitOne(10000); |
||||
# $x | Get-Member | write-host |
||||
; |
||||
write-host "msg: $(${x}.AsyncState.Message)" |
||||
if ($x.IsCompleted) { |
||||
write-host "completed"; |
||||
}; |
||||
$SyncEvent.reset(); |
||||
|
||||
$loop += 1; |
||||
#$cli = New-Object System.IO.Pipes.NamedPipeClientStream($pipeName); |
||||
#$cli.w |
||||
} |
||||
|
||||
# Keep the script running to allow the asynchronous operation to complete |
||||
# In a real-world scenario, you might have a loop or other logic here. |
||||
#Read-Host "Press Enter to exit the server." |
||||
|
||||
# Clean up |
||||
$pipeServer.Dispose(); |
||||
|
||||
|
||||
@ -1,263 +1,278 @@
|
||||
#!SEMICOLONS must be placed after each command as scriptdata needs to be sent to powershell directly with the -c parameter! |
||||
; |
||||
|
||||
if ($PSVersionTable.PSVersion.Major -le 5) { |
||||
# For Windows PowerShell, we want to remove any PowerShell 7 paths from PSModulePath |
||||
#snipped from https://github.com/PowerShell/DSC/pull/777/commits/af9b99a4d38e0cf1e54c4bbd89cbb6a8a8598c4e |
||||
#Presumably users are supposed to know not to have custom paths for powershell desktop containing a 'powershell' subfolder?? |
||||
; |
||||
$env:PSModulePath = ($env:PSModulePath -split ';' | Where-Object { $_ -notlike '*\powershell\*' }) -join ';'; |
||||
}; |
||||
|
||||
$consoleModeSource = @" |
||||
using System; |
||||
using System.Runtime.InteropServices; |
||||
|
||||
public class NativeConsoleMethods |
||||
{ |
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] |
||||
public static extern IntPtr GetStdHandle(int handleId); |
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] |
||||
public static extern bool GetConsoleMode(IntPtr hConsoleOutput, out uint dwMode); |
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] |
||||
public static extern bool SetConsoleMode(IntPtr hConsoleOutput, uint dwMode); |
||||
|
||||
public static uint GetConsoleMode(bool input = false) |
||||
{ |
||||
var handle = GetStdHandle(input ? -10 : -11); |
||||
uint mode; |
||||
if (GetConsoleMode(handle, out mode)) |
||||
{ |
||||
return mode; |
||||
} |
||||
return 0xffffffff; |
||||
} |
||||
|
||||
public static uint SetConsoleMode(bool input, uint mode) |
||||
{ |
||||
var handle = GetStdHandle(input ? -10 : -11); |
||||
if (SetConsoleMode(handle, mode)) |
||||
{ |
||||
return GetConsoleMode(input); |
||||
} |
||||
return 0xffffffff; |
||||
} |
||||
} |
||||
"@ |
||||
; |
||||
[Flags()] |
||||
enum ConsoleModeInputFlags |
||||
{ |
||||
ENABLE_PROCESSED_INPUT = 0x0001 |
||||
ENABLE_LINE_INPUT = 0x0002 |
||||
ENABLE_ECHO_INPUT = 0x0004 |
||||
ENABLE_WINDOW_INPUT = 0x0008 |
||||
ENABLE_MOUSE_INPUT = 0x0010 |
||||
ENABLE_INSERT_MODE = 0x0020 |
||||
ENABLE_QUICK_EDIT_MODE = 0x0040 |
||||
ENABLE_EXTENDED_FLAGS = 0x0080 |
||||
ENABLE_AUTO_POSITION = 0x0100 |
||||
ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200 |
||||
}; |
||||
|
||||
[Flags()] |
||||
enum ConsoleModeOutputFlags |
||||
{ |
||||
ENABLE_PROCESSED_OUTPUT = 0x0001 |
||||
ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002 |
||||
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 |
||||
DISABLE_NEWLINE_AUTO_RETURN = 0x0008 |
||||
}; |
||||
|
||||
if (!('NativeConsoleMethods' -as [System.Type])) { |
||||
Add-Type $consoleModeSource |
||||
} |
||||
|
||||
function rawmode { |
||||
param ( |
||||
[validateSet('enable', 'disable')] |
||||
[string]$Action |
||||
); |
||||
# $inputflags = Get-ConsoleMode -StandardInput; |
||||
if (!('NativeConsoleMethods' -as [System.Type])) { |
||||
Add-Type $consoleModeSource |
||||
} |
||||
$inputFlags = [NativeConsoleMethods]::GetConsoleMode($true); |
||||
$resultflags = $inputflags; |
||||
if (($inputflags -band [ConsoleModeInputFlags]::ENABLE_LINE_INPUT) -eq [ConsoleModeInputFlags]::ENABLE_LINE_INPUT) { |
||||
#cooked mode |
||||
$initialstate = "cooked"; |
||||
if ($action -eq "enable") { |
||||
#disable cooked flags |
||||
$disable = [uint32](-bnot [uint32][ConsoleModeInputFlags]::ENABLE_LINE_INPUT) -band ( -bnot [uint32][ConsoleModeInputFlags]::ENABLE_ECHO_INPUT); |
||||
$adjustedflags = $inputflags -band ($disable); |
||||
$resultflags = [NativeConsoleMethods]::SetConsoleMode($true,$adjustedflags); |
||||
} |
||||
} else { |
||||
#raw mode |
||||
$initialstate = "raw"; |
||||
if ($action -eq "disable") { |
||||
#set cooked flags |
||||
$adjustedflags = $inputflags -bor [ConsoleModeInputFlags]::ENABLE_LINE_INPUT -bor [ConsoleModeInputFlags]::ENABLE_ECHO_INPUT; |
||||
$resultflags = [NativeConsoleMethods]::SetConsoleMode($true,$adjustedflags); |
||||
} |
||||
} |
||||
#return in format that can act as a tcl dict |
||||
#write-host "startflags: $inputflags initialstate: $initialstate action: $Action endflags: $resultflags"; |
||||
}; |
||||
# rawmode 'enable'; |
||||
|
||||
$consoleid = $args[0]; |
||||
if ([string]::IsNullOrEmpty($consoleid)) { |
||||
$consoleid= "<punkshell_consoleid>" |
||||
}; |
||||
$pipeName = "punkshell_ps_consolemode_$consoleid"; |
||||
"pipename: $pipeName" |
||||
#$pipeServer = New-Object System.IO.Pipes.NamedPipeServerStream($pipeName); |
||||
|
||||
$sharedData = [hashtable]::Synchronized(@{}) #TSV |
||||
$scriptblock = { |
||||
param($tsv); |
||||
Add-Type -AssemblyName System.IO.Pipes; |
||||
#$pipeServer = New-Object System.IO.Pipes.NamedPipeServerStream( |
||||
# $pipeName, |
||||
# [System.IO.Pipes.PipeDirection]::In, |
||||
# 1, |
||||
# [System.IO.Pipes.PipeTransmissionMode]::Byte, |
||||
# [System.IO.Pipes.PipeOptions]::Asynchronous |
||||
#); |
||||
; |
||||
$serverloop = 0; |
||||
while ($true) { |
||||
$pipeServer = New-Object System.IO.Pipes.NamedPipeServerStream($pipeName); |
||||
$serverloop += 1; |
||||
$pipeServer.WaitForConnection(); |
||||
#write-host "Connection established"; |
||||
$reader = New-Object System.IO.StreamReader($pipeServer); |
||||
if ($reader -ne $null) { |
||||
$message = $reader.ReadLine(); |
||||
$reader.Close(); |
||||
$reader.Dispose(); |
||||
if ($message -ne $null) { |
||||
if ($message -eq "exit") { |
||||
#write-host "consolemode_server.ps1 exiting"; |
||||
$tsv.State = "done"; |
||||
break; |
||||
} elseif ($message -eq "enableraw") { |
||||
#write-host "RECEIVED: $message"; |
||||
$tsv.Message = $message; |
||||
#$tsv.Message = "$pipeName serverloop: $serverloop"; |
||||
|
||||
$tsv.Ping = Get-Date; |
||||
$msync.Set(); |
||||
}; |
||||
} else { |
||||
# write-host "consolemode_server.ps1 null-msg"; |
||||
$tsv.State = "done"; |
||||
break; |
||||
}; |
||||
}; |
||||
$pipeServer.Disconnect(); |
||||
$pipeServer.Dispose(); |
||||
}; |
||||
exit; |
||||
}; |
||||
|
||||
$keepalive_timeout = 20; #number of seconds without ping or other message, after which we terminate the process. |
||||
try { |
||||
$syncEvent = New-Object System.Threading.ManualResetEvent($false); |
||||
|
||||
$runspace = [runspacefactory]::CreateRunspace(); |
||||
[void]$runspace.Open(); |
||||
$runspace.SessionStateProxy.SetVariable("pipeName", $pipeName); |
||||
$runspace.SessionStateProxy.SetVariable("pipeServer", $null); |
||||
$runspace.SessionStateProxy.SetVariable("msync", $syncEvent); |
||||
|
||||
$powershell = [System.Management.Automation.PowerShell]::Create(); |
||||
$powershell.Runspace = $runspace; |
||||
[void]$powershell.Addscript($scriptblock).AddArgument($sharedData); |
||||
|
||||
$sharedData.State = "running"; |
||||
$sharedData.Ping = Get-Date; |
||||
$asyncResult = $powershell.BeginInvoke(); |
||||
|
||||
write-Host "Started named pipe server $pipeName in runspace" |
||||
$loop = 0; |
||||
while ($true) { |
||||
$loop += 1; |
||||
#write-host "loop $loop"; |
||||
[void]$syncEvent.WaitOne(($keepalive_timeout * 1000 / 2)); |
||||
$msg = $sharedData.Message; |
||||
#Write-Host "$pipeName Last message: $msg"; |
||||
$sharedData.Message = ""; |
||||
if ($msg -eq "enableraw") { |
||||
$null = rawmode 'enable' |
||||
} elseif ($msg -eq "disableraw") { |
||||
$null = rawmode 'disable' |
||||
} |
||||
#write-host "STATE: $(${sharedData}.State)" |
||||
if ($(${sharedData}.State) -eq "done") { |
||||
break; |
||||
}; |
||||
$tnow = Get-Date; |
||||
$elapsed = New-TimeSpan -Start $sharedData.Ping -End $tnow; |
||||
if ($elapsed.TotalSeconds -lt $keepalive_timeout) { |
||||
# write-host "ping ok"; |
||||
} else { |
||||
write-host "ping stale for pipe $pipeName - exiting"; |
||||
break; |
||||
} |
||||
[void]$syncEvent.Reset(); |
||||
# start-sleep -Milliseconds 300 |
||||
}; |
||||
} finally { |
||||
# Failing to properly shut down the run process can leave an orphan powershell process |
||||
# We need to use a client for the named pipe to send an exit message. |
||||
# Write-Host "terminating process for $pipeName"; |
||||
try { |
||||
# Write-Host "creating cli for $pipeName"; |
||||
$cli = New-Object System.IO.Pipes.NamedPipeClientStream($pipeName); |
||||
$cli.connect(1000); |
||||
#Write-Host "sending exit for $pipeName"; |
||||
$writer = new-object System.IO.StreamWriter($cli); |
||||
$writer.writeline("exit"); |
||||
$writer.flush(); |
||||
#Write-Host "disposing of cli for $pipeName"; |
||||
$cli.Dispose(); |
||||
} catch { |
||||
write-host "error during cli tidyup"; |
||||
Write-Error "error: $($PSItem.ToString())"; |
||||
Write-Host "Detailed Exception Message: $($PSItem.Exception.Message)"; |
||||
}; |
||||
|
||||
|
||||
try { |
||||
if ($null -ne $runspace) { |
||||
#Write-Host "closing runspace for $pipeName"; |
||||
$runspace.Close(); |
||||
#Write-Host "disposing of runspace for $pipeName"; |
||||
$runspace.Dispose(); |
||||
}; |
||||
} catch { |
||||
write-host "error during runspace tidyup"; |
||||
Write-Error "error: $($PSItem.ToString())"; |
||||
Write-Host "Detailed Exception Message: $($PSItem.Exception.Message)"; |
||||
} finally { |
||||
}; |
||||
|
||||
try { |
||||
if ($null -ne $powershell) { |
||||
#Write-Host "tidying up powershell for $pipeName"; |
||||
$powershell.dispose(); |
||||
}; |
||||
} finally { |
||||
}; |
||||
|
||||
}; |
||||
write-host "consolemode_server_async.ps1 shutdown for pipe $pipeName"; |
||||
exit 0; |
||||
|
||||
|
||||
|
||||
#!SEMICOLONS must be placed after each command as scriptdata needs to be sent to powershell directly with the -c parameter! |
||||
; |
||||
# consolemode_server_async.ps1 - punkshell console-mode fallback server (canonical copy) (G-106) |
||||
# Persistent named-pipe server used by punk::console when twapi is absent on windows. |
||||
# The Tcl side (punk::console::system) resolves this file - or falls back to its embedded copy - and launches: |
||||
# pwsh|powershell -nop -nol -noni -c <this text with the <punkshell_*> placeholders substituted> |
||||
# The -c (command string) mechanism is deliberate: it needs no script file at run time (kits) and is not |
||||
# subject to ExecutionPolicy restrictions that can block -File runs. |
||||
# The server process shares the launching process's console; it must NOT have its stdin redirected |
||||
# (the console input handle is how it reaches the console), and it never reads stdin. |
||||
# Placeholders (each overridable by a positional arg for standalone debug runs): |
||||
# <punkshell_consoleid> $args[0] unique id suffix for the pipe name |
||||
# <punkshell_parentpid> $args[1] pid of the owning tcl process; the server exits when it does |
||||
# <punkshell_psdebug> $args[2] "1" enables diagnostic write-host output (default silent) |
||||
# Standalone debug run example (from this directory): |
||||
# pwsh -nop -nol -f consolemode_server_async.ps1 test1 0 1 |
||||
# Protocol (one line per named-pipe connection): enableraw | disableraw | ping | exit |
||||
# MAINTENANCE: src/modules/punk/console-999999.0a1.0.tm carries this file's text as |
||||
# punk::console::system::ps_consolemode_script_embedded (the last-resort resolution for kits and |
||||
# unusual cwds). Keep the two in sync - the console testsuite (psfallback.test) fails when they diverge. |
||||
; |
||||
# The NativeConsoleMethods C# snippet below derives from posh-git (github.com/dahlbyk/posh-git): |
||||
# Copyright (c) 2010-2018 Keith Dahlby, Keith Hill, and contributors - MIT license. |
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: |
||||
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||||
; |
||||
if ($PSVersionTable.PSVersion.Major -le 5) { |
||||
# For Windows PowerShell, we want to remove any PowerShell 7 paths from PSModulePath |
||||
#snipped from https://github.com/PowerShell/DSC/pull/777/commits/af9b99a4d38e0cf1e54c4bbd89cbb6a8a8598c4e |
||||
; |
||||
$env:PSModulePath = ($env:PSModulePath -split ';' | Where-Object { $_ -notlike '*\powershell\*' }) -join ';'; |
||||
}; |
||||
|
||||
$consoleModeSource = @" |
||||
using System; |
||||
using System.Runtime.InteropServices; |
||||
|
||||
public class NativeConsoleMethods |
||||
{ |
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] |
||||
public static extern IntPtr GetStdHandle(int handleId); |
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] |
||||
public static extern bool GetConsoleMode(IntPtr hConsoleOutput, out uint dwMode); |
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] |
||||
public static extern bool SetConsoleMode(IntPtr hConsoleOutput, uint dwMode); |
||||
|
||||
public static uint GetConsoleMode(bool input = false) |
||||
{ |
||||
var handle = GetStdHandle(input ? -10 : -11); |
||||
uint mode; |
||||
if (GetConsoleMode(handle, out mode)) |
||||
{ |
||||
return mode; |
||||
} |
||||
return 0xffffffff; |
||||
} |
||||
|
||||
public static uint SetConsoleMode(bool input, uint mode) |
||||
{ |
||||
var handle = GetStdHandle(input ? -10 : -11); |
||||
if (SetConsoleMode(handle, mode)) |
||||
{ |
||||
return GetConsoleMode(input); |
||||
} |
||||
return 0xffffffff; |
||||
} |
||||
} |
||||
"@ |
||||
; |
||||
[Flags()] |
||||
enum ConsoleModeInputFlags |
||||
{ |
||||
ENABLE_PROCESSED_INPUT = 0x0001 |
||||
ENABLE_LINE_INPUT = 0x0002 |
||||
ENABLE_ECHO_INPUT = 0x0004 |
||||
ENABLE_WINDOW_INPUT = 0x0008 |
||||
ENABLE_MOUSE_INPUT = 0x0010 |
||||
ENABLE_INSERT_MODE = 0x0020 |
||||
ENABLE_QUICK_EDIT_MODE = 0x0040 |
||||
ENABLE_EXTENDED_FLAGS = 0x0080 |
||||
ENABLE_AUTO_POSITION = 0x0100 |
||||
ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200 |
||||
}; |
||||
|
||||
if (!('NativeConsoleMethods' -as [System.Type])) { |
||||
Add-Type $consoleModeSource |
||||
}; |
||||
|
||||
function rawmode { |
||||
param ( |
||||
[validateSet('enable', 'disable')] |
||||
[string]$Action |
||||
); |
||||
if (!('NativeConsoleMethods' -as [System.Type])) { |
||||
Add-Type $consoleModeSource |
||||
}; |
||||
$inputFlags = [NativeConsoleMethods]::GetConsoleMode($true); |
||||
$resultflags = $inputflags; |
||||
if (($inputflags -band [ConsoleModeInputFlags]::ENABLE_LINE_INPUT) -eq [ConsoleModeInputFlags]::ENABLE_LINE_INPUT) { |
||||
#cooked mode |
||||
$initialstate = "cooked"; |
||||
if ($action -eq "enable") { |
||||
#disable cooked flags |
||||
$disable = [uint32](-bnot [uint32][ConsoleModeInputFlags]::ENABLE_LINE_INPUT) -band ( -bnot [uint32][ConsoleModeInputFlags]::ENABLE_ECHO_INPUT); |
||||
$adjustedflags = $inputflags -band ($disable); |
||||
$resultflags = [NativeConsoleMethods]::SetConsoleMode($true,$adjustedflags); |
||||
}; |
||||
} else { |
||||
#raw mode |
||||
$initialstate = "raw"; |
||||
if ($action -eq "disable") { |
||||
#set cooked flags |
||||
$adjustedflags = $inputflags -bor [ConsoleModeInputFlags]::ENABLE_LINE_INPUT -bor [ConsoleModeInputFlags]::ENABLE_ECHO_INPUT; |
||||
$resultflags = [NativeConsoleMethods]::SetConsoleMode($true,$adjustedflags); |
||||
}; |
||||
}; |
||||
}; |
||||
|
||||
$consoleid = $args[0]; |
||||
if ([string]::IsNullOrEmpty($consoleid)) { |
||||
$consoleid = "<punkshell_consoleid>" |
||||
}; |
||||
$parentpidraw = $args[1]; |
||||
if ([string]::IsNullOrEmpty($parentpidraw)) { |
||||
$parentpidraw = "<punkshell_parentpid>" |
||||
}; |
||||
$psdebugraw = $args[2]; |
||||
if ([string]::IsNullOrEmpty($psdebugraw)) { |
||||
$psdebugraw = "<punkshell_psdebug>" |
||||
}; |
||||
$psdebug = ($psdebugraw -eq "1"); |
||||
$pipeName = "punkshell_ps_consolemode_$consoleid"; |
||||
function dbg { |
||||
param([string]$m); |
||||
if ($psdebug) { |
||||
write-host "consolemode_server($pipeName): $m" |
||||
}; |
||||
}; |
||||
dbg "starting - parentpidraw: $parentpidraw"; |
||||
|
||||
# Parent-process watch: hold a Process object obtained once by pid (handle-based, so immune to |
||||
# pid reuse) and poll HasExited in the main loop. This is the orphan-prevention guarantee - the |
||||
# owning tcl process does not need to send exit for this server to go away (G-106). |
||||
# An unparseable/zero parent pid (e.g a standalone run with placeholders unsubstituted) disables the watch. |
||||
$parentproc = $null; |
||||
[int]$parentpidnum = 0; |
||||
if ([int]::TryParse($parentpidraw, [ref]$parentpidnum) -and ($parentpidnum -gt 0)) { |
||||
try { |
||||
$parentproc = [System.Diagnostics.Process]::GetProcessById($parentpidnum); |
||||
} catch { |
||||
dbg "parent process $parentpidnum not found - exiting"; |
||||
exit 1; |
||||
}; |
||||
}; |
||||
|
||||
$sharedData = [hashtable]::Synchronized(@{}); |
||||
$scriptblock = { |
||||
param($tsv); |
||||
Add-Type -AssemblyName System.IO.Pipes; |
||||
$keepgoing = $true; |
||||
while ($keepgoing) { |
||||
$pipeServer = $null; |
||||
try { |
||||
$pipeServer = New-Object System.IO.Pipes.NamedPipeServerStream($pipeName); |
||||
try { |
||||
$pipeServer.WaitForConnection(); |
||||
$reader = New-Object System.IO.StreamReader($pipeServer); |
||||
$message = $reader.ReadLine(); |
||||
if ($message -eq "exit") { |
||||
$tsv.State = "done"; |
||||
[void]$msync.Set(); |
||||
$keepgoing = $false; |
||||
} elseif (($message -eq "enableraw") -or ($message -eq "disableraw")) { |
||||
$tsv.Message = $message; |
||||
[void]$msync.Set(); |
||||
} elseif ($message -eq "ping") { |
||||
# liveness probe - wake the main loop, no mode action |
||||
; |
||||
[void]$msync.Set(); |
||||
}; |
||||
# A null message (a connection that closed without sending a line - e.g a probe) |
||||
# and unknown messages are ignored; only an explicit exit message or |
||||
# parent-process death shuts the server down. |
||||
# Do NOT Close/Dispose the StreamReader here: that disposes the underlying pipe |
||||
# stream and a subsequent Disconnect/Dispose on it throws, killing this listener. |
||||
# Disposing the pipe stream (finally below) is the whole per-connection cleanup. |
||||
} finally { |
||||
if ($null -ne $pipeServer) { |
||||
$pipeServer.Dispose(); |
||||
}; |
||||
}; |
||||
} catch { |
||||
# unexpected listener failure (e.g pipe name already in use) - shut down rather than spin |
||||
; |
||||
$tsv.State = "done"; |
||||
[void]$msync.Set(); |
||||
$keepgoing = $false; |
||||
}; |
||||
}; |
||||
}; |
||||
|
||||
$exitcode = 0; |
||||
try { |
||||
# AutoResetEvent: WaitOne consumes the signal atomically, so a Set that lands while the main |
||||
# loop is servicing a message is never lost (the next WaitOne returns immediately). |
||||
$syncEvent = New-Object System.Threading.AutoResetEvent($false); |
||||
|
||||
$runspace = [runspacefactory]::CreateRunspace(); |
||||
[void]$runspace.Open(); |
||||
$runspace.SessionStateProxy.SetVariable("pipeName", $pipeName); |
||||
$runspace.SessionStateProxy.SetVariable("msync", $syncEvent); |
||||
|
||||
$powershell = [System.Management.Automation.PowerShell]::Create(); |
||||
$powershell.Runspace = $runspace; |
||||
[void]$powershell.Addscript($scriptblock).AddArgument($sharedData); |
||||
|
||||
$sharedData.State = "running"; |
||||
$sharedData.Message = ""; |
||||
$asyncResult = $powershell.BeginInvoke(); |
||||
|
||||
dbg "named pipe server started in runspace"; |
||||
while ($true) { |
||||
[void]$syncEvent.WaitOne(5000); |
||||
$msg = $sharedData.Message; |
||||
$sharedData.Message = ""; |
||||
if ($msg -eq "enableraw") { |
||||
dbg "enableraw"; |
||||
$null = rawmode 'enable'; |
||||
} elseif ($msg -eq "disableraw") { |
||||
dbg "disableraw"; |
||||
$null = rawmode 'disable'; |
||||
}; |
||||
if ($sharedData.State -eq "done") { |
||||
dbg "exit message received"; |
||||
break; |
||||
}; |
||||
if (($null -ne $parentproc) -and $parentproc.HasExited) { |
||||
dbg "parent process $parentpidnum has exited"; |
||||
break; |
||||
}; |
||||
}; |
||||
} finally { |
||||
# The listener runspace may be parked in WaitForConnection - connect once and send exit to |
||||
# unblock it, otherwise the process could linger on a parked runspace thread. Skipped when |
||||
# the listener already shut itself down (State done - it exited on an exit message or error). |
||||
if ($sharedData.State -ne "done") { |
||||
try { |
||||
$cli = New-Object System.IO.Pipes.NamedPipeClientStream($pipeName); |
||||
$cli.connect(1000); |
||||
$writer = new-object System.IO.StreamWriter($cli); |
||||
$writer.writeline("exit"); |
||||
$writer.flush(); |
||||
$cli.Dispose(); |
||||
} catch { |
||||
dbg "listener unblock skipped: $($PSItem.Exception.Message)"; |
||||
}; |
||||
}; |
||||
try { |
||||
if ($null -ne $runspace) { |
||||
$runspace.Close(); |
||||
$runspace.Dispose(); |
||||
}; |
||||
} catch { |
||||
dbg "runspace tidyup error: $($PSItem.Exception.Message)"; |
||||
}; |
||||
try { |
||||
if ($null -ne $powershell) { |
||||
$powershell.dispose(); |
||||
}; |
||||
} catch { |
||||
dbg "powershell tidyup error: $($PSItem.Exception.Message)"; |
||||
}; |
||||
}; |
||||
dbg "shutdown complete"; |
||||
exit $exitcode; |
||||
|
||||
@ -1,266 +0,0 @@
|
||||
#!SEMICOLONS must be placed after each command as scriptdata needs to be sent to powershell directly with the -c parameter! |
||||
; |
||||
|
||||
if ($PSVersionTable.PSVersion.Major -le 5) { |
||||
# For Windows PowerShell, we want to remove any PowerShell 7 paths from PSModulePath |
||||
#snipped from https://github.com/PowerShell/DSC/pull/777/commits/af9b99a4d38e0cf1e54c4bbd89cbb6a8a8598c4e |
||||
#Presumably users are supposed to know not to have custom paths for powershell desktop containing a 'powershell' subfolder?? |
||||
; |
||||
$env:PSModulePath = ($env:PSModulePath -split ';' | Where-Object { $_ -notlike '*\powershell\*' }) -join ';'; |
||||
}; |
||||
|
||||
$consoleModeSource = @" |
||||
using System; |
||||
using System.Runtime.InteropServices; |
||||
|
||||
public class NativeConsoleMethods |
||||
{ |
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] |
||||
public static extern IntPtr GetStdHandle(int handleId); |
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] |
||||
public static extern bool GetConsoleMode(IntPtr hConsoleOutput, out uint dwMode); |
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] |
||||
public static extern bool SetConsoleMode(IntPtr hConsoleOutput, uint dwMode); |
||||
|
||||
public static uint GetConsoleMode(bool input = false) |
||||
{ |
||||
var handle = GetStdHandle(input ? -10 : -11); |
||||
uint mode; |
||||
if (GetConsoleMode(handle, out mode)) |
||||
{ |
||||
return mode; |
||||
} |
||||
return 0xffffffff; |
||||
} |
||||
|
||||
public static uint SetConsoleMode(bool input, uint mode) |
||||
{ |
||||
var handle = GetStdHandle(input ? -10 : -11); |
||||
if (SetConsoleMode(handle, mode)) |
||||
{ |
||||
return GetConsoleMode(input); |
||||
} |
||||
return 0xffffffff; |
||||
} |
||||
} |
||||
"@ |
||||
; |
||||
[Flags()] |
||||
enum ConsoleModeInputFlags |
||||
{ |
||||
ENABLE_LINE_INPUT = 0x0002 |
||||
ENABLE_ECHO_INPUT = 0x0004 |
||||
}; |
||||
|
||||
if (!('NativeConsoleMethods' -as [System.Type])) { |
||||
Add-Type $consoleModeSource |
||||
} |
||||
|
||||
function psmain { |
||||
param ( |
||||
[validateSet('enableRaw', 'disableRaw')] |
||||
[string]$Action |
||||
); |
||||
# $inputflags = Get-ConsoleMode -StandardInput; |
||||
if (!('NativeConsoleMethods' -as [System.Type])) { |
||||
Add-Type $consoleModeSource |
||||
} |
||||
$inputFlags = [NativeConsoleMethods]::GetConsoleMode($true); |
||||
$resultflags = $inputflags; |
||||
if (($inputflags -band [ConsoleModeInputFlags]::ENABLE_LINE_INPUT) -eq [ConsoleModeInputFlags]::ENABLE_LINE_INPUT) { |
||||
#cooked mode |
||||
$initialstate = "cooked"; |
||||
if ($action -eq "enableraw") { |
||||
#disable cooked flags |
||||
$disable = [uint32](-bnot [uint32][ConsoleModeInputFlags]::ENABLE_LINE_INPUT) -band ( -bnot [uint32][ConsoleModeInputFlags]::ENABLE_ECHO_INPUT); |
||||
$adjustedflags = $inputflags -band ($disable); |
||||
$resultflags = [NativeConsoleMethods]::SetConsoleMode($true,$adjustedflags); |
||||
} |
||||
} else { |
||||
#raw mode |
||||
$initialstate = "raw"; |
||||
if ($action -eq "disableraw") { |
||||
#set cooked flags |
||||
$adjustedflags = $inputflags -bor [ConsoleModeInputFlags]::ENABLE_LINE_INPUT -bor [ConsoleModeInputFlags]::ENABLE_ECHO_INPUT; |
||||
$resultflags = [NativeConsoleMethods]::SetConsoleMode($true,$adjustedflags); |
||||
} |
||||
} |
||||
#return in format that can act as a tcl dict |
||||
#write-host "startflags: $inputflags initialstate: $initialstate action: $Action endflags: $resultflags"; |
||||
}; |
||||
# psmain 'enableRaw' |
||||
; |
||||
$consoleid = $args[0]; |
||||
if ([string]::IsNullOrEmpty($consoleid)) { |
||||
$consoleid= "<punkshell_consoleid>" |
||||
}; |
||||
$pipeName = "punkshell_ps_consolemode_$consoleid"; |
||||
"pipename: $pipeName" |
||||
# Create the NamedPipeServerStream |
||||
$pipeServer = New-Object System.IO.Pipes.NamedPipeServerStream( |
||||
$pipeName, |
||||
[System.IO.Pipes.PipeDirection]::In, |
||||
1, |
||||
[System.IO.Pipes.PipeTransmissionMode]::Message, |
||||
[System.IO.Pipes.PipeOptions]::Asynchronous |
||||
); |
||||
; |
||||
#$pipeServer = New-Object System.IO.Pipes.NamedPipeServerStream($pipeName); |
||||
; |
||||
|
||||
|
||||
# Create a synchronization object (ManualResetEvent) to signal completion |
||||
; |
||||
$syncEvent = New-Object System.Threading.ManualResetEvent($false) |
||||
|
||||
# Define the callback function for BeginWaitForConnection |
||||
$connectionCallback = [System.AsyncCallback]{ |
||||
param($ar,$s); |
||||
# Get the NamedPipeServerStream from the AsyncResult object |
||||
; |
||||
$server = $ar.AsyncState; |
||||
# End the asynchronous wait operation |
||||
; |
||||
$server.EndWaitForConnection($ar); |
||||
Write-Host "Client connected!"; |
||||
# Create a new runspace to handle client communication |
||||
; |
||||
$runspace = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace(); |
||||
$runspace.Open(); |
||||
# Create a PowerShell pipeline within the runspace |
||||
; |
||||
$powershell = [System.Management.Automation.PowerShell]::Create(); |
||||
$powershell.Runspace = $runspace; |
||||
|
||||
$scriptBlock = { |
||||
param($pipeStream); |
||||
$reader = New-Object System.IO.StreamReader($pipeStream); |
||||
#$writer = New-Object System.IO.StreamWriter($pipeStream); |
||||
#$writer.AutoFlush = $true; |
||||
|
||||
$message = $reader.ReadLine(); |
||||
Write-Host "Received from client: $message"; |
||||
|
||||
#$response = "Server received: $message" |
||||
#$writer.WriteLine($response) |
||||
#Write-Host "Sent to client: $response" |
||||
|
||||
# Disconnect the pipe to allow new connections if desired |
||||
; |
||||
$pipeStream.Disconnect(); |
||||
}; |
||||
# Add the script block to the PowerShell pipeline and pass the pipe stream |
||||
; |
||||
$powershell.AddScript($scriptBlock).AddArgument($server); |
||||
# Invoke the pipeline asynchronously |
||||
; |
||||
$asyncResult = $powershell.BeginInvoke(); |
||||
# You can do other work here while the client communication happens in the runspace |
||||
; |
||||
; |
||||
# Wait for the pipeline to complete and close the runspace |
||||
$powershell.EndInvoke($asyncResult); |
||||
$powershell.Dispose(); |
||||
$runspace.Close(); |
||||
$runspace.Dispose(); |
||||
|
||||
Write-Host "Client communication handled. Waiting for next connection..."; |
||||
|
||||
$s.Set(); |
||||
|
||||
# Begin waiting for the next connection; |
||||
#$server.BeginWaitForConnection($callback, $server); |
||||
; |
||||
} |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
$global:keep_listening = $true; |
||||
|
||||
|
||||
while ($global:keep_listening) { |
||||
# Begin waiting for a client connection asynchronously |
||||
; |
||||
|
||||
$runspace = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace(); |
||||
$runspace.Open(); |
||||
# Create a PowerShell pipeline within the runspace |
||||
; |
||||
$powershell = [System.Management.Automation.PowerShell]::Create(); |
||||
$powershell.Runspace = $runspace; |
||||
|
||||
$scriptblock = { |
||||
param($p,$s); |
||||
Write-Host "Waiting for client connection on pipe: $p"; |
||||
$p.BeginWaitForConnection($connectionCallback, $p,$s); |
||||
$s.WaitOne(10000); |
||||
} |
||||
$powershell.AddScript($scriptBlock) |
||||
[void]$powershell.AddParameter('p',$pipeName); |
||||
[void]$powershell.AddParameter('s',$syncEvent); |
||||
$asyncResult = $powershell.BeginInvoke(); |
||||
# You can do other work here while the client communication happens in the runspace |
||||
; |
||||
write-host "interim" |
||||
; |
||||
# Wait for the pipeline to complete and close the runspace |
||||
$powershell.EndInvoke($asyncResult); |
||||
$powershell.Dispose(); |
||||
$runspace.Close(); |
||||
$runspace.Dispose(); |
||||
write-host "looping" |
||||
}; |
||||
|
||||
#$pipeServer.BeginWaitForConnection($connectionCallback, $pipeServer); |
||||
|
||||
Write-Host "Server shutting down."; |
||||
$pipeServer.Dispose(); |
||||
|
||||
|
||||
|
||||
#try { |
||||
# while ($true) { |
||||
# #"Waiting for connection on '$pipeName'"; |
||||
# $pipeServer.WaitForConnection(); |
||||
# #"Connection established"; |
||||
# $pipeReader = New-Object System.IO.StreamReader($pipeServer); |
||||
# #$pipeWriter = New-Object System.IO.StreamWriter($pipeServer); |
||||
# #$pipeWriter.AutoFlush = $true; |
||||
# $request = $pipeReader.ReadLine(); |
||||
# # "Received request: $request"; |
||||
# if ($request -eq "exit") { |
||||
# "consolemode_server.ps1 Exiting"; |
||||
# exit; |
||||
# } elseif ($request -eq "") { |
||||
# #"Empty input"; |
||||
# $pipeServer.Disconnect(); |
||||
# #"Disconnected"; |
||||
# continue; |
||||
# } elseif ($request -eq $none) { |
||||
# "Remote disconnected before sending"; |
||||
# $pipeServer.Disconnect(); |
||||
# "Disconnected"; |
||||
# continue; |
||||
# } elseif ($request -eq "enableraw") { |
||||
# #$result = psmain 'enableRaw'; |
||||
# $null = psmain 'enableRaw' |
||||
# # "Sending result: '$result'"; |
||||
# #$pipeWriter.Write($result); |
||||
# $pipeServer.Disconnect(); |
||||
# continue; |
||||
# } else { |
||||
# "consolemode_server.ps1 ignoring request: $request"; |
||||
# $pipeServer.Disconnect(); |
||||
# continue; |
||||
# } |
||||
# } |
||||
#} |
||||
#finally { |
||||
# $pipeServer.Dispose(); |
||||
#}; |
||||
|
||||
|
||||
@ -0,0 +1,156 @@
|
||||
#family_artifacts.tcl (G-103): emit punkbin-layout artifact copies + per-artifact |
||||
#toml metadata + sha1sums for the verified runtime kit family members. |
||||
# |
||||
#Deliberately run UNDER THE PLAIN FAMILY KIT itself (not the builder shell): the |
||||
#sha1 digests come from the kit's attached tcllib/tcllibc, so every emission run |
||||
#doubles as a proof that the family runtime executes real tooling self-contained. |
||||
# |
||||
#Artifact tier naming (G-103 naming decision): <workingname minus .exe>-r<N>.exe - |
||||
#immutable punkbin names; the -r<N> assembly revision comes from the invocation |
||||
#(-rev, suite option -Dfamilyrev). The emitted tree mirrors a punkbin platform |
||||
#folder: <outdir>/<artifact>.exe + <artifact>.toml + sha1sums.txt (punkbin |
||||
#format: '<sha1> *<filename>'). Publication to the real punkbin repo is a |
||||
#deliberate user step (copy + build_sha1sums.tcl there); it is DEFERRED per the |
||||
#goal notes until the family shape is accepted. |
||||
# |
||||
#args: -outdir <dir> -rev <N> -target <punkbin platform, e.g win32-x86_64> |
||||
# -suite <name> -tclpatch <patchlevel> -zig <version> -optimize <mode> |
||||
# -components {<name> <ver> ...} (attached battery versions, all variants) |
||||
# -bicomponents {<name> <ver> ...} (additional bi-only batteries) |
||||
# -provenance {<name> <uuid> ...} (source checkout uuids) |
||||
# -testreports <dir> (G-107 evidence summaries; optional) |
||||
# -kits {<variant> <workingexepath> ...} |
||||
|
||||
proc fail {msg} {puts stderr "family_artifacts FAIL: $msg"; flush stderr; exit 1} |
||||
proc note {msg} {puts stdout "family_artifacts: $msg"; flush stdout} |
||||
|
||||
array set opt { |
||||
-outdir {} -rev 1 -target {} -suite {} -tclpatch {} -zig {} -optimize {} |
||||
-components {} -bicomponents {} -provenance {} -testreports {} -kits {} |
||||
} |
||||
foreach {k v} $argv { |
||||
if {![info exists opt($k)]} {fail "unknown option '$k'"} |
||||
set opt($k) $v |
||||
} |
||||
foreach req {-outdir -target -suite -tclpatch -zig -optimize -components -kits} { |
||||
if {$opt($req) eq ""} {fail "missing required option $req"} |
||||
} |
||||
if {![string is integer -strict $opt(-rev)] || $opt(-rev) < 1} {fail "-rev must be a positive integer"} |
||||
|
||||
if {[catch {package require sha1} sha1ver]} { |
||||
fail "package require sha1 failed under [info nameofexecutable] - the family kit must carry tcllib: $sha1ver" |
||||
} |
||||
|
||||
proc toml_str {s} { |
||||
#basic toml string: escape backslash and double-quote (values here are names, |
||||
#versions, uuids, iso dates - no control chars expected) |
||||
return "\"[string map {\\ \\\\ \" \\\"} $s]\"" |
||||
} |
||||
|
||||
proc exe_split {name} { |
||||
#{root ext} splitting only a .exe suffix - dotted tcl patchlevels make |
||||
#[file rootname] wrong for extensionless (unix) artifact names |
||||
#(tclsh9.0.5-punk-r1 would truncate at the last version dot) |
||||
if {[string match -nocase "*.exe" $name]} { |
||||
return [list [string range $name 0 end-4] .exe] |
||||
} |
||||
return [list $name ""] |
||||
} |
||||
|
||||
file mkdir $opt(-outdir) |
||||
set built [clock format [clock seconds] -format %Y-%m-%dT%H:%M:%SZ -timezone :UTC] |
||||
set sha1lines {} |
||||
set emitted {} |
||||
|
||||
foreach {variant kitpath} $opt(-kits) { |
||||
set kitpath [file normalize $kitpath] |
||||
if {![file exists $kitpath]} {fail "kit exe not found: $kitpath"} |
||||
set working [file tail $kitpath] |
||||
lassign [exe_split $working] wroot wext |
||||
set artifact "$wroot-r$opt(-rev)$wext" |
||||
set dest [file join $opt(-outdir) $artifact] |
||||
file delete -force $dest |
||||
file copy $kitpath $dest |
||||
set sha1 [sha1::sha1 -hex -file $dest] |
||||
set size [file size $dest] |
||||
lappend sha1lines "$sha1 *$artifact" |
||||
|
||||
set components $opt(-components) |
||||
if {$variant eq "punk-bi"} {lappend components {*}$opt(-bicomponents)} |
||||
set batteries {} |
||||
foreach {n v} $components {lappend batteries [toml_str "$n $v"]} |
||||
|
||||
set m {} |
||||
lappend m "#punkshell runtime artifact metadata (G-103) - generated by family_artifacts.tcl" |
||||
lappend m "\[artifact\]" |
||||
lappend m "name = [toml_str $artifact]" |
||||
lappend m "class = \"runtime\"" |
||||
lappend m "variant = [toml_str $variant]" |
||||
lappend m "working_name = [toml_str $working]" |
||||
lappend m "revision = $opt(-rev)" |
||||
lappend m "target = [toml_str $opt(-target)]" |
||||
lappend m "sha1 = [toml_str $sha1]" |
||||
lappend m "size = $size" |
||||
lappend m "built = [toml_str $built]" |
||||
lappend m "" |
||||
lappend m "\[runtime\]" |
||||
lappend m "tcl_patchlevel = [toml_str $opt(-tclpatch)]" |
||||
set pr [expr {$variant ne "plain"}] |
||||
lappend m "piperepl = [expr {$pr ? "true" : "false"}]" |
||||
if {$pr} { |
||||
lappend m "piperepl_default = \"on\"" |
||||
lappend m "piperepl_opt_out = \"TCLSH_PIPEREPL=0\"" |
||||
} |
||||
lappend m "attached_batteries = \[[join $batteries {, }]\]" |
||||
lappend m "" |
||||
lappend m "\[provenance\]" |
||||
lappend m "suite = [toml_str $opt(-suite)]" |
||||
lappend m "toolchain = [toml_str "zig $opt(-zig)"]" |
||||
lappend m "optimize = [toml_str $opt(-optimize)]" |
||||
foreach {n uuid} $opt(-provenance) { |
||||
lappend m "${n}_checkout = [toml_str $uuid]" |
||||
} |
||||
#G-107 evidence summaries available at emission time (result lines only; the |
||||
#full line-record summaries stay the canonical evidence artifacts) |
||||
if {$opt(-testreports) ne "" && [file isdirectory $opt(-testreports)]} { |
||||
set tlines {} |
||||
foreach sf [lsort [glob -nocomplain -directory $opt(-testreports) *.summary]] { |
||||
set rec [dict create] |
||||
set f [open $sf r] |
||||
foreach line [split [read $f] \n] { |
||||
set line [string trim $line] |
||||
if {$line eq "" || [string index $line 0] eq "#"} continue |
||||
if {[catch {llength $line} n] || $n < 2} continue |
||||
dict set rec [lindex $line 0] [lrange $line 1 end] |
||||
} |
||||
close $f |
||||
if {![dict exists $rec library] || ![dict exists $rec result]} continue |
||||
set lib [dict get $rec library] |
||||
set parts [list "result=[dict get $rec result]"] |
||||
foreach fkey {mode total passed skipped failed} { |
||||
if {[dict exists $rec $fkey]} {lappend parts "$fkey=[dict get $rec $fkey]"} |
||||
} |
||||
lappend tlines "$lib = [toml_str [join $parts { }]]" |
||||
} |
||||
if {[llength $tlines]} { |
||||
lappend m "" |
||||
lappend m "\[tests\]" |
||||
lappend m {*}$tlines |
||||
} |
||||
} |
||||
set mf [file join $opt(-outdir) "[lindex [exe_split $artifact] 0].toml"] |
||||
set f [open $mf w] |
||||
fconfigure $f -translation lf |
||||
puts $f [join $m \n] |
||||
close $f |
||||
lappend emitted "$variant -> $artifact" |
||||
note "emitted $artifact (sha1 $sha1, [expr {$size/1024}] KB) + [file tail $mf]" |
||||
} |
||||
|
||||
set f [open [file join $opt(-outdir) sha1sums.txt] w] |
||||
fconfigure $f -translation lf |
||||
puts $f [join $sha1lines \n] |
||||
close $f |
||||
note "sha1sums.txt written ([llength $sha1lines] artifacts)" |
||||
puts "family_artifacts OK: [join $emitted {; }] -> $opt(-outdir)" |
||||
exit 0 |
||||
@ -0,0 +1,201 @@
|
||||
#family_check.tcl (G-103): self-containment verification for one runtime kit |
||||
#family member (plain / punk / punk-bi). Run under the suite-built BUILDER shell; |
||||
#the KIT exe under test is exec'd as probe children. |
||||
# |
||||
#Why the copy-to-scratch: beside the installed out/family location sits ../lib |
||||
#(the suite prefix), which the stock tm-path setup (<exedir>/../lib) and |
||||
#tcl_findLibrary's exe-relative entries would silently satisfy. The kit is copied |
||||
#alone into a fresh scratch dir and probed FROM THERE with cwd=scratch, so only |
||||
#the attached image can serve the probes ("no external Tcl visible"). |
||||
# |
||||
#The probe environment is scrubbed of every tcl redirection var (TCL_LIBRARY, |
||||
#TK_LIBRARY, TCLLIBPATH, VFS_LIBRARY, TCL*_TM_PATH); TCLSH_PIPEREPL is controlled |
||||
#per probe - it is the punk-vs-plain discriminator (G-096 behaviour matrix: with |
||||
#the gate open the patched shell publishes ::tclsh(istty) before the startup |
||||
#script runs; with TCLSH_PIPEREPL=0 or unpatched sources no ::tclsh machinery |
||||
#exists). Script-arg probes never set ::tclsh(dorepl), so no probe can land in |
||||
#the console-reopen repl (no hang risk). |
||||
# |
||||
#args: -exe <kitexe> -variant plain|punk|punk-bi -expectpatch <patchlevel> |
||||
# -thread <ver> -vfs <ver> -tcllib <ver> ?-tk <patchlevel>? ?-tklib <ver>? |
||||
|
||||
proc fail {msg} {puts stderr "family_check FAIL: $msg"; flush stderr; exit 1} |
||||
proc note {msg} {puts stdout "family_check: $msg"; flush stdout} |
||||
|
||||
array set opt {-exe {} -variant {} -expectpatch {} -thread {} -vfs {} -tcllib {} -tk {} -tklib {}} |
||||
foreach {k v} $argv { |
||||
if {![info exists opt($k)]} {fail "unknown option '$k'"} |
||||
set opt($k) $v |
||||
} |
||||
foreach req {-exe -variant -expectpatch -thread -vfs -tcllib} { |
||||
if {$opt($req) eq ""} {fail "missing required option $req"} |
||||
} |
||||
if {$opt(-variant) ni {plain punk punk-bi}} {fail "-variant must be plain|punk|punk-bi"} |
||||
if {$opt(-variant) eq "punk-bi" && ($opt(-tk) eq "" || $opt(-tklib) eq "")} { |
||||
fail "punk-bi variant requires -tk and -tklib expected versions" |
||||
} |
||||
set exe [file normalize $opt(-exe)] |
||||
if {![file exists $exe]} {fail "kit exe not found: $exe"} |
||||
|
||||
#-- scrub the probe environment (children inherit ::env at exec time) --------- |
||||
foreach ev [array names ::env] { |
||||
if {$ev in {TCL_LIBRARY TK_LIBRARY TCLLIBPATH VFS_LIBRARY TCLSH_PIPEREPL} |
||||
|| [string match TCL*_TM_PATH $ev]} { |
||||
unset ::env($ev) |
||||
} |
||||
} |
||||
|
||||
#-- scratch dir with the kit copied in alone --------------------------------- |
||||
set scratch [file join [file tempdir] "punkfamilycheck_[pid]_[clock clicks -microseconds]"] |
||||
file mkdir $scratch |
||||
set kitname [file tail $exe] |
||||
set kit [file join $scratch $kitname] |
||||
file copy $exe $kit |
||||
|
||||
proc probe {name script args} { |
||||
#write the probe script into the scratch dir and run the kit on it with |
||||
#cwd=scratch; env pairs in $args are set for the child and removed after. |
||||
#returns the child output; fails hard on nonzero exit. |
||||
global scratch kit |
||||
set sf [file join $scratch probe_$name.tcl] |
||||
set f [open $sf w] |
||||
puts $f $script |
||||
close $f |
||||
foreach {ev val} $args {set ::env($ev) $val} |
||||
set rc [catch {exec $kit $sf 2>@1} out] |
||||
foreach {ev val} $args {unset ::env($ev)} |
||||
if {$rc} {fail "probe '$name' failed (kit $kit):\n$out"} |
||||
return $out |
||||
} |
||||
proc checks {output} { |
||||
#parse 'CHECK <key> <value>' lines into a dict |
||||
set d [dict create] |
||||
foreach line [split $output \n] { |
||||
set line [string trim $line] |
||||
if {[string match "CHECK *" $line]} { |
||||
dict set d [lindex $line 1] [lindex $line 2] |
||||
} |
||||
} |
||||
return $d |
||||
} |
||||
proc asserteq {d key expect what} { |
||||
if {![dict exists $d $key]} {fail "$what: no CHECK line for '$key' (got: $d)"} |
||||
set got [dict get $d $key] |
||||
if {$got ne $expect} {fail "$what: $key = '$got', expected '$expect'"} |
||||
} |
||||
|
||||
#-- probe: core batteries (all variants) ------------------------------------- |
||||
#Also proves cwd independence: the probe chdirs into a subdir it creates, so |
||||
#relative-path leakage from the scratch dir itself would surface. |
||||
set core_script { |
||||
proc out {k v} {puts [list CHECK $k $v]} |
||||
out patchlevel [info patchlevel] |
||||
out tcl_library $::tcl_library |
||||
out library_zipfs [string match //zipfs:/app/* $::tcl_library] |
||||
#attached-library facts (suite_smoke parity) |
||||
out tz [expr {[catch {clock format 0 -gmt 0 -format %Z}] ? "FAIL" : "ok"}] |
||||
out encoding [expr {[catch {encoding convertto cp1250 test}] ? "FAIL" : "ok"}] |
||||
#tm path serves modules from the attached image (tcl9/<ver> beside tcl_library) |
||||
out platform_tm [expr {[catch {package require platform} v] ? "FAIL:$v" : "ok"}] |
||||
#Thread: version + functional cross-thread eval |
||||
if {[catch {package require Thread} tver]} {out thread "FAIL:$tver"} else { |
||||
out thread $tver |
||||
set tid [thread::create] |
||||
out thread_eval [thread::send $tid {expr {6*7}}] |
||||
thread::release $tid |
||||
} |
||||
#tclvfs: version + representative vfs::* + functional zip mount round-trip |
||||
#(the kit's own zipfs mkzip makes the test archive - no external tools) |
||||
if {[catch {package require vfs} vver]} {out vfs "FAIL:$vver"} else { |
||||
out vfs $vver |
||||
out vfs_zip [expr {[catch {package require vfs::zip} zv] ? "FAIL:$zv" : $zv}] |
||||
out vfs_urltype [expr {[catch {package require vfs::urltype} uv] ? "FAIL:$uv" : $uv}] |
||||
set d [file join [pwd] vfstest] |
||||
file mkdir $d/payload |
||||
set f [open $d/payload/hello.txt w]; puts -nonewline $f "family-vfs-roundtrip"; close $f |
||||
cd $d |
||||
tcl::zipfs::mkzip probe.zip payload payload |
||||
set mnt [vfs::zip::Mount [file join $d probe.zip] zipmnt] |
||||
set f [open zipmnt/hello.txt r]; set data [read $f]; close $f |
||||
vfs::zip::Unmount $mnt zipmnt |
||||
out vfs_roundtrip [expr {$data eq "family-vfs-roundtrip" ? "ok" : "FAIL:$data"}] |
||||
} |
||||
#tcllib module + tcllibc acceleration engaged (pkg_smoke -accel parity) |
||||
if {[catch {package require md5} mver]} {out md5 "FAIL:$mver"} else { |
||||
out md5 $mver |
||||
out tcllibc [expr {[catch {package require tcllibc} cv] ? "FAIL:$cv" : $cv}] |
||||
upvar #0 ::md5::accel accelarr |
||||
out md5_accel [expr {[info exists accelarr(critcl)] && $accelarr(critcl) ? 1 : 0}] |
||||
} |
||||
exit 0 |
||||
} |
||||
set d [checks [probe core $core_script]] |
||||
asserteq $d patchlevel $opt(-expectpatch) "core" |
||||
asserteq $d library_zipfs 1 "core (tcl_library=[dict get $d tcl_library])" |
||||
asserteq $d tz ok "core" |
||||
asserteq $d encoding ok "core" |
||||
asserteq $d platform_tm ok "core" |
||||
asserteq $d thread $opt(-thread) "core" |
||||
asserteq $d thread_eval 42 "core" |
||||
asserteq $d vfs $opt(-vfs) "core" |
||||
asserteq $d vfs_roundtrip ok "core" |
||||
asserteq $d md5_accel 1 "core (tcllibc=[expr {[dict exists $d tcllibc]?[dict get $d tcllibc]:"?"}])" |
||||
foreach k {vfs_zip vfs_urltype tcllibc} { |
||||
if {[string match FAIL* [dict get $d $k]]} {fail "core: $k [dict get $d $k]"} |
||||
} |
||||
note "core OK ($opt(-variant)): tcl [dict get $d patchlevel] library [dict get $d tcl_library] thread [dict get $d thread] vfs [dict get $d vfs] (zip [dict get $d vfs_zip], urltype [dict get $d vfs_urltype]) md5 [dict get $d md5] accel=1" |
||||
|
||||
#-- probe: Tk create/destroy (bi only) --------------------------------------- |
||||
if {$opt(-variant) eq "punk-bi"} { |
||||
set tk_script { |
||||
proc out {k v} {puts [list CHECK $k $v]} |
||||
if {[catch {package require Tk} tkver]} {out tk "FAIL:$tkver"; exit 0} |
||||
out tk $tkver |
||||
wm withdraw . |
||||
button .b -text family |
||||
out tk_widget [winfo exists .b] |
||||
destroy .b |
||||
out tk_widget_destroyed [expr {![winfo exists .b]}] |
||||
destroy . |
||||
out tk_done ok |
||||
exit 0 |
||||
} |
||||
set d [checks [probe tk $tk_script]] |
||||
asserteq $d tk $opt(-tk) "tk" |
||||
asserteq $d tk_widget 1 "tk" |
||||
asserteq $d tk_widget_destroyed 1 "tk" |
||||
asserteq $d tk_done ok "tk" |
||||
#tklib rides only in the bi payload |
||||
set tklib_script { |
||||
proc out {k v} {puts [list CHECK $k $v]} |
||||
package require Tk |
||||
wm withdraw . |
||||
out tooltip [expr {[catch {package require tooltip} v] ? "FAIL:$v" : $v}] |
||||
destroy . |
||||
exit 0 |
||||
} |
||||
set d [checks [probe tklib $tklib_script]] |
||||
if {[string match FAIL* [dict get $d tooltip]]} {fail "tklib: tooltip [dict get $d tooltip]"} |
||||
note "tk OK: Tk $opt(-tk) create/destroy + tklib tooltip [dict get $d tooltip]" |
||||
} |
||||
|
||||
#-- probe: piperepl gate (variant discriminator) ------------------------------ |
||||
set gate_script {puts [list CHECK tclshmachinery [info exists ::tclsh(istty)]]; exit 0} |
||||
set d_default [checks [probe gate_default $gate_script]] |
||||
set d_optout [checks [probe gate_optout $gate_script TCLSH_PIPEREPL 0]] |
||||
if {$opt(-variant) eq "plain"} { |
||||
#unpatched: no machinery regardless of the env var (even explicitly enabled) |
||||
asserteq $d_default tclshmachinery 0 "piperepl (plain, env unset)" |
||||
set d_on [checks [probe gate_on $gate_script TCLSH_PIPEREPL 1]] |
||||
asserteq $d_on tclshmachinery 0 "piperepl (plain, TCLSH_PIPEREPL=1)" |
||||
note "piperepl OK (plain): stock behaviour - no ::tclsh machinery with env unset or =1" |
||||
} else { |
||||
#punk kits: ACTIVE BY DEFAULT (machinery published), documented opt-out =0 |
||||
asserteq $d_default tclshmachinery 1 "piperepl ($opt(-variant), env unset - default must be ON)" |
||||
asserteq $d_optout tclshmachinery 0 "piperepl ($opt(-variant), TCLSH_PIPEREPL=0 opt-out)" |
||||
note "piperepl OK ($opt(-variant)): active by default, disabled via TCLSH_PIPEREPL=0" |
||||
} |
||||
|
||||
file delete -force $scratch |
||||
puts "family_check OK: $opt(-variant) $kitname self-contained (scratch-dir probes, scrubbed env)" |
||||
exit 0 |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,237 @@
|
||||
# -*- tcl -*- |
||||
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from <pkg>-buildversion.txt |
||||
# |
||||
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. |
||||
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. |
||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||
# (C) 2026 |
||||
# |
||||
# @@ Meta Begin |
||||
# Application punk::platform 999999.0a1.0 |
||||
# Meta platform tcl |
||||
# Meta license BSD |
||||
# @@ Meta End |
||||
|
||||
|
||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||
## Requirements |
||||
package require platform ;#Tcl's own platform package (vendored 1.0.19 in kits; 1.1.x in tcl9 core) - the RAW identifier underneath this module's normalization |
||||
|
||||
|
||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||
namespace eval punk::platform { |
||||
#punk::platform - the CANONICAL punkshell platform naming layer (2026-07-22 |
||||
#platform-folder synchronization; groundwork for the G-105 cross-target arc). |
||||
# |
||||
#punkshell artifact trees are organized by PLATFORM-DIR names of the form |
||||
#<os>-<cpu> (e.g win32-x86_64), used by: the punkbin artifact repo's top-level |
||||
#folders, bin/runtime/<platform>/ (the runtime store punk-runtime manages), |
||||
#lib_tclX/<platform> + vendorlib_tclX/<platform> auto_path dirs (wired by the |
||||
#boot machinery), and the G-103 runtime artifact metadata 'target' field. |
||||
# |
||||
#Tcl's platform package remains the raw identifier (platform::generic / |
||||
#platform::identify) but its outputs vary by version and pass some machine |
||||
#names through unmapped - the differences this module's normalize absorbs: |
||||
# - cpu 'amd64' (BSD machine names) -> x86_64 |
||||
# - cpu 'aarch64' (linux arm64) -> arm64 |
||||
# - os 'macos' (platform 1.1.x on osVersion>19; older versions and the |
||||
# boot's snipped copy say 'macosx') -> macosx |
||||
# - macosx cpu 'arm' -> arm64 (platform::generic's 'arm*' glob collapses |
||||
# the darwin 'arm64' machine name to 'arm'; Apple silicon is 64-bit only, |
||||
# so the arm token on macosx always means arm64. Elsewhere 'arm' is a |
||||
# genuine 32-bit token and is preserved.) |
||||
#The special name 'macosx' (no cpu suffix) is the RUNTIME-tier convention for |
||||
#universal (multi-arch) macOS binaries - punkbin and bin/runtime keep one |
||||
#universal folder; the per-arch macosx-x86_64/macosx-arm64 names serve the |
||||
#lib-tree tier where per-arch payloads are the norm. |
||||
# |
||||
#The boot machinery (punkboot::lib in src/vfs/_config/punk_main.tcl / |
||||
#project_main.tcl and src/make.tcl) cannot 'package require' at its stage and |
||||
#carries a snipped platform::generic plus an inline copy of THIS module's |
||||
#normalization mapping - if the mapping here changes, those copies must |
||||
#change with it (each carries a pointer comment to this module). |
||||
|
||||
namespace eval argdoc { |
||||
variable PUNKARGS |
||||
} |
||||
|
||||
#canonical platform records. status: |
||||
# supported - a punkshell target: artifact trees exist or are planned and |
||||
# tooling/build arcs (G-103/G-105) treat it as a real target |
||||
# dormant - recognized and folders may exist, but utility is under review |
||||
# recognized - a name the naming scheme reserves; no artifacts or tooling yet |
||||
#tiers: which artifact-tree tiers use the name - |
||||
# runtime = punkbin platform folders + bin/runtime/<platform>/ |
||||
# lib = lib_tclX/<platform> + vendorlib_tclX/<platform> auto_path dirs |
||||
#buildsuite: whether OUR zig buildsuite system (src/buildsuites; G-103 kit |
||||
#family, G-105 cross-target) produces runtimes for the platform. A SEPARATE |
||||
#AXIS from tiers deliberately: punkbin (or a third-party repo using the same |
||||
#structure) can host runtimes built by any mechanism - a platform can be |
||||
#runtime-tier hosted while buildsuite=none (we may never build it ourselves). |
||||
# supported - a tracked suite produces family artifacts today |
||||
# planned - named in an active/proposed goal (e.g G-105 linux-first) |
||||
# candidate - plausible zig target, no committed goal |
||||
# none - no build intention; hosted/third-party runtimes only |
||||
variable platforms { |
||||
win32-x86_64 {status supported tiers {runtime lib} buildsuite supported notes "primary development platform (suite_tcl90)"} |
||||
win32-ix86 {status supported tiers {runtime lib} buildsuite candidate notes "32-bit x86; hosting for available third-party runtimes/libs - zig can target it but a buildsuite is undetermined"} |
||||
linux-x86_64 {status supported tiers {runtime lib} buildsuite planned notes "G-105 first cross-target, WSL-verified"} |
||||
linux-arm64 {status supported tiers {runtime lib} buildsuite candidate notes "aarch64; punkbin's existing arm kit predates the arm64 name and sits in linux-arm"} |
||||
linux-arm {status supported tiers {runtime lib} buildsuite candidate notes "32-bit arm"} |
||||
macosx {status supported tiers {runtime} buildsuite candidate notes "universal (multi-arch) macOS binaries - runtime tier keeps one folder; zig darwin cross needs SDK work"} |
||||
macosx-x86_64 {status supported tiers {lib} buildsuite none notes "would follow a macosx runtime arc"} |
||||
macosx-arm64 {status supported tiers {lib} buildsuite none notes "Apple silicon; would follow a macosx runtime arc"} |
||||
freebsd-x86_64 {status supported tiers {runtime lib} buildsuite candidate notes ""} |
||||
freebsd-arm64 {status supported tiers {runtime lib} buildsuite candidate notes "no artifacts yet"} |
||||
msys-x86_64 {status dormant tiers {lib} buildsuite none notes "msys/cygwin-built tclsh runtimes; utility under review"} |
||||
openbsd-x86_64 {status recognized tiers {runtime} buildsuite none notes "hosted third-party runtimes possible"} |
||||
netbsd-x86_64 {status recognized tiers {runtime} buildsuite none notes "hosted third-party runtimes possible"} |
||||
dragonflybsd-x86_64 {status recognized tiers {runtime} buildsuite none notes "hosted third-party runtimes possible"} |
||||
} |
||||
|
||||
namespace eval argdoc { |
||||
variable PUNKARGS |
||||
lappend PUNKARGS [list { |
||||
@id -id ::punk::platform::platforms |
||||
@cmd -name punk::platform::platforms\ |
||||
-summary\ |
||||
"Canonical punkshell platform records."\ |
||||
-help\ |
||||
"Return the canonical punkshell platform-name records as a dict |
||||
keyed by platform-dir name (e.g win32-x86_64). Each value is a |
||||
dict with keys: |
||||
status supported|dormant|recognized |
||||
tiers which artifact-tree tiers use the name |
||||
(runtime = punkbin + bin/runtime folders, |
||||
lib = lib_tclX/vendorlib_tclX auto_path dirs) |
||||
buildsuite supported|planned|candidate|none - whether the |
||||
punkshell zig buildsuite system produces |
||||
runtimes for the platform. Deliberately a |
||||
separate axis from tiers: punkbin-structured |
||||
repos can host runtimes built by any mechanism, |
||||
so a platform can be runtime-tier hosted while |
||||
buildsuite=none. |
||||
notes free-text qualifiers |
||||
These names are the contract for punkbin platform folders, |
||||
bin/runtime/<platform>/, lib_tclX/vendorlib_tclX platform |
||||
dirs and the G-103 runtime-artifact metadata target field. |
||||
See also 'help platforms' in the punk shell." |
||||
@values -min 0 -max 0 |
||||
}] |
||||
} |
||||
proc platforms {} { |
||||
variable platforms |
||||
return $platforms |
||||
} |
||||
|
||||
namespace eval argdoc { |
||||
variable PUNKARGS |
||||
lappend PUNKARGS [list { |
||||
@id -id ::punk::platform::normalize |
||||
@cmd -name punk::platform::normalize\ |
||||
-summary\ |
||||
"Normalize a platform identifier to the canonical punkshell name."\ |
||||
-help\ |
||||
"Normalize a platform::generic-style <os>-<cpu> identifier (or an |
||||
already-canonical punkshell name) to the canonical punkshell |
||||
platform-dir name: |
||||
cpu amd64 -> x86_64, aarch64 -> arm64 |
||||
os macos -> macosx (Tcl platform 1.1.x renamed modern macOS) |
||||
macosx cpu arm -> arm64 (platform::generic's arm* glob folds |
||||
the darwin arm64 machine name to arm; Apple silicon is |
||||
64-bit only - non-macosx 'arm' stays 32-bit arm) |
||||
Unrecognized os/cpu tokens pass through unchanged - normalize |
||||
never invents names, it only folds known aliases." |
||||
@values -min 1 -max 1 |
||||
platform -type string -help\ |
||||
"Platform identifier, e.g the result of platform::generic." |
||||
}] |
||||
} |
||||
proc normalize {platform} { |
||||
set parts [split $platform -] |
||||
if {[llength $parts] < 2} { |
||||
#single-token names (e.g the universal 'macosx') - fold macos only |
||||
if {$platform eq "macos"} { |
||||
return macosx |
||||
} |
||||
return $platform |
||||
} |
||||
#os may itself contain a dash in principle - treat last token as cpu |
||||
set cpu [lindex $parts end] |
||||
set os [join [lrange $parts 0 end-1] -] |
||||
switch -- $os { |
||||
macos {set os macosx} |
||||
} |
||||
switch -- $cpu { |
||||
amd64 {set cpu x86_64} |
||||
aarch64 {set cpu arm64} |
||||
i386 - i486 - i586 - i686 {set cpu ix86} |
||||
arm { |
||||
if {$os eq "macosx"} { |
||||
set cpu arm64 |
||||
} |
||||
} |
||||
} |
||||
return "${os}-${cpu}" |
||||
} |
||||
|
||||
namespace eval argdoc { |
||||
variable PUNKARGS |
||||
lappend PUNKARGS [list { |
||||
@id -id ::punk::platform::local |
||||
@cmd -name punk::platform::local\ |
||||
-summary\ |
||||
"Canonical punkshell platform name of the running interpreter."\ |
||||
-help\ |
||||
"Return the canonical punkshell platform-dir name for the running |
||||
interpreter: platform::generic normalized per |
||||
punk::platform::normalize. |
||||
With -tier runtime, macosx per-arch names collapse to the |
||||
universal 'macosx' (the runtime-store convention: punkbin and |
||||
bin/runtime keep one universal macOS folder; the same collapse |
||||
src/make.tcl applies when locating bin/runtime/<platform>). |
||||
-tier lib (the default) returns the per-arch name used by the |
||||
lib_tclX/vendorlib_tclX auto_path dirs." |
||||
@opts |
||||
-tier -type string -default lib -choices {lib runtime} -help\ |
||||
"Artifact-tree tier the name is for." |
||||
@values -min 0 -max 0 |
||||
}] |
||||
} |
||||
proc local {args} { |
||||
set tier lib |
||||
foreach {k v} $args { |
||||
switch -- $k { |
||||
-tier { |
||||
if {$v ni {lib runtime}} { |
||||
error "punk::platform::local - invalid -tier '$v' (expected lib|runtime)" |
||||
} |
||||
set tier $v |
||||
} |
||||
default { |
||||
error "punk::platform::local - unknown option '$k' (known: -tier)" |
||||
} |
||||
} |
||||
} |
||||
set p [normalize [::platform::generic]] |
||||
if {$tier eq "runtime" && [string match macosx-* $p]} { |
||||
return macosx |
||||
} |
||||
return $p |
||||
} |
||||
} |
||||
|
||||
|
||||
namespace eval ::punk::args::register { |
||||
#use fully qualified so 8.6 doesn't find existing var in global namespace |
||||
lappend ::punk::args::register::NAMESPACES ::punk::platform |
||||
} |
||||
|
||||
|
||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||
## Ready |
||||
package provide punk::platform [namespace eval punk::platform { |
||||
variable version |
||||
set version 999999.0a1.0 |
||||
}] |
||||
return |
||||
@ -0,0 +1,4 @@
|
||||
0.1.0 |
||||
#First line must be a semantic version number |
||||
#all other lines are ignored. |
||||
#0.1.0 - initial release (2026-07-22 platform-folder synchronization; G-105 groundwork): the canonical punkshell platform naming layer over Tcl's platform package - platforms (canonical records: status/tiers/buildsuite/notes; buildsuite = whether the punkshell zig buildsuites produce runtimes for the platform, deliberately a separate axis from the tiers a name organizes - punkbin-structured repos can host runtimes built by any mechanism, so runtime-tier hosting can coexist with buildsuite=none), normalize (amd64->x86_64, aarch64->arm64, macos->macosx, macosx arm->arm64), local ?-tier lib|runtime? (runtime tier collapses macosx per-arch to the universal macosx runtime-store name). Canon consumed by: punkbin platform folders, bin/runtime/<platform>/, lib_tclX/vendorlib_tclX platform dirs, G-103 artifact metadata targets, 'help platforms' topic. The boot machinery (punkboot::lib platform_generic snips in punk_main.tcl/project_main.tcl/make.tcl) carries an inline copy of the normalization mapping - keep in sync. |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,149 @@
|
||||
package require tcltest |
||||
tcltest::configure {*}$::argv |
||||
|
||||
#min-version bound documents that these tests target the dev module's API and protects against |
||||
#stable bootsupport copies shadowing the alpha dev version if this file is sourced outside |
||||
#runtests.tcl (whose testinterp runs 'package prefer latest' making the bound redundant there). |
||||
package require punk::console 999999.0a1.0- |
||||
|
||||
#Tests for the G-106 powershell console-mode fallback support machinery in punk::console::system: |
||||
#script resolution (ps_consolemode_script_get) and the embedded-copy sync contract with the |
||||
#canonical scriptlib/utils/pwsh/consolemode_server_async.ps1. Deliberately does NOT start a |
||||
#server or flip console modes - lifecycle verification is the G-106 recipe's hidden-console |
||||
#selftest (goals/archive/G-106-powershell-consolemode-fallback.md), which cannot run inside a |
||||
#shared test console. |
||||
|
||||
namespace eval ::testspace { |
||||
namespace import ::tcltest::* |
||||
|
||||
variable common { |
||||
set result "" |
||||
} |
||||
|
||||
#locate the repo checkout root by walking up from this test file - the canonical ps1 lives at |
||||
#<root>/scriptlib/utils/pwsh/consolemode_server_async.ps1. Walk-up (rather than a fixed depth) |
||||
#survives file moves; capped to avoid scanning to the filesystem root. |
||||
variable canonical_ps1 "" |
||||
variable testfiledir [file dirname [file normalize [info script]]] |
||||
proc find_canonical {} { |
||||
variable testfiledir |
||||
set dir $testfiledir |
||||
for {set i 0} {$i < 12} {incr i} { |
||||
set cand [file join $dir scriptlib utils pwsh consolemode_server_async.ps1] |
||||
if {[file exists $cand]} { |
||||
return $cand |
||||
} |
||||
set parent [file dirname $dir] |
||||
if {$parent eq $dir} { |
||||
break |
||||
} |
||||
set dir $parent |
||||
} |
||||
return "" |
||||
} |
||||
set canonical_ps1 [find_canonical] |
||||
tcltest::testConstraint canonicalps1found [expr {$canonical_ps1 ne ""}] |
||||
|
||||
proc normalized_text {text} { |
||||
#line-ending + edge-whitespace insensitive comparison form: the embedded copy is delivered |
||||
#to powershell via -c where leading/trailing whitespace is insignificant, and checkouts may |
||||
#vary line endings |
||||
return [string trim [string map [list \r\n \n] $text]] |
||||
} |
||||
|
||||
variable env_save { |
||||
set saved_env_script unset |
||||
if {[info exists ::env(PUNK_PS_CONSOLEMODE_SCRIPT)]} { |
||||
set saved_env_script [set ::env(PUNK_PS_CONSOLEMODE_SCRIPT)] |
||||
unset ::env(PUNK_PS_CONSOLEMODE_SCRIPT) |
||||
} |
||||
} |
||||
variable env_restore { |
||||
if {$saved_env_script ne "unset"} { |
||||
set ::env(PUNK_PS_CONSOLEMODE_SCRIPT) $saved_env_script |
||||
} else { |
||||
unset -nocomplain ::env(PUNK_PS_CONSOLEMODE_SCRIPT) |
||||
} |
||||
} |
||||
|
||||
#added 2026-07-22 (agent, G-106) - embedded-copy sync contract and script resolution chain |
||||
test psfallback_embedded_matches_canonical {embedded server script matches the canonical scriptlib file}\ |
||||
-constraints canonicalps1found\ |
||||
-setup $common -body { |
||||
variable canonical_ps1 |
||||
set fd [open $canonical_ps1 r] |
||||
chan configure $fd -translation binary |
||||
set filetext [read $fd] |
||||
close $fd |
||||
set embedded [set ::punk::console::system::ps_consolemode_script_embedded] |
||||
lappend result [expr {[normalized_text $filetext] eq [normalized_text $embedded]}] |
||||
lappend result [expr {[string length [normalized_text $embedded]] > 1000}] |
||||
}\ |
||||
-result [list\ |
||||
1\ |
||||
1\ |
||||
] |
||||
|
||||
test psfallback_placeholders_present {template placeholders present in embedded and canonical copies}\ |
||||
-constraints canonicalps1found\ |
||||
-setup $common -body { |
||||
variable canonical_ps1 |
||||
set fd [open $canonical_ps1 r] |
||||
chan configure $fd -translation binary |
||||
set filetext [read $fd] |
||||
close $fd |
||||
set embedded [set ::punk::console::system::ps_consolemode_script_embedded] |
||||
foreach ph {<punkshell_consoleid> <punkshell_parentpid> <punkshell_psdebug>} { |
||||
lappend result [expr {[string first $ph $embedded] >= 0}][expr {[string first $ph $filetext] >= 0}] |
||||
} |
||||
set result |
||||
}\ |
||||
-result [list\ |
||||
11\ |
||||
11\ |
||||
11\ |
||||
] |
||||
|
||||
test psfallback_script_get_shape {ps_consolemode_script_get returns source/path/contents dict with non-empty contents}\ |
||||
-setup $common -body { |
||||
set sg [punk::console::system::ps_consolemode_script_get] |
||||
lappend result [expr {[dict exists $sg source] && [dict exists $sg path] && [dict exists $sg contents]}] |
||||
lappend result [expr {[dict get $sg source] in {env scriptlib embedded}}] |
||||
lappend result [expr {[string length [dict get $sg contents]] > 1000}] |
||||
}\ |
||||
-result [list\ |
||||
1\ |
||||
1\ |
||||
1\ |
||||
] |
||||
|
||||
test psfallback_script_get_env_override {PUNK_PS_CONSOLEMODE_SCRIPT env override wins when the file exists}\ |
||||
-setup [join [list $common $env_save] \n] -body { |
||||
set overridefile [tcltest::makeFile "# override script content marker-xyzzy" psfallback_override.ps1] |
||||
set ::env(PUNK_PS_CONSOLEMODE_SCRIPT) $overridefile |
||||
set sg [punk::console::system::ps_consolemode_script_get] |
||||
lappend result [dict get $sg source] |
||||
lappend result [expr {[string first "marker-xyzzy" [dict get $sg contents]] >= 0}] |
||||
}\ |
||||
-cleanup [join [list $env_restore { |
||||
tcltest::removeFile psfallback_override.ps1 |
||||
}] \n]\ |
||||
-result [list\ |
||||
env\ |
||||
1\ |
||||
] |
||||
|
||||
test psfallback_script_get_env_override_missing {missing env-override file falls through to another source}\ |
||||
-setup [join [list $common $env_save] \n] -body { |
||||
set ::env(PUNK_PS_CONSOLEMODE_SCRIPT) [file join [tcltest::temporaryDirectory] no_such_psfallback_file.ps1] |
||||
set sg [punk::console::system::ps_consolemode_script_get] |
||||
lappend result [expr {[dict get $sg source] ne "env"}] |
||||
}\ |
||||
-cleanup $env_restore\ |
||||
-result [list\ |
||||
1\ |
||||
] |
||||
|
||||
cleanupTests |
||||
} |
||||
namespace delete ::testspace |
||||
@ -1,9 +0,0 @@
|
||||
Tcl library dependencies |
||||
|
||||
pkgIndex.tcl based libraries specific to the freebsd-amd64 platform. |
||||
Note that amd64 is equivalent to x86_64 in this context. |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
Tcl library dependencies |
||||
|
||||
pkgIndex.tcl based libraries specific to the freebsd-arm64 platform. |
||||
(Canonical punkshell platform name - see punk::platform / 'help platforms') |
||||
@ -0,0 +1,5 @@
|
||||
Tcl library dependencies |
||||
|
||||
pkgIndex.tcl based libraries specific to the freebsd-x86_64 platform. |
||||
(Renamed from freebsd-amd64 2026-07-22: punkshell canonical platform names |
||||
normalize amd64 to x86_64 - see punk::platform / 'help platforms') |
||||
@ -0,0 +1,4 @@
|
||||
Tcl library dependencies |
||||
|
||||
pkgIndex.tcl based libraries specific to the linux-arm platform. |
||||
(Canonical punkshell platform name - see punk::platform / 'help platforms') |
||||
@ -0,0 +1,4 @@
|
||||
Tcl library dependencies |
||||
|
||||
pkgIndex.tcl based libraries specific to the linux-arm64 platform. |
||||
(Canonical punkshell platform name - see punk::platform / 'help platforms') |
||||
@ -0,0 +1,4 @@
|
||||
Tcl library dependencies |
||||
|
||||
pkgIndex.tcl based libraries specific to the macosx-arm64 platform. |
||||
(Canonical punkshell platform name - see punk::platform / 'help platforms') |
||||
@ -1,8 +1,6 @@
|
||||
Tcl library dependencies |
||||
|
||||
pkgIndex.tcl based libraries specific to the msys-x86_64 platform. |
||||
This is a somewhat unix-like environment running on windows. |
||||
|
||||
|
||||
|
||||
|
||||
pkgIndex.tcl based libraries specific to the msys-x86_64 platform |
||||
(msys/cygwin-built tclsh runtimes). |
||||
Status: DORMANT - recognized canonical name but utility under review |
||||
(2026-07-22 platform survey; see punk::platform / 'help platforms'). |
||||
|
||||
@ -0,0 +1,6 @@
|
||||
Tcl library dependencies |
||||
|
||||
pkgIndex.tcl based libraries specific to the win32-ix86 platform (32-bit x86 |
||||
windows). Hosting for available third-party libraries - a punkshell buildsuite |
||||
for this platform is undetermined (canonical punkshell platform name - see |
||||
punk::platform / 'help platforms') |
||||
@ -1,9 +0,0 @@
|
||||
Tcl library dependencies |
||||
|
||||
pkgIndex.tcl based libraries specific to the freebsd-amd64 platform. |
||||
Note that amd64 is equivalent to x86_64 in this context. |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
Tcl library dependencies |
||||
|
||||
pkgIndex.tcl based libraries specific to the freebsd-arm64 platform. |
||||
(Canonical punkshell platform name - see punk::platform / 'help platforms') |
||||
@ -0,0 +1,5 @@
|
||||
Tcl library dependencies |
||||
|
||||
pkgIndex.tcl based libraries specific to the freebsd-x86_64 platform. |
||||
(Renamed from freebsd-amd64 2026-07-22: punkshell canonical platform names |
||||
normalize amd64 to x86_64 - see punk::platform / 'help platforms') |
||||
@ -0,0 +1,4 @@
|
||||
Tcl library dependencies |
||||
|
||||
pkgIndex.tcl based libraries specific to the linux-arm platform. |
||||
(Canonical punkshell platform name - see punk::platform / 'help platforms') |
||||
@ -0,0 +1,4 @@
|
||||
Tcl library dependencies |
||||
|
||||
pkgIndex.tcl based libraries specific to the linux-arm64 platform. |
||||
(Canonical punkshell platform name - see punk::platform / 'help platforms') |
||||
@ -0,0 +1,4 @@
|
||||
Tcl library dependencies |
||||
|
||||
pkgIndex.tcl based libraries specific to the macosx-arm64 platform. |
||||
(Canonical punkshell platform name - see punk::platform / 'help platforms') |
||||
@ -1,8 +1,6 @@
|
||||
Tcl library dependencies |
||||
|
||||
pkgIndex.tcl based libraries specific to the msys-x86_64 platform. |
||||
This is a somewhat unix-like environment running on windows. |
||||
|
||||
|
||||
|
||||
|
||||
pkgIndex.tcl based libraries specific to the msys-x86_64 platform |
||||
(msys/cygwin-built tclsh runtimes). |
||||
Status: DORMANT - recognized canonical name but utility under review |
||||
(2026-07-22 platform survey; see punk::platform / 'help platforms'). |
||||
|
||||
@ -0,0 +1,6 @@
|
||||
Tcl library dependencies |
||||
|
||||
pkgIndex.tcl based libraries specific to the win32-ix86 platform (32-bit x86 |
||||
windows). Hosting for available third-party libraries - a punkshell buildsuite |
||||
for this platform is undetermined (canonical punkshell platform name - see |
||||
punk::platform / 'help platforms') |
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue