**execx - EXECuting transparently out of the VFS of a starkit/starpack** The following simplifies the execution of .EXEs that are inside of a [Starkit]s/[Starpack]s [VFS] by transparently copying them out of the VFS to a temporary location and EXECing them from there (just as it already works from the Tcl core with LOADing DLLs). I pack various external tools needed by my scripts in the VFS because it's not always clear if the machine the script is later running on has such tools installed in the path or if the tool-versions are correct. So it's safer to deliver the tools right within the script and to make a temp copy out of the VFS and call them from there. However, the default is to use an external available tool (to save the copy step). To do/Notes: * Make the temporary-destination-location a parameter, at least more robust (%temp% may not exist everywhere or may not be writable...). * Translate to the English language. * Beginning with version 1.5 it is possible to unpack whole directories out of the VFS. After unpacking, an executable with the same name as the directory is EXECed. This is because some programs need to have their config files aside them. For example, if you want to call a program named '''the.exe''' that needs a config file '''profile.the''', put these two together in ''VFS''/tools or ''VFS''/bin in a directory named '''the''' - that's all. * Beside different progs I use this tool in a project that takes the concept to an even higher level: if a requested tool is neither on the local search path, nor in the VFS, it is transparently loaded from a http-based Server (of course, driven by [tclhttpd]). I don't need to install each and every simple tool on each and every server where I need it ;-) But, this advanced logic is not included in execx. * I think parts of my implementation rely on the fact that [exec] has a built-in kind of intelligence: even on [Microsoft Windows] one can start a program without specifying the file extension... **The Package** ---- ====== ################################################################################ # Modul : execx.tcl # Stand : 26.02.2008 # Zweck : Erweiterter Exec-Befehl, um Programme direkt aus Starkits/- # packs heraus ausführen zu können. Dazu wird das Programm (oder ein # ganzer Ordner) vor EXEC an eine temporäre Position kopiert. # ACHTUNG: Nur das erste Programm einer Pipe (|) wird berücksichtigt. # Autor : M.Hoffmann # Historie : 1.0 21.10.2003: Urversion # : 1.0 05.08.2004: Review Urversion (list $progCallNew) # : 1.1 09.08.2004: execx::setforce execx::settrace # : 1.2 21.10.2005: Handling für &-Prozesse geändert; running geändert; # Uplevel statt eval. # : 1.3 28.10.2005: BugFix. # : 1.4 15.11.2005, Neben Tools auch Bin als Quellverz. im VFS (später # 16.11.2005: per Kommando beeinflußbar machen); # (für neue Tcl-Standardumgebung). Neu: fehlt eine # Endung, wird erste übereinstimmende Datei im VFS # verwendet (erübrigt explizite .Ext-Angabe). Bugfix. # : 1.5 13.01.2006: Unterstützung von QuellVERZ im VFS, Name := EXEName # : 1.6 19.01.2006: Bugfix. # : 1.7 18.12.2007: Bugfix/Workaround: Aufruf scheiterte, wenn selbes # Programm bereits im Temp vorhanden und gelockt. Nun # die bereits vorhandene Instanz starten (ein # gewisses Risiko. Sicherer wäre, einen Tempnamen zu # verwenden und zu starten, denn sonst kann jemand # ein beliebiges Programm unterjubeln). Abbruch, wenn # Programm nicht auffindbar + nicht im VFS, oder bei # Kopierfehlern. # : 1.8 20.12.2007: Bugfix. # : 1.9 26.02.2008: Bugfix; setforce, settrace mit -1 -> query; # Ergebnis von EXEC zurückliefern (Code); recodiert! # Ideen : Caching der Suchergebnisse zwecks Beschleunigung. ################################################################################ package provide execx 1.9 namespace eval execx { variable running variable force 0 variable exectrace 0 variable vfsDirs array set running {} set vfsDirs [list bin tools]; # kein externes Interface für diese Var } proc execx::setforce {{setting -1}} { variable force if {$setting != -1} {set force $setting} return $force } #---------------------------------------------------------------------------------- proc execx::settrace {{setting -1}} { variable exectrace if {$setting != -1} {set exectrace $setting} return $exectrace } #---------------------------------------------------------------------------------- # args - Parameter genau wie für Originalexec-Befehl (Pipes nicht unterstützt!) # Rückgabe: wie Originalbefehl # Sofern ein Programm nicht im Hintergrund gestartet wird, wird eine evtl. # temporäre Kopie wieder gelöscht; ansonsten wird ein Namespacearray mit dem # Programmnamen gefüllt. # proc execx::execx1 {args} { variable running variable force variable exectrace variable vfsDirs set progIdx -1 foreach a $args { incr progIdx if {$a != "-keepnewline" && $a != "--"} { break; } } set toDelete "" set progCallOrg [lindex $args $progIdx] if { $force == 1 || [auto_execok $progCallOrg] == "" } { # Programm nicht im Dateisystem gefunden, oder Einkopieren aus VFS # zwingend -> also zunächst im VFS nach Ordner oder Dateimatch suchen set progName [file tail $progCallOrg] foreach dir $vfsDirs { set toolDir [file join $::starkit::topdir $dir]; # hier suchen # Extension ist unbekannt, erster Match gilt! # ACHTUNG: glob im VFS arbeitet CASE-sensitiv! set match [lindex [glob -nocomplain -dir $toolDir $progName*] 0] # Hinweis: [file normalize] scheint nicht zu funktionieren mit VFS if {[string length $match]} { break } elseif {$dir == [lindex $vfsDirs end]} { return -code error "Programm nicht im VFS gefunden!" } } set dest [file join $::env(temp) [file tail $match]] # Datei ODER Ordner kopieren... if {![file exists $dest]} { # ...wenn noch nicht im Ziel vorhanden if {[catch {eval [list file copy -force -- $match $::env(temp)]} rc]} { return -code error $rc } } set progCallNew $dest set toDelete $progCallNew if {[file isdirectory $match]} { set progCallNew [file join $progCallNew [file tail $match]] } lset args $progIdx [list $progCallNew] } if {$execx::exectrace} { puts -nonewline {>>> } puts $args } set code [catch {uplevel exec $args} rc] # TempProgramm löschen, sofern es nicht im Hintergrund läuft # Ansonsten den Namen für späteres Löschen in execx::running(PID) sichern if {[lindex $args end] != "&"} { # Programm lief im Vordergrund if {$toDelete != ""} { # und war aus VFS einkopiert -> Beseitigen catch {file delete -force -- $toDelete} } } else { # Programm läuft noch im Hintergrund -> Spec sicherstellen als Löschhilfe set running($rc) [list $args $toDelete] } return -code $code $rc; # PID oder Ergebnis zurückgeben, Status propagieren } #================================================================================== ====== ---- **Testroutine** ====== ################################################################################### # Modul : execxtest.tcl # Stand : 19.01.2005, 26.02.2008 # Zweck : Tests des Pakets execx (nur syntaktisch, da kein VFS vorhanden) ################################################################################### lappend auto_path ./ package require execx # fake Starkit namespace eval starkit { variable topdir [pwd] } puts "\[execx::settrace\] -> [execx::settrace]" puts "\[execx::settrace 1\] -> [execx::settrace 1]" puts "\[execx::settrace -1\] -> [execx::settrace -1]" puts "\[execx::settrace 0\] -> [execx::settrace 0]" puts "\[execx::settrace -1\] -> [execx::settrace -1]" puts "\[execx::settrace 1\] -> [execx::settrace 1]" puts "\[execx::setforce\] -> [execx::setforce]" puts "\[execx::setforce 1\] -> [execx::setforce 1]" puts "\[execx::setforce -1\] -> [execx::setforce -1]" puts "\[execx::setforce 0\] -> [execx::setforce 0]" puts "\[execx::setforce -1\] -> [execx::setforce -1]" # 1. Aufruf eines üblicherweise vorhandenen Programmes (nicht im VFS), synchron puts "execx::execx1 ipconfig" catch {execx::execx1 ipconfig} rc puts $rc # 2. Aufruf eines üblicherweise vorhandenen Programmes (nicht im VFS), asynchron puts "execx::execx1 ipconfig &" catch {execx::execx1 ipconfig &} rc puts $rc parray ::execx::running # ab jetzt nur noch im VFS suchen puts "\[execx::setforce 1\] -> [execx::setforce 1]" # 3. Aufruf von say1.exe aus /bin, synchron puts "execx::execx1 say1" catch {execx::execx1 say1} rc puts $rc # 4. Aufruf von say1.exe aus /bin, asynchron puts "execx::execx1 say1 &" catch {execx::execx1 say1 &} rc puts $rc parray ::execx::running # 5. Aufruf von say2.exe aus /tools, synchron puts "execx::execx1 say2.exe" catch {execx::execx1 say2.exe} rc puts $rc # 6. Aufruf von say3 aus /tools/say3, synchron puts "execx::execx1 say3" catch {execx::execx1 say3} rc puts $rc # 7. Aufruf von say3 aus /tools/say3, asynchron puts "execx::execx1 say3 &" catch {execx::execx1 say3 &} rc puts $rc parray ::execx::running puts "Wenn Programm beendet, Strg+C drücken...." vwait forever #================================================================================== ====== * say1, say2 and say3 are little executables which simply put their own name to stdout. I use the following dir structure for this test: ...\_TESTSDOC\EXECX │ execxtest.tcl │ ├───bin │ say1.exe │ └───tools │ say2.exe │ └───say3 say3.ccc say3.exe ---- **Comments** Yes, you need a standard '''pkgIndex.tcl''' file, which is not included here. [LV] Anyone want to code one up and add it to the page? [MHo]: Here's one: package ifneeded execx 1.9 [list source [file join $dir execx.tcl]] ---- Notes: There are some difficulties starting ''Console-mode''-programs in the background with this method, because when execx has finished launching such kind of progs and finishes itself, immediately the exec'd program loses its console-connection, it seems. Not sure yet what happens in detail - but one has to press a key then to terminate the whole thing. That's why I included a '''vwait forever''' in the test routine. For such programs, it is better to launch them in the background with this tool: [Matthias Hoffmann - Tcl-Code-Snippets - Misc - Bgexec]. One important difference between the standard [exec] and '''execx::execx1'''-command is that only the first program in the process pipeline is handled, that is, copied out of the VFS, if neccessary. So, constructs like '''exec::execx1 -- test1 |& test2''' won't work, if ''test2'' only resides within the VFS! Conclusion: to me it seems to be nearly impossible to reimplement the whole [exec]-command with all of its complicated aspects.... [exec] should really be modified to ''natively'' support exec from [starpack]s! * 2007/07/04: [MHo]: uploaded source code again because some german characters where messed... * 2007/12/18: [MHo]: v'''1.7'''. Fixed: program copied and loaded before cannot be copied and loaded again (now loading existing copy instead stopping silently); returning errors if no startable program found or error occured during copy from VFS to Temp. * 2007/12/20: [MHo] v'''1.8'''. Fixed another (stupid) bug. * 2008/02/26: [MHo] v'''1.9'''. Fixed bug when extracting a whole dir. Fixed other bugs. Recoded everything because I lost overwiew - almost everything remains compatible... ---- [LV] 2007 Dec 18 Have you submitted an enhancement request at http://tcl.sf.net/ for the changes in exec that you want? Perhaps you could see if you could interest whatever maintenance person is responsible for exec to join with you to write a [TIP] on the topic. [MHo] I think [VFS, exec and command pipelines] goes in this direction! ---- [MHo] 2009 Jun 28 v'''1.10''' is in progress. I tried to parse the exec statement to support copying of ''all'' the needed programs of a command pipe out of the vfs, but didn't succeed. So see the whole new try at [execx2]! <> Tclkit