diff options
author | William Joye <wjoye@cfa.harvard.edu> | 2016-10-27 19:39:39 (GMT) |
---|---|---|
committer | William Joye <wjoye@cfa.harvard.edu> | 2016-10-27 19:39:39 (GMT) |
commit | ea28451286d3ea4a772fa174483f9a7a66bb1ab3 (patch) | |
tree | 6ee9d8a7848333a7ceeee3b13d492e40225f8b86 /tcllib/installer.tcl | |
parent | b5ca09bae0d6a1edce939eea03594dd56383f2c8 (diff) | |
parent | 7c621da28f07e449ad90c387344f07a453927569 (diff) | |
download | blt-ea28451286d3ea4a772fa174483f9a7a66bb1ab3.zip blt-ea28451286d3ea4a772fa174483f9a7a66bb1ab3.tar.gz blt-ea28451286d3ea4a772fa174483f9a7a66bb1ab3.tar.bz2 |
Merge commit '7c621da28f07e449ad90c387344f07a453927569' as 'tcllib'
Diffstat (limited to 'tcllib/installer.tcl')
-rwxr-xr-x | tcllib/installer.tcl | 595 |
1 files changed, 595 insertions, 0 deletions
diff --git a/tcllib/installer.tcl b/tcllib/installer.tcl new file mode 100755 index 0000000..2dcf8a9 --- /dev/null +++ b/tcllib/installer.tcl @@ -0,0 +1,595 @@ +#!/bin/sh +# -*- tcl -*- \ +exec tclsh "$0" ${1+"$@"} + +# -------------------------------------------------------------- +# Installer for Tcllib. The lowest version of the tcl core supported +# by any module is 8.2. So we enforce that the installer is run with +# at least that. + +package require Tcl 8.2 + +set distribution [file dirname [info script]] +lappend auto_path [file join $distribution modules] + + +# -------------------------------------------------------------- +# Version information for tcllib. +# List of modules to install (and definitions guiding the process) + +proc package_name {text} {global package_name ; set package_name $text} +proc package_version {text} {global package_version ; set package_version $text} +proc dist_exclude {path} {} +proc critcl {name files} {} +proc critcl_main {name files} {} +proc critcl_notes {text} {} + +source [file join $distribution support installation version.tcl] ; # Get version information. +source [file join $distribution support installation modules.tcl] ; # Get list of installed modules. +source [file join $distribution support installation actions.tcl] ; # Get code to perform install actions. + +set package_nv ${package_name}-${package_version} +set package_name_cap [string toupper [string index $package_name 0]][string range $package_name 1 end] + +# -------------------------------------------------------------- +# Low-level commands of the installation engine. + +proc gen_main_index {outdir package version} { + global config + + log "\nGenerating [file join $outdir pkgIndex.tcl]" + if {$config(dry)} {return} + + set index [open [file join $outdir pkgIndex.tcl] w] + + puts $index "# Tcl package index file, version 1.1" + puts $index "# Do NOT edit by hand. Let $package install generate this file." + puts $index "# Generated by $package installer for version $version" + + puts $index { +# All tcllib packages need Tcl 8 (use [namespace]) +if {![package vsatisfies [package provide Tcl] 8]} {return} + +# Extend the auto_path to make tcllib packages available +if {[lsearch -exact $::auto_path $dir] == -1} { + lappend ::auto_path $dir +} + +# For Tcl 8.3.1 and later, that's all we need +if {[package vsatisfies [package provide Tcl] 8.4]} {return} +if {(0 == [catch { + package vcompare [info patchlevel] [info patchlevel] +}]) && ( + [package vcompare [info patchlevel] 8.3.1] >= 0 +)} {return} + +# For older Tcl releases, here are equivalent contents +# of the pkgIndex.tcl files of all the modules + +if {![package vsatisfies [package provide Tcl] 8.0]} {return} +} + puts $index "" + puts $index "set maindir \$dir" + + foreach pi [lsort [glob -nocomplain [file join $outdir * pkgIndex.tcl]]] { + set subdir [file tail [file dirname $pi]] + puts $index "set dir \[file join \$maindir [list $subdir]\] ;\t source \[file join \$dir pkgIndex.tcl\]" + } + + puts $index "unset maindir" + puts $index "" + close $index + return +} + +proc xcopyfile {src dest} { + # dest can be dir or file + run file copy -force $src $dest + return +} + +proc xcopy {src dest recurse {pattern *}} { + run file mkdir $dest + + if {[string equal $pattern *] || !$recurse} { + foreach file [glob -nocomplain [file join $src $pattern]] { + set base [file tail $file] + set sub [file join $dest $base] + + if {0 == [string compare CVS $base]} {continue} + + if {[file isdirectory $file]} then { + if {$recurse} { + run file mkdir $sub + xcopy $file $sub $recurse $pattern + + # If the directory is empty after the recursion remove it again. + if {![llength [glob -nocomplain [file join $sub *]]]} { + file delete $sub + } + } + } else { + xcopyfile $file $sub + } + } + } else { + foreach file [glob -nocomplain [file join $src *]] { + set base [file tail $file] + set sub [file join $dest $base] + + if {[string equal CVS $base]} {continue} + + if {[file isdirectory $file]} then { + if {$recurse} { + run file mkdir $sub + xcopy $file $sub $recurse $pattern + + # If the directory is empty after the recursion remove it again. + if {![llength [glob -nocomplain [file join $sub *]]]} { + run file delete $sub + } + } + } else { + if {![string match $pattern $base]} {continue} + xcopyfile $file $sub + } + } + } +} + +proc get_input {f} {return [read [set if [open $f r]]][close $if]} +proc write_out {f text} { + global config + if {$config(dry)} {log "Generate $f" ; return} + catch {file delete -force $f} + puts -nonewline [set of [open $f w]] $text + close $of +} + + +# -------------------------------------------------------------- +# Use configuration to perform installation + +proc clear {} {global message ; set message ""} +proc msg {text} {global message ; append message $text \n ; return} +proc get {} {global message ; return $message} + +proc log {text} { + global config + if {!$config(gui)} {puts stdout $text ; flush stdout ; return} + .l.t insert end $text\n + .l.t see end + update + return +} +proc log* {text} { + global config + if {!$config(gui)} {puts -nonewline stdout $text ; flush stdout ; return} + .l.t insert end $text + .l.t see end + update + return +} + +proc run {args} { + global config + if {$config(dry)} { + log [join $args] + return + } + if {[catch {eval $args} msg]} { + if {$config(gui)} { + installErrorMsgBox $msg + } else { + return -code error "Install error:\n $msg" + } + } + log* . + return +} + +proc xinstall {type args} { + global modules guide + foreach m $modules { + eval $guide($m,$type) $m $args + } + return +} + +proc ainstall {} { + global apps config tcl_platform distribution + + if {[string compare $tcl_platform(platform) windows] == 0} { + set ext .tcl + } else { + set ext "" + } + + foreach a $apps { + set aexe [file join $distribution apps $a] + set adst [file join $config(app,path) ${a}$ext] + + log "\nGenerating $adst" + if {!$config(dry)} { + file mkdir [file dirname $adst] + catch {file delete -force $adst} + file copy -force $aexe $adst + } + } + return +} + +proc doinstall {} { + global config package_version distribution package_name modules excluded + + if {!$config(no-exclude)} { + foreach p $excluded { + set pos [lsearch -exact $modules $p] + if {$pos < 0} {continue} + set modules [lreplace $modules $pos $pos] + } + } + + if {$config(doc,nroff)} { + set config(man.macros) [string trim [get_input \ + [file join $distribution support installation man.macros]]] + } + if {$config(pkg)} { + xinstall pkg $config(pkg,path) + gen_main_index $config(pkg,path) $package_name $package_version + } + if {$config(doc,nroff)} { + foreach dir [glob -directory $distribution/idoc/man/files/modules *] { + xcopy $dir $config(doc,nroff,path) 1 + } + xcopy $distribution/idoc/man/files/apps $config(doc,nroff,path) 1 + } + if {$config(doc,html)} { + #xinstall doc html html $config(doc,html,path) + xcopy $distribution/idoc/www $config(doc,html,path) 1 + } + if {$config(exa)} {xinstall exa $config(exa,path)} + if {$config(app)} {ainstall} + log "" + return +} + + +# -------------------------------------------------------------- +# Initialize configuration. + +array set config { + pkg 1 pkg,path {} + app 1 app,path {} + doc,nroff 0 doc,nroff,path {} + doc,html 0 doc,html,path {} + exa 1 exa,path {} + dry 0 wait 1 valid 1 + gui 0 no-gui 0 no-exclude 0 +} + +# -------------------------------------------------------------- +# Determine a default configuration, if possible + +proc defaults {} { + global tcl_platform config package_version package_name distribution + + if {[string compare $distribution [info nameofexecutable]] == 0} { + # Starpack. No defaults for location. + } else { + # Starkit, or unwrapped. Derive defaults location from the + # location of the executable running the installer, or the + # location of its library. + + # For a starkit [info library] is inside the running + # tclkit. Detect this and derive the lcoation from the + # location of the executable itself for that case. + + if {[string match [info nameofexecutable]* [info library]]} { + # Starkit + set libdir [file join [file dirname [file dirname [info nameofexecutable]]] lib] + } else { + # Unwrapped. + if {[catch {set libdir [lindex $::tcl_pkgPath end]}]} { + set libdir [file dirname [info library]] + } + } + + set basedir [file dirname $libdir] + set bindir [file join $basedir bin] + + if {[string compare $tcl_platform(platform) windows] == 0} { + set mandir {} + set htmldir [file join $basedir ${package_name}_doc] + } else { + set mandir [file join $basedir man mann] + set htmldir [file join $libdir ${package_name}${package_version} ${package_name}_doc] + } + + set config(app,path) $bindir + set config(pkg,path) [file join $libdir ${package_name}${package_version}] + set config(doc,nroff,path) $mandir + set config(doc,html,path) $htmldir + set config(exa,path) [file join $bindir ${package_name}_examples${package_version}] + } + + if {[string compare $tcl_platform(platform) windows] == 0} { + set config(doc,nroff) 0 + set config(doc,html) 1 + } else { + set config(doc,nroff) 1 + set config(doc,html) 0 + } + return +} + +# -------------------------------------------------------------- +# Show configuration on stdout. + +proc showpath {prefix key} { + global config + + if {$config($key)} { + if {[string length $config($key,path)] == 0} { + puts "${prefix}Empty path, invalid." + set config(valid) 0 + msg "Invalid path: [string trim $prefix " :"]" + } else { + puts "${prefix}$config($key,path)" + } + } else { + puts "${prefix}Not installed." + } +} + +proc showconfiguration {} { + global config package_version package_name_cap + + puts "Installing $package_name_cap $package_version" + if {$config(dry)} { + puts "\tDry run, simulation, no actual activity." + puts "" + } + + puts "You have chosen the following configuration ..." + puts "" + + showpath "Packages: " pkg + showpath "Applications: " app + showpath "Examples: " exa + + if {$config(doc,nroff) || $config(doc,html)} { + puts "Documentation:" + puts "" + + showpath "\tNROFF: " doc,nroff + showpath "\tHTML: " doc,html + } else { + puts "Documentation: Not installed." + } + puts "" + return +} + +# -------------------------------------------------------------- +# Setup the installer user interface + +proc browse {label key} { + global config + + set initial $config($key) + if {$initial == {}} {set initial [pwd]} + + set dir [tk_chooseDirectory \ + -title "Select directory for $label" \ + -parent . \ + -initialdir $initial \ + ] + + if {$dir == {}} {return} ; # Cancellation + + set config($key) $dir + return +} + +proc setupgui {} { + global config package_name_cap package_version + set config(gui) 1 + + wm withdraw . + wm title . "Installing $package_name_cap $package_version" + + foreach {w type cspan col row opts} { + .pkg checkbutton 1 0 0 {-anchor w -text {Packages:} -variable config(pkg)} + .app checkbutton 1 0 1 {-anchor w -text {Applications:} -variable config(app)} + .dnr checkbutton 1 0 2 {-anchor w -text {Doc. Nroff:} -variable config(doc,nroff)} + .dht checkbutton 1 0 3 {-anchor w -text {Doc. HTML:} -variable config(doc,html)} + .exa checkbutton 1 0 4 {-anchor w -text {Examples:} -variable config(exa)} + + .spa frame 3 0 5 {-bg black -height 2} + + .dry checkbutton 2 0 7 {-anchor w -text {Simulate installation} -variable config(dry)} + + .pkge entry 1 1 0 {-width 40 -textvariable config(pkg,path)} + .appe entry 1 1 1 {-width 40 -textvariable config(app,path)} + .dnre entry 1 1 2 {-width 40 -textvariable config(doc,nroff,path)} + .dhte entry 1 1 3 {-width 40 -textvariable config(doc,html,path)} + .exae entry 1 1 4 {-width 40 -textvariable config(exa,path)} + + .pkgb button 1 2 0 {-text ... -command {browse Packages pkg,path}} + .appb button 1 2 1 {-text ... -command {browse Applications app,path}} + .dnrb button 1 2 2 {-text ... -command {browse Nroff doc,nroff,path}} + .dhtb button 1 2 3 {-text ... -command {browse HTML doc,html,path}} + .exab button 1 2 4 {-text ... -command {browse Examples exa,path}} + + .sep frame 3 0 8 {-bg black -height 2} + + .run button 1 0 9 {-text {Install} -command {set ::run 1}} + .can button 1 1 9 {-text {Cancel} -command {exit}} + } { + eval [list $type $w] $opts + grid $w -column $col -row $row -sticky ew -columnspan $cspan + grid rowconfigure . $row -weight 0 + } + + grid .can -sticky e + + grid rowconfigure . 9 -weight 1 + grid columnconfigure . 0 -weight 0 + grid columnconfigure . 1 -weight 1 + + wm deiconify . + return +} + +proc handlegui {} { + setupgui + vwait ::run + showconfiguration + validate + + toplevel .l + wm title .l "Install log" + text .l.t -width 70 -height 25 -relief sunken -bd 2 + pack .l.t -expand 1 -fill both + + return +} + +# -------------------------------------------------------------- +# Handle a command line + +proc handlecmdline {} { + showconfiguration + validate + wait + return +} + +proc processargs {} { + global argv argv0 config + + while {[llength $argv] > 0} { + switch -exact -- [lindex $argv 0] { + +excluded {set config(no-exclude) 1} + -no-wait {set config(wait) 0} + -no-gui {set config(no-gui) 1} + -simulate - + -dry-run {set config(dry) 1} + -html {set config(doc,html) 1} + -nroff {set config(doc,nroff) 1} + -examples {set config(exa) 1} + -pkgs {set config(pkg) 1} + -apps {set config(app) 1} + -no-html {set config(doc,html) 0} + -no-nroff {set config(doc,nroff) 0} + -no-examples {set config(exa) 0} + -no-pkgs {set config(pkg) 0} + -no-apps {set config(app) 0} + -pkg-path { + set config(pkg) 1 + set config(pkg,path) [lindex $argv 1] + set argv [lrange $argv 1 end] + } + -app-path { + set config(app) 1 + set config(app,path) [lindex $argv 1] + set argv [lrange $argv 1 end] + } + -nroff-path { + set config(doc,nroff) 1 + set config(doc,nroff,path) [lindex $argv 1] + set argv [lrange $argv 1 end] + } + -html-path { + set config(doc,html) 1 + set config(doc,html,path) [lindex $argv 1] + set argv [lrange $argv 1 end] + } + -example-path { + set config(exa) 1 + set config(exa,path) [lindex $argv 1] + set argv [lrange $argv 1 end] + } + -help - + default { + puts stderr "usage: $argv0 ?-dry-run/-simulate? ?-no-wait? ?-no-gui? ?-html|-no-html? ?-nroff|-no-nroff? ?-examples|-no-examples? ?-pkgs|-no-pkgs? ?-pkg-path path? ?-apps|-no-apps? ?-app-path path? ?-nroff-path path? ?-html-path path? ?-example-path path?" + exit 1 + } + } + set argv [lrange $argv 1 end] + } + return +} + +proc validate {} { + global config + + if {$config(valid)} {return} + + puts "Invalid configuration detected, aborting." + puts "" + puts "Please use the option -help to get more information" + puts "" + + if {$config(gui)} { + tk_messageBox \ + -icon error -type ok \ + -default ok \ + -title "Illegal configuration" \ + -parent . -message [get] + clear + } + exit 1 +} + +proc installErrorMsgBox {msg} { + tk_messageBox \ + -icon error -type ok \ + -default ok \ + -title "Install error" \ + -parent . -message $msg + exit 1 +} + +proc wait {} { + global config + + if {!$config(wait)} {return} + + puts -nonewline stdout "Is the chosen configuration ok ? y/N: " + flush stdout + set answer [gets stdin] + if {($answer == {}) || [string match "\[Nn\]*" $answer]} { + puts stdout "\tNo. Aborting." + puts stdout "" + exit 0 + } + return +} + +# -------------------------------------------------------------- +# Main code + +proc main {} { + global config + + defaults + processargs + if {$config(no-gui) || [catch {package require Tk}]} { + handlecmdline + } else { + handlegui + } + doinstall + return +} + +# -------------------------------------------------------------- +if {[catch { + main +}]} { + puts $errorInfo +} +exit 0 +# -------------------------------------------------------------- |