modmanage.bash.in 14.9 KB
Newer Older
1
#!@PMODULES_HOME@/bin/bash
2

3 4
unset CDPATH

5
shopt -s expand_aliases
6

7 8
declare -r bindir=$(cd $(dirname "$0") && pwd)
declare -r libdir=$(cd "${bindir}/../lib" && pwd)
9

10 11
declare PMODULES_VERSION='@PMODULES_VERSION@'

12
source "${libdir}/libpmodules.bash"
13

gsell's avatar
gsell committed
14 15
PATH="${bindir}:${PATH}"

16 17
print_version() {
	echo "
18
Pmodules @PMODULES_VERSION@ using Tcl Environment Modules @MODULES_VERSION@
19 20
Copyright GNU GPL v2
" 1>&2
21 22
}

23 24 25 26 27 28 29 30 31 32 33
usage() {
	local -r prog=$(basename $0)
	print_version
	echo "
Usage: ${prog} [ switches ] [ subcommand ] [subcommand-args ]

Switches:
        --dry-run     do nothing
        --force       force overwrite

Available SubCommands and Args:
34
        init [--src=<src>] [--user=<user>] <dst>
35
                Initialize a new minimal Pmodule environment.
36 37 38

        install <module> [--with=<dep>...]
                Install matching modules
39 40 41

        sync [--delete] [--dst=<dst>] <src>
                Synchronize modules.
42 43
"
}
44

45 46

declare force='no'
47 48
declare dry_run='no'
declare DRY=''
49 50
declare subcommand=''
declare sargs=()
51

52 53
subcommand_help_init() {
	echo "
gsell's avatar
gsell committed
54
init [--src=<src>] [--user=<user>] [--version=<version>] <dst>
55 56 57 58 59
                Initialize a new minimal Pmodule environment in directory
                <dst>. The <user> parameter must only be present if
                 ${prog} is executed as root.
" 1>&2
}
60

61 62
subcommand_help_install() {
	echo "
63
install <module>... [--with=<dep>...] [--release=<release>...] [--src=<src>]
64 65 66 67
                Install matching modules
" 1>&2
}

68 69 70 71 72 73
subcommand_help_sync() {
	echo "
sync [--delete] [--dst=<dst>] <src>
                Synchronize environment modules and configuration files
                from Pmodule environment <src> to Pmodule environment <dst>
                (default: currently active Pmodule environment).
74
                Not yet implemented:
75 76 77 78 79
                If --delete is given, unmarked modules present in <dst>
                will be deleted.
" 1>&2
}

80 81 82 83 84 85 86 87 88
subcommand_help() {
	if [[ $# == 0 ]]; then
	        usage
	elif typeset -F subcommand_help_$1 > /dev/null 2>&1 ; then
	        # help for sub-command
		subcommand_help_$1
	else
		usage
	fi
89 90
}

91 92 93 94 95 96 97 98 99 100
#
# Derive the relative module installation path
# from the relative modulefile path
#
# $1: relative module file path
#
get_module_prefix() {
	local -a comp=( ${1//\// } )    # split rel.path into components
	local path="${comp[0]}"		# result path
	local -i i
gsell's avatar
gsell committed
101
	for ((i=1; i<${#comp[@]}-1; i+=2)); do
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
		path+="/${comp[$((-i-1))]}/${comp[$((-i))]}"
	done
	echo "${path}"
}

#
# Derive the relative module release file path
# from the relative module file path
#
# $1: relative module file path
#
get_releasefile_name() {
	echo "$(dirname "$1")/.release-$(basename "$1")"
}

#
# Sync a module from one Pmodules environment to another:
# - sync module installation
# - sync modulefile
# - sync release file
#
gsell's avatar
gsell committed
123 124 125 126
# Note:
#   We do not take care of files in $PMODULES_ROOT/$PMODULES_TEMPLATES_DIR. If
#   the modulefile is a sym-link it is expected that the target exists.
#
gsell's avatar
gsell committed
127
# $1: relative modulefile path (something like: Tools/gnuplot/5.0.0)
128 129 130 131 132 133 134 135 136 137 138
# $2: source prefix of Pmodule environment
# $3: target prefix of Pmodule environment
#
sync_module() {
	local -r rel_modulefile=$1
	local -r src_prefix=$2
	local -r target_prefix=$3

	local -r rel_module_prefix=$( get_module_prefix "${rel_modulefile}" )
	local -r rel_releasefile=$( get_releasefile_name "${rel_modulefile}" )

139
	# install/update module
140 141 142 143 144 145
	if [[ ! -d "${target_prefix}/${rel_module_prefix}" ]] || [[ "${force}" == 'yes' ]]; then
		$DRY mkdir -p "${target_prefix}/${rel_module_prefix}" || return $?
		$DRY rsync --links --perms --recursive --delete \
		      "${src_prefix}/${rel_module_prefix}/" \
		      "${target_prefix}/${rel_module_prefix}/" || return $?
	fi
gsell's avatar
gsell committed
146 147 148 149
	local -r src_modulefile="${src_prefix}/${rel_modulefile}"
	local -r src_releasefile="${src_prefix}/${rel_releasefile}"
	local -r target_modulefile="${target_prefix}/${rel_modulefile}"
	local -r target_releasefile="${target_prefix}/${rel_releasefile}"
150

151
	# create target directory for module- and release-file
152 153
	if [[ -e "${src_modulefile}" ]] || [[ -e "${src_releasefile}" ]]; then
	        local dir=$( dirname "${target_modulefile}" )
154
		$DRY mkdir -p "${dir}" || return $?
155
	fi
156 157

	# copy modulefile
158 159 160 161
	if [[ -e "${src_modulefile}" ]]; then
	        $DRY rsync --links --perms --recursive \
		     "${src_modulefile}" "${target_modulefile}" || return $?
	fi
162 163

	# copy release-file
164 165 166 167
	if [[ -e "${src_releasefile}" ]]; then
	        $DRY rsync --links --perms --recursive \
		     "${src_releasefile}" "${target_releasefile}" || return $?
	fi
168 169
}

170 171 172 173 174 175 176
#
# Sync the Pmodules configuration and templates
#
# $1: source prefix of Pmodule environment
# $2: target prefix of Pmodule environment
#
sync_config() {
gsell's avatar
gsell committed
177 178
	src="$1/${PMODULES_CONFIG_DIR}/"
	dst="$2/${PMODULES_CONFIG_DIR}/"
179 180
	$DRY rsync --recursive --links --perms --delete \
		"${src}" "${dst}" || die 1 "Error: synch operation failed!"
181
	sed -i.bak "s/PMODULES_VERSION=\(.*\)/PMODULES_VERSION=${PMODULES_VERSION}/" "${dst}/environment.bash"
182 183
	echo

gsell's avatar
gsell committed
184 185
	src="$1/${PMODULES_TEMPLATES_DIR}/"
	dst="$2/${PMODULES_TEMPLATES_DIR}/"
186 187 188 189 190
	$DRY rsync --recursive --links --perms --delete \
		"${src}" "${dst}" || die 1 "Error: synch operation failed!"
	echo
}

191 192 193
#
# Delete a module
#
194 195
# $1: relative modulefile path
# $2: target prefix of Pmodule environment
196 197
#
delete_module() {
198
        echo "Not implemented yet!"
199 200
}

201 202
subcommand_init() {
	local src=''
203
	local target_prefixes=()
204 205
	local user=''
	local opts=''
206
	opts=$(get_options -o h -l src: -l user: -l help -l version: -- "$@")
207 208 209 210 211 212
	if [[ $? != 0 ]]; then
		subcommand_help_init
		exit 1
	fi
	eval set --  "${opts}"
	while (($# > 0)); do
213
		case $1 in
214 215 216 217 218 219 220 221
			--src )
				src=$2
				shift
				;;
			--user )
				user=$2
				shift
				;;
222 223 224 225
			--version )
				PMODULES_VERSION=$2
				shift
				;;
226 227 228 229 230 231 232 233 234
			-- )
				:
				;;
			-* | -h | --help )
				echo "$1: illegal option" 1>&2
				subcommand_help_init
				exit 1
				;;
			* )
235
				target_prefixes+=( "$1" )
236 237 238 239
				;;
		esac
		shift
	done
240
	(( ${#target_prefixes[@]} != 0 )) || die 1 "Error: no target directory specified!"
241

242
	# if source directory is not passed as argument, derive it from script name
gsell's avatar
gsell committed
243 244
	if [[ -z "${src}" ]]; then
	    src=$(cd "${bindir}/../../../.." && pwd) 
245
	fi
gsell's avatar
gsell committed
246 247 248 249 250 251
	[[ -d "${src}" ]] || \
	    die 1 "Error: ${src}: source directory does not exist!"
	[[ -r "${src}/config/profile.bash" ]] || \
	    die 1 "Error: ${src}: shell profile does not exist or is not readable!"
	source "${src}/config/profile.bash"

252 253
	local -i euid=$(id -u)
	if (( euid == 0 )); then
254
	        [[ -n "${user}" ]] || \
255
		    die 1 "Error: --user parameter is required!"
256 257
		id -u "${user}" > /dev/null 2>&1 || \
		    die 1 "Error: Unable to retrieve user id of user '${user}'"
258
	else
259
		[[ -z "${user}" ]] || \
260
		    die 1 "Error: --user option is only allowed if running as root!"
261
	fi
262 263 264

	check_pmodules_env || die 1 "Giving up..."

265
	echo "
266
Attempting to create a minimal module environment from the
gsell's avatar
gsell committed
267
environment at '${PMODULES_ROOT}'
268 269
"

270
	init_pmodules_environment() {
gsell's avatar
gsell committed
271
		local -r src_prefix="${PMODULES_ROOT}"
272
		local -r target_prefix=$1
273 274
		local src=''
		local dst=''
275 276 277 278
		echo "Initializing target directory '${target_prefix}' ..."
		echo
		if [[ -d "${target_prefix}" ]]  && [[ ${force} == no ]]; then
			echo "Warning: ${target_prefix} already exists."
279
			get_YN_answer "Do you really want to re-run the initialization? (y/N) " || \
280
				      die 1 "Abort ..."
281
		fi
gsell's avatar
gsell committed
282
		force='yes'
283 284
		echo "Creating target directory '${target_prefix}'..."
		$DRY mkdir -p "${target_prefix}" || die 1 "Error: make directory failed!"
285
		echo
286

287
		echo "Syncing configuration ..."
gsell's avatar
gsell committed
288
		sync_config "${PMODULES_ROOT}" \
289
		            "${target_prefix}" || die 1 "Error: configuration synchronization failed!"
290

gsell's avatar
gsell committed
291
		echo "Syncing Pmodules ${PMODULES_VERSION} from '${src_prefix}' to '${target_prefix}'..."
292
		sync_module "Tools/Pmodules/${PMODULES_VERSION}" \
293
			    "${src_prefix}" \
294
			    "${target_prefix}" || die 1 "Error: sync Pmodules failed!"
295
		echo
296

gsell's avatar
gsell committed
297
		dst="${target_prefix}/${PMODULES_CONFIG_DIR}/environment.bash"
298
		echo "Adding installation source '${src_prefix}' to '${dst}'..."
gsell's avatar
gsell committed
299
		sed -i.bak '/PMODULES_INSTALL_SOURCE/d' "${dst}"
300 301 302
		echo "declare -x PMODULES_INSTALL_SOURCE=\"${src_prefix}\"" >> "${dst}"
		echo

303 304 305 306
		if [[ -n "${user}" ]]; then
	                echo "Changing user of new module environment to '${user}'..."
                        $DRY chown -R "${user}" "${target_prefix}" || die 1 "Error: changing owner failed!"
                        echo
307
		fi
308
		echo "New minimal module environment created at '${target_prefix}'."
309
		echo "To use this environment, execute"
310
		echo "   sudo ln -fs ${target_prefix} /opt/psi"
gsell's avatar
gsell committed
311
		echo "   source /opt/psi/${PMODULES_CONFIG_DIR}/profile.bash"
312 313 314
	}

	umask 022
315 316
	for target_prefix in "${target_prefixes[@]}"; do
		init_pmodules_environment "${target_prefix}"
317 318
	done

319 320
}

321 322 323 324 325
subcommand_install() {
	local opts=''
	local -a with=()
	local -a releases=()
	local -a module_pattern=()
326
	local -r src_prefix="${PMODULES_INSTALL_SOURCE}"
gsell's avatar
gsell committed
327
	local -r target_prefix="${PMODULES_ROOT}"
328

329
	opts=$(get_options -o hf -l dry-run -l force -l with: -l release: -l help -l src: -- "$@")
330 331 332 333 334 335 336
	if [[ $? != 0 ]]; then
		subcommand_help_install
		exit 1
	fi
	eval set --  "${opts}"
	while (($# > 0)); do
		case $1 in
337 338 339 340 341 342
			--dry-run )
				DRY='echo'
				;;
			--force | -f )
				force='yes'
				;;
343 344
			--release )
				releases+=( "$2" )
345 346 347 348
				shift
				;;
			--src )
				src_prefix="$2"
349 350 351 352 353 354 355 356 357
				shift
				;;
			--with )
				with+=( "$2" )
				shift
				;;
			-- )
				:
				;;
358 359 360 361 362
			-h | --help )
				subcommand_help_install
				exit 1
				;;
			-* )
363 364 365 366 367 368 369 370 371 372
				echo "$1: illegal option" 1>&2
				subcommand_help_init
				exit 1
				;;
			* )
				module_pattern+=( "$1" )
				;;
		esac
		shift
	done
373 374 375 376 377 378 379
	local -A modules_to_install
	local -i n=0
	while read rel_modulefile; do
		modules_to_install["${rel_modulefile}"]+='.'
		let n+=1
	done < <(${PMODULES_HOME}/bin/modulecmd bash search \
				 "${module_pattern[@]}" \
380
				 "${with[@]/#/--with=}" \
381 382
				 "${releases[@]/#/--release=}" \
				 --no-header --print-modulefiles \
gsell's avatar
gsell committed
383
				 --src="${src_prefix}"  2>&1 1>/dev/null)
384 385 386
	(( n == 0 )) && die 0 "Nothing to install..."
	echo -e "The following modules will be installed/updated:\n" 1>&2
	for key in "${!modules_to_install[@]}"; do
gsell's avatar
gsell committed
387
		echo "    ${key/\/${PMODULES_MODULEFILES_DIR}\//: }" 1>&2
388 389 390 391 392 393 394 395 396 397 398 399 400 401 402
	done
	echo 1>&2
	get_YN_answer "Do you want to continue? [n] " || die 1 "Aborting..."
	echo 1>&2
	for rel_modulefile in "${!modules_to_install[@]}"; do
		if [[ -d "${target_prefix}/${rel_modulefile}" ]]; then
			echo "    Updating; ${rel_modulefile}..." 1>&2
		else
			echo "    Installing: ${rel_modulefile}..."
		fi
		sync_module "${rel_modulefile}" \
			    "${src_prefix}" \
			    "${target_prefix}"
	done
	echo -e "\nDone!\n" 1>&2
403 404
}

405
subcommand_sync() {
gsell's avatar
gsell committed
406
        [[ -z "${PMODULES_ROOT}" ]] && die 1 "Error: No current module environment is configured!"
407

408 409 410
	local delete=false
	local opts=''
	local dst_prefix=''
411 412
	local src_prefix=''

413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436
	opts=$(get_options -o h -l dst: -l delete -l help -- "$@")
	if [[ $? != 0 ]]; then
		subcommand_help_sync
		exit 1
	fi
	eval set --  "${opts}"
	while (($# > 0)); do
		case $1 in
			--dst )
				dst_prefix="$2"
				shift
				;;
			--delete )
				delete=true
				;;
			-- )
				:
				;;
			-* | -h | --help )
				echo "$1: illegal option" 1>&2
				subcommand_help_init
				exit 1
				;;
			* )
gsell's avatar
gsell committed
437 438
				[[ -n "${src_prefix}" ]] && \
				    die 1 "Error: Only one source is allowed!"
439
				src_prefix="$1"
440 441 442 443 444
				;;
		esac
		shift
	done
	unset -v opts
445

446
	if [[ -z "${dst_prefix}" ]]; then
gsell's avatar
gsell committed
447
		dst_prefix="${PMODULES_ROOT}"
448 449
	fi
	(
gsell's avatar
gsell committed
450
		PMODULES_ROOT="${dst_prefix}" check_pmodules_env || \
gsell's avatar
gsell committed
451
		    die 1 "Error: invalid destination modules environment!"
452
	) || die 1 "Giving up..."
gsell's avatar
gsell committed
453
	
454
	: ${src_prefix:=${PMODULES_INSTALL_SOURCE}} 
455 456 457
	if [[ -z "${src_prefix}" ]]; then
            die 1 "Error: no source module environment was specified!"
        fi
458
	(
gsell's avatar
gsell committed
459
		PMODULES_ROOT="${src_prefix}" check_pmodules_env || \
gsell's avatar
gsell committed
460
		    die 1 "Error: invalid source modules environment!"
461
	) || die 1 "Giving up..."
gsell's avatar
gsell committed
462 463
	[[ "$( cd "$src_prefix"; pwd -P )" == "$( cd "$dst_prefix"; pwd -P )" ]] && \
	    die 1 "Error: source and destination are equal!"
gsell's avatar
gsell committed
464
	local modbin=${PMODULES_HOME#"${PMODULES_ROOT}/"}/bin/modulecmd.tcl
465 466
	local file_type_src=$( file -b "${src_prefix}/${modbin}" 2>&1 || echo err1 )
	local file_type_dst=$( file -b "${dst_prefix}/${modbin}" 2>&1 || echo err2 )
gsell's avatar
gsell committed
467 468
	[[ "${file_type_src}" == "${file_type_dst}" ]] || \
	    die 1 "Error: The file signatures in the source and destination installation do not match!"
469
	unset -v file_type_src file_type_dst
470
	local dialog_script="${PMODULES_HOME}/bin/dialog.bash"
gsell's avatar
gsell committed
471 472
	[[ -r "$dialog_script" ]] || \
	    die 1 "Error: Unable to find dialog script of installation $dialog_script";
473 474 475 476 477

	DIALOG_LIB=1                # use dialog script as a library
	source "$dialog_script"     # dialog functions

	# Redefine module_out to append modules to the selected_modules variable
478
        local -a selected_modules
gsell's avatar
gsell committed
479
	module_out() {
480 481 482 483 484 485 486 487
		local -a args=(${modlist[$1]})
		local path=""
		IFS=/
		[[ -n "${args[3]}" ]] && path="/${args[*]:3}"
		unset IFS
		selected_modules+=( "${args[2]}${path}/${args[0]}" )
	}

gsell's avatar
gsell committed
488 489 490 491 492
	module_picker "${dst_prefix}" "${src_prefix}" || {
		# this calls module_out for each selected module,
		#filling up the selected_modules array
		echo "Abort!"
		exit 1
493
	}
494

gsell's avatar
gsell committed
495
	local -a destination_modules=( $(cd "${dst_prefix}/${PMODULES_MODULEFILES_DIR}"; find -L . -type f | while read f; do n=${f##*/}; [[ "${n:0:1}" == "." ]] || echo ${f#./}; done) )
496 497

	# redefine set difference, the version in dialog.bash only handles integers
gsell's avatar
gsell committed
498
	set_difference() {  #  $1 \ $2
499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514
		local -a operand1=($1)
		local -a operand2=($2)
		local -A members
		local elem
		for elem in "${operand1[@]}"; do
			members[$elem]=1
		done
		for elem in "${operand2[@]}"; do
			unset members[$elem]
		done
		echo ${!members[@]}
	}

	if [[ "$delete" == "true" ]]; then
		local -a modules_delete=( $(set_difference "${destination_modules[*]}" "${selected_modules[*]}") )
		for m in "${modules_delete[@]}"; do
515
                        echo "Deleting module $m ..."
516 517 518 519 520 521 522 523 524 525 526
			delete_module "$m" "$dst_prefix"
		done
		unset modules_delete
	fi

	local -a modules_copy=( $(set_difference "${selected_modules[*]}" "${destination_modules[*]}") )
	if [[ -n $modules_copy ]]; then
		echo "Syncing configuration ..."
		sync_config "$src_prefix" "$dst_prefix" || die 1 "Error: syncing the configuration failed"
        fi
        for m in "${modules_copy[@]}"; do
527
                echo "Copying module $m ..."
528 529 530 531 532
		sync_module "$m" "$src_prefix" "$dst_prefix" || die 1 "Error: syncing of module $m failed!"
        done
	unset modules_copy
}

533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553
while (($# > 0)); do
	case $1 in
		-h | -H | -\? | --help | -help )
			usage
			exit 1
			;;
		-V | --version )
			print_version
			exit 1
			;;
		-f | --force )
			force='yes'
			;;
		--dry-run )
			dry_run='yes'
			DRY='echo'
			;;
		-* )
			echo "$1: unknown switch.\n" 1>&2
			exit 1
			;;
554
		init|install|sync|help )
555 556 557 558 559 560 561 562 563 564
			subcommand="subcommand_$1"
			shift
			sargs=( $* )
			shift $#
			;;
		* )
			echo "$1: unknown sub-command" 1>&2
			exit 1
	esac
	shift
565
done
566

567 568 569 570
if [[ -z ${subcommand} ]]; then
        usage
	exit 1
fi
571 572 573 574 575 576 577
$subcommand "${sargs[@]}"

# Local Variables:
# mode: sh
# sh-basic-offset: 8
# tab-width: 8
# End: