shell-1.1.4.tm 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. # -*- tcl -*-
  2. # ### ### ### ######### ######### #########
  3. ## Overview
  4. # Higher-level commands which invoke the functionality of this package
  5. # for an arbitrary tcl shell (tclsh, wish, ...). This is required by a
  6. # repository as while the tcl shell executing packages uses the same
  7. # platform in general as a repository application there can be
  8. # differences in detail (i.e. 32/64 bit builds).
  9. # ### ### ### ######### ######### #########
  10. ## Requirements
  11. package require platform
  12. namespace eval ::platform::shell {}
  13. # ### ### ### ######### ######### #########
  14. ## Implementation
  15. # -- platform::shell::generic
  16. proc ::platform::shell::generic {shell} {
  17. # Argument is the path to a tcl shell.
  18. CHECK $shell
  19. LOCATE base out
  20. set code {}
  21. # Forget any pre-existing platform package, it might be in
  22. # conflict with this one.
  23. lappend code {package forget platform}
  24. # Inject our platform package
  25. lappend code [list source $base]
  26. # Query and print the architecture
  27. lappend code {puts [platform::generic]}
  28. # And done
  29. lappend code {exit 0}
  30. set arch [RUN $shell [join $code \n]]
  31. if {$out} {file delete -force $base}
  32. return $arch
  33. }
  34. # -- platform::shell::identify
  35. proc ::platform::shell::identify {shell} {
  36. # Argument is the path to a tcl shell.
  37. CHECK $shell
  38. LOCATE base out
  39. set code {}
  40. # Forget any pre-existing platform package, it might be in
  41. # conflict with this one.
  42. lappend code {package forget platform}
  43. # Inject our platform package
  44. lappend code [list source $base]
  45. # Query and print the architecture
  46. lappend code {puts [platform::identify]}
  47. # And done
  48. lappend code {exit 0}
  49. set arch [RUN $shell [join $code \n]]
  50. if {$out} {file delete -force $base}
  51. return $arch
  52. }
  53. # -- platform::shell::platform
  54. proc ::platform::shell::platform {shell} {
  55. # Argument is the path to a tcl shell.
  56. CHECK $shell
  57. set code {}
  58. lappend code {puts $tcl_platform(platform)}
  59. lappend code {exit 0}
  60. return [RUN $shell [join $code \n]]
  61. }
  62. # ### ### ### ######### ######### #########
  63. ## Internal helper commands.
  64. proc ::platform::shell::CHECK {shell} {
  65. if {![file exists $shell]} {
  66. return -code error "Shell \"$shell\" does not exist"
  67. }
  68. if {![file executable $shell]} {
  69. return -code error "Shell \"$shell\" is not executable (permissions)"
  70. }
  71. return
  72. }
  73. proc ::platform::shell::LOCATE {bv ov} {
  74. upvar 1 $bv base $ov out
  75. # Locate the platform package for injection into the specified
  76. # shell. We are using package management to find it, whereever it
  77. # is, instead of using hardwired relative paths. This allows us to
  78. # install the two packages as TMs without breaking the code
  79. # here. If the found package is wrapped we copy the code somewhere
  80. # where the spawned shell will be able to read it.
  81. # This code is brittle, it needs has to adapt to whatever changes
  82. # are made to the TM code, i.e. the provide statement generated by
  83. # tm.tcl
  84. set pl [package ifneeded platform [package require platform]]
  85. set base [lindex $pl end]
  86. set out 0
  87. if {[lindex [file system $base]] ne "native"} {
  88. set temp [TEMP]
  89. file copy -force $base $temp
  90. set base $temp
  91. set out 1
  92. }
  93. return
  94. }
  95. proc ::platform::shell::RUN {shell code} {
  96. set c [TEMP]
  97. set cc [open $c w]
  98. puts $cc $code
  99. close $cc
  100. set e [TEMP]
  101. set code [catch {
  102. exec $shell $c 2> $e
  103. } res]
  104. file delete $c
  105. if {$code} {
  106. append res \n[read [set chan [open $e r]]][close $chan]
  107. file delete $e
  108. return -code error "Shell \"$shell\" is not executable ($res)"
  109. }
  110. file delete $e
  111. return $res
  112. }
  113. proc ::platform::shell::TEMP {} {
  114. set prefix platform
  115. # This code is copied out of Tcllib's fileutil package.
  116. # (TempFile/tempfile)
  117. set tmpdir [DIR]
  118. set chars "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
  119. set nrand_chars 10
  120. set maxtries 10
  121. set access [list RDWR CREAT EXCL TRUNC]
  122. set permission 0600
  123. set channel ""
  124. set checked_dir_writable 0
  125. set mypid [pid]
  126. for {set i 0} {$i < $maxtries} {incr i} {
  127. set newname $prefix
  128. for {set j 0} {$j < $nrand_chars} {incr j} {
  129. append newname [string index $chars \
  130. [expr {int(rand()*62)}]]
  131. }
  132. set newname [file join $tmpdir $newname]
  133. if {[file exists $newname]} {
  134. after 1
  135. } else {
  136. if {[catch {open $newname $access $permission} channel]} {
  137. if {!$checked_dir_writable} {
  138. set dirname [file dirname $newname]
  139. if {![file writable $dirname]} {
  140. return -code error "Directory $dirname is not writable"
  141. }
  142. set checked_dir_writable 1
  143. }
  144. } else {
  145. # Success
  146. close $channel
  147. return [file normalize $newname]
  148. }
  149. }
  150. }
  151. if {$channel ne ""} {
  152. return -code error "Failed to open a temporary file: $channel"
  153. } else {
  154. return -code error "Failed to find an unused temporary file name"
  155. }
  156. }
  157. proc ::platform::shell::DIR {} {
  158. # This code is copied out of Tcllib's fileutil package.
  159. # (TempDir/tempdir)
  160. global tcl_platform env
  161. set attempdirs [list]
  162. foreach tmp {TMPDIR TEMP TMP} {
  163. if { [info exists env($tmp)] } {
  164. lappend attempdirs $env($tmp)
  165. }
  166. }
  167. switch $tcl_platform(platform) {
  168. windows {
  169. lappend attempdirs "C:\\TEMP" "C:\\TMP" "\\TEMP" "\\TMP"
  170. }
  171. macintosh {
  172. set tmpdir $env(TRASH_FOLDER) ;# a better place?
  173. }
  174. default {
  175. lappend attempdirs \
  176. [file join / tmp] \
  177. [file join / var tmp] \
  178. [file join / usr tmp]
  179. }
  180. }
  181. lappend attempdirs [pwd]
  182. foreach tmp $attempdirs {
  183. if { [file isdirectory $tmp] && [file writable $tmp] } {
  184. return [file normalize $tmp]
  185. }
  186. }
  187. # Fail if nothing worked.
  188. return -code error "Unable to determine a proper directory for temporary files"
  189. }
  190. # ### ### ### ######### ######### #########
  191. ## Ready
  192. package provide platform::shell 1.1.4