Files
modules/modulecmd.tcl.in
2019-05-15 20:50:30 +02:00

9438 lines
324 KiB
Tcl

#!@TCLSHDIR@/tclsh
#
# MODULECMD.TCL, a pure TCL implementation of the module command
# Copyright (C) 2002-2004 Mark Lakata
# Copyright (C) 2004-2017 Kent Mein
# Copyright (C) 2016-2019 Xavier Delaruelle
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
##########################################################################
#
# Some Global Variables.....
#
set g_debug 0 ;# Set to 1 to enable debugging
set error_count 0 ;# Start with 0 errors
set g_return_false 0 ;# False value is rendered if == 1
set g_autoInit 0
set g_inhibit_interp 0 ;# Modulefile interpretation disabled if == 1
set g_inhibit_errreport 0 ;# Non-critical error reporting disabled if == 1
set CSH_LIMIT 4000 ;# Workaround for commandline limits in csh
set flag_default_dir 1 ;# Report default directories
set flag_default_mf 1 ;# Report default modulefiles and version alias
set reportfd stderr ;# File descriptor to use to report messages
set g_pager {@pager@} ;# Default command to page into, empty=disable
set g_pager_opts {@pageropts@} ;# Options to pass to the pager command
set g_siteconfig @etcdir@/siteconfig.tcl ;# Site configuration
set g_tclextlib @libdir@/libtclenvmodules@SHLIB_SUFFIX@ ;# Tcl extension lib
# Source site config which can be used to define global procedures or
# settings. We first look for the global siteconfig, then if
# $MODULES_SITECONFIG is defined, source that file if it exists
proc sourceSiteConfig {} {
lappend siteconfiglist $::g_siteconfig
if {[info exist ::env(MODULES_SITECONFIG)]} {
lappend siteconfiglist $::env(MODULES_SITECONFIG)
}
foreach siteconfig $siteconfiglist {
if {[file readable $siteconfig]} {
reportDebug "Source site configuration ($siteconfig)"
if {[catch {uplevel 1 source $siteconfig} errMsg]} {
reportErrorAndExit "Site configuration source failed\n$errMsg"
}
set ::g_siteconfig_loaded 1
}
}
}
# Used to tell if a machine is running Windows or not
proc isWin {} {
return [expr {$::tcl_platform(platform) eq {windows}}]
}
# Get default path separator
proc getPathSeparator {} {
if {![info exists ::g_def_separator]} {
set ::g_def_separator [expr {[isWin] ? {;} : {:}}]
}
return $::g_def_separator
}
# Sub level separator to serialize a second level of info in env var
set ::g_sub1_separator &
# Sub sub level separator to serialize a third level of info in env var
set ::g_sub2_separator |
# Detect if terminal is attached to stderr message channel
proc isStderrTty {} {
if {![info exists ::g_is_stderr_tty]} {
set ::g_is_stderr_tty [expr {![catch {fconfigure stderr -mode}]}]
}
return $::g_is_stderr_tty
}
# Provide columns number for output formatting
proc getTtyColumns {} {
if {![info exists ::g_tty_columns]} {
# determine col number from tty capabilites
# tty info query depends on running OS
switch -- $::tcl_platform(os) {
SunOS {
catch {regexp {columns = (\d+);} [exec stty] match cols} errMsg
}
{Windows NT} {
catch {regexp {Columns:\s+(\d+)} [exec mode] match cols} errMsg
}
default {
catch {set cols [lindex [exec stty size] 1]} errMsg
}
}
# default size if tty cols cannot be found
set ::g_tty_columns [expr {![info exists cols] || $cols eq {0} ? 80 :\
$cols}]
}
return $::g_tty_columns
}
# Get automated module handling mode
proc getAutoHandling {} {
if {![info exists ::g_auto_handling]} {
# default value set at configure time
set auto_handling @autohandling@ ;# Automated modules handling if 1
# overriden value coming from environment
if {[info exists ::env(MODULES_AUTO_HANDLING)]} {
# ignore non-valid values
switch -- $::env(MODULES_AUTO_HANDLING) {
0 - 1 {
set auto_handling $::env(MODULES_AUTO_HANDLING)
}
}
}
# overriden value coming the command-line
if {[info exists ::asked_auto_handling]} {
set auto_handling $::asked_auto_handling
}
set ::g_auto_handling $auto_handling
reportDebug "auto_handling set to '$auto_handling'"
}
return $::g_auto_handling
}
# Get force mode
proc getForce {} {
if {![info exists ::g_force]} {
set force 0 ;# By-pass dependency consistency
# overriden value coming the command-line
if {[info exists ::asked_force]} {
set force $::asked_force
}
set ::g_force $force
reportDebug "force set to '$force'"
}
return $::g_force
}
# Get avail in depth mode
proc getAvailInDepth {} {
if {![info exists ::g_avail_indepth]} {
set avail_indepth @availindepth@ ;# Search modulefiles in depth
# overriden value coming from environment
if {[info exists ::env(MODULES_AVAIL_INDEPTH)]} {
# ignore non-valid values
switch -- $::env(MODULES_AVAIL_INDEPTH) {
0 - 1 {
set avail_indepth $::env(MODULES_AVAIL_INDEPTH)
}
}
}
set ::g_avail_indepth $avail_indepth
reportDebug "avail_indepth set to '$avail_indepth'"
}
return $::g_avail_indepth
}
# Get terminal background color kind (dark or light)
proc getTermBackground {} {
if {![info exists ::g_term_background]} {
set term_background @termbg@ ;# Terminal background color
# overriden value coming from environment
if {[info exists ::env(MODULES_TERM_BACKGROUND)]} {
# ignore non-valid values
switch -- $::env(MODULES_TERM_BACKGROUND) {
dark - light {
set term_background $::env(MODULES_TERM_BACKGROUND)
}
}
}
set ::g_term_background $term_background
reportDebug "term_background set to '$term_background'"
}
return $::g_term_background
}
# Initialize Select Graphic Rendition table
proc initColors {} {
if {![array exists ::g_colors]} {
# overriden value coming from environment
if {[info exists ::env(MODULES_COLORS)]} {
set ::g_colors_list $::env(MODULES_COLORS)
if {[catch {
array set ::g_colors [split $::g_colors_list {:=}]
} errMsg ]} {
# report issue as a debug message rather warning to avoid
# disturbing user with a warning message in the middle of a
# useful output as this table will be initialized at first use
reportDebug "Ignore invalid value set in MODULES_COLORS\
($::g_colors_list)"
# fully clear array in case it was partially initialized
unset -nocomplain ::g_colors
}
}
# if no valid override set use default color theme for terminal
# background color kind (light or dark)
if {![array exists ::g_colors]} {
set termbg [getTermBackground]
if {$termbg eq {light}} {
set ::g_colors_list {@lightbgcolors@}
} else {
set ::g_colors_list {@darkbgcolors@}
}
if {[catch {
array set ::g_colors [split $::g_colors_list {:=}]
} errMsg ]} {
reportDebug "Ignore invalid default $termbg background colors\
($::g_colors_list)"
# define an empty table if no valid value set
array set ::g_colors {}
}
}
# check each color defined and unset invalid codes
foreach {elt col} [array get ::g_colors] {
if {![regexp {^[\d;]+$} $col]} {
unset ::g_colors($elt)
reportDebug "Ignore invalid color code for '$elt' ($col)"
}
}
reportDebug "colors set to '[array get ::g_colors]'"
}
}
# Get color mode
proc getColor {} {
if {![info exists ::g_color]} {
set color @color@ ;# Color content automatically
# overriden value coming from environment
if {[info exists ::env(MODULES_COLOR)]} {
# ignore non-valid values
switch -- $::env(MODULES_COLOR) {
always {
set color 2
}
auto {
set color 1
}
never {
set color 0
}
}
}
# overriden value coming the command-line
if {[info exists ::asked_color]} {
set color $::asked_color
}
# disable color mode if no terminal attached except if 'always' asked
if {$color != 0 && (![isStderrTty] || $color == 2)} {
incr color -1
}
# initialize color theme if color mode enabled
initColors
set ::g_color $color
reportDebug "color set to '$color'"
}
return $::g_color
}
# Get contact address for modulefile issue
proc getContact {} {
if {![info exists ::g_contact]} {
# default value, change this to your support email address...
set contact root@localhost
# overriden value coming from environment
if {[info exists ::env(MODULECONTACT)]} {
set contact $::env(MODULECONTACT)
}
set ::g_contact $contact
reportDebug "contact set to '$contact'"
}
return $::g_contact
}
# Some directories are ignored when looking for modules.
proc getIgnoredDirs {} {
if {![info exists ::g_ignored_dirs]} {
set ::g_ignored_dirs {CVS RCS SCCS .svn .git .SYNC .sos}
}
return $::g_ignored_dirs
}
proc raiseErrorCount {} {
incr ::error_count
}
proc renderFalse {} {
reportDebug called.
if {[info exists ::g_false_rendered]} {
reportDebug {false already rendered}
} elseif {[info exists ::g_shellType]} {
# setup flag to render only once
set ::g_false_rendered 1
# render a false value most of the time through a variable assignement
# that will be looked at in the shell module function calling
# modulecmd.tcl to return in turns a boolean status. Except for python
# and cmake, the value assigned to variable is also returned as the
# entire rendering status
switch -- $::g_shellType {
sh - csh - fish {
# no need to set a variable on real shells as last statement
# result can easily be checked
puts stdout {test 0 = 1;}
}
tcl {
puts stdout {set _mlstatus 0;}
}
cmd {
puts stdout {set errorlevel=1}
}
perl {
puts stdout {$_mlstatus = 0;}
}
python {
puts stdout {_mlstatus = False}
}
ruby {
puts stdout {_mlstatus = false}
}
lisp {
puts stdout {nil}
}
cmake {
puts stdout {set(_mlstatus FALSE)}
}
r {
puts stdout {mlstatus <- FALSE}
}
}
}
}
proc renderTrue {} {
reportDebug called.
# render a true value most of the time through a variable assignement that
# will be looked at in the shell module function calling modulecmd.tcl to
# return in turns a boolean status. Except for python and cmake, the
# value assigned to variable is also returned as the full rendering status
switch -- $::g_shellType {
sh - csh - fish {
# no need to set a variable on real shells as last statement
# result can easily be checked
puts stdout {test 0;}
}
tcl {
puts stdout {set _mlstatus 1;}
}
cmd {
puts stdout {set errorlevel=0}
}
perl {
puts stdout {$_mlstatus = 1;}
}
python {
puts stdout {_mlstatus = True}
}
ruby {
puts stdout {_mlstatus = true}
}
lisp {
puts stdout {t}
}
cmake {
puts stdout {set(_mlstatus TRUE)}
}
r {
puts stdout {mlstatus <- TRUE}
}
}
}
proc renderText {text} {
reportDebug "called ($text)."
# render a text value most of the time through a variable assignement that
# will be looked at in the shell module function calling modulecmd.tcl to
# return in turns a string value.
switch -- $::g_shellType {
sh - csh - fish {
foreach word $text {
# no need to set a variable on real shells, echoing text will make
# it available as result
puts stdout "echo '$word';"
}
}
tcl {
puts stdout "set _mlstatus \"$text\";"
}
cmd {
foreach word $text {
puts stdout "echo $word"
}
}
perl {
puts stdout "\$_mlstatus = '$text';"
}
python {
puts stdout "_mlstatus = '$text'"
}
ruby {
puts stdout "_mlstatus = '$text'"
}
lisp {
puts stdout "(message \"$text\")"
}
cmake {
puts stdout "set(_mlstatus \"$text\")"
}
r {
puts stdout "mlstatus <- '$text'"
}
}
}
#
# Debug, Info, Warnings and Error message handling.
#
# save message when report is not currently initialized as we do not
# know yet if debug mode is enabled or not
proc reportDebug {message {showcaller 1}} {
# display caller name as prefix
set prefix [expr {$showcaller && [info level] > 1 ? "[lindex [info level\
-1] 0]: " : {}}]
lappend ::errreport_buffer [list reportDebug $prefix$message 0]
}
# regular procedure to use once error report is initialized
proc __reportDebug {message {showcaller 1}} {
# display active interp details if not the main one
set prefix [currentDebugMsgPrefix]
# display caller name as prefix
if {$showcaller && [info level] > 1} {
append prefix "[lindex [info level -1] 0]: "
}
report [sgr db "DEBUG $prefix$message"] 0 1
}
# alternative procedure used when debug is disabled
proc __reportDebugNop {args} {}
proc reportWarning {message {recordtop 0}} {
reportError $message $recordtop WARNING wa 0
}
proc reportError {message {recordtop 0} {severity ERROR} {sgrkey er}\
{raisecnt 1}} {
lappend ::errreport_buffer [list reportError $message $recordtop $severity\
$sgrkey $raisecnt]
}
proc __reportError {message {recordtop 0} {severity ERROR} {sgrkey er}\
{raisecnt 1}} {
# if report disabled, also disable error raise to get a coherent
# behavior (if no message printed, no error code change)
if {!$::g_inhibit_errreport} {
if {$raisecnt} {
raiseErrorCount
}
set message "[sgr $sgrkey $severity]: $message"
# record message to report it later on if a record id is found
if {[currentMsgRecordId] ne {}} {
recordMessage $message $recordtop
} else {
report $message
}
}
}
# save message if report is not yet initialized
proc reportErrorAndExit {message} {
lappend ::errreport_buffer [list reportErrorAndExit $message]
}
# regular procedure to use once error report is initialized
proc __reportErrorAndExit {message} {
raiseErrorCount
renderFalse
error $message
}
proc reportInternalBug {message modfile} {
# change line padding depending on output-kind
set pad [expr {[currentMsgRecordId] ne {} ? {} : { }}]
reportError "$message\n${pad}In '$modfile'\n${pad}Please contact\
<[getContact]>" 0 {Module ERROR} me
}
proc reportInfo {message {title INFO}} {
# use reportError for conveniance but there is no error here
reportError $message 0 $title in 0
}
# is currently active message record id at top level
proc isMsgRecordIdTop {} {
return [expr {[llength $::g_msgRecordIdStack] eq 1}]
}
# record messages on the eventual additional module evaluations that have
# occurred during the current evaluation
proc reportModuleEval {} {
set recid [currentMsgRecordId]
array set contexttitle {conun {Unloading conflict} reqlo {Loading\
requirement} depre {Reloading dependent} depun {Unloading dependent}\
urequn {Unloading useless requirement} unmo {Unloading module} lomo\
{Loading module}}
if {[info exists ::g_moduleEval($recid)]} {
foreach contextevallist $::g_moduleEval($recid) {
set modlist [lassign $contextevallist context]
# skip context with no description title
if {[info exists contexttitle($context)]} {
reportInfo [join $modlist] $contexttitle($context)
}
}
# purge list in case same evaluation is re-done afterward
unset ::g_moduleEval($recid)
}
}
# render messages related to current record id under an header block
proc reportMsgRecord {header} {
set recid [currentMsgRecordId]
if {[info exists ::g_msgRecord($recid)]} {
set tty_cols [getTtyColumns]
set padding { }
set dispmsg $header
foreach msg $::g_msgRecord($recid) {
# split lines if too large for terminal
set first 1
set max_idx [expr {$tty_cols - [string length $padding]}]
set linelist [list]
foreach line [split $msg \n] {
set lineadd {}
while {$lineadd ne $line} {
# no split if no whitespace found to slice
if {[string length $line] > $max_idx && [set cut_idx [string\
last { } $line $max_idx]] != -1} {
set lineadd [string range $line 0 [expr {$cut_idx-1}]]
set line [string range $line [expr {$cut_idx+1}] end]
} else {
set lineadd $line
}
lappend linelist $lineadd
if {$first} {
set first 0
incr max_idx -[string length $padding]
}
}
}
# display each line
set first 1
foreach line $linelist {
append dispmsg \n
if {$first} {
set first 0
} else {
append dispmsg $padding
}
append dispmsg $padding$line
}
}
reportSeparateNextContent
report $dispmsg
reportSeparateNextContent
# purge message list in case same evaluation is re-done afterward
unset ::g_msgRecord($recid)
}
}
# separate next content produced if any
proc reportSeparateNextContent {} {
lappend ::errreport_buffer [list reportSeparateNextContent]
}
# regular procedure to use once error report is initialized
proc __reportSeparateNextContent {} {
# hold or apply
if {[isReportHeld]} {
lappend ::g_holdReport([currentReportHoldId]) [list\
reportSeparateNextContent]
} else {
set ::g_report_sep_next 1
}
}
# save message for block rendering
proc recordMessage {message {recordtop 0}} {
lappend ::g_msgRecord([expr {$recordtop ? [topMsgRecordId] :\
[currentMsgRecordId]}]) $message
}
# Select Graphic Rendition of a string with passed sgr key (if color enabled)
proc sgr {sgrkey str} {
if {[getColor] && [info exists ::g_colors($sgrkey)]} {
set sgrset $::g_colors($sgrkey)
# if render bold or faint just reset that attribute, not all
if {$sgrset == 1 || $sgrset == 2} {
set sgrreset 22
} else {
set sgrreset 0
}
set str "\033\[${sgrset}m$str\033\[${sgrreset}m"
}
return $str
}
# save message if report is not yet initialized
proc report {message {nonewline 0} {immed 0}} {
lappend ::errreport_buffer [list report $message $nonewline $immed]
}
# regular procedure to use once error report is initialized
proc __report {message {nonewline 0} {immed 0}} {
# hold or print output
if {!$immed && [isReportHeld]} {
lappend ::g_holdReport([currentReportHoldId]) [list report $message\
$nonewline]
} else {
if {![info exists ::g_already_report]} {
set ::g_already_report 1
# start pager at first call and only if enabled
if {$::start_pager} {
startPager
}
# produce blank line prior message if asked to
} elseif {[info exists ::g_report_sep_next]} {
unset ::g_report_sep_next
report {}
}
# protect from issue with fd, just ignore it
catch {
if {$nonewline} {
puts -nonewline $::reportfd $message
} else {
puts $::reportfd $message
}
}
}
}
# report error the correct way depending of its type
proc reportIssue {issuetype issuemsg {issuefile {}}} {
switch -- $issuetype {
invalid {
reportInternalBug $issuemsg $issuefile
}
default {
reportError $issuemsg
}
}
}
# report defined command (used in display evaluation mode)
proc reportCmd {cmd args} {
set extratab [expr {[string length $cmd] < 8 ? "\t" : {}}]
# only brace empty arguments or those containing whitespace
set cmdargs {}
foreach cmdarg $args {
if {$cmdargs ne {}} {
append cmdargs { }
}
append cmdargs [expr {$cmdarg eq {} || [string first { } $cmdarg] > -1\
? "{$cmdarg}" : $cmdarg}]
}
report [sgr cm $cmd]$extratab\t$cmdargs
# empty string returns if command result is another command input
return {}
}
# report defined command (called as an execution trace)
proc reportCmdTrace {cmdstring args} {
eval reportCmd $cmdstring
}
proc reportVersion {} {
report {Modules Release @MODULES_RELEASE@@MODULES_BUILD@\
(@MODULES_BUILD_DATE@)}
}
# disable error reporting (non-critical report only) unless debug enabled
proc inhibitErrorReport {} {
if {!$::g_debug} {
set ::g_inhibit_errreport 1
}
}
proc reenableErrorReport {} {
set ::g_inhibit_errreport 0
}
proc isErrorReportInhibited {} {
return $::g_inhibit_errreport
}
# init error report and output buffered messages
proc initErrorReport {} {
# ensure init is done only once
if {![info exists ::g_init_error_report]} {
set ::g_init_error_report 1
# determine message paging configuration and enablement
initPager
# ask for color init now as debug mode has already fire lines to render
# and we want them to be reported first (not the color init lines)
if {$::g_debug} {
getColor
}
# replace report procedures used to bufferize messages until error
# report being initialized by regular report procedures
rename ::reportDebug {}
if {$::g_debug} {
rename ::__reportDebug ::reportDebug
} else {
# set a disabled version if debug is disabled
rename ::__reportDebugNop ::reportDebug
}
rename ::reportError {}
rename ::__reportError ::reportError
rename ::reportErrorAndExit {}
rename ::__reportErrorAndExit ::reportErrorAndExit
rename ::reportSeparateNextContent {}
rename ::__reportSeparateNextContent ::reportSeparateNextContent
rename ::report {}
rename ::__report ::report
# now error report is init output every message saved in buffer
foreach errreport $::errreport_buffer {
eval $errreport
}
}
}
# drop or report held messages
proc releaseHeldReport {args} {
foreach {holdid action} $args {
if {[info exists ::g_holdReport($holdid)]} {
if {$action eq {report}} {
foreach repcall $::g_holdReport($holdid) {
eval $repcall
}
}
unset ::g_holdReport($holdid)
}
}
}
# exit in a clean manner by closing interaction with external components
proc cleanupAndExit {code} {
# close pager if enabled
if {$::reportfd ne {stderr}} {
catch {flush $::reportfd}
catch {close $::reportfd}
}
exit $code
}
# init configuration for output paging to prepare for startup
proc initPager {} {
# default pager enablement depends of pager command value
if {$::g_pager eq {} || [file tail $::g_pager] eq {cat}} {
set ::use_pager 0
set init_use_pager 0
} else {
set ::use_pager 1
set init_use_pager 1
}
if {[info exists ::env(MODULES_PAGER)]} {
if {$::env(MODULES_PAGER) ne {}} {
# MODULES_PAGER env variable set means pager should be enabled
if {!$::use_pager} {
set ::use_pager 1
}
# fetch pager command and option
set ::g_pager [lindex $::env(MODULES_PAGER) 0]
set ::g_pager_opts [lrange $::env(MODULES_PAGER) 1 end]
# variable defined empty means no-pager
} else {
set ::use_pager 0
set ::g_pager {}
set ::g_pager_opts {}
}
reportDebug "configure pager from MODULES_PAGER variable\
(use_pager=$::use_pager, cmd='$::g_pager', opts='$::g_pager_opts')"
}
# paging may have been enabled or disabled from the command-line
if {[info exists ::asked_pager]} {
# enable from command-line only if it is enabled in script config
if {$::asked_pager && !$::use_pager && $init_use_pager} {
set ::use_pager 1
} elseif {!$::asked_pager && $::use_pager} {
set ::use_pager 0
}
set asked $::asked_pager
} else {
set asked -
}
# empty or 'cat' pager command means no-pager
if {$::use_pager && ($::g_pager eq {} || [file tail $::g_pager] eq\
{cat})} {
set ::use_pager 0
}
# some module command may also turn off pager
if {$::command eq {clear}} {
set ::use_pager 0
}
# start paging if enabled and if error stream is attached to a terminal
set is_tty [isStderrTty]
if {$is_tty && $::use_pager} {
reportDebug "start pager (asked_pager=$asked, cmd='$::g_pager',\
opts='$::g_pager_opts')"
set ::start_pager 1
} else {
reportDebug "no pager start (is_tty=$is_tty, mod_cmd='$::command',\
use_pager=$::use_pager, asked_pager=$asked, cmd='$::g_pager',\
opts='$::g_pager_opts')"
set ::start_pager 0
}
}
# start pager pipe process with defined configuration
proc startPager {} {
if {[catch {
set ::reportfd [open "|$::g_pager $::g_pager_opts >@stderr 2>@stderr" w]
fconfigure $::reportfd -buffering line -blocking 1 -buffersize 65536
} errMsg]} {
reportWarning $errMsg
}
}
# helper procedures to format various messages
proc getHintUnFirstMsg {modlist} {
return "HINT: Might try \"module unload [join $modlist]\" first."
}
proc getHintLoFirstMsg {modlist} {
if {[llength $modlist] > 1} {
set oneof {at least one of }
set mod modules
} else {
set oneof {}
set mod module
}
return "HINT: ${oneof}the following $mod must be loaded first: $modlist"
}
proc getErrConflictMsg {mod conlist} {
return "$mod cannot be loaded due to a conflict.\n[getHintUnFirstMsg\
$conlist]"
}
proc getErrPrereqMsg {mod prelist {load 1}} {
lassign [if {$load} {list {} missing [getHintLoFirstMsg $prelist]}\
{list un a [getHintUnFirstMsg $prelist]}] un mis hintmsg
return "$mod cannot be ${un}loaded due to $mis prereq.\n$hintmsg"
}
proc getErrReqLoMsg {prelist} {
return "Load of requirement [join $prelist { or }] failed"
}
proc getReqNotLoadedMsg {prelist} {
return "Requirement [join $prelist { or }] is not loaded"
}
proc getDepLoadedMsg {prelist} {
set is [expr {[llength $prelist] > 1 ? {are} : {is}}]
return "Dependent [join $prelist { and }] $is loaded"
}
proc getErrConUnMsg {conlist} {
return "Unload of conflicting [join $conlist { and }] failed"
}
proc getConIsLoadedMsg {conlist {loading 0}} {
set is [expr {[llength $conlist] > 1 ? {are} : {is}}]
set loaded [expr {$loading ? {loading} : {loaded}}]
return "Conflicting [join $conlist { and }] $is $loaded"
}
########################################################################
# Use a slave TCL interpreter to execute modulefiles
#
# dummy proc to disable modulefile commands on some evaluation modes
proc nop {args} {}
# dummy proc for commands available on other Modules flavor but not here
proc nimp {cmd args} {
reportWarning "'$cmd' command not implemented"
}
proc get-env {var {valifunset {}}} {
# return current value if exists and not cleared
if {[info exists ::env($var)] && ![info exists ::g_clearedEnvVars($var)]} {
return $::env($var)
} else {
return $valifunset
}
}
proc set-env {var val} {
set mode [currentMode]
reportDebug "$var=$val"
set ::env($var) $val
# variable is not cleared anymore if set again
if {[info exists ::g_clearedEnvVars($var)]} {
unset ::g_clearedEnvVars($var)
}
# propagate variable setup to shell environment on load and unload mode
if {$mode eq {load} || $mode eq {unload}} {
set ::g_stateEnvVars($var) new
}
}
proc reset-to-unset-env {var {val {}}} {
set ::env($var) $val
# set var as cleared if val is empty
if {$val eq {}} {
set ::g_clearedEnvVars($var) 1
}
}
proc unset-env {var {internal 0} {val {}}} {
set mode [currentMode]
reportDebug "$var (internal=$internal, val=$val)"
# clear value instead of unset it not to break variable later reference
# in modulefile. clear whether variable set or not to get a later usage
# consistent behavior whatever env is setup
if {!$internal} {
reset-to-unset-env $var $val
# internal variables (like ref counter var) are purely unset if they exists
} elseif {[info exists ::env($var)]} {
unset ::env($var)
set intwasset 1
}
# propagate deletion in any case if variable is public and for internal
# one only if variable was set
if {($mode eq {load} || $mode eq {unload}) && (!$internal ||\
[info exists intwasset])} {
set ::g_stateEnvVars($var) del
}
}
# Initialize list of interp alias commands to define for given evaluation mode
proc initModfileModeAliases {mode aliasesVN aliasesPassArgVN tracesVN} {
global g_modfilePerModeAliases
upvar #0 $aliasesVN aliases
upvar #0 $aliasesPassArgVN aliasesPassArg
upvar #0 $tracesVN traces
if {![info exists g_modfilePerModeAliases]} {
set ::g_modfileBaseAliases [list getenv getenv is-loaded is-loaded\
is-saved is-saved is-used is-used is-avail is-avail uname uname\
module-info module-info exit exitModfileCmd reportCmdTrace\
reportCmdTrace reportInternalBug reportInternalBug reportWarning\
reportWarning reportError reportError raiseErrorCount\
raiseErrorCount report report isWin isWin puts putsModfileCmd\
readModuleContent readModuleContent]
# list of alias commands whose target procedure is adapted according to
# the evaluation mode
set ::g_modfileEvalModes {load unload display help test whatis}
array set g_modfilePerModeAliases {
append-path {append-path remove-path append-path append-path append-path edit-path-wh }
chdir {chdir nop reportCmd nop nop nop }
conflict {conflict nop reportCmd nop nop nop }
module {module module reportCmd nop nop nop }
module-alias {module-alias module-alias module-alias module-alias module-alias module-alias }
module-log {nimp nimp reportCmd nop nop nop }
module-trace {nimp nimp reportCmd nop nop nop }
module-user {nimp nimp reportCmd nop nop nop }
module-verbosity {nimp nimp reportCmd nop nop nop }
module-version {module-version module-version module-version module-version module-version module-version}
module-virtual {module-virtual module-virtual module-virtual module-virtual module-virtual module-virtual}
module-whatis {nop nop reportCmd nop nop module-whatis }
prepend-path {prepend-path remove-path prepend-path prepend-path prepend-path edit-path-wh }
prereq {prereq nop reportCmd nop nop nop }
remove-path {remove-path remove-path-un remove-path remove-path remove-path edit-path-wh }
set-alias {set-alias set-alias-un reportCmd nop nop nop }
set-function {set-function set-function-un reportCmd nop nop nop }
setenv {setenv setenv-un setenv setenv setenv setenv-wh }
system {system system reportCmd nop nop nop }
unset-alias {unset-alias nop reportCmd nop nop nop }
unset-function {unset-function nop reportCmd nop nop nop }
unsetenv {unsetenv unsetenv-un unsetenv unsetenv unsetenv setenv-wh }
x-resource {x-resource x-resource reportCmd nop nop nop }
}
}
# alias commands where interpreter ref should be passed as argument
array set aliasesPassArg [list puts __itrp__]
# initialize list with all commands not dependent of the evaluation mode
array set aliases $::g_modfileBaseAliases
# add alias commands whose target command vary depending on the eval mode
set modeidx [lsearch -exact $::g_modfileEvalModes $mode]
foreach alias [array names g_modfilePerModeAliases] {
set aliastarget [set aliases($alias) [lindex\
$g_modfilePerModeAliases($alias) $modeidx]]
# some target procedures need command name as first arg
if {$aliastarget eq {reportCmd} || $aliastarget eq {nimp}} {
set aliasesPassArg($alias) $alias
# associate a trace command if per-mode alias command is not reportCmd
# in display mode
} elseif {$mode eq {display}} {
set traces($alias) reportCmdTrace
}
}
}
proc execute-modulefile {modfile modname modspec {must_have_cookie 1}} {
pushModuleFile $modfile
pushModuleName $modname
pushSpecifiedName $modspec
set mode [currentMode]
pushDebugMsgPrefix [getEvalModuleStackDepth] $mode $modname
# skip modulefile if interpretation has been inhibited
if {$::g_inhibit_interp} {
reportDebug "skipping $modfile"
return 1
}
reportDebug "sourcing $modfile"
if {![info exists ::g_modfileUntrackVars]} {
# list variable that should not be tracked for saving
array set ::g_modfileUntrackVars [list ModulesCurrentModulefile 1\
must_have_cookie 1 modcontent 1 env 1]
# commands that should be renamed before aliases setup
array set ::g_modfileRenameCmds [list puts _puts]
}
# dedicate an interpreter per mode and per level of interpretation to have
# a dedicated interpreter in case of cascaded multi-mode interpretations
set itrp __modfile_${mode}_[getEvalModuleStackDepth]
# evaluation mode-specific configuration
set dumpCommandsVN g_modfile${mode}Commands
set aliasesVN g_modfile${mode}Aliases
set aliasesPassArgVN g_modfile${mode}AliasesPassArg
set tracesVN g_modfile${mode}Traces
if {![info exists ::$aliasesVN]} {
initModfileModeAliases $mode $aliasesVN $aliasesPassArgVN $tracesVN
}
# create modulefile interpreter at first interpretation
if {![interp exists $itrp]} {
reportDebug "creating interp $itrp"
interp create $itrp
# dump initial interpreter state to restore it before each modulefile
# interpretation. use same dump state for all modes/levels
if {![info exists ::g_modfileVars]} {
dumpInterpState $itrp g_modfileVars g_modfileArrayVars\
g_modfileUntrackVars g_modfileProcs
}
# interp has just been created
set fresh 1
} else {
set fresh 0
}
# reset interp state command before each interpretation
resetInterpState $itrp $fresh g_modfileVars g_modfileArrayVars\
g_modfileUntrackVars g_modfileProcs $aliasesVN $aliasesPassArgVN\
$tracesVN g_modfileRenameCmds $dumpCommandsVN
# reset modulefile-specific variable before each interpretation
interp eval $itrp set ::ModulesCurrentModulefile $modfile
interp eval $itrp set must_have_cookie $must_have_cookie
set errorVal [interp eval $itrp {
set modcontent [readModuleContent $::ModulesCurrentModulefile 1\
$must_have_cookie]
if {$modcontent eq {}} {
return 1
}
info script $::ModulesCurrentModulefile
# eval then call for specific proc depending mode under same catch
set sourceFailed [catch {
eval $modcontent
switch -- [module-info mode] {
help {
if {[info procs ModulesHelp] eq {ModulesHelp}} {
ModulesHelp
} else {
reportWarning "Unable to find ModulesHelp in\
$::ModulesCurrentModulefile."
}
}
display {
if {[info procs ModulesDisplay] eq {ModulesDisplay}} {
ModulesDisplay
}
}
test {
if {[info procs ModulesTest] eq {ModulesTest}} {
if {[string is true -strict [ModulesTest]]} {
report {Test result: PASS}
} else {
report {Test result: FAIL}
raiseErrorCount
}
} else {
reportWarning "Unable to find ModulesTest in\
$::ModulesCurrentModulefile."
}
}
}
} errorMsg]
if {$sourceFailed} {
# no error in case of "continue" command
# catch continue even if called outside of a loop
if {$errorMsg eq {invoked "continue" outside of a loop}\
|| $sourceFailed == 4} {
unset errorMsg
return 0
# catch break even if called outside of a loop
} elseif {$errorMsg eq {invoked "break" outside of a loop}\
|| ($errorMsg eq {} && (![info exists ::errorInfo]\
|| $::errorInfo eq {}))} {
raiseErrorCount
unset errorMsg
return 1
} elseif {$errorMsg eq {SUB_FAILED}} {
# error counter and message already handled, just return error
return 1
} elseif {[string range $errorMsg 0 12] eq {GLOBALERRTOP }} {
reportError [string range $errorMsg 13 end] 1
return 1
} elseif {[string range $errorMsg 0 9] eq {GLOBALERR }} {
reportError [string range $errorMsg 10 end]
return 1
} else {
reportInternalBug $errorMsg $::ModulesCurrentModulefile
return 1
}
} else {
unset errorMsg
return 0
}
}]
reportDebug "exiting $modfile"
popDebugMsgPrefix
popSpecifiedName
popModuleName
popModuleFile
return $errorVal
}
# Smaller subset than main module load... This function runs modulerc and
# .version files
proc execute-modulerc {modfile modname modspec} {
pushModuleFile $modfile
# push name to be found by module-alias and version
pushModuleName $modname
pushSpecifiedName $modspec
set ::ModulesVersion {}
pushDebugMsgPrefix [getEvalModuleStackDepth] $modname
if {![info exists ::g_modrcUntrackVars]} {
# list variable that should not be tracked for saving
array set ::g_modrcUntrackVars [list ModulesCurrentModulefile 1\
ModulesVersion 1 modcontent 1 env 1]
# commands that should be renamed before aliases setup
array set ::g_modrcRenameCmds [list]
# list interpreter alias commands to define
array set ::g_modrcAliases [list uname uname system system chdir\
nop is-loaded is-loaded module-version module-version module-alias\
module-alias module-virtual module-virtual module nop module-info\
module-info module-trace nop module-verbosity nop module-user nop\
module-log nop reportInternalBug reportInternalBug setModulesVersion\
setModulesVersion readModuleContent readModuleContent]
# alias commands where an argument should be passed
array set ::g_modrcAliasesPassArg [list]
# trace commands that should be associated to aliases
array set ::g_modrcAliasesTraces [list]
}
# dedicate an interpreter per level of interpretation to have in case of
# cascaded interpretations a specific interpreter per level
set itrp __modrc_[getEvalModuleStackDepth]
reportDebug "sourcing $modfile"
# create modulerc interpreter at first interpretation
if {![interp exists $itrp]} {
reportDebug "creating interp $itrp"
interp create $itrp
# dump initial interpreter state to restore it before each modulerc
# interpreation. use same dump state for all levels
if {![info exists ::g_modrcVars]} {
dumpInterpState $itrp g_modrcVars g_modrcArrayVars\
g_modrcUntrackVars g_modrcProcs
}
# interp has just been created
set fresh 1
} else {
set fresh 0
}
# reset interp state command before each interpretation
resetInterpState $itrp $fresh g_modrcVars g_modrcArrayVars\
g_modrcUntrackVars g_modrcProcs g_modrcAliases g_modrcAliasesPassArg\
g_modrcAliasesTraces g_modrcRenameCmds g_modrcCommands
interp eval $itrp set ::ModulesCurrentModulefile $modfile
interp eval $itrp {set ::ModulesVersion {}}
set errorVal [interp eval $itrp {
set modcontent [readModuleContent $::ModulesCurrentModulefile]
if {$modcontent eq {}} {
# simply skip rc file, no exit on error here
return 1
}
info script $::ModulesCurrentModulefile
if [catch {eval $modcontent} errorMsg] {
reportInternalBug $errorMsg $::ModulesCurrentModulefile
return 1
} else {
# pass ModulesVersion value to master interp
if {[info exists ::ModulesVersion]} {
setModulesVersion $::ModulesVersion
}
return 0
}
}]
# default version set via ModulesVersion variable in .version file
# override previously defined default version for modname
lassign [getModuleNameVersion] mod modname modversion
if {$modversion eq {.version} && $::ModulesVersion ne {}} {
# ModulesVersion should target an element in current directory
if {[string first / $::ModulesVersion] == -1} {
setModuleResolution $modname/default $modname/$::ModulesVersion\
default
} else {
reportError "Invalid ModulesVersion '$::ModulesVersion' defined"
}
}
popDebugMsgPrefix
popSpecifiedName
popModuleName
popModuleFile
return $::ModulesVersion
}
# Save list of the defined procedure and the global variables with their
# associated values set in slave interpreter passed as argument. Global
# structures are used to save these information and the name of these
# structures are provided as argument.
proc dumpInterpState {itrp dumpVarsVN dumpArrayVarsVN untrackVarsVN\
dumpProcsVN} {
upvar #0 $dumpVarsVN dumpVars
upvar #0 $dumpArrayVarsVN dumpArrayVars
upvar #0 $untrackVarsVN untrackVars
upvar #0 $dumpProcsVN dumpProcs
regexp {^__[a-z]+} $itrp itrpkind
# save name and value for any other global variables
foreach var [$itrp eval {info globals}] {
if {![info exists untrackVars($var)]} {
reportDebug "saving for $itrpkind var $var"
if {[$itrp eval array exists ::$var]} {
set dumpVars($var) [$itrp eval array get ::$var]
set dumpArrayVars($var) 1
} else {
set dumpVars($var) [$itrp eval set ::$var]
}
}
}
# save name of every defined procedures
foreach var [$itrp eval {info procs}] {
set dumpProcs($var) 1
}
reportDebug "saving for $itrpkind proc list [array names dumpProcs]"
}
# Define commands to be known by slave interpreter.
proc initInterpCommands {itrp fresh aliasesVN aliasesPassArgVN tracesVN\
renameCmdsVN} {
upvar #0 $aliasesVN aliases
upvar #0 $aliasesPassArgVN aliasesPassArg
upvar #0 $tracesVN traces
upvar #0 $renameCmdsVN renameCmds
# rename some commands on freshly created interp before aliases defined
# below overwrite them
if {$fresh} {
foreach cmd [array names renameCmds] {
$itrp eval rename $cmd $renameCmds($cmd)
}
}
# set interpreter alias commands each time to guaranty them being
# defined and not overridden by modulefile or modulerc content
foreach alias [array names aliases] {
if {[info exists aliasesPassArg($alias)]} {
set aliasarg $aliasesPassArg($alias)
# pass current itrp reference on special keyword
if {$aliasarg eq {__itrp__}} {
set aliasarg $itrp
}
interp alias $itrp $alias {} $aliases($alias) $aliasarg
} else {
interp alias $itrp $alias {} $aliases($alias)
}
}
foreach alias [array names traces] {
interp eval $itrp [list trace add execution $alias leave\
$traces($alias)]
}
}
# Restore initial setup of slave interpreter passed as argument based on
# global structure previously filled with initial list of defined procedure
# and values of global variable.
proc resetInterpState {itrp fresh dumpVarsVN dumpArrayVarsVN untrackVarsVN\
dumpProcsVN aliasesVN aliasesPassArgVN tracesVN renameCmdsVN\
dumpCommandsVN} {
upvar #0 $dumpVarsVN dumpVars
upvar #0 $dumpArrayVarsVN dumpArrayVars
upvar #0 $untrackVarsVN untrackVars
upvar #0 $dumpProcsVN dumpProcs
upvar #0 $dumpCommandsVN dumpCommands
# look at list of defined procedures and delete those not part of the
# initial state list. do not check if they have been altered as no vital
# procedures lied there. note that if a Tcl command has been overridden
# by a proc, it will be removed here and command will also disappear
foreach var [$itrp eval {info procs}] {
if {![info exists dumpProcs($var)]} {
reportDebug "removing on $itrp proc $var"
$itrp eval [list rename $var {}]
}
}
# rename some commands and set aliases on interpreter
initInterpCommands $itrp $fresh $aliasesVN $aliasesPassArgVN $tracesVN\
$renameCmdsVN
# dump interpreter command list here on first time as aliases should be
# set prior to be found on this list for correct match
if {![info exists dumpCommands]} {
set dumpCommands [$itrp eval {info commands}]
reportDebug "saving for $itrp command list $dumpCommands"
# if current interpreter command list does not match initial list it
# means that at least one command has been altered so we need to recreate
# interpreter to guaranty proper functioning
} elseif {$dumpCommands ne [$itrp eval {info commands}]} {
reportDebug "missing command(s), recreating interp $itrp"
interp delete $itrp
interp create $itrp
initInterpCommands $itrp 1 $aliasesVN $aliasesPassArgVN $tracesVN\
$renameCmdsVN
}
# check every global variables currently set and correct them to restore
# initial interpreter state. work on variables at the very end to ensure
# procedures and commands are correctly defined
foreach var [$itrp eval {info globals}] {
if {![info exists untrackVars($var)]} {
if {![info exists dumpVars($var)]} {
reportDebug "removing on $itrp var $var"
$itrp eval unset ::$var
} elseif {![info exists dumpArrayVars($var)]} {
if {$dumpVars($var) ne [$itrp eval set ::$var]} {
reportDebug "restoring on $itrp var $var"
if {[llength $dumpVars($var)] > 1} {
# restore value as list
$itrp eval set ::$var [list $dumpVars($var)]
} else {
# brace value to be able to restore empty string
$itrp eval set ::$var "{$dumpVars($var)}"
}
}
} else {
if {$dumpVars($var) ne [$itrp eval array get ::$var]} {
reportDebug "restoring on $itrp var $var"
$itrp eval array set ::$var [list $dumpVars($var)]
}
}
}
}
}
########################################################################
# commands run from inside a module file
#
proc module-info {what {more {}}} {
set mode [currentMode]
reportDebug "$what $more"
switch -- $what {
mode {
if {$more ne {}} {
set command [currentCommandName]
return [expr {$mode eq $more || ($more eq {remove} && $mode eq \
{unload}) || ($more eq {switch} && $command eq {switch})}]
} else {
return $mode
}
}
command {
set command [currentCommandName]
if {$more eq {}} {
return $command
} else {
return [expr {$command eq $more}]
}
}
name {
return [currentModuleName]
}
specified {
return [currentSpecifiedName]
}
shell {
if {$more ne {}} {
return [expr {$::g_shell eq $more}]
} else {
return $::g_shell
}
}
flags {
# C-version specific option, not relevant for Tcl-version but return
# a zero integer value to avoid breaking modulefiles using it
return 0
}
shelltype {
if {$more ne {}} {
return [expr {$::g_shellType eq $more}]
} else {
return $::g_shellType
}
}
user {
# C-version specific option, not relevant for Tcl-version but return
# an empty value or false to avoid breaking modulefiles using it
if {$more ne {}} {
return 0
} else {
return {}
}
}
alias {
set ret [resolveModuleVersionOrAlias $more]
if {$ret ne $more} {
return $ret
} else {
return {}
}
}
trace {
return {}
}
tracepat {
return {}
}
type {
return Tcl
}
symbols {
lassign [getModuleNameVersion $more 1] mod modname modversion
set tag_list [getVersAliasList $mod]
# if querying special symbol "default" but nothing found registered
# on it, look at symbol registered on bare module name in case there
# are symbols registered on it but no default symbol set yet to link
# to them
if {[llength $tag_list] == 0 && $modversion eq {default}} {
set tag_list [getVersAliasList $modname]
}
return [join $tag_list :]
}
version {
lassign [getModuleNameVersion $more 1] mod
return [resolveModuleVersionOrAlias $mod]
}
loaded {
lassign [getModuleNameVersion $more 1] mod
return [getLoadedMatchingName $mod returnall]
}
default {
error "module-info $what not supported"
return {}
}
}
}
proc module-whatis {args} {
set message [join $args]
reportDebug $message
lappend ::g_whatis $message
return {}
}
# convert environment variable references in string to their values
# every local variable is prefixed by '0' to ensure they will not be
# overwritten through variable reference resolution process
proc resolvStringWithEnv {0str} {
# fetch variable references in string
set 0match_list [regexp -all -inline {\$[{]?([A-Za-z_][A-Za-z0-9_]*)[}]?}\
${0str}]
if {[llength ${0match_list}] > 0} {
# put in local scope every environment variable referred in string
for {set 0i 1} {${0i} < [llength ${0match_list}]} {incr 0i 2} {
set 0varname [lindex ${0match_list} ${0i}]
if {![info exists ${0varname}]} {
set ${0varname} [get-env ${0varname}]
}
}
# resolv variable reference with values (now in local scope)
set 0res [subst -nobackslashes -nocommands ${0str}]
} else {
set 0res ${0str}
}
reportDebug "'${0str}' resolved to '${0res}'"
return ${0res}
}
# deduce modulepath from modulefile and module name
proc getModulepathFromModuleName {modfile modname} {
return [string range $modfile 0 end-[string length /$modname]]
}
# deduce module name from modulefile and modulepath
proc getModuleNameFromModulepath {modfile modpath} {
return [string range $modfile [string length $modpath/] end]
}
# extract module name from modulefile and currently enabled modulepaths
proc findModuleNameFromModulefile {modfile} {
set ret {}
foreach modpath [getModulePathList] {
if {[string first $modpath/ $modfile/] == 0} {
set ret [getModuleNameFromModulepath $modfile $modpath]
break
}
}
return $ret
}
# extract modulepath from modulefile and currently enabled modulepaths
proc findModulepathFromModulefile {modfile} {
set ret {}
foreach modpath [getModulePathList] {
if {[string first $modpath/ $modfile/] == 0} {
set ret $modpath
break
}
}
return $ret
}
# Determine with a name provided as argument the corresponding module name,
# version and name/version. Module name is guessed from current module name
# when shorthand version notation is used. Both name and version are guessed
# from current module if name provided is empty. If 'name_relative_tocur' is
# enabled then name argument may be interpreted as a name relative to the
# current modulefile directory (useful for module-version and module-alias
# for instance).
proc getModuleNameVersion {{name {}} {name_relative_tocur 0}} {
set curmod [currentModuleName]
set curmodname [file dirname $curmod]
set curmodversion [file tail $curmod]
if {$name eq {}} {
set name $curmodname
set version $curmodversion
# check for shorthand version notation like "/version" or "./version"
# only if we are currently interpreting a modulefile or modulerc
} elseif {$curmod ne {} && [regexp {^\.?\/(.*)$} $name match version]} {
# if we cannot distinguish a module name, raise error when shorthand
# version notation is used
if {$::ModulesCurrentModulefile ne $curmod && $curmod ne {.modulerc}} {
# name is the name of current module directory
set name $curmodname
} else {
reportError "Invalid modulename '$name' found"
return {}
}
} else {
set name [string trimright $name /]
set version [file tail $name]
if {$name eq $version} {
set version {}
} else {
set name [file dirname $name]
}
# name may correspond to last part of current module
# if so name is replaced by current module name
if {$name_relative_tocur && [file tail $curmodname] eq $name} {
set name $curmodname
}
}
if {$version eq {}} {
set mod $name
} else {
set mod $name/$version
}
return [list $mod $name $version]
}
# Register alias or symbolic version deep resolution in a global array that
# can be used thereafter to get in one query the actual modulefile behind
# a virtual name. Also consolidate a global array that in the same manner
# list all the symbols held by modulefiles.
proc setModuleResolution {mod target {symver {}} {override_res_path 1}} {
global g_moduleResolved g_resolvedHash g_resolvedPath g_symbolHash
# find end-point module and register step-by-step path to get to it
set res $target
set res_path $res
while {$mod ne $res && [info exists g_resolvedPath($res)]} {
set res $g_resolvedPath($res)
lappend res_path $res
}
# error if resolution end on initial module
if {$mod eq $res} {
reportError "Resolution loop on '$res' detected"
return 0
}
# module name will be useful when registering symbol
if {$symver ne {}} {
lassign [getModuleNameVersion $mod] modfull modname
}
# change default symbol owner if previously given
if {$symver eq {default}} {
# alternative name "modname" is set when mod = "modname/default" both
# names will be registered to be known for queries and resolution defs
set modalt $modname
if {[info exists g_moduleResolved($mod)]} {
set prev $g_moduleResolved($mod)
# no test needed, there must be a "default" in $prev symbol list
set idx [lsearch -exact $g_symbolHash($prev) default]
reportDebug "remove symbol 'default' from '$prev'"
set g_symbolHash($prev) [lreplace $g_symbolHash($prev) $idx $idx]
}
}
# register end-point resolution
reportDebug "$mod resolved to $res"
set g_moduleResolved($mod) $res
# set first element of resolution path only if not already set or
# scratching enabled, no change when propagating symbol along res path
if {$override_res_path || ![info exists g_resolvedPath($mod)]} {
set g_resolvedPath($mod) $target
}
lappend g_resolvedHash($res) $mod
# also register resolution on alternative name if any
if {[info exists modalt]} {
reportDebug "$modalt resolved to $res"
set g_moduleResolved($modalt) $res
if {$override_res_path || ![info exists g_resolvedPath($modalt)]} {
set g_resolvedPath($modalt) $target
}
lappend g_resolvedHash($res) $modalt
# register name alternative to know their existence
set ::g_moduleAltName($modalt) $mod
set ::g_moduleAltName($mod) $modalt
}
# if other modules were pointing to this one, adapt resolution end-point
set relmod_list {}
if {[info exists g_resolvedHash($mod)]} {
set relmod_list $g_resolvedHash($mod)
unset g_resolvedHash($mod)
}
# also adapt resolution for modules pointing to the alternative name
if {[info exists modalt] && [info exists g_resolvedHash($modalt)]} {
set relmod_list [concat $relmod_list $g_resolvedHash($modalt)]
unset g_resolvedHash($modalt)
}
foreach relmod $relmod_list {
set g_moduleResolved($relmod) $res
reportDebug "$relmod now resolved to $res"
lappend g_resolvedHash($res) $relmod
}
# register and propagate symbols to the resolution path
set sym_list [expr {[info exists g_symbolHash($mod)] ? $g_symbolHash($mod)\
: {}}]
if {$symver ne {}} {
# merge symbol definitions in case of alternative name
if {[info exists modalt] && [info exists g_symbolHash($modalt)]} {
set sym_list [lsort -dictionary -unique [concat $sym_list\
$g_symbolHash($modalt)]]
reportDebug "set symbols '$sym_list' to $mod and $modalt"
set g_symbolHash($mod) $sym_list
set g_symbolHash($modalt) $sym_list
}
# dictionary-sort symbols and remove eventual duplicates
set sym_list [lsort -dictionary -unique [concat $sym_list\
[list $symver]]]
# propagate symbols in g_symbolHash and g_moduleVersion toward the
# resolution path, handle that locally if we still work on same
# modulename, call for a proper resolution as soon as we change of
# module to get this new resolution registered
foreach modres $res_path {
lassign [getModuleNameVersion $modres] modfull modresname
if {$modname eq $modresname} {
if {[info exists g_symbolHash($modres)]} {
set modres_sym_list [lsort -dictionary -unique [concat\
$g_symbolHash($modres) $sym_list]]
} else {
set modres_sym_list $sym_list
}
# sync symbols of alternative name if any
if {[info exists ::g_moduleAltName($modres)]} {
set altmodres $::g_moduleAltName($modres)
reportDebug "set symbols '$modres_sym_list' to $modres and\
$altmodres"
set g_symbolHash($altmodres) $modres_sym_list
} else {
reportDebug "set symbols '$modres_sym_list' to $modres"
}
set g_symbolHash($modres) $modres_sym_list
# register symbolic version for querying in g_moduleVersion
foreach symelt $sym_list {
set modvers $modresname/$symelt
reportDebug "module-version $modvers = $modres"
set ::g_moduleVersion($modvers) $modres
set ::g_sourceVersion($modvers) $::ModulesCurrentModulefile
}
# as we change of module name a proper resolution call should be
# made (see below) and will handle the rest of the resolution path
} else {
set need_set_res 1
break
}
}
# when registering an alias, existing symbols on alias source name should
# be broadcast along the resolution path with a proper resolution call
# (see below)
} else {
lassign [getModuleNameVersion $target] modres modresname
set need_set_res 1
}
# resolution needed to broadcast symbols along resolution path without
# altering initial path already set for these symbols
if {[info exists need_set_res]} {
foreach symelt $sym_list {
set modvers $modresname/$symelt
reportDebug "set resolution for $modvers"
setModuleResolution $modvers $modres $symelt 0
}
}
return 1
}
# retrieve all names that resolve to passed mod
proc getAllModuleResolvedName {mod} {
set namelist {}
# get parent directories of mod
foreach modelt [split $mod /] {
if {[info exists modroot]} {
append modroot /
}
append modroot $modelt
lappend resmodlist $modroot
}
# add additionnaly all the altnames set on directories, parents of mod
# or on distant directories whose default version resolves to mod
for {set i 0} {$i < [llength $resmodlist]} {incr i 1} {
set modelt [lindex $resmodlist $i]
if {[info exists ::g_resolvedHash($modelt)]} {
foreach resmod $::g_resolvedHash($modelt) {
# if modelt is not a parent directory of mod, check its resolution
# points to mod (directly for alias/sym or indirectly for dir
# whose default version bridge resolution toward mod)
if {[string first $modelt/ $mod/] == 0 ||\
$::g_moduleResolved($resmod) eq $mod || [lindex\
[getPathToModule $::g_moduleResolved($resmod)] 1] eq $mod} {
appendNoDupToList namelist $resmod
unset modroot
foreach reselt [split [file dirname $resmod] /] {
if {[info exists modroot]} {
append modroot /
}
append modroot $reselt
appendNoDupToList resmodlist $modroot
}
}
}
}
}
return $namelist
}
# Specifies a default or alias version for a module that points to an
# existing module version Note that aliases defaults are stored by the
# short module name (not the full path) so aliases and defaults from one
# directory will apply to modules of the same name found in other
# directories.
proc module-version {args} {
reportDebug $args
lassign [getModuleNameVersion [lindex $args 0] 1] mod modname modversion
# go for registration only if valid modulename
if {$mod ne {}} {
foreach version [lrange $args 1 end] {
set aliasversion $modname/$version
# do not alter a previously defined alias version
if {![info exists ::g_moduleVersion($aliasversion)]} {
setModuleResolution $aliasversion $mod $version
} else {
reportWarning "Symbolic version '$aliasversion' already defined"
}
}
}
return {}
}
proc module-alias {args} {
lassign [getModuleNameVersion [lindex $args 0]] alias
lassign [getModuleNameVersion [lindex $args 1] 1] mod
reportDebug "$alias = $mod"
if {[setModuleResolution $alias $mod]} {
set ::g_moduleAlias($alias) $mod
set ::g_sourceAlias($alias) $::ModulesCurrentModulefile
}
return {}
}
proc module-virtual {args} {
lassign [getModuleNameVersion [lindex $args 0]] mod
set modfile [getAbsolutePath [lindex $args 1]]
reportDebug "$mod = $modfile"
set ::g_moduleVirtual($mod) $modfile
set ::g_sourceVirtual($mod) $::ModulesCurrentModulefile
return {}
}
proc module {command args} {
set mode [currentMode]
# resolve command if alias or shortcut name used
switch -regexp -- $command {
{^(add|lo)} {set command "load"}
{^(rm|del|unlo|remove$)} {set command "unload"}
{^(ref|rel)} {set command "reload"}
{^sw} {set command "switch"}
{^(di|show)} {set command "display"}
{^av} {set command "avail"}
{^al} {set command "aliases"}
{^li} {set command "list"}
{^wh} {set command "whatis"}
{^(apropos|keyword)$} {set command "search"}
{^pu} {set command "purge"}
{^init(a|lo)} {set command "initadd"}
{^initp} {set command "initprepend"}
{^initsw} {set command "initswitch"}
{^init(rm|unlo)$} {set command "initrm"}
{^initl} {set command "initlist"}
{^$} {
# default command is help if empty string supplied
set command help
# clear other args if no command name supplied
set args {}
}
}
reportDebug "cmd='$command', args='$args'"
# parse options, do that globally to ignore options not related to a given
# module sub-command (exclude them from arg list)
lassign [eval parseModuleCommandArgs $command $args] show_oneperline\
show_mtime show_filter search_filter dump_state args
# guess if called from top level
set topcall [expr {[getEvalModuleStackDepth] == 0}]
if {$topcall} {
set msgprefix {}
} else {
set msgprefix {module: }
# some commands can only be called from top level, not within modulefile
switch -- $command {
path - paths - autoinit - help - prepend-path - append-path -\
remove-path - is-loaded - is-saved - is-used - is-avail -\
info-loaded - clear - config {
set errormsg "${msgprefix}Command '$command' not supported"
}
}
}
# argument number check
switch -- $command {
unload - source - display - initadd - initprepend - initrm - test -\
is-avail {
if {[llength $args] == 0} {
set argnberr 1
}
}
reload - aliases - list - purge - savelist - initlist - initclear -\
autoinit {
if {[llength $args] != 0} {
set argnberr 1
}
}
switch {
if {[llength $args] == 0 || [llength $args] > 2} {
set argnberr 1
}
}
path - paths - info-loaded {
if {[llength $args] != 1} {
set argnberr 1
}
}
search - save - restore - saverm - saveshow - clear {
if {[llength $args] > 1} {
set argnberr 1
}
}
initswitch {
if {[llength $args] != 2} {
set argnberr 1
}
}
prepend-path - append-path - remove-path {
if {[llength $args] < 2} {
set argnberr 1
}
}
config {
if {[llength $args] > 2} {
set argnberr 1
}
}
}
if {[info exists argnberr]} {
set errormsg "Unexpected number of args for '$command' command"
}
# skip command processing if error already spotted
if {![info exists errormsg]} {
# define if modfile should always be fully read even for validity check
pushAlwaysReadFullFile [expr {[isInList [list path paths list avail\
aliases] $command]} ? 0 : 1]
pushCommandName $command
if {$topcall} {
# Find and execute any global rc file found
runModulerc
}
switch -- $command {
load {
# ignore flag used in collection to track non-user asked state
set args [replaceFromList $args --notuasked]
# no error raised on empty argument list to cope with
# initadd command that may expect this behavior
if {[llength $args] > 0} {
set ret 0
# if top command is source, consider module load commands made
# within sourced file evaluation as top load command
if {$topcall || ([getEvalModuleStackDepth] == 1 && (
[aboveCommandName] eq {source} || [aboveCommandName] eq\
{autoinit}))} {
set ret [eval cmdModuleLoad load 1 $args]
} elseif {$mode eq {load}} {
# load here if no auto mode, done through prereq elsewhere
# inhibited if currently in DepRe context
if {![getAutoHandling] && [currentModuleEvalContext] ne\
{depre} && ![eval is-loaded $args] && ![eval is-loading\
$args]} {
set ret [eval cmdModuleLoad reqlo 0 $args]
# ignore obtained error if force mode enabled
if {[getForce]} {
set ret 0
}
}
# register modulefiles to load as individual prereqs
foreach arg $args {
prereq $arg
}
# mods unload is handled via UReqUn mechanism when auto enabled
} elseif {![getAutoHandling]} {
# on unload mode, unload mods in reverse order, if loaded
# prior this mod, if not user asked and not required by
# other loaded mods
set modlist [getLoadedModuleList]
set modidx [lsearch -exact $modlist [currentModuleName]]
if {$modidx != 0} {
set priormodlist [lrange $modlist 0 $modidx]
foreach arg [lreverse $args] {
if {[set unmod [getLoadedMatchingName $arg returnlast\
0 $priormodlist]] ne {}} {
if {[cmdModuleUnload urequn match 1 0 1 1\
$unmod]} {
reportWarning "Unload of useless requirement\
$unmod failed" 1
}
}
}
}
}
# sub-module interpretation failed, raise error
if {$ret && !$topcall} {
set errormsg SUB_FAILED
}
}
}
unload {
# if top command is source, consider module load commands made
# within sourced file evaluation as top load command
if {$topcall || ([getEvalModuleStackDepth] == 1 && (
[aboveCommandName] eq {source} || [aboveCommandName] eq\
{autoinit}))} {
set ret [eval cmdModuleUnload unload match 1 0 0 0 $args]
} elseif {$mode eq {load}} {
# unload mods only on load mode, nothing done on unload mode as
# the registered conflict guarantees the target module cannot
# be loaded unless forced
# do not unload module required by others even in force mode
set ret [eval cmdModuleUnload conun match 0 0 0 1 $args]
# register modulefiles to unload as individual conflicts
foreach arg $args {
# do not break on error yet, go through the whole modfile
# evaluation in case conflict is solved later on
catch {conflict $arg}
}
# sub-module interpretation failed, raise error
if {$ret} {
set errormsg SUB_FAILED
}
}
}
reload {
cmdModuleReload
}
use {
if {$topcall || $mode eq {load}} {
eval cmdModuleUse $args
} else {
eval cmdModuleUnuse $args
}
}
unuse {
eval cmdModuleUnuse $args
}
source {
if {$topcall || $mode eq {load}} {
eval cmdModuleSource $args
} else {
# on unload mode, unsource script in reverse order
eval cmdModuleUnsource [lreverse $args]
}
}
switch {
# pass 'user asked state' to switch procedure
set uasked [expr {$topcall || ([getEvalModuleStackDepth] == 1 &&\
([aboveCommandName] eq {source} || [aboveCommandName] eq\
{autoinit}))}]
if {$uasked} {
eval cmdModuleSwitch $uasked $args
} else {
# CAUTION: it is not recommended to use the `switch`
# sub-command in modulefiles as this command is intended for
# the command-line for a 2in1 operation. Could be removed from
# the modulefile scope in a future release. Use `module unload`
# and `module load` commands in modulefiles instead.
switch -- $mode {
load {
eval cmdModuleSwitch $uasked $args
}
unload {
# find what has been asked for unload and load
lassign $args swunmod swlomod
if {$swlomod eq {} && $swunmod ne {}} {
set swlomod $swunmod
}
# apply same mechanisms than for 'module load' and
# 'module unload' for an unload evaluation: nothing done
# for switched-off module and unload of switched-on
# module. If auto handling is enabled switched-on module
# is handled via UReqUn mechanism
if {![getAutoHandling] && $swlomod ne {}} {
# unload mod if it was loaded prior this mod, not user
# asked and not required by another loaded module
set modlist [getLoadedModuleList]
set modidx [lsearch -exact $modlist [currentModuleName]]
if {$modidx != 0} {
set priormodlist [lrange $modlist 0 $modidx]
if {[set unmod [getLoadedMatchingName $swlomod\
returnlast 0 $priormodlist]] ne {}} {
if {[cmdModuleUnload urequn match 1 0 1 1\
$unmod]} {
reportWarning "Unload of useless requirement\
$unmod failed" 1
}
}
}
}
}
}
}
}
display {
eval cmdModuleDisplay $args
}
avail {
eval cmdModuleAvail $show_oneperline $show_mtime "{$show_filter}"\
"{$search_filter}" $args
}
aliases {
cmdModuleAliases
}
path {
eval cmdModulePath $args
}
paths {
eval cmdModulePaths $args
}
list {
cmdModuleList $show_oneperline $show_mtime
}
whatis {
if {$args ne {}} {
foreach arg $args {
cmdModuleWhatIs $arg
}
} else {
cmdModuleWhatIs
}
}
search {
eval cmdModuleApropos $args
}
purge {
eval cmdModulePurge
}
save {
eval cmdModuleSave $args
}
restore {
eval cmdModuleRestore $args
}
saverm {
eval cmdModuleSaverm $args
}
saveshow {
eval cmdModuleSaveshow $args
}
savelist {
cmdModuleSavelist $show_oneperline $show_mtime
}
initadd {
eval cmdModuleInit add $args
}
initprepend {
eval cmdModuleInit prepend $args
}
initswitch {
eval cmdModuleInit switch $args
}
initrm {
eval cmdModuleInit rm $args
}
initlist {
eval cmdModuleInit list $args
}
initclear {
eval cmdModuleInit clear $args
}
autoinit {
cmdModuleAutoinit
}
clear {
# ensure empty string is correctly passed
eval cmdModuleClear "{$args}"
}
config {
eval cmdModuleConfig $dump_state $args
}
help {
eval cmdModuleHelp $args
}
test {
eval cmdModuleTest $args
}
prepend-path - append-path - remove-path - is-loaded - is-saved -\
is-used - is-avail {
eval cmdModuleResurface $command $args
}
info-loaded {
eval cmdModuleResurface module-info loaded $args
}
default {
set errormsg "${msgprefix}Invalid command '$command'"
}
}
popCommandName
popAlwaysReadFullFile
}
# if an error need to be raised, proceed differently depending of
# call level: if called from top level render errors then raise error
# elsewhere call is made from a modulefile or modulerc and error
# will be managed from execute-modulefile or execute-modulerc
if {[info exists errormsg]} {
if {$topcall} {
reportErrorAndExit "$errormsg\nTry 'module --help'\
for more information."
} else {
error $errormsg
}
# if called from top level render settings if any
} elseif {$topcall} {
renderSettings
}
return {}
}
proc getModshareVarName {var} {
# specific modshare variable for DYLD-related variables as a suffixed
# variable will lead to warning messages with this tool
if {[string range $var 0 4] eq {DYLD_}} {
return MODULES_MODSHARE_${var}
} else {
return ${var}_modshare
}
}
proc setenv {var val} {
reportDebug "var='$var', val='$val'"
# clean any previously defined reference counter array
unset-env [getModshareVarName $var] 1
# Set the variable for later use during the modulefile evaluation
set-env $var $val
return {}
}
# undo setenv in unload mode
proc setenv-un {var val} {
reportDebug "var='$var', val='$val'"
# clean any existing reference counter array
unset-env [getModshareVarName $var] 1
# Add variable to the list of variable to unset in shell output code but
# set it in interp context as done on load mode for later use during the
# modulefile evaluation
unset-env $var 0 $val
return {}
}
# optimized setenv/unsetenv for whatis mode: init env variable with an empty
# value if undefined. do not care about value, just avoid variable to be
# undefined for later use during the modulefile evaluation
proc setenv-wh {var args} {
if {![info exists ::env($var)]} {
reportDebug "var='$var', val=''"
set ::env($var) {}
}
return {}
}
proc getenv {var {valifundef _UNDEFINED_}} {
reportDebug "var='$var', valifundef='$valifundef'"
return [expr {[currentMode] ne {display} ? [get-env $var $valifundef] :\
"\$$var"}]
}
proc unsetenv {var {val {}}} {
reportDebug "var='$var', val='$val'"
# clean any existing reference counter array
unset-env [getModshareVarName $var] 1
# Set the variable for later use during the modulefile evaluation
unset-env $var
return {}
}
# undo unsetenv in unload mode
proc unsetenv-un {var {val {}}} {
if {$val ne {}} {
return [setenv $var $val]
} else {
return [unsetenv $var]
}
}
proc chdir {dir} {
reportDebug $dir
if {[file exists $dir] && [file isdirectory $dir]} {
set ::g_changeDir $dir
} else {
# report issue but does not treat it as an error to have the
# same behavior as C-version
reportWarning "Cannot chdir to '$dir' for '[currentModuleName]'"
}
return {}
}
# superseed exit command to handle it if called within a modulefile
# rather than exiting the whole process
proc exitModfileCmd {{code 0}} {
set mode [currentMode]
reportDebug ($code)
if {$mode eq {load}} {
reportDebug {Inhibit next modulefile interpretations}
set ::g_inhibit_interp 1
}
# break to gently end interpretation of current modulefile
return -code break
}
# enables slave interp to return ModulesVersion value to the master interp
proc setModulesVersion {val} {
set ::ModulesVersion $val
}
# supersede puts command to catch content sent to stdout/stderr within
# modulefile in order to correctly send stderr content (if a pager has been
# enabled) or postpone content channel send after rendering on stdout the
# relative environment changes required by the modulefile
proc putsModfileCmd {itrp args} {
reportDebug "$args (itrp=$itrp)"
# determine if puts call targets the stdout or stderr channel
switch -- [llength $args] {
1 {
set deferPuts 1
}
2 {
switch -- [lindex $args 0] {
-nonewline - stdout {
set deferPuts 1
}
stderr {
set reportArgs [list [lindex $args 1]]
}
}
}
3 {
if {[lindex $args 0] eq {-nonewline}} {
switch -- [lindex $args 1] {
stdout {
set deferPuts 1
}
stderr {
set reportArgs [list [lindex $args 2] 1]
}
}
} else {
set wrongNumArgs 1
}
}
default {
set wrongNumArgs 1
}
}
# raise error if bad argument number detected, do this here rather in _puts
# not to confuse people with an error reported by an internal name (_puts)
if {[info exists wrongNumArgs]} {
error {wrong # args: should be "puts ?-nonewline? ?channelId? string"}
# defer puts if it targets stdout (see renderSettings)
} elseif {[info exists deferPuts]} {
lappend ::g_stdoutPuts $args
# if it targets stderr call report, which knows what channel to use
} elseif {[info exists reportArgs]} {
eval report $reportArgs
# pass to real puts command if not related to stdout and do that in modfile
# interpreter context to get access to eventual specific channel
} else {
$itrp eval _puts $args
}
}
########################################################################
# path fiddling
#
proc getReferenceCountArray {var separator} {
# get reference counter set in environment
set sharevar [getModshareVarName $var]
array set refcount {}
if {[info exists ::env($sharevar)]} {
set modsharelist [psplit $::env($sharevar) [getPathSeparator]]
# ignore environment ref count variable if malformed
if {([llength $modsharelist] % 2) == 0} {
array set refcount $modsharelist
} else {
reportDebug "Reference counter value in '$sharevar' is malformed\
($modsharelist)"
}
}
array set countarr {}
if {[info exists ::env($var)]} {
# do not skip a bare empty path entry that can also be found in
# reference counter array (sometimes var is cleared by setting it
# empty not unsetting it, ignore var in this case)
if {$::env($var) eq {} && [info exists refcount()]} {
lappend eltlist {}
} else {
set eltlist [split $::env($var) $separator]
}
# just go thought the elements of the variable, which means additional
# elements part of the reference counter variable will be ignored
foreach elt $eltlist {
# no reference counter, means value has been set once
if {![info exists refcount($elt)]} {
set count 1
# bad reference counter value is ignored
} elseif {![string is digit -strict $refcount($elt)]} {
reportDebug "Reference counter value for '$elt' in '$sharevar' is\
erroneous ($refcount($elt))"
set count 1
} else {
set count $refcount($elt)
}
set countarr($elt) $count
}
}
set count_list [array get countarr]
reportDebug "(var=$var, delim=$separator) got '$count_list'"
return $count_list
}
proc unload-path {args} {
reportDebug ($args)
lassign [eval parsePathCommandArgs unload-path $args] separator\
allow_dup idx_val var path_list
array set countarr [getReferenceCountArray $var $separator]
# Don't worry about dealing with this variable if it is already scheduled
# for deletion
if {[info exists ::g_stateEnvVars($var)] && $::g_stateEnvVars($var) eq\
{del}} {
return {}
}
# save initial variable content to match index arguments
set dir_list [split [get-env $var] $separator]
# detect if empty env value means empty path entry
if {[llength $dir_list] == 0 && [info exists countarr()]} {
lappend dir_list {}
}
# build list of index to remove from variable
set del_idx_list [list]
foreach dir $path_list {
# retrieve dir value if working on an index list
if {$idx_val} {
set idx $dir
# go to next index if this one is not part of the existing range
# needed to distinguish an empty value to an out-of-bound value
if {$idx < 0 || $idx >= [llength $dir_list]} {
continue
} else {
set dir [lindex $dir_list $idx]
}
}
# update reference counter array
if {[info exists countarr($dir)]} {
incr countarr($dir) -1
set newcount $countarr($dir)
if {$countarr($dir) <= 0} {
unset countarr($dir)
}
} else {
set newcount 0
}
# get all entry indexes corresponding to dir
set found_idx_list [lsearch -all -exact $dir_list $dir]
# remove all found entries
if {$newcount <= 0} {
# only remove passed position in --index mode
if {$idx_val} {
lappend del_idx_list $idx
} else {
set del_idx_list [concat $del_idx_list $found_idx_list]
}
# if multiple entries found remove the extra entries compared to new
# reference counter
} elseif {[llength $found_idx_list] > $newcount} {
# only remove passed position in --index mode
if {$idx_val} {
lappend del_idx_list $idx
} else {
# delete extra entries, starting from end of the list (on a path
# variable, entries at the end have less priority than those at
# the start)
set del_idx_list [concat $del_idx_list [lrange $found_idx_list\
$newcount end]]
}
}
}
# update variable if some element need to be removed
if {[llength $del_idx_list] > 0} {
set del_idx_list [lsort -integer -unique $del_idx_list]
set newpath [list]
set nbelem [llength $dir_list]
# rebuild list of element without indexes set for deletion
for {set i 0} {$i < $nbelem} {incr i} {
if {[notInList $del_idx_list $i]} {
lappend newpath [lindex $dir_list $i]
}
}
} else {
set newpath $dir_list
}
# set env variable and corresponding reference counter in any case
if {[llength $newpath] == 0} {
unset-env $var
} else {
set-env $var [join $newpath $separator]
}
set sharevar [getModshareVarName $var]
if {[array size countarr] > 0} {
set-env $sharevar [pjoin [array get countarr] [getPathSeparator]]
} else {
unset-env $sharevar 1
}
return {}
}
proc add-path {pos args} {
reportDebug "($args) pos=$pos"
lassign [eval parsePathCommandArgs add-path $args] separator allow_dup\
idx_val var path_list
set sharevar [getModshareVarName $var]
array set countarr [getReferenceCountArray $var $separator]
if {$pos eq {prepend}} {
set path_list [lreverse $path_list]
}
set val [get-env $var]
foreach dir $path_list {
if {![info exists countarr($dir)] || $allow_dup} {
# ignore env var set empty if no empty entry found in reference
# counter array (sometimes var is cleared by setting it empty not
# unsetting it)
if {$val ne {} || [info exists countarr()]} {
set val [expr {$pos eq {prepend} ? "$dir$separator$val" :\
"$val$separator$dir"}]
} else {
set val $dir
}
}
if {[info exists countarr($dir)]} {
incr countarr($dir)
} else {
set countarr($dir) 1
}
}
set-env $var $val
set-env $sharevar [pjoin [array get countarr] [getPathSeparator]]
return {}
}
# analyze argument list passed to a path command to set default value or raise
# error in case some attributes are missing
proc parsePathCommandArgs {cmd args} {
# parse argument list
set next_is_delim 0
set allow_dup 0
set idx_val 0
foreach arg $args {
switch -glob -- $arg {
--index {
if {$cmd eq {add-path}} {
reportWarning "--index option has no effect on $cmd"
} else {
set idx_val 1
}
}
--duplicates {
if {$cmd eq {unload-path}} {
reportWarning "--duplicates option has no effect on $cmd"
} else {
set allow_dup 1
}
}
-d - -delim - --delim {
set next_is_delim 1
}
--delim=* {
set delim [string range $arg 8 end]
}
-* {
error "invalid option '$arg' for $cmd"
}
default {
if {$next_is_delim} {
set delim $arg
set next_is_delim 0
} elseif {![info exists var]} {
set var $arg
} else {
# set multiple passed values in a list
lappend val_raw_list $arg
}
}
}
}
# adapt with default value or raise error if some arguments are missing
if {![info exists delim]} {
set delim [getPathSeparator]
} elseif {$delim eq {}} {
error "$cmd should get a non-empty path delimiter"
}
if {![info exists var]} {
error "$cmd should get an environment variable name"
} elseif {$var eq {}} {
error "$cmd should get a valid environment variable name"
}
if {![info exists val_raw_list]} {
error "$cmd should get a value for environment variable $var"
}
# set list of value to add
set val_list [list]
foreach val $val_raw_list {
# check passed indexes are numbers
if {$idx_val && ![string is integer -strict $val]} {
error "$cmd should get valid number as index value"
}
switch -- $val \
{} {
# add empty entry in list
lappend val_list {}
} \
$delim {
error "$cmd cannot handle path equals to separator string"
} \
default {
# split passed value with delimiter
set val_list [concat $val_list [split $val $delim]]
}
}
reportDebug "(delim=$delim, allow_dup=$allow_dup, idx_val=$idx_val,\
var=$var, val=$val_list, nbval=[llength $val_list])"
return [list $delim $allow_dup $idx_val $var $val_list]
}
proc prepend-path {args} {
reportDebug $args
# Set the variable for later use during the modulefile evaluation
eval add-path prepend $args
return {}
}
proc append-path {args} {
reportDebug $args
# Set the variable for later use during the modulefile evaluation
eval add-path append $args
return {}
}
proc remove-path {args} {
reportDebug $args
# Set the variable for later use during the modulefile evaluation
eval unload-path $args
return {}
}
# undo remove-path in unload mode
proc remove-path-un {args} {
# clear variable if it does not exist on unload mode for later use
# during the modulefile evaluation
lassign [eval parsePathCommandArgs unload-path $args] separator\
allow_dup idx_val var path_list
if {![info exists ::env($var)]} {
reset-to-unset-env $var
}
}
# optimized *-path for whatis mode: init env variable with an empty value if
# undefined. do not care about value, just avoid variable to be undefined for
# later use during the modulefile evaluation
proc edit-path-wh {args} {
reportDebug $args
# get variable name
lassign [eval parsePathCommandArgs edit-path-wh $args] separator\
allow_dup idx_val var path_list
if {![info exists ::env($var)]} {
set ::env($var) {}
}
return {}
}
proc set-alias {alias what} {
reportDebug "alias='$alias', val='$what'"
set ::g_Aliases($alias) $what
set ::g_stateAliases($alias) new
return {}
}
# undo set-alias in unload mode
proc set-alias-un {alias what} {
return [unset-alias $alias]
}
proc unset-alias {alias} {
reportDebug alias='$alias'
set ::g_Aliases($alias) {}
set ::g_stateAliases($alias) del
return {}
}
proc set-function {function what} {
reportDebug "function='$function', val='$what'"
set ::g_Functions($function) $what
set ::g_stateFunctions($function) new
return {}
}
# undo set-function in unload mode
proc set-function-un {function what} {
return [unset-function $function]
}
proc unset-function {function} {
reportDebug function='$function'
set ::g_Functions($function) {}
set ::g_stateFunctions($function) del
return {}
}
proc is-loaded {args} {
reportDebug $args
foreach mod $args {
if {[getLoadedMatchingName $mod returnfirst] ne {}} {
return 1
}
}
# is something loaded whatever it is?
return [expr {[llength $args] == 0 && [llength [getLoadedModuleList]] > 0}]
}
proc is-loading {args} {
reportDebug $args
foreach mod $args {
if {[getLoadedMatchingName $mod returnfirst 1] ne {}} {
return 1
}
}
# is something else loading whatever it is?
return [expr {[llength $args] == 0 && [llength [getLoadingModuleList]] >1}]
}
proc conflict {args} {
reportDebug $args
set currentModule [currentModuleName]
# register conflict list
eval setLoadedConflict $currentModule $args
foreach mod $args {
# if the conflict module is loading and it does not correspond to
# currently evaluated module, we cannot proceed
set isloading [expr {![doesModuleMatchesName $currentModule $mod] &&\
[is-loading $mod]}]
# if the conflicting module is loaded, we cannot either
if {[is-loaded $mod] || $isloading} {
set retisconun [eval isModuleEvaluated conun $mod]
# report message on currently evaluated module message block
if {![set retiseval [eval isModuleEvaluated any $mod]] ||\
[currentMsgRecordId] ne [topMsgRecordId] || !$retisconun} {
# more appropriate msg if an eval was attempted or is by-passed
set msg [expr {$retiseval || [getForce] ? [getConIsLoadedMsg\
$mod $isloading] : [getErrConflictMsg $currentModule $mod]}]
# still proceed if force mode enabled
if {[getForce]} {
reportWarning $msg
# indicate message has already been reported
lappend ::report_conflict($currentModule) $mod
} else {
error "GLOBALERR $msg"
}
}
}
}
return {}
}
proc prereq {args} {
reportDebug $args
set currentModule [currentModuleName]
# register prereq list (sets of optional prereq are registered as list)
setLoadedPrereq $currentModule $args
# if dependency resolving is enabled try to load prereq
if {[getAutoHandling] && ![eval is-loaded $args] && ![eval is-loading\
$args]} {
set imax [llength $args]
set prereqloaded 0
# if prereq list specified, try to load first then
# try next if load of first module not successful
for {set i 0} {$i<$imax && $prereqloaded==0} {incr i 1} {
set arg [lindex $args $i]
# hold output of each evaluation until they are all done to drop
# those that failed if one succeed
set curholdid load-$i-$arg
pushReportHoldId $curholdid
if {[catch {cmdModuleLoad reqlo 0 $arg} errorMsg]} {
# if an error is raised, release output and rethrow the error
# (could be raised if no modulepath defined for instance)
popReportHoldId
lappend holdidlist $curholdid report
eval releaseHeldReport $holdidlist
error $errorMsg
}
popReportHoldId
if {[is-loaded $arg]} {
set prereqloaded 1
# set previous reports to be dropped as this one succeed
if {[info exists holdidlist]} {
foreach {holdid action} $holdidlist {
lappend newholdidlist $holdid drop
}
set holdidlist $newholdidlist
}
}
lappend holdidlist $curholdid report
}
# output held messages
eval releaseHeldReport $holdidlist
}
if {![eval is-loaded $args] && ![eval is-loading $args]} {
set retisreqlo [eval isModuleEvaluated reqlo $args]
# report message on currently evaluated module message block
if {![set retiseval [eval isModuleEvaluated any $args]] ||\
[currentMsgRecordId] ne [topMsgRecordId] || !$retisreqlo} {
# more appropriate msg if an evaluation was attempted or is by-passed
set msg [expr {$retiseval || [getForce] ? [getReqNotLoadedMsg $args]\
: [getErrPrereqMsg $currentModule $args]}]
# still proceed if force mode enabled
if {[getForce]} {
reportWarning $msg
# no error raise if done later
} elseif {$retisreqlo} {
reportError $msg
} else {
error "GLOBALERR $msg"
}
}
# raise reqlo-specific msg to top level if attempted
if {$retisreqlo} {
set msg [getErrReqLoMsg $args]
if {[getForce]} {
reportWarning $msg 1
} else {
error "GLOBALERRTOP $msg"
}
}
}
return {}
}
proc x-resource {resource {value {}}} {
reportDebug "($resource, $value)"
# sometimes x-resource value may be provided within resource name
# as the "x-resource {Ileaf.popup.saveUnder: True}" example provided
# in manpage. so here is an attempt to extract real resource name and
# value from resource argument
if {[string length $value] == 0 && ![file exists $resource]} {
# look first for a space character as delimiter, then for a colon
set sepapos [string first { } $resource]
if { $sepapos == -1 } {
set sepapos [string first : $resource]
}
if { $sepapos > -1 } {
set value [string range $resource [expr {$sepapos + 1}] end]
set resource [string range $resource 0 [expr {$sepapos - 1}]]
reportDebug "corrected ($resource, $value)"
} else {
# if not a file and no value provided x-resource cannot be
# recorded as it will produce an error when passed to xrdb
reportWarning "x-resource $resource is not a valid string or file"
return {}
}
}
# check current environment can handle X11 resource edition elsewhere exit
if {[catch {runCommand xrdb -query} errMsg]} {
if {[string range $errMsg 0 9] eq {GLOBALERR }} {
set errMsg [string range $errMsg 10 end]
}
error "GLOBALERR X11 resources cannot be edited, issue spotted\n[sgr er\
ERROR]: $errMsg"
}
# if a resource does hold an empty value in g_newXResources or
# g_delXResources arrays, it means this is a resource file to parse
if {[currentMode] eq {load}} {
set ::g_newXResources($resource) $value
} else {
set ::g_delXResources($resource) $value
}
return {}
}
proc uname {what} {
set result {}
reportDebug $what
if {! [info exists ::unameCache($what)]} {
switch -- $what {
sysname {
set result $::tcl_platform(os)
}
machine {
set result $::tcl_platform(machine)
}
nodename - node {
set result [runCommand uname -n]
}
release {
set result $::tcl_platform(osVersion)
}
domain {
set result [runCommand domainname]
}
version {
set result [runCommand uname -v]
}
default {
error "uname $what not supported"
}
}
set ::unameCache($what) $result
}
return $::unameCache($what)
}
# run shell command
proc system {args} {
reportDebug $args
set mode [currentMode]
set status {}
switch -- $mode {
load - unload {
# run through the appropriate shell
if {[isWin]} {
set shell cmd.exe
set shellarg /c
} else {
set shell /bin/sh
set shellarg -c
}
if {[catch {exec >&@stderr $shell $shellarg [join $args]}]} {
# non-zero exit status, get it:
set status [lindex $::errorCode 2]
} else {
# exit status was 0
set status 0
}
}
}
return $status
}
# test at least one of the collections passed as argument exists
proc is-saved {args} {
reportDebug $args
foreach coll $args {
lassign [getCollectionFilename $coll] collfile colldesc
if {[file exists $collfile]} {
return 1
}
}
# is something saved whatever it is?
return [expr {[llength $args] == 0 && [llength [findCollections]] > 0}]
}
# test at least one of the directories passed as argument is set in MODULEPATH
proc is-used {args} {
reportDebug $args
set modpathlist [getModulePathList]
foreach path $args {
# transform given path in an absolute path to compare with dirs
# registered in the MODULEPATH env var which are returned absolute.
set abspath [getAbsolutePath $path]
if {[isInList $modpathlist $abspath]} {
return 1
}
}
# is something used whatever it is?
return [expr {[llength $args] == 0 && [llength $modpathlist] > 0}]
}
# test at least one of the modulefiles passed as argument exists
proc is-avail {args} {
reportDebug $args
set ret 0
# disable error reporting to avoid modulefile errors
# to pollute result. Only if not already inhibited
set alreadyinhibit [isErrorReportInhibited]
if {!$alreadyinhibit} {
inhibitErrorReport
}
foreach mod $args {
lassign [getPathToModule $mod] modfile modname
if {$modfile ne {}} {
set ret 1
break
}
}
# re-enable only is it was disabled from this procedure
if {!$alreadyinhibit} {
reenableErrorReport
}
return $ret
}
########################################################################
# internal module procedures
#
set g_modeStack {}
proc currentMode {} {
return [lindex $::g_modeStack end]
}
proc pushMode {mode} {
lappend ::g_modeStack $mode
}
proc popMode {} {
set ::g_modeStack [lrange $::g_modeStack 0 end-1]
}
set g_moduleNameStack {}
proc currentModuleName {} {
return [lindex $::g_moduleNameStack end]
}
proc pushModuleName {moduleName} {
lappend ::g_moduleNameStack $moduleName
}
proc popModuleName {} {
set ::g_moduleNameStack [lrange $::g_moduleNameStack 0 end-1]
}
# get number of either modulefile/modulerc currently being evaluated
proc getEvalModuleStackDepth {} {
return [llength $::g_moduleNameStack]
}
set g_moduleFileStack {}
proc pushModuleFile {modfile} {
lappend ::g_moduleFileStack $modfile
set ::ModulesCurrentModulefile $modfile
}
proc popModuleFile {} {
set ::g_moduleFileStack [lrange $::g_moduleFileStack 0 end-1]
set ::ModulesCurrentModulefile [lindex $::g_moduleFileStack end]
}
set g_specifiedNameStack {}
proc currentSpecifiedName {} {
return [lindex $::g_specifiedNameStack end]
}
proc pushSpecifiedName {specifiedName} {
lappend ::g_specifiedNameStack $specifiedName
}
proc popSpecifiedName {} {
set ::g_specifiedNameStack [lrange $::g_specifiedNameStack 0 end-1]
}
set g_commandNameStack {}
proc currentCommandName {} {
return [lindex $::g_commandNameStack end]
}
proc aboveCommandName {} {
return [lindex $::g_commandNameStack end-1]
}
proc pushCommandName {commandName} {
lappend ::g_commandNameStack $commandName
}
proc popCommandName {} {
set ::g_commandNameStack [lrange $::g_commandNameStack 0 end-1]
}
# stack of report holder unique identifiers
set g_reportHoldIdStack {}
proc isReportHeld {} {
return [expr {[llength $::g_reportHoldIdStack] > 0}]
}
proc currentReportHoldId {} {
return [lindex $::g_reportHoldIdStack end]
}
proc pushReportHoldId {holdid} {
lappend ::g_reportHoldIdStack $holdid
}
proc popReportHoldId {} {
set ::g_reportHoldIdStack [lrange $::g_reportHoldIdStack 0 end-1]
}
# stack of message recording unique identifiers
set g_msgRecordIdStack {}
proc currentMsgRecordId {} {
return [lindex $::g_msgRecordIdStack end]
}
proc topMsgRecordId {} {
return [lindex $::g_msgRecordIdStack 0]
}
proc pushMsgRecordId {recid} {
lappend ::g_msgRecordIdStack $recid
}
proc popMsgRecordId {} {
set ::g_msgRecordIdStack [lrange $::g_msgRecordIdStack 0 end-1]
}
# stack of prefixes clarifying debug message entries
set g_debugMsgPrefixStack {}
proc currentDebugMsgPrefix {} {
return [lindex $::g_debugMsgPrefixStack end]
}
proc pushDebugMsgPrefix {args} {
lappend ::g_debugMsgPrefixStack "\[#[join $args :]\] "
}
proc popDebugMsgPrefix {} {
set ::g_debugMsgPrefixStack [lrange $::g_debugMsgPrefixStack 0 end-1]
}
# gather for the current top evaluation the information on all evaluations
# happening under its umbrella
proc registerModuleEval {context mod {unset 0} {failedcontext {}}} {
set recid [topMsgRecordId]
set contextset 0
# add mod to existing evaluation context list
if {[info exists ::g_moduleEval($recid)]} {
for {set i 0} {$i < [llength $::g_moduleEval($recid)]} {incr i 1} {
set contextevallist [lindex $::g_moduleEval($recid) $i]
if {[lindex $contextevallist 0] eq $context} {
if {$unset} {
set contextevallist [replaceFromList $contextevallist $mod]
} else {
lappend contextevallist $mod
}
set ::g_moduleEval($recid) [expr {[llength $contextevallist] > 1\
? [lreplace $::g_moduleEval($recid) $i $i $contextevallist]\
: [lreplace $::g_moduleEval($recid) $i $i]}]
set contextset 1
break
}
}
}
# add mod to new evaluation context list
if {!$unset && !$contextset} {
lappend ::g_moduleEval($recid) [list $context $mod]
}
# add mod to failed evaluation list
if {$unset} {
lappend ::g_moduleFailedEval($recid) $failedcontext $mod
}
}
# get context of currently evaluated module
proc currentModuleEvalContext {} {
return [lindex $::g_moduleEvalAttempt([currentModuleName]) end]
}
# record module evaluation attempt and corresponding context
proc registerModuleEvalAttempt {context mod} {
appendNoDupToList ::g_moduleEvalAttempt($mod) $context
}
proc unregisterModuleEvalAttempt {context mod} {
set ::g_moduleEvalAttempt($mod) [replaceFromList\
$::g_moduleEvalAttempt($mod) $context]
}
# is at least one module passed as argument evaluated in passed context
proc isModuleEvaluated {context args} {
set ret 0
foreach mod $args {
# get actual module name if mod refers to an alias or symver
set modres [resolveModuleVersionOrAlias $mod]
if {[info exists ::g_moduleEvalAttempt($modres)] && ($context eq\
{any} || [isInList $::g_moduleEvalAttempt($modres) $context])} {
set ret 1
break
}
}
return $ret
}
# was passed mod already evaluated for context and failed
proc isModuleEvalFailed {context mod} {
set ret 0
set recid [topMsgRecordId]
if {[info exists ::g_moduleFailedEval($recid)]} {
foreach {curcon curmod} $::g_moduleFailedEval($recid) {
if {$context eq $curcon && $mod eq $curmod} {
set ret 1
break
}
}
}
return $ret
}
# stack of flag defining whether a modfile should be always fully read or not
# even for validity check, which is useful in case a file need to be read
# multiple times as a full read will make file content cached thus file will
# be read only once
set g_alwaysReadFullFileStack {}
proc currentAlwaysReadFullFile {} {
return [lindex $::g_alwaysReadFullFileStack end]
}
proc pushAlwaysReadFullFile {alwaysReadFullFile} {
lappend ::g_alwaysReadFullFileStack $alwaysReadFullFile
}
proc popAlwaysReadFullFile {} {
set ::g_alwaysReadFullFileStack [lrange $::g_alwaysReadFullFileStack 0\
end-1]
}
# return list of currently loading modules in stack
proc getLoadingModuleList {} {
set modlist [list]
for {set i 0} {$i < [llength $::g_moduleNameStack]} {incr i 1} {
if {[lindex $::g_modeStack $i] eq {load}} {
lappend modlist [lindex $::g_moduleNameStack $i]
}
}
return $modlist
}
# return list of currently loading modulefiles in stack
proc getLoadingModuleFileList {} {
set modlist [list]
for {set i 0} {$i < [llength $::g_moduleFileStack]} {incr i 1} {
if {[lindex $::g_modeStack $i] eq {load}} {
lappend modlist [lindex $::g_moduleFileStack $i]
}
}
return $modlist
}
# return list of currently unloading modules in stack
proc getUnloadingModuleList {} {
set modlist [list]
for {set i 0} {$i < [llength $::g_moduleNameStack]} {incr i 1} {
if {[lindex $::g_modeStack $i] eq {unload}} {
lappend modlist [lindex $::g_moduleNameStack $i]
}
}
return $modlist
}
# return list of loaded modules by parsing LOADEDMODULES env variable
proc getLoadedModuleList {{filter_empty 1}} {
set modlist [list]
foreach mod [split [get-env LOADEDMODULES] [getPathSeparator]] {
# ignore empty element
if {$mod ne {} || !$filter_empty} {
lappend modlist $mod
}
}
return $modlist
}
# return list of loaded module files by parsing _LMFILES_ env variable
proc getLoadedModuleFileList {} {
set modfilelist [list]
foreach modfile [split [get-env _LMFILES_] [getPathSeparator]] {
# ignore empty element
if {$modfile ne {}} {
lappend modfilelist $modfile
}
}
return $modfilelist
}
# return list of loaded module declared conflict by parsing MODULES_LMCONFLICT
proc getLoadedModuleConflictList {} {
set modconlist [list]
foreach modconser [split [get-env MODULES_LMCONFLICT] [getPathSeparator]] {
set conlist [split $modconser $::g_sub1_separator]
# ignore empty element (1 is meaningless as first elt is loaded mod)
if {[llength $conlist] > 1} {
lappend modconlist $conlist
}
}
return $modconlist
}
# return list of loaded module declared prereq by parsing MODULES_LMPREREQ
proc getLoadedModulePrereqList {} {
set modprelist [list]
foreach modpreser [split [get-env MODULES_LMPREREQ] [getPathSeparator]] {
set prelist [split $modpreser $::g_sub1_separator]
# ignore empty element (1 is meaningless as first elt is loaded mod)
if {[llength $prelist] > 1} {
set modpre {}
foreach pre $prelist {
lappend modpre [split $pre $::g_sub2_separator]
}
lappend modprelist $modpre
}
}
return $modprelist
}
# return list of loaded module asked by user by parsing MODULES_LMNOTUASKED
proc getLoadedModuleNotUserAskedList {} {
set nuaskedlist [list]
foreach mod [split [get-env MODULES_LMNOTUASKED] [getPathSeparator]] {
# ignore empty element
if {$mod ne {}} {
lappend nuaskedlist $mod
}
}
return $nuaskedlist
}
# return list of loaded module declared altnames by parsing MODULES_LMALTNAME
proc getLoadedModuleAltnameList {} {
set modaltlist [list]
foreach modaltser [split [get-env MODULES_LMALTNAME] [getPathSeparator]] {
set altlist [split $modaltser $::g_sub1_separator]
# ignore empty element (1 is meaningless as first elt is loaded mod)
if {[llength $altlist] > 1} {
lappend modaltlist $altlist
}
}
return $modaltlist
}
# sort passed module list following both loaded and dependency orders
proc sortModulePerLoadedAndDepOrder {modlist {nporeq 0} {loading 0}} {
# sort per loaded order
set sortlist {}
if {[llength $modlist] > 0} {
foreach lmmod [getLoadedModuleList] {
if {[isInList $modlist $lmmod]} {
lappend sortlist $lmmod
}
}
# also sort eventual loading modules if asked
if {$loading} {
foreach loadingmod [lreverse [getLoadingModuleList]] {
if {[isInList $modlist $loadingmod]} {
lappend sortlist $loadingmod
}
}
}
}
# then refine sort with dependencies between loaded modules: a dependent
# module should be placed prior the loaded module requiring it
set reqListVar [expr {$nporeq ? {::g_moduleNPODepend} :\
{::g_moduleDepend}}]
set i 0
set imax [llength $sortlist]
while {$i < $imax} {
set mod [lindex $sortlist $i]
set jmin $imax
if {[info exists ${reqListVar}($mod)]} {
# goes over all dependend modules to find the first one in the loaded
# order list located after requiring mod
foreach lmmodlist [set ${reqListVar}($mod)] {
foreach lmmod $lmmodlist {
set j [lsearch -exact $sortlist $lmmod]
if {$j > $i && $j < $jmin} {
set jmin $j
set jminmod $lmmod
}
}
}
}
# move first dependent module found after currently inspected mod right
# before it
if {$jmin != $imax} {
set sortlist [linsert [lreplace $sortlist $jmin $jmin] $i $jminmod]
# or go to next element in list if current element has not been changed
} else {
incr i
}
}
return $sortlist
}
# return list of module paths by parsing MODULEPATH env variable
# behavior param enables to exit in error when no MODULEPATH env variable
# is set. by default an empty list is returned if no MODULEPATH set
# resolv_var param tells if environement variable references in path elements
# should be resolved or passed as-is in result list
# set_abs param applies an absolute path name convertion to path elements
# if enabled
proc getModulePathList {{behavior returnempty} {resolv_var 1} {set_abs 1}} {
if {[info exists ::env(MODULEPATH)]} {
set modpathlist [list]
foreach modpath [split $::env(MODULEPATH) [getPathSeparator]] {
# ignore empty element
if {$modpath ne {}} {
if {$resolv_var} {
set modpath [resolvStringWithEnv $modpath]
}
if {$set_abs} {
set modpath [getAbsolutePath $modpath]
}
appendNoDupToList modpathlist $modpath
}
}
return $modpathlist
} elseif {$behavior eq {exiterronundef}} {
reportErrorAndExit {No module path defined}
} else {
return {}
}
}
# test if two modules share the same root name
proc isSameModuleRoot {mod1 mod2} {
set mod1split [split $mod1 /]
set mod2split [split $mod2 /]
return [expr {[lindex $mod1split 0] eq [lindex $mod2split 0]}]
}
# test if one element in module name has a leading "dot" making this module
# a hidden module
proc isModuleHidden {mod} {
foreach elt [split $mod /] {
if {[string index $elt 0] eq {.}} {
return 1
}
}
return 0
}
# check if module name is specified as a full pathname (not a name relative
# to a modulepath)
proc isModuleFullPath {mod} {
return [regexp {^(|\.|\.\.)/} $mod]
}
# check if a module corresponds to a virtual module (module name
# does not corresponds to end of the modulefile name)
proc isModuleVirtual {mod modfile} {
return [expr {[string first $mod $modfile end-[string length $mod]] == -1}]
}
# Return the full pathname and modulename to the module.
# Resolve aliases and default versions if the module name is something like
# "name/version" or just "name" (find default version).
proc getPathToModule {mod {indir {}} {look_loaded no} {excdir {}}} {
reportDebug "finding '$mod' in '$indir' (excdir='$excdir')"
if {$mod eq {}} {
set retlist [list {} 0 none {Invalid empty module name}]
# try first to look at loaded modules if enabled to find maching module
# or to find a closest match (used when switching with single name arg)
} elseif {$look_loaded ne {no}} {
switch -- $look_loaded {
match {set getLoadedNameProc getLoadedMatchingName}
close {set getLoadedNameProc getLoadedWithClosestName}
}
set retlist [if {[set lm [$getLoadedNameProc $mod]] ne {}} {list\
[getModulefileFromLoadedModule $lm] $lm} {list {} $mod notloaded}]
# Check for $mod specified as a full pathname
} elseif {[isModuleFullPath $mod]} {
set mod [getAbsolutePath $mod]
# note that a raw filename as an argument returns the full
# path as the module name
lassign [checkValidModule $mod] check_valid check_msg
switch -- $check_valid {
true {
set retlist [list $mod $mod]
}
invalid - accesserr {
set retlist [list {} $mod $check_valid $check_msg $mod]
}
}
} else {
set dir_list [expr {$indir ne {} ? $indir : [getModulePathList\
exiterronundef]}]
# remove excluded directories (already searched)
foreach dir $excdir {
set dir_list [replaceFromList $dir_list $dir]
}
# modparent is the the modulename minus the module version.
lassign [getModuleNameVersion $mod] mod modparent modversion
set modroot [lindex [split $mod /] 0]
# determine if we need to get hidden modules
set fetch_hidden [isModuleHidden $mod]
# Now search for $mod in module paths
foreach dir $dir_list {
# get list of modules related to the root of searched module to get
# in one call a complete list of any module kind (file, alias, etc)
# related to search to be able to then determine in this proc the
# correct module to return without restarting new searches
array unset mod_list
array set mod_list [getModules $dir $modroot 0 rc_defs_included\
$fetch_hidden]
set prevmod {}
set mod_res {}
# loop to resolve correct modulefile in case specified mod is a
# directory that should be analyzed to get default mod in it
while {$prevmod ne $mod} {
set prevmod $mod
if {[info exists mod_list($mod)]} {
switch -- [lindex $mod_list($mod) 0] {
alias - version {
set newmod [resolveModuleVersionOrAlias $mod]
# continue search on newmod if module from same root and
# not hidden (if hidden search disabled) as mod_list
# already contains everything related to this root module
if {[isSameModuleRoot $mod $newmod] && ($fetch_hidden ||\
![isModuleHidden $newmod])} {
set mod $newmod
# indicate an alias or a symbol was solved
set mod_res $newmod
# elsewhere restart search on new modulename, constrained
# to specified dir if set
} else {
return [getPathToModule $newmod $indir]
}
}
directory {
# Move to default element in directory
set mod $mod/[lindex $mod_list($mod) 1]
# restart search if default element is hidden and hidden
# elements were not searched
if {!$fetch_hidden && [isModuleHidden $mod]} {
return [getPathToModule $mod $indir]
}
}
modulefile {
# If mod was a file in this path, return that file
set retlist [list $dir/$mod $mod]
}
virtual {
# return virtual name with file it targets
set retlist [list [lindex $mod_list($mod) 2] $mod]
}
invalid - accesserr {
# may found mod but issue, so end search with error
set retlist [concat [list {} $mod] $mod_list($mod)]
}
}
}
}
# break loop if found something (valid or invalid module)
# elsewhere go to next path
if {[info exists retlist]} {
break
# found nothing after solving a matching alias or symbol
} elseif {$mod_res eq $mod} {
lappend excdir $dir
# look for this name in the other module paths, so restart
# directory search from first dir in list to ensure precedence
return [getPathToModule $mod $indir no $excdir]
}
}
}
# set result if nothing found
if {![info exists retlist]} {
set retlist [list {} $mod none "Unable to locate a modulefile for\
'$mod'"]
}
if {[lindex $retlist 0] ne {}} {
reportDebug "found '[lindex $retlist 0]' as '[lindex $retlist 1]'"
# no error if we look at loaded modules and passed mod not found loaded
} elseif {[lindex $retlist 2] ne {notloaded}} {
eval reportIssue [lrange $retlist 2 4]
}
return $retlist
}
proc isModuleLoaded {mod} {
cacheCurrentModules
return [info exists ::g_loadedModules($mod)]
}
proc getModulefileFromLoadedModule {mod} {
if {[isModuleLoaded $mod]} {
return $::g_loadedModules($mod)
} else {
return {}
}
}
proc isModulefileLoaded {modfile} {
cacheCurrentModules
return [info exists ::g_loadedModuleFiles($modfile)]
}
proc getModuleFromLoadedModulefile {modfile {idx all}} {
set ret {}
if {[isModulefileLoaded $modfile]} {
set ret [expr {$idx eq {all} ? $::g_loadedModuleFiles($modfile) :\
[lindex $::g_loadedModuleFiles($modfile) $idx]}]
}
return $ret
}
proc isModuleLoading {mod} {
return [isInList [getLoadingModuleList] $mod]
}
proc isModulefileLoading {modfile} {
return [isInList [getLoadingModuleFileList] $modfile]
}
proc getModuleFromLoadingModulefile {modfile {idx all}} {
set ret {}
if {[isModulefileLoading $modfile]} {
set loadingmodlist [getLoadingModuleList]
foreach i [lsearch -all -exact [getLoadingModuleFileList] $modfile] {
lappend modlist [lindex $loadingmodlist $i]
}
set ret [expr {$idx eq {all} ? $modlist : [lindex $modlist $idx]}]
}
return $ret
}
proc setLoadedModule {mod modfile uasked} {
set ::g_loadedModules($mod) $modfile
# a loaded modfile may correspond to multiple loaded virtual modules
lappend ::g_loadedModuleFiles($modfile) $mod
# record if mod has been asked by user
if {$uasked} {
set ::g_loadedModuleUasked($mod) 1
}
# build dependency chain
setModuleDependency $mod
}
proc unsetLoadedModule {mod modfile} {
unset ::g_loadedModules($mod)
# a loaded modfile may correspond to multiple loaded virtual modules
if {[llength $::g_loadedModuleFiles($modfile)] == 1} {
unset ::g_loadedModuleFiles($modfile)
} else {
set ::g_loadedModuleFiles($modfile) [replaceFromList\
$::g_loadedModuleFiles($modfile) $mod]
}
if {[info exists ::g_loadedModuleUasked($mod)]} {
unset ::g_loadedModuleUasked($mod)
}
# update dependencies
unsetModuleDependency $mod
}
# check if name matches passed mod name or one of its alternative name
proc doesModuleMatchesName {mod name} {
cacheCurrentModules
set ret 0
# check if main or alternative names of loaded mod matches passed name
foreach matchmod [concat [list $mod] [getLoadedAltname $mod]] {
if {[string first $name/ $matchmod/] == 0} {
set ret 1
break
}
}
return $ret
}
# check if name matches one name of passed loading mod (main or alternative)
proc doesLoadingModuleMatchesName {mod name} {
set ret 0
# check if main or alternative names of loading mod matches passed name
# directly look at all resolved names structure as alternative names for
# loading modules are not yet registered elsewhere
foreach matchmod [concat [list $mod] [getAllModuleResolvedName $mod]] {
if {[string first $name/ $matchmod/] == 0} {
set ret 1
break
}
}
return $ret
}
# return the currently loaded module whose name is the closest to the
# name passed as argument. if no loaded module match at least one part
# of the passed name, an empty string is returned.
proc getLoadedWithClosestName {name} {
set ret {}
set retmax 0
if {[isModuleFullPath $name]} {
set fullname [getAbsolutePath $name]
# if module is passed as full modulefile path name, get corresponding
# short name from used modulepaths
if {[set shortname [findModuleNameFromModulefile $fullname]] ne {}} {
set namesplit [split $shortname /]
# or look at lmfile names to return the eventual exact match
} else {
# module may be loaded with its full path name
if {[isModuleLoaded $fullname]} {
set ret $fullname
# or name corresponds to the _lmfiles_ entry of a virtual modules in
# which case lastly loaded virtual module is returned
} elseif {[isModulefileLoaded $fullname]} {
set ret [getModuleFromLoadedModulefile $fullname end]
}
}
} else {
set namesplit [split $name /]
}
if {[info exists namesplit]} {
# compare name to each currently loaded module name
foreach mod [getLoadedModuleList] {
# if module loaded as fullpath but test name not, try to get loaded
# mod short name (with currently used modulepaths) to compare it
if {[isModuleFullPath $mod] && [set modname\
[findModuleNameFromModulefile $mod]] ne {}} {
# no alt name to retrieve if module has been loaded full path
set matchmodlist [list $modname]
} else {
# add alternative names of mod to the matching list
set matchmodlist [concat [list $mod] [getLoadedAltname $mod]]
}
foreach matchmod $matchmodlist {
set modsplit [split $matchmod /]
# min expr function is not supported in Tcl8.4 and earlier
set imax [if {[llength $namesplit] < [llength $modsplit]}\
{llength $namesplit} {llength $modsplit}]
# compare each element of the name to find closest answer
# in case of equality, last loaded module will be returned as it
# overwrites previously found value
for {set i 0} {$i < $imax} {incr i} {
if {[lindex $modsplit $i] eq [lindex $namesplit $i]} {
if {$i >= $retmax} {
set retmax $i
set ret $mod
}
} else {
# end of match, go next mod
break
}
}
}
}
}
reportDebug "'$ret' closest to '$name'"
return $ret
}
# return the currently loaded module whose name is equal or include the name
# passed as argument. if no loaded module match, an empty string is returned.
# loading: look at currently loading modules instead of loaded if loading == 1
# lmlist: only take into account passed loaded module list not all loaded mods
proc getLoadedMatchingName {name {behavior returnlast} {loading 0} {lmlist\
{}}} {
set ret {}
set retmax 0
# use loading-specific procedures instead of loaded-specific ones
if {$loading} {
set isModulefileLoaded isModulefileLoading
set getModuleFromLoadedModulefile getModuleFromLoadingModulefile
set getLoadedModuleList getLoadingModuleList
set doesModuleMatchesName doesLoadingModuleMatchesName
} else {
set isModulefileLoaded isModulefileLoaded
set getModuleFromLoadedModulefile getModuleFromLoadedModulefile
set getLoadedModuleList getLoadedModuleList
set doesModuleMatchesName doesModuleMatchesName
}
# fetch currently loaded/loading module name is no list provided
if {[llength $lmlist] == 0} {
set lmlist [$getLoadedModuleList]
}
# if module is passed as full modulefile path name, look at lmfile names
# to return the eventual exact match
if {[isModuleFullPath $name]} {
set mod [getAbsolutePath $name]
# if module is loaded with its full path name loadedmodules entry is
# equivalent to _lmfiles_ corresponding entry so only check _lmfiles_
if {[$isModulefileLoaded $mod]} {
# a loaded modfile may correspond to multiple loaded virtual modules
switch -- $behavior {
returnlast {
# the last loaded/loading module will be returned
set ret [$getModuleFromLoadedModulefile $mod end]
}
returnfirst {
# the first loaded/loading module will be returned
set ret [$getModuleFromLoadedModulefile $mod 0]
}
returnall {
# all loaded/loading modules will be returned
set ret [$getModuleFromLoadedModulefile $mod]
}
}
}
} elseif {$name ne {}} {
# compare name to each currently loaded/loading module name, if multiple
# mod match name:
foreach mod $lmlist {
# if module loaded as fullpath but test name not, try to get loaded
# mod short name (with currently used modulepaths) to compare it
if {[isModuleFullPath $mod] && [set modname\
[findModuleNameFromModulefile $mod]] ne {}} {
set matchmod $modname
} else {
set matchmod $mod
}
if {[$doesModuleMatchesName $matchmod $name]} {
switch -- $behavior {
returnlast {
# the last loaded module will be returned
set ret $mod
}
returnfirst {
# the first loaded module will be returned
set ret $mod
break
}
returnall {
# all loaded modules will be returned
lappend ret $mod
}
}
}
}
}
reportDebug "'$ret' matches '$name'"
return $ret
}
proc setLoadedConflict {mod args} {
eval appendNoDupToList ::g_loadedModuleConflict($mod) $args
}
proc unsetLoadedConflict {mod} {
if {[info exists ::g_loadedModuleConflict($mod)]} {
unset ::g_loadedModuleConflict($mod)
}
}
proc getLoadedConflict {mod {serialized 0}} {
set ret {}
if {[info exists ::g_loadedModuleConflict($mod)]} {
if {$serialized} {
# get conflict info as a string that can be registered in an env var
set ret [join [concat [list $mod] $::g_loadedModuleConflict($mod)]\
$::g_sub1_separator]
} else {
set ret $::g_loadedModuleConflict($mod)
}
}
return $ret
}
proc doesModuleConflict {mod} {
set does 0
set modconlist {}
set moddecconlist {}
# check if any loaded module has declared a conflict
foreach modcon [array names ::g_loadedModuleConflict] {
# look if some loaded or loading modules correspond to conflict defined
# by mod
if {$modcon eq $mod} {
foreach withmod $::g_loadedModuleConflict($modcon) {
# skip own reflexive conflict (look at mod main and alternative
# names) and those already known
if {![doesModuleMatchesName $mod $withmod] && ([set lmmod\
[getLoadedMatchingName $withmod returnfirst]] ne {} || [set\
lmmod [getLoadedMatchingName $withmod returnfirst 1]] ne {})} {
appendNoDupToList modconlist $lmmod
# also collect conflict declared name
appendNoDupToList moddecconlist $withmod
set does 1
}
}
# other loaded module declared conflicts (skipping those already known)
} elseif {[notInList $modconlist $modcon]} {
foreach withmod $::g_loadedModuleConflict($modcon) {
# check if mod or one of its alt name match conflict
if {[doesModuleMatchesName $mod $withmod]} {
lappend modconlist $modcon
lappend moddecconlist $modcon
set does 1
break
}
}
}
}
reportDebug "'$mod' conflicts with '$modconlist' (declared as '$moddecconlist')"
return [list $does $modconlist $moddecconlist]
}
proc setLoadedPrereq {mod args} {
eval appendNoDupToList ::g_loadedModulePrereq($mod) $args
}
proc unsetLoadedPrereq {mod} {
if {[info exists ::g_loadedModulePrereq($mod)]} {
unset ::g_loadedModulePrereq($mod)
}
}
proc getLoadedPrereq {mod {serialized 0}} {
set ret {}
if {[info exists ::g_loadedModulePrereq($mod)]} {
if {$serialized} {
# get prereq info as a string that can be registered in an env var
foreach pre $::g_loadedModulePrereq($mod) {
lappend modpre [join $pre $::g_sub2_separator]
}
set ret [join [concat [list $mod] $modpre] $::g_sub1_separator]
} else {
set ret $::g_loadedModulePrereq($mod)
}
}
return $ret
}
proc setLoadedAltname {mod args} {
eval appendNoDupToList ::g_loadedModuleAltname($mod) $args
}
proc unsetLoadedAltname {mod} {
if {[info exists ::g_loadedModuleAltname($mod)]} {
unset ::g_loadedModuleAltname($mod)
}
}
proc getLoadedAltname {mod {serialized 0}} {
set ret {}
if {[info exists ::g_loadedModuleAltname($mod)]} {
if {$serialized} {
# get altname info as a string that can be registered in an env var
set ret [join [concat [list $mod] $::g_loadedModuleAltname($mod)]\
$::g_sub1_separator]
} else {
set ret $::g_loadedModuleAltname($mod)
}
}
return $ret
}
proc isModuleUserAsked {mod} {
cacheCurrentModules
return [info exists ::g_loadedModuleUasked($mod)]
}
# register conflict violation state between loaded modules
proc setModuleConflictViolation {mod modconlist} {
reportDebug "set conflict violation state for '$mod'"
set ::g_conflictViolation($mod) $modconlist
# also update violation state for loaded mod conflicting with mod
foreach lmmod $modconlist {
if {[appendNoDupToList ::g_conflictViolation($lmmod) $mod]} {
reportDebug "set/update conflict violation state for '$lmmod'"
}
}
}
# unregister conflict violation state between modules
proc unsetModuleConflictViolation {mod} {
if {[info exists ::g_conflictViolation($mod)]} {
# also update violation state for loaded mod conflicting with mod
foreach lmmod $::g_conflictViolation($mod) {
set convio [replaceFromList\
$::g_conflictViolation($lmmod) $mod]
reportDebug "unset/update conflict violation state for '$lmmod'"
if {[llength $convio] == 0} {
unset ::g_conflictViolation($lmmod)
} else {
set ::g_conflictViolation($lmmod) $convio
}
}
reportDebug "unset conflict violation state for '$mod'"
unset ::g_conflictViolation($mod)
}
}
# build dependency chain between loaded modules based on registered prereqs
proc setModuleDependency {mod} {
set modlist [getLoadedModuleList]
# only look at modules loaded prior current one to find requirements,
# modules loaded afterward are unmet dependencies as dependents have
# not been reloaded after them
set modidx [lsearch -exact $modlist $mod]
set modnpolist [lrange $modlist [expr {$modidx + 1}] end]
# reverse list to get closest match
set modlist [lreverse [lrange $modlist 0 $modidx]]
set deplist {}
set depnpolist {}
foreach prereq [getLoadedPrereq $mod] {
# get corresponding loaded module for each element of the prereq order
set lmprelist {}
set lmnpolist {}
foreach modpre $prereq {
set lmfound {}
foreach lmmod $modlist {
if {[doesModuleMatchesName $lmmod $modpre]} {
set lmfound $lmmod
break
}
}
# register an unmet dependency/requirement if no loaded mod matches
if {$lmfound eq {}} {
reportDebug "set an unmet requirement on '$modpre' for '$mod'"
lappend ::g_moduleUnmetDep($mod) $modpre
lappend ::g_unmetDepHash($modpre) $mod
# add matching mod elsewhere
} else {
appendNoDupToList lmprelist $lmfound
appendNoDupToList lmnpolist $lmfound
}
# look if requirement can be found in the No Particular Order list
foreach lmmod $modnpolist {
if {[doesModuleMatchesName $lmmod $modpre]} {
appendNoDupToList lmnpolist $lmmod
break
}
}
}
switch -- [llength $lmprelist] {
0 {
# prereq not satisfied
reportDebug "set prereq violation state for '$mod'"
lappend ::g_prereqViolation($mod) $prereq
}
1 {
set lmmod [lindex $lmprelist 0]
lappend deplist $lmmod
# set 'is depended by' relations
lappend ::g_dependHash($lmmod) $mod
}
default {
lappend deplist $lmprelist
# many modules in prereq list, means they all set an optional dep
foreach lmmod $lmprelist {
lappend ::g_dependHash($lmmod) [list $mod 1]
}
}
}
# build 'is depended by' relations not taking loading order into account
switch -- [llength $lmnpolist] {
0 {
# even on No Particular Order mode, prereq is not satisfied
reportDebug "set NPO prereq violation state for '$mod'"
lappend ::g_prereqNPOViolation($mod) $prereq
}
1 {
set lmmod [lindex $lmnpolist 0]
lappend depnpolist $lmmod
# set 'is depended by' relations
lappend ::g_dependNPOHash($lmmod) $mod
}
default {
lappend depnpolist $lmnpolist
# many modules in prereq list, means they all set an optional dep
foreach lmmod $lmnpolist {
lappend ::g_dependNPOHash($lmmod) [list $mod 1]
}
}
}
}
# conflict not satisfied
lassign [doesModuleConflict $mod] doescon modconlist
if {$doescon} {
setModuleConflictViolation $mod $modconlist
}
# update eventual registered unmet dependencies
foreach modpre [array names ::g_unmetDepHash] {
if {[doesModuleMatchesName $mod $modpre]} {
reportDebug "refresh requirements targetting '$modpre'"
foreach lmmod $::g_unmetDepHash($modpre) {
if {[isInList [getDependentLoadedModuleList $lmmod 0 0] $mod]} {
reportDebug "skip deps refresh for '$lmmod' as dep cycle\
detected with '$mod'"
# remove dependency link in no particular order structs to
# avoid cycle first in 'is depended by' struct
if {[info exists ::g_dependNPOHash($mod)]} {
set depmodlist $::g_dependNPOHash($mod)
for {set i 0} {$i < [llength $depmodlist]} {incr i 1} {
if {[lindex [lindex $depmodlist $i] 0] eq $lmmod} {
set depmodlist [lreplace $depmodlist $i $i]
break
}
}
set ::g_dependNPOHash($mod) $depmodlist
reportDebug "update NPO dependent of '$mod' to\
'$depmodlist'"
}
# then update 'depend on' struct
set lmmoddepnpolist {}
foreach depmodlist $::g_moduleNPODepend($lmmod) {
if {[set depidx [lsearch -exact $depmodlist $mod]] != -1} {
set depmodlist [lreplace $depmodlist $depidx $depidx]
# implies to update consistenly alternate requirement or
# violation state if no alternative loaded
switch -- [llength $depmodlist] {
0 {
# do not know exact prereq name, so use correspond.
# loaded module matching it
lappend ::g_prereqNPOViolation($lmmod) $mod
reportDebug "set NPO prereq violation state for\
'$lmmod'"
}
1 {
# update alternate loaded mod which became a strong
# requirement
set altmod [lindex $depmodlist 0]
set ::g_dependNPOHash($altmod) [replaceFromList\
$::g_dependNPOHash($altmod) [list $lmmod 1]\
$lmmod]
reportDebug "update NPO dependent of '$altmod' to\
'$::g_dependNPOHash($altmod)'"
}
}
}
lappend lmmoddepnpolist $depmodlist
}
reportDebug "update NPO requirement of '$lmmod' to\
'$lmmoddepnpolist'"
set ::g_moduleNPODepend($lmmod) $lmmoddepnpolist
} else {
# refresh actual dependencies of targetting mod
unsetModuleDependency $lmmod
setModuleDependency $lmmod
}
}
}
}
# set 'depends on' relation
reportDebug "set requirements of '$mod' to '$deplist'"
set ::g_moduleDepend($mod) $deplist
reportDebug "set NPO requirements of '$mod' to '$depnpolist'"
set ::g_moduleNPODepend($mod) $depnpolist
}
# update dependency chain when unloading module
proc unsetModuleDependency {mod} {
foreach lmmodlist $::g_moduleDepend($mod) {
set manymod [expr {[llength $lmmodlist] > 1 ? 1 : 0}]
# unset 'is depended by' mod relations
foreach lmmod $lmmodlist {
if {[info exists ::g_dependHash($lmmod)]} {
set hashdep [expr {$manymod ? [list $mod 1] : $mod}]
set ::g_dependHash($lmmod) [replaceFromList\
$::g_dependHash($lmmod) $hashdep]
if {[llength $::g_dependHash($lmmod)] == 0} {
unset ::g_dependHash($lmmod)
}
}
}
}
# unset mod's 'depends on' relation
reportDebug "unset requirements of '$mod'"
unset ::g_moduleDepend($mod)
foreach lmmodlist $::g_moduleNPODepend($mod) {
set manymod [expr {[llength $lmmodlist] > 1 ? 1 : 0}]
# unset 'is depended by' mod relations
foreach lmmod $lmmodlist {
if {[info exists ::g_dependNPOHash($lmmod)]} {
set hashdep [expr {$manymod ? [list $mod 1] : $mod}]
set ::g_dependNPOHash($lmmod) [replaceFromList\
$::g_dependNPOHash($lmmod) $hashdep]
if {[llength $::g_dependNPOHash($lmmod)] == 0} {
unset ::g_dependNPOHash($lmmod)
}
}
}
}
# unset mod's No Particular Order 'depends on' relation
reportDebug "unset NPO requirements of '$mod'"
unset ::g_moduleNPODepend($mod)
# unset eventual violation states
if {[info exists ::g_prereqViolation($mod)]} {
reportDebug "unset prereq violation state for '$mod'"
unset ::g_prereqViolation($mod)
}
if {[info exists ::g_prereqNPOViolation($mod)]} {
reportDebug "unset NPO prereq violation state for '$mod'"
unset ::g_prereqNPOViolation($mod)
}
unsetModuleConflictViolation $mod
# unset eventual registered unmet dependencies
if {[info exists ::g_moduleUnmetDep($mod)]} {
foreach ummod $::g_moduleUnmetDep($mod) {
if {[info exists ::g_unmetDepHash($ummod)]} {
set ::g_unmetDepHash($ummod) [replaceFromList\
$::g_unmetDepHash($ummod) $mod]
if {[llength $::g_unmetDepHash($ummod)] == 0} {
unset ::g_unmetDepHash($ummod)
}
}
}
reportDebug "unset unmet requirements for '$mod'"
unset ::g_moduleUnmetDep($mod)
}
# unset mod's 'is depended by' relations
set hashdeplist [getDirectDependentList $mod]
if {[llength $hashdeplist] > 0} {
reportDebug "refresh dependent of '$mod'"
foreach lmmod $hashdeplist {
# refresh actual dependencies of targetting mod
unsetModuleDependency $lmmod
setModuleDependency $lmmod
}
}
}
# returns if any loaded module (if passed mod is empty) or passed mod and all
# its requirement chain satisfy their loading constraints (prereq & conflict)
proc areModuleConstraintsSatisfied {{mod {}} {nporeq 0}} {
set ret 1
cacheCurrentModules
# are requirements loaded after their dependent included or not
if {$nporeq} {
set reqVioVar ::g_prereqNPOViolation
set reqListVar ::g_moduleNPODepend
} else {
set reqVioVar ::g_prereqViolation
set reqListVar ::g_moduleDepend
}
# check if any loaded module violates its prereq or conflict constraints
if {$mod eq {}} {
if {[array size ::g_conflictViolation] > 0 || [array size\
$reqVioVar] > 0} {
set ret 0
}
} else {
set fulllist [list $mod]
for {set i 0} {$i < [llength $fulllist]} {incr i 1} {
set depmod [lindex $fulllist $i]
# check if depmod violates its prereq or conflict constraints
if {[info exists ::g_conflictViolation($depmod)] || [info exists\
${reqVioVar}($depmod)]} {
# found violation among the requirement chain of mod so the
# constraint of mod are not satisfied
set ret 0
break
}
# add requirements of depmod to the module to check list
foreach lmmodlist [set ${reqListVar}($depmod)] {
eval appendNoDupToList fulllist $lmmodlist
}
}
}
return $ret
}
# return list of loaded modules having an unmet requirement on passed mod
# and their recursive dependent
proc getUnmetDependentLoadedModuleList {mod} {
reportDebug "get dependent of upcoming loaded '$mod'"
set unmetdeplist {}
set depmodlist {}
# skip dependent analysis if mod has a conflict with a loaded module
lassign [doesModuleConflict $mod] doescon modconlist
if {!$doescon} {
foreach ummod [array names ::g_unmetDepHash] {
if {[doesModuleMatchesName $mod $ummod]} {
foreach depmod $::g_unmetDepHash($ummod) {
lappend depmodlist $depmod
# temporarily remove prereq violation of depmod if mod
# load solves it (no other prereq is missing)
if {[info exists ::g_prereqViolation($depmod)]} {
foreach prereq $::g_prereqViolation($depmod) {
foreach modpre $prereq {
# also temporarily remove prereq violation for
# requirements loaded after dependent module
if {[doesModuleMatchesName $mod $modpre] ||\
[is-loaded $modpre]} {
# backup original violation to restore it later
if {![info exists preunvioarr($depmod)]} {
set preunvioarr($depmod)\
$::g_prereqViolation($depmod)
}
# temporarily remove matching violation
set ::g_prereqViolation($depmod) [replaceFromList\
$::g_prereqViolation($depmod) $prereq]
if {[llength $::g_prereqViolation($depmod)] == 0} {
unset ::g_prereqViolation($depmod)
}
break
}
}
}
}
}
}
}
}
# select dependent if all its constraint are now satisfied (after removing
# eventual prereq violation toward mod)
foreach depmod $depmodlist {
if {[areModuleConstraintsSatisfied $depmod]} {
appendNoDupToList unmetdeplist $depmod
}
}
# get dependent of dependent
set deplist [getDependentLoadedModuleList $unmetdeplist 0 0 0 0 1]
# restore temporarily lift prereq violation
if {[array exists preunvioarr]} {
foreach depmod [array names preunvioarr] {
set ::g_prereqViolation($depmod) $preunvioarr($depmod)
}
}
set sortlist [sortModulePerLoadedAndDepOrder [concat $unmetdeplist\
$deplist]]
reportDebug "got '$sortlist'"
return $sortlist
}
# return list of loaded modules declaring a prereq on passed mod with
# distinction made with strong prereqs (no alternative loaded) or weak and
# also with prereq loaded after their dependent module
proc getDirectDependentList {mod {strong 0} {nporeq 0} {loading 0}\
{othmodlist {}}} {
set deplist {}
# include or not requirements loaded after their dependent
if {$nporeq} {
set depListVar ::g_dependNPOHash
set reqListVar ::g_moduleNPODepend
} else {
set depListVar ::g_dependHash
set reqListVar ::g_moduleDepend
}
if {[info exists ${depListVar}($mod)]} {
foreach depmod [set ${depListVar}($mod)] {
set add 1
# skip optional dependency if only looking for strong ones
# look at an additionally processed mod list to determine if all
# mods from a dependent list (composed of optional parts) are part
# of the search, which means mod is not optional but strong dependent
if {$strong && [llength $depmod] > 1} {
foreach lmmodlist [set ${reqListVar}([lindex $depmod 0])] {
if {[isInList $lmmodlist $mod]} {
foreach lmmod $lmmodlist {
# other mod part of the opt list is not there so mod
# is considered optional
if {[notInList $othmodlist $lmmod]} {
set add 0
break
}
}
break
}
}
}
if {$add} {
lappend deplist [lindex $depmod 0]
}
}
}
# take currently loading modules into account if asked
if {$loading} {
# reverse list to get closest match
set modlist [lreverse [getLoadedModuleList]]
foreach loadingmod [getLoadingModuleList] {
foreach prereq [getLoadedPrereq $loadingmod] {
set lmprelist {}
set moddep 0
foreach modpre $prereq {
foreach lmmod $modlist {
if {[doesModuleMatchesName $lmmod $modpre]} {
lappend lmprelist $lmmod
if {$lmmod eq $mod} {
set moddep 1
}
break
}
}
}
if {$moddep && (!$strong || [llength $lmprelist] == 1)} {
lappend deplist $loadingmod
break
}
}
}
}
return $deplist
}
# gets the list of all loaded modules which are dependent of passed modlist
# ordered by load position. strong argument controls whether only the active
# dependent modules should be returned or also those that are optional. direct
# argument controls if only dependent module directly requiring passed mods
# should be returned or its full dependent tree. nporeq argument tells if
# requirement loaded after their dependent should be returned. sat_constraint
# argument controls whether only the loaded module satisfying their constraint
# should be part or not of the resulting list. being_unload argument controls
# whether loaded modules in conflict with one or multiple modules from modlist
# should be added to the dependent list as these modules are currently being
# unloaded and these conflicting loaded modules should be refreshed.
proc getDependentLoadedModuleList {modlist {strong 1} {direct 1} {nporeq 0}\
{loading 1} {sat_constraint 0} {being_unload 0}} {
reportDebug "get loaded mod dependent of '$modlist' (strong=$strong,\
direct=$direct, nporeq=$nporeq, loading=$loading,\
sat_constraint=$sat_constraint, being_unload=$being_unload)"
set deplist {}
set fulllist $modlist
# look at consistent requirements for unloading modules
set unlonporeq [expr {$being_unload ? 0 : $nporeq}]
foreach mod $modlist {
# no duplicates or modules from query list
eval appendNoDupToList fulllist [getDirectDependentList $mod $strong\
$unlonporeq $loading $fulllist]
}
if {$being_unload} {
# invite modules in violation with mods to be part of the dependent list
# with their own dependent modules as mod is being unloaded. Achieve so
# by faking that conflict violation is gone
foreach mod $modlist {
lassign [doesModuleConflict $mod] doescon modconlist
if {$doescon} {
unsetModuleConflictViolation $mod
set conunvioarr($mod) $modconlist
eval appendNoDupToList fulllist $modconlist
}
}
}
set unloadingmodlist [getUnloadingModuleList]
for {set i [llength $modlist]} {$i < [llength $fulllist]} {incr i 1} {
set depmod [lindex $fulllist $i]
# skip already added mod or mod violating constraints if asked
if {!$sat_constraint || [areModuleConstraintsSatisfied $depmod\
$nporeq]} {
# get dependent mod of dep mod when looking at full dep tree
if {!$direct} {
eval appendNoDupToList fulllist [getDirectDependentList $depmod\
$strong $nporeq 0 $fulllist]
}
# avoid module currently unloading from result list
if {[notInList $unloadingmodlist $depmod]} {
lappend deplist $depmod
}
}
}
# restore conflict violation if any
if {[array exists conunvioarr]} {
foreach conunvio [array names conunvioarr] {
setModuleConflictViolation $conunvio $conunvioarr($conunvio)
}
}
# sort complete result list to match both loaded and dependency orders
set sortlist [sortModulePerLoadedAndDepOrder $deplist $nporeq $loading]
reportDebug "got '$sortlist'"
return $sortlist
}
# test if passed 'mod' could be automatically unloaded or not, which means it
# has been loaded automatically and no loaded modules require it anymore.
# unmodlist: pass a list of modules that are going to be unloaded
proc isModuleUnloadable {mod {unmodlist {}}} {
set ret 1
# get currently unloading modules if no specific unmodlist set
if {[llength $unmodlist] == 0} {
set unmodlist [getUnloadingModuleList]
}
if {[isModuleUserAsked $mod]} {
set ret 0
} else {
# mod is unloadable if all its dependent are unloaded or unloading
foreach depmod [getDirectDependentList $mod] {
if {[notInList $unmodlist $depmod]} {
set ret 0
break
}
}
}
return $ret
}
# gets the list of all loaded modules which are required by passed modlist
# ordered by load position.
proc getRequiredLoadedModuleList {modlist} {
reportDebug "get mods required by '$modlist'"
# search over all list of loaded modules, starting with passed module
# list, then adding in turns their requirements
set fulllist $modlist
for {set i 0} {$i < [llength $fulllist]} {incr i 1} {
# gets the list of loaded modules which are required by depmod
eval appendNoDupToList fulllist $::g_moduleDepend([lindex $fulllist $i])
}
# sort complete result list to match both loaded and dependency orders
set sortlist [sortModulePerLoadedAndDepOrder [lrange $fulllist [llength\
$modlist] end]]
reportDebug "got '$sortlist'"
return $sortlist
}
# finds required modules that can be unloaded if passed modules are unloaded:
# they have been loaded automatically and are not depended (mandatory or
# optionally) by other module
proc getUnloadableLoadedModuleList {modlist} {
reportDebug "get unloadable mods once '$modlist' unloaded"
# search over all list of unloaded modules, starting with passed module
# list, then adding in turns unloadable requirements
set fulllist $modlist
for {set i 0} {$i < [llength $fulllist]} {incr i 1} {
set depmod [lindex $fulllist $i]
# gets the list of loaded modules which are required by depmod
set deplist {}
foreach lmmodlist $::g_moduleDepend($depmod) {
foreach lmmod $lmmodlist {
if {[notInList $fulllist $lmmod]} {
lappend deplist $lmmod
}
}
}
# get those required module that have been automatically loaded and are
# only required by modules currently being unloaded
foreach lmmod $deplist {
if {[isModuleUnloadable $lmmod $fulllist]} {
lappend fulllist $lmmod
}
}
}
# sort complete result list to match both loaded and dependency orders
set sortlist [sortModulePerLoadedAndDepOrder [lrange $fulllist [llength\
$modlist] end]]
reportDebug "got '$sortlist'"
return $sortlist
}
# runs the global RC files if they exist
proc runModulerc {} {
set rclist {}
reportDebug running...
if {[info exists ::env(MODULERCFILE)]} {
# if MODULERCFILE is a dir, look at a modulerc file in it
if {[file isdirectory $::env(MODULERCFILE)]\
&& [file isfile $::env(MODULERCFILE)/modulerc]} {
lappend rclist $::env(MODULERCFILE)/modulerc
} elseif {[file isfile $::env(MODULERCFILE)]} {
lappend rclist $::env(MODULERCFILE)
}
}
if {[file isfile @prefix@/etc/rc]} {
lappend rclist @prefix@/etc/rc
}
if {[info exists ::env(HOME)] && [file isfile $::env(HOME)/.modulerc]} {
lappend rclist $::env(HOME)/.modulerc
}
foreach rc $rclist {
if {[file readable $rc]} {
reportDebug "Executing $rc"
cmdModuleSource $rc
lappend ::g_rc_loaded $rc
}
}
# identify alias or symbolic version set in these global RC files to be
# able to include them or not in output or resolution processes
array set ::g_rcAlias [array get ::g_moduleAlias]
array set ::g_rcVersion [array get ::g_moduleVersion]
array set ::g_rcVirtual [array get ::g_moduleVirtual]
}
# how many settings bundle are currently saved
proc getSavedSettingsStackDepth {} {
return [llength $::g_SAVE_g_loadedModules]
}
# manage settings to save as a stack to have a separate set of settings
# for each module loaded or unloaded in order to be able to restore the
# correct set in case of failure
proc pushSettings {} {
foreach var {env g_clearedEnvVars g_Aliases g_stateEnvVars g_stateAliases\
g_stateFunctions g_Functions g_newXResources g_delXResources\
g_loadedModules g_loadedModuleFiles g_loadedModuleUasked\
g_loadedModuleConflict g_loadedModulePrereq g_loadedModuleAltname\
g_moduleDepend g_dependHash g_moduleNPODepend g_dependNPOHash\
g_prereqViolation g_prereqNPOViolation g_conflictViolation\
g_moduleUnmetDep g_unmetDepHash g_moduleEval} {
eval "lappend ::g_SAVE_$var \[array get ::$var\]"
}
# save non-array variable and indication if it was set
foreach var {g_changeDir g_stdoutPuts g_return_text} {
if {[info exists ::$var]} {
eval "lappend ::g_SAVE_$var \[list 1 \[set ::$var\]\]"
} else {
eval "lappend ::g_SAVE_$var \[list 0 {}\]"
}
}
reportDebug "settings saved (#[getSavedSettingsStackDepth])"
}
proc popSettings {} {
set flushedid [getSavedSettingsStackDepth]
foreach var {env g_clearedEnvVars g_Aliases g_stateEnvVars g_stateAliases\
g_stateFunctions g_Functions g_newXResources g_delXResources\
g_changeDir g_stdoutPuts g_return_text\
g_loadedModules g_loadedModuleFiles g_loadedModuleUasked\
g_loadedModuleConflict g_loadedModulePrereq g_loadedModuleAltname\
g_moduleDepend g_dependHash g_moduleNPODepend g_dependNPOHash\
g_prereqViolation g_prereqNPOViolation g_conflictViolation\
g_moduleUnmetDep g_unmetDepHash g_moduleEval} {
eval "set ::g_SAVE_$var \[lrange \$::g_SAVE_$var 0 end-1\]"
}
reportDebug "previously saved settings flushed (#$flushedid)"
}
proc restoreSettings {} {
foreach var {env g_clearedEnvVars g_Aliases g_stateEnvVars g_stateAliases\
g_stateFunctions g_Functions g_newXResources g_delXResources\
g_loadedModules g_loadedModuleFiles g_loadedModuleUasked\
g_loadedModuleConflict g_loadedModulePrereq g_loadedModuleAltname\
g_moduleDepend g_dependHash g_moduleNPODepend g_dependNPOHash\
g_prereqViolation g_prereqNPOViolation g_conflictViolation\
g_moduleUnmetDep g_unmetDepHash g_moduleEval} {
# clear current $var arrays
if {[info exists ::$var]} {
eval "unset ::$var; array set ::$var {}"
}
eval "array set ::$var \[lindex \$::g_SAVE_$var end\]"
}
# restore non-array variable if it was set
foreach var {g_changeDir g_stdoutPuts g_return_text} {
if {[info exists ::$var]} {
eval "unset ::$var"
}
eval "lassign \[lindex \$::g_SAVE_$var end\] isdefined val"
if {$isdefined} {
set ::$var $val
}
}
reportDebug "previously saved settings restored\
(#[getSavedSettingsStackDepth])"
}
proc renderSettings {} {
global g_stateEnvVars g_stateAliases g_stateFunctions g_newXResources\
g_delXResources
reportDebug called.
# required to work on cygwin, shouldn't hurt real linux
fconfigure stdout -translation lf
# preliminaries if there is stuff to render
if {$::g_autoInit || [array size g_stateEnvVars] > 0 ||\
[array size g_stateAliases] > 0 || [array size g_newXResources] > 0 ||\
[array size g_stateFunctions] > 0 || [array size g_delXResources] > 0\
|| [info exists ::g_changeDir] || [info exists ::g_stdoutPuts] ||\
[info exists ::g_return_text]} {
switch -- $::g_shellType {
python {
puts stdout {import os}
}
}
set has_rendered 1
} else {
set has_rendered 0
}
if {$::g_autoInit} {
renderAutoinit
}
# new environment variables
foreach var [array names g_stateEnvVars] {
switch -- $g_stateEnvVars($var) {
new {
switch -- $::g_shellType {
csh {
set val [charEscaped $::env($var)]
# csh barfs on long env vars
if {$::g_shell eq {csh} && [string length $val] >\
$::CSH_LIMIT} {
if {$var eq {PATH}} {
reportWarning "PATH exceeds $::CSH_LIMIT characters,\
truncating and appending /usr/bin:/bin ..."
set val [string range $val 0 [expr {$::CSH_LIMIT\
- 1}]]:/usr/bin:/bin
} else {
reportWarning "$var exceeds $::CSH_LIMIT characters,\
truncating..."
set val [string range $val 0 [expr {$::CSH_LIMIT\
- 1}]]
}
}
puts stdout "setenv $var $val;"
}
sh {
puts stdout "$var=[charEscaped $::env($var)];\
export $var;"
}
fish {
set val [charEscaped $::env($var)]
# fish shell has special treatment for PATH variable
# so its value should be provided as a list separated
# by spaces not by semi-colons
if {$var eq {PATH}} {
regsub -all : $val { } val
}
puts stdout "set -xg $var $val;"
}
tcl {
set val $::env($var)
puts stdout "set ::env($var) {$val};"
}
cmd {
set val $::env($var)
puts stdout "set $var=$val"
}
perl {
set val [charEscaped $::env($var) \']
puts stdout "\$ENV{'$var'} = '$val';"
}
python {
set val [charEscaped $::env($var) \']
puts stdout "os.environ\['$var'\] = '$val'"
}
ruby {
set val [charEscaped $::env($var) \']
puts stdout "ENV\['$var'\] = '$val'"
}
lisp {
set val [charEscaped $::env($var) \"]
puts stdout "(setenv \"$var\" \"$val\")"
}
cmake {
set val [charEscaped $::env($var) \"]
puts stdout "set(ENV{$var} \"$val\")"
}
r {
set val [charEscaped $::env($var) {\\'}]
puts stdout "Sys.setenv('$var'='$val')"
}
}
}
del {
switch -- $::g_shellType {
csh {
puts stdout "unsetenv $var;"
}
sh {
puts stdout "unset $var;"
}
fish {
puts stdout "set -e $var;"
}
tcl {
puts stdout "catch {unset ::env($var)};"
}
cmd {
puts stdout "set $var="
}
perl {
puts stdout "delete \$ENV{'$var'};"
}
python {
puts stdout "os.environ\['$var'\] = ''"
puts stdout "del os.environ\['$var'\]"
}
ruby {
puts stdout "ENV\['$var'\] = nil"
}
lisp {
puts stdout "(setenv \"$var\" nil)"
}
cmake {
puts stdout "unset(ENV{$var})"
}
r {
puts stdout "Sys.unsetenv('$var')"
}
}
}
}
}
foreach var [array names g_stateAliases] {
switch -- $g_stateAliases($var) {
new {
set val $::g_Aliases($var)
# convert $n in !!:n and $* in !* on csh (like on compat version)
if {$::g_shellType eq {csh}} {
regsub -all {([^\\]|^)\$([0-9]+)} $val {\1!!:\2} val
regsub -all {([^\\]|^)\$\*} $val {\1!*} val
}
# unescape \$ after now csh-specific conversion is over
regsub -all {\\\$} $val {$} val
switch -- $::g_shellType {
csh {
set val [charEscaped $val]
puts stdout "alias $var $val;"
}
sh {
set val [charEscaped $val]
puts stdout "alias $var=$val;"
}
fish {
set val [charEscaped $val]
puts stdout "alias $var $val;"
}
cmd {
puts stdout "doskey $var=$val"
}
}
}
del {
switch -- $::g_shellType {
csh {
puts stdout "unalias $var;"
}
sh {
puts stdout "unalias $var;"
}
fish {
puts stdout "functions -e $var;"
}
cmd {
puts stdout "doskey $var="
}
}
}
}
}
foreach funcname [array names g_stateFunctions] {
switch -- $g_stateFunctions($funcname) {
new {
# trim function body to smoothly add a finishing ;
set val [string trim $::g_Functions($funcname) "; \t\n\r"]
switch -- $::g_shellType {
sh {
puts stdout "$funcname () { $val; }; export $funcname;"
}
fish {
puts stdout "function $funcname; $val; end;"
}
}
}
del {
switch -- $::g_shellType {
sh {
puts stdout "unset -f $funcname;"
}
fish {
puts stdout "functions -e $funcname;"
}
}
}
}
}
# preliminaries for x-resources stuff
if {[array size g_newXResources] > 0 || [array size g_delXResources] > 0} {
switch -- $::g_shellType {
python {
puts stdout {import subprocess}
}
ruby {
puts stdout {require 'open3'}
}
}
}
# new x resources
if {[array size g_newXResources] > 0} {
# xrdb executable has already be verified in x-resource
set xrdb [getCommandPath xrdb]
foreach var [array names g_newXResources] {
set val $g_newXResources($var)
# empty val means that var is a file to parse
if {$val eq {}} {
switch -- $::g_shellType {
sh - csh - fish {
puts stdout "$xrdb -merge $var;"
}
tcl {
puts stdout "exec $xrdb -merge $var;"
}
perl {
puts stdout "system(\"$xrdb -merge $var\");"
}
python {
set var [charEscaped $var \']
puts stdout "subprocess.Popen(\['$xrdb',\
'-merge', '$var'\])"
}
ruby {
set var [charEscaped $var \']
puts stdout "Open3.popen2('$xrdb -merge $var')"
}
lisp {
puts stdout "(shell-command-to-string \"$xrdb\
-merge $var\")"
}
cmake {
puts stdout "execute_process(COMMAND $xrdb -merge $var)"
}
r {
set var [charEscaped $var {\\'}]
puts stdout "system('$xrdb -merge $var')"
}
}
} else {
switch -- $::g_shellType {
sh - csh - fish {
set var [charEscaped $var \"]
set val [charEscaped $val \"]
puts stdout "echo \"$var: $val\" | $xrdb -merge;"
}
tcl {
puts stdout "set XRDBPIPE \[open \"|$xrdb -merge\" r+\];"
set var [charEscaped $var \"]
set val [charEscaped $val \"]
puts stdout "puts \$XRDBPIPE \"$var: $val\";"
puts stdout {close $XRDBPIPE;}
puts stdout {unset XRDBPIPE;}
}
perl {
puts stdout "open(XRDBPIPE, \"|$xrdb -merge\");"
set var [charEscaped $var \"]
set val [charEscaped $val \"]
puts stdout "print XRDBPIPE \"$var: $val\\n\";"
puts stdout {close XRDBPIPE;}
}
python {
set var [charEscaped $var \']
set val [charEscaped $val \']
puts stdout "subprocess.Popen(\['$xrdb', '-merge'\],\
stdin=subprocess.PIPE).communicate(input='$var:\
$val\\n')"
}
ruby {
set var [charEscaped $var \']
set val [charEscaped $val \']
puts stdout "Open3.popen2('$xrdb -merge') {|i,o,t| i.puts\
'$var: $val'}"
}
lisp {
puts stdout "(shell-command-to-string \"echo $var:\
$val | $xrdb -merge\")"
}
cmake {
set var [charEscaped $var \"]
set val [charEscaped $val \"]
puts stdout "execute_process(COMMAND echo \"$var: $val\"\
COMMAND $xrdb -merge)"
}
r {
set var [charEscaped $var {\\'}]
set val [charEscaped $val {\\'}]
puts stdout "system('$xrdb -merge', input='$var: $val')"
}
}
}
}
}
if {[array size g_delXResources] > 0} {
set xrdb [getCommandPath xrdb]
set xres_to_del {}
foreach var [array names g_delXResources] {
# empty val means that var is a file to parse
if {$g_delXResources($var) eq {}} {
# xresource file has to be parsed to find what resources
# are declared there and need to be unset
foreach fline [split [exec $xrdb -n load $var] \n] {
lappend xres_to_del [lindex [split $fline :] 0]
}
} else {
lappend xres_to_del $var
}
}
# xresource strings are unset by emptying their value since there
# is no command of xrdb that can properly remove one property
switch -- $::g_shellType {
sh - csh - fish {
foreach var $xres_to_del {
puts stdout "echo \"$var:\" | $xrdb -merge;"
}
}
tcl {
foreach var $xres_to_del {
puts stdout "set XRDBPIPE \[open \"|$xrdb -merge\" r+\];"
set var [charEscaped $var \"]
puts stdout "puts \$XRDBPIPE \"$var:\";"
puts stdout {close $XRDBPIPE;}
puts stdout {unset XRDBPIPE;}
}
}
perl {
foreach var $xres_to_del {
puts stdout "open(XRDBPIPE, \"|$xrdb -merge\");"
set var [charEscaped $var \"]
puts stdout "print XRDBPIPE \"$var:\\n\";"
puts stdout {close XRDBPIPE;}
}
}
python {
foreach var $xres_to_del {
set var [charEscaped $var \']
puts stdout "subprocess.Popen(\['$xrdb', '-merge'\],\
stdin=subprocess.PIPE).communicate(input='$var:\\n')"
}
}
ruby {
foreach var $xres_to_del {
set var [charEscaped $var \']
puts stdout "Open3.popen2('$xrdb -merge') {|i,o,t| i.puts\
'$var:'}"
}
}
lisp {
foreach var $xres_to_del {
puts stdout "(shell-command-to-string \"echo $var: |\
$xrdb -merge\")"
}
}
cmake {
foreach var $xres_to_del {
set var [charEscaped $var \"]
puts stdout "execute_process(COMMAND echo \"$var:\"\
COMMAND $xrdb -merge)"
}
}
r {
foreach var $xres_to_del {
set var [charEscaped $var {\\'}]
puts stdout "system('$xrdb -merge', input='$var:')"
}
}
}
}
if {[info exists ::g_changeDir]} {
switch -- $::g_shellType {
sh - csh - fish {
puts stdout "cd '$::g_changeDir';"
}
tcl {
puts stdout "cd \"$::g_changeDir\";"
}
cmd {
puts stdout "cd $::g_changeDir"
}
perl {
puts stdout "chdir '$::g_changeDir';"
}
python {
puts stdout "os.chdir('$::g_changeDir')"
}
ruby {
puts stdout "Dir.chdir('$::g_changeDir')"
}
lisp {
puts stdout "(shell-command-to-string \"cd '$::g_changeDir'\")"
}
r {
puts stdout "setwd('$::g_changeDir')"
}
}
# cannot change current directory of cmake "shell"
}
# send content deferred during modulefile interpretation
if {[info exists ::g_stdoutPuts]} {
foreach putsArgs $::g_stdoutPuts {
eval puts $putsArgs
# check if a finishing newline will be needed after content sent
set needPutsNl [expr {[lindex $putsArgs 0] eq {-nonewline} ? 1 : 0}]
}
if {$needPutsNl} {
puts stdout {}
}
}
# return text value if defined even if error happened
if {[info exists ::g_return_text]} {
reportDebug {text value should be returned.}
renderText $::g_return_text
} elseif {$::error_count > 0} {
reportDebug "$::error_count error(s) detected."
renderFalse
} elseif {$::g_return_false} {
reportDebug {false value should be returned.}
renderFalse
} elseif {$has_rendered} {
# finish with true statement if something has been put
renderTrue
}
}
proc renderAutoinit {} {
reportDebug called.
# automatically detect which tclsh should be used for
# future module commands
set tclshbin [info nameofexecutable]
# ensure script path is absolute
set ::argv0 [getAbsolutePath $::argv0]
switch -- $::g_shellType {
csh {
set pre_hi {set _histchars = $histchars; unset histchars;}
set post_hi {set histchars = $_histchars; unset _histchars;}
set pre_pr {set _prompt=$prompt:q; set prompt="";}
set post_pr {set prompt=$_prompt:q; unset _prompt;}
set eval_cmd "eval \"`$tclshbin $::argv0 $::g_shell \\!*:q`\";"
set pre_ex {set _exit="$status";}
set post_ex {test 0 = $_exit}
set fdef "if ( \$?histchars && \$?prompt )\
alias module '$pre_hi $pre_pr $eval_cmd $pre_ex $post_hi $post_pr $post_ex' ;
if ( \$?histchars && ! \$?prompt )\
alias module '$pre_hi $eval_cmd $pre_ex $post_hi $post_ex' ;
if ( ! \$?histchars && \$?prompt )\
alias module '$pre_pr $eval_cmd $pre_ex $post_pr $post_ex' ;
if ( ! \$?histchars && ! \$?prompt ) alias module '$eval_cmd' ;"
}
sh {
# Considering the diversity of ways local variables are handled
# through the sh-variants ('local' known everywhere except on ksh,
# 'typeset' known everywhere except on pure-sh, and on some systems
# the pure-sh is in fact a 'ksh'), no local variables are defined and
# these variables that should have been local are unset at the end
# on zsh, word splitting should be enabled explicitly
set wsplit [expr {$::g_shell eq {zsh} ? {=} : {}}]
# only redirect module from stderr to stdout when session is
# attached to a terminal to avoid breaking non-terminal session
# (scp, sftp, etc)
set fname [expr {[isStderrTty] ? {_module_raw} : {module}}]
# build quarantine mechanism in module function
# an empty runtime variable is set even if no corresponding
# MODULES_RUNENV_* variable found, as var cannot be unset on
# modified environment command-line
set fdef "${fname}() {"
@silentshdbgsupport@ append fdef {
@silentshdbgsupport@ unset _mlshdbg;
@silentshdbgsupport@ if [ "${MODULES_SILENT_SHELL_DEBUG:-0}" = '1' ]; then
@silentshdbgsupport@ case "$-" in
@silentshdbgsupport@ *v*x*) set +vx; _mlshdbg='vx' ;;
@silentshdbgsupport@ *v*) set +v; _mlshdbg='v' ;;
@silentshdbgsupport@ *x*) set +x; _mlshdbg='x' ;;
@silentshdbgsupport@ *) _mlshdbg='' ;;
@silentshdbgsupport@ esac;
@silentshdbgsupport@ fi;}
@quarantinesupport@ append fdef "
@quarantinesupport@ unset _mlre _mlIFS;
@quarantinesupport@ if \[ -n \"\${IFS+x}\" \]; then
@quarantinesupport@ _mlIFS=\$IFS;
@quarantinesupport@ fi;
@quarantinesupport@ IFS=' ';
@quarantinesupport@ for _mlv in \${${wsplit}MODULES_RUN_QUARANTINE:-}; do"
@quarantinesupport@ append fdef {
@quarantinesupport@ if [ "${_mlv}" = "${_mlv##*[!A-Za-z0-9_]}" -a "${_mlv}" = "${_mlv#[0-9]}" ]; then
@quarantinesupport@ if [ -n "`eval 'echo ${'$_mlv'+x}'`" ]; then
@quarantinesupport@ _mlre="${_mlre:-}${_mlv}_modquar='`eval 'echo ${'$_mlv'}'`' ";
@quarantinesupport@ fi;
@quarantinesupport@ _mlrv="MODULES_RUNENV_${_mlv}";
@quarantinesupport@ _mlre="${_mlre:-}${_mlv}='`eval 'echo ${'$_mlrv':-}'`' ";
@quarantinesupport@ fi;
@quarantinesupport@ done;
@quarantinesupport@ if [ -n "${_mlre:-}" ]; then}
@quarantinesupport@ append fdef "\n eval `eval \${${wsplit}_mlre}$tclshbin $::argv0\
@quarantinesupport@$::g_shell '\"\$@\"'`;
@quarantinesupport@ else
@quarantinesupport@ eval `$tclshbin $::argv0 $::g_shell \"\$@\"`;
@quarantinesupport@ fi;"
@notquarantinesupport@ append fdef "
@notquarantinesupport@ eval `$tclshbin $::argv0 $::g_shell \"\$@\"`;"
append fdef {
_mlstatus=$?;}
@quarantinesupport@ append fdef {
@quarantinesupport@ if [ -n "${_mlIFS+x}" ]; then
@quarantinesupport@ IFS=$_mlIFS;
@quarantinesupport@ else
@quarantinesupport@ unset IFS;
@quarantinesupport@ fi;
@quarantinesupport@ unset _mlre _mlv _mlrv _mlIFS;}
@silentshdbgsupport@ append fdef {
@silentshdbgsupport@ if [ -n "${_mlshdbg:-}" ]; then
@silentshdbgsupport@ set -$_mlshdbg;
@silentshdbgsupport@ fi;
@silentshdbgsupport@ unset _mlshdbg;}
append fdef {
return $_mlstatus;}
append fdef "\n};"
if {[isStderrTty]} {
append fdef "\nmodule() { _module_raw \"\$@\" 2>&1; };"
}
}
fish {
set fdef [expr {[isStderrTty] ? "function _module_raw\n" :\
"function module\n"}]
@quarantinesupport@ append fdef { set -l _mlre ''; set -l _mlv; set -l _mlrv;
@quarantinesupport@ for _mlv in (string split ' ' $MODULES_RUN_QUARANTINE)
@quarantinesupport@ if string match -r '^[A-Za-z_][A-Za-z0-9_]*$' $_mlv >/dev/null
@quarantinesupport@ if set -q $_mlv
@quarantinesupport@ set _mlre $_mlre$_mlv"_modquar='$$_mlv' "
@quarantinesupport@ end
@quarantinesupport@ set _mlrv "MODULES_RUNENV_$_mlv"
@quarantinesupport@ set _mlre "$_mlre$_mlv='$$_mlrv' "
@quarantinesupport@ end
@quarantinesupport@ end
@quarantinesupport@ if [ -n "$_mlre" ]
@quarantinesupport@ set _mlre "env $_mlre"
@quarantinesupport@ end}
# use "| source -" rather than "eval" to be able
# to redirect stderr after stdout being evaluated
@quarantinesupport@ append fdef "\n eval \$_mlre $tclshbin $::argv0 $::g_shell\
(string escape -- \$argv) | source -\n"
@notquarantinesupport@ append fdef " eval $tclshbin $::argv0 $::g_shell\
(string escape -- \$argv) | source -\n"
if {[isStderrTty]} {
append fdef {end
function module
_module_raw $argv ^&1
end}
} else {
append fdef end
}
}
tcl {
set fdef "proc module {args} {"
@quarantinesupport@ append fdef {
@quarantinesupport@ set _mlre {};
@quarantinesupport@ if {[info exists ::env(MODULES_RUN_QUARANTINE)]} {
@quarantinesupport@ foreach _mlv [split $::env(MODULES_RUN_QUARANTINE) " "] {
@quarantinesupport@ if {[regexp {^[A-Za-z_][A-Za-z0-9_]*$} $_mlv]} {
@quarantinesupport@ if {[info exists ::env($_mlv)]} {
@quarantinesupport@ lappend _mlre "${_mlv}_modquar=$::env($_mlv)"
@quarantinesupport@ }
@quarantinesupport@ set _mlrv "MODULES_RUNENV_${_mlv}"
@quarantinesupport@ lappend _mlre [expr {[info exists ::env($_mlrv)] ?\
"${_mlv}=$::env($_mlrv)" : "${_mlv}="}]
@quarantinesupport@ }
@quarantinesupport@ }
@quarantinesupport@ if {[llength $_mlre] > 0} {
@quarantinesupport@ set _mlre [linsert $_mlre 0 "env"]
@quarantinesupport@ }
@quarantinesupport@ }}
append fdef {
set _mlstatus 1;}
@quarantinesupport@ append fdef "\n catch {eval exec \$_mlre \"$tclshbin\"\
\"$::argv0\" \"$::g_shell\" \$args 2>@stderr} script\n"
@notquarantinesupport@ append fdef "\n catch {eval exec \"$tclshbin\"\
\"$::argv0\" \"$::g_shell\" \$args 2>@stderr} script\n"
append fdef { eval $script;
return $_mlstatus}
append fdef "\n}"
}
cmd {
reportErrorAndExit {No autoinit mode available for 'cmd' shell}
}
perl {
set fdef "sub module {"
@quarantinesupport@ append fdef {
@quarantinesupport@ my $_mlre = '';
@quarantinesupport@ if (defined $ENV{'MODULES_RUN_QUARANTINE'}) {
@quarantinesupport@ foreach my $_mlv (split(' ', $ENV{'MODULES_RUN_QUARANTINE'})) {
@quarantinesupport@ if ($_mlv =~ /^[A-Za-z_][A-Za-z0-9_]*$/) {
@quarantinesupport@ if (defined $ENV{$_mlv}) {
@quarantinesupport@ $_mlre .= "${_mlv}_modquar='$ENV{$_mlv}' ";
@quarantinesupport@ }
@quarantinesupport@ my $_mlrv = "MODULES_RUNENV_$_mlv";
@quarantinesupport@ $_mlre .= "$_mlv='$ENV{$_mlrv}' ";
@quarantinesupport@ }
@quarantinesupport@ }
@quarantinesupport@ if ($_mlre ne "") {
@quarantinesupport@ $_mlre = "env $_mlre";
@quarantinesupport@ }
@quarantinesupport@ }}
append fdef {
my $args = '';
if (@_ > 0) {
$args = '"' . join('" "', @_) . '"';
}
my $_mlstatus = 1;}
@quarantinesupport@ append fdef "\n eval `\${_mlre}$tclshbin $::argv0 perl \$args`;\n"
@notquarantinesupport@ append fdef "\n eval `$tclshbin $::argv0 perl \$args`;\n"
append fdef { return $_mlstatus;}
append fdef "\n}"
}
python {
set fdef {import re, subprocess
def module(*arguments):}
@quarantinesupport@ append fdef {
@quarantinesupport@ _mlre = os.environ.copy()
@quarantinesupport@ if 'MODULES_RUN_QUARANTINE' in os.environ:
@quarantinesupport@ for _mlv in os.environ['MODULES_RUN_QUARANTINE'].split():
@quarantinesupport@ if re.match('^[A-Za-z_][A-Za-z0-9_]*$', _mlv):
@quarantinesupport@ if _mlv in os.environ:
@quarantinesupport@ _mlre[_mlv + '_modquar'] = os.environ[_mlv]
@quarantinesupport@ _mlrv = 'MODULES_RUNENV_' + _mlv
@quarantinesupport@ if _mlrv in os.environ:
@quarantinesupport@ _mlre[_mlv] = os.environ[_mlrv]
@quarantinesupport@ else:
@quarantinesupport@ _mlre[_mlv] = ''}
append fdef {
ns = {}}
@quarantinesupport@ append fdef "\n exec(subprocess.Popen(\['$tclshbin',\
'$::argv0', 'python'\] + list(arguments),\
stdout=subprocess.PIPE, env=_mlre).communicate()\[0\], ns)\n"
@notquarantinesupport@ append fdef "\n exec(subprocess.Popen(\['$tclshbin',\
'$::argv0', 'python'\] + list(arguments),\
stdout=subprocess.PIPE).communicate()\[0\], ns)\n"
append fdef { if '_mlstatus' in ns:
_mlstatus = ns['_mlstatus']
else:
_mlstatus = True
return _mlstatus}
}
ruby {
set fdef {class ENVModule
def ENVModule.module(*args)}
@quarantinesupport@ append fdef {
@quarantinesupport@ _mlre = ''
@quarantinesupport@ if ENV.has_key?('MODULES_RUN_QUARANTINE') then
@quarantinesupport@ ENV['MODULES_RUN_QUARANTINE'].split(' ').each do |_mlv|
@quarantinesupport@ if _mlv =~ /^[A-Za-z_][A-Za-z0-9_]*$/ then
@quarantinesupport@ if ENV.has_key?(_mlv) then
@quarantinesupport@ _mlre << _mlv + "_modquar='" + ENV[_mlv].to_s + "' "
@quarantinesupport@ end
@quarantinesupport@ _mlrv = 'MODULES_RUNENV_' + _mlv
@quarantinesupport@ _mlre << _mlv + "='" + ENV[_mlrv].to_s + "' "
@quarantinesupport@ end
@quarantinesupport@ end
@quarantinesupport@ unless _mlre.empty?
@quarantinesupport@ _mlre = 'env ' + _mlre
@quarantinesupport@ end
@quarantinesupport@ end}
append fdef {
if args[0].kind_of?(Array) then
args = args[0]
end
if args.length == 0 then
args = ''
else
args = "\"#{args.join('" "')}\""
end
_mlstatus = true}
@quarantinesupport@ append fdef "\n eval `#{_mlre}$tclshbin $::argv0 ruby #{args}`\n"
@notquarantinesupport@ append fdef "\n eval `$tclshbin $::argv0 ruby #{args}`\n"
append fdef { return _mlstatus
end
end}
}
lisp {
reportErrorAndExit {lisp mode autoinit not yet implemented}
}
cmake {
@quarantinesupport@ set pre_exec "\n execute_process(COMMAND \${_mlre} $tclshbin\
$::argv0 cmake "
@notquarantinesupport@ set pre_exec "\n execute_process(COMMAND $tclshbin\
$::argv0 cmake "
set post_exec "\n OUTPUT_FILE \${tempfile_name})\n"
set fdef {function(module)
cmake_policy(SET CMP0007 NEW)}
@quarantinesupport@ append fdef {
@quarantinesupport@ set(_mlre "")
@quarantinesupport@ if(DEFINED ENV{MODULES_RUN_QUARANTINE})
@quarantinesupport@ string(REPLACE " " ";" _mlv_list "$ENV{MODULES_RUN_QUARANTINE}")
@quarantinesupport@ foreach(_mlv ${_mlv_list})
@quarantinesupport@ if(${_mlv} MATCHES "^[A-Za-z_][A-Za-z0-9_]*$")
@quarantinesupport@ if(DEFINED ENV{${_mlv}})
@quarantinesupport@ set(_mlre "${_mlre}${_mlv}_modquar=$ENV{${_mlv}};")
@quarantinesupport@ endif()
@quarantinesupport@ set(_mlrv "MODULES_RUNENV_${_mlv}")
@quarantinesupport@ set(_mlre "${_mlre}${_mlv}=$ENV{${_mlrv}};")
@quarantinesupport@ endif()
@quarantinesupport@ endforeach()
@quarantinesupport@ if (NOT "${_mlre}" STREQUAL "")
@quarantinesupport@ set(_mlre "env;${_mlre}")
@quarantinesupport@ endif()
@quarantinesupport@ endif()}
append fdef {
set(_mlstatus TRUE)
execute_process(COMMAND mktemp -t moduleinit.cmake.XXXXXXXXXXXX
OUTPUT_VARIABLE tempfile_name
OUTPUT_STRIP_TRAILING_WHITESPACE)
if(${ARGC} EQUAL 1)}
# adapt command definition depending on the number of args to be
# able to pass to some extend (<5 args) empty string element to
# modulecmd (no other way as empty element in ${ARGV} are skipped
append fdef "$pre_exec\"\${ARGV0}\"$post_exec"
append fdef { elseif(${ARGC} EQUAL 2)}
append fdef "$pre_exec\"\${ARGV0}\" \"\${ARGV1}\"$post_exec"
append fdef { elseif(${ARGC} EQUAL 3)}
append fdef "$pre_exec\"\${ARGV0}\" \"\${ARGV1}\"\
\"\${ARGV2}\"$post_exec"
append fdef { elseif(${ARGC} EQUAL 4)}
append fdef "$pre_exec\"\${ARGV0}\" \"\${ARGV1}\"\
\"\${ARGV2}\" \"\${ARGV3}\"$post_exec"
append fdef { else()}
append fdef "$pre_exec\${ARGV}$post_exec"
append fdef { endif()
if(EXISTS ${tempfile_name})
include(${tempfile_name})
file(REMOVE ${tempfile_name})
endif()
set(module_result ${_mlstatus} PARENT_SCOPE)
endfunction(module)}
}
r {
set fdef "module <- function(...){"
@quarantinesupport@ append fdef {
@quarantinesupport@ mlre <- ''
@quarantinesupport@ if (!is.na(Sys.getenv('MODULES_RUN_QUARANTINE', unset=NA))) {
@quarantinesupport@ for (mlv in strsplit(Sys.getenv('MODULES_RUN_QUARANTINE'), ' ')[[1]]) {
@quarantinesupport@ if (grepl('^[A-Za-z_][A-Za-z0-9_]*$', mlv)) {
@quarantinesupport@ if (!is.na(Sys.getenv(mlv, unset=NA))) {
@quarantinesupport@ mlre <- paste0(mlre, mlv, "_modquar='", Sys.getenv(mlv), "' ")
@quarantinesupport@ }
@quarantinesupport@ mlrv <- paste0('MODULES_RUNENV_', mlv)
@quarantinesupport@ mlre <- paste0(mlre, mlv, "='", Sys.getenv(mlrv), "' ")
@quarantinesupport@ }
@quarantinesupport@ }
@quarantinesupport@ if (mlre != '') {
@quarantinesupport@ mlre <- paste0('env ', mlre)
@quarantinesupport@ }
@quarantinesupport@ }}
append fdef {
arglist <- as.list(match.call())
arglist[1] <- 'r'
args <- paste0('"', paste0(arglist, collapse='" "'), '"')}
@quarantinesupport@ append fdef "\n cmd <- paste(mlre, '$tclshbin', '$::argv0', args,\
sep=' ')\n"
@notquarantinesupport@ append fdef "\n cmd <- paste('$tclshbin', '$::argv0', args,\
sep=' ')\n"
append fdef { mlstatus <- TRUE
hndl <- pipe(cmd)
eval(expr = parse(file=hndl))
close(hndl)
invisible(mlstatus)}
append fdef "\n}"
}
}
# output function definition
puts stdout $fdef
}
proc cacheCurrentModules {} {
# parse loaded modules information only once, global arrays are updated
# afterwards when module commands update loaded modules state
if {![info exists ::g_lm_info_cached]} {
set ::g_lm_info_cached 1
# mark specific as well as generic modules as loaded
set i 0
set modfilelist [getLoadedModuleFileList]
set modlist [getLoadedModuleList]
set nuaskedlist [getLoadedModuleNotUserAskedList]
if {[llength $modlist] == [llength $modfilelist]} {
# cache declared alternative names of loaded modules
foreach modalt [getLoadedModuleAltnameList] {
eval setLoadedAltname $modalt
}
# cache declared conflict of loaded modules
foreach modcon [getLoadedModuleConflictList] {
eval setLoadedConflict $modcon
}
# cache declared prereq of loaded modules, prior to setLoadedModule
# which triggers dependency chain build
foreach modpre [getLoadedModulePrereqList] {
eval setLoadedPrereq $modpre
}
foreach mod $modlist {
setLoadedModule $mod [lindex $modfilelist $i] [notInList\
$nuaskedlist $mod]
incr i
}
reportDebug "$i loaded"
} else {
reportErrorAndExit "Loaded environment state is inconsistent\n \
LOADEDMODULES=$modlist\n _LMFILES_=$modfilelist"
}
}
}
# This proc resolves module aliases or version aliases to the real module name
# and version.
proc resolveModuleVersionOrAlias {name} {
set ret [expr {[info exists ::g_moduleResolved($name)] ?\
$::g_moduleResolved($name) : $name}]
reportDebug "'$name' resolved to '$ret'"
return $ret
}
proc charEscaped {str {charlist { \\\t\{\}|<>!;#^$&*?"'`()}}} {
return [regsub -all "\(\[$charlist\]\)" $str {\\\1}]
}
proc charUnescaped {str {charlist { \\\t\{\}|<>!;#^$&*?"'`()}}} {
return [regsub -all "\\\\\(\[$charlist\]\)" $str {\1}]
}
# find command path and remember it
proc getCommandPath {cmd} {
return [lindex [auto_execok $cmd] 0]
}
# find then run command or raise error if command not found
proc runCommand {cmd args} {
set cmdpath [getCommandPath $cmd]
if {$cmdpath eq {}} {
error "GLOBALERR Command '$cmd' cannot be found"
} else {
return [eval exec $cmdpath $args]
}
}
proc getAbsolutePath {path} {
# currently executing a modulefile or rc, so get the directory of this file
if {$::ModulesCurrentModulefile ne {}} {
set curdir [file dirname $::ModulesCurrentModulefile]
# elsewhere get module command current working directory
} else {
# register pwd at first call
if {![info exists ::cwd]} {
set ::cwd [pwd]
}
set curdir $::cwd
}
# empty result if empty path
if {$path eq {}} {
set abspath {}
} else {
set abslist {}
# get a first version of the absolute path by joining the current
# working directory to the given path. if given path is already absolute
# 'file join' will not break it as $curdir will be ignored as soon a
# beginning '/' character is found on $path. this first pass also clean
# extra '/' character. then each element of the path is analyzed to
# clear "." and ".." components.
foreach elt [file split [file join $curdir $path]] {
if {$elt eq {..}} {
# skip ".." element if it comes after root element, remove last
# element elsewhere
if {[llength $abslist] > 1} {
set abslist [lreplace $abslist end end]
}
# skip any "." element
} elseif {$elt ne {.}} {
lappend abslist $elt
}
}
set abspath [eval file join $abslist]
}
# return cleaned absolute path
return $abspath
}
# split string while ignore any separator character that is espaced
proc psplit {str sep} {
# use standard split if no sep character found
if {[string first \\$sep $str] == -1} {
set res [split $str $sep]
} else {
set previdx -1
set idx [string first $sep $str]
while {$idx != -1} {
# look ahead if found separator is escaped
if {[string index $str [expr {$idx-1}]] ne "\\"} {
# unescape any separator character when adding to list
lappend res [charUnescaped [string range $str [expr {$previdx+1}]\
[expr {$idx-1}]] $sep]
set previdx $idx
}
set idx [string first $sep $str [expr {$idx+1}]]
}
lappend res [charUnescaped [string range $str [expr {$previdx+1}] end]\
$sep]
}
return $res
}
# join list while escape any character equal to separator
proc pjoin {lst sep} {
# use standard join if no sep character found
if {[string first $sep $lst] == -1} {
set res [join $lst $sep]
} else {
set res {}
foreach elt $lst {
# preserve empty entries
if {[info exists not_first]} {
append res $sep
} else {
set not_first 1
}
# escape any separator character when adding to string
append res [charEscaped $elt $sep]
}
}
return $res
}
# Dictionary-style string comparison
# Use dictionary sort of lsort proc to compare two strings in the "string
# compare" fashion (returning -1, 0 or 1). Tcl dictionary-style comparison
# enables to compare software versions (ex: "1.10" is greater than "1.8")
proc compareVersion {str1 str2} {
if {$str1 eq $str2} {
return 0
# put both strings in a list, then lsort it and get first element
} elseif {[lindex [lsort -dictionary [list $str1 $str2]] 0] eq $str1} {
return -1
} else {
return 1
}
}
# provide a lreverse proc for Tcl8.4 and earlier
if {[info commands lreverse] eq {}} {
proc lreverse {l} {
set r [list]
for {set i [expr {[llength $l] - 1}]} {$i >= 0} {incr i -1} {
lappend r [lindex $l $i]
}
return $r
}
}
# provide a lassign proc for Tcl8.4 and earlier
if {[info commands lassign] eq {}} {
proc lassign {values args} {
uplevel 1 [list foreach $args [linsert $values end {}] break]
lrange $values [llength $args] end
}
}
proc isInList {lst elt} {
return [expr {[lsearch -exact $lst $elt] != -1}]
}
proc notInList {lst elt} {
return [expr {[lsearch -exact $lst $elt] == -1}]
}
proc appendNoDupToList {lstname args} {
set ret 0
upvar $lstname lst
foreach elt $args {
if {![info exists lst] || [notInList $lst $elt]} {
lappend lst $elt
set ret 1
}
}
return $ret
}
proc replaceFromList {list1 item {item2 {}}} {
while {[set xi [lsearch -exact $list1 $item]] >= 0} {
set list1 [if {[string length $item2] == 0} {lreplace $list1 $xi $xi}\
{lreplace $list1 $xi $xi $item2}]
}
return $list1
}
# returns elements from list1 not part of list2 and elements from list2 not
# part of list1
proc getDiffBetweenList {list1 list2} {
set res1 [list]
set res2 [list]
foreach elt $list1 {
if {[notInList $list2 $elt]} {
lappend res1 $elt
}
}
foreach elt $list2 {
if {[notInList $list1 $elt]} {
lappend res2 $elt
}
}
return [list $res1 $res2]
}
proc parseAccessIssue {modfile} {
# retrieve and return access issue message
if {[regexp {POSIX .* \{(.*)\}$} $::errorCode match errMsg]} {
return "[string totitle $errMsg] on '$modfile'"
} else {
return "Cannot access '$modfile'"
}
}
proc checkValidModule {modfile} {
reportDebug $modfile
# test file only once, cache result obtained to minimize file query
return [expr {[info exists ::g_modfileValid($modfile)]\
? $::g_modfileValid($modfile)\
: [set ::g_modfileValid($modfile) [readModuleContent $modfile 1 1 1]]}]
}
# get file modification time, cache it at first query, use cache afterward
proc getFileMtime {fpath} {
if {[info exists ::g_fileMtime($fpath)]} {
return $::g_fileMtime($fpath)
} else {
return [set ::g_fileMtime($fpath) [file mtime $fpath]]
}
}
# define proc that will be used as fallback to command provided by extension
# library in case this library is not loaded
proc __readFile {filename {firstline 0}} {
set fid [open $filename r]
set fdata [if {$firstline} {gets $fid} {read $fid}]
close $fid
return $fdata
}
proc readModuleContent {modfile {report_read_issue 0} {must_have_cookie 1}\
{only_check_validity 0}} {
reportDebug $modfile
set res {}
# read file
if {[catch {
if {[info exists ::g_modfileContent($modfile)]} {
lassign $::g_modfileContent($modfile) fh fdata
} else {
# only read beginning of file if just checking validity and not
# asked to always fully read files
set fdata [readFile $modfile [expr {$only_check_validity &&\
![currentAlwaysReadFullFile]}]]
# extract magic cookie (first word of modulefile)
set fh [lindex [split [string range $fdata 0 32]] 0]
# cache full file read to minimize file operations
if {!$only_check_validity || [currentAlwaysReadFullFile]} {
set ::g_modfileContent($modfile) [list $fh $fdata]
}
}
} errMsg ]} {
if {$report_read_issue} {
set msg [parseAccessIssue $modfile]
if {$only_check_validity} {
set res [list accesserr $msg]
} else {
reportError $msg
}
}
} else {
# check module validity if magic cookie is mandatory
if {$must_have_cookie && ![string equal -length 8 $fh {#%Module}]} {
set msg {Magic cookie '#%Module' missing}
if {$only_check_validity} {
set res [list invalid $msg]
} else {
reportInternalBug $msg $modfile
}
# check if min version requirement is met if magic cookie is mandatory
} elseif {$must_have_cookie && [string length $fh] > 8 &&\
[compareVersion {@MODULES_RELEASE@} [string range $fh 8 end]] <0} {
set msg "Modulefile requires at least Modules version [string range\
$fh 8 end]"
if {$only_check_validity} {
set res [list invalid $msg]
} else {
reportInternalBug $msg $modfile
}
} else {
if {$only_check_validity} {
# set validity information as result
set res [list true {}]
} else {
# set file content as result
set res $fdata
}
}
}
return $res
}
# If given module maps to default or other symbolic versions, a list of
# those versions is returned. This takes module/version as an argument.
proc getVersAliasList {mod} {
set tag_list [expr {[info exists ::g_symbolHash($mod)] ?\
$::g_symbolHash($mod) : {}}]
reportDebug "'$mod' has tag list '$tag_list'"
return $tag_list
}
# get list of elements located in a directory passed as argument. a flag is
# set after each element to know if it is considered hidden or not. a
# fetch_hidden argument is there to control search of hidden elements. a
# fetch_dotversion argument controls whether .version file should be looked at
# in directory .proc will be used as a fallback to command provided by
# extension library
proc __getFilesInDirectory {dir fetch_hidden fetch_dotversion} {
set dir_list [list]
# try then catch any issue rather than test before trying
# workaround 'glob -nocomplain' which does not return permission
# error on Tcl 8.4, so we need to avoid registering issue if
# raised error is about a no match
if {[catch {set elt_list [glob $dir/*]} errMsg]} {
if {$errMsg eq "no files matched glob pattern \"$dir/*\""} {
set elt_list {}
} else {
# rethrow other error to catch it in caller proc
error $errMsg $::errorInfo $::errorCode
}
}
# Add each element in the current directory to the list
if {[file readable $dir/.modulerc]} {
lappend dir_list $dir/.modulerc 0
}
if {$fetch_dotversion && [file readable $dir/.version]} {
lappend dir_list $dir/.version 0
}
foreach elt $elt_list {
lappend dir_list $elt 0
}
# search for hidden files if asked
if {$fetch_hidden} {
foreach elt [glob -nocomplain -types hidden -directory\
$dir -tails *] {
switch -- $elt {
.modulerc - .version - . - .. { }
default {
lappend dir_list $dir/$elt 1
}
}
}
}
return $dir_list
}
# check if an existing findModules cache entry matches current search by
# evaluating search ids. if an exact match cannot be found, look at saved
# searches that contains current search (superset of looked elements), extra
# elements will be filtered-out by GetModules
proc findModulesInMemCache {searchid} {
# exact same search is cached
if {[info exists ::g_foundModulesMemCache($searchid)]} {
set match_searchid $searchid
set mod_list $::g_foundModulesMemCache($searchid)
# look for a superset search
} else {
set match_searchid {}
set mod_list {}
foreach cacheid [array names ::g_foundModulesMemCache] {
# cache id acts as pattern to check if it contains current search
if {[string match $cacheid $searchid]} {
set match_searchid $cacheid
set mod_list $::g_foundModulesMemCache($cacheid)
break
}
}
}
return [list $match_searchid $mod_list]
}
# finds all module-related files matching mod in the module path dir
proc findModules {dir mod depthlvl fetch_mtime fetch_hidden} {
reportDebug "finding '$mod' in $dir (depthlvl=$depthlvl,\
fetch_mtime=$fetch_mtime, fetch_hidden=$fetch_hidden)"
# generated search id (for cache search/save) by compacting given args
set searchid $dir:$mod:$depthlvl:$fetch_mtime:$fetch_hidden
# look at memory cache for a compatible result
lassign [findModulesInMemCache $searchid] cache_searchid cache_list
if {$cache_searchid ne {}} {
reportDebug "use cache entry '$cache_searchid'"
return $cache_list
}
# skip search in top dir if directly looking to a deep element, which means
# findModules has already been called and top dir has already been analyzed
if {[file dirname $mod] eq {.}} {
# use catch protection to handle non-readable and non-existent dir
if {[catch {
set full_list {}
foreach {fpelt hid} [getFilesInDirectory $dir $fetch_hidden 0] {
set elt [file tail $fpelt]
# include any .modulerc file found at the modulepath root
if {$elt eq {.modulerc} || $mod eq {} || [string match $mod\
$elt]} {
lappend full_list $fpelt
}
}
}]} {
return {}
}
} else {
lappend full_list [file join $dir $mod]
}
foreach igndir [getIgnoredDirs] {
set ignored_dirs($igndir) 1
}
array set mod_list {}
for {set i 0} {$i < [llength $full_list]} {incr i 1} {
set element [lindex $full_list $i]
set tag_list {}
set tail [file tail $element]
set modulename [getModuleNameFromModulepath $element $dir]
set parentname [file dirname $modulename]
set moddepthlvl [llength [file split $modulename]]
set add_ref_to_parent 0
if {[file isdirectory $element]} {
if {![info exists ignored_dirs($tail)]} {
if {[catch {
set elt_list [getFilesInDirectory $element $fetch_hidden 1]
} errMsg]} {
set mod_list($modulename) [list accesserr [parseAccessIssue\
$element] $element]
} else {
set mod_list($modulename) [list directory]
# Add each element in the current directory to the list
foreach {fpelt hid} $elt_list {
lappend full_list $fpelt
# Flag hidden files
if {$hid} {
set hidden_list($fpelt) 1
}
}
set add_ref_to_parent 1
}
}
} else {
switch -glob -- $tail {
.modulerc {
set mod_list($modulename) [list modulerc]
}
.version {
# skip .version file from different depth level than search
# targets if no in depth mode is enabled
if {$depthlvl == 0 || $moddepthlvl == $depthlvl} {
set mod_list($modulename) [list modulerc]
}
}
*~ - *,v - \#*\# { }
default {
# skip modfile in no in depth mode search if it does not relate
# to targeted depth level and one valid modfile has already be
# found for the dirs lying at other depth level
if {$depthlvl == 0 || $moddepthlvl == $depthlvl || ![info\
exists modfile_indir($parentname)]} {
lassign [checkValidModule $element] check_valid check_msg
switch -- $check_valid {
true {
set mtime [expr {$fetch_mtime ? [getFileMtime\
$element] : {}}]
set mod_list($modulename) [list modulefile $mtime]
# if modfile hidden, do not ref it in parent list
set add_ref_to_parent [expr {$fetch_hidden && [info\
exists hidden_list($element)] ? 0 : 1}]
# a valid modfile has been found in directory
if {$add_ref_to_parent} {
set modfile_indir($parentname) 1
}
}
default {
# register check error and relative message to get it
# in case of direct access of this module element, but
# no registering in parent directory structure as
# element is not valid
set mod_list($modulename) [list $check_valid\
$check_msg $element]
}
}
}
}
}
}
# add reference to parent structure
if {$add_ref_to_parent && [info exists mod_list($parentname)]} {
lappend mod_list($parentname) $tail
}
}
reportDebug "found [array names mod_list]"
# cache search results
reportDebug "create cache entry '$searchid'"
set found_list [array get mod_list]
set ::g_foundModulesMemCache($searchid) $found_list
return $found_list
}
proc getModules {dir {mod {}} {fetch_mtime 0} {search {}} {fetch_hidden 0}} {
global g_sourceAlias g_sourceVersion g_sourceVirtual g_resolvedPath
global g_rcAlias g_moduleAlias g_rcVersion g_moduleVersion
global g_rcVirtual g_moduleVirtual g_rcfilesSourced
reportDebug "get '$mod' in $dir (fetch_mtime=$fetch_mtime, search=$search,\
fetch_hidden=$fetch_hidden)"
# perform an in depth search or not
set indepth [expr {[isInList $search noindepth] ? 0 : 1}]
# if search for global or user rc alias only, no dir lookup is performed
# and aliases from g_rcAlias are returned
if {[isInList $search rc_alias_only]} {
set add_rc_defs 1
array set found_list {}
} else {
# find modules by searching with first path element if mod is a deep
# modulefile (elt1/etl2/vers) in order to catch all .modulerc and
# .version files of module-related parent directories in case we need
# to translate an alias or a version
set parentlist [split $mod /]
set findmod [lindex $parentlist 0]
# if searched mod is an empty or flat element append wildcard character
# to match anything starting with mod
set wild [expr {[isInList $search wild] ? 1 : 0}]
if {$wild && [llength $parentlist] <= 1} {
append findmod *
}
# add alias/version definitions from global or user rc to result
set add_rc_defs [expr {[isInList $search rc_defs_included] ? 1 : 0}]
if {!$fetch_hidden} {
set fetch_hidden [isModuleHidden $mod]
reportDebug "is '$mod' requiring hidden search ($fetch_hidden)"
}
# if no indepth mode search, pass the depth level of the search query
set depthlvl [expr {$indepth ? 0 : [llength $parentlist]}]
array set found_list [findModules $dir $findmod $depthlvl $fetch_mtime\
$fetch_hidden]
}
# check search query string corresponds to directory
set querydir [string trimright $mod *]
set isquerydir [expr {[string index $querydir end] eq {/}}]
set querydir [string range $querydir 0 end-1]
array set dir_list {}
array set mod_list {}
foreach elt [lsort [array names found_list]] {
set elt_type [lindex $found_list($elt) 0]
if {$elt_type eq {modulerc}} {
if {![info exists g_rcfilesSourced($dir/$elt)]} {
execute-modulerc $dir/$elt $elt $elt
# Keep track of already sourced rc files not to run them again
set g_rcfilesSourced($dir/$elt) 1
}
# add other entry kind to the result list
# also add dirname to results if query name finishes with trailing slash
} elseif {($wild && [string match $mod* $elt]) || (!$wild && ([string\
match $mod/* $elt] || [string match $mod $elt])) || ($isquerydir\
&& $elt_type eq {directory} && [string match $querydir $elt])} {
set mod_list($elt) $found_list($elt)
# list dirs to rework their definition at the end
if {$elt_type eq {directory}} {
set dir_list($elt) 1
}
}
}
# add versions found when parsing .version or .modulerc files in this
# directory (skip versions not registered from this directory except if
# global or user rc definitions should be included)) if they match passed
# $mod (as for regular modulefiles)
foreach vers [array names g_moduleVersion -glob $mod*] {
set versmod $g_moduleVersion($vers)
if {($dir ne {} && [string first $dir/ $g_sourceVersion($vers)] == 0)\
|| ($add_rc_defs && [info exists g_rcVersion($vers)])} {
set mod_list($vers) [list version $versmod]
}
# no reference add to parent directory structure as versions are virtual
# add the target of symbolic versions found when parsing .version or
# .modulerc files if these symbols match passed $mod (as for regular
# modulefiles). modulefile target of these version symbol should have
# been found previously to be added
if {![info exists mod_list($versmod)]} {
# exception made to hidden modulefile target which should not be
# found previously as not searched (except if we already look for
# hidden modules). in case symbolic version matches passed $mod
# look for this hidden target
if {$mod eq $vers && !$fetch_hidden && [isModuleHidden $versmod]} {
array set found_list [findModules $dir $versmod 0 $fetch_mtime 1]
}
# symbolic version targets a modulefile most of the time
if {[info exists found_list($versmod)]} {
set mod_list($versmod) $found_list($versmod)
# but sometimes they may target an alias
} elseif {[info exists g_moduleAlias($versmod)]} {
lappend matching_versalias $versmod
# or a virtual module
} elseif {[info exists g_moduleVirtual($versmod)]} {
lappend matching_versvirt $versmod
}
}
}
# add aliases found when parsing .version or .modulerc files in this
# directory (skip aliases not registered from this directory except if
# global or user rc definitions should be included) if they match passed
# $mod (as for regular modulefiles) or if a symbolic versions targeting
# alias match passed $mod
set matching_alias [array names g_moduleAlias -glob $mod*]
if {[info exists matching_versalias]} {
eval appendNoDupToList matching_alias $matching_versalias
}
foreach alias $matching_alias {
if {($dir ne {} && [string first $dir/ $g_sourceAlias($alias)] == 0)\
|| ($add_rc_defs && [info exists g_rcAlias($alias)])} {
set mod_list($alias) [list alias $g_moduleAlias($alias)]
# in case alias overwrites a directory definition
if {[info exists dir_list($alias)]} {
unset dir_list($alias)
}
# add reference to this alias version in parent structure
set parentname [file dirname $alias]
if {[info exists mod_list($parentname)]} {
lappend mod_list($parentname) [file tail $alias]
} else {
# add reference to orphan list if dir does not exist may be added
# below if dir is virtually set by a virtual deep module
lappend orphan_list($parentname) [file tail $alias]
}
}
}
# add virtual mods found when parsing .version or .modulerc files in this
# directory (skip virtual mods not registered from this directory except if
# global or user rc definitions should be included) if they match passed
# $mod (as for regular modulefiles) or if a symbolic versions targeting
# virtual mod match passed $mod
set matching_virtual [array names g_moduleVirtual -glob $mod*]
if {[info exists matching_versvirt]} {
eval appendNoDupToList matching_virtual $matching_versvirt
}
foreach virt $matching_virtual {
if {($dir ne {} && [string first $dir/ $g_sourceVirtual($virt)] == 0)\
|| ($add_rc_defs && [info exists g_rcVirtual($virt)])} {
lassign [checkValidModule $g_moduleVirtual($virt)] check_valid\
check_msg
switch -- $check_valid {
true {
set mtime [expr {$fetch_mtime ? [getFileMtime\
$g_moduleVirtual($virt)] : {}}]
# set mtime at index 1 like a modulefile entry
set mod_list($virt) [list virtual $mtime\
$g_moduleVirtual($virt)]
set add_ref_to_parent 1
}
default {
# register check error and relative message to get it in
# case of direct access of this module element, but no
# registering in parent directory structure as element
# is not valid
set mod_list($virt) [list $check_valid $check_msg\
$g_moduleVirtual($virt)]
# no reference to parent list
set add_ref_to_parent 0
}
}
# in case virtual mod overwrites a directory definition
if {[info exists dir_list($virt)]} {
unset dir_list($virt)
}
# add reference to this virtual mod in parent structure
if {$add_ref_to_parent} {
set parentname [file dirname $virt]
set elt [file tail $virt]
# initialize virtual parent structure if it does not exist
if {![info exists mod_list($parentname)]} {
# loop until reaching an existing or a top entry
while {![info exists mod_list($parentname)]\
&& $parentname ne {.}} {
# create virtual directory entry
set mod_list($parentname) [list directory $elt]
set dir_list($parentname) 1
set elt [file tail $parentname]
set parentname [file dirname $parentname]
}
# add reference to reached existing entry
if {[info exists mod_list($parentname)]} {
lappend mod_list($parentname) $elt
}
} else {
lappend mod_list($parentname) $elt
}
}
}
}
# integrate aliases defined in orphan directories if these dirs have been
# virtually created by a virtual module reference
foreach dir [array names orphan_list] {
if {[info exists mod_list($dir)]} {
set mod_list($dir) [concat $mod_list($dir) $orphan_list($dir)]
}
}
# work on directories integrated in the result list by registering
# default element in this dir and list of all child elements dictionary
# sorted, so last element in dir is also last element in this list
# this treatment happen at the end to find all directory entries in
# result list (alias and virtual included)
foreach dir [lsort [array names dir_list]] {
set elt_list [lsort -dictionary [lrange $mod_list($dir) 1 end]]
# remove dir from list if it is empty
if {[llength $elt_list] == 0} {
unset mod_list($dir)
# rework upper directories content if registered
while {[set par_dir [file dirname $dir]] ne {.}\
&& [info exists mod_list($par_dir)]} {
set dir_name [file tail $dir]
set dir $par_dir
# quit if something has overwritten the directory definition
if {[lindex $mod_list($dir) 0] ne {directory}} {
break
}
# get upper dir content without empty dir (as dir_list is sorted
# parent dir information have already been consolidated)
set elt_list [lsearch -all -inline -not -exact [lrange\
$mod_list($dir) 2 end] $dir_name]
# remove also parent dir if it becomes empty
if {[llength $elt_list] == 0} {
unset mod_list($dir)
} else {
# change default by last element if empty dir was default
set dfl_elt [lindex $mod_list($dir) 1]
if {$dfl_elt eq $dir_name} {
set dfl_elt [lindex $elt_list end]
}
set mod_list($dir) [concat [list directory $dfl_elt]\
$elt_list]
# no need to update upper directory as this one persists
break
}
}
} else {
# get default element (defined or implicit)
set dfl_elt [if {[info exists g_resolvedPath($dir)]} {file tail\
$g_resolvedPath($dir)} {lindex $elt_list end}]
set mod_list($dir) [concat [list directory $dfl_elt] $elt_list]
}
}
# now all modulefiles are settled (regular, symbol, alias, virtual) only
# keep those found at search query depth level if 'noindepth' mode asked
if {!$indepth} {
# remove entries with more filename path separator than query pattern
set lastqslash [expr {[string last / $mod] + 1}]
foreach elt [array names mod_list] {
if {[string first / $elt $lastqslash] != -1} {
unset mod_list($elt)
}
}
}
reportDebug "got [array names mod_list]"
return [array get mod_list]
}
# format an element with its tags for display in a list
proc formatListEltToDisplay {elt eltsgr eltsuffix tag_list tagsgr show_tags\
sgrdef {matchmap {}}} {
set disp $elt$eltsuffix
# hightlight matching substring
if {$matchmap ne {}} {
set dispsgr [sgr $eltsgr [string map $matchmap $elt]]
} else {
set dispsgr [sgr $eltsgr $elt]
}
if {$show_tags} {
# display default tag graphically over element name
if {[set defidx [lsearch -exact $tag_list default]] != -1 && $sgrdef} {
set tag_list [lreplace $tag_list $defidx $defidx]
set dispsgr [sgr de $dispsgr]
}
# format remaining tag list
if {[llength $tag_list] > 0} {
append disp "([join $tag_list :])"
set tagssgr [sgr se (]
foreach tag $tag_list {
if {![info exists colonsgr]} {
set colonsgr [sgr se :]
} else {
append tagssgr $colonsgr
}
append tagssgr [sgr $tagsgr $tag]
}
append tagssgr [sgr se )]
append dispsgr $eltsuffix$tagssgr
} else {
append dispsgr $eltsuffix
}
} else {
append dispsgr $eltsuffix
}
return [list $disp $dispsgr]
}
# format an element with its tags for a long/detailled display in a list
proc formatListEltToLongDisplay {elt eltsgr eltsuffix tag_list tagsgr mtime\
sgrdef {matchmap {}}} {
set disp $elt$eltsuffix
set displen [string length $disp]
# hightlight matching substring
if {$matchmap ne {}} {
set dispsgr [sgr $eltsgr [string map $matchmap $elt]]
} else {
set dispsgr [sgr $eltsgr $elt]
}
# display default tag graphically over element name
if {[set defidx [lsearch -exact $tag_list default]] != -1 && $sgrdef} {
set tag_list [lreplace $tag_list $defidx $defidx]
set dispsgr [sgr de $dispsgr]
}
# format remaining tag list
if {[llength $tag_list] > 0} {
set tagslen [string length [join $tag_list :]]
foreach tag $tag_list {
if {![info exists colonsgr]} {
set colonsgr [sgr se :]
} else {
append tagssgr $colonsgr
}
append tagssgr [sgr $tagsgr $tag]
}
} else {
set tagssgr {}
set tagslen 0
}
set nbws1 [expr {40 - $displen}]
set nbws2 [expr {20 - $tagslen + [expr {$nbws1 < 0 ? $nbws1 : 0}]}]
return [list $disp $dispsgr$eltsuffix[string repeat { }\
$nbws1]$tagssgr[string repeat { } $nbws2]$mtime]
}
# Prepare a map list to translate later on a substring in its highlighted
# counterpart. Translate substring after right trimming it from wildcard
# characters. No translation map is returned if hightlight rendering is
# disabled or if some wildcard characters persist after the trim.
proc prepareMapToHightlightSubstr {substr} {
set m [string trimright $substr {*?}]
return [expr {[sgr hi {}] ne {} && $m ne {} && [string first * $m] == -1\
&& [string first ? $m] == -1 ? [list $m [sgr hi $m]] : {}}]
}
# Finds all module versions for mod in the module path dir
proc listModules {dir mod show_mtime filter search} {
global flag_default_mf flag_default_dir
reportDebug "get '$mod' in $dir (show_mtime=$show_mtime, filter=$filter,\
search=$search)"
# get module list
# as we treat a full directory content do not exit on an error
# raised from one modulerc file
array set mod_list [getModules $dir $mod $show_mtime $search]
# in depth search performed or not
set indepth [expr {[isInList $search noindepth] ? 0 : 1}]
# extract directory name specified in search query string if any
set querydir [string trimright $mod *]
set isquerydir [expr {[string index $querydir end] eq {/}}]
set querydir [string range $querydir 0 end-1]
# no filtering when in depth disabled and query targets modulepath root
set filtering [expr {$filter ne {} && ($indepth || [string first / $mod]\
!= -1)}]
# prepare results for display
set alias_colored [expr {[sgr al {}] ne {}}]
set default_colored [expr {[sgr de {}] ne {}}]
set matchmap [prepareMapToHightlightSubstr $mod]
set clean_list {}
foreach elt [array names mod_list] {
set elt_type [lindex $mod_list($elt) 0]
set add_to_clean_list 1
if {$filtering} {
# only analyze directories or modulefile at the root in case of
# result filtering. depending on filter kind the selection of the
# modulefile to display will be made using the definition
# information of its upper directory
if {$elt_type eq {directory}} {
switch -- $filter {
onlydefaults {
set elt_vers [lindex $mod_list($elt) 1]
}
onlylatest {
set elt_vers [lindex $mod_list($elt) end]
}
}
# switch to selected modulefile to display
append elt /$elt_vers
# verify it exists elsewhere skip result for this directory
if {![info exists mod_list($elt)]} {
continue
}
set elt_type [lindex $mod_list($elt) 0]
# skip if directory selected, will be looked at in a next round
if {$elt_type eq {directory}} {
set add_to_clean_list 0
}
} elseif {[file dirname $elt] ne {.}} {
set add_to_clean_list 0
}
if {$add_to_clean_list} {
set tag_list [getVersAliasList $elt]
}
} else {
set tag_list [getVersAliasList $elt]
# do not add a dir if it does not hold tags and depth mode is enabled
# or if search query corresponds to dir: only its content is relevant
if {$elt_type eq {directory} && (($indepth && [llength $tag_list]\
== 0) || ($isquerydir && [string match $querydir $elt]))} {
set add_to_clean_list 0
}
}
if {$add_to_clean_list} {
set dispsgr {}
# ignore "version" entries as symbolic version are treated
# along to their relative modulefile not independently
switch -- $elt_type {
directory {
if {$show_mtime} {
# append / char after name to clearly indicate this is a dir
lassign [formatListEltToLongDisplay $elt di / $tag_list sy\
{} $default_colored $matchmap] disp dispsgr
} else {
lassign [formatListEltToDisplay $elt di / $tag_list sy\
$flag_default_dir $default_colored $matchmap] disp\
dispsgr
}
}
modulefile - virtual {
if {$show_mtime} {
# add to display file modification time in addition
# to potential tags
lassign [formatListEltToLongDisplay $elt {} {} $tag_list sy\
[clock format [lindex $mod_list($elt) 1] -format\
{%Y/%m/%d %H:%M:%S}] $default_colored $matchmap] disp\
dispsgr
} else {
lassign [formatListEltToDisplay $elt {} {} $tag_list sy\
$flag_default_mf $default_colored $matchmap] disp dispsgr
}
}
alias {
if {$show_mtime} {
lassign [formatListEltToLongDisplay $elt al " -> [lindex\
$mod_list($elt) 1]" $tag_list sy {} $default_colored\
$matchmap] disp dispsgr
} else {
# add a '@' tag to indicate elt is an alias if not colored
if {!$alias_colored} {
lappend tag_list @
}
lassign [formatListEltToDisplay $elt al {} $tag_list sy\
$flag_default_mf $default_colored $matchmap] disp dispsgr
}
}
}
if {$dispsgr ne {}} {
lappend clean_list $disp
set sgrmap($disp) $dispsgr
}
}
}
set display_list {}
set len_list {}
set max_len 0
# always dictionary-sort results
foreach disp [lsort -dictionary $clean_list] {
# compute display element length list on sorted result
lappend display_list $sgrmap($disp)
lappend len_list [set len [string length $disp]]
if {$len > $max_len} {
set max_len $len
}
}
reportDebug "Returning $display_list"
return [list $display_list $len_list $max_len]
}
proc showModulePath {} {
set modpathlist [getModulePathList]
if {[llength $modpathlist] > 0} {
report {Search path for module files (in search order):}
foreach path $modpathlist {
report " [sgr mp $path]"
}
} else {
reportWarning {No directories on module search path}
}
}
proc displayTableHeader {sgrkey args} {
foreach {title col_len} $args {
set col "- [sgr $sgrkey $title] "
append col [string repeat - [expr {$col_len - [string length $title] -\
3}]]
lappend col_list $col
}
report [join $col_list .]
}
proc displaySeparatorLine {{title {}} {sgrkey {}}} {
set tty_cols [getTtyColumns]
if {$title eq {}} {
# adapt length if screen width is very small
set max_rep 67
set rep [expr {$tty_cols > $max_rep ? $max_rep : $tty_cols}]
report [string repeat - $rep]
} else {
set len [string length $title]
# max expr function is not supported in Tcl8.4 and earlier
if {[set lrep [expr {($tty_cols - $len - 2)/2}]] < 1} {
set lrep 1
}
if {[set rrep [expr {$tty_cols - $len - 2 - $lrep}]] < 1} {
set rrep 1
}
report "[string repeat - $lrep] [sgr $sgrkey $title] [string repeat -\
$rrep]"
}
}
# get a list of elements and print them in a column or in a
# one-per-line fashion
proc displayElementList {header sgrkey hstyle one_per_line display_idx\
display_list {len_list {}} {max_len 0}} {
set elt_cnt [llength $display_list]
reportDebug "header=$header, sgrkey=$sgrkey, hstyle=$hstyle,\
elt_cnt=$elt_cnt, max_len=$max_len, one_per_line=$one_per_line,\
display_idx=$display_idx"
# end proc if no element are to print
if {$elt_cnt == 0} {
return
}
# display header if any provided
if {$header ne {noheader}} {
if {$hstyle eq {sepline}} {
displaySeparatorLine $header $sgrkey
} else {
report [sgr $sgrkey $header]:
}
}
# display one element per line
if {$one_per_line} {
if {$display_idx} {
set idx 1
foreach elt $display_list {
append displist [format {%2d) %s } $idx $elt] \n
incr idx
}
} else {
append displist [join $display_list \n] \n
}
# elsewhere display elements in columns
} else {
if {$display_idx} {
# save room for numbers and spacing: 2 digits + ) + space
set elt_prefix_len 4
} else {
set elt_prefix_len 0
}
# save room for two spaces after element
set elt_suffix_len 2
# compute rows*cols grid size with optimized column number
# the size of each column is computed to display as much column
# as possible on each line
incr max_len $elt_suffix_len
foreach len $len_list {
lappend elt_len [incr len $elt_suffix_len]
}
set tty_cols [getTtyColumns]
# find valid grid by starting with non-optimized solution where each
# column length is equal to the length of the biggest element to display
set cur_cols [expr {int(($tty_cols - $elt_prefix_len) / $max_len)}]
# when display is found too short to display even one column
if {$cur_cols == 0} {
set cols 1
set rows $elt_cnt
array set col_width [list 0 $max_len]
} else {
set cols 0
set rows 0
}
set last_round 0
set restart_loop 0
while {$cur_cols > $cols} {
if {!$restart_loop} {
if {$last_round} {
incr cur_rows
} else {
set cur_rows [expr {int(ceil(double($elt_cnt) / $cur_cols))}]
}
for {set i 0} {$i < $cur_cols} {incr i} {
set cur_col_width($i) 0
}
for {set i 0} {$i < $cur_rows} {incr i} {
set row_width($i) 0
}
set istart 0
} else {
set istart [expr {$col * $cur_rows}]
# only remove width of elements from current col
for {set row 0} {$row < ($i % $cur_rows)} {incr row} {
incr row_width($row) -[expr {$pre_col_width + $elt_prefix_len}]
}
}
set restart_loop 0
for {set i $istart} {$i < $elt_cnt} {incr i} {
set col [expr {int($i / $cur_rows)}]
set row [expr {$i % $cur_rows}]
# restart loop if a column width change
if {[lindex $elt_len $i] > $cur_col_width($col)} {
set pre_col_width $cur_col_width($col)
set cur_col_width($col) [lindex $elt_len $i]
set restart_loop 1
break
}
# end search of maximum number of columns if computed row width
# is larger than terminal width
if {[incr row_width($row) +[expr {$cur_col_width($col) \
+ $elt_prefix_len}]] > $tty_cols} {
# start last optimization pass by increasing row number until
# reaching number used for previous column number, by doing so
# this number of column may pass in terminal width, if not
# fallback to previous number of column
if {$last_round && $cur_rows == $rows} {
incr cur_cols -1
} else {
set last_round 1
}
break
}
}
# went through all elements without reaching terminal width limit so
# this number of column solution is valid, try next with a greater
# column number
if {$i == $elt_cnt} {
set cols $cur_cols
set rows $cur_rows
array set col_width [array get cur_col_width]
# number of column is fixed if last optimization round has started
# reach end also if there is only one row of results
if {!$last_round && $rows > 1} {
incr cur_cols
}
}
}
reportDebug list=$display_list
reportDebug "rows/cols=$rows/$cols,\
lastcol_item_cnt=[expr {int($elt_cnt % $rows)}]"
for {set row 0} {$row < $rows} {incr row} {
for {set col 0} {$col < $cols} {incr col} {
set index [expr {$col * $rows + $row}]
if {$index < $elt_cnt} {
if {$display_idx} {
append displist [format "%2d) " [expr {$index +1}]]
}
# cannot use 'format' as strings may contain SGR codes
append displist [lindex $display_list $index][string repeat\
{ } [expr {$col_width($col) - [lindex $len_list $index]}]]
}
}
append displist \n
}
}
report $displist 1
reportSeparateNextContent
}
# Return conf value and from where an eventual def value has been overridden
proc displayConfig {val env_var {asked_var {}} {trans {}}} {
array set transarr $trans
# get overridden value and know what has overridden it
if {$asked_var ne {} && [info exists ::$asked_var]} {
set defby " (cmd-line)"
} elseif {$env_var ne {} && [info exists ::env($env_var)]} {
set defby " (env-var)"
} else {
set defby {}
}
# translate fetched value if translation table exists
if {[info exists transarr($val)]} {
set val $transarr($val)
}
return $val$defby
}
# build list of what to undo then do to move from an initial list to a target
# list, eventually checking element presence in extra from/to lists
proc getMovementBetweenList {from to {extfrom {}} {extto {}}} {
reportDebug "from($from) to($to) with extfrom($extfrom) extto($extto)"
set undo {}
set do {}
# determine what element to undo then do
# to restore a target list from a current list
# with preservation of the element order
set imax [if {[llength $to] > [llength $from]} {llength $to} {llength\
$from}]
set list_equal 1
for {set i 0} {$i < $imax} {incr i} {
set to_obj [lindex $to $i]
set from_obj [lindex $from $i]
# check from/to element presence in extra from/to list
set in_extfrom [isInList $extfrom $from_obj]
set in_extto [isInList $extto $to_obj]
# are elts the sames and are both part of or missing from extra lists
if {$to_obj ne $from_obj || $in_extfrom != $in_extto} {
set list_equal 0
}
if {$list_equal == 0} {
if {$to_obj ne {}} {
lappend do $to_obj
}
if {$from_obj ne {}} {
lappend undo $from_obj
}
}
}
return [list $undo $do]
}
# build list of currently loaded modules where modulename is registered minus
# module version if loaded version is the default one. a helper list may be
# provided and looked at prior to module search
proc getSimplifiedLoadedModuleList {{helper_raw_list {}}\
{helper_list {}}} {
reportDebug called.
set curr_mod_list {}
set curr_nuasked_list {}
set modpathlist [getModulePathList]
foreach mod [getLoadedModuleList] {
# if mod found in a previous LOADEDMODULES list use simplified
# version of this module found in relative helper list (previously
# computed simplified list)
if {[set helper_idx [lsearch -exact $helper_raw_list $mod]] != -1} {
set simplemod [lindex $helper_list $helper_idx]
# look through modpaths for a simplified mod name if not full path
} elseif {![isModuleFullPath $mod] && [llength $modpathlist] > 0} {
set modfile [getModulefileFromLoadedModule $mod]
set parentmod [file dirname $mod]
set simplemod $mod
# simplify to parent name as long as it resolves to current mod
while {$parentmod ne {.}} {
lassign [getPathToModule $parentmod $modpathlist] parentfile
if {$parentfile eq $modfile} {
set simplemod $parentmod
set parentmod [file dirname $parentmod]
} else {
set parentmod .
}
}
} else {
set simplemod $mod
}
lappend curr_mod_list $simplemod
# record not user asked module list in simplified version form
if {![isModuleUserAsked $mod]} {
lappend curr_nuasked_list $simplemod
}
}
return [list $curr_mod_list $curr_nuasked_list]
}
# should modulefile version be pinned when saving collection?
proc pinVersionInCollection {} {
return [expr {[get-env MODULES_COLLECTION_PIN_VERSION] eq {1}}]
}
# return saved collections found in user directory which corresponds to
# enabled collection target if any set.
proc findCollections {} {
if {[info exists ::env(HOME)]} {
set coll_search $::env(HOME)/.module/*
} else {
reportErrorAndExit {HOME not defined}
}
# find saved collections (matching target suffix)
# a target is a domain on which a collection is only valid.
# when a target is set, only the collections made for that target
# will be available to list and restore, and saving will register
# the target footprint
set colltarget [get-env MODULES_COLLECTION_TARGET]
if {$colltarget ne {}} {
append coll_search .$colltarget
}
# workaround 'glob -nocomplain' which does not return permission
# error on Tcl 8.4, so we need to avoid raising error if no match
# glob excludes by default files starting with "."
if {[catch {set coll_list [glob $coll_search]} errMsg ]} {
if {$errMsg eq "no files matched glob pattern \"$coll_search\""} {
set coll_list {}
} else {
reportErrorAndExit "Cannot access collection directory.\n$errMsg"
}
}
return $coll_list
}
# get filename corresponding to collection name provided as argument.
# name provided may already be a file name. collection description name
# (with target info if any) is returned along with collection filename
proc getCollectionFilename {coll} {
# initialize description with collection name
set colldesc $coll
if {$coll eq {}} {
reportErrorAndExit {Invalid empty collection name}
# is collection a filepath
} elseif {[string first / $coll] > -1} {
# collection target has no influence when
# collection is specified as a filepath
set collfile $coll
# elsewhere collection is a name
} elseif {[info exists ::env(HOME)]} {
set collfile $::env(HOME)/.module/$coll
# if a target is set, append the suffix corresponding
# to this target to the collection file name
set colltarget [get-env MODULES_COLLECTION_TARGET]
if {$colltarget ne {}} {
append collfile .$colltarget
# add knowledge of collection target on description
append colldesc " (for target \"$colltarget\")"
}
} else {
reportErrorAndExit {HOME not defined}
}
return [list $collfile $colldesc]
}
# generate collection content based on provided path and module lists
proc formatCollectionContent {path_list mod_list nuasked_list {sgr 0}} {
set content {}
# graphically enhance module command if asked
set modcmd [expr {$sgr ? [sgr cm module] : {module}}]
# start collection content with modulepaths
foreach path $path_list {
# 'module use' prepends paths by default so we clarify
# path order here with --append flag
append content "$modcmd use --append $path" \n
}
# then add modules
foreach mod $mod_list {
# mark modules not asked by user to restore the user asked state
set opt [expr {[isInList $nuasked_list $mod] ? {--notuasked } : {}}]
append content "$modcmd load $opt$mod" \n
}
return $content
}
# read given collection file and return the path and module lists it defines
proc readCollectionContent {collfile colldesc} {
# init lists (maybe coll does not set mod to load)
set path_list {}
set mod_list {}
set nuasked_list {}
# read file
if {[catch {
set fdata [split [readFile $collfile] \n]
} errMsg ]} {
reportErrorAndExit "Collection $colldesc cannot be read.\n$errMsg"
}
# analyze collection content
foreach fline $fdata {
if {[regexp {module use (.*)$} $fline match patharg] == 1} {
# paths are appended by default
set stuff_path append
# manage with "split" multiple paths and path options
# specified on single line, for instance:
# module use --append path1 path2 path3
foreach path [split $patharg] {
# following path is asked to be appended
if {($path eq {--append}) || ($path eq {-a})\
|| ($path eq {-append})} {
set stuff_path append
# following path is asked to be prepended
# collection generated with 'save' does not prepend
} elseif {($path eq {--prepend}) || ($path eq {-p})\
|| ($path eq {-prepend})} {
set stuff_path prepend
} else {
# ensure given path is absolute to be able to correctly
# compare with paths registered in MODULEPATH
set path [getAbsolutePath $path]
# add path to end of list
if {$stuff_path eq {append}} {
lappend path_list $path
# insert path to first position
} else {
set path_list [linsert $path_list 0 $path]
}
}
}
} elseif {[regexp {module load (.*)$} $fline match modarg] == 1} {
# manage multiple modules specified on a single line with "split",
# for instance: module load mod1 mod2 mod3
set arglist [split $modarg]
# make a list of modules that were not directly asked by user
set cleanlist [lsearch -all -inline -not -exact $arglist\
--notuasked]
if {[llength $arglist] != [llength $cleanlist]} {
set nuasked_list [concat $nuasked_list $cleanlist]
}
set mod_list [concat $mod_list $cleanlist]
}
}
return [list $path_list $mod_list $nuasked_list]
}
# analyze arg list passed to a module cmd to set options
proc parseModuleCommandArgs {cmd args} {
set show_oneperline 0
set show_mtime 0
set show_filter {}
set search_filter [expr {[getAvailInDepth] ? {} : {noindepth}}]
set dump_state 0
set otherargs {}
# parse argument list
foreach arg $args {
switch -glob -- $arg {
-t - --terse {
set show_oneperline 1
set show_mtime 0
}
-l - --long {
set show_mtime 1
set show_oneperline 0
}
-d - --default {
# in case of *-path command, -d means --delim
if {$arg eq {-d} && [string match *-path $cmd]} {
lappend otherargs $arg
} else {
set show_filter onlydefaults
}
}
-L - --latest {
set show_filter onlylatest
}
--indepth {
# empty value means 'in depth' as it is default behavior
set search_filter {}
}
--no-indepth {
set search_filter noindepth
}
--dump-state {
set dump_state 1
}
--auto - --no-auto - -f - --force {
reportWarning "Unsupported option '$arg'"
}
default {
lappend otherargs $arg
}
}
}
reportDebug "(show_oneperline=$show_oneperline, show_mtime=$show_mtime,\
show_filter=$show_filter, search_filter=$search_filter,\
dump_state=$dump_state, otherargs=$otherargs)"
return [list $show_oneperline $show_mtime $show_filter $search_filter\
$dump_state $otherargs]
}
# unload phase of a list of modules reload process
proc reloadModuleListUnloadPhase {lmname {force 0} {errmsgtpl {Reload of\
_MOD_ failed}} {context unload}} {
upvar $lmname lmlist
# unload one by one to ensure same behavior whatever auto_handling state
foreach mod [lreverse $lmlist] {
# save user asked state before it vanishes
set isuasked($mod) [isModuleUserAsked $mod]
# force unload even if requiring mods are not part of the unload list
# (violation state) as modules are loaded again just after
if {[cmdModuleUnload $context match 0 1 0 0 $mod]} {
# avoid failing module on load phase
set lmlist [replaceFromList $lmlist $mod]
set errMsg [string map [list _MOD_ $mod] $errmsgtpl]
if {$force} {
reportWarning $errMsg 1
# stop if one unload fails unless force mode enabled
} else {
error $errMsg
}
}
}
return [array get isuasked]
}
# load phase of a list of modules reload process
proc reloadModuleListLoadPhase {lmname isuaskedlist {force 0} {errmsgtpl\
{Reload of _MOD_ failed}} {context load}} {
upvar $lmname lmlist
array set isuasked $isuaskedlist
# loads are made with auto handling mode disabled to avoid disturbances
# from a missing prereq automatically reloaded, so these module loads may
# fail as prereq may not be satisfied anymore
set ::g_auto_handling 0
foreach mod $lmlist {
# reload module with user asked property preserved
if {[cmdModuleLoad $context $isuasked($mod) $mod]} {
set errMsg [string map [list _MOD_ $mod] $errmsgtpl]
if {$force} {
reportWarning $errMsg 1
# stop if one load fails unless force mode enabled
} else {
error $errMsg
}
}
}
set ::g_auto_handling 1
}
########################################################################
# command line commands
#
proc cmdModuleList {show_oneperline show_mtime} {
set loadedmodlist [getLoadedModuleList]
if {[llength $loadedmodlist] == 0} {
report {No Modulefiles Currently Loaded.}
} else {
set display_list {}
set default_colored [expr {[sgr de {}] ne {}}]
set len_list {}
set max_len 0
foreach mod $loadedmodlist {
if {$show_oneperline} {
lappend display_list $mod
} else {
set modfile [getModulefileFromLoadedModule $mod]
# skip rc find and execution if mod is registered as full path
if {[isModuleFullPath $mod]} {
set mtime [getFileMtime $mod]
set tag_list {}
# or if loaded module is a virtual module
} elseif {[isModuleVirtual $mod $modfile]} {
set mtime [getFileMtime $modfile]
set tag_list {}
} else {
# call getModules to find and execute rc files for this mod
set dir [getModulepathFromModuleName $modfile $mod]
array set mod_list [getModules $dir $mod $show_mtime]
# fetch info only if mod found
if {[info exists mod_list($mod)]} {
set mtime [lindex $mod_list($mod) 1]
set tag_list [getVersAliasList $mod]
} else {
set tag_list {}
}
}
if {$show_mtime} {
if {[info exists mtime]} {
set clock_mtime [clock format $mtime -format\
{%Y/%m/%d %H:%M:%S}]
unset mtime
} else {
set clock_mtime {}
}
# add to display file modification time in addition to tags
lassign [formatListEltToLongDisplay $mod {} {} $tag_list sy\
$clock_mtime $default_colored] disp dispsgr
} else {
lassign [formatListEltToDisplay $mod {} {} $tag_list sy 1\
$default_colored] disp dispsgr
lappend len_list [set len [string length $disp]]
if {$len > $max_len} {
set max_len $len
}
}
lappend display_list $dispsgr
}
}
if {$show_mtime} {
displayTableHeader hi Package 39 Versions 19 {Last mod.} 19
}
report {Currently Loaded Modulefiles:}
if {$show_mtime || $show_oneperline} {
set display_idx 0
set one_per_line 1
} else {
set display_idx 1
set one_per_line 0
}
displayElementList noheader {} {} $one_per_line $display_idx\
$display_list $len_list $max_len
}
}
proc cmdModuleDisplay {args} {
reportDebug "displaying $args"
pushMode display
set first_report 1
foreach mod $args {
lassign [getPathToModule $mod] modfile modname
if {$modfile ne {}} {
# only one separator lines between 2 modules
if {$first_report} {
displaySeparatorLine
set first_report 0
}
report [sgr hi $modfile]:\n
execute-modulefile $modfile $modname $mod
displaySeparatorLine
}
}
popMode
}
proc cmdModulePaths {mod} {
reportDebug ($mod)
set dir_list [getModulePathList exiterronundef]
foreach dir $dir_list {
array unset mod_list
array set mod_list [getModules $dir $mod 0 rc_defs_included]
# prepare list of dirs for alias/symbol target search, will first search
# in currently looked dir, then in other dirs following precedence order
set target_dir_list [concat [list $dir] [replaceFromList $dir_list\
$dir]]
# build list of modulefile to print
foreach elt [array names mod_list] {
switch -- [lindex $mod_list($elt) 0] {
modulefile {
lappend ::g_return_text $dir/$elt
}
virtual {
lappend ::g_return_text [lindex $mod_list($elt) 2]
}
alias - version {
# resolve alias target
set aliastarget [lindex $mod_list($elt) 1]
lassign [getPathToModule $aliastarget $target_dir_list]\
modfile modname
# add module target as result instead of alias
if {$modfile ne {} && ![info exists mod_list($modname)]} {
lappend ::g_return_text $modfile
}
}
}
}
}
# sort results if any and remove duplicates
if {[info exists ::g_return_text]} {
set ::g_return_text [lsort -dictionary -unique $::g_return_text]
} else {
# set empty value to return empty if no result
set ::g_return_text {}
}
}
proc cmdModulePath {mod} {
reportDebug ($mod)
lassign [getPathToModule $mod] modfile modname
# if no result set empty value to return empty
set ::g_return_text $modfile
}
proc cmdModuleWhatIs {{mod {}}} {
cmdModuleSearch $mod {}
}
proc cmdModuleApropos {{search {}}} {
cmdModuleSearch {} $search
}
proc cmdModuleSearch {{mod {}} {search {}}} {
reportDebug "($mod, $search)"
# disable error reporting to avoid modulefile errors
# to mix with valid search results
inhibitErrorReport
lappend searchmod rc_defs_included
if {$mod eq {}} {
lappend searchmod wild
}
set foundmod 0
pushMode whatis
set dir_list [getModulePathList exiterronundef]
foreach dir $dir_list {
array unset mod_list
array set mod_list [getModules $dir $mod 0 $searchmod]
array unset interp_list
array set interp_list {}
# build list of modulefile to interpret
foreach elt [array names mod_list] {
switch -- [lindex $mod_list($elt) 0] {
modulefile {
set interp_list($elt) $dir/$elt
# register module name in a global list (shared across
# modulepaths) to get hints when solving aliases/version
set full_list($elt) 1
}
virtual {
set interp_list($elt) [lindex $mod_list($elt) 2]
set full_list($elt) 1
}
alias - version {
# resolve alias target
set elt_target [lindex $mod_list($elt) 1]
if {![info exists full_list($elt_target)]} {
lassign [getPathToModule $elt_target $dir]\
modfile modname issuetype issuemsg
# add module target as result instead of alias
if {$modfile ne {} && ![info exists mod_list($modname)]} {
set interp_list($modname) $modfile
set full_list($modname) 1
} elseif {$modfile eq {}} {
# if module target not found in current modulepath add to
# list for global search after initial modulepath lookup
if {[string first {Unable to locate} $issuemsg] == 0} {
set extra_search($modname) [list $dir [expr {$elt eq\
$mod}]]
# register resolution error if alias name matches search
} elseif {$elt eq $mod} {
set err_list($modname) [list $issuetype $issuemsg]
}
}
}
}
invalid - accesserr {
# register any error occuring on element matching search
if {$elt eq $mod} {
set err_list($elt) $mod_list($elt)
}
}
}
}
# in case during modulepath lookup we find an alias target we were
# looking for in previous modulepath, remove this element from global
# search list
foreach elt [array names extra_search] {
if {[info exists full_list($elt)]} {
unset extra_search($elt)
}
}
# save results from this modulepath for interpretation step as there
# is an extra round of search to match missing alias target, we cannot
# process modulefiles found immediately
if {[array size interp_list] > 0} {
set interp_save($dir) [array get interp_list]
}
}
# find target of aliases in all modulepath except the one already tried
foreach elt [array names extra_search] {
lassign [getPathToModule $elt {} no [lindex $extra_search($elt) 0]]\
modfile modname issuetype issuemsg issuefile
# found target so append it to results in corresponding modulepath
if {$modfile ne {}} {
# get belonging modulepath dir depending of module kind
if {[isModuleVirtual $modname $modfile]} {
set dir [findModulepathFromModulefile\
$::g_sourceVirtual($modname)]
} else {
set dir [getModulepathFromModuleName $modfile $modname]
}
array unset interp_list
if {[info exists interp_save($dir)]} {
array set interp_list $interp_save($dir)
}
set interp_list($modname) $modfile
set interp_save($dir) [array get interp_list]
# register resolution error if primal alias name matches search
} elseif {$modfile eq {} && [lindex $extra_search($elt) 1]} {
set err_list($modname) [list $issuetype $issuemsg $issuefile]
}
}
# prepare string translation to highlight search query string
set matchmodmap [prepareMapToHightlightSubstr $mod]
set matchsearchmap [prepareMapToHightlightSubstr $search]
# interpret all modulefile we got for each modulepath
foreach dir $dir_list {
if {[info exists interp_save($dir)]} {
array unset interp_list
array set interp_list $interp_save($dir)
set foundmod 1
set display_list {}
# interpret every modulefiles obtained to get their whatis text
foreach elt [lsort -dictionary [array names interp_list]] {
set ::g_whatis {}
execute-modulefile $interp_list($elt) $elt $elt
# treat whatis as a multi-line text
if {$search eq {} || [regexp -nocase $search $::g_whatis]} {
set eltsgr [string map $matchmodmap $elt]
foreach line $::g_whatis {
set linesgr [string map $matchsearchmap $line]
lappend display_list "[string repeat { } [expr {20 -\
[string length $elt]}]]$eltsgr: $linesgr"
}
}
}
displayElementList $dir mp sepline 1 0 $display_list
}
}
popMode
reenableErrorReport
# report errors if a modulefile was searched but not found
if {$mod ne {} && !$foundmod} {
# no error registered means nothing was found to match search
if {![array exists err_list]} {
set err_list($mod) [list none "Unable to locate a modulefile for\
'$mod'"]
}
foreach elt [array names err_list] {
eval reportIssue $err_list($elt)
}
}
}
proc cmdModuleSwitch {uasked old {new {}}} {
# if a single name is provided it matches for the module to load and in
# this case the module to unload is searched to find the closest match
# (loaded module that shares at least the same root name)
if {$new eq {}} {
set new $old
set unload_match close
} else {
set unload_match match
}
# save orig names to register them as deps if called from modulefile
set argnew $new
set argold [expr {$new eq $old ? {} : $old}]
reportDebug "old='$old' new='$new' (uasked=$uasked)"
# record sumup messages from underlying unload/load actions under the same
# switch message record id to report (evaluation messages still go under
# their respective unload/load block
pushMsgRecordId switch-$old-$new-[getEvalModuleStackDepth]
set ret [cmdModuleUnload swunload $unload_match 1 0 0 0 $old]
# register modulefile to unload as conflict if an unload module is
# mentionned on this module switch command set in a modulefile
set orig_auto_handling [getAutoHandling]
if {!$uasked && $argold ne {}} {
# temporarily disable auto handling just to record deps, not to try to
# load or unload them (already tried)
set ::g_auto_handling 0
catch {conflict $argold}
set ::g_auto_handling $orig_auto_handling
}
# attempt load and depre reload only if unload succeed
if {!$ret} {
cmdModuleLoad swload $uasked $new
if {[getAutoHandling] && [info exists deprelist] && [llength\
$deprelist] > 0} {
# cmdModuleUnload handles the DepUn, UReqUn mechanisms and the unload
# phase of the DepRe mechanism. List of DepRe mods and their user
# asked state is set from cmdModuleUnload procedure to be used here
# for the load phase of the DepRe mechanism.
# Try DepRe load phase: load failure will not lead to switch failure
reloadModuleListLoadPhase deprelist [array get isuasked]\
1 {Reload of dependent _MOD_ failed} depre
}
# report a summary of automated evaluations if no error
reportModuleEval
}
# report all recorded sumup messages for this evaluation
reportMsgRecord "Switching from [sgr hi $old] to [sgr hi $new]"
popMsgRecordId
# register modulefile to load as prereq when called from modulefile
if {!$uasked && !$ret && $argnew ne {}} {
set ::g_auto_handling 0
prereq $argnew
set ::g_auto_handling $orig_auto_handling
}
}
proc cmdModuleSave {{coll default}} {
reportDebug $coll
if {![areModuleConstraintsSatisfied]} {
reportErrorAndExit {Cannot save collection, some module constraints are\
not satistied}
}
# format collection content, version number of modulefile are saved if
# version pinning is enabled
if {[pinVersionInCollection]} {
set curr_mod_list [getLoadedModuleList]
set curr_nuasked_list [getLoadedModuleNotUserAskedList]
} else {
lassign [getSimplifiedLoadedModuleList] curr_mod_list curr_nuasked_list
}
set save [formatCollectionContent [getModulePathList returnempty 0]\
$curr_mod_list $curr_nuasked_list]
if { [string length $save] == 0} {
reportErrorAndExit {Nothing to save in a collection}
}
# get coresponding filename and its directory
lassign [getCollectionFilename $coll] collfile colldesc
set colldir [file dirname $collfile]
if {![file exists $colldir]} {
reportDebug "Creating $colldir"
file mkdir $colldir
} elseif {![file isdirectory $colldir]} {
reportErrorAndExit "$colldir exists but is not a directory"
}
reportDebug "Saving $collfile"
if {[catch {
set fid [open $collfile w]
puts $fid $save
close $fid
} errMsg ]} {
reportErrorAndExit "Collection $colldesc cannot be saved.\n$errMsg"
}
}
proc cmdModuleRestore {{coll default}} {
reportDebug $coll
# get coresponding filename
lassign [getCollectionFilename $coll] collfile colldesc
if {![file exists $collfile]} {
reportErrorAndExit "Collection $colldesc cannot be found"
}
# read collection
lassign [readCollectionContent $collfile $colldesc] coll_path_list\
coll_mod_list coll_nuasked_list
# collection should at least define a path or a mod
if {[llength $coll_path_list] == 0 && [llength $coll_mod_list] == 0} {
reportErrorAndExit "$colldesc is not a valid collection"
}
# fetch what is currently loaded
set curr_path_list [getModulePathList returnempty 0]
# get current loaded module list in simplified and raw versions
# these lists may be used later on, see below
set curr_mod_list_raw [getLoadedModuleList]
set curr_nuasked_list_raw [getLoadedModuleNotUserAskedList]
lassign [getSimplifiedLoadedModuleList] curr_mod_list curr_nuasked_list
# determine what module to unload to restore collection
# from current situation with preservation of the load order
lassign [getMovementBetweenList $curr_mod_list $coll_mod_list\
$curr_nuasked_list $coll_nuasked_list] mod_to_unload mod_to_load
# determine unload movement with raw loaded list in case versions are
# pinning in saved collection
lassign [getMovementBetweenList $curr_mod_list_raw $coll_mod_list\
$curr_nuasked_list_raw $coll_nuasked_list] mod_to_unload_raw\
mod_to_load_raw
if {[llength $mod_to_unload] > [llength $mod_to_unload_raw]} {
set mod_to_unload $mod_to_unload_raw
}
# proceed as well for modulepath
lassign [getMovementBetweenList $curr_path_list $coll_path_list] \
path_to_unuse path_to_use
# set a unique id to record messages related to this evaluation.
pushMsgRecordId restore-$coll-[getEvalModuleStackDepth]
# unload modules one by one (no dependency auto unload)
if {[llength $mod_to_unload] > 0} {
eval cmdModuleUnload unmo match 0 0 0 0 [lreverse $mod_to_unload]
}
# unuse paths
if {[llength $path_to_unuse] > 0} {
eval cmdModuleUnuse [lreverse $path_to_unuse]
}
# since unloading a module may unload other modules or
# paths, what to load/use has to be determined after
# the undo phase, so current situation is fetched again
set curr_path_list [getModulePathList returnempty 0]
# here we may be in a situation were no more path is left
# in module path, so we cannot easily compute the simplified loaded
# module list. so we provide two helper lists: simplified and raw
# versions of the loaded module list computed before starting to
# unload modules. these helper lists may help to learn the
# simplified counterpart of a loaded module if it was already loaded
# before starting to unload modules
lassign [getSimplifiedLoadedModuleList $curr_mod_list_raw $curr_mod_list]\
curr_mod_list curr_nuasked_list
set curr_mod_list_raw [getLoadedModuleList]
set curr_nuasked_list_raw [getLoadedModuleNotUserAskedList]
# determine what module to load to restore collection
# from current situation with preservation of the load order
lassign [getMovementBetweenList $curr_mod_list $coll_mod_list\
$curr_nuasked_list $coll_nuasked_list] mod_to_unload mod_to_load
# determine load movement with raw loaded list in case versions are
# pinning in saved collection
lassign [getMovementBetweenList $curr_mod_list_raw $coll_mod_list\
$curr_nuasked_list_raw $coll_nuasked_list] mod_to_unload_raw\
mod_to_load_raw
if {[llength $mod_to_load] > [llength $mod_to_load_raw]} {
set mod_to_load $mod_to_load_raw
}
# proceed as well for modulepath
lassign [getMovementBetweenList $curr_path_list $coll_path_list] \
path_to_unuse path_to_use
# use paths
if {[llength $path_to_use] > 0} {
# always append path here to guaranty the order
# computed above in the movement lists
eval cmdModuleUse --append $path_to_use
}
# load modules one by one with user asked state preserved
foreach mod $mod_to_load {
cmdModuleLoad lomo [notInList $coll_nuasked_list $mod] $mod
}
reportModuleEval
reportMsgRecord "Restoring collection [sgr hi $colldesc]"
popMsgRecordId
}
proc cmdModuleSaverm {{coll default}} {
reportDebug $coll
# avoid to remove any kind of file with this command
if {[string first / $coll] > -1} {
reportErrorAndExit {Command does not remove collection specified as\
filepath}
}
# get coresponding filename
lassign [getCollectionFilename $coll] collfile colldesc
if {![file exists $collfile]} {
reportErrorAndExit "Collection $colldesc cannot be found"
}
# attempt to delete specified colletion
if {[catch {
file delete $collfile
} errMsg ]} {
reportErrorAndExit "Collection $colldesc cannot be removed.\n$errMsg"
}
}
proc cmdModuleSaveshow {{coll default}} {
reportDebug $coll
# get coresponding filename
lassign [getCollectionFilename $coll] collfile colldesc
if {![file exists $collfile]} {
reportErrorAndExit "Collection $colldesc cannot be found"
}
# read collection
lassign [readCollectionContent $collfile $colldesc] coll_path_list\
coll_mod_list coll_nuasked_list
# collection should at least define a path or a mod
if {[llength $coll_path_list] == 0 && [llength $coll_mod_list] == 0} {
reportErrorAndExit "$colldesc is not a valid collection"
}
displaySeparatorLine
report [sgr hi $collfile]:\n
report [formatCollectionContent $coll_path_list $coll_mod_list\
$coll_nuasked_list 1]
displaySeparatorLine
}
proc cmdModuleSavelist {show_oneperline show_mtime} {
# if a target is set, only list collection matching this
# target (means having target as suffix in their name)
set colltarget [get-env MODULES_COLLECTION_TARGET]
if {$colltarget ne {}} {
set suffix .$colltarget
set targetdesc " (for target \"$colltarget\")"
} else {
set suffix {}
set targetdesc {}
}
reportDebug "list collections for target \"$colltarget\""
set coll_list [findCollections]
if { [llength $coll_list] == 0} {
report "No named collection$targetdesc."
} else {
set list {}
if {$show_mtime} {
displayTableHeader hi Collection 59 {Last mod.} 19
}
report "Named collection list$targetdesc:"
set display_list {}
set len_list {}
set max_len 0
if {$show_mtime || $show_oneperline} {
set display_idx 0
set one_per_line 1
} else {
set display_idx 1
set one_per_line 0
}
foreach coll [lsort -dictionary $coll_list] {
# remove target suffix from names to display
regsub $suffix$ [file tail $coll] {} mod
# no need to test mod consistency as findCollections does not return
# collection whose name starts with "."
if {$show_mtime} {
set filetime [clock format [getFileMtime $coll]\
-format {%Y/%m/%d %H:%M:%S}]
lappend display_list [format %-60s%19s $mod $filetime]
} else {
lappend display_list $mod
lappend len_list [set len [string length $mod]]
if {$len > $max_len} {
set max_len $len
}
}
}
displayElementList noheader {} {} $one_per_line $display_idx\
$display_list $len_list $max_len
}
}
proc cmdModuleSource {args} {
reportDebug $args
foreach fpath $args {
set absfpath [getAbsolutePath $fpath]
if {$fpath eq {}} {
reportErrorAndExit {File name empty}
} elseif {[file exists $absfpath]} {
pushMode load
# relax constraint of having a magic cookie at the start of the
# modulefile to execute as sourced files may need more flexibility
# as they may be managed outside of the modulefile environment like
# the initialization modulerc file
execute-modulefile $absfpath $absfpath $absfpath 0
popMode
} else {
reportErrorAndExit "File $fpath does not exist"
}
}
}
proc cmdModuleUnsource {args} {
reportDebug $args
foreach fpath $args {
set absfpath [getAbsolutePath $fpath]
if {$fpath eq {}} {
reportErrorAndExit {File name empty}
} elseif {[file exists $absfpath]} {
pushMode unload
# relax constraint of having a magic cookie at the start of the
# modulefile to execute as sourced files may need more flexibility
# as they may be managed outside of the modulefile environment like
# the initialization modulerc file
execute-modulefile $absfpath $absfpath $absfpath 0
popMode
} else {
reportErrorAndExit "File $fpath does not exist"
}
}
}
proc cmdModuleLoad {context uasked args} {
reportDebug "loading $args (context=$context, uasked=$uasked)"
set ret 0
pushMode load
foreach mod $args {
# if a switch action is ongoing...
if {$context eq {swload}} {
set swprocessing 1
# context is ReqLo if switch is called from a modulefile
if {![isMsgRecordIdTop]} {
set context reqlo
}
}
# record evaluation attempt on specified module name
registerModuleEvalAttempt $context $mod
lassign [getPathToModule $mod] modfile modname
if {$modfile eq {}} {
set ret 1
# go to next module to unload
continue
}
if {[isModuleEvalFailed load $modname]} {
reportDebug "$modname ($modfile) load was already tried and failed"
# nullify this evaluation attempt to avoid duplicate issue report
unregisterModuleEvalAttempt $context $mod
continue
}
# if a switch action is ongoing...
if {[info exists swprocessing]} {
# pass the DepRe mod list to the calling cmdModuleSwitch procedure to
# let it handle the load phase of the DepRe mechanism along with the
# DepRe modules set from switched off module.
upvar deprelist swdeprelist
upvar isuasked isuasked
# transmit loaded mod name for switch report summary
uplevel 1 set new $modname
}
# set a unique id to record messages related to this evaluation.
set msgrecid load-$modname-[getEvalModuleStackDepth]
pushMsgRecordId $msgrecid
# record evaluation attempt on actual module name
registerModuleEvalAttempt $context $modname
registerModuleEvalAttempt $context $modfile
# check if passed modname correspond to an already loaded modfile
# and get its loaded name (in case it has been loaded as full path)
set loadedmodname [getLoadedMatchingName $modname]
if {$loadedmodname ne {}} {
set modname $loadedmodname
}
pushSettings
if {[set errCode [catch {
if {[isModuleLoaded $modname] || [isModuleLoading $modname]} {
reportDebug "$modname ($modfile) already loaded/loading"
# exit treatment but no need to restore settings
continue
}
# register altname of modname prior any conflict check
eval setLoadedAltname $modname [getAllModuleResolvedName $modname]
if {[getAutoHandling]} {
# get loaded modules holding a requirement on modname and able to
# be reloaded
set deprelist [getUnmetDependentLoadedModuleList $modname]
reportDebug "depre mod list is '$deprelist'"
# Reload all modules that have declared a prereq on mod as they
# may take benefit from their prereq availability if it is newly
# loaded. First perform unload phase of the reload, prior mod load
# to ensure these dependent modules are unloaded with the same
# loaded prereq as when they were loaded
if {[llength $deprelist] > 0} {
array set isuasked [reloadModuleListUnloadPhase deprelist\
[getForce] {Unload of dependent _MOD_ failed} depun]
if {[info exists swprocessing]} {
set swdeprelist [expr {[info exists swdeprelist] ? [concat\
$deprelist $swdeprelist] : $deprelist}]
}
}
}
if {[execute-modulefile $modfile $modname $mod]} {
break
}
# register this evaluation on the main one that triggered it (after
# load evaluation to report correct order with other evaluations)
registerModuleEval $context $modname
# raise an error if a conflict violation is detected
# do that after modfile evaluation to give it the chance to solve its
# (module unload) conflicts through its evaluation
lassign [doesModuleConflict $modname] doescon modconlist\
moddecconlist
set retisconun [eval isModuleEvaluated conun $modconlist]
if {![set retiseval [eval isModuleEvaluated any $modconlist]] ||\
[currentMsgRecordId] ne [topMsgRecordId] || !$retisconun} {
# more appropriate msg if an evaluation was attempted or is
# by-passed. error is reported using declared conflict name (as if
# it was raised raised from a conflict modulefile command)
set conmsg [expr {$retiseval || [getForce] ? [getConIsLoadedMsg\
$moddecconlist [is-loading $modconlist]] : [getErrConflictMsg\
$modname $moddecconlist]}]
}
# still proceed if force mode enabled
if {[getForce] && $doescon} {
# report warning if not already done
set report_con 1
if {[info exists ::report_conflict($modname)]} {
# check if conflict has not been already reported with an
# alternative name
foreach modalt [concat [getLoadedAltname $modconlist]\
$modconlist $moddecconlist] {
foreach reportmod $::report_conflict($modname) {
if {[doesModuleMatchesName $modalt $reportmod]} {
set report_con 0
break
}
}
}
}
if {$report_con && [info exists conmsg]} {
reportWarning $conmsg
}
# raise conun-specific msg to top level if attempted
if {$retisconun} {
reportWarning [getErrConUnMsg $moddecconlist] 1
}
} elseif {$doescon} {
if {$retisconun} {
if {[info exists conmsg]} {
reportError $conmsg
}
# raise conun-specific msg to top level if attempted
error [getErrConUnMsg $moddecconlist]
} else {
set errlocalreport 1
error $conmsg
}
}
add-path append LOADEDMODULES $modname
# allow duplicate modfile entries for virtual modules
add-path append --duplicates _LMFILES_ $modfile
# update cache arrays
setLoadedModule $modname $modfile $uasked
# register declared conflict in environment
if {[set modcon [getLoadedConflict $modname 1]] ne {}} {
add-path append MODULES_LMCONFLICT $modcon
}
# declare the prereq of this module
if {[set modpre [getLoadedPrereq $modname 1]] ne {}} {
add-path append MODULES_LMPREREQ $modpre
}
# declare module as not asked by user (automatically loaded as
# dependency) if it is the case
if {!$uasked} {
add-path append MODULES_LMNOTUASKED $modname
}
# declare the alternative names of this module
if {[set modalt [getLoadedAltname $modname 1]] ne {}} {
add-path append MODULES_LMALTNAME $modalt
}
# Load phase of dependent module reloading. These modules can adapt
# now that mod is seen loaded. Except if switch action ongoing (DepRe
# load phase will occur from switch)
if {[getAutoHandling] && [llength $deprelist] > 0 && ![info exists\
swprocessing]} {
reloadModuleListLoadPhase deprelist [array get isuasked]\
[getForce] {Reload of dependent _MOD_ failed} depre
}
# report a summary of automated evaluations if no error
reportModuleEval
} errMsg]] != 0 && $errCode != 4} {
if {$errMsg ne {}} {
reportError $errMsg [expr {![info exists errlocalreport]}]
}
# report switched-on module load failure under switch info block
# unless the above reportError call already put a mesg to this block
if {[info exists swprocessing] && ($errMsg eq {} || [info exists\
errlocalreport])} {
# warn as this issue does not lead to a rollback of switch action
reportWarning "Load of switched-on $modname failed" 1
}
# rollback settings if some evaluation went wrong
set ret 1
restoreSettings
# remove from successfully evaluated module list
registerModuleEval $context $modname 1 load
unset -nocomplain errlocalreport
}
popSettings
# report all recorded messages for this evaluation
reportMsgRecord "Loading [sgr hi $modname]"
popMsgRecordId
}
popMode
return $ret
}
proc cmdModuleUnload {context match auto force onlyureq onlyndep args} {
reportDebug "unloading $args (context=$context, match=$match, auto=$auto,\
force=$force, onlyureq=$onlyureq, onlyndep=$onlyndep)"
set ret 0
pushMode unload
foreach mod $args {
# if a switch action is ongoing...
if {$context eq {swunload}} {
set swprocessing 1
# context is ConUn if switch is called from a modulefile
if {![isMsgRecordIdTop]} {
set context conun
}
}
# record evaluation attempt on specified module name
registerModuleEvalAttempt $context $mod
# resolve by also looking at matching loaded module
lassign [getPathToModule $mod {} $match] modfile modname errkind
if {$modfile eq {}} {
# no error return if module is not loaded
if {$errkind eq {notloaded}} {
reportDebug "$modname is not loaded"
} else {
set ret 1
}
# go to next module to unload
continue
}
if {$onlyureq && ![isModuleUnloadable $modname]} {
reportDebug "$modname ($modfile) is required by loaded module or\
asked by user"
continue
}
if {[isModuleEvalFailed unload $modname]} {
reportDebug "$modname ($modfile) unload was already tried and failed"
# nullify this evaluation attempt to avoid duplicate issue report
unregisterModuleEvalAttempt $context $mod
continue
}
# if a switch action is ongoing...
if {[info exists swprocessing]} {
# pass the DepRe mod list to the calling cmdModuleSwitch
# procedure to let it handle the load phase of the DepRe
# mechanism once the switched-to module will be loaded
upvar deprelist deprelist
upvar isuasked isuasked
# transmit unloaded mod name for switch report summary
uplevel 1 set old $modname
}
# set a unique id to record messages related to this evaluation.
set msgrecid unload-$modname-[getEvalModuleStackDepth]
pushMsgRecordId $msgrecid
# record evaluation attempt on actual module name
registerModuleEvalAttempt $context $modname
registerModuleEvalAttempt $context $modfile
pushSettings
if {[set errCode [catch {
# error if unloading module violates a registered prereq
# and auto handling mode is disabled
set prereq_list [getDependentLoadedModuleList $modname]
if {[llength $prereq_list] > 0 && (![getAutoHandling] || !$auto)} {
# force mode should not affect if we only look for mods w/o dep
if {([getForce] || $force) && !$onlyndep} {
# in case unload is called for a DepRe mechanism or a purge do
# not warn about prereq violation enforced as it is due to the
# dependent module which is already in a violation state
if {$auto || !$force} {
reportWarning [getDepLoadedMsg $prereq_list]
}
} else {
set errlocalreport 1
# exit treatment but no need to set return code to error if
# called from a 'module unload' command in a modulefile in a
# load evaluation mode, as set conflict will raise error at end
# of modulefile evaluation
if {$onlyndep} {
set errharmless 1
}
error [expr {[eval isModuleEvaluated any $prereq_list] ?\
[getDepLoadedMsg $prereq_list] : [getErrPrereqMsg $modname\
$prereq_list 0]}]
}
}
if {[getAutoHandling] && $auto} {
# compute lists of modules to update due to modname unload prior
# unload to get requirement info before it vanishes
# DepUn: Dependent to Unload (modules actively requiring modname
# or a module part of this DepUn batch)
set depunnpolist [getDependentLoadedModuleList $modname 1 0 1 0]
set depunlist [getDependentLoadedModuleList $modname 1 0 0 0]
# look at both regular dependencies or No Particular Order
# dependencies: use NPO result if situation can be healed with NPO
# dependencies, which will be part of DepRe list to restore the
# correct loading order for them
if {[llength $depunnpolist] <= [llength $depunlist]} {
set depunlist $depunnpolist
}
reportDebug "depun mod list is '$depunlist'"
# do not check for UReqUn mods coming from DepUn modules as these
# DepUn modules are reloaded
set urequnqry [expr {[info exists swprocessing] ? $modname :\
[concat $depunlist [list $modname]]}]
# UReqUn: Useless Requirement to Unload (autoloaded requirements
# of modname or DepUn modules not required by any remaining mods)
set urequnlist [getUnloadableLoadedModuleList $urequnqry]
reportDebug "urequn mod list is '$urequnlist'"
# DepRe: Dependent to Reload (modules optionnaly dependent or in
# conflict with modname, DepUn or UReqUn modules + modules
# dependent of a module part of this DepRe batch)
set deprelist [getDependentLoadedModuleList [concat $urequnlist\
$depunlist [list $modname]] 0 0 1 0 1 1]
reportDebug "depre mod list is '$deprelist'"
# DepUn mods are merged into the DepRe list as an attempt to
# reload these DepUn mods is made once switched-to mod loaded
if {[info exists swprocessing]} {
set deprelist [sortModulePerLoadedAndDepOrder [concat\
$depunlist $deprelist] 1]
set depunlist {}
}
# Reload of all DepRe mods, as they may adapt from the mod unloads
# happening here. First perform unload phase of the reload, prior
# mod unloads to ensure these dependent mods are unloaded with the
# same loaded prereq as when they were loaded. Avoid modules not
# satisfying their constraint.
if {[llength $deprelist] > 0} {
array set isuasked [reloadModuleListUnloadPhase deprelist\
[getForce] {Unload of dependent _MOD_ failed} depun]
}
# DepUn modules unload prior main mod unload
if {[llength $depunlist] > 0} {
foreach unmod [lreverse $depunlist] {
if {[cmdModuleUnload depun match 0 0 0 0 $unmod]} {
# stop if one unload fails unless force mode enabled
set errMsg "Unload of dependent $unmod failed"
if {[getForce] || $force} {
reportWarning $errMsg 1
} else {
error $errMsg
}
}
}
}
}
# register this evaluation on the main one that triggered it (prior
# unload evaluation to report correct order with other evaluations)
registerModuleEval $context $modname
if {[execute-modulefile $modfile $modname $mod]} {
break
}
# get module position in loaded list to remove corresponding loaded
# modulefile (entry at same position in _LMFILES_)
# need the unfiltered loaded module list to get correct index
set lmidx [lsearch -exact [getLoadedModuleList 0] $modname]
unload-path LOADEDMODULES $modname
unload-path --index _LMFILES_ $lmidx
if {![isModuleUserAsked $modname]} {
unload-path MODULES_LMNOTUASKED $modname
}
# update cache arrays
unsetLoadedModule $modname $modfile
# unregister declared conflict
if {[set modcon [getLoadedConflict $modname 1]] ne {}} {
unload-path MODULES_LMCONFLICT $modcon
}
unsetLoadedConflict $modname
# unset prereq declared for this module
if {[llength [set modpre [getLoadedPrereq $modname]]] > 0} {
unload-path MODULES_LMPREREQ [getLoadedPrereq $modname 1]
}
unsetLoadedPrereq $modname
# unset alternative names declared for this module
if {[llength [set modalt [getLoadedAltname $modname]]] >0} {
unload-path MODULES_LMALTNAME [getLoadedAltname $modname 1]
}
unsetLoadedAltname $modname
if {[getAutoHandling] && $auto} {
# UReqUn modules unload now DepUn+main mods are unloaded
if {[llength $urequnlist] > 0} {
set urequnlist [lreverse $urequnlist]
for {set i 0} {$i < [llength $urequnlist]} {incr i 1} {
set unmod [lindex $urequnlist $i]
if {[cmdModuleUnload urequn match 0 0 0 0 $unmod]} {
# just warn if UReqUn module cannot be unloaded, main
# unload process continues, just the UReqUn modules that
# are required by unmod (whose unload failed) are
# withdrawn from UReqUn module list
reportWarning "Unload of useless requirement $unmod\
failed" 1
lassign [getDiffBetweenList $urequnlist\
[getRequiredLoadedModuleList $unmod]] urequnlist
}
}
}
# DepRe modules load phase now DepUn+UReqUn+main mods are unloaded
# except if a switch action is ongoing as this DepRe load phase
# will occur after the new mod load
if {[llength $deprelist] > 0 && ![info exists swprocessing]} {
reloadModuleListLoadPhase deprelist [array get isuasked]\
[getForce] {Reload of dependent _MOD_ failed} depre
}
}
# report a summary of automated evaluations if no error
reportModuleEval
} errMsg]] != 0 && $errCode != 4} {
if {$errMsg ne {}} {
reportError $errMsg [expr {![info exists errlocalreport]}]
}
# report switched-off module unload failure under switch info block
# unless the above reportError call already put a mesg to this block
if {[info exists swprocessing] && ($errMsg eq {} || [info exists\
errlocalreport])} {
reportError "Unload of switched-off $modname failed" 1
}
# rollback settings if some evaluation went wrong
if {![info exists errharmless]} {
set ret 1
restoreSettings
# remove from successfully evaluated module list
registerModuleEval $context $modname 1 unload
}
unset -nocomplain errlocalreport errharmless
}
popSettings
# report all recorded messages for this evaluation
reportMsgRecord "Unloading [sgr hi $modname]"
popMsgRecordId
}
popMode
return $ret
}
proc cmdModulePurge {} {
reportDebug called.
# create a message record if for purge action to let underlying unload
# actions to know they are not the top-level action
pushMsgRecordId purge-[getEvalModuleStackDepth]
# unload one by one to ensure same behavior whatever auto_handling state
# force it to handle loaded modules in violation state
eval cmdModuleUnload unload match 0 1 0 0 [lreverse [getLoadedModuleList]]
# no sumup report for purge action for the moment
popMsgRecordId
}
proc cmdModuleReload {args} {
# reload all loaded modules if no module list passed
set lmlist [expr {[llength $args] == 0 ? [getLoadedModuleList] : $args}]
reportDebug "reloading $lmlist"
# create a message record if for reload action to let underlying unload and
# load actions to know they are not the top-level action
pushMsgRecordId reload-[getEvalModuleStackDepth]
# no reload of all loaded modules attempt if constraints are violated
if {[llength $args] == 0 && ![areModuleConstraintsSatisfied]} {
reportError {Cannot reload modules, some of their constraints are not\
satistied}
} else {
pushSettings
if {[set errCode [catch {
# run unload then load-again phases
array set isuasked [reloadModuleListUnloadPhase lmlist]
reloadModuleListLoadPhase lmlist [array get isuasked]
} errMsg]] == 1} {
reportError $errMsg
# rollback settings if some evaluation went wrong
restoreSettings
}
popSettings
}
# report all recorded messages for this evaluation
reportMsgRecord "Reloading all loaded modules"
popMsgRecordId
}
proc cmdModuleAliases {} {
# disable error reporting to avoid modulefile errors
# to mix with avail results
inhibitErrorReport
# parse paths to fill g_moduleAlias and g_moduleVersion
foreach dir [getModulePathList exiterronundef] {
getModules $dir {} 0 {}
}
reenableErrorReport
set display_list {}
foreach name [lsort -dictionary [array names ::g_moduleAlias]] {
lappend display_list "[sgr al $name] -> $::g_moduleAlias($name)"
}
displayElementList Aliases hi sepline 1 0 $display_list
set display_list {}
foreach name [lsort -dictionary [array names ::g_moduleVersion]] {
lappend display_list "[sgr sy $name] -> $::g_moduleVersion($name)"
}
displayElementList Versions hi sepline 1 0 $display_list
}
proc cmdModuleAvail {show_oneperline show_mtime show_filter search_filter\
args} {
if {[llength $args] == 0} {
lappend args *
}
if {$show_mtime || $show_oneperline} {
set one_per_line 1
set hstyle terse
set theader_shown 0
set theader_cols [list hi Package/Alias 39 Versions 19 {Last mod.} 19]
} else {
set one_per_line 0
set hstyle sepline
}
# disable error reporting to avoid modulefile errors
# to mix with avail results
inhibitErrorReport
foreach mod $args {
# look if aliases have been defined in the global or user-specific
# modulerc and display them if any in a dedicated list
lassign [listModules {} $mod $show_mtime $show_filter [concat\
$search_filter [list rc_alias_only]]] display_list len_list max_len
if {[llength $display_list] > 0 && $show_mtime && !$theader_shown} {
set theader_shown 1
eval displayTableHeader $theader_cols
}
displayElementList {global/user modulerc} hi $hstyle $one_per_line 0\
$display_list $len_list $max_len
foreach dir [getModulePathList exiterronundef] {
lassign [listModules $dir $mod $show_mtime $show_filter [concat\
$search_filter [list wild]]] display_list len_list max_len
if {[llength $display_list] > 0 && $show_mtime && !$theader_shown} {
set theader_shown 1
eval displayTableHeader $theader_cols
}
displayElementList $dir mp $hstyle $one_per_line 0 $display_list\
$len_list $max_len
}
}
reenableErrorReport
}
proc cmdModuleUse {args} {
reportDebug $args
if {$args eq {}} {
showModulePath
} else {
set pos prepend
foreach path $args {
switch -- $path {
-a - --append - -append {
set pos append
}
-p - --prepend - -prepend {
set pos prepend
}
{} {
reportError {Directory name empty}
}
default {
# tranform given path in an absolute path to avoid dependency
# to the current work directory. except if this path starts
# with a variable reference
if {[string index $path 0] ne {$}} {
set path [getAbsolutePath $path]
}
if {[file isdirectory [resolvStringWithEnv $path]]} {
pushMode load
catch {
add-path $pos MODULEPATH $path
}
popMode
} else {
reportError "Directory '$path' not found"
}
}
}
}
}
}
proc cmdModuleUnuse {args} {
reportDebug $args
if {$args eq {}} {
showModulePath
} else {
foreach path $args {
# get current module path list
# no absolute path conversion for the moment
if {![info exists modpathlist]} {
set modpathlist [getModulePathList returnempty 0 0]
}
# skip empty string
if {$path eq {}} {
reportError {Directory name empty}
continue
}
# transform given path in an absolute path which should have been
# registered in the MODULEPATH env var. however for compatibility
# with previous behavior where relative paths were registered in
# MODULEPATH given path is first checked against current path list
set abspath [getAbsolutePath $path]
if {[isInList $modpathlist $path]} {
set unusepath $path
} elseif {[isInList $modpathlist $abspath]} {
set unusepath $abspath
} else {
set unusepath {}
}
if {$unusepath ne {}} {
pushMode unload
catch {
unload-path MODULEPATH $unusepath
}
popMode
# refresh path list after unload
set modpathlist [getModulePathList returnempty 0 0]
if {[isInList $modpathlist $unusepath]} {
reportWarning "Did not unuse $unusepath"
}
}
}
}
}
proc cmdModuleAutoinit {} {
reportDebug called.
# flag to make renderSettings define the module command
set ::g_autoInit 1
# initialize env variables around module command
pushMode load
# default MODULESHOME
setenv MODULESHOME @prefix@
# register command location
setenv MODULES_CMD [getAbsolutePath $::argv0]
# define current Modules version if versioning enabled
@VERSIONING@if {![info exists ::env(MODULE_VERSION)]} {
@VERSIONING@ setenv MODULE_VERSION @MODULES_RELEASE@@MODULES_BUILD@
@VERSIONING@ setenv MODULE_VERSION_STACK @MODULES_RELEASE@@MODULES_BUILD@
@VERSIONING@}
# initialize default MODULEPATH and LOADEDMODULES
if {![info exists ::env(MODULEPATH)] || $::env(MODULEPATH) eq {}} {
# set modpaths defined in .modulespath config file if it exists
if {[file readable @initdir@/.modulespath]} {
set fdata [split [readFile @initdir@/.modulespath] \n]
foreach fline $fdata {
if {[regexp {^\s*(.*?)\s*(#.*|)$} $fline match patharg] == 1\
&& $patharg ne {}} {
eval cmdModuleUse --append [split $patharg :]
}
}
}
if {![info exists ::env(MODULEPATH)]} {
setenv MODULEPATH {}
}
}
if {![info exists ::env(LOADEDMODULES)]} {
setenv LOADEDMODULES {}
}
# source initialization modulerc if any and if no env already initialized
if {$::env(MODULEPATH) eq {} && $::env(LOADEDMODULES) eq {}\
&& [file exists @initdir@/modulerc]} {
cmdModuleSource @initdir@/modulerc
}
popMode
}
proc cmdModuleInit {args} {
set init_cmd [lindex $args 0]
set init_list [lrange $args 1 end]
set notdone 1
set nomatch 1
reportDebug $args
# Define startup files for each shell
set files(csh) [list .modules .cshrc .cshrc_variables .login]
set files(tcsh) [list .modules .tcshrc .cshrc .cshrc_variables .login]
set files(sh) [list .modules .bash_profile .bash_login .profile .bashrc]
set files(bash) $files(sh)
set files(ksh) $files(sh)
set files(fish) [list .modules .config/fish/config.fish]
set files(zsh) [list .modules .zshrc .zshenv .zlogin]
# Process startup files for this shell
set current_files $files($::g_shell)
foreach filename $current_files {
if {$notdone} {
set filepath $::env(HOME)
append filepath / $filename
reportDebug "Looking at $filepath"
if {[file readable $filepath] && [file isfile $filepath]} {
set newinit {}
set thismatch 0
foreach curline [split [readFile $filepath] \n] {
# Find module load/add command in startup file
set comments {}
if {$notdone && [regexp {^([ \t]*module[ \t]+(load|add)[\
\t]*)(.*)} $curline match cmd subcmd modules]} {
set nomatch 0
set thismatch 1
regexp {([ \t]*\#.+)} $modules match comments
regsub {\#.+} $modules {} modules
# remove existing references to the named module from
# the list Change the module command line to reflect the
# given command
switch -- $init_cmd {
list {
if {![info exists notheader]} {
report "$::g_shell initialization file\
\$HOME/$filename loads modules:"
set notheader 0
}
report \t$modules
}
add {
foreach newmodule $init_list {
set modules [replaceFromList $modules $newmodule]
}
lappend newinit "$cmd$modules $init_list$comments"
# delete new modules in potential next lines
set init_cmd rm
}
prepend {
foreach newmodule $init_list {
set modules [replaceFromList $modules $newmodule]
}
lappend newinit "$cmd$init_list $modules$comments"
# delete new modules in potential next lines
set init_cmd rm
}
rm {
set oldmodcount [llength $modules]
foreach oldmodule $init_list {
set modules [replaceFromList $modules $oldmodule]
}
set modcount [llength $modules]
lappend newinit [expr {$modcount > 0 ?\
"$cmd$modules$comments" : [string trim $cmd]}]
if {$oldmodcount > $modcount} {
set notdone 0
}
}
switch {
set oldmodule [lindex $init_list 0]
set newmodule [lindex $init_list 1]
set newmodules [replaceFromList $modules\
$oldmodule $newmodule]
lappend newinit $cmd$newmodules$comments
if {$modules ne $newmodules} {
set notdone 0
}
}
clear {
lappend newinit [string trim $cmd]
}
}
} elseif {$curline ne {}} {
# copy the line from the old file to the new
lappend newinit $curline
}
}
if {$init_cmd ne {list} && $thismatch} {
reportDebug "Writing $filepath"
if {[catch {
set fid [open $filepath w]
puts $fid [join $newinit \n]
close $fid
} errMsg ]} {
reportErrorAndExit "Init file $filepath cannot be\
written.\n$errMsg"
}
}
}
}
}
# quit in error if command was not performed due to no match
if {$nomatch && $init_cmd ne {list}} {
reportErrorAndExit "Cannot find a 'module load' command in any of the\
'$::g_shell' startup files"
}
}
# provide access to modulefile specific commands from the command-line, making
# them standing as a module sub-command (see module procedure)
proc cmdModuleResurface {cmd args} {
reportDebug "cmd='$cmd', args='$args'"
pushMode load
pushCommandName $cmd
# run modulefile command and get its result
if {[catch {eval $cmd $args} res]} {
# report error if any and return false
reportError $res
} else {
# register result depending of return kind (false or text)
switch -- $cmd {
module-info {
set ::g_return_text $res
}
default {
if {$res == 0} {
# render false if command returned false
set ::g_return_false 1
}
}
}
}
popCommandName
popMode
}
proc cmdModuleTest {args} {
reportDebug "testing $args"
pushMode test
set first_report 1
foreach mod $args {
lassign [getPathToModule $mod] modfile modname
if {$modfile ne {}} {
# only one separator lines between 2 modules
if {$first_report} {
displaySeparatorLine
set first_report 0
}
report "Module Specific Test for [sgr hi $modfile]:\n"
execute-modulefile $modfile $modname $mod
displaySeparatorLine
}
}
popMode
}
proc cmdModuleClear {{doit {}}} {
reportDebug "($doit)"
# fetch confirmation if no arg passed and force mode disabled
if {$doit eq {} && ![getForce]} {
# ask for it if stdin is attached to a terminal
if {![catch {fconfigure stdin -mode}]} {
report "Are you sure you want to clear all loaded modules!? \[n\] " 1
flush $::reportfd
}
# fetch stdin content even if not attached to terminal in case some
# content has been piped to this channel
set doit [gets stdin]
}
# should be confirmed or forced to proceed
if {[string equal -nocase -length 1 $doit y] || [getForce]} {
set vartoclear [list LOADEDMODULES MODULES_LMALTNAME MODULES_LMCONFLICT\
MODULES_LMNOTUASKED MODULES_LMPREREQ _LMFILES_]
# add any reference counter variable to the list to unset
set vartoclear [concat $vartoclear [array names ::env -glob *_modshare]\
[array names ::env -glob MODULES_MODSHARE_*]]
# unset all Modules runtime variables
pushMode load
foreach var $vartoclear {
unset-env $var
}
popMode
} else {
reportInfo "Modules runtime information were not cleared"
}
}
proc cmdModuleConfig {dump_state args} {
# parse arguments
set nameunset 0
switch -- [llength $args] {
1 {
lassign $args name
}
2 {
lassign $args name value
# check if configuration should be set or unset
if {$name eq {--reset}} {
set name $value
set nameunset 1
unset value
}
}
}
reportDebug "dump_state='$dump_state', reset=$nameunset,\
name=[expr {[info exists name] ? "'$name'" : {<undef>}}], value=[expr\
{[info exists value] ? "'$value'" : {<undef>}}]"
array set confvar [list contact MODULECONTACT auto_handling\
MODULES_AUTO_HANDLING avail_indepth MODULES_AVAIL_INDEPTH\
collection_pin_version MODULES_COLLECTION_PIN_VERSION collection_target\
MODULES_COLLECTION_TARGET color MODULES_COLOR colors MODULES_COLORS\
pager MODULES_PAGER rcfile MODULERCFILE run_quarantine\
MODULES_RUN_QUARANTINE silent_shell_debug MODULES_SILENT_SHELL_DEBUG\
term_background MODULES_TERM_BACKGROUND]
array set confvalid [list auto_handling [list 0 1] avail_indepth [list 0\
1] collection_pin_version [list 0 1] color [list never auto always]\
silent_shell_debug [list 0 1] term_background [list dark light]]
# define each attribute/fetched value pair
array set confval [list contact [getContact] auto_handling\
[getAutoHandling] avail_indepth [getAvailInDepth] avail_report_dir_sym\
$::flag_default_dir avail_report_mfile_sym $::flag_default_mf\
collection_pin_version [pinVersionInCollection] collection_target\
[get-env $confvar(collection_target) <undef>] color [getColor] colors\
$::g_colors_list ignored_dirs [getIgnoredDirs] pager "$::g_pager\
$::g_pager_opts" rcfile [get-env $confvar(rcfile) <undef>]\
run_quarantine [get-env $confvar(run_quarantine) <undef>]\
silent_shell_debug [get-env $confvar(silent_shell_debug) <undef>]\
siteconfig $::g_siteconfig tcl_ext_lib $::g_tclextlib term_background\
[getTermBackground]]
# catch any environment variable set for modulecmd run-time execution
foreach runenvvar [array names ::env -glob MODULES_RUNENV_*] {
set runenvconf [string tolower [string range $runenvvar 8 end]]
set confval($runenvconf) [get-env $runenvvar]
# enable modification of runenv conf
set confvar($runenvconf) $runenvvar
}
if {[info exists name] && ![info exists confval($name)]} {
reportErrorAndExit "Configuration key '$name' does not exist"
# set configuration
} elseif {[info exists name] && ($nameunset || [info exists value])} {
if {![info exists confvar($name)]} {
reportErrorAndExit "Configuration key '$name' cannot be altered"
} elseif {$nameunset} {
# unset configuration variable
pushMode load
unsetenv $confvar($name)
popMode
} elseif {[info exists confvalid($name)] && [notInList\
$confvalid($name) $value]} {
reportErrorAndExit "Valid values for configuration key '$name' are:\
$confvalid($name)"
} else {
# effectively set configuration variable
pushMode load
setenv $confvar($name) $value
popMode
}
# report configuration
} else {
# some configuration may be overridden from the command-line
array set confasked [list auto_handling asked_auto_handling color\
asked_color]
# internal conf value may be map to a public value
array set confvtrans [list color [list 0 never 1 auto 2 always]]
reportVersion
reportSeparateNextContent
displayTableHeader hi {Config. name} 24 {Value (set by if default\
overridden)} 54
# report all configs or just queried one
if {[info exists name]} {
set varlist [list $name]
} else {
set varlist [lsort [array names confval]]
}
foreach var $varlist {
set valrep [displayConfig $confval($var)\
[expr {[info exists confvar($var)] ? $confvar($var) : {}}]\
[expr {[info exists confasked($var)] ? $confasked($var) : {}}]\
[expr {[info exists confvtrans($var)] ? $confvtrans($var) : {}}]]
append displist [format {%-25s %s} $var $valrep] \n
}
report $displist 1
reportSeparateNextContent
if {$dump_state} {
displayTableHeader hi {State name} 24 {Value} 54
# define each attribute/fetched state value pair
set state_list [list\
cmdline "$::argv0 $::argv"\
debug $::g_debug\
force [getForce]\
is_stderr_tty [isStderrTty]\
is_win [isWin]\
machine [uname machine]\
os "[uname sysname] [uname release]"\
pager_started $::start_pager\
paginate $::use_pager\
path_separator [getPathSeparator]\
rc_loaded [expr {[info exists ::g_rc_loaded] ? $::g_rc_loaded :\
{<undef>}}]\
siteconfig_loaded [info exists ::g_siteconfig_loaded]\
shell $::g_shell\
shelltype $::g_shellType\
subcmd $::command\
subcmd_args $::otherargv\
tcl_ext_lib_loaded [info exists ::g_tclextlib_loaded]\
tcl_version [info patchlevel]\
term_columns [getTtyColumns]]
unset displist
foreach {var val} $state_list {
append displist [format {%-25s %s} $var $val] \n
}
report $displist 1
reportSeparateNextContent
# report environment variable set related to Modules
displayTableHeader hi {Env. variable} 24 {Value} 54
set envvar_list {}
foreach var [list LOADEDMODULES _LMFILES_ MODULE* *_modshare\
*_modquar *_module*] {
set envvar_list [concat $envvar_list [array names ::env -glob\
$var]]
}
unset displist
foreach var [lsort -unique $envvar_list] {
append displist [format {%-25s %s} $var $::env($var)] \n
}
report $displist 1
}
}
}
proc cmdModuleHelp {args} {
pushMode help
set first_report 1
foreach arg $args {
lassign [getPathToModule $arg] modfile modname
if {$modfile ne {}} {
# only one separator lines between 2 modules
if {$first_report} {
displaySeparatorLine
set first_report 0
}
report "Module Specific Help for [sgr hi $modfile]:\n"
execute-modulefile $modfile $modname $arg
displaySeparatorLine
}
}
popMode
if {[llength $args] == 0} {
reportVersion
report {Usage: module [options] [command] [args ...]
Loading / Unloading commands:
add | load modulefile [...] Load modulefile(s)
rm | unload modulefile [...] Remove modulefile(s)
purge Unload all loaded modulefiles
reload | refresh Unload then load all loaded modulefiles
switch | swap [mod1] mod2 Unload mod1 and load mod2
Listing / Searching commands:
list [-t|-l] List loaded modules
avail [-d|-L] [-t|-l] [--indepth|--no-indepth] [mod ...]
List all or matching available modules
aliases List all module aliases
whatis [modulefile ...] Print whatis information of modulefile(s)
apropos | keyword | search str Search all name and whatis containing str
is-loaded [modulefile ...] Test if any of the modulefile(s) are loaded
is-avail modulefile [...] Is any of the modulefile(s) available
info-loaded modulefile Get full name of matching loaded module(s)
Collection of modules handling commands:
save [collection|file] Save current module list to collection
restore [collection|file] Restore module list from collection or file
saverm [collection] Remove saved collection
saveshow [collection|file] Display information about collection
savelist [-t|-l] List all saved collections
is-saved [collection ...] Test if any of the collection(s) exists
Shell's initialization files handling commands:
initlist List all modules loaded from init file
initadd modulefile [...] Add modulefile to shell init file
initrm modulefile [...] Remove modulefile from shell init file
initprepend modulefile [...] Add to beginning of list in init file
initswitch mod1 mod2 Switch mod1 with mod2 from init file
initclear Clear all modulefiles from init file
Environment direct handling commands:
prepend-path [-d c] var val [...] Prepend value to environment variable
append-path [-d c] var val [...] Append value to environment variable
remove-path [-d c] var val [...] Remove value from environment variable
Other commands:
help [modulefile ...] Print this or modulefile(s) help info
display | show modulefile [...] Display information about modulefile(s)
test [modulefile ...] Test modulefile(s)
use [-a|-p] dir [...] Add dir(s) to MODULEPATH variable
unuse dir [...] Remove dir(s) from MODULEPATH variable
is-used [dir ...] Is any of the dir(s) enabled in MODULEPATH
path modulefile Print modulefile path
paths modulefile Print path of matching available modules
clear [-f] Reset Modules-specific runtime information
source scriptfile [...] Execute scriptfile(s)
config [--dump-state|name [val]] Display or set Modules configuration
Switches:
-t | --terse Display output in terse format
-l | --long Display output in long format
-d | --default Only show default versions available
-L | --latest Only show latest versions available
-a | --append Append directory to MODULEPATH
-p | --prepend Prepend directory to MODULEPATH
--auto Enable automated module handling mode
--no-auto Disable automated module handling mode
-f | --force By-pass dependency consistency or confirmation dialog
Options:
-h | --help This usage info
-V | --version Module version
-D | --debug Enable debug messages
--paginate Pipe mesg output into a pager if stream attached to terminal
--no-pager Do not pipe message output into a pager
--color[=WHEN] Colorize the output; WHEN can be 'always' (default if
omitted), 'auto' or 'never'}
}
}
########################################################################
# main program
# needed on a gentoo system. Shouldn't hurt since it is
# supposed to be the default behavior
fconfigure stderr -translation auto
if {[catch {
# parse all command-line arguments before doing any action, no output is
# made during argument parse to wait for potential paging to be setup
set show_help 0
set show_version 0
reportDebug "CALLING $argv0 $argv"
# Load extension library if enabled
@libtclenvmodules@if {[file readable $g_tclextlib]} {
@libtclenvmodules@ reportDebug "Load Tcl extension library ($g_tclextlib)"
@libtclenvmodules@ load $g_tclextlib Envmodules
@libtclenvmodules@ set g_tclextlib_loaded 1
@libtclenvmodules@}
# use fallback procs if extension library is not loaded
if {[info commands readFile] eq {}} {
rename ::__readFile ::readFile
rename ::__getFilesInDirectory ::getFilesInDirectory
}
# source site configuration script if any
sourceSiteConfig
# Parse shell
set g_shell [lindex $argv 0]
switch -- $g_shell {
sh - bash - ksh - zsh {
set g_shellType sh
}
csh - tcsh {
set g_shellType csh
}
fish - cmd - tcl - perl - python - ruby - lisp - cmake - r {
set g_shellType $g_shell
}
default {
reportErrorAndExit "Unknown shell type \'($g_shell)\'"
}
}
# extract options and command switches from other args
set otherargv {}
set extraargv {}
set ddelimarg 0
# split first arg if multi-word string detected for compat with previous
# doc on module usage with scripting language: module('load mod1 mod2')
set argtoparse [if {[llength [lindex $argv 1]] > 1} {concat [split [lindex\
$argv 1]] [lrange $argv 2 end]} {lrange $argv 1 end}]
foreach arg $argtoparse {
if {[info exists ignore_next_arg]} {
unset ignore_next_arg
} else {
switch -glob -- $arg {
-D - --debug {
set g_debug 1
}
--help - -h {
set show_help 1
}
-V - --version {
set show_version 1
}
--paginate {
set asked_pager 1
}
--no-pager {
set asked_pager 0
}
--auto {
set asked_auto_handling 1
}
--no-auto {
set asked_auto_handling 0
}
-f - --force {
set asked_force 1
}
--color* {
switch -- [string range $arg 8 end] {
always - {} {
set asked_color 2
}
auto {
set asked_color 1
}
never {
set asked_color 0
}
}
}
-t - --terse - -l - --long - --default - -L - --latest {
# command-specific switches that can for compatibility be
# passed before the command name, so add them to a specific
# arg list to ensure command name as first position argument
lappend extraargv $arg
}
-d {
# in case of *-path command, -d means --delim
if {$ddelimarg} {
lappend otherargv $arg
} else {
lappend extraargv $arg
}
}
-a - --append - -append - -p - --prepend - -prepend - --delim -\
-delim - --delim=* - -delim=* - --duplicates - --index -\
--notuasked - --indepth - --no-indepth - --dump-state - --reset {
# command-specific switches interpreted later on
lappend otherargv $arg
}
append-path - prepend-path - remove-path {
# detect *-path commands to say -d means --delim, not --default
set ddelimarg 1
lappend otherargv $arg
}
--human - -v - --verbose - -s - --silent - -c - --create - -i -\
--icase - --userlvl=* {
# ignore C-version specific option, no error only warning
reportWarning "Unsupported option '$arg'"
}
-u - --userlvl {
reportWarning "Unsupported option '$arg'"
# also ignore argument value
set ignore_next_arg 1
}
-* {
reportErrorAndExit "Invalid option '$arg'\nTry\
'module --help' for more information."
}
default {
lappend otherargv $arg
}
}
}
}
set command [lindex $otherargv 0]
set otherargv [concat [lreplace $otherargv 0 0] $extraargv]
# now options are known initialize error report (start pager if enabled)
initErrorReport
# put back quarantine variables in env, if quarantine mechanism supported
@quarantinesupport@if {[info exists env(MODULES_RUN_QUARANTINE)] && $g_shellType ne {csh}} {
@quarantinesupport@ foreach var [split $env(MODULES_RUN_QUARANTINE)] {
@quarantinesupport@ # check variable name is valid
@quarantinesupport@ if {[regexp {^[A-Za-z_][A-Za-z0-9_]*$} $var]} {
@quarantinesupport@ set quarvar ${var}_modquar
@quarantinesupport@ # put back value
@quarantinesupport@ if {[info exists env($quarvar)]} {
@quarantinesupport@ reportDebug "Release '$var' environment variable from\
quarantine ($env($quarvar))"
@quarantinesupport@ set env($var) $env($quarvar)
@quarantinesupport@ unset env($quarvar)
@quarantinesupport@ # or unset env var if no value found in quarantine
@quarantinesupport@ } elseif {[info exists env($var)]} {
@quarantinesupport@ reportDebug "Unset '$var' environment variable after\
quarantine"
@quarantinesupport@ unset env($var)
@quarantinesupport@ }
@quarantinesupport@ } elseif {[string length $var] > 0} {
@quarantinesupport@ reportWarning "Bad variable name set in MODULES_RUN_QUARANTINE\
($var)"
@quarantinesupport@ }
@quarantinesupport@ }
@quarantinesupport@}
if {$show_help} {
cmdModuleHelp
cleanupAndExit 0
}
if {$show_version} {
reportVersion
cleanupAndExit 0
}
# no modulefile is currently being interpreted
pushModuleFile {}
# eval needed to pass otherargv as list to module proc
eval module "{$command}" $otherargv
} errMsg ]} {
# no use of reportError here to get independent from any
# previous error report inhibition
report "[sgr er ERROR]: $errMsg"
# init error report here in case the error raised before the regular init
initErrorReport
cleanupAndExit 1
}
cleanupAndExit 0
# ;;; Local Variables: ***
# ;;; mode:tcl ***
# ;;; End: ***
# vim:set tabstop=3 shiftwidth=3 expandtab autoindent: