Browse Source

make.tcl: self-build guard for kit builds under a built punk exe; quieten libunknown double-init

Self-build guard (user-identified gap): when make.tcl is driven by a built punk
executable, the kit whose deployed executable is the running interpreter is now
skipped before any build work, with guidance to rerun under tclsh or a
different kit. Previously the pre-deploy process sweep matched processes by
executable name only and would taskkill the build's own process mid-run with
-k (suicide: remaining kits unbuilt, no recap, dangling punkcheck event);
without -k only the polite kill's refusal on console processes saved the run,
and the deploy delete of the running image would fail with a cryptic message.
The sweep additionally excludes the build's own pid unconditionally, covering
a same-named copy driving the build from a non-deploy path. Informational and
update subcommands were already safe (they exit before the kit machinery);
building other kits from a punk exe remains supported. Verified live:
'punk91 src/make.tcl vfs -confirm 0' skips punk91.exe with the warning and
processes the other kits normally; plain tclsh runs unaffected.

libunknown quietening: 'punkexe src/make.tcl <cmd>' emitted
"punk::libunknown::init already done - unnecessary call? info frame -1: ..."
because punk_main.tcl had already initialised libunknown (renaming ::package)
and make.tcl unconditionally re-sourced the bootsupport copy and called init
again, tripping init's load-bearing rename guard. make.tcl now leaves an
already-active libunknown in place (no re-source - also avoids silently
swapping the running copy for the bootsupport copy when versions diverge);
plain tclsh runs source+init exactly as before. Verified: no diagnostic from
'punk91 src/make.tcl projectversion'.

src/AGENTS.md build guidance updated (punk-exe-driven builds and the self-kit
skip); layout make.tcl copies re-synced via the punkcheck channel.

Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
master
Julian Noble 1 day ago
parent
commit
1370484f5e
  1. 4
      CHANGELOG.md
  2. 2
      punkproject.toml
  3. 2
      src/AGENTS.md
  4. 44
      src/make.tcl
  5. 44
      src/project_layouts/custom/_project/punk.basic/src/make.tcl
  6. 44
      src/project_layouts/custom/_project/punk.project-0.1/src/make.tcl
  7. 44
      src/project_layouts/custom/_project/punk.shell-0.1/src/make.tcl

4
CHANGELOG.md

@ -5,6 +5,10 @@ The latest `## [X.Y.Z]` header must match the `version` field in `punkproject.to
Entries are newest-first; one bullet per notable change. See the root `AGENTS.md`
"Project Versioning" section for the bump policy.
## [0.12.26] - 2026-07-14
- make.tcl under a built punk executable: kit builds gain a self-build guard - the kit whose deployed executable is running the build is skipped with a clear warning (previously the pre-deploy process sweep would taskkill the build's own process mid-run with -k, since it matched processes by executable name only; without -k the polite kill's failure was the only thing saving the run). The sweep now also excludes the build's own pid unconditionally (covers a same-named copy driving the build from a non-deploy path). Informational/update subcommands were already safe (they exit before the kit machinery); building *other* kits from a punk exe remains supported. Also quietened the 'punk::libunknown::init already done - unnecessary call?' stderr diagnostic when a punk exe runs make.tcl: make.tcl now leaves an already-active libunknown in place instead of re-sourcing the bootsupport copy over it and tripping init's rename guard. src/AGENTS.md build guidance updated.
## [0.12.25] - 2026-07-14
- G-030 follow-up (user review): make.tcl's constructed punk::args definitions adopt the G-045 authoring mechanisms - @cmd -help bodies converted to block-form values with a bare @normalize per definition, fixing the constructed-definition indent leak (first Description line rendered less indented than continuations; the braced option fragments never leaked because literal block-shaped parts get file-style normalization). Added a define-time capability probe (get_spec on representative ids) so a stale punk::args that accepts the text but cannot resolve a mechanism degrades to the plain fallback immediately. AGENTS infrastructure for future documentation work: new "punk::args definition authoring ergonomics" subsection in src/modules/AGENTS.md (constructed-def @normalize + block form, -unindentedfields, -& record continuation, container quoting rules, explicit @form -synopsis for script-level ids, the optional-choice error-quality workaround), cross-referenced from src/AGENTS.md with ::punkboot::argdoc named as the in-tree exemplar. Layout make.tcl copies followed via the punkcheck channel.

2
punkproject.toml

@ -1,4 +1,4 @@
[project]
name = "punkshell"
version = "0.12.25"
version = "0.12.26"
license = "BSD-2-Clause"

2
src/AGENTS.md

@ -69,7 +69,7 @@ Recovery after a wrong path guess:
- Use `tclsh src/make.tcl vfscommonupdate` to rebuild `_vfscommon.vfs`. The REPLACE confirmation prompts on an interactive terminal; unattended/agent runs must pass `-confirm 0` (with the default `-confirm 1` and a piped/closed stdin, make.tcl aborts fast with guidance instead of reading stdin - do not pipe `y`).
- make.tcl subcommands and flags are punk::args-declared (G-030): `tclsh src/make.tcl help ?subcommand?` or `<subcommand> -help` shows tabled usage; invalid arguments produce punk::args usage errors. The dispatch degrades to plain scan/help when the bootsupport punk::args (or its rendering stack) is stale or unavailable; `PUNKBOOT_PLAIN=1` forces the degraded mode for troubleshooting. The `::punkboot::argdoc` definitions are constructed (string-built) and use the G-045 authoring mechanisms — block-form help values + `@normalize`, explicit `@form -synopsis` — see src/modules/AGENTS.md "punk::args definition authoring ergonomics" before editing them or authoring similar definitions.
- Uncommitted `make.tcl`-generated outputs may be batched into one commit, regardless of how many `make.tcl` invocations produced them. This covers punkcheck-managed build outputs that are VCS-tracked: `src/bootsupport/`, `_vfscommon.vfs/modules` + `/lib`, and declared per-kit `*.vfs/lib_tcl<N>/<pkg>` subfolders. (Root `modules/`, `lib/`, `modules_tcl<N>/`, `lib_tcl<N>/` are gitignored and never committed.) Each module's old→new version rename is atomic; a regeneration reflects one build-output refresh, not independent hand-edits. This is a carve-out from generic "split aggressively" commit guidance for punkcheck-managed build outputs only — it does not cover user-curated VFS content (`_config/`, per-kit `*.vfs/` outside declared `lib_tcl<N>` subfolders, `_aside/`, `mkzipfix.vfs`, `_vfscommon.vfs/doc`), which commits separately per its own concerns.
- Use `punk make.tcl project` or `punk902z make.tcl project` inside Punk shell when building binaries through Punk.
- Use `punk make.tcl project` or `punk902z make.tcl project` inside Punk shell when building binaries through Punk. Driving make.tcl from a built punk executable is supported for informational/update subcommands and for kit builds of *other* kits — the kit whose deployed executable is running the build is skipped with a warning (it cannot be replaced while running, and the pre-deploy process sweep must not kill the build itself; the sweep also excludes the build's own pid in all cases). Rebuild that kit from tclsh or a different kit.
- Binary images are platform-specific; build on each target platform rather than expecting a cross-platform flag.
- Remove `_build/` artifacts only when a clean/resync is needed, then rerun the relevant `make.tcl` command. Avoid partial cleans that break boot modules.
- Superseded module intermediates are pruned automatically so payload wrapping inherits clean trees:

44
src/make.tcl

@ -508,9 +508,17 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} {
}
}
if {$libunknown ne ""} {
source $libunknown
if {[catch {punk::libunknown::init -caller make.tcl} errM]} {
puts stderr "error initialising punk::libunknown\n$errM"
if {[info commands ::punk::libunknown::package] ne ""} {
#libunknown already active in this interp - e.g make.tcl driven by a built punk
#executable whose boot (punk_main.tcl) initialised it. init's rename of ::package
#must not be repeated (its guard would emit an 'init already done' diagnostic and
#return), and re-sourcing would silently swap the running copy for the bootsupport
#copy - leave the active one in place.
} else {
source $libunknown
if {[catch {punk::libunknown::init -caller make.tcl} errM]} {
puts stderr "error initialising punk::libunknown\n$errM"
}
}
#puts stdout " *** [package names]"
#puts stdout " **** [dict get $::punk::libunknown::epoch pkg untracked]"
@ -3917,6 +3925,24 @@ foreach vfstail $vfs_tails {
foreach targetkit_info $targetkits {
puts stdout " processing targetkit: $targetkit_info"
lassign $targetkit_info targetkit target_kit_type
#Self-build guard: never process the kit whose deployed executable is running this
#build - the process sweep below would kill this very process, and the deploy step
#cannot replace a running image anyway. Informational/update subcommands are
#unaffected (they exit before the kit machinery), so driving make.tcl from a built
#punk executable remains supported for everything except rebuilding that executable.
set self_exe [file normalize [info nameofexecutable]]
set self_deploy_target [file normalize $binfolder/$targetkit]
if {$::tcl_platform(platform) eq "windows"} {
set is_self_build [string equal -nocase $self_exe $self_deploy_target]
} else {
set is_self_build [string equal $self_exe $self_deploy_target]
}
if {$is_self_build} {
puts stderr "SKIPPING kit $targetkit - its deployed executable is the one running this build ($self_exe)"
puts stderr " Rerun 'make.tcl $::punkboot::command' under tclsh or a different built kit to rebuild $targetkit."
lappend skipped_kits [list kit $targetkit reason "deployed executable is running this build"]
continue
}
# -- ----------
set vfs_installer [punkcheck::installtrack new $installername $basedir/.punkcheck]
$vfs_installer set_source_target $sourcefolder $buildfolder
@ -4480,6 +4506,18 @@ foreach vfstail $vfs_tails {
foreach ln [split [string trim $ps_lines] \n] {
if {[string match "* grep *" $ln]} {continue}
if {[string match "*$targetkit*" $ln]} {
#never target the process running this build. Name-only matching
#cannot distinguish an unrelated same-named executable elsewhere on
#the system (see TODO above) - but the build must at least not kill
#itself (e.g a copy of the target executable driving this build from
#a path other than the deploy target, which the self-build guard
#above doesn't catch).
if {$::tcl_platform(platform) eq "windows"} {
set ln_pid [lindex $ln 1]
} else {
set ln_pid [lindex $ln 0]
}
if {$ln_pid eq [pid]} {continue}
lappend still_running_lines $ln
}
}

44
src/project_layouts/custom/_project/punk.basic/src/make.tcl

@ -508,9 +508,17 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} {
}
}
if {$libunknown ne ""} {
source $libunknown
if {[catch {punk::libunknown::init -caller make.tcl} errM]} {
puts stderr "error initialising punk::libunknown\n$errM"
if {[info commands ::punk::libunknown::package] ne ""} {
#libunknown already active in this interp - e.g make.tcl driven by a built punk
#executable whose boot (punk_main.tcl) initialised it. init's rename of ::package
#must not be repeated (its guard would emit an 'init already done' diagnostic and
#return), and re-sourcing would silently swap the running copy for the bootsupport
#copy - leave the active one in place.
} else {
source $libunknown
if {[catch {punk::libunknown::init -caller make.tcl} errM]} {
puts stderr "error initialising punk::libunknown\n$errM"
}
}
#puts stdout " *** [package names]"
#puts stdout " **** [dict get $::punk::libunknown::epoch pkg untracked]"
@ -3917,6 +3925,24 @@ foreach vfstail $vfs_tails {
foreach targetkit_info $targetkits {
puts stdout " processing targetkit: $targetkit_info"
lassign $targetkit_info targetkit target_kit_type
#Self-build guard: never process the kit whose deployed executable is running this
#build - the process sweep below would kill this very process, and the deploy step
#cannot replace a running image anyway. Informational/update subcommands are
#unaffected (they exit before the kit machinery), so driving make.tcl from a built
#punk executable remains supported for everything except rebuilding that executable.
set self_exe [file normalize [info nameofexecutable]]
set self_deploy_target [file normalize $binfolder/$targetkit]
if {$::tcl_platform(platform) eq "windows"} {
set is_self_build [string equal -nocase $self_exe $self_deploy_target]
} else {
set is_self_build [string equal $self_exe $self_deploy_target]
}
if {$is_self_build} {
puts stderr "SKIPPING kit $targetkit - its deployed executable is the one running this build ($self_exe)"
puts stderr " Rerun 'make.tcl $::punkboot::command' under tclsh or a different built kit to rebuild $targetkit."
lappend skipped_kits [list kit $targetkit reason "deployed executable is running this build"]
continue
}
# -- ----------
set vfs_installer [punkcheck::installtrack new $installername $basedir/.punkcheck]
$vfs_installer set_source_target $sourcefolder $buildfolder
@ -4480,6 +4506,18 @@ foreach vfstail $vfs_tails {
foreach ln [split [string trim $ps_lines] \n] {
if {[string match "* grep *" $ln]} {continue}
if {[string match "*$targetkit*" $ln]} {
#never target the process running this build. Name-only matching
#cannot distinguish an unrelated same-named executable elsewhere on
#the system (see TODO above) - but the build must at least not kill
#itself (e.g a copy of the target executable driving this build from
#a path other than the deploy target, which the self-build guard
#above doesn't catch).
if {$::tcl_platform(platform) eq "windows"} {
set ln_pid [lindex $ln 1]
} else {
set ln_pid [lindex $ln 0]
}
if {$ln_pid eq [pid]} {continue}
lappend still_running_lines $ln
}
}

44
src/project_layouts/custom/_project/punk.project-0.1/src/make.tcl

@ -508,9 +508,17 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} {
}
}
if {$libunknown ne ""} {
source $libunknown
if {[catch {punk::libunknown::init -caller make.tcl} errM]} {
puts stderr "error initialising punk::libunknown\n$errM"
if {[info commands ::punk::libunknown::package] ne ""} {
#libunknown already active in this interp - e.g make.tcl driven by a built punk
#executable whose boot (punk_main.tcl) initialised it. init's rename of ::package
#must not be repeated (its guard would emit an 'init already done' diagnostic and
#return), and re-sourcing would silently swap the running copy for the bootsupport
#copy - leave the active one in place.
} else {
source $libunknown
if {[catch {punk::libunknown::init -caller make.tcl} errM]} {
puts stderr "error initialising punk::libunknown\n$errM"
}
}
#puts stdout " *** [package names]"
#puts stdout " **** [dict get $::punk::libunknown::epoch pkg untracked]"
@ -3917,6 +3925,24 @@ foreach vfstail $vfs_tails {
foreach targetkit_info $targetkits {
puts stdout " processing targetkit: $targetkit_info"
lassign $targetkit_info targetkit target_kit_type
#Self-build guard: never process the kit whose deployed executable is running this
#build - the process sweep below would kill this very process, and the deploy step
#cannot replace a running image anyway. Informational/update subcommands are
#unaffected (they exit before the kit machinery), so driving make.tcl from a built
#punk executable remains supported for everything except rebuilding that executable.
set self_exe [file normalize [info nameofexecutable]]
set self_deploy_target [file normalize $binfolder/$targetkit]
if {$::tcl_platform(platform) eq "windows"} {
set is_self_build [string equal -nocase $self_exe $self_deploy_target]
} else {
set is_self_build [string equal $self_exe $self_deploy_target]
}
if {$is_self_build} {
puts stderr "SKIPPING kit $targetkit - its deployed executable is the one running this build ($self_exe)"
puts stderr " Rerun 'make.tcl $::punkboot::command' under tclsh or a different built kit to rebuild $targetkit."
lappend skipped_kits [list kit $targetkit reason "deployed executable is running this build"]
continue
}
# -- ----------
set vfs_installer [punkcheck::installtrack new $installername $basedir/.punkcheck]
$vfs_installer set_source_target $sourcefolder $buildfolder
@ -4480,6 +4506,18 @@ foreach vfstail $vfs_tails {
foreach ln [split [string trim $ps_lines] \n] {
if {[string match "* grep *" $ln]} {continue}
if {[string match "*$targetkit*" $ln]} {
#never target the process running this build. Name-only matching
#cannot distinguish an unrelated same-named executable elsewhere on
#the system (see TODO above) - but the build must at least not kill
#itself (e.g a copy of the target executable driving this build from
#a path other than the deploy target, which the self-build guard
#above doesn't catch).
if {$::tcl_platform(platform) eq "windows"} {
set ln_pid [lindex $ln 1]
} else {
set ln_pid [lindex $ln 0]
}
if {$ln_pid eq [pid]} {continue}
lappend still_running_lines $ln
}
}

44
src/project_layouts/custom/_project/punk.shell-0.1/src/make.tcl

@ -508,9 +508,17 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} {
}
}
if {$libunknown ne ""} {
source $libunknown
if {[catch {punk::libunknown::init -caller make.tcl} errM]} {
puts stderr "error initialising punk::libunknown\n$errM"
if {[info commands ::punk::libunknown::package] ne ""} {
#libunknown already active in this interp - e.g make.tcl driven by a built punk
#executable whose boot (punk_main.tcl) initialised it. init's rename of ::package
#must not be repeated (its guard would emit an 'init already done' diagnostic and
#return), and re-sourcing would silently swap the running copy for the bootsupport
#copy - leave the active one in place.
} else {
source $libunknown
if {[catch {punk::libunknown::init -caller make.tcl} errM]} {
puts stderr "error initialising punk::libunknown\n$errM"
}
}
#puts stdout " *** [package names]"
#puts stdout " **** [dict get $::punk::libunknown::epoch pkg untracked]"
@ -3917,6 +3925,24 @@ foreach vfstail $vfs_tails {
foreach targetkit_info $targetkits {
puts stdout " processing targetkit: $targetkit_info"
lassign $targetkit_info targetkit target_kit_type
#Self-build guard: never process the kit whose deployed executable is running this
#build - the process sweep below would kill this very process, and the deploy step
#cannot replace a running image anyway. Informational/update subcommands are
#unaffected (they exit before the kit machinery), so driving make.tcl from a built
#punk executable remains supported for everything except rebuilding that executable.
set self_exe [file normalize [info nameofexecutable]]
set self_deploy_target [file normalize $binfolder/$targetkit]
if {$::tcl_platform(platform) eq "windows"} {
set is_self_build [string equal -nocase $self_exe $self_deploy_target]
} else {
set is_self_build [string equal $self_exe $self_deploy_target]
}
if {$is_self_build} {
puts stderr "SKIPPING kit $targetkit - its deployed executable is the one running this build ($self_exe)"
puts stderr " Rerun 'make.tcl $::punkboot::command' under tclsh or a different built kit to rebuild $targetkit."
lappend skipped_kits [list kit $targetkit reason "deployed executable is running this build"]
continue
}
# -- ----------
set vfs_installer [punkcheck::installtrack new $installername $basedir/.punkcheck]
$vfs_installer set_source_target $sourcefolder $buildfolder
@ -4480,6 +4506,18 @@ foreach vfstail $vfs_tails {
foreach ln [split [string trim $ps_lines] \n] {
if {[string match "* grep *" $ln]} {continue}
if {[string match "*$targetkit*" $ln]} {
#never target the process running this build. Name-only matching
#cannot distinguish an unrelated same-named executable elsewhere on
#the system (see TODO above) - but the build must at least not kill
#itself (e.g a copy of the target executable driving this build from
#a path other than the deploy target, which the self-build guard
#above doesn't catch).
if {$::tcl_platform(platform) eq "windows"} {
set ln_pid [lindex $ln 1]
} else {
set ln_pid [lindex $ln 0]
}
if {$ln_pid eq [pid]} {continue}
lappend still_running_lines $ln
}
}

Loading…
Cancel
Save