Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 28 additions & 4 deletions .github/workflows/create-deployment-spack.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,25 @@ on:
required: true
default: main
description: A version of ACCESS-NRI/spack-config
deployment-location:
spack-deployment-location:
type: string
required: true
description: |
A path in the deployment environment where Spack should be created.
For example, if it is `opt/spack`, spack will be installed under `opt/spack/<spack-version>/`
module-deployment-location:
type: string
required: true
description: |
A path in the deployment environment where modules referencing the deployed Spack should be created.
For example, if it is `opt/modules/spack` for spack v1.1, the modulefile created will be under `opt/modules/spack/1.1`
jobs:
log-inputs:
name: Log Inputs
runs-on: ubuntu-latest
steps:
- run: |
echo '::notice::url = `${{ inputs.spack-git-url }}`, spack version = `${{ inputs.spack-version }}`, spack-config version = `${{ inputs.spack-config-version }}`, deployment location = `${{ inputs.deployment-location }}`'
echo '::notice::url = `${{ inputs.spack-git-url }}`, spack version = `${{ inputs.spack-version }}`, spack-config version = `${{ inputs.spack-config-version }}`, deployment location = `${{ inputs.spack-deployment-location }}`, module location = `${{ inputs.module-deployment-location }}`'

create-spack:
name: Spack
Expand All @@ -48,11 +54,12 @@ jobs:
id: strip
# this step removes the 'release/' part from some spack tags, so our
# directory structure doesn't contain a 'releases' subdirectory
run: echo "version-dir=$(echo '${{ inputs.spack-version }}' | cut --delimiter 'v' --fields 2)" >> $GITHUB_OUTPUT
run: echo "version=$(echo '${{ inputs.spack-version }}' | cut --delimiter 'v' --fields 2)" >> $GITHUB_OUTPUT

- name: Install
id: spack-install
env:
ROOT_VERSION_LOCATION: ${{ inputs.deployment-location }}/${{ steps.strip.outputs.version-dir }}
ROOT_VERSION_LOCATION: ${{ inputs.spack-deployment-location }}/${{ steps.strip.outputs.version }}
# This step will fail if `mkdir` fails to create a version directory (e.g. `0.20` is already deployed and exists)
run: |
ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} -i ${{ steps.ssh.outputs.private-key-path }} /bin/bash <<'EOT'
Expand All @@ -74,3 +81,20 @@ jobs:

. ${{ env.ROOT_VERSION_LOCATION }}/spack/share/spack/setup-env.sh
spack -d bootstrap now

- name: Checkout build-cd for modulefiles
uses: actions/checkout@v6

- name: Deploy modulefiles
run: |
rsync --archive \
-i ${{ steps.ssh.outputs.private-key-path }} \
tools/modules/spack/bash_script.sh \
${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:${{ inputs.module-deployment-location }}/.${{ steps.strip.outputs.version }}.sh

rsync --archive \
-i ${{ steps.ssh.outputs.private-key-path }} \
tools/modules/spack/modulefile \
${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:${{ inputs.module-deployment-location }}/${{ steps.strip.outputs.version }}

echo "::notice::Modulefiles for ${{ steps.strip.outputs.version }} deployed. This does not affect the default module."
Comment thread
atteggiani marked this conversation as resolved.
2 changes: 2 additions & 0 deletions tools/modules/spack/.modulerc
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A comment on this file:

This is an example.
We can decide to set the default version like this, or have another way for setting it.

If we choose this method, this file in the repo should probably be dynamic, with the version set at deployment time.

Other solutions (which would not require a dynamic file) could be:

  • set the default version to the latest one deployed (if we're sure we always want the latest-deployed version to be the default one)
  • default version automatically set based on version name (in this case we need a stable version naming scheme, and I think we do)

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#%Module1.0
module-version spack/1.1 default
159 changes: 159 additions & 0 deletions tools/modules/spack/bash_script.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# Set TCL variables passed as arguments
# old environment env var
old_env_var_name="$1"
# setup-env script
setup_env_script="$2"
# python path used by spack
spack_python="$3"
# spack root
spack_root="$4"

# Create a name reference variable, so we can use the reference to dynamically set/update the variable
declare -n old_env_var_reference="$old_env_var_name"

# Set regex for bash functions in the environment printout
function_regex='^BASH_FUNC_(.+)%%=\(\) (.*)'

# ==============================================================================
# Capture the environment and aliases before sourcing the setup-env script
# ==============================================================================
# Functions and variables
declare -A before_functions
declare -A before_variables
while IFS= read -r -d '' entry; do
if [[ "$entry" =~ $function_regex ]]; then
# Set function
name="${BASH_REMATCH[1]}"
value="${BASH_REMATCH[2]}"
before_functions["$name"]="$value"
else
# Set variable
name="${entry%%=*}"
value="${entry#*=}"
before_variables["$name"]="$value"
fi
done < <(env -0)
#Aliases
declare -A before_aliases
while IFS= read -r entry; do
entry="${entry#alias }"
name="${entry%%=*}"
value="${entry#*=}"
before_aliases["$name"]="$value"
done < <(alias -p)

# ==============================================================================
# Source the setup-env script
# ==============================================================================
# Set up SPACK_PYTHON
export SPACK_PYTHON="$spack_python"
# Set up SPACK_ROOT
export SPACK_ROOT="$spack_root"
source "$setup_env_script" > /dev/null

# ==============================================================================
# Capture the environment and aliases after sourcing the setup-env script
# ==============================================================================
# Functions and variables
declare -A after_functions
declare -A after_variables
while IFS= read -r -d '' entry; do
if [[ "$entry" =~ $function_regex ]]; then
# Set function
name="${BASH_REMATCH[1]}"
value="${BASH_REMATCH[2]}"
after_functions["$name"]="$value"
else
# Set variable
name="${entry%%=*}"
value="${entry#*=}"
after_variables["$name"]="$value"
fi
done < <(env -0)
# Aliases
declare -A after_aliases
while IFS= read -r entry; do
entry="${entry#alias }"
name="${entry%%=*}"
value="${entry#*=}"
after_aliases["$name"]="$value"
done < <(alias -p)

# ==============================================================================
# Parse before and after variables, functions and aliases to find addition, changes and removals
# ==============================================================================
# FUNCTIONS
# Added and changed
for name in "${!after_functions[@]}"; do
value="${after_functions[$name]}"
if [[ ! -v before_functions["$name"] ]]; then
# Added function: print command for it to be exported and record the command for it to be unset
printf '%s ; ' "${name}() $value ; export -f $name"
old_env_var_reference+=$(printf '%s ;' "unset -f $name")
elif [[ "${before_functions[$name]}" != "$value" ]]; then
# Changed function: print command for it to be exported and record command for it to be reinstated
printf '%s ; ' "${name}() $value ; export -f $name"
old_value="${before_functions["$name"]}"
old_env_var_reference+=$(printf '%s ; ' "${name}() $old_value ; export -f $name")
fi
done
# Removed
for name in "${!before_functions[@]}"; do
if [[ ! -v after_functions["$name"] ]]; then
# Removed function: record command for it to be reinstated
old_value="${before_functions[$name]}"
old_env_var_reference+=$(printf '%s ; ' "${name}() $old_value ; export -f $name")
fi
done
# VARIABLES
# Added and changed
for name in "${!after_variables[@]}"; do
value="${after_variables[$name]}"
if [[ ! -v before_variables["$name"] ]]; then
# Added variable: print command for it to be exported and record the command for it to be unset
printf '%s ; ' "export $name=$value"
old_env_var_reference+=$(printf '%s ; ' "unset $name")
elif [[ "${before_variables[$name]}" != "$value" ]]; then
# Changed variable: print command for it to be exported and record command for it to be reinstated
printf '%s ; ' "export $name=$value"
old_value="${before_variables["$name"]}"
old_env_var_reference+=$(printf '%s ; ' "export $name=$old_value")
fi
done
# Removed
for name in "${!before_variables[@]}"; do
if [[ ! -v after_variables["$name"] ]]; then
# Removed variable: record command for it to be reinstated
old_value="${before_variables["$name"]}"
old_env_var_reference+=$(printf '%s ; ' "export $name=$old_value")
fi
done
# ALIASES
# Added and changed
for name in "${!after_aliases[@]}"; do
value="${after_aliases[$name]}"
if [[ ! -v before_aliases["$name"] ]]; then
# Added aliase: print command for it to be set and record the command for it to be unset
printf '%s ; ' "alias $name=$value"
old_env_var_reference+=$(printf '%s ; ' "unalias $name")
elif [[ "${before_aliases[$name]}" != "$value" ]]; then
# Changed aliase: print command for it to be set and record command for it to be reinstated
printf '%s ; ' "alias $name=$value"
old_value="${before_aliases["$name"]}"
old_env_var_reference+=$(printf '%s ; ' "alias $name=$old_value")
fi
done
# Removed
for name in "${!before_aliases[@]}"; do
if [[ ! -v after_aliases["$name"] ]]; then
# Removed aliase: record command for it to be unset
old_value="${before_aliases["$name"]}"
old_env_var_reference+=$(printf '%s ; ' "unalias $name")
fi
done

# Print a null character as a separator, to help splitting the commands to set the new environment
# from those to reinstate the old environment
printf '\x00'
# Print the commands to reinstate the old environment
echo "${!old_env_var_name}"
82 changes: 82 additions & 0 deletions tools/modules/spack/modulefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#%Module1.0

# Get module name and version
lassign [split [module-info name] {/}] module_name module_version

conflict $module_name

# Get the spack root directory for the spack instance.
# First, we get this modulefile parent directory, then we construct the spack root as
# <parent-directory>/../../apps/spack/<version>/spack
set spack_module_dir [file dirname [info script]]
set base_dir [file normalize "$spack_module_dir/../../apps/spack/$module_version"]
set spack_root "$base_dir/spack"

# Set spack setup-env.sh
set setup_env "$spack_root/share/spack/setup-env.sh"

# Set Python to be used with spack
set spack_python /bin/python3

# Set the name of the env variable used to store the current environment information so
# the environment can be reinstated when this module is unloaded
set old_env_var_name _SPACK_MODULE_OLD_ENV

# If the module is loaded
if {[module-info mode load]} {
### Set spack environment
# Since the Module version is < 5.0 we cannot directly source spack's setup-env script within this modulefile.
# A work-around is to run a bash script that computes the environment before and after sourcing the spack's
# setup-env and:
# - When the module is loaded, set the new variables and functions.
# When the module is unloaded, unset them.
# - When the module is loaded, set the variables and functions that changed to their new values.
# When the module is unloaded, re-set them to their old values.

# The bash script is in the same directory as this modulefile, and is named .<module_version>.sh
set bash_script "$spack_module_dir/.$module_version.sh"

# Run the bash script and capture its output into a variable
set bash_commands [exec /bin/bash $bash_script \
$old_env_var_name \
$setup_env \
$spack_python \
$spack_root]

# Split the variable with null character (\x00) to get the commands to set the new environment
# and those to reinstate the old environment
lassign [split $bash_commands "\x00"] new_env_commands old_env_commands

# Set the environment variable to reinstate the old environment
puts "export $old_env_var_name='$old_env_commands'"

# Set the new environment
puts $new_env_commands

puts stderr "Spack environment set up using $setup_env"
} else {
# If the module is unloaded, we use the old_env_var_name env variable to reinstate the old
# environment
if {[info exists env($old_env_var_name)]} {
puts $env($old_env_var_name)
# Unset the old_env_var_name
puts "unset $old_env_var_name"
} else {
# If the old_env_var_name env variable doesn't exist, print out a warning message
puts stderr [string cat \
"WARNING: The $old_env_var_name env variable which stores the environment " \
"information prior to loading the $module_name module is not set. Environment restoration failed."
]
}
}

# Set spack user cache path
set spack_admin_config_env_var_name ACCESS_SPACK_ADMIN
if { [info exists env($spack_admin_config_env_var_name)] } {
setenv SPACK_USER_CACHE_PATH "$base_dir/spack-admin-cache"
} else {
setenv SPACK_USER_CACHE_PATH "/g/data/$env(PROJECT)/$env(USER)/spack/$module_version/spack-user-cache"
if {[module-info mode load]} {
puts stderr "Spack user cache set to $env(SPACK_USER_CACHE_PATH)"
}
}
Loading