tm_path_additional_ifneeded referenced an out-of-scope \ variable that was never a parameter or local, causing 'can't read project_root: no such variable' whenever a #modpod module was found. The proc now requires project_root as a 2nd parameter. Additionally, the #modpod directory pattern was hardcoded to 999999.0a1.0 which the build system replaces with the real version (e.g 0.3.0) during make.tcl modules — breaking pattern matching in built bootsupport copies that looked for #modpod-*-0.3.0 directories which don't exist on disk. The proc now extracts both modname and version dynamically from the #modpod-<modname>-<version> directory name by splitting on the last dash, making it version-agnostic. Punk::lib::tm_version_magic convention was considered but dynamic extraction is preferred here as it will also work in the future src package_mode boot context where punk modules aren't loaded yet.
#If the supplied tmpath contains any unbuilt #modpod modules, we want to add package ifneeded definitions for those modules.
#If the supplied tmpath contains any unbuilt #modpod modules, we want to add package ifneeded definitions for those modules.
#return a single script containing the package ifneeded definitions.
#return a single script containing the package ifneeded definitions.
#when running against unbuilt modules - we want to ensure that the unbuilt versions of any modules are used rather than any installed versions - so we add package ifneeded definitions for the unbuilt versions of any modules that are present.
#when running against unbuilt modules - we want to ensure that the unbuilt versions of any modules are used rather than any installed versions - so we add package ifneeded definitions for the unbuilt versions of any modules that are present.
# add 'package ifneeded' definitions for unbuilt #modpod modules.
# add 'package ifneeded' definitions for unbuilt #modpod modules.
#first gather subdirectories of modules that contain #modpod-*-0.1.0 in their name - these should be the unbuilt versions of zip based modules.
#first gather subdirectories of modules that contain #modpod-*-<version> in their name - these should be the unbuilt versions of zip based modules.
#'punk::path::subfolders' currently only supports negative matching with -exclude, so we have to filter for the positive match ourselves.
#'punk::path::subfolders' currently only supports negative matching with -exclude, so we have to filter for the positive match ourselves.
set subfolders [punk::path::subfolders -recursive -exclude {**/_build/** **/_build} $tmpath]
set subfolders [punk::path::subfolders -recursive -exclude {**/_build/** **/_build} $tmpath]
set script ""
set script ""
foreach sub $subfolders {
foreach sub $subfolders {
#In most cases we could use string match - but the * within modpod-*-0.1.0 could match a forward slash which could then match some other file under a #modpod- folder structure,
#In most cases we could use string match - but the * within modpod-*-<version> could match a forward slash which could then match some other file under a #modpod- folder structure,
#so we use globmatchpath which treats * as matching any characters except path separators.
#so we use globmatchpath which treats * as matching any characters except path separators.
if {[punk::path::globmatchpath "**/#modpod-*-0.1.0" $sub]} {
if {[punk::path::globmatchpath "**/#modpod-*" $sub]} {
set modname [file tail $sub]
set tail [file tail $sub]
set modname [string range $modname 8 end-13] ;#strip off #modpod- and -0.1.0
# Extract modname and version from the #modpod-<modname>-<version> directory name.
set modpath [file join $sub "$modname-0.1.0.tm"]
# The directory name format is: #modpod-<modname>-<version>
# Strip the leading "#modpod-" (8 chars), then split on the last "-" to separate modname from version.
set rest [string range $tail 8 end]
set last_dash [string last "-" $rest]
if {$last_dash < 0} { continue }
set modname [string range $rest 0 [expr {$last_dash - 1}]]
set modver [string range $rest [expr {$last_dash + 1}] end]
set modpath [file join $sub "$modname-$modver.tm"]
#calculate fully qualified module name based on path relative to the modules folder we added to the tcl::tm path.
#calculate fully qualified module name based on path relative to the modules folder we added to the tcl::tm path.
set relpath [punk::path::relative $project_root/src/modules [file dirname $sub]]
#If the supplied tmpath contains any unbuilt #modpod modules, we want to add package ifneeded definitions for those modules.
#If the supplied tmpath contains any unbuilt #modpod modules, we want to add package ifneeded definitions for those modules.
#return a single script containing the package ifneeded definitions.
#return a single script containing the package ifneeded definitions.
#when running against unbuilt modules - we want to ensure that the unbuilt versions of any modules are used rather than any installed versions - so we add package ifneeded definitions for the unbuilt versions of any modules that are present.
#when running against unbuilt modules - we want to ensure that the unbuilt versions of any modules are used rather than any installed versions - so we add package ifneeded definitions for the unbuilt versions of any modules that are present.
# add 'package ifneeded' definitions for unbuilt #modpod modules.
# add 'package ifneeded' definitions for unbuilt #modpod modules.
#first gather subdirectories of modules that contain #modpod-*-999999.0a1.0 in their name - these should be the unbuilt versions of zip based modules.
#first gather subdirectories of modules that contain #modpod-*-<version> in their name - these should be the unbuilt versions of zip based modules.
#'punk::path::subfolders' currently only supports negative matching with -exclude, so we have to filter for the positive match ourselves.
#'punk::path::subfolders' currently only supports negative matching with -exclude, so we have to filter for the positive match ourselves.
set subfolders [punk::path::subfolders -recursive -exclude {**/_build/** **/_build} $tmpath]
set subfolders [punk::path::subfolders -recursive -exclude {**/_build/** **/_build} $tmpath]
set script ""
set script ""
foreach sub $subfolders {
foreach sub $subfolders {
#In most cases we could use string match - but the * within modpod-*-999999.0a1.0 could match a forward slash which could then match some other file under a #modpod- folder structure,
#In most cases we could use string match - but the * within modpod-*-<version> could match a forward slash which could then match some other file under a #modpod- folder structure,
#so we use globmatchpath which treats * as matching any characters except path separators.
#so we use globmatchpath which treats * as matching any characters except path separators.
if {[punk::path::globmatchpath "**/#modpod-*-999999.0a1.0" $sub]} {
if {[punk::path::globmatchpath "**/#modpod-*" $sub]} {
set modname [file tail $sub]
set tail [file tail $sub]
set modname [string range $modname 8 end-13] ;#strip off #modpod- and -999999.0a1.0
# Extract modname and version from the #modpod-<modname>-<version> directory name.
set modpath [file join $sub "$modname-999999.0a1.0.tm"]
# The directory name format is: #modpod-<modname>-<version>
# Strip the leading "#modpod-" (8 chars), then split on the last "-" to separate modname from version.
set rest [string range $tail 8 end]
set last_dash [string last "-" $rest]
if {$last_dash < 0} { continue }
set modname [string range $rest 0 [expr {$last_dash - 1}]]
set modver [string range $rest [expr {$last_dash + 1}] end]
set modpath [file join $sub "$modname-$modver.tm"]
#calculate fully qualified module name based on path relative to the modules folder we added to the tcl::tm path.
#calculate fully qualified module name based on path relative to the modules folder we added to the tcl::tm path.
set relpath [punk::path::relative $project_root/src/modules [file dirname $sub]]
#0.3.0 - breaking: tm_path_additional_ifneeded now requires project_root as a 2nd parameter to fix out-of-scope $project_root bug that caused 'no such variable' errors whenever #modpod modules were found
#0.3.0 - fix: #modpod version pattern is now extracted dynamically from directory name instead of hardcoded, preventing build-system version replacement from breaking pattern matching in built bootsupport copies
#0.3.0 - fix: runtests.tcl second tm_path_additional_ifneeded call now passes modules_tcl$tcl_major instead of modules (copy-paste bug)
#0.2.0 - parse_testrun now preserves errorinfo, result_was, result_expected in testcase_fails entries (backward-compatible additive keys)
#0.2.0 - parse_testrun now preserves errorinfo, result_was, result_expected in testcase_fails entries (backward-compatible additive keys)
#0.2.0 - fix test name extraction: took/PASSED/SKIPPED handlers and microseconds lookup now key by the full test name instead of the first word (lindex $ln 1), which corrupted timing associations and testcase_passes/testcase_constraintskips keys for multi-word test names
#0.2.0 - fix test name extraction: took/PASSED/SKIPPED handlers and microseconds lookup now key by the full test name instead of the first word (lindex $ln 1), which corrupted timing associations and testcase_passes/testcase_constraintskips keys for multi-word test names