modmanage.in 14.2 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
source "${libdir}/libpmodules.bash"
11 12 13 14 15 16

print_version() {
	echo "
Pmodules ${version} using Tcl Environment Modules @MODULES_VERSION@
Copyright GNU GPL v2
" 1>&2
17 18
}

19 20 21 22 23 24 25 26 27 28 29
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:
30
        init [--src=<src>] [--user=<user>] <dst>
31
                Initialize a new minimal Pmodule environment.
32 33 34

        install <module> [--with=<dep>...]
                Install matching modules
35 36 37

        sync [--delete] [--dst=<dst>] <src>
                Synchronize modules.
38 39
"
}
40

41 42

declare force='no'
43 44
declare dry_run='no'
declare DRY=''
45 46
declare subcommand=''
declare sargs=()
47

48 49 50 51 52 53 54 55
subcommand_help_init() {
	echo "
init [--src=<src>] [--user=<user>] <dst>
                Initialize a new minimal Pmodule environment in directory
                <dst>. The <user> parameter must only be present if
                 ${prog} is executed as root.
" 1>&2
}
56

57 58
subcommand_help_install() {
	echo "
59
install <module>... [--with=<dep>...] [--release=<release>...] [--src=<src>]
60 61 62 63
                Install matching modules
" 1>&2
}

64 65 66 67 68 69
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).
70
                Not yet implemented:
71 72 73 74 75
                If --delete is given, unmarked modules present in <dst>
                will be deleted.
" 1>&2
}

76 77 78 79 80 81 82 83 84
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
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
#
# 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
	for ((i=1; i<${#comp[@]}; i+=2)); do
		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
#
# $1: relative modulefile path
# $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}" )

131 132 133 134 135 136
	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
137 138 139 140 141 142 143
	local -r src_modulefile="${src_prefix}/${PSI_MODULES_ROOT}/${rel_modulefile}"
	local -r src_releasefile="${src_prefix}/${PSI_MODULES_ROOT}/${rel_releasefile}"
	local -r target_modulefile="${target_prefix}/${PSI_MODULES_ROOT}/${rel_modulefile}"
	local -r target_releasefile="${target_prefix}/${PSI_MODULES_ROOT}/${rel_releasefile}"

	if [[ -e "${src_modulefile}" ]] || [[ -e "${src_releasefile}" ]]; then
	        local dir=$( dirname "${target_modulefile}" )
144
		$DRY mkdir -p "${dir}" || return $?
145 146 147 148 149 150 151 152 153
	fi
	if [[ -e "${src_modulefile}" ]]; then
	        $DRY rsync --links --perms --recursive \
		     "${src_modulefile}" "${target_modulefile}" || return $?
	fi
	if [[ -e "${src_releasefile}" ]]; then
	        $DRY rsync --links --perms --recursive \
		     "${src_releasefile}" "${target_releasefile}" || return $?
	fi
154 155
}

156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
#
# Sync the Pmodules configuration and templates
#
# $1: source prefix of Pmodule environment
# $2: target prefix of Pmodule environment
#
sync_config() {
	src="$1/${PSI_CONFIG_DIR}/"
	dst="$2/${PSI_CONFIG_DIR}/"
	$DRY rsync --recursive --links --perms --delete \
		"${src}" "${dst}" || die 1 "Error: synch operation failed!"
	echo

	src="$1/${PSI_TEMPLATES_DIR}/"
	dst="$2/${PSI_TEMPLATES_DIR}/"
	$DRY rsync --recursive --links --perms --delete \
		"${src}" "${dst}" || die 1 "Error: synch operation failed!"
	echo
}

176 177 178
#
# Delete a module
#
179 180
# $1: relative modulefile path
# $2: target prefix of Pmodule environment
181 182
#
delete_module() {
183
        echo "Not implemented yet!"
184 185
}

186 187
subcommand_init() {
	local src=''
188
	local target_prefixes=()
189 190
	local user=''
	local opts=''
191
	opts=$(get_options -o h -l src: -l user: -l help -l version: -- "$@")
192 193 194 195 196 197
	if [[ $? != 0 ]]; then
		subcommand_help_init
		exit 1
	fi
	eval set --  "${opts}"
	while (($# > 0)); do
198
		case $1 in
199 200 201 202 203 204 205 206
			--src )
				src=$2
				shift
				;;
			--user )
				user=$2
				shift
				;;
207 208 209 210
			--version )
				PMODULES_VERSION=$2
				shift
				;;
211 212 213 214 215 216 217 218 219
			-- )
				:
				;;
			-* | -h | --help )
				echo "$1: illegal option" 1>&2
				subcommand_help_init
				exit 1
				;;
			* )
220
				target_prefixes+=( "$1" )
221 222 223 224
				;;
		esac
		shift
	done
225
	(( ${#target_prefixes[@]} != 0 )) || die 1 "Error: no target directory specified!"
226

227 228
	if [[ -n "${src}" ]]; then
		[[ -d "${src}" ]] || die 1 "Error: ${src}: source directory does not exist!"
229 230
		[[ -r "${src}/config/profile.bash" ]] || \
		    die 1 "Error: ${src}: shell profile does not exist or is not readable!"
231 232 233 234
		source "${src}/config/profile.bash"
	fi
	local -i euid=$(id -u)
	if (( euid == 0 )); then
235
	        [[ -n "${user}" ]] || \
236
		    die 1 "Error: --user parameter is required!"
237 238
		id -u "${user}" > /dev/null 2>&1 || \
		    die 1 "Error: Unable to retrieve user id of user '${user}'"
239
	else
240
		[[ -z "${user}" ]] || \
241
		    die 1 "Error: --user option is only allowed if running as root!"
242
	fi
243 244 245

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

246
	echo "
247 248 249 250
Attempting to create a minimal module environment from the
environment at '${PSI_PREFIX}'
"

251
	init_pmodules_environment() {
252
		local -r src_prefix="${PSI_PREFIX}"
253
		local -r target_prefix=$1
254 255
		local src=''
		local dst=''
256 257 258 259
		echo "Initializing target directory '${target_prefix}' ..."
		echo
		if [[ -d "${target_prefix}" ]]  && [[ ${force} == no ]]; then
			echo "Warning: ${target_prefix} already exists."
260
			get_YN_answer "Do you really want to re-run the initialization? (y/N) " || \
261
				      die 1 "Abort ..."
262
		fi
263

264 265
		echo "Creating target directory '${target_prefix}'..."
		$DRY mkdir -p "${target_prefix}" || die 1 "Error: make directory failed!"
266
		echo
267

268 269 270
		echo "Syncing configuration ..."
		sync_config "${PSI_PREFIX}" \
		            "${target_prefix}" || die 1 "Error: configuration synchronization failed!"
271

272 273 274 275 276
		dst="${target_prefix}/${PSI_MODULES_ROOT}/"
		echo "Creating root directory '${dst}' for module hierarchy ..."
		$DRY mkdir -p "${dst}"
		echo

277 278
		echo "Syncing Pmodules ..."
		sync_module "Tools/Pmodules/${PMODULES_VERSION}" \
279
			    "${src_prefix}" \
280
			    "${target_prefix}" || die 1 "Error: sync Pmodules failed!"
281
		echo
282

283 284 285 286 287 288
		dst="${target_prefix}/${PSI_CONFIG_DIR}/environment.bash"
		echo "Adding installation source '${src_prefix}' to '${dst}'..."
		sed -i .bak '/PMODULES_INSTALL_SOURCE/d' "${dst}"
		echo "declare -x PMODULES_INSTALL_SOURCE=\"${src_prefix}\"" >> "${dst}"
		echo

289 290 291 292
		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
293
		fi
294
		echo "New minimal module environment created at '${target_prefix}'."
295
		echo "To use this environment, execute"
296 297
		echo "   sudo ln -fs ${target_prefix} /opt/psi"
		echo "   source /opt/psi/${PSI_CONFIG_DIR}/profile.bash"
298 299 300
	}

	umask 022
301 302
	for target_prefix in "${target_prefixes[@]}"; do
		init_pmodules_environment "${target_prefix}"
303 304
	done

305 306
}

307 308 309 310 311
subcommand_install() {
	local opts=''
	local -a with=()
	local -a releases=()
	local -a module_pattern=()
312 313
	local -r src_prefix="${PMODULES_INSTALL_SOURCE}"
	local -r target_prefix="${PSI_PREFIX}"
314

315
	opts=$(get_options -o hf -l dry-run -l force -l with: -l release: -l help -l src: -- "$@")
316 317 318 319 320 321 322
	if [[ $? != 0 ]]; then
		subcommand_help_install
		exit 1
	fi
	eval set --  "${opts}"
	while (($# > 0)); do
		case $1 in
323 324 325 326 327 328
			--dry-run )
				DRY='echo'
				;;
			--force | -f )
				force='yes'
				;;
329 330
			--release )
				releases+=( "$2" )
331 332 333 334
				shift
				;;
			--src )
				src_prefix="$2"
335 336 337 338 339 340 341 342 343
				shift
				;;
			--with )
				with+=( "$2" )
				shift
				;;
			-- )
				:
				;;
344 345 346 347 348
			-h | --help )
				subcommand_help_install
				exit 1
				;;
			-* )
349 350 351 352 353 354 355 356 357 358
				echo "$1: illegal option" 1>&2
				subcommand_help_init
				exit 1
				;;
			* )
				module_pattern+=( "$1" )
				;;
		esac
		shift
	done
359 360 361 362 363 364 365
	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[@]}" \
366
				 "${with[@]/#/--with=}" \
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388
				 "${releases[@]/#/--release=}" \
				 --no-header --print-modulefiles \
				 --src="${src_prefix}" 2>&1)
	(( 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
		echo "    ${key}" 1>&2
	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
389 390
}

391
subcommand_sync() {
392 393
        [[ -z "${PSI_PREFIX}" ]] && die 1 "Error: No current module environment is configured!"

394 395 396
	local delete=false
	local opts=''
	local dst_prefix=''
397 398
	local src_prefix=''

399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422
	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
				;;
			* )
423 424
				[[ -n "${src_prefix}" ]] && die 1 "Error: Only one source is allowed!"
				src_prefix="$1"
425 426 427 428 429
				;;
		esac
		shift
	done
	unset -v opts
430

431 432 433 434 435 436
	if [[ -z "${dst_prefix}" ]]; then
		dst_prefix="${PSI_PREFIX}"
	fi
	(
		PSI_PREFIX="${dst_prefix}" check_pmodules_env || die 1 "Error: invalid destination modules environment!"
	) || die 1 "Giving up..."
437
	: ${src_prefix:=${PMODULES_INSTALL_SOURCE}} 
438 439 440
	if [[ -z "${src_prefix}" ]]; then
            die 1 "Error: no source module environment was specified!"
        fi
441 442 443 444 445 446 447 448 449
	(
		PSI_PREFIX="${src_prefix}" check_pmodules_env || die 1 "Error: invalid source modules environment!"
	) || die 1 "Giving up..."
	[[ "$( cd "$src_prefix"; pwd -P )" == "$( cd "$dst_prefix"; pwd -P )" ]] && die 1 "Error: source and destination are equal!"
	local modbin=${PMODULES_HOME#"${PSI_PREFIX}/"}/bin/modulecmd.tcl
	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 )
	[[ "${file_type_src}" == "${file_type_dst}" ]] || die 1 "Error: The file signatures in the source and destination installation do not match!"
	unset -v file_type_src file_type_dst
450
	local dialog_script="${PMODULES_HOME}/bin/dialog.bash"
451 452 453 454 455 456
	[[ -r "$dialog_script" ]] || die 1 "Error: Unable to find dialog script of installation $dialog_script";

	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
457
        local -a selected_modules
458 459 460 461 462 463 464 465 466
	function module_out() {
		local -a args=(${modlist[$1]})
		local path=""
		IFS=/
		[[ -n "${args[3]}" ]] && path="/${args[*]:3}"
		unset IFS
		selected_modules+=( "${args[2]}${path}/${args[0]}" )
	}

467 468 469 470
	module_picker "${dst_prefix}" "${src_prefix}" || {      # this calls module_out for each selected module, filling up the selected_modules array
            echo "Abort!"
            exit 1
	}
471

472
	local -a destination_modules=( $(cd "${dst_prefix}/${PSI_MODULES_ROOT}"; find -L . -type f | while read f; do n=${f##*/}; [[ "${n:0:1}" == "." ]] || echo ${f#./}; done) )
473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491

	# redefine set difference, the version in dialog.bash only handles integers
	function set_difference() {  #  $1 \ $2
		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
492
                        echo "Deleting module $m ..."
493 494 495 496 497 498 499 500 501 502 503
			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
504
                echo "Copying module $m ..."
505 506 507 508 509
		sync_module "$m" "$src_prefix" "$dst_prefix" || die 1 "Error: syncing of module $m failed!"
        done
	unset modules_copy
}

510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530
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
			;;
531
		init|install|sync|help )
532 533 534 535 536 537 538 539 540 541
			subcommand="subcommand_$1"
			shift
			sargs=( $* )
			shift $#
			;;
		* )
			echo "$1: unknown sub-command" 1>&2
			exit 1
	esac
	shift
542
done
543 544 545 546 547 548 549 550

$subcommand "${sargs[@]}"

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