|
1 | 1 | #!/bin/bash
|
2 |
| -# Start interactive shell to access EESSI through build container |
3 |
| -# mkdir -p /tmp/$USER/EESSI |
4 |
| -# cd /tmp/$USER/EESSI |
5 |
| -# git clone https://github.com/EESSI/software-layer |
6 |
| -# cd software-layer |
7 |
| -# ./eessi_container.sh |
8 |
| - |
9 |
| -# Initialize EESSI + load/configure EasyBuild |
10 |
| -# source /cvmfs/software.eessi.io/versions/2023.06/init/bash |
11 |
| -# module load EasyBuild/4.9.2 |
12 |
| -# export WORKDIR=/tmp/$USER/EESSI |
13 |
| -# source configure_easybuild |
14 |
| - |
15 |
| -# .eb directory as an argument |
16 |
| -if [ -z "$1" ]; then |
17 |
| - echo "Usage: $0 <directory>" |
| 2 | + |
| 3 | +# This script checks the consistency of EB-generated modules and identifies broken or missing modules. |
| 4 | +# Usage: ./module_check.sh <path to easystack file> [<optional path to PR diff>] |
| 5 | + |
| 6 | +# It uses an adapted approach from check_missing_installations.sh to handling PRs/unmerged PRs |
| 7 | +TOPDIR=$(dirname $(realpath $0)) |
| 8 | + |
| 9 | +if [ "$#" -eq 1 ]; then |
| 10 | + echo "No PR diff provided. Processing all modules in the easystack file." |
| 11 | + pr_exceptions="" |
| 12 | +elif [ "$#" -eq 2 ]; then |
| 13 | + echo "Using $2 to create exceptions for PR filtering of easystack" |
| 14 | + pr_diff="$2" |
| 15 | + pr_exceptions=$(grep '^+' "$pr_diff" | grep 'from-pr' | uniq | awk '{print $3}' | xargs -I {} echo " || /'{}'/") |
| 16 | +else |
| 17 | + echo "ERROR: Usage: $0 <path to easystack file> [<optional path to PR diff>]" >&2 |
18 | 18 | exit 1
|
19 | 19 | fi
|
20 | 20 |
|
21 |
| -base_dir="$1" |
| 21 | +easystack="$1" |
| 22 | + |
| 23 | +LOCAL_TMPDIR=$(mktemp -d) |
| 24 | +mkdir -p "$LOCAL_TMPDIR" |
| 25 | + |
| 26 | +# Clone the develop branch of EasyBuild and use that to search for easyconfigs |
| 27 | +git clone -b develop https://github.com/easybuilders/easybuild-easyconfigs.git $LOCAL_TMPDIR/easyconfigs |
| 28 | +export EASYBUILD_ROBOT_PATHS=$LOCAL_TMPDIR/easyconfigs/easybuild/easyconfigs |
22 | 29 |
|
23 |
| -# Dir where the modules will be |
24 |
| -module_install_dir="/tmp/$USER/EESSI/module-only" |
| 30 | +# All PRs used in EESSI are supposed to be merged, so we can strip ou all cases of from-pr |
| 31 | +tmp_easystack="${LOCAL_TMPDIR}/$(basename "${easystack}")" |
| 32 | +grep -v 'from-pr' "${easystack}" > "${tmp_easystack}" |
| 33 | + |
| 34 | +# If PR exceptions exist, modify the easystack file to include exceptions |
| 35 | +if [ -n "$pr_exceptions" ]; then |
| 36 | + # Use awk to exclude lines containing PR numbers specified in pr_exceptions |
| 37 | + awk_command="awk '!/from-pr/ EXCEPTIONS' ${easystack}" |
| 38 | + awk_command=${awk_command/\\/} |
| 39 | + eval "${awk_command/EXCEPTIONS/$pr_exceptions}" > "${tmp_easystack}" |
| 40 | +fi |
25 | 41 |
|
26 |
| -locks_dir="/tmp/$USER/EESSI/locks" |
| 42 | +# Set up temporary directories for module installation and lock files |
| 43 | +TMPDIR=${TMPDIR:-/tmp}/$USER |
| 44 | +module_install_dir="$TMPDIR/EESSI/module-only" |
| 45 | +locks_dir="$TMPDIR/EESSI/locks" |
| 46 | +mkdir -p "$module_install_dir" "$locks_dir" |
27 | 47 |
|
28 |
| -# Log file to record broken modules |
| 48 | +# Log file to record broken modules |
29 | 49 | broken_modules_log="broken_modules.log"
|
30 |
| -> $broken_modules_log |
| 50 | +> "$broken_modules_log" |
31 | 51 |
|
| 52 | +# To keep track of already-checked modules and avoid re-checking |
32 | 53 | declare -A checked_modules
|
33 | 54 |
|
34 |
| -# Locate all .eb files within the base dir |
35 |
| -easyconfig_files=$(find $base_dir -name "*.eb") |
| 55 | +# Identify missing easyconfigs based on the temporary easystack file |
| 56 | +echo "Identifying missing easyconfigs using the temporary easystack file..." |
| 57 | +missing_easyconfigs=$(eb --easystack "${tmp_easystack}" --missing --robot 2>&1) |
36 | 58 |
|
37 |
| -# Iterate over all eb files found. Package name based on eb file name |
38 |
| -for easyconfig_file in $easyconfig_files; do |
39 |
| - package_name=$(basename $easyconfig_file .eb) |
| 59 | +if [ -z "$missing_easyconfigs" ]; then |
| 60 | + echo "No missing easyconfigs to install." |
| 61 | + rm -rf "$LOCAL_TMPDIR" |
| 62 | + exit 0 |
| 63 | +fi |
| 64 | + |
| 65 | +# Process each missing easyconfig file |
| 66 | +for easyconfig_file in $missing_easyconfigs; do |
| 67 | + package_name=$(basename "$easyconfig_file" .eb) |
| 68 | + |
| 69 | + # Building of the easyconfig |
| 70 | + echo "Building $package_name using EasyBuild..." |
| 71 | + eb "$easyconfig_file" --robot |
| 72 | + if [ $? -ne 0 ]; then |
| 73 | + echo "EasyBuild build failed for $package_name. Skipping..." |
| 74 | + echo "$package_name: EasyBuild build failed" >> "$broken_modules_log" |
| 75 | + continue |
| 76 | + fi |
40 | 77 |
|
41 |
| - # Run EB to generate the modules. Check if the eb command failed. |
42 |
| - echo "Generating modules for $package_name using EasyBuild..." |
43 |
| - eb $easyconfig_file --module-only --installpath-modules $module_install_dir --locks-dir $locks_dir --force --robot |
| 78 | + # Generate the module using --module-only |
| 79 | + echo "Generating module for $package_name using --module-only..." |
| 80 | + eb "$easyconfig_file" --module-only --installpath-modules "$module_install_dir" --locks-dir "$locks_dir" --force --robot |
44 | 81 | if [ $? -ne 0 ]; then
|
45 |
| - echo "EasyBuild command failed for $package_name. Skipping..." |
46 |
| - echo "$package_name: EasyBuild command failed" >> $broken_modules_log |
| 82 | + echo "EasyBuild --module-only command failed for $package_name. Skipping..." |
| 83 | + echo "$package_name: EasyBuild --module-only command failed" >> "$broken_modules_log" |
| 84 | + continue |
| 85 | + fi |
| 86 | + |
| 87 | + # Find the module file generated from the build |
| 88 | + module_relpath=$(eb "$easyconfig_file" --show-module --robot 2>/dev/null) |
| 89 | + if [ -z "$module_relpath" ]; then |
| 90 | + echo "Failed to get module relative path for $package_name" |
| 91 | + echo "$package_name: Failed to get module relative path" >> "$broken_modules_log" |
| 92 | + continue |
| 93 | + fi |
| 94 | + |
| 95 | + # Modules names and version |
| 96 | + module_software=$(echo "$module_relpath" | sed 's/\.lua$//') |
| 97 | + |
| 98 | + # Check if the module has already been validated to avoid redundant checks |
| 99 | + if [ -n "${checked_modules[$module_software]}" ]; then |
| 100 | + echo "Module $module_software already checked. Skipping." |
47 | 101 | continue
|
48 | 102 | fi
|
49 | 103 |
|
50 |
| - # Check the generated modules and iterate over the modules in the 'all' dir |
51 |
| - echo "Checking generated modules for $package_name..." |
52 |
| - for module_category in $(ls $module_install_dir/all); do |
53 |
| - for module_version in $(ls $module_install_dir/all/$module_category); do |
54 |
| - module_name="$module_category/$module_version" |
55 |
| - |
56 |
| - # Checks if the module has already been tested |
57 |
| - if [ -n "${checked_modules[$module_name]}" ]; then |
58 |
| - echo "Module $module_name already checked. Skipping." |
59 |
| - continue |
60 |
| - fi |
61 |
| - |
62 |
| - echo "Testing module: $module_name" |
63 |
| - |
64 |
| - # Try loading the module |
65 |
| - if module --ignore_cache load $module_name 2>/dev/null; then |
66 |
| - echo "$module_name loaded successfully." |
67 |
| - module unload $module_name |
68 |
| - else |
69 |
| - echo "$module_name is broken." |
70 |
| - echo "$package_name: $module_name" >> $broken_modules_log |
71 |
| - fi |
72 |
| - |
73 |
| - checked_modules[$module_name]=1 |
74 |
| - done |
75 |
| - done |
| 104 | + # Paths to the module files generated from build and the --module-only |
| 105 | + module_file_build="${EASYBUILD_INSTALLPATH}/modules/all/${module_relpath}" |
| 106 | + module_file_module_only="${module_install_dir}/all/${module_relpath}" |
| 107 | + |
| 108 | + # Check if both module files exist |
| 109 | + if [ ! -f "$module_file_build" ]; then |
| 110 | + echo "Module file from full build not found: $module_file_build" |
| 111 | + echo "$package_name: Module file from full build not found" >> "$broken_modules_log" |
| 112 | + continue |
| 113 | + fi |
| 114 | + |
| 115 | + if [ ! -f "$module_file_module_only" ]; then |
| 116 | + echo "Module file from --module-only build not found: $module_file_module_only" |
| 117 | + echo "$package_name: Module file from --module-only build not found" >> "$broken_modules_log" |
| 118 | + continue |
| 119 | + fi |
| 120 | + |
| 121 | + # Compare the module files |
| 122 | + if diff -q "$module_file_build" "$module_file_module_only" >/dev/null; then |
| 123 | + echo "Module files for $package_name match" |
| 124 | + else |
| 125 | + echo "Module files for $package_name differ" |
| 126 | + echo "$package_name: Module files differ" >> "$broken_modules_log" |
| 127 | + # Save differences |
| 128 | + diff_file="${module_software//\//_}_module_diff.txt" |
| 129 | + diff "$module_file_build" "$module_file_module_only" > "$diff_file" |
| 130 | + echo "Module file differences saved to $diff_file" |
| 131 | + fi |
| 132 | + |
| 133 | + # Proceed to compare the environments |
| 134 | + echo "Testing module: $module_software" |
| 135 | + |
| 136 | + # Function to get filtered environment variables, excluding lmod-related vars |
| 137 | + get_filtered_env() { |
| 138 | + env | grep -v -E '^(LMOD_|MODULEPATH|MODULESHOME|LOADEDMODULES|BASH_FUNC_module|_ModuleTable_|PWD=|SHLVL=|OLDPWD=|PS1=|PS2=|_LMFILES_)=.*$' | sort |
| 139 | + } |
| 140 | + |
| 141 | + # Compare the environments of the modules |
| 142 | + module purge |
| 143 | + module unuse "$module_install_dir" |
| 144 | + module load EasyBuild |
| 145 | + |
| 146 | + # Load the module from the full build |
| 147 | + if module --ignore_cache load "$module_software" 2>/dev/null; then |
| 148 | + original_env=$(get_filtered_env) |
| 149 | + module unload "$module_software" |
| 150 | + else |
| 151 | + echo "Failed to load module from full build: $module_software." |
| 152 | + original_env="" |
| 153 | + fi |
| 154 | + |
| 155 | + # Load the module from the --module-only |
| 156 | + module purge |
| 157 | + module use "$module_install_dir" |
| 158 | + |
| 159 | + if module --ignore_cache load "$module_software" 2>/dev/null; then |
| 160 | + new_env=$(get_filtered_env) |
| 161 | + module unload "$module_software" |
| 162 | + else |
| 163 | + echo "Failed to load module from --module-only build: $module_software." |
| 164 | + echo "$package_name: Failed to load module from --module-only build" >> "$broken_modules_log" |
| 165 | + module unuse "$module_install_dir" |
| 166 | + continue |
| 167 | + fi |
| 168 | + |
| 169 | + # Compare the environments |
| 170 | + if [ -n "$original_env" ]; then |
| 171 | + if diff <(echo "$original_env") <(echo "$new_env") >/dev/null; then |
| 172 | + echo "$module_software loaded with identical environment." |
| 173 | + else |
| 174 | + echo "$module_software environment mismatch." |
| 175 | + echo "$package_name: $module_software (environment mismatch)" >> "$broken_modules_log" |
| 176 | + diff_file="${module_software//\//_}_env_diff.txt" |
| 177 | + diff <(echo "$original_env") <(echo "$new_env") > "$diff_file" |
| 178 | + echo "Environment differences saved to $diff_file" |
| 179 | + fi |
| 180 | + else |
| 181 | + echo "Original environment not available for comparison for $module_software." |
| 182 | + echo "$package_name: $module_software (failed to load module from full build)" >> "$broken_modules_log" |
| 183 | + fi |
| 184 | + |
| 185 | + |
| 186 | + module unuse "$module_install_dir" |
| 187 | + |
| 188 | + # Mark module as checked |
| 189 | + checked_modules[$module_software]=1 |
| 190 | + |
76 | 191 | done
|
77 | 192 |
|
78 |
| -echo "All module checks completed. Broken modules are listed in $broken_modules_log" |
| 193 | +# Report |
| 194 | +if [ -f "$broken_modules_log" ] && [ -s "$broken_modules_log" ]; then |
| 195 | + echo "Some modules did not match. See $broken_modules_log for details." |
| 196 | + exit 1 |
| 197 | +else |
| 198 | + echo "All modules match between build and --module-only build." |
| 199 | +fi |
| 200 | + |
| 201 | +# Clean up temporary directories |
| 202 | +rm -rf "$LOCAL_TMPDIR" |
79 | 203 |
|
0 commit comments