354 lines
10 KiB
Bash
Executable File
354 lines
10 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# Hardcoded path to dialog software
|
|
DIALOG_CMD=$PMODULES_HOME/bin/dialog
|
|
|
|
declare -a modlist # module info
|
|
declare -A selected # module info indices selected
|
|
declare -a depcnt # dependency reference counter by module info index
|
|
declare -A uidmap # unique module id to module info index
|
|
declare -A modmap # map module names to module info indices for modlist
|
|
declare -A fdmap # module name to family definition mapping
|
|
declare -A fmmap # module name to family member mapping
|
|
declare -a relmap # module info index to release mapping
|
|
declare tempfile # temporary dialog results
|
|
|
|
set_difference() { # $1 \ $2
|
|
local -a operand1=($1)
|
|
local -a operand2=($2)
|
|
local -A members
|
|
local -i elem
|
|
for elem in "${operand1[@]}"; do
|
|
members[$elem]=1
|
|
done
|
|
for elem in "${operand2[@]}"; do
|
|
unset members[$elem]
|
|
done
|
|
echo ${!members[@]}
|
|
}
|
|
|
|
set_merge() { # $1 U $2 (where $1 and $2 are disjoint)
|
|
if [[ -z "$1" ]]; then
|
|
echo "$2"
|
|
elif [[ -z "$2" ]]; then
|
|
echo "$1"
|
|
else
|
|
echo "$1 $2"
|
|
fi
|
|
}
|
|
|
|
set_union() { # $1 U $2 (sorted)
|
|
local -a operand1=($1)
|
|
local -a operand2=($2)
|
|
local -A members
|
|
local -i elem
|
|
for elem in ${operand1[@]} ${operand2[@]}; do
|
|
members[$elem]=1
|
|
done
|
|
{ IFS=$'\n'; echo "${!members[*]}"; } | sort -n
|
|
}
|
|
|
|
# unique id for a module
|
|
unique_id() { # $1: module info index
|
|
local -a minfo=( ${modlist[$1]} )
|
|
if (( ${#minfo[@]} < 4 )); then
|
|
echo ${minfo[0]}
|
|
else
|
|
echo "${minfo[@]:3} ${minfo[0]}"
|
|
fi
|
|
}
|
|
|
|
mod_path() { # $1: module info index
|
|
local -i i
|
|
local -a m=(${modlist[$1]})
|
|
local res="$PMODULES_ROOT/${fmmap[${m[0]%%/*}]}/${m[0]}"
|
|
for (( i=${#m[@]}; i>3; i-- )); do
|
|
res+="/${m[i-1]}"
|
|
done
|
|
echo "$res"
|
|
}
|
|
|
|
calc_deps() { # $1: module info index
|
|
local dpath="$(mod_path $1)/.dependencies"
|
|
[[ ! -r "$dpath" ]] && return
|
|
local -a d=( $(< "$dpath") ) # dependencies as versioned module names
|
|
local -A p # map family to versioned module name
|
|
local -A did # map dependency (versioned module name) to unique module id
|
|
local -a deps # set of module info indices
|
|
local m n f
|
|
for m in ${d[@]}; do
|
|
n=${m%%/*}
|
|
f=${fdmap[$n]}
|
|
[[ -n "$f" ]] && { p[$f]=$m; }
|
|
f=${fmmap[$n]}
|
|
if [[ -z "$f" ]]; then
|
|
did[$m]=$m
|
|
else
|
|
n=${p[$f]}
|
|
if [[ -z "$n" ]]; then
|
|
did[$m]=$m
|
|
else
|
|
did[$m]="${did[$n]} $m"
|
|
fi
|
|
fi
|
|
deps+=( ${uidmap["${did[$m]}"]} )
|
|
done
|
|
echo "${deps[@]}"
|
|
}
|
|
|
|
update_deps() { # $1: 1-add dependency, -1-remove dependency $2: set of module info indices
|
|
[[ -z "$2" ]] && return
|
|
local -a q=($2) # work queue
|
|
local deps="" # set of dependencies
|
|
local -i m
|
|
while (( ${#q[@]} > 0 )); do
|
|
m=${q[-1]}
|
|
unset q[-1]
|
|
d="$(calc_deps $m)"
|
|
[[ -z "$d" ]] && continue
|
|
d="$(set_difference "$d" "$deps")"
|
|
[[ -z "$d" ]] && continue
|
|
q+=($d)
|
|
deps="$(set_merge "$d" "$deps")"
|
|
done
|
|
for m in $deps; do
|
|
let depcnt[m]+=$1
|
|
done
|
|
}
|
|
|
|
# "$1": source module environment
|
|
find_modules() {
|
|
# construct modlist/modmap/uidmap/depcnt/fmmap/relmap arrays from module search output
|
|
local -a mc # module info components
|
|
local -i i=0
|
|
local current=""
|
|
local name m uid
|
|
while read m; do
|
|
mc=($m)
|
|
[[ "${mc[2]}" == "Legacy" ]] && continue # filter out legacy stuff
|
|
name=${mc[0]%%/*}
|
|
if [[ "$current" != "$name" ]]; then
|
|
modmap[$name]="$i"
|
|
current=$name
|
|
else
|
|
modmap[$name]+=" $i"
|
|
fi
|
|
modlist[i]=$m
|
|
uid="$(unique_id $i)"
|
|
uidmap["$uid"]=$i
|
|
depcnt[i]=0
|
|
[[ -z ${fmmap[$name]} ]] && { fmmap[$name]=${mc[2]}; }
|
|
relmap[i]=${mc[1]}
|
|
i+=1
|
|
done < <(${PMODULES_HOME}/bin/modulecmd bash search --src="$1" --no-header -a 2>&1)
|
|
}
|
|
|
|
# "$1": source module environment
|
|
find_families() {
|
|
# construct fdmap
|
|
local -a t # tcl file components
|
|
local l s n
|
|
while read l; do
|
|
s=${l%%:*}
|
|
s=${s%/*}
|
|
n=${s##*/}
|
|
if [[ -z "${fdmap[$n]}" ]]; then
|
|
t=( ${l##*:} )
|
|
fdmap[$n]=${t[-1]//\"}
|
|
fi
|
|
done < <(grep -R set-family "$1/*/${PMODULES_MODULEFILES_DIR}")
|
|
}
|
|
|
|
select_uid() { # $1: module uid
|
|
local -a uidc=($1) # uid components
|
|
local name=${uidc[-1]%%/*} # module name
|
|
local midx=${uidmap["$1"]} # module info index
|
|
[[ -z "$midx" ]] && return
|
|
selected[$name]="$(set_union "${selected[$name]}" "$midx")"
|
|
update_deps 1 "$midx"
|
|
}
|
|
|
|
preselect() { # "$1": prefix for preselected modules
|
|
# module paths must not contain white space
|
|
[[ -z "$1" ]] && return
|
|
local -a mpc # module path components
|
|
local -i i
|
|
local uid n
|
|
pushd "$1/$PMODULES_MODULEFILES_DIR" > /dev/null || exit 1;
|
|
trap "popd" EXIT
|
|
|
|
for m in $(find . -follow -type f); do
|
|
n=${m##*/}
|
|
[[ "${n:0:1}" == "." ]] && continue
|
|
uid=""
|
|
mpc=( ${m//\// } )
|
|
for ((i=2; i<${#mpc[@]}-2; i+=2)); do
|
|
uid+="${mpc[i]}/${mpc[i+1]} "
|
|
done
|
|
uid+="${mpc[-2]}/${mpc[-1]}"
|
|
PMODULES_ROOT="$1" select_uid "$uid"
|
|
done
|
|
|
|
popd
|
|
trap - EXIT
|
|
}
|
|
|
|
is_dependency() { # $1: module name
|
|
local -a map=(${modmap[$1]})
|
|
local -i m
|
|
for ((m=0; m<${#map[@]}; m++)); do
|
|
(( ${depcnt[${map[m]}]} > 0 )) && return 0
|
|
done
|
|
return 1
|
|
}
|
|
|
|
dialog_1() {
|
|
local -a input
|
|
local marker
|
|
local m
|
|
for m in $(IFS=$'\n'; echo "${!modmap[*]}" | sort); do
|
|
marker=""
|
|
[[ -n ${selected[$m]} ]] && { marker+="*"; }
|
|
is_dependency $m && { marker+="+"; }
|
|
input+=($m "$marker$m")
|
|
done
|
|
|
|
$DIALOG_CMD --ok-label 'Select' \
|
|
--extra-button --extra-label 'Exit' \
|
|
--no-tags \
|
|
--menu Modules 50 80 50 "${input[@]}" 2>$tempfile
|
|
return $?
|
|
}
|
|
|
|
module_id() { # $@: module info components
|
|
echo "$1 ${@:4}"
|
|
}
|
|
|
|
module_release() { # $@: module info components
|
|
echo "$2"
|
|
}
|
|
|
|
dialog_2() { # $1: module name
|
|
local -a map=(${modmap[$1]})
|
|
local -a sel=(${selected[$1]})
|
|
local -i j # mapping index
|
|
local -i k=0 # selection index
|
|
local -a input
|
|
local marker minfo rel m s
|
|
for (( j=0; j!=${#map[@]}; j++ )); do
|
|
minfo=${modlist[${map[j]}]}
|
|
m="$(module_id $minfo)"
|
|
rel=" ($(module_release $minfo))"
|
|
[[ $rel = " (stable)" ]] && { rel=""; }
|
|
[[ "${map[j]}" = "${sel[k]}" ]] && { s="on"; k+=1; } || { s="off"; }
|
|
(( ${depcnt[${map[j]}]} > 0 )) && { marker="+"; l+=1; } || { marker=""; }
|
|
input+=( ${map[j]} "$marker$m$rel" $s )
|
|
done
|
|
|
|
$DIALOG_CMD --extra-button --extra-label 'Clear' --no-tags --checklist Versions 80 90 80 "${input[@]}" 2>$tempfile
|
|
return $?
|
|
}
|
|
|
|
# final dialog output
|
|
module_out() { # $1: module info index
|
|
local -a args=(${modlist[$1]})
|
|
echo "${args[@]}"
|
|
}
|
|
|
|
# "$1": prefix for preselected modules (destination module environment)
|
|
# "$2": prefix for selectable modules (source module environment)
|
|
module_picker() {
|
|
find_families "$2"
|
|
find_modules "$2"
|
|
preselect "$1"
|
|
|
|
tempfile=$(mktemp ${TMPDIR:-/tmp}/msyncXXXXXX) || {
|
|
echo "Unable to create temporary file!"
|
|
exit 1
|
|
}
|
|
trap "rm -f $tempfile" EXIT
|
|
|
|
local -i level=1
|
|
local -i operation=0 # 0: OK, 1: Cancel
|
|
local oldsel
|
|
local sel
|
|
local m
|
|
while (( level != 0 )); do
|
|
case $level in
|
|
1)
|
|
dialog_1
|
|
res=$?
|
|
case $res in
|
|
0) #OK
|
|
sel=$(< $tempfile)
|
|
level=2
|
|
;;
|
|
1) #Cancel
|
|
operation=1
|
|
level=0
|
|
;;
|
|
3|255) #ESC/Exit = Commit
|
|
for m in ${selected[@]}; do
|
|
depcnt[m]=1
|
|
done
|
|
for ((m=0; m<${#depcnt[@]}; m++)); do
|
|
(( ${depcnt[m]} > 0 )) && module_out $m >&2
|
|
done
|
|
level=0
|
|
;;
|
|
*)
|
|
echo "Unknown return value from dialog_1: $res"
|
|
exit 1
|
|
;;
|
|
esac
|
|
;;
|
|
2)
|
|
dialog_2 $sel
|
|
res=$?
|
|
case $res in
|
|
0) #OK
|
|
oldsel=${selected[$sel]} # old selection
|
|
selected[$sel]=$(< $tempfile) # new selection
|
|
PMODULES_ROOT="$2" update_deps -1 "$(set_difference "$oldsel" "${selected[$sel]}")" # remove dependencies
|
|
PMODULES_ROOT="$2" update_deps 1 "$(set_difference "${selected[$sel]}" "$oldsel")" # add dependencies
|
|
level=1
|
|
;;
|
|
1|255) #ESC/Cancel
|
|
level=1
|
|
;;
|
|
3) #Clear
|
|
oldsel=${selected[$sel]} # old selection
|
|
selected[$sel]="" # new selection
|
|
update_deps -1 "$oldsel" # remove dependencies
|
|
level=1
|
|
;;
|
|
*)
|
|
echo "Unknown return value from dialog_2: $res"
|
|
exit 1
|
|
;;
|
|
esac
|
|
;;
|
|
*)
|
|
echo "Unknown level: $level"
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
rm -f $tempfile
|
|
trap - EXIT
|
|
|
|
return $operation
|
|
}
|
|
|
|
# if DIALOG_LIB is NOT set, call module picker
|
|
[[ ${DIALOG_LIB:+"is_lib"} == "is_lib" ]] || {
|
|
if [[ -x ${PMODULES_HOME}/bin/modulecmd ]]; then
|
|
module_picker "${1:-$PMODULES_ROOT}" "${2:-/afs/psi.ch/sys/psi.x86_64_slp6}"
|
|
exit $?
|
|
else
|
|
echo "ERROR: module environment configuration: ${PMODULES_HOME}/bin/modulecmd is not an executable!"
|
|
exit 1
|
|
fi
|
|
}
|