# tcl # #make any tclkits and modules in src and place them and associated data files/scripts in the parent folder of src. #e.g in 'bin' and 'modules' folders at same level as 'src' folder. set hashline "# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###" puts $hashline puts " punkshell make script " puts $hashline\n namespace eval ::punkmake { variable scriptfolder [file normalize [file dirname [info script]]] variable foldername [file tail $scriptfolder] variable pkg_requirements [list]; variable pkg_missing [list];variable pkg_loaded [list] variable non_help_flags [list -k] variable help_flags [list -help --help /?] variable known_commands [list project get-project-info shell bootsupport] } if {"::try" ni [info commands ::try]} { puts stderr "Tcl interpreter possibly too old - 'try' command not found - aborting" exit 1 } #------------------------------------------------------------------------------ #Module loading from /src/bootsupport or src/*.vfs if script is within a .vfs folder #------------------------------------------------------------------------------ #If the there is a folder under the current directory /src/bootsupport/modules which contains .tm files # - then it will attempt to preference these modules # This allows a source update via 'fossil update' 'git pull' etc to pull in a minimal set of support modules for the make script # and load these in preference to ones that may have been in the interp's tcl::tm::list or auto_path due to environment variables set startdir [pwd] if {[file exists [file join $startdir src bootsupport]]} { set bootsupport_mod [file join $startdir src bootsupport modules] set bootsupport_lib [file join $startdir src bootsupport lib] } else { set bootsupport_mod [file join $startdir bootsupport modules] set bootsupport_lib [file join $startdir bootsupport lib] } if {[file exists $bootsupport_mod] || [file exists $bootsupport_lib]} { set original_tm_list [tcl::tm::list] tcl::tm::remove {*}$original_tm_list set original_auto_path $::auto_path set ::auto_path [list $bootsupport_lib] set support_modules [glob -nocomplain -dir $bootsupport_mod -type f -tail *.tm] set tcl_core_packages [list tcl::zlib zlib tcl::oo TclOO tcl::tommath tcl::zipfs Tcl Tk] ;#packages we if {[llength $support_modules] || [llength [glob -nocomplain -dir $bootsupport_lib -tail *]]} { #only forget all *unloaded* package names foreach pkg [package names] { if {$pkg in $tcl_core_packages} { continue } if {![llength [package versions $pkg]]} { #puts stderr "Got no versions for pkg $pkg" continue } if {![string length [package provide $pkg]]} { #no returned version indicates it wasn't loaded - so we can forget its index package forget $pkg } } tcl::tm::add $bootsupport_mod } if {[file exists [pwd]/modules]} { tcl::tm::add [pwd]/modules } #package require Thread # - the full repl requires Threading and punk,shellfilter,shellrun to call and display properly. # tm list already indexed - need 'package forget' to find modules based on current tcl::tm::list #These are strong dependencies package forget punk::mix package require punk::mix package forget punk::repo package require punk::repo package forget punkcheck package require punkcheck #restore module paths and auto_path in addition to the bootsupport ones set tm_list_now [tcl::tm::list] foreach p $original_tm_list { if {$p ni $tm_list_now} { tcl::tm::add $p } } set ::auto_path [list $bootsupport_lib {*}$original_auto_path] #------------------------------------------------------------------------------ } # ** *** *** *** *** *** *** *** *** *** *** *** #*temporarily* hijack package command # ** *** *** *** *** *** *** *** *** *** *** *** try { rename ::package ::punkmake::package_temp_aside proc ::package {args} { if {[lindex $args 0] eq "require"} { lappend ::punkmake::pkg_requirements [lindex $args 1] } } package require punk::mix package require punk::repo } finally { catch {rename ::package ""} catch {rename ::punkmake::package_temp_aside ::package} } # ** *** *** *** *** *** *** *** *** *** *** *** foreach pkg $::punkmake::pkg_requirements { if {[catch {package require $pkg} errM]} { puts stderr "missing pkg: $pkg" lappend ::punkmake::pkg_missing $pkg } else { lappend ::punkmake::pkg_loaded $pkg } } proc punkmake_gethelp {args} { set scriptname [file tail [info script]] append h "Usage:" \n append h "" \n append h " $scriptname -help or $scriptname --help or $scriptname /? or just $scriptname" \n append h " - This help." \n \n append h " $scriptname project ?-k?" \n append h " - this is the literal word project - and confirms you want to run the project build" \n append h " - the optional -k flag will terminate processes running as the executable being built (if applicable)" \n \n append h " $scriptname bootsupport" \n append h " - update the src/bootsupport modules as well as the mixtemplates/layouts//src/bootsupport modules if the folder exists" \n \n append h " $scriptname get-project-info" \n append h " - show the name and base folder of the project to be built" \n append h "" \n if {[llength $::punkmake::pkg_missing]} { append h "* ** NOTE ** ***" \n append h " punkmake has detected that the following packages could not be loaded:" \n append h " " [join $::punkmake::pkg_missing "\n "] \n append h "* ** *** *** ***" \n append h " These packages are required for punk make to function" \n \n append h "* ** *** *** ***" \n\n append h "Successfully Loaded packages:" \n append h " " [join $::punkmake::pkg_loaded "\n "] \n } return $h } set scriptargs $::argv set do_help 0 if {![llength $scriptargs]} { set do_help 1 } else { foreach h $::punkmake::help_flags { if {[lsearch $scriptargs $h] >= 0} { set do_help 1 break } } } set commands_found [list] foreach a $scriptargs { if {![string match -* $a]} { lappend commands_found $a } else { if {$a ni $::punkmake::non_help_flags} { set do_help 1 } } } if {[llength $commands_found] != 1 } { set do_help 1 } elseif {[lindex $commands_found 0] ni $::punkmake::known_commands} { puts stderr "Unknown command: [lindex $commands_found 0]\n\n" set do_help 1 } if {$do_help} { puts stderr [punkmake_gethelp] exit 0 } set ::punkmake::command [lindex $commands_found 0] if {[lsearch $::argv -k] >= 0} { set forcekill 1 } else { set forcekill 0 } #puts stdout "::argv $::argv" # ---------------------------------------- set scriptfolder $::punkmake::scriptfolder #first look for a project root (something under fossil or git revision control AND matches punk project folder structure) #If that fails - just look for a 'project shaped folder' ie meets minimum requirements of /src /src/lib /src/modules /lib /modules if {![string length [set projectroot [punk::repo::find_project $scriptfolder]]]} { if {![string length [set projectroot [punk::repo::find_candidate $scriptfolder]]]} { puts stderr "punkmake script unable to determine an approprite project root at or above the path '$scriptfolder' ensure the make script is within a project folder structure" puts stderr " -aborted- " exit 2 #todo? #ask user for a project name and create basic structure? #call punk::mix::cli::new $projectname on parent folder? } else { puts stderr "WARNING punkmake script operating in project space that is not under version control" } } else { } set sourcefolder $projectroot/src if {$::punkmake::command eq "get-project-info"} { puts stdout "- -- --- --- --- --- --- --- --- --- ---" puts stdout "- -- get-project-info -- -" puts stdout "- -- --- --- --- --- --- --- --- --- ---" puts stdout "- projectroot : $projectroot" if {[punk::repo::find_fossil $scriptfolder] eq $projectroot} { set vc "fossil" set rev [punk::repo::fossil_revision $scriptfolder] set rem [punk::repo::fossil_remote $scriptfolder] } elseif {[punk::repo::find_git $scriptfolder] eq $projectroot} { set vc "git" set rev [punk::repo::git_revision $scriptfolder] set rem [punk::repo::git_remote $scriptfolder] } else { set vc " - none found -" set rev "n/a" set remotes "n/a" } puts stdout "- version control : $vc" puts stdout "- revision : $rev" puts stdout "- remote : $rem" puts stdout "- -- --- --- --- --- --- --- --- --- ---" exit 0 } if {$::punkmake::command eq "shell"} { package require punk package require punk::repl puts stderr "make shell not fully implemented - dropping into ordinary punk shell" repl::start stdin exit 1 } if {$::punkmake::command eq "bootsupport"} { puts "projectroot: $projectroot" puts "script: [info script]" #puts "-- [tcl::tm::list] --" puts stdout "Updating bootsupport from local files" proc bootsupport_localupdate {projectroot} { set bootsupport_modules [list] set bootsupport_module_folders [list] set bootsupport_config $projectroot/src/bootsupport/include_modules.config ;# if {[file exists $bootsupport_config]} { set targetroot $projectroot/src/bootsupport/modules source $bootsupport_config ;#populate $bootsupport_modules with project-specific list if {![llength $bootsupport_modules]} { puts stderr "No local bootsupport modules configured for updating" } else { if {[catch { #---------- set boot_installer [punkcheck::installtrack new make.tcl $projectroot/src/bootsupport/.punkcheck] $boot_installer set_source_target $projectroot $projectroot/src/bootsupport set boot_event [$boot_installer start_event {-make_step bootsupport}] #---------- } errM]} { puts stderr "Unable to use punkcheck for bootsupport error: $errM" set boot_event "" } foreach {relpath module} $bootsupport_modules { set module [string trim $module :] set module_subpath [string map [list :: /] [namespace qualifiers $module]] set srclocation [file join $projectroot $relpath $module_subpath] #puts stdout "$relpath $module $module_subpath $srclocation" set pkgmatches [glob -nocomplain -dir $srclocation -tail [namespace tail $module]-*] #lsort won't sort version numbers properly e.g with -dictionary 0.1.1 comes before 0.1 if {![llength $pkgmatches]} { puts stderr "Missing source for bootsupport module $module - not found in $srclocation" continue } set latestfile [lindex $pkgmatches 0] set latestver [lindex [split [file rootname $latestfile] -] 1] foreach m $pkgmatches { lassign [split [file rootname $m] -] _pkg ver #puts "comparing $ver vs $latestver" if {[package vcompare $ver $latestver] == 1} { set latestver $ver set latestfile $m } } set srcfile [file join $srclocation $latestfile] set tgtfile [file join $targetroot $module_subpath $latestfile] if {$boot_event ne ""} { #---------- $boot_event targetset_init INSTALL $tgtfile $boot_event targetset_addsource $srcfile #---------- if {\ [llength [dict get [$boot_event targetset_source_changes] changed]]\ || [llength [$boot_event get_targets_exist]] < [llength [$boot_event get_targets]]\ } { file mkdir [file dirname $tgtfile] ;#ensure containing folder for target exists $boot_event targetset_started # -- --- --- --- --- --- puts "BOOTSUPPORT update: $srcfile -> $tgtfile" if {[catch { file copy -force $srcfile $tgtfile } errM]} { $boot_event targetset_end FAILED } else { $boot_event targetset_end OK } # -- --- --- --- --- --- } else { puts -nonewline stderr "." $boot_event targetset_end SKIPPED } $boot_event end } else { file copy -force $srcfile $tgtfile } } if {$boot_event ne ""} { puts \n $boot_event destroy $boot_installer destroy } } foreach folder $bootsupport_module_folders { #explicitly ignore punk/mix/templates folder even if specified in config. #punk/mix/templates contains modules including punk/mix/templates itself - the actual templates aren't needed for the bootsupport system, # as make.tcl shouldn't be building new projects from the one being made. #review. #should we be autodetecting such recursive folder structures - (or is the bootsupport copying in need of a rethink?) if {[string trim $folder /] eq "punk/mix/templates"} { puts stderr "IGNORING punk/mix/templates - not needed/desirable in bootsupport" continue } set src [file join $projectroot/modules $folder] if {![file isdirectory $src]} { puts stderr "bootsupport folder not found: $src" continue } set tgt [file join $targetroot $folder] file mkdir $tgt puts stdout "BOOTSUPPORT non_tm_files $src - copying to $tgt (if source file changed)" set overwrite "installedsourcechanged-targets" set resultdict [punkcheck::install_non_tm_files $src $tgt -installer make.tcl -overwrite $overwrite -punkcheck_folder $projectroot/src/bootsupport] puts stdout [punkcheck::summarize_install_resultdict $resultdict] } } } bootsupport_localupdate $projectroot #/modules/punk/mix/templates/layouts only applies if the project has it's own copy of the punk/mix modules. Generally this should only apply to the punkshell project itself. set layout_bases [list\ $sourcefolder/mixtemplates/layouts\ $sourcefolder/modules/punk/mix/templates/layouts\ ] foreach project_layout_base $layout_bases { if {[file exists $project_layout_base]} { set project_layouts [glob -nocomplain -dir $project_layout_base -type d -tail *] foreach layoutname $project_layouts { if {[file exists [file join $project_layout_base $layoutname/src/bootsupport]]} { set antipaths [list\ README.md\ ] set sourcemodules $projectroot/src/bootsupport/modules set targetroot [file join $project_layout_base $layoutname/src/bootsupport/modules] file mkdir $targetroot puts stdout "BOOTSUPPORT layouts/$layoutname: copying from $sourcemodules to $targetroot (if source file changed)" set resultdict [punkcheck::install $sourcemodules $targetroot -overwrite installedsourcechanged-targets -antiglob_paths $antipaths] puts stdout [punkcheck::summarize_install_resultdict $resultdict] flush stdout } } } else { puts stderr "No layout base at $project_layout_base" } } puts stdout " bootsupport done " flush stderr flush stdout #punk86 can hang if calling make.tcl via 'run' without this 'after' delay. punk87 unaffected. cause unknown. #after 500 ::exit 0 } if {$::punkmake::command ne "project"} { puts stderr "Command $::punkmake::command not implemented - aborting." flush stderr after 100 exit 1 } #only a single consolidated /modules folder used for target set target_modules_base $projectroot/modules file mkdir $target_modules_base #external libs and modules first - and any supporting files - no 'building' required if {[file exists $sourcefolder/vendorlib]} { #exclude README.md from source folder - but only the root one #-antiglob_paths takes relative patterns e.g # */test.txt will only match test.txt exactly one level deep. # */*/*.foo will match any path ending in .foo that is exactly 2 levels deep. # **/test.txt will match at any level below the root (but not in the root) set antipaths [list\ README.md\ ] puts stdout "VENDORLIB: copying from $sourcefolder/vendorlib to $projectroot/lib (if source file changed)" set resultdict [punkcheck::install $sourcefolder/vendorlib $projectroot/lib -overwrite installedsourcechanged-targets -antiglob_paths $antipaths] puts stdout [punkcheck::summarize_install_resultdict $resultdict] } else { puts stderr "VENDORLIB: No src/vendorlib folder found." } if {[file exists $sourcefolder/vendormodules]} { #install .tm *and other files* puts stdout "VENDORMODULES: copying from $sourcefolder/vendormodules to $target_modules_base (if source file changed)" set resultdict [punkcheck::install $sourcefolder/vendormodules $target_modules_base -installer make.tcl -overwrite installedsourcechanged-targets -antiglob_paths {README.md}] puts stdout [punkcheck::summarize_install_resultdict $resultdict] } else { puts stderr "VENDORMODULES: No src/vendormodules folder found." } ######################################################## #templates #e.g The default project layout is mainly folder structure and readme files - but has some scripts developed under the main src that we want to sync #src to src/modules/punk/mix/templates/layouts/project/src set layout_update_list [list\ [list project $sourcefolder/modules/punk/mix/templates]\ [list basic $sourcefolder/mixtemplates]\ ] foreach layoutinfo $layout_update_list { lassign $layoutinfo layout templatebase if {![file exists $templatebase]} { continue } set config [dict create\ -make-step sync_templates\ ] #---------- set tpl_installer [punkcheck::installtrack new make.tcl $templatebase/.punkcheck] $tpl_installer set_source_target $sourcefolder $templatebase set tpl_event [$tpl_installer start_event $config] #---------- set pairs [list] set pairs [list\ [list $sourcefolder/build.tcl $templatebase/layouts/$layout/src/build.tcl]\ [list $sourcefolder/make.tcl $templatebase/layouts/$layout/src/make.tcl]\ ] foreach filepair $pairs { lassign $filepair srcfile tgtfile file mkdir [file dirname $tgtfile] #---------- $tpl_event targetset_init INSTALL $tgtfile $tpl_event targetset_addsource $srcfile #---------- if {\ [llength [dict get [$tpl_event targetset_source_changes] changed]]\ || [llength [$tpl_event get_targets_exist]] < [llength [$tpl_event get_targets]]\ } { $tpl_event targetset_started # -- --- --- --- --- --- puts stdout "punk module templates: Copying from $srcfile to $tgtfile" if {[catch { file copy -force $srcfile $tgtfile } errM]} { $tpl_event targetset_end FAILED -note "copy failed with err: $errM" } else { $tpl_event targetset_end OK -note "test" } # -- --- --- --- --- --- } else { puts stderr "." $tpl_event targetset_end SKIPPED } } $tpl_event end $tpl_event destroy $tpl_installer destroy } ######################################################## #default source module folder is at projectroot/src/modules #There may be multiple other src module folders at same level (e.g folder not being other special-purpose folder and not matching name vendor* that contains at least one .tm file in its root) set source_module_folderlist [punk::mix::cli::lib::find_source_module_paths $projectroot] foreach src_module_dir $source_module_folderlist { puts stderr "Processing source module dir: $src_module_dir" set dirtail [file tail $src_module_dir] #modules and associated files belonging to this package/app set copied [punk::mix::cli::lib::build_modules_from_source_to_base $src_module_dir $target_modules_base -glob *.tm] ;#will only accept a glob ending in .tm #set copied [list] puts stdout "--------------------------" puts stderr "Copied [llength $copied] tm modules from src/$dirtail to $target_modules_base " puts stdout "--------------------------" set overwrite "installedsourcechanged-targets" #set overwrite "ALL-TARGETS" puts stdout "MODULEFOLDER non_tm_files $src_module_dir - copying to $target_modules_base (if source file changed)" set resultdict [punkcheck::install_non_tm_files $src_module_dir $target_modules_base -installer make.tcl -overwrite $overwrite -antiglob_paths {README.md}] puts stdout [punkcheck::summarize_install_resultdict $resultdict] } set installername "make.tcl" # ---------------------------------------- if {[punk::repo::is_fossil_root $projectroot]} { set config [dict create\ -make-step configure_fossil\ ] #---------- set installer [punkcheck::installtrack new $installername $projectroot/.punkcheck] $installer set_source_target $projectroot $projectroot set event [$installer start_event $config] $event targetset_init VIRTUAL fossil_settings_mainmenu ;#VIRTUAL - since there is no actual target file set menufile $projectroot/.fossil-custom/mainmenu $event targetset_addsource $menufile #---------- if {\ [llength [dict get [$event targetset_source_changes] changed]]\ } { $event targetset_started # -- --- --- --- --- --- puts stdout "Configuring fossil setting: mainmenu from: $menufile" if {[catch { set fd [open $menufile r] fconfigure $fd -translation binary set data [read $fd] close $fd exec fossil settings mainmenu $data } errM]} { $event targetset_end FAILED -note "fossil update failed: $errM" } else { $event targetset_end OK } # -- --- --- --- --- --- } else { puts stderr "." $event targetset_end SKIPPED } $event end $event destroy $installer destroy } set buildfolder [punk::mix::cli::lib::get_build_workdir $sourcefolder] if {$buildfolder ne "$sourcefolder/_build"} { puts stderr "$sourcefolder/_build doesn't match the project buildfolder $buildfolder - check project filestructure" puts stdout " -aborted- " exit 2 } #find runtimes set rtfolder $sourcefolder/runtime set runtimes [glob -nocomplain -dir $rtfolder -types {f x} -tail *] if {![llength $runtimes]} { puts stderr "No executable runtimes found in $rtfolder - unable to build any .vfs folders into executables." puts stderr "Add runtimes to $sourcefolder/runtime if required" exit 0 } if {[catch {exec sdx help} errM]} { puts stderr "FAILED to find usable sdx command - check that sdx executable is on path" puts stderr "err: $errM" exit 1 } # -- --- --- --- --- --- --- --- --- --- #load mapvfs.config file (if any) in runtime folder to map runtimes to vfs folders. #build a dict keyed on runtime executable name. #If no mapfile (or no mapfile entry for that runtime) - the runtime will be paired with a matching .vfs folder in src folder. e.g punk.exe to src/punk.vfs #If vfs folders or runtime executables which are explicitly listed in the mapfile don't exist - warn on stderr - but continue. if such nonexistants found; prompt user for whether to continue or abort. set mapfile $rtfolder/mapvfs.config set runtime_vfs_map [dict create] set vfs_runtime_map [dict create] if {[file exists $mapfile]} { set fdmap [open $mapfile r] fconfigure $fdmap -translation binary set mapdata [read $fdmap] close $fdmap set mapdata [string map [list \r\n \n] $mapdata] set missing [list] foreach ln [split $mapdata \n] { set ln [string trim $ln] if {$ln eq "" || [string match #* $ln]} { continue } set vfspaths [lassign $ln runtime] if {[string match *.exe $runtime]} { #.exe is superfluous but allowed #drop windows .exe suffix so same config can work cross platform - extension will be re-added if necessary later set runtime [string range $runtime 0 end-4] } if {$runtime ne "-"} { set runtime_test $runtime if {"windows" eq $::tcl_platform(platform)} { set runtime_test $runtime.exe } if {![file exists [file join $rtfolder $runtime_test]]} { puts stderr "WARNING: Missing runtime file $rtfolder/$runtime_test (line in mapvfs.config: $ln)" lappend missing $runtime } } foreach vfs $vfspaths { if {![file isdirectory [file join $sourcefolder $vfs]]} { puts stderr "WARNNING: Missing vfs folder [file join $sourcefolder $vfs] specified in mapvfs.config for runtime $runtime" lappend missing $vfs } dict lappend vfs_runtime_map $vfs $runtime } if {[dict exists $runtime_vfs_map $runtime]} { puts stderr "CONFIG FILE ERROR. runtime: $runtime was specified more than once in $mapfile." exit 3 } dict set runtime_vfs_map $runtime $vfspaths } if {[llength $missing]} { puts stderr "WARNING [llength $missing] missing items from $mapfile. (TODO - prompt user to continue/abort)" foreach m $missing { puts stderr " $m" } puts stderr "continuing..." } } # -- --- --- --- --- --- --- --- --- --- set vfs_folders [glob -nocomplain -dir $sourcefolder -types d -tail *.vfs] #add any extra .vfs folders found in runtime/mapvfs.config file (e.g myotherruntimes/something.vfs) foreach vfs [dict keys $vfs_runtime_map] { if {$vfs ni $vfs_folders} { lappend vfs_folders $vfs } } if {![llength $vfs_folders]} { puts stdout "No .vfs folders found at '$sourcefolder' - no kits to build" puts stdout " -done- " exit 0 } set vfs_folder_changes [dict create] ;#cache whether each .vfs folder has changes so we don't re-run tests if building from same .vfs with multiple runtime executables # -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- #set runtimefile [lindex $runtimes 0] foreach runtimefile $runtimes { #runtimefile e.g tclkit86bi.exe on windows tclkit86bi on other platforms #sdx *may* be pointed to use the runtime we use to build the kit, or the user may manually use this runtime if they don't have tclsh #sdx will complain if the same runtime is used for the shell as is used in the -runtime argument - so we make a copy (REVIEW) #if {![file exists $buildfolder/buildruntime.exe]} { # file copy $rtfolder/$runtimefile $buildfolder/buildruntime.exe #} set basedir $buildfolder set config [dict create\ -make-step copy_runtime\ ] #---------- set installer [punkcheck::installtrack new $installername $basedir/.punkcheck] $installer set_source_target $rtfolder $buildfolder set event [$installer start_event $config] $event targetset_init INSTALL $buildfolder/build_$runtimefile $event targetset_addsource $rtfolder/$runtimefile #---------- #set changed_unchanged [punkcheck::recordlist::file_install_record_source_changes [lindex [dict get $file_record body] end]] if {\ [llength [dict get [$event targetset_source_changes] changed]]\ || [llength [$event get_targets_exist]] < [llength [$event get_targets]]\ } { $event targetset_started # -- --- --- --- --- --- puts stdout "Copying runtime from $rtfolder/$runtimefile to $buildfolder/build_$runtimefile" if {[catch { file copy -force $rtfolder/$runtimefile $buildfolder/build_$runtimefile } errM]} { $event targetset_end FAILED } else { $event targetset_end OK } # -- --- --- --- --- --- } else { puts stderr "." $event targetset_end SKIPPED } $event end } # -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # # loop over vfs_folders and for each one, loop over configured (or matching) runtimes - build with sdx if source .vfs or source runtime exe has changed. # we are using punkcheck to install result to buildfolder so we create a .punkcheck file at the target folder to store metadata. # punkcheck allows us to not rely purely on timestamps (which may be unreliable) # set startdir [pwd] puts stdout "Found [llength $vfs_folders] .vfs folders - building executable for each..." cd [file dirname $buildfolder] #root folder mtime is insufficient for change detection. Tree mtime of folders only is a barely passable mechanism for vfs change detection in some circumstances - e.g if files added/removed but never edited in place #a hash of full tree file & dir mtime may be more reasonable - but it remains to be seen if just tar & checksum is any/much slower. #Simply rebuilding all the time may be close the speed of detecting change anyway - and almost certainly much faster when there is a change. #Using first mtime encountered that is later than target is another option - but likely to be highly variable in speed. Last file in the tree could happen to be the latest, and this mechanism doesn't handle build on reversion to older source. set exe_names_seen [list] foreach vfs $vfs_folders { set vfsname [file rootname $vfs] puts stdout " Processing vfs $sourcefolder/$vfs" puts stdout " ------------------------------------" set skipped_vfs_build 0 # -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- set basedir $buildfolder set config [dict create\ -make-step build_vfs\ ] set runtimes [list] if {[dict exists $vfs_runtime_map $vfs]} { set runtimes [dict get $vfs_runtime_map $vfs] ;#map dict is unsuffixed (.exe stripped or was not present) if {"windows" eq $::tcl_platform(platform)} { set runtimes_raw $runtimes set runtimes [list] foreach rt $runtimes_raw { if {![string match *.exe $rt] && $rt ne "-"} { set rt $rt.exe } lappend runtimes $rt } } } else { #only match this vfs to a correspondingly named runtime if there was no explicit entry for that runtime set matchrt [file rootname [file tail $vfs]] ;#e.g project.vfs -> project if {![dict exists $runtime_vfs_map $matchrt]} { if {"windows" eq $::tcl_platform(platform)} { if {[file exists $rtfolder/$matchrt.exe]} { lappend runtimes $matchrt.exe } } else { lappend runtimes $matchrt } } } #assert $runtimes is a list of executable names suffixed with .exe if on windows - whether or not specified with .exe in the mapvfs.config #todo - non kit based - zipkit? # $runtimes may now include a dash entry "-" (from mapvfs.config file) foreach rtname $runtimes { #rtname of "-" indicates build a kit without a runtime #first configured runtime will be the one to use the same name as .vfs folder for output executable. Additional runtimes on this .vfs will need to suffix the runtime name to disambiguate. #review: This mechanism may not be great for multiplatform builds ? We may be better off consistently combining vfsname and rtname and letting a later platform-specific step choose ones to install in bin with simpler names. if {$rtname eq "-"} { set targetkit $vfsname.kit } else { if {$::tcl_platform(platform) eq "windows"} { set targetkit ${vfsname}.exe } else { set targetkit $vfsname } if {$targetkit in $exe_names_seen} { #more than one runtime for this .vfs set targetkit ${vfsname}_$rtname } } lappend exe_names_seen $targetkit # -- ---------- set vfs_installer [punkcheck::installtrack new $installername $basedir/.punkcheck] $vfs_installer set_source_target $sourcefolder $buildfolder set vfs_event [$vfs_installer start_event {-make-step build_vfs}] $vfs_event targetset_init INSTALL $buildfolder/$targetkit $vfs_event targetset_addsource $sourcefolder/$vfs if {$rtname ne "-"} { $vfs_event targetset_addsource $buildfolder/build_$rtname } # -- ---------- set changed_unchanged [$vfs_event targetset_source_changes] if {[llength [dict get $changed_unchanged changed]] || [llength [$vfs_event get_targets_exist]] < [llength [$vfs_event get_targets]]} { #source .vfs folder has changes $vfs_event targetset_started # -- --- --- --- --- --- #use if {[file exists $buildfolder/$vfsname.new]} { puts stderr "deleting existing $buildfolder/$vfsname.new" file delete $buildfolder/$vfsname.new } puts stdout "building $vfsname with sdx.. vfsdir:$vfs cwd: [pwd]" if {[catch { if {$rtname ne "-"} { exec sdx wrap $buildfolder/$vfsname.new -vfs $sourcefolder/$vfs -runtime $buildfolder/build_$rtname -verbose } else { exec sdx wrap $buildfolder/$vfsname.new -vfs $sourcefolder/$vfs -verbose } } result]} { if {$rtname ne "-"} { puts stderr "sdx wrap $buildfolder/$vfsname.new -vfs $sourcefolder/$vfs -runtime $buildfolder/build_$rtname -verbose failed with msg: $result" } else { puts stderr "sdx wrap $buildfolder/$vfsname.new -vfs $sourcefolder/$vfs -verbose failed with msg: $result" } } else { puts stdout "ok - finished sdx" set separator [string repeat = 40] puts stdout $separator puts stdout $result puts stdout $separator } if {![file exists $buildfolder/$vfsname.new]} { puts stderr "|err> make.tcl build didn't seem to produce output at $sourcefolder/_build/$vfsname.new" $vfs_event targetset_end FAILED exit 2 } # -- --- --- if {$::tcl_platform(platform) eq "windows"} { set pscmd "tasklist" } else { set pscmd "ps" } #killing process doesn't apply to .kit build if {$rtname ne "-"} { if {![catch { exec $pscmd | grep $vfsname } still_running]} { puts stdout "found $vfsname instances still running\n" set count_killed 0 foreach ln [split $still_running \n] { puts stdout " $ln" if {$::tcl_platform(platform) eq "windows"} { set pid [lindex $ln 1] if {$forcekill} { set killcmd [list taskkill /F /PID $pid] } else { set killcmd [list taskkill /PID $pid] } } else { set pid [lindex $ln 0] #review! if {$forcekill} { set killcmd [list kill -9 $pid] } else { set killcmd [list kill $pid] } } puts stdout " pid: $pid (attempting to kill now using '$killcmd')" if {[catch { exec {*}$killcmd } errMsg]} { puts stderr "$killcmd returned an error:" puts stderr $errMsg if {!$forcekill} { puts stderr "(try '[info script] -k' option to force kill)" } #avoid exiting if the kill failure was because the task has already exited #review - *no running instance* works with windows taskkill - "*No such process*" works with kill -9 on FreeBSD and linux - other platforms? if {![string match "*no running instance*" $errMsg] && ![string match "*No such process*" $errMsg]} { exit 4 } } else { puts stderr "$killcmd ran without error" incr count_killed } } if {$count_killed > 0} { puts stderr "\nKilled $count_killed processes. Waiting a short time before attempting to delete executable" after 1000 } } else { puts stderr "Ok.. no running '$vfsname' processes found" } } if {[file exists $buildfolder/$targetkit]} { puts stderr "deleting existing $buildfolder/$targetkit" if {[catch { file delete $buildfolder/$targetkit } msg]} { puts stderr "Failed to delete $buildfolder/$targetkit" exit 4 } } #WINDOWS filesystem 'tunneling' (file replacement within 15secs) could cause targetkit to copy ctime & shortname metadata from previous file! #This is probably harmless - but worth being aware of. file rename $buildfolder/$vfsname.new $buildfolder/$targetkit # -- --- --- --- --- --- $vfs_event targetset_end OK after 200 set deployment_folder [file dirname $sourcefolder]/bin file mkdir $deployment_folder # -- ---------- set bin_installer [punkcheck::installtrack new "make.tcl" $deployment_folder/.punkcheck] $bin_installer set_source_target $buildfolder $deployment_folder set bin_event [$bin_installer start_event {-make-step final_kit_install}] $bin_event targetset_init INSTALL $deployment_folder/$targetkit #todo - move final deployment step outside of the build vfs loop? (final deployment can fail and then isn't rerun even though _build and deployed versions differ, unless .vfs modified again) #set last_completion [$bin_event targetset_last_complete] $bin_event targetset_addsource $buildfolder/$targetkit $bin_event targetset_started # -- ---------- set delete_failed 0 if {[file exists $deployment_folder/$targetkit]} { puts stderr "deleting existing deployed at $deployment_folder/$targetkit" if {[catch { file delete $deployment_folder/$targetkit } errMsg]} { puts stderr "deletion of deployed version at $deployment_folder/$targetkit failed: $errMsg" set delete_failed 1 } } if {!$delete_failed} { puts stdout "copying.." puts stdout "$buildfolder/$targetkit" puts stdout "to:" puts stdout "$deployment_folder/$targetkit" after 300 file copy $buildfolder/$targetkit $deployment_folder/$targetkit # -- ---------- $bin_event targetset_end OK # -- ---------- } else { $bin_event targetset_end FAILED -note "could not delete" exit 5 } $bin_event destroy $bin_installer destroy } else { set skipped_vfs_build 1 puts stderr "." puts stdout "Skipping build for vfs $vfs with runtime $rtname - no change detected" $vfs_event targetset_end SKIPPED } $vfs_event destroy $vfs_installer destroy } ;#end foreach rtname in runtimes # -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- } cd $startdir puts stdout "done" exit 0