1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428
|
##########################################################################
# COLL.TCL, collection management procedures
# Copyright (C) 2016-2025 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/>.
##########################################################################
# 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 {}} {cmp eq}} {
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
##nagelfar ignore #2 Badly formed if statement
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 [expr {$from_obj in $extfrom}]
set in_extto [expr {$to_obj in $extto}]
# are elts the sames and are both part of or missing from extra lists
# when comparing modules, ask comparison against loaded module
# alternative and simplified names (modEq will also compare variants)
if {($cmp eq {modeq} && ![modEq $to_obj $from_obj equal 1 3 1]) ||\
($cmp eq {eq} && $to_obj ne $from_obj) || $in_extfrom != $in_extto} {
set list_equal 0
}
if {!$list_equal} {
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
proc getSimplifiedLoadedModuleList {} {
set curr_mod_list {}
array set curr_tag_arr {}
set modpathlist [getModulePathList]
foreach mod [getEnvLoadedModulePropertyParsedList name] {
set altandsimplist [getLoadedAltAndSimplifiedName $mod]
set parentmod [file dirname $mod]
set simplemod $mod
# simplify to parent name as long as it is found in simplified name list
while {$parentmod ne {.}} {
if {$parentmod in $altandsimplist} {
set simplemod $parentmod
set parentmod [file dirname $parentmod]
} else {
set parentmod .
}
}
# add each module specification as list to correctly enclose spaces in
# module name or variant name or value
set simplemodvr [list $simplemod {*}[getVariantList $mod 5 1]]
lappend curr_mod_list $simplemodvr
# record tags applying to module in simplified version form
set tag_list [getSaveTagList $mod]
if {[llength $tag_list]} {
set curr_tag_arr($simplemodvr) $tag_list
}
}
return [list $curr_mod_list [array get curr_tag_arr]]
}
# return saved collections found in user directory which corresponds to
# enabled collection target if any set. extract one collection specifically
# when search mode is set to exact. only compute collection name if mode is
# set to name. translate collection name to __init__ if not found and
# swap_by_init enabled. if no_other_target enabled, ensure no result from
# other target are returned from glob search
proc findCollections {{coll *} {search glob} {swap_by_init 0} {errnomatch 0}\
{checkvalid 1} {no_other_target 0}} {
# initialize description with collection name
set colldesc $coll
if {$coll eq {}} {
reportErrorAndExit [getEmptyNameMsg collection]
} elseif {$coll eq {__init__}} {
set collfile $coll
set colldesc {}
# 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 {[isEnvVarDefined HOME]} {
set coll_dir [file join $::env(HOME) .module]
set coll_glob $coll
# 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. current target
# is ignored if --all option is set on savelist command
set colltarget [getConf collection_target]
if {$colltarget ne {} && ([getState hiding_threshold] < 2 ||\
[currentState commandname] ne {savelist})} {
append coll_glob .$colltarget
# add knowledge of collection target on description
append colldesc " (for target \"$colltarget\")"
}
set collfile [file join $coll_dir $coll_glob]
} else {
reportErrorAndExit {HOME not defined}
}
switch -- $search {
glob {
# glob excludes by default files starting with "."
if {[catch {set clist [glob -nocomplain -directory $coll_dir\
$coll_glob]} errMsg]} {
reportErrorAndExit "Cannot access collection directory.\n$errMsg"
} else {
set res {}
foreach cfile $clist {
# test collection is from correct target or no target if
# no_other_target is enabled
set cfile_ext [string range [file extension $cfile] 1 end]
if {(!$no_other_target || $cfile_ext eq [getConf\
collection_target]) && [checkValidColl $cfile]} {
lappend res $cfile
}
}
}
}
exact {
if {$coll ne {__init__}} {
# verify that file exists
if {![file exists $collfile]} {
if {$errnomatch} {
reportErrorAndExit "Collection $colldesc cannot be found"
} else {
set collfile {}
}
# error will be raised if collection not valid
} elseif {$checkvalid && ![checkValidColl $collfile\
$errnomatch]} {
set collfile {}
}
}
if {$collfile eq {} && $swap_by_init} {
set collfile __init__
set colldesc {}
}
# return coll filename and its description for exact and name modes
set res [list $collfile $colldesc]
}
name {
set res [list $collfile $colldesc]
}
}
return $res
}
proc checkValidColl {collfile {report_issue 0}} {
set res 0
if {[catch {
set fdata [readFile $collfile 1]
# extract magic cookie (first word)
set fh [string trimright [lindex [split [string range $fdata 0 32]]\
0] #]
} errMsg ]} {
if {$report_issue} {
reportErrorAndExit [parseAccessIssue $collfile]
}
} else {
# collection without magic cookie are valid
# check if min version requirement is met
if {[string equal -length 8 $fh {#%Module}] && [string length $fh] \
> 8 && [versioncmp [getState modules_release] [string range $fh 8\
end]] < 0} {
if {$report_issue} {
reportErrorAndExit "Collection $collfile requires at least\
Modules version [string range $fh 8 end]"
}
} else {
set res 1
}
}
return $res
}
# generate collection content based on provided path and module lists
proc formatCollectionContent {path_list mod_list tag_arrser header {sgr 0}} {
set content {}
array set tag_arr $tag_arrser
# graphically enhance module command if asked
set modcmd [expr {$sgr ? [sgr cm module] : {module}}]
# start collection content with modulepaths
foreach path $path_list {
# enclose path if space character found in it
if {[string first { } $path] != -1} {
set path "{$path}"
}
# '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 {
# save tags associated to module (like auto-loaded tag)
if {[info exists tag_arr($mod)] && [llength $tag_arr($mod)]} {
set opt "--tag=[join $tag_arr($mod) :] "
} else {
set opt {}
}
# no need to specifically enclose module specification if space char
# used in it as $mod is a list so elements including space will be
# automatically enclosed
append content "$modcmd load $opt$mod" \n
}
# prepend header if defined and some content has been generated
if {[string length $header] && [string length $content]} {
set content "$header\n$content"
}
return $content
}
# read given collection file and return the path and module lists it defines
proc readCollectionContent {collfile colldesc} {
# read file
if {[catch {
set fdata [split [readFile $collfile] \n]
} errMsg ]} {
reportErrorAndExit "Collection $colldesc cannot be read.\n$errMsg"
}
return [parseCollectionContent $fdata]
}
proc parseCollectionContent {fdata} {
# init lists (maybe coll does not set mod to load)
set path_list {}
set mod_list {}
set nuasked_list {}
array set tag_arr {}
# analyze collection content
foreach fline $fdata {
if {[regexp {module use (.*)$} $fline match patharg]} {
# paths are appended by default
set stuff_path append
# manage multiple paths and path options specified on single line,
# for instance "module use --append path1 path2 path3", with list
# representation of patharg (which handles quoted elements containing
# space in their name)
foreach path $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 {
lprepend path_list $path
}
}
}
} elseif {[regexp {module load (.*)$} $fline match modarg]} {
# extract collection-specific flags from module specification
switch -glob -- [lindex $modarg 0] {
--notuasked {
set tag_list [list auto-loaded]
set cleanlist [lrange $modarg 1 end]
}
--tag=* {
set tag_list [split [string range [lindex $modarg 0] 6 end] :]
set cleanlist [lrange $modarg 1 end]
}
default {
set tag_list {}
set cleanlist $modarg
}
}
# parse module specification to distinguish between module + variant
# specified and multiple modules specified on a single line
set parsedlist [parseModuleSpecification 0 0 0 0 {*}$cleanlist]
foreach parsed $parsedlist {
set tag_arr($parsed) $tag_list
}
lappend mod_list {*}$parsedlist
}
}
return [list $path_list $mod_list [array get tag_arr]]
}
# return specified collection content and differences compared to currently
# defined environment
proc getDiffBetweenCurEnvAndColl {collfile colldesc} {
# read specific __init__ collection from __MODULES_LMINIT env var
if {$collfile eq {__init__}} {
lassign [parseCollectionContent [getEnvLoadedModulePropertyParsedList\
init]] coll_path_list coll_mod_list coll_tag_arrser
} else {
lassign [readCollectionContent $collfile $colldesc] coll_path_list\
coll_mod_list coll_tag_arrser
}
# build list of module tagged auto-loaded in collection
array set coll_tag_arr $coll_tag_arrser
set coll_nuasked_list {}
foreach mod [array names coll_tag_arr] {
if {{auto-loaded} in $coll_tag_arr($mod)} {
lappend coll_nuasked_list $mod
}
}
# collection should at least define a path or a mod, but initial env may be
# totally empty
if {$collfile ne {__init__} && ![llength $coll_path_list] && ![llength\
$coll_mod_list]} {
reportErrorAndExit "$colldesc is not a valid collection"
}
# load tags from loaded modules
cacheCurrentModules
defineModEqProc [isIcase] [getConf extended_default]
# fetch what is currently loaded
set curr_path_list [getModulePathList returnempty 0]
# get current loaded module list
set curr_mod_list [getEnvLoadedModulePropertyParsedList name]
set curr_nuasked_list [getTaggedLoadedModuleList auto-loaded]
# get current save tags of loaded modules
array set curr_tag_arr [getLoadedModuleWithVariantSaveTagArrayList]
# determine what module to unload to restore collection from current
# situation with preservation of the load order (asking for a modeq
# comparison will help to check against simplified mod name and variants)
lassign [getMovementBetweenList $curr_mod_list $coll_mod_list\
$curr_nuasked_list $coll_nuasked_list modeq] mod_to_unload mod_to_load
# proceed as well for modulepath
lassign [getMovementBetweenList $curr_path_list $coll_path_list] \
path_to_unuse path_to_use
# indicate if loaded modules that matches modules in collection have
# different tags set
if {![llength $mod_to_load]} {
# consider a not-set entry as an empty element when comparing collection
# and current environment tags. compare tags as unordered lists
lassign [getDiffBetweenArray curr_tag_arr coll_tag_arr 1 1] notincoll\
diff notincurr
set is_tags_diff [llength $diff]
# if some module from collection are not yet loaded, consider there is a
# difference
} else {
set is_tags_diff 1
}
return [list $coll_path_list $coll_mod_list $coll_tag_arrser\
$coll_nuasked_list $mod_to_unload $mod_to_load $path_to_unuse\
$path_to_use $is_tags_diff]
}
proc getCollectionFromStash {stash} {
if {[string match stash-* $stash]} {
set coll $stash
} elseif {[string is integer -strict $stash]} {
# filter collection from other target (especially if no target set)
set collfile [lindex [lsort -decreasing [findCollections stash-* glob\
0 0 1 1]] $stash]
if {$collfile eq {}} {
knerror "Invalid stash index '$stash'"
}
# extract collection name (without path and target extension)
set coll [file rootname [file tail $collfile]]
} else {
knerror "Invalid stash collection name '$stash'"
}
return $coll
}
# ;;; Local Variables: ***
# ;;; mode:tcl ***
# ;;; End: ***
# vim:set tabstop=3 shiftwidth=3 expandtab autoindent:
|