Skip to content
Open
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
141 changes: 121 additions & 20 deletions .github/workflows/end2end_bwrap.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,123 @@ jobs:
bash -lc "source /tmp/eb_env; $cmd"
done

- name: Define bwrap validation helper
shell: bash
run: |
cat > /tmp/check_bwrap_install.sh <<'EOF'
#!/usr/bin/env bash

check_no_glob_matches() {
local label="$1"
local pattern="$2"

echo ">>> CHECK: $label"
echo " glob: $pattern"

if compgen -G "$pattern" > /dev/null; then
echo "FAIL: files/directories matched"
compgen -G "$pattern" | sort
return 1
fi

echo "PASS: no files/directories matched"
echo
}

check_path_exists() {
local label="$1"
local path="$2"

echo ">>> CHECK: $label"
echo " path: $path"

if [[ -e "$path" ]]; then
echo "PASS: path exists"
ls -ld "$path"
echo
return 0
fi

echo "FAIL: path does not exist"
echo "Parent contents:"
ls -la "$(dirname "$path")" || true
echo
return 1
}

check_glob_exists() {
local label="$1"
local pattern="$2"

echo ">>> CHECK: $label"
echo " glob: $pattern"

if compgen -G "$pattern" > /dev/null; then
echo "PASS: matches found"
compgen -G "$pattern" | sort | xargs -r ls -l
echo
return 0
fi

echo "FAIL: no matches found"
echo
return 1
}

main() {
local failures=0

echo "=== Host-side EasyBuild path state ==="
find "$HOME/.local/easybuild" -maxdepth 5 -print || true
echo

echo "=== bwrap install path state ==="
find /tmp/bwrap-installpath -maxdepth 5 -print || true
echo

check_no_glob_matches "no host UnZip modules" \
"$HOME/.local/easybuild/modules/all/UnZip/*" || failures=$((failures + 1))

check_no_glob_matches "no host bzip2 modules" \
"$HOME/.local/easybuild/modules/all/bzip2/*" || failures=$((failures + 1))

check_no_glob_matches "no host UnZip software files" \
"$HOME/.local/easybuild/software/UnZip/6.0/*" || failures=$((failures + 1))

check_no_glob_matches "no host bzip2 software files" \
"$HOME/.local/easybuild/software/bzip2/1.0.8/*" || failures=$((failures + 1))

check_path_exists "bwrap bzip2 module file exists" \
"/tmp/bwrap-installpath/modules/all/bzip2/1.0.8.lua" || failures=$((failures + 1))

check_path_exists "bwrap UnZip module file exists" \
"/tmp/bwrap-installpath/modules/all/UnZip/6.0.lua" || failures=$((failures + 1))

check_glob_exists "bwrap UnZip installed binaries exist" \
"/tmp/bwrap-installpath/software/UnZip/*/bin/*" || failures=$((failures + 1))

check_glob_exists "bwrap bzip2 installed binaries exist" \
"/tmp/bwrap-installpath/software/bzip2/*/bin/*" || failures=$((failures + 1))

check_glob_exists "bwrap UnZip EasyBuild logs exist" \
"/tmp/bwrap-installpath/software/UnZip/*/easybuild/*log*" || failures=$((failures + 1))

check_glob_exists "bwrap bzip2 EasyBuild logs exist" \
"/tmp/bwrap-installpath/software/bzip2/*/easybuild/*log*" || failures=$((failures + 1))

if [[ "$failures" -ne 0 ]]; then
echo "ERROR: $failures bwrap validation check(s) failed"
return 1
fi

echo "All bwrap validation checks passed"
}

main "$@"
EOF

chmod +x /tmp/check_bwrap_install.sh

- name: End-to-end test of installing UnZip with EasyBuild in bwrap namespace
shell: bash
run: |
Expand All @@ -66,16 +183,8 @@ jobs:
# test installation of UnZip + bzip2 dependency
eb UnZip-6.0.eb --trace --robot --experimental --bwrap --bwrap-installpath /tmp/bwrap-installpath --filter-deps=binutils

# no module files should be created in configured install path (default: $HOME/.local/easybuild),
# not for UnZip, nor for bzip2 dependency
(test -d $HOME/.local/easybuild/modules/all/UnZip || echo 'no UnZip module dir found: OK') > /tmp/out-UnZip-mod
(test -d $HOME/.local/easybuild/modules/all/bzip2 || echo 'no bzip2 module dir found: OK') > /tmp/out-bzip2-mod
/tmp/check_bwrap_install.sh

# software subdirectories in configured install path should be empty
(test -e $HOME/.local/easybuild/software/UnZip/6.0/* || echo 'UnZip software dir in $HOME/.local/easybuild is empty: OK') > /tmp/out-UnZip-sw-dir
(test -e $HOME/.local/easybuild/software/bzip2/1.0.8/* || echo 'bzip2 software dir in $HOME/.local/easybuild is empty: OK') > /tmp/out-bzip2-sw-dir

grep 'no UnZip module dir found: OK' /tmp/out-UnZip-mod && grep 'no bzip2 module dir found: OK' /tmp/out-bzip2-mod && grep 'UnZip software dir in .* is empty' /tmp/out-UnZip-sw-dir && grep 'bzip2 software dir in .* is empty' /tmp/out-bzip2-sw-dir && ls -l /tmp/bwrap-installpath/modules/all/{bzip2/1.0.8.lua,UnZip/6.0.lua} && ls -l /tmp/bwrap-installpath/software/{UnZip,bzip2}/*/bin/* && ls -l /tmp/bwrap-installpath/software/{UnZip,bzip2}/*/easybuild/*log*
EOF

- name: End-to-end test of installing UnZip with EasyBuild in bwrap namespace with overlay
Expand All @@ -89,22 +198,14 @@ jobs:
source /tmp/eb_env

# remove previously created install directories
rm -r $HOME/.local/easybuild/software/* /tmp/bwrap-installpath
rm -r $HOME/.local/easybuild/{software,modules}/* /tmp/bwrap-installpath

# make install directory read-only to force using overlay
chmod -w $HOME/.local/easybuild/software
chmod -w $HOME/.local/easybuild/{software,modules}

# test installation of UnZip + bzip2 dependency
eb UnZip-6.0.eb --trace --robot --experimental --bwrap --bwrap-installpath /tmp/bwrap-installpath --filter-deps=binutils

# no module files should be created in configured install path (default: $HOME/.local/easybuild),
# not for UnZip, nor for bzip2 dependency
(test -d $HOME/.local/easybuild/modules/all/UnZip || echo 'no UnZip module dir found: OK') > /tmp/out-UnZip-mod
(test -d $HOME/.local/easybuild/modules/all/bzip2 || echo 'no bzip2 module dir found: OK') > /tmp/out-bzip2-mod

# software subdirectories in configured install path should be empty
(test -e $HOME/.local/easybuild/software/UnZip/6.0/* || echo 'UnZip software dir in $HOME/.local/easybuild is empty: OK') > /tmp/out-UnZip-sw-dir
(test -e $HOME/.local/easybuild/software/bzip2/1.0.8/* || echo 'bzip2 software dir in $HOME/.local/easybuild is empty: OK') > /tmp/out-bzip2-sw-dir
/tmp/check_bwrap_install.sh

grep 'no UnZip module dir found: OK' /tmp/out-UnZip-mod && grep 'no bzip2 module dir found: OK' /tmp/out-bzip2-mod && grep 'UnZip software dir in .* is empty' /tmp/out-UnZip-sw-dir && grep 'bzip2 software dir in .* is empty' /tmp/out-bzip2-sw-dir && ls -l /tmp/bwrap-installpath/modules/all/{bzip2/1.0.8.lua,UnZip/6.0.lua} && ls -l /tmp/bwrap-installpath/software/{UnZip,bzip2}/*/bin/* && ls -l /tmp/bwrap-installpath/software/{UnZip,bzip2}/*/easybuild/*log*
EOF
51 changes: 41 additions & 10 deletions easybuild/tools/bwrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
from easybuild.base import fancylogger
from easybuild.tools.build_log import EasyBuildError, print_msg
from easybuild.tools.config import install_path, ConfigurationVariables
from easybuild.tools.filetools import mkdir, write_file
from easybuild.tools.filetools import copy_dir, mkdir, write_file
from easybuild.tools.utilities import trace_msg


Expand Down Expand Up @@ -104,10 +104,11 @@ def prepare_bwrap(bwrap_installpath):

set_bwrap_info('bwrap_installpath', bwrap_installpath)

installpath_software = install_path(typ='software')
installpath_software = os.path.realpath(install_path(typ='software'))
set_bwrap_info('installpath_software', installpath_software)

set_bwrap_info('installpath_modules', install_path(typ='modules'))
installpath_modules = os.path.realpath(install_path(typ='modules'))
set_bwrap_info('installpath_modules', installpath_modules)

variables = ConfigurationVariables()
bwrap_installpath_software = os.path.join(bwrap_installpath, variables['subdir_software'])
Expand All @@ -117,34 +118,64 @@ def prepare_bwrap(bwrap_installpath):

bwrap_cmd = ['bwrap', '--dev-bind', '/', '/']

mkdir(bwrap_installpath_modules, parents=True)

bwrap_workdir = os.path.join(bwrap_installpath, 'workdir')

try:
mkdir(installpath_modules, parents=True)
# copy installpath_modules to bwrap_installpath_modules to ensure all installed modules are available
# required for building multiple unrelated easyconfigs (e.g. easystacks)
if os.path.exists(installpath_modules):
copy_dir(installpath_modules, bwrap_installpath_modules, dirs_exist_ok=True)
# bind mount the modules installpath
bwrap_cmd.extend([
'--bind', bwrap_installpath_modules, installpath_modules])

except EasyBuildError:
# if we can't create the external modules directory, try to use overlayfs
mkdir(bwrap_workdir, parents=True)
bwrap_cmd.extend([
'--overlay-src', installpath_modules,
'--overlay', bwrap_installpath_modules, bwrap_workdir, installpath_modules])

# store bwrap options in a set to avoid duplicate binds
bwrap_opts = set()

# bind mount all software directories
for mod in sorted(get_bwrap_info('modules_to_install')):
installdir = os.path.join(os.path.realpath(installpath_software), mod)
bwrap_installdir = os.path.join(bwrap_installpath_software, mod)
mkdir(bwrap_installdir, parents=True)

use_overlayfs = False
try:
mkdir(installdir, parents=True)
except EasyBuildError:
# if we can't create the external installation directory, try to use overlayfs
use_overlayfs = True
mkdir(bwrap_installdir, parents=True)

if use_overlayfs:
bwrap_workdir = os.path.join(bwrap_installpath, 'workdir', mod)
mkdir(bwrap_workdir, parents=True)
# go up the tree until we find a directory that exists
while not os.path.exists(installdir):
installdir = os.path.dirname(installdir)
bwrap_installdir = os.path.dirname(bwrap_installdir)
opts = ['--overlay-src', installdir, '--overlay', bwrap_installdir, bwrap_workdir, installdir]
mkdir(bwrap_workdir, parents=True)
bwrap_opts.add((
'--overlay-src', installdir,
'--overlay', bwrap_installdir, bwrap_workdir, installdir))
else:
opts = ['--bind', bwrap_installdir, installdir]
bwrap_cmd.extend(opts)
bwrap_opts.add((
'--bind', bwrap_installdir, installdir))

for x in bwrap_opts:
bwrap_cmd.extend(x)

set_bwrap_info('bwrap_cmd', bwrap_cmd)
bwrap_cmd_str = ' '.join(bwrap_cmd)

# disable `--bwrap` to prepare for a real installation (in bwrap namespace)
bwrap_eb_options = ['--disable-bwrap', f'--installpath-modules={bwrap_installpath_modules}']
bwrap_eb_options = ['--disable-bwrap']
set_bwrap_info('bwrap_eb_options', bwrap_eb_options)

_log.info(f'Info needed for bwrap: {_bwrap_info}')
Expand Down
17 changes: 10 additions & 7 deletions easybuild/tools/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -877,13 +877,16 @@ def check_module_path(self):
self.set_mod_paths()
self.log.debug("self.mod_paths set based on $MODULEPATH: %s" % self.mod_paths)

# determine module path for EasyBuild install path to be included in $MODULEPATH
eb_modpath = os.path.join(install_path(typ='modules'), build_option('suffix_modules_path'))

# make sure EasyBuild module path is in 1st place
mkdir(eb_modpath, parents=True)
self.prepend_module_path(eb_modpath)
self.log.info("Prepended list of module paths with path used by EasyBuild: %s" % eb_modpath)
# avoid creating the module path when in --bwrap mode (before actually running with bwrap),
# in case the user does not have the necessary write permissions
if not build_option('bwrap'):
# determine module path for EasyBuild install path to be included in $MODULEPATH
eb_modpath = os.path.join(install_path(typ='modules'), build_option('suffix_modules_path'))

# make sure EasyBuild module path is in 1st place
mkdir(eb_modpath, parents=True)
self.prepend_module_path(eb_modpath)
self.log.info("Prepended list of module paths with path used by EasyBuild: %s" % eb_modpath)

# set the module path environment accordingly
curr_mod_paths = curr_module_paths()
Expand Down