diff options
author | Didier Raboud <odyx@debian.org> | 2019-11-20 20:30:03 +0100 |
---|---|---|
committer | Didier Raboud <odyx@debian.org> | 2019-11-20 20:30:03 +0100 |
commit | b0aeaea912817dd53fda33331a8f6e562b695a50 (patch) | |
tree | 4c3eca617f759c0bc9698cc35c599ef3113fa706 /autosetup/autosetup | |
parent | 68b3a623cde64e8dd53f8b1929a7cc9eabf0dadd (diff) | |
parent | 052cee686ea886c16b59dcabb5a04b2e6d390ade (diff) |
Update to upstream 0.79+dfsg0
[git-debrebase anchor: new upstream 0.79+dfsg0, merge]
Diffstat (limited to 'autosetup/autosetup')
-rwxr-xr-x | autosetup/autosetup | 1081 |
1 files changed, 802 insertions, 279 deletions
diff --git a/autosetup/autosetup b/autosetup/autosetup index 84886c2..da3a835 100755 --- a/autosetup/autosetup +++ b/autosetup/autosetup @@ -3,12 +3,13 @@ # All rights reserved # vim:se syntax=tcl: # \ -dir=`dirname "$0"`; exec "`$dir/find-tclsh`" "$0" "$@" +dir=`dirname "$0"`; exec "`$dir/autosetup-find-tclsh`" "$0" "$@" -set autosetup(version) 0.6.5 +# Note that the version has a trailing + on unreleased versions +set autosetup(version) 0.6.9 # Can be set to 1 to debug early-init problems -set autosetup(debug) 0 +set autosetup(debug) [expr {"--debug" in $argv}] ################################################################## # @@ -61,7 +62,7 @@ proc main {argv} { set autosetup(srcdir) [pwd] } else { # Invoked via the configure wrapper - set autosetup(srcdir) [file dirname $autosetup(exe)] + set autosetup(srcdir) [file-normalize [file dirname $autosetup(exe)]] } set autosetup(autodef) [relative-path $autosetup(srcdir)/auto.def] @@ -70,23 +71,47 @@ proc main {argv} { set autosetup(argv) $argv set autosetup(cmdline) {} + # options is a list of known options set autosetup(options) {} + # optset is a dictionary of option values set by the user based on getopt + set autosetup(optset) {} + # optdefault is a dictionary of default values + set autosetup(optdefault) {} + # options-defaults is a dictionary of overrides for default values for options + set autosetup(options-defaults) {} set autosetup(optionhelp) {} set autosetup(showhelp) 0 + use util + # Parse options use getopt - array set ::useropts [getopt argv] + # At the is point we don't know what is a valid option + # We simply parse anything that looks like an option + set autosetup(getopt) [getopt argv] #"=Core Options:" options-add { help:=local => "display help and options. Optionally specify a module name, such as --help=system" + licence license => "display the autosetup license" version => "display the version of autosetup" ref:=text manual:=text reference:=text => "display the autosetup command reference. 'text', 'wiki', 'asciidoc' or 'markdown'" debug => "display debugging output as autosetup runs" - install:=. => "install autosetup to the current or given directory (in the 'autosetup/' subdirectory)" + install:=. => "install autosetup to the current or given directory" + } + if {$autosetup(installed)} { + # hidden options so we can produce a nice error + options-add { + sysinstall:path + } + } else { + options-add { + sysinstall:path => "install standalone autosetup to the given directory (e.g.: /usr/local)" + } + } + options-add { force init:=help => "create initial auto.def, etc. Use --init=help for known types" # Undocumented options option-checking=1 @@ -96,15 +121,14 @@ proc main {argv} { conf: } - #parray ::useropts if {[opt-bool version]} { puts $autosetup(version) exit 0 } # autosetup --conf=alternate-auto.def - if {[opt-val conf] ne ""} { - set autosetup(autodef) [opt-val conf] + if {[opt-str conf o]} { + set autosetup(autodef) $o } # Debugging output (set this early) @@ -120,38 +144,47 @@ proc main {argv} { } # Now any auto-load modules - foreach file [glob -nocomplain $autosetup(libdir)/*.auto $autosetup(libdir)/*/*.auto] { - automf_load source $file - } + autosetup_load_auto_modules - if {[opt-val help] ne ""} { + if {[opt-str help o]} { incr autosetup(showhelp) use help - autosetup_help [opt-val help] + autosetup_help $o } - if {[opt-val {manual ref reference}] ne ""} { + if {[opt-bool licence license]} { use help - autosetup_reference [opt-val {manual ref reference}] + autosetup_show_license + exit 0 + } + + if {[opt-str {manual ref reference} o]} { + use help + autosetup_reference $o } # Allow combining --install and --init set earlyexit 0 - if {[opt-val install] ne ""} { + if {[opt-str install o]} { use install - autosetup_install [opt-val install] + autosetup_install $o incr earlyexit } - if {[opt-val init] ne ""} { + if {[opt-str init o]} { use init - autosetup_init [opt-val init] + autosetup_init $o incr earlyexit } if {$earlyexit} { exit 0 } + if {[opt-str sysinstall o]} { + use install + autosetup_install $o 1 + exit 0 + } if {![file exists $autosetup(autodef)]} { # Check for invalid option first @@ -181,6 +214,7 @@ proc main {argv} { # Log how we were invoked configlog "Invoked as: [getenv WRAPPER $::argv0] [quote-argv $autosetup(argv)]" + configlog "Tclsh: [info nameofexecutable]" # Note that auto.def is *not* loaded in the global scope source $autosetup(autodef) @@ -200,30 +234,134 @@ proc main {argv} { exit 0 } -# @opt-bool option ... +# @opt-bool ?-nodefault? option ... +# +# Check each of the named, boolean options and if any have been explicitly enabled +# or disabled by the user, return 1 or 0 accordingly. +# +# If the option was specified more than once, the last value wins. +# e.g. With '--enable-foo --disable-foo', '[opt-bool foo]' will return 0 # -# Check each of the named, boolean options and return 1 if any of them have -# been set by the user. +# If no value was specified by the user, returns the default value for the +# first option. If '-nodefault' is given, this behaviour changes and +# -1 is returned instead. # proc opt-bool {args} { + set nodefault 0 + if {[lindex $args 0] eq "-nodefault"} { + set nodefault 1 + set args [lrange $args 1 end] + } option-check-names {*}$args - opt_bool ::useropts {*}$args + + foreach opt $args { + if {[dict exists $::autosetup(optset) $opt]} { + return [dict get $::autosetup(optset) $opt] + } + } + + if {$nodefault} { + return -1 + } + # Default value is the default for the first option + return [dict get $::autosetup(optdefault) [lindex $args 0]] } -# @opt-val option-list ?default=""? +# @opt-val optionlist ?default=""? # -# Returns a list containing all the values given for the non-boolean options in 'option-list'. +# Returns a list containing all the values given for the non-boolean options in '$optionlist'. # There will be one entry in the list for each option given by the user, including if the # same option was used multiple times. -# If only a single value is required, use something like: # -## lindex [opt-val $names] end +# If no options were set, '$default' is returned (exactly, not as a list). # -# If no options were set, $default is returned (exactly, not as a list). +# Note: For most use cases, 'opt-str' should be preferred. # proc opt-val {names {default ""}} { option-check-names {*}$names - join [opt_val ::useropts $names $default] + + foreach opt $names { + if {[dict exists $::autosetup(optset) $opt]} { + lappend result {*}[dict get $::autosetup(optset) $opt] + } + } + if {[info exists result]} { + return $result + } + return $default +} + +# @opt-str optionlist varname ?default? +# +# Sets '$varname' in the callers scope to the value for one of the given options. +# +# For the list of options given in '$optionlist', if any value is set for any option, +# the option value is taken to be the *last* value of the last option (in the order given). +# +# If no option was given, and a default was specified with 'options-defaults', +# that value is used. +# +# If no 'options-defaults' value was given and '$default' was given, it is used. +# +# If none of the above provided a value, no value is set. +# +# The return value depends on whether '$default' was specified. +# If it was, the option value is returned. +# If it was not, 1 is returns if a value was set, or 0 if not. +# +# Typical usage is as follows: +# +## if {[opt-str {myopt altname} o]} { +## do something with $o +## } +# +# Or: +## define myname [opt-str {myopt altname} o "/usr/local"] +# +proc opt-str {names varname args} { + global autosetup + + option-check-names {*}$names + upvar $varname value + + if {[llength $args]} { + # A default was given, so always return the string value of the option + set default [lindex $args 0] + set retopt 1 + } else { + # No default, so return 0 or 1 to indicate if a value was found + set retopt 0 + } + + foreach opt $names { + if {[dict exists $::autosetup(optset) $opt]} { + set result [lindex [dict get $::autosetup(optset) $opt] end] + } + } + + if {![info exists result]} { + # No user-specified value. Has options-defaults been set? + foreach opt $names { + if {[dict exists $::autosetup(options-defaults) $opt]} { + set result [dict get $autosetup(options-defaults) $opt] + } + } + } + + if {[info exists result]} { + set value $result + if {$retopt} { + return $value + } + return 1 + } + + if {$retopt} { + set value $default + return $value + } + + return 0 } proc option-check-names {args} { @@ -235,10 +373,10 @@ proc option-check-names {args} { } # Parse the option definition in $opts and update -# ::useropts() and ::autosetup(optionhelp) appropriately +# ::autosetup(setoptions) and ::autosetup(optionhelp) appropriately # proc options-add {opts {header ""}} { - global useropts autosetup + global autosetup # First weed out comment lines set realopts {} @@ -257,6 +395,7 @@ proc options-add {opts {header ""}} { set header {} continue } + unset -nocomplain defaultvalue equal value #puts "i=$i, opt=$opt" regexp {^([^:=]*)(:)?(=)?(.*)$} $opt -> name colon equal value @@ -275,35 +414,101 @@ proc options-add {opts {header ""}} { # Boolean option lappend autosetup(options) $name - if {![info exists useropts($name)]} { - set useropts($name) $value + # Check for override + if {[dict exists $autosetup(options-defaults) $name]} { + # A default was specified with options-defaults, so use it + set value [dict get $autosetup(options-defaults) $name] } + if {$value eq "1"} { set opthelp "--disable-$name" } else { set opthelp "--$name" } + + # Set the default + if {$value eq ""} { + set value 0 + } + set defaultvalue $value + dict set autosetup(optdefault) $name $defaultvalue + + if {[dict exists $autosetup(getopt) $name]} { + # The option was specified by the user. Look at the last value. + lassign [lindex [dict get $autosetup(getopt) $name] end] type setvalue + if {$type eq "str"} { + # Can we convert the value to a boolean? + if {$setvalue in {1 enabled yes}} { + set setvalue 1 + } elseif {$setvalue in {0 disabled no}} { + set setvalue 0 + } else { + user-error "Boolean option $name given as --$name=$setvalue" + } + } + dict set autosetup(optset) $name $setvalue + #puts "Found boolean option --$name=$setvalue" + } } else { # String option. lappend autosetup(options) $name - if {$equal eq "="} { - if {[info exists useropts($name)]} { - # If the user specified the option with no value, the value will be "1" - # Replace with the default - if {$useropts($name) eq "1"} { - set useropts($name) $value - } + if {$colon eq ":"} { + # Was ":name=default" given? + # If so, set $value to the display name and $defaultvalue to the default + # (This is the preferred way to set a default value for a string option) + if {[regexp {^([^=]+)=(.*)$} $value -> value defaultvalue]} { + dict set autosetup(optdefault) $name $defaultvalue } + } + + # Maybe override the default value + if {[dict exists $autosetup(options-defaults) $name]} { + # A default was specified with options-defaults, so use it + set defaultvalue [dict get $autosetup(options-defaults) $name] + dict set autosetup(optdefault) $name $defaultvalue + } elseif {![info exists defaultvalue]} { + # For backward compatibility, if ":name" was given, use name as both + # the display text and the default value, but only if the user + # specified the option without the value + set defaultvalue $value + } + + if {$equal eq "="} { + # String option with optional value set opthelp "--$name?=$value?" } else { + # String option with required value set opthelp "--$name=$value" } + + # Get the values specified by the user + if {[dict exists $autosetup(getopt) $name]} { + set listvalue {} + + foreach pair [dict get $autosetup(getopt) $name] { + lassign $pair type setvalue + if {$type eq "bool" && $setvalue} { + if {$equal ne "="} { + user-error "Option --$name requires a value" + } + # If given as a boolean, use the default value + set setvalue $defaultvalue + } + lappend listvalue $setvalue + } + + #puts "Found string option --$name=$listvalue" + dict set autosetup(optset) $name $listvalue + } } # Now create the help for this option if appropriate if {[lindex $opts $i+1] eq "=>"} { set desc [lindex $opts $i+2] + if {[info exists defaultvalue]} { + set desc [string map [list @default@ $defaultvalue] $desc] + } #string match \n* $desc if {$header ne ""} { lappend autosetup(optionhelp) $header "" @@ -391,31 +596,35 @@ proc options-show {} { } } -# @options options-spec +# @options optionspec # # Specifies configuration-time options which may be selected by the user -# and checked with opt-val and opt-bool. The format of options-spec follows. +# and checked with 'opt-str' and 'opt-bool'. '$optionspec' contains a series +# of options specifications separated by newlines, as follows: # # A boolean option is of the form: # ## name[=0|1] => "Description of this boolean option" # -# The default is name=0, meaning that the option is disabled by default. -# If name=1 is used to make the option enabled by default, the description should reflect +# The default is 'name=0', meaning that the option is disabled by default. +# If 'name=1' is used to make the option enabled by default, the description should reflect # that with text like "Disable support for ...". # # An argument option (one which takes a parameter) is of the form: # ## name:[=]value => "Description of this option" # -# If the name:value form is used, the value must be provided with the option (as --name=myvalue). -# If the name:=value form is used, the value is optional and the given value is used as the default +# If the 'name:value' form is used, the value must be provided with the option (as '--name=myvalue'). +# If the 'name:=value' form is used, the value is optional and the given value is used as the default # if it is not provided. # -# Undocumented options are also supported by omitting the "=> description. -# These options are not displayed with --help and can be useful for internal options or as aliases. +# The description may contain '@default@', in which case it will be replaced with the default +# value for the option (taking into account defaults specified with 'options-defaults'. +# +# Undocumented options are also supported by omitting the '=> description'. +# These options are not displayed with '--help' and can be useful for internal options or as aliases. # -# For example, --disable-lfs is an alias for --disable=largefile: +# For example, '--disable-lfs' is an alias for '--disable=largefile': # ## lfs=1 largefile=1 => "Disable large file support" # @@ -430,7 +639,7 @@ proc options {optlist} { # Check for invalid options if {[opt-bool option-checking]} { - foreach o [array names ::useropts] { + foreach o [dict keys $::autosetup(getopt)] { if {$o ni $::autosetup(options)} { user-error "Unknown option --$o" } @@ -438,22 +647,32 @@ proc options {optlist} { } } +# @options-defaults dictionary +# +# Specifies a dictionary of options and a new default value for each of those options. +# Use before any 'use' statements in 'auto.def' to change the defaults for +# subsequently included modules. +proc options-defaults {dict} { + foreach {n v} $dict { + dict set ::autosetup(options-defaults) $n $v + } +} + proc config_guess {} { - if {[file-isexec $::autosetup(dir)/config.guess]} { - exec-with-stderr sh $::autosetup(dir)/config.guess - if {[catch {exec-with-stderr sh $::autosetup(dir)/config.guess} alias]} { + if {[file-isexec $::autosetup(dir)/autosetup-config.guess]} { + if {[catch {exec-with-stderr sh $::autosetup(dir)/autosetup-config.guess} alias]} { user-error $alias } return $alias } else { - configlog "No config.guess, so using uname" + configlog "No autosetup-config.guess, so using uname" string tolower [exec uname -p]-unknown-[exec uname -s][exec uname -r] } } proc config_sub {alias} { - if {[file-isexec $::autosetup(dir)/config.sub]} { - if {[catch {exec-with-stderr sh $::autosetup(dir)/config.sub $alias} alias]} { + if {[file-isexec $::autosetup(dir)/autosetup-config.sub]} { + if {[catch {exec-with-stderr sh $::autosetup(dir)/autosetup-config.sub $alias} alias]} { user-error $alias } } @@ -464,7 +683,7 @@ proc config_sub {alias} { # # Defines the named variable to the given value. # These (name, value) pairs represent the results of the configuration check -# and are available to be checked, modified and substituted. +# and are available to be subsequently checked, modified and substituted. # proc define {name {value 1}} { set ::define($name) $value @@ -473,7 +692,7 @@ proc define {name {value 1}} { # @undefine name # -# Undefine the named variable +# Undefine the named variable. # proc undefine {name} { unset -nocomplain ::define($name) @@ -482,17 +701,26 @@ proc undefine {name} { # @define-append name value ... # -# Appends the given value(s) to the given 'defined' variable. -# If the variable is not defined or empty, it is set to $value. +# Appends the given value(s) to the given "defined" variable. +# If the variable is not defined or empty, it is set to '$value'. # Otherwise the value is appended, separated by a space. # Any extra values are similarly appended. # If any value is already contained in the variable (as a substring) it is omitted. # proc define-append {name args} { if {[get-define $name ""] ne ""} { - # Make a token attempt to avoid duplicates + # Avoid duplicates foreach arg $args { - if {[string first $arg $::define($name)] == -1} { + if {$arg eq ""} { + continue + } + set found 0 + foreach str [split $::define($name) " "] { + if {$str eq $arg} { + incr found + } + } + if {!$found} { append ::define($name) " " $arg } } @@ -504,7 +732,7 @@ proc define-append {name args} { # @get-define name ?default=0? # -# Returns the current value of the 'defined' variable, or $default +# Returns the current value of the "defined" variable, or '$default' # if not set. # proc get-define {name {default 0}} { @@ -524,9 +752,21 @@ proc is-defined {name} { info exists ::define($name) } +# @is-define-set name +# +# Returns 1 if the given variable is defined and is set +# to a value other than "" or 0 +# +proc is-define-set {name} { + if {[get-define $name] in {0 ""}} { + return 0 + } + return 1 +} + # @all-defines # -# Returns a dictionary (name value list) of all defined variables. +# Returns a dictionary (name, value list) of all defined variables. # # This is suitable for use with 'dict', 'array set' or 'foreach' # and allows for arbitrary processing of the defined variables. @@ -538,9 +778,9 @@ proc all-defines {} { # @get-env name default # -# If $name was specified on the command line, return it. -# If $name was set in the environment, return it. -# Otherwise return $default. +# If '$name' was specified on the command line, return it. +# Otherwise if '$name' was set in the environment, return it. +# Otherwise return '$default'. # proc get-env {name default} { if {[dict exists $::autosetup(cmdline) $name]} { @@ -551,7 +791,7 @@ proc get-env {name default} { # @env-is-set name # -# Returns 1 if the $name was specified on the command line or in the environment. +# Returns 1 if '$name' was specified on the command line or in the environment. # Note that an empty environment variable is not considered to be set. # proc env-is-set {name} { @@ -567,7 +807,7 @@ proc env-is-set {name} { # @readfile filename ?default=""? # # Return the contents of the file, without the trailing newline. -# If the file doesn't exist or can't be read, returns $default. +# If the file doesn't exist or can't be read, returns '$default'. # proc readfile {filename {default_value ""}} { set result $default_value @@ -581,7 +821,7 @@ proc readfile {filename {default_value ""}} { # @writefile filename value # -# Creates the given file containing $value. +# Creates the given file containing '$value'. # Does not add an extra newline. # proc writefile {filename value} { @@ -605,59 +845,56 @@ proc quote-argv {argv} { join $args } -# @suffix suf list +# @list-non-empty list # -# Takes a list and returns a new list with $suf appended -# to each element -# -## suffix .c {a b c} => {a.c b.c c.c} -# -proc suffix {suf list} { +# Returns a copy of the given list with empty elements removed +proc list-non-empty {list} { set result {} foreach p $list { - lappend result $p$suf + if {$p ne ""} { + lappend result $p + } } return $result } -# @prefix pre list +# @find-executable-path name # -# Takes a list and returns a new list with $pre prepended -# to each element -# -## prefix jim- {a.c b.c} => {jim-a.c jim-b.c} +# Searches the path for an executable with the given name. +# Note that the name may include some parameters, e.g. 'cc -mbig-endian', +# in which case the parameters are ignored. +# The full path to the executable if found, or "" if not found. +# Returns 1 if found, or 0 if not. # -proc prefix {pre list} { - set result {} - foreach p $list { - lappend result $pre$p +proc find-executable-path {name} { + # Ignore any parameters + set name [lindex $name 0] + # The empty string is never a valid executable + if {$name ne ""} { + foreach p [split-path] { + dputs "Looking for $name in $p" + set exec [file join $p $name] + if {[file-isexec $exec]} { + dputs "Found $name -> $exec" + return $exec + } + } } - return $result + return {} } # @find-executable name # # Searches the path for an executable with the given name. -# Note that the name may include some parameters, e.g. "cc -mbig-endian", +# Note that the name may include some parameters, e.g. 'cc -mbig-endian', # in which case the parameters are ignored. # Returns 1 if found, or 0 if not. # proc find-executable {name} { - # Ignore any parameters - set name [lindex $name 0] - if {$name eq ""} { - # The empty string is never a valid executable + if {[find-executable-path $name] eq {}} { return 0 } - foreach p [split-path] { - dputs "Looking for $name in $p" - set exec [file join $p $name] - if {[file-isexec $exec]} { - dputs "Found $name -> $exec" - return 1 - } - } - return 0 + return 1 } # @find-an-executable ?-required? name ... @@ -692,7 +929,7 @@ proc find-an-executable {args} { # @configlog msg # -# Writes the given message to the configuration log, config.log +# Writes the given message to the configuration log, 'config.log'. # proc configlog {msg} { if {![info exists ::autosetup(logfh)]} { @@ -728,8 +965,8 @@ proc msg-result {msg} { # @msg-quiet command ... # -# msg-quiet evaluates it's arguments as a command with output -# from msg-checking and msg-result suppressed. +# 'msg-quiet' evaluates it's arguments as a command with output +# from 'msg-checking' and 'msg-result' suppressed. # # This is useful if a check needs to run a subcheck which isn't # of interest to the user. @@ -769,7 +1006,7 @@ proc dputs {msg} { # # Indicate incorrect usage to the user, including if required components # or features are not found. -# autosetup exits with a non-zero return code. +# 'autosetup' exits with a non-zero return code. # proc user-error {msg} { show-notices @@ -816,6 +1053,17 @@ proc maybe-show-timestamp {} { } } +# @autosetup-require-version required +# +# Checks the current version of 'autosetup' against '$required'. +# A fatal error is generated if the current version is less than that required. +# +proc autosetup-require-version {required} { + if {[compare-versions $::autosetup(version) $required] < 0} { + user-error "autosetup version $required is required, but this is $::autosetup(version)" + } +} + proc autosetup_version {} { return "autosetup v$::autosetup(version)" } @@ -914,19 +1162,31 @@ proc autosetup_add_dep {filename} { # when it is loaded. # proc use {args} { + global autosetup libmodule modsource + + set dirs [list $autosetup(libdir)] + if {[info exists autosetup(srcdir)]} { + lappend dirs $autosetup(srcdir)/autosetup + } foreach m $args { - if {[info exists ::libmodule($m)]} { + if {[info exists libmodule($m)]} { continue } - set ::libmodule($m) 1 - if {[info exists ::modsource($m)]} { - automf_load eval $::modsource($m) + set libmodule($m) 1 + if {[info exists modsource(${m}.tcl)]} { + automf_load eval $modsource(${m}.tcl) } else { - set sources [list $::autosetup(libdir)/${m}.tcl $::autosetup(libdir)/${m}/init.tcl] + set locs [list ${m}.tcl ${m}/init.tcl] set found 0 - foreach source $sources { - if {[file exists $source]} { - incr found + foreach dir $dirs { + foreach loc $locs { + set source $dir/$loc + if {[file exists $source]} { + incr found + break + } + } + if {$found} { break } } @@ -943,6 +1203,18 @@ proc use {args} { } } +proc autosetup_load_auto_modules {} { + global autosetup modsource + # First load any embedded auto modules + foreach mod [array names modsource *.auto] { + automf_load eval $modsource($mod) + } + # Now any external auto modules + foreach file [glob -nocomplain $autosetup(libdir)/*.auto $autosetup(libdir)/*/*.auto] { + automf_load source $file + } +} + # Load module source in the global scope by executing the given command proc automf_load {args} { if {[catch [list uplevel #0 $args] msg opts] ni {0 2 3}} { @@ -955,14 +1227,17 @@ set autosetup(exe) $::argv0 set autosetup(istcl) 1 set autosetup(start) [clock millis] set autosetup(installed) 0 +set autosetup(sysinstall) 0 set autosetup(msg-checking) 0 set autosetup(msg-quiet) 0 +set autosetup(inittypes) {} # Embedded modules are inserted below here set autosetup(installed) 1 -# ----- module asciidoc-formatting ----- +set autosetup(sysinstall) 0 +# ----- @module asciidoc-formatting.tcl ----- -set modsource(asciidoc-formatting) { +set modsource(asciidoc-formatting.tcl) { # Copyright (c) 2010 WorkWare Systems http://www.workware.net.au/ # All rights reserved @@ -1030,15 +1305,15 @@ proc defn {first args} { } } -# ----- module formatting ----- +# ----- @module formatting.tcl ----- -set modsource(formatting) { +set modsource(formatting.tcl) { # Copyright (c) 2010 WorkWare Systems http://www.workware.net.au/ # All rights reserved # Module which provides common text formatting -# This is designed for documenation which looks like: +# This is designed for documentation which looks like: # code {...} # or # code { @@ -1087,21 +1362,28 @@ proc parse_code_block {text} { } } -# ----- module getopt ----- +# ----- @module getopt.tcl ----- -set modsource(getopt) { +set modsource(getopt.tcl) { # Copyright (c) 2006 WorkWare Systems http://www.workware.net.au/ # All rights reserved # Simple getopt module # Parse everything out of the argv list which looks like an option -# Knows about --enable-thing and --disable-thing as alternatives for --thing=0 or --thing=1 # Everything which doesn't look like an option, or is after --, is left unchanged +# Understands --enable-xxx as a synonym for --xxx to enable the boolean option xxx. +# Understands --disable-xxx to disable the boolean option xxx. +# +# The returned value is a dictionary keyed by option name +# Each value is a list of {type value} ... where type is "bool" or "str". +# The value for a boolean option is 0 or 1. The value of a string option is the value given. proc getopt {argvname} { upvar $argvname argv set nargv {} + set opts {} + for {set i 0} {$i < [llength $argv]} {incr i} { set arg [lindex $argv $i] @@ -1115,65 +1397,33 @@ proc getopt {argvname} { } if {[regexp {^--([^=][^=]+)=(.*)$} $arg -> name value]} { - lappend opts($name) $value + # --name=value + dict lappend opts $name [list str $value] } elseif {[regexp {^--(enable-|disable-)?([^=]*)$} $arg -> prefix name]} { - if {$prefix eq "disable-"} { - set value 0 - } else { + if {$prefix in {enable- ""}} { set value 1 + } else { + set value 0 } - lappend opts($name) $value + dict lappend opts $name [list bool $value] } else { lappend nargv $arg } } #puts "getopt: argv=[join $argv] => [join $nargv]" - #parray opts + #array set getopt $opts + #parray getopt set argv $nargv - return [array get opts] + return $opts } - -proc opt_val {optarrayname options {default {}}} { - upvar $optarrayname opts - - set result {} - - foreach o $options { - if {[info exists opts($o)]} { - lappend result {*}$opts($o) - } - } - if {[llength $result] == 0} { - return $default - } - return $result } -proc opt_bool {optarrayname args} { - upvar $optarrayname opts - - # Support the args being passed as a list - if {[llength $args] == 1} { - set args [lindex $args 0] - } +# ----- @module help.tcl ----- - foreach o $args { - if {[info exists opts($o)]} { - if {"1" in $opts($o) || "yes" in $opts($o)} { - return 1 - } - } - } - return 0 -} -} - -# ----- module help ----- - -set modsource(help) { +set modsource(help.tcl) { # Copyright (c) 2010 WorkWare Systems http://workware.net.au/ # All rights reserved @@ -1205,6 +1455,24 @@ proc autosetup_help {what} { exit 0 } +proc autosetup_show_license {} { + global modsource autosetup + use_pager + + if {[info exists modsource(LICENSE)]} { + puts $modsource(LICENSE) + return + } + foreach dir [list $autosetup(libdir) $autosetup(srcdir)] { + set path [file join $dir LICENSE] + if {[file exists $path]} { + puts [readfile $path] + return + } + } + puts "LICENSE not found" +} + # If not already paged and stdout is a tty, pipe the output through the pager # This is done by reinvoking autosetup with --nopager added proc use_pager {} { @@ -1257,6 +1525,12 @@ proc autosetup_reference {{type text}} { proc autosetup_output_block {type lines} { if {[llength $lines]} { switch $type { + section { + section $lines + } + subsection { + subsection $lines + } code { codelines $lines } @@ -1278,16 +1552,30 @@ proc automf_command_reference {} { lappend files $::autosetup(prog) lappend files {*}[lsort [glob -nocomplain $::autosetup(libdir)/*.tcl]] - section "Core Commands" - set type p - set lines {} - set cmd {} + # We want to process all non-module files before module files + # and then modules in alphabetical order. + # So examine all files and extract docs into doc($modulename) and doc(_core_) + # + # Each entry is a list of {type data} where $type is one of: section, subsection, code, list, p + # and $data is a string for section, subsection or a list of text lines for other types. + + # XXX: Should commands be in alphabetical order too? Currently they are in file order. + + set doc(_core_) {} + lappend doc(_core_) [list section "Core Commands"] foreach file $files { + set modulename [file rootname [file tail $file]] + set current _core_ set f [open $file] while {![eof $f]} { set line [gets $f] + # Find embedded module names + if {[regexp {^#.*@module ([^ ]*)} $line -> modulename]} { + continue + } + # Find lines starting with "# @*" and continuing through the remaining comment lines if {![regexp {^# @(.*)} $line -> cmd]} { continue @@ -1295,9 +1583,10 @@ proc automf_command_reference {} { # Synopsis or command? if {$cmd eq "synopsis:"} { - section "Module: [file rootname [file tail $file]]" + set current $modulename + lappend doc($current) [list section "Module: $modulename"] } else { - subsection $cmd + lappend doc($current) [list subsection $cmd] } set lines {} @@ -1322,7 +1611,7 @@ proc automf_command_reference {} { if {$t ne $type || $cmd eq ""} { # Finish the current block - autosetup_output_block $type $lines + lappend doc($current) [list $type $lines] set lines {} set type $t } @@ -1331,16 +1620,25 @@ proc automf_command_reference {} { } } - autosetup_output_block $type $lines + lappend doc($current) [list $type $lines] } close $f } + + # Now format and output the results + + # _core_ will sort first + foreach module [lsort [array names doc]] { + foreach item $doc($module) { + autosetup_output_block {*}$item + } + } } } -# ----- module init ----- +# ----- @module init.tcl ----- -set modsource(init) { +set modsource(init.tcl) { # Copyright (c) 2010 WorkWare Systems http://www.workware.net.au/ # All rights reserved @@ -1397,25 +1695,63 @@ proc autosetup_check_create {filename contents} { } } -# ----- module install ----- +# ----- @module install.tcl ----- -set modsource(install) { +set modsource(install.tcl) { # Copyright (c) 2006-2010 WorkWare Systems http://www.workware.net.au/ # All rights reserved # Module which can install autosetup -proc autosetup_install {dir} { - if {[catch { +# autosetup(installed)=1 means that autosetup is not running from source +# autosetup(sysinstall)=1 means that autosetup is running from a sysinstall version +# shared=1 means that we are trying to do a sysinstall. This is only possible from the development source. + +proc autosetup_install {dir {shared 0}} { + global autosetup + if {$shared} { + if {$autosetup(installed) || $autosetup(sysinstall)} { + user-error "Can only --sysinstall from development sources" + } + } elseif {$autosetup(installed) && !$autosetup(sysinstall)} { + user-error "Can't --install from project install" + } + + if {$autosetup(sysinstall)} { + # This is the sysinstall version, so install just uses references cd $dir + + puts "[autosetup_version] creating configure to use system-installed autosetup" + autosetup_create_configure 1 + puts "Creating autosetup/README.autosetup" file mkdir autosetup + autosetup_install_readme autosetup/README.autosetup 1 + return + } - set f [open autosetup/autosetup w] + if {[catch { + if {$shared} { + set target $dir/bin/autosetup + set installedas $target + } else { + if {$dir eq "."} { + set installedas autosetup + } else { + set installedas $dir/autosetup + } + cd $dir + file mkdir autosetup + set target autosetup/autosetup + } + set targetdir [file dirname $target] + file mkdir $targetdir + + set f [open $target w] - set publicmodules [glob $::autosetup(libdir)/*.auto] + set publicmodules {} # First the main script, but only up until "CUT HERE" - set in [open $::autosetup(dir)/autosetup] + set in [open $autosetup(dir)/autosetup] while {[gets $in buf] >= 0} { if {$buf ne "##-- CUT HERE --##"} { puts $f $buf @@ -1424,48 +1760,88 @@ proc autosetup_install {dir} { # Insert the static modules here # i.e. those which don't contain @synopsis: + # All modules are inserted if $shared is set puts $f "set autosetup(installed) 1" - foreach file [lsort [glob $::autosetup(libdir)/*.tcl]] { + puts $f "set autosetup(sysinstall) $shared" + foreach file [lsort [glob $autosetup(libdir)/*.{tcl,auto}]] { + set modname [file tail $file] + set ext [file ext $modname] set buf [readfile $file] - if {[string match "*\n# @synopsis:*" $buf]} { - lappend publicmodules $file - continue + if {!$shared} { + if {$ext eq ".auto" || [string match "*\n# @synopsis:*" $buf]} { + lappend publicmodules $file + continue + } } - set modname [file rootname [file tail $file]] - puts $f "# ----- module $modname -----" + dputs "install: importing lib/[file tail $file]" + puts $f "# ----- @module $modname -----" puts $f "\nset modsource($modname) \{" puts $f $buf puts $f "\}\n" } + if {$shared} { + foreach {srcname destname} [list $autosetup(libdir)/README.autosetup-lib README.autosetup \ + $autosetup(srcdir)/LICENSE LICENSE] { + dputs "install: importing $srcname as $destname" + puts $f "\nset modsource($destname) \\\n[list [readfile $srcname]\n]\n" + } + } } close $in close $f - exec chmod 755 autosetup/autosetup + catch {exec chmod 755 $target} + + set installfiles {autosetup-config.guess autosetup-config.sub autosetup-test-tclsh} + set removefiles {} + + if {!$shared} { + autosetup_install_readme $targetdir/README.autosetup 0 - # Install public modules - foreach file $publicmodules { - autosetup_install_file $file autosetup + # Install public modules + foreach file $publicmodules { + set tail [file tail $file] + autosetup_install_file $file $targetdir/$tail + } + lappend installfiles jimsh0.c autosetup-find-tclsh LICENSE + lappend removefiles config.guess config.sub test-tclsh find-tclsh + } else { + lappend installfiles {sys-find-tclsh autosetup-find-tclsh} } # Install support files - foreach file {config.guess config.sub jimsh0.c find-tclsh test-tclsh LICENSE} { - autosetup_install_file $::autosetup(dir)/$file autosetup + foreach fileinfo $installfiles { + if {[llength $fileinfo] == 2} { + lassign $fileinfo source dest + } else { + lassign $fileinfo source + set dest $source + } + autosetup_install_file $autosetup(dir)/$source $targetdir/$dest } - exec chmod 755 autosetup/config.sub autosetup/config.guess autosetup/find-tclsh - - writefile autosetup/README.autosetup \ - "This is [autosetup_version]. See http://msteveb.github.com/autosetup/\n" + # Remove obsolete files + foreach file $removefiles { + if {[file exists $targetdir/$file]} { + file delete $targetdir/$file + } + } } error]} { user-error "Failed to install autosetup: $error" } - puts "Installed [autosetup_version] to autosetup/" + if {$shared} { + set type "system" + } else { + set type "local" + } + puts "Installed $type [autosetup_version] to $installedas" - # Now create 'configure' if necessary - autosetup_create_configure + if {!$shared} { + # Now create 'configure' if necessary + autosetup_create_configure 0 + } } -proc autosetup_create_configure {} { +proc autosetup_create_configure {shared} { if {[file exists configure]} { if {!$::autosetup(force)} { # Could this be an autosetup configure? @@ -1480,36 +1856,71 @@ proc autosetup_create_configure {} { } else { puts "I don't see configure, so I will create it." } - writefile configure \ + if {$shared} { + writefile configure \ +{#!/bin/sh +WRAPPER="$0"; export WRAPPER; "autosetup" "$@" +} + } else { + writefile configure \ {#!/bin/sh dir="`dirname "$0"`/autosetup" -WRAPPER="$0"; export WRAPPER; exec "`$dir/find-tclsh`" "$dir/autosetup" "$@" +WRAPPER="$0"; export WRAPPER; exec "`"$dir/autosetup-find-tclsh"`" "$dir/autosetup" "$@" } + } catch {exec chmod 755 configure} } # Append the contents of $file to filehandle $f proc autosetup_install_append {f file} { + dputs "install: include $file" set in [open $file] puts $f [read $in] close $in } -proc autosetup_install_file {file dir} { - if {![file exists $file]} { - error "Missing installation file '$file'" +proc autosetup_install_file {source target} { + dputs "install: $source => $target" + if {![file exists $source]} { + error "Missing installation file '$source'" } - writefile [file join $dir [file tail $file]] [readfile $file]\n + writefile $target [readfile $source]\n + # If possible, copy the file mode + file stat $source stat + set mode [format %o [expr {$stat(mode) & 0x1ff}]] + catch {exec chmod $mode $target} +} + +proc autosetup_install_readme {target sysinstall} { + set readme "README.autosetup created by [autosetup_version]\n\n" + if {$sysinstall} { + append readme \ +{This is the autosetup directory for a system install of autosetup. +Loadable modules can be added here. +} + } else { + append readme \ +{This is the autosetup directory for a local install of autosetup. +It contains autosetup, support files and loadable modules. } +} + + append readme { +*.tcl files in this directory are optional modules which +can be loaded with the 'use' directive. + +*.auto files in this directory are auto-loaded. -if {$::autosetup(installed)} { - user-error "autosetup can only be installed from development source, not from installed copy" +For more information, see http://msteveb.github.com/autosetup/ +} + dputs "install: autosetup/README.autosetup" + writefile $target $readme } } -# ----- module markdown-formatting ----- +# ----- @module markdown-formatting.tcl ----- -set modsource(markdown-formatting) { +set modsource(markdown-formatting.tcl) { # Copyright (c) 2010 WorkWare Systems http://www.workware.net.au/ # All rights reserved @@ -1580,9 +1991,9 @@ proc defn {first args} { } } -# ----- module misc ----- +# ----- @module misc.tcl ----- -set modsource(misc) { +set modsource(misc.tcl) { # Copyright (c) 2007-2010 WorkWare Systems http://www.workware.net.au/ # All rights reserved @@ -1758,9 +2169,9 @@ proc error-dump {msg opts fulltrace} { } } -# ----- module text-formatting ----- +# ----- @module text-formatting.tcl ----- -set modsource(text-formatting) { +set modsource(text-formatting.tcl) { # Copyright (c) 2010 WorkWare Systems http://www.workware.net.au/ # All rights reserved @@ -1769,91 +2180,203 @@ set modsource(text-formatting) { use formatting proc wordwrap {text length {firstprefix ""} {nextprefix ""}} { - set len 0 - set space $firstprefix - foreach word [split $text] { - set word [string trim $word] - if {$word == ""} { - continue - } - if {$len && [string length $space$word] + $len >= $length} { - puts "" - set len 0 - set space $nextprefix - } - incr len [string length $space$word] - - # Use man-page conventions for highlighting 'quoted' and *quoted* - # single words. - # Use x^Hx for *bold* and _^Hx for 'underline'. - # - # less and more will both understand this. - # Pipe through 'col -b' to remove them. - if {[regexp {^'(.*)'([^a-zA-Z0-9_]*)$} $word -> bareword dot]} { - regsub -all . $bareword "_\b&" word - append word $dot - } elseif {[regexp {^[*](.*)[*]([^a-zA-Z0-9_]*)$} $word -> bareword dot]} { - regsub -all . $bareword "&\b&" word - append word $dot - } - puts -nonewline $space$word - set space " " - } - if {$len} { - puts "" - } + set len 0 + set space $firstprefix + + foreach word [split $text] { + set word [string trim $word] + if {$word eq ""} { + continue + } + if {[info exists partial]} { + append partial " " $word + if {[string first $quote $word] < 0} { + # Haven't found end of quoted word + continue + } + # Finished quoted word + set word $partial + unset partial + unset quote + } else { + set quote [string index $word 0] + if {$quote in {' *}} { + if {[string first $quote $word 1] < 0} { + # Haven't found end of quoted word + # Not a whole word. + set first [string index $word 0] + # Start of quoted word + set partial $word + continue + } + } + } + + if {$len && [string length $space$word] + $len >= $length} { + puts "" + set len 0 + set space $nextprefix + } + incr len [string length $space$word] + + # Use man-page conventions for highlighting 'quoted' and *quoted* + # single words. + # Use x^Hx for *bold* and _^Hx for 'underline'. + # + # less and more will both understand this. + # Pipe through 'col -b' to remove them. + if {[regexp {^'(.*)'(.*)} $word -> quoted after]} { + set quoted [string map {~ " "} $quoted] + regsub -all . $quoted "&\b&" quoted + set word $quoted$after + } elseif {[regexp {^[*](.*)[*](.*)} $word -> quoted after]} { + set quoted [string map {~ " "} $quoted] + regsub -all . $quoted "_\b&" quoted + set word $quoted$after + } + puts -nonewline $space$word + set space " " + } + if {[info exists partial]} { + # Missing end of quote + puts -nonewline $space$partial + } + if {$len} { + puts "" + } } proc title {text} { - underline [string trim $text] = - nl + underline [string trim $text] = + nl } proc p {text} { - wordwrap $text 80 - nl + wordwrap $text 80 + nl } proc codelines {lines} { - foreach line $lines { - puts " $line" - } - nl + foreach line $lines { + puts " $line" + } + nl } proc nl {} { - puts "" + puts "" } proc underline {text char} { - regexp "^(\[ \t\]*)(.*)" $text -> indent words - puts $text - puts $indent[string repeat $char [string length $words]] + regexp "^(\[ \t\]*)(.*)" $text -> indent words + puts $text + puts $indent[string repeat $char [string length $words]] } proc section {text} { - underline "[string trim $text]" - - nl + underline "[string trim $text]" - + nl } proc subsection {text} { - underline "$text" ~ - nl + underline "$text" ~ + nl } proc bullet {text} { - wordwrap $text 76 " * " " " + wordwrap $text 76 " * " " " } proc indent {text} { - wordwrap $text 76 " " " " + wordwrap $text 76 " " " " } proc defn {first args} { - if {$first ne ""} { - underline " $first" ~ - } - foreach p $args { - if {$p ne ""} { - indent $p - } - } + if {$first ne ""} { + underline " $first" ~ + } + foreach p $args { + if {$p ne ""} { + indent $p + } + } +} +} + +# ----- @module util.tcl ----- + +set modsource(util.tcl) { +# Copyright (c) 2012 WorkWare Systems http://www.workware.net.au/ +# All rights reserved + +# Module which contains miscellaneous utility functions + +# @compare-versions version1 version2 +# +# Versions are of the form 'a.b.c' (may be any number of numeric components) +# +# Compares the two versions and returns: +## -1 if v1 < v2 +## 0 if v1 == v2 +## 1 if v1 > v2 +# +# If one version has fewer components than the other, 0 is substituted to the right. e.g. +## 0.2 < 0.3 +## 0.2.5 > 0.2 +## 1.1 == 1.1.0 +# +proc compare-versions {v1 v2} { + foreach c1 [split $v1 .] c2 [split $v2 .] { + if {$c1 eq ""} { + set c1 0 + } + if {$c2 eq ""} { + set c2 0 + } + if {$c1 < $c2} { + return -1 + } + if {$c1 > $c2} { + return 1 + } + } + return 0 +} + +# @suffix suf list +# +# Takes a list and returns a new list with '$suf' appended +# to each element +# +## suffix .c {a b c} => {a.c b.c c.c} +# +proc suffix {suf list} { + set result {} + foreach p $list { + lappend result $p$suf + } + return $result +} + +# @prefix pre list +# +# Takes a list and returns a new list with '$pre' prepended +# to each element +# +## prefix jim- {a.c b.c} => {jim-a.c jim-b.c} +# +proc prefix {pre list} { + set result {} + foreach p $list { + lappend result $pre$p + } + return $result +} + +# @lpop list +# +# Removes the last entry from the given list and returns it. +proc lpop {listname} { + upvar $listname list + set val [lindex $list end] + set list [lrange $list 0 end-1] + return $val } } -# ----- module wiki-formatting ----- +# ----- @module wiki-formatting.tcl ----- -set modsource(wiki-formatting) { +set modsource(wiki-formatting.tcl) { # Copyright (c) 2010 WorkWare Systems http://www.workware.net.au/ # All rights reserved @@ -1928,7 +2451,7 @@ if {$autosetup(debug)} { } if {[catch {main $argv} msg opts] == 1} { show-notices - autosetup-full-error [error-dump $msg $opts $::autosetup(debug)] + autosetup-full-error [error-dump $msg $opts $autosetup(debug)] if {!$autosetup(debug)} { puts stderr "Try: '[file tail $autosetup(exe)] --debug' for a full stack trace" } |