Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
151 commits
Select commit Hold shift + click to select a range
c61c437
retry fetching extensions from PyPI with alternative download_filename
Jun 27, 2025
04da499
use is_pypi_source variable name
Jun 30, 2025
d647db8
deprecate support for running EasyBuild with Python < 3.9
boegel Jul 30, 2025
ce6acf3
Make command environment of shell commands more discoverable in the log
Flamefire Sep 15, 2025
da8aa4e
Combine log messages for run_shell_cmd into 1
Flamefire Sep 15, 2025
c03b922
Test that report_test_failure shows formatted text
Flamefire Sep 26, 2025
e83556b
Use `EasyBuildError.msg` for `report_test_failure`
Flamefire Sep 26, 2025
8cfea98
Revert "Use `EasyBuildError.msg` for `report_test_failure`"
Flamefire Sep 26, 2025
c6bf21c
Return message when converting `EasyBuildError` to string
Flamefire Sep 26, 2025
8be2c5d
Adapt tests
Flamefire Sep 29, 2025
f9827f9
Avoid needless `module list` call in `load()` function
Flamefire Oct 6, 2025
5476d7f
Introduce context manager for restoring environment in tests
Flamefire Oct 6, 2025
b074752
Handle post_install_patches of extension in GitHub uploads
Flamefire Oct 7, 2025
71ee773
Also handle postinstallpatches at root level
Flamefire Oct 7, 2025
23ed09a
Add test for postinstallpatches handling in GitHub integration
Flamefire Oct 7, 2025
2102678
Remove superflous check
Flamefire Oct 7, 2025
d2d36c6
Improve test report formatting
Flamefire Oct 7, 2025
822faeb
make extended status bold
Flamefire Oct 7, 2025
1c32d9e
Fix formatting
Flamefire Oct 7, 2025
8b92e94
Adapt test
Flamefire Oct 7, 2025
76e4666
Remove superflous check
Flamefire Mar 3, 2025
83f7a89
Add support for `--disable-robot`
Flamefire Mar 3, 2025
3a29ef3
Fix test
Flamefire Mar 24, 2025
f403b77
Add decorator to ignore PR test failures due to rate limit
Flamefire Oct 7, 2025
35d15c3
Add templates for source with just version numbers
Micket Oct 11, 2025
a7759a3
Update easybuild/framework/easyconfig/templates.py
Micket Oct 13, 2025
fb81732
Add templates for patch versions
Flamefire Oct 20, 2025
8731fdc
Also add `version_major_minor_patch` template
Flamefire Oct 20, 2025
542deef
Enhance wording
Flamefire Oct 23, 2025
4a3f703
Combine string building
Flamefire Oct 23, 2025
2d69680
add search_path_vars_headers and search_path_vars_linker properties t…
lexming Oct 24, 2025
4e773d3
WIP: LLVM toolchain
Crivella Feb 13, 2025
01638da
Improved for LLVM specific toolchain
Crivella May 22, 2025
494a0d3
Added GCCcore as a subtoolchain
Crivella May 26, 2025
df4f251
Fix version
Crivella May 26, 2025
ad7c190
Revert changes to `clang.py`
Crivella May 27, 2025
274262f
Removed OpenBLAS dep from lfoss
Crivella Jun 5, 2025
96ec6b1
Uue a unified `llvm.py` for the compiler and a new `llvm.py` for the …
Crivella Jun 5, 2025
813cf4f
lint
Crivella Jun 5, 2025
595f4e6
fix use a string that really should not match
Crivella Jun 11, 2025
8ec06cd
Added `-Wno-error=int-conversion` as a default present parameter
Crivella Jul 14, 2025
2270a59
Added known unsupported flag `math-errno`
Crivella Jul 21, 2025
ce38592
LLVM 21 now support vectorize flags
Crivella Aug 28, 2025
41d47bf
Fix typos and unchanged copy sections
Crivella Sep 29, 2025
19efb5e
add error message to Toolchain._search_path_vars()
lexming Nov 5, 2025
bd6e428
Fix LLVM compiler not propagating some toolchain arguments
Thyre Nov 7, 2025
8ebac9a
Add MPICH and MPFLF toolchains
Thyre Nov 7, 2025
c7eb1c6
Merge pull request #1 from Thyre/exp-LLVMtoolchain
Crivella Nov 7, 2025
c6d365c
Rename `LLVMtc` -> `llvm-compilers`
Crivella Nov 7, 2025
31a27c6
Add changes to support LLVM linker in rpath wrappers
Crivella Nov 10, 2025
5b33aed
take into location of header files of dependencies in toolchain metho…
boegel Nov 12, 2025
f60db75
Merge pull request #5041 from boegel/fix_toolchain_add_dependency_cpp…
bedroge Nov 13, 2025
1d1346f
also pick up on toolchain components in Toolchain._add_dependency_var…
boegel Nov 13, 2025
1ceb141
add test easyconfigs so toolchain hierarchy for gompi/2023a can be co…
boegel Nov 13, 2025
b4edc71
extend test_exts_deps_build_env to also check for path to header file…
boegel Nov 13, 2025
f7b811b
fix tests that got broken by adding additional test easyconfigs
boegel Nov 13, 2025
7805471
Merge pull request #5042 from boegel/fix_toolchain_add_dependency_var…
bedroge Nov 13, 2025
c963200
Remove superflous `module list` call
Flamefire Nov 14, 2025
845200c
Refactor: Minor simplifications
Flamefire Nov 14, 2025
04dbf3d
Enable comprehension rules flake8 rule
Flamefire Nov 14, 2025
8eb3bd5
Remove Python 2 workaround in flake8 setting
Flamefire Nov 14, 2025
486631d
Ignore some logging related flake8 rules
Flamefire Nov 14, 2025
2628b93
Add `runtime_only` param to `dependency_names`
Flamefire Nov 14, 2025
148267c
Fix flake8-comprehension issues
Flamefire Nov 14, 2025
f619947
Add backtrace to logged exception
Flamefire Nov 14, 2025
af01256
Use callstack as name
Flamefire Nov 14, 2025
5be4b97
Fix error due to access to toolchain build dependencies
Flamefire Nov 14, 2025
e2e8025
support both $VAR and ${VAR} variable formats in modextravars
smoors Nov 14, 2025
9b29892
add tests
smoors Nov 14, 2025
8285fc2
enhance test_exts_deps_build_env to verify fix made in #5048
boegel Nov 14, 2025
b4ef5df
Merge pull request #5048 from Flamefire/fix-runtime-tc-deps
boegel Nov 14, 2025
5d44902
add test for escaped variables
smoors Nov 15, 2025
42f4437
fix test
smoors Nov 15, 2025
8de5e93
Merge pull request #5019 from Flamefire/rate-limit-test
lexming Nov 18, 2025
e1d03ae
Merge pull request #5044 from Flamefire/runtime-dep-names
boegel Nov 19, 2025
76e684d
Merge pull request #5043 from Flamefire/comprehensions
boegel Nov 19, 2025
8385f40
Merge pull request #5046 from Flamefire/no-mod-list
boegel Nov 19, 2025
dfd3553
Merge pull request #5028 from Flamefire/patch-version-template
boegel Nov 19, 2025
fffc836
Update easybuild/framework/easyconfig/templates.py
Micket Nov 20, 2025
67c8994
add constants back
smoors Nov 20, 2025
8ca8e3a
Move `assert_multi_regex` to `EnhancedTestCase`
Flamefire Nov 20, 2025
9ffa140
Add test for backtrace logging
Flamefire Nov 20, 2025
7faa56c
add tests for wrap_shell_vars_function
smoors Nov 20, 2025
37dbfa0
Don't print location of LoggedException constructor as source of error.
Flamefire Nov 20, 2025
91252e1
Make test more strict
Flamefire Nov 20, 2025
0b16507
Disable trace output by default in tests
Flamefire Nov 20, 2025
825f842
Merge pull request #5015 from Flamefire/restore-test-env
boegel Nov 21, 2025
ccf9aa7
Update easybuild/framework/easyconfig/templates.py
Micket Nov 21, 2025
57d8a45
Show more readable error message when uploading gist or posting comme…
Flamefire Nov 21, 2025
7d18069
Merge branch 'develop' into alt-pypi
Nov 21, 2025
6ea3564
Merge branch 'develop' into disable-test-trace
Flamefire Nov 21, 2025
efe3412
Handle some linter issues
Flamefire Mar 7, 2025
4915582
Fix description of exts_defaultclass
Flamefire Mar 7, 2025
f595469
Remove redundant assertion
Flamefire Mar 7, 2025
9c39abe
Make specifying `exts_defaultclass` optional
Flamefire Mar 7, 2025
9456d63
update rpath_args.py script to take into account that linker command …
boegel Nov 23, 2025
164df4e
use tuple of LINKERS value in llvm-compilers definition + make sure t…
boegel Nov 23, 2025
ca78ecf
Apply suggestions from code review
Crivella Nov 26, 2025
f3f2399
Remove `lolf` toolchain
Crivella Nov 26, 2025
02755c9
Remove noop `optarch` from `COMPILER_UNIQUE_OPTION_MAP`
Crivella Nov 26, 2025
232ef0b
Attempt setting `lld_undefined_version` to default ot false
Crivella Nov 26, 2025
2885cad
Merge pull request #4943 from smoors/alt-pypi
jfgrimm Nov 27, 2025
9e78979
Set an environment variable to indicate an EB session
ocaisa Nov 28, 2025
f1e1601
also cover aarch64 in COMPILER_OPTIMAL_ARCHITECTURE_OPTION and COMPIL…
boegel Nov 29, 2025
d95636f
use -mcpu=generic instead if -march=aarch64 for LLVM compiler with --…
boegel Nov 29, 2025
98f6a32
Don't append empty component to start_dir
Flamefire Nov 24, 2025
5e8b0ee
Fix check in test_start_dir_template
Flamefire Nov 24, 2025
b426cd9
set $___EASYBUILD___ via prepare_main in main.py
boegel Dec 1, 2025
383dcbd
Merge pull request #5047 from Flamefire/20251114172150_new_pr_YMYMOpxEbY
boegel Dec 3, 2025
0fd9f82
Merge pull request #5049 from smoors/envvars-sub
boegel Dec 3, 2025
1f9aad6
Merge pull request #5057 from Flamefire/start_dir_slash
boegel Dec 3, 2025
345d231
Merge pull request #4800 from Flamefire/optional-exts_defaultclass
boegel Dec 3, 2025
2ffaa39
Merge pull request #5053 from Flamefire/disable-test-trace
boegel Dec 3, 2025
cdceb5a
Merge pull request #5025 from Micket/version_templates
boegel Dec 3, 2025
fef44a5
Merge pull request #48 from boegel/eb_session_var
ocaisa Dec 3, 2025
1ec1fb1
Merge branch 'develop' into deprecate_py39
boegel Dec 3, 2025
889a06b
Merge pull request #5056 from Flamefire/http-gist-error
boegel Dec 3, 2025
efe239b
Merge pull request #5058 from ocaisa/eb_session_var
boegel Dec 3, 2025
00eb564
only print warning on using Python > 3.9 when $GITHUB_ACTIONS is not …
boegel Dec 3, 2025
b6d0646
Fix skip message in test
Flamefire Dec 4, 2025
42265b9
CI: fix cut param
Flamefire Dec 4, 2025
5fa01e7
Add CI for Python 3.6
Flamefire Dec 3, 2025
3f9d989
Avoid segfault with pyenv installed Python
Flamefire Dec 4, 2025
80f5830
Don't unset environment keys while iterating the dict
Flamefire Dec 4, 2025
0b08dfc
Fix symlink handling in `copy_dir` in Python 3.6
Flamefire Dec 5, 2025
1d9210c
Pin Python packages not supported in 3.6 anymore
Flamefire Dec 5, 2025
9a184b2
Merge pull request #5064 from Flamefire/python3.6
boegel Dec 6, 2025
7515263
Merge pull request #4914 from Crivella/exp-LLVMtoolchain
boegel Dec 6, 2025
981cfd6
fix test_github_det_commit_status by using more recent commits
boegel Dec 7, 2025
d3e75a2
Merge pull request #5066 from boegel/fix_github_test
branfosj Dec 7, 2025
b75f4f2
Merge pull request #4966 from boegel/deprecate_py39
Micket Dec 7, 2025
a720482
Merge pull request #5009 from Flamefire/improve-error-reporting
boegel Dec 7, 2025
ea5834e
Merge pull request #4999 from Flamefire/dump-env-visibility
boegel Dec 7, 2025
0250933
add newline before starting code block for traceback in test report
boegel Dec 7, 2025
0eb76dd
Merge pull request #5031 from lexming/access-header-vars
boegel Dec 7, 2025
cb8b421
Merge pull request #4781 from Flamefire/disable-robot
boegel Dec 7, 2025
121b4b0
Merge pull request #5016 from Flamefire/20251007085255_new_pr_IpOuRJvyQP
boegel Dec 7, 2025
633cc4b
Merge pull request #5018 from Flamefire/20251007122536_new_pr_XPJPAxSjWb
boegel Dec 7, 2025
3925b51
Include `tomli` 2.3.0
Flamefire Dec 3, 2025
1ccc310
Remove Python 3.7+ features
Flamefire Dec 3, 2025
869c136
Exclude tomli from flake8
Flamefire Dec 3, 2025
b16000e
Add sanity check for `easybuild_packages`
Flamefire Dec 8, 2025
5af8b8f
Add tools.tomlib to easybuild packages
Flamefire Dec 8, 2025
a65fa8b
also accept f suffix in cuda compute capabilities regex
bedroge Dec 9, 2025
da23d7e
allow f suffix in cuda cc regex used to extract info from cuobjdump o…
bedroge Dec 9, 2025
f565c11
also use CUDA CC 9.0a and 10.0f in test_cuda_compute_capabilities
bedroge Dec 9, 2025
95525be
use CUDA archs 10.0, 10.0a, 10.0f for CUOBJDUMP_DEVICE_CODE_ONLY
bedroge Dec 9, 2025
8814af9
Merge pull request #5067 from bedroge/cuda_cc_regex_allow_f
boegel Dec 9, 2025
ac1661b
Merge pull request #5063 from Flamefire/tomli
boegel Dec 9, 2025
e346b55
Merge branch 'develop' into nvhpc
boegel Dec 10, 2025
956f038
use -nomp compiler option when OpenMP toolchain option is not enabled…
boegel Dec 10, 2025
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
2 changes: 1 addition & 1 deletion .github/workflows/linting.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
- name: install Python packages
run: |
pip install --upgrade pip
pip install --upgrade flake8
pip install --upgrade flake8 flake8-comprehensions

- name: Run flake8 to verify PEP8-compliance of Python code
run: |
Expand Down
72 changes: 62 additions & 10 deletions .github/workflows/unit_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ concurrency:
group: ${{format('{0}:{1}:{2}', github.repository, github.ref, github.workflow)}}
cancel-in-progress: true

env:
PYENV_ROOT: /opt/pyenv

jobs:
setup:
runs-on: ubuntu-latest
Expand All @@ -22,6 +25,8 @@ jobs:
build:
needs: setup
runs-on: ${{matrix.os || 'ubuntu-24.04'}}
env:
JOB_OS: ${{matrix.os || 'ubuntu-24.04'}}
strategy:
matrix:
# Python 3.10 is default in Ubuntu 22.04
Expand All @@ -34,6 +39,9 @@ jobs:
- ${{needs.setup.outputs.modules5}}
include:
# Test different Python 3 versions with Lmod 8.x (with both Lua and Tcl module syntax)
- python: '3.6.1'
modules_tool: ${{needs.setup.outputs.lmod8}}
use_pyenv: '2.6.15'
- python: '3.7'
modules_tool: ${{needs.setup.outputs.lmod8}}
os: ubuntu-22.04
Expand All @@ -56,13 +64,7 @@ jobs:
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2

- name: set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: ${{matrix.python}}
architecture: x64

- name: install OS & Python packages
- name: install OS packages
run: |
# for modules tool
APT_PKGS="lua5.3 liblua5.3-dev lua-filesystem lua-posix tcl tcl-dev"
Expand All @@ -71,6 +73,14 @@ jobs:
# dep for GC3Pie
APT_PKGS+=" time"

if [[ -n "${{matrix.use_pyenv}}" ]]; then
# See https://github.com/pyenv/pyenv/wiki#suggested-build-environment
APT_PKGS+=" libssl-dev zlib1g-dev \
libbz2-dev libreadline-dev libsqlite3-dev curl git \
libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev"
# Avoid segfault with older Pythons: https://github.com/pyenv/pyenv/issues/2239#issuecomment-1079275184
APT_PKGS+=" clang"
fi
# Avoid apt-get update, as we don't really need it,
# and it does more harm than good (it's fairly expensive, and it results in flaky test runs)
if ! sudo apt-get install $APT_PKGS; then
Expand All @@ -79,7 +89,40 @@ jobs:
sudo apt-get install $APT_PKGS
fi

# Python packages
- name: set up Python
if: '!matrix.use_pyenv'
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: ${{matrix.python}}
architecture: x64

- name: Cache PyEnv
id: cache-pyenv
if: matrix.use_pyenv
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # 4.3.0
with:
path: ${{env.PYENV_ROOT}}
key: ${{env.JOB_OS}}-py${{matrix.python}}-pyenv${{matrix.use_pyenv}}

- name: Setup PyEnv
if: matrix.use_pyenv
run: |
if [[ -z "${{steps.cache-pyenv.outputs.cache-hit}}" ]]; then
mkdir "$PYENV_ROOT"
wget https://github.com/pyenv/pyenv/archive/refs/tags/v${{matrix.use_pyenv}}.tar.gz -O- | tar xzf - -C "$PYENV_ROOT" --strip-components=1
fi
echo "${PYENV_ROOT}/bin" >> $GITHUB_PATH
export PATH="$PYENV_ROOT/bin:$PATH"
echo 'eval "$(pyenv init -)"' > ~/init_pyenv
. ~/init_pyenv
CC=clang pyenv install --skip-existing ${{matrix.python}}
echo 'pyenv global ${{matrix.python}}' >> ~/init_pyenv

- name: install Python packages
run: |
touch ~/init_pyenv # In case it doesn't exist, make empty file
. ~/init_pyenv
python -V
pip --version
pip install --upgrade pip
pip --version
Expand All @@ -100,6 +143,7 @@ jobs:
# and are only run after the PR gets merged
GITHUB_TOKEN: ${{secrets.CI_UNIT_TESTS_GITHUB_TOKEN}}
run: |
. ~/init_pyenv
# only install GitHub token when testing with Lmod 8.x + Python 3.9, to avoid hitting GitHub rate limit
# tests that require a GitHub token are skipped automatically when no GitHub token is available
if [[ "${{matrix.modules_tool}}" =~ 'Lmod-8' ]] && [[ "${{matrix.python}}" =~ 3.9 ]]; then
Expand All @@ -126,13 +170,15 @@ jobs:

- name: check sources
run: |
. ~/init_pyenv
# make sure there are no (top-level) "import setuptools" or "import pkg_resources" statements,
# since EasyBuild should not have a runtime requirement on setuptools
SETUPTOOLS_IMPORTS=$(egrep --exclude setup.py -RI '^(from|import)[ ]*pkg_resources|^(from|import)[ ]*setuptools' * || true)
test "x$SETUPTOOLS_IMPORTS" = "x" || (echo "Found setuptools and/or pkg_resources imports in easybuild/:\n${SETUPTOOLS_IMPORTS}" && exit 1)

- name: install sources
run: |
. ~/init_pyenv
# install from source distribution tarball, to test release as published on PyPI
python setup.py sdist
ls dist
Expand All @@ -142,7 +188,7 @@ jobs:
- name: run test suite
env:
EB_VERBOSE: 1
LC_ALL: ""
LC_ALL: ""
run: |
# run tests *outside* of checked out easybuild-framework directory,
# to ensure we're testing installed version (see previous step)
Expand All @@ -158,7 +204,13 @@ jobs:
export PATH=$PREFIX/bin:$(cat $HOME/path)
export PYTHONPATH=$PREFIX/lib/python$(echo ${{matrix.python}} | cut -f1,2 -d'.')/site-packages:$PYTHONPATH
echo "PYTHONPATH=$PYTHONPATH"
ls $(echo $PYTHONPATH | cut -f1:)
ls $(echo $PYTHONPATH | cut -f1 -d':')

# Do AFTER setting $PATH above
. ~/init_pyenv
which python3
python3 --version

python3 -m easybuild.main --version
eb --version
# tell EasyBuild which modules tool is available
Expand Down
36 changes: 24 additions & 12 deletions easybuild/base/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ def __init__(self, msg, *args, **kwargs):
if args:
msg = msg % args

backtrace = []
if self.LOC_INFO_TOP_PKG_NAMES is not None:
# determine correct frame to fetch location information from
frames_up = 1
Expand All @@ -101,19 +102,30 @@ def __init__(self, msg, *args, **kwargs):
if self.INCLUDE_LOCATION:
# figure out where error was raised from
# current frame: this constructor, one frame above: location where LoggedException was created/raised
frameinfo = inspect.getouterframes(inspect.currentframe())[frames_up]

# determine short location of Python module where error was raised from,
# i.e. starting with an entry from LOC_INFO_TOP_PKG_NAMES
path_parts = frameinfo[1].split(os.path.sep)
if path_parts[0] == '':
path_parts[0] = os.path.sep
top_indices = [path_parts.index(n) for n in self.LOC_INFO_TOP_PKG_NAMES if n in path_parts]
relpath = os.path.join(*path_parts[max(top_indices or [0]):])
frames = inspect.getouterframes(inspect.currentframe())[frames_up:]
backtrace = []
for frame in frames:
# determine short location of Python module where error was raised from,
# i.e. starting with an entry from LOC_INFO_TOP_PKG_NAMES
path_parts = frame[1].split(os.path.sep)
if path_parts[0] == '':
path_parts[0] = os.path.sep
try:
top_idx = max(path_parts.index(n) for n in self.LOC_INFO_TOP_PKG_NAMES if n in path_parts)
except ValueError:
# If none found (outside PKG) stop backtrace if we have at least 1 entry
if backtrace:
break
relpath = frame[1]
else:
relpath = os.path.join(*path_parts[top_idx:])
backtrace.append(f'{relpath}:{frame[2]} in {frame[3]}')

# include location info at the end of the message
# for example: "Nope, giving up (at easybuild/tools/somemodule.py:123 in some_function)"
msg = "%s (at %s:%s in %s)" % (msg, relpath, frameinfo[2], frameinfo[3])
msg = f"{msg} (at {backtrace[0]})"

super().__init__(msg)

logger = kwargs.get('logger', None)
# try to use logger defined in caller's environment
Expand All @@ -123,6 +135,6 @@ def __init__(self, msg, *args, **kwargs):
if logger is None:
logger = self.LOGGER_MODULE.getLogger()

if backtrace:
msg += '\nCallstack:\n\t' + '\n\t'.join(backtrace)
getattr(logger, self.LOGGING_METHOD_NAME)(msg)

super().__init__(msg)
6 changes: 3 additions & 3 deletions easybuild/base/fancylogger.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ def thread_name():
"""
returns the current threads name
"""
return threading.currentThread().getName()
return threading.current_thread().name


def getLogger(name=None, fname=False, clsname=False, fancyrecord=None):
Expand Down Expand Up @@ -577,8 +577,8 @@ def logToFile(filename, enable=True, filehandler=None, name=None, max_bytes=MAX_
'mode': 'a',
'maxBytes': max_bytes,
'backupCount': backup_count,
'encoding': 'utf-8',
}
handleropts['encoding'] = 'utf-8'
# logging to a file is going to create the file later on, so let's try to be helpful and create the path if needed
directory = os.path.dirname(filename)
if not os.path.exists(directory):
Expand Down Expand Up @@ -783,7 +783,7 @@ def getAllExistingLoggers():
"""
# not-so-well documented manager (in 2.6 and later)
# return list of (name,logger) tuple
return [x for x in logging.Logger.manager.loggerDict.items()] + [(logging.root.name, logging.root)]
return list(logging.Logger.manager.loggerDict.items()) + [(logging.root.name, logging.root)]


def getAllNonFancyloggers():
Expand Down
19 changes: 11 additions & 8 deletions easybuild/base/generaloption.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,8 @@ class ExtOption(CompleterOption):
- set default to None if no option passed,
- set to default if option without value passed,
- set to value if option with value passed
- store_or_False: Same as store_or_None, but set to False instead of None
- Additionally supports --disable-

Types:
- strlist, strtuple : convert comma-separated string in a list resp. tuple of strings
Expand All @@ -196,14 +198,15 @@ class ExtOption(CompleterOption):

ENABLE = 'enable' # do nothing
DISABLE = 'disable' # inverse action
STORE_OR_FALSE = 'store_or_False'

EXTOPTION_EXTRA_OPTIONS = ('date', 'datetime', 'regex', 'add', 'add_first', 'add_flex',)
EXTOPTION_STORE_OR = ('store_or_None', 'help') # callback type
EXTOPTION_STORE_OR = ('store_or_None', STORE_OR_FALSE, 'help') # callback type
EXTOPTION_LOG = ('store_debuglog', 'store_infolog', 'store_warninglog',)
EXTOPTION_HELP = ('shorthelp', 'confighelp', 'help')

ACTIONS = Option.ACTIONS + EXTOPTION_EXTRA_OPTIONS + EXTOPTION_STORE_OR + EXTOPTION_LOG + EXTOPTION_HELP
STORE_ACTIONS = Option.STORE_ACTIONS + EXTOPTION_EXTRA_OPTIONS + EXTOPTION_LOG + ('store_or_None',)
STORE_ACTIONS = Option.STORE_ACTIONS + EXTOPTION_EXTRA_OPTIONS + EXTOPTION_LOG + ('store_or_None', STORE_OR_FALSE)
TYPED_ACTIONS = Option.TYPED_ACTIONS + EXTOPTION_EXTRA_OPTIONS + EXTOPTION_STORE_OR
ALWAYS_TYPED_ACTIONS = Option.ALWAYS_TYPED_ACTIONS + EXTOPTION_EXTRA_OPTIONS

Expand Down Expand Up @@ -232,7 +235,9 @@ def store_or(option, opt_str, value, parser, *args, **kwargs): # pylint: disabl
"""Callback for supporting options with optional values."""
# see http://stackoverflow.com/questions/1229146/parsing-empty-options-in-python
# ugly code, optparse is crap
if parser.rargs and not parser.rargs[0].startswith('-'):
if option.store_or == self.STORE_OR_FALSE and opt_str.startswith("--%s-" % self.DISABLE):
val = False
elif parser.rargs and not parser.rargs[0].startswith('-'):
val = option.check_value(opt_str, parser.rargs.pop(0))
else:
val = kwargs.get('orig_default', None)
Expand All @@ -250,11 +255,7 @@ def store_or(option, opt_str, value, parser, *args, **kwargs): # pylint: disabl
'orig_default': copy.deepcopy(self.default),
}
self.action = 'callback' # act as callback

if self.store_or in self.EXTOPTION_STORE_OR:
self.default = None
else:
self.log.raiseException("_set_attrs: unknown store_or %s" % self.store_or, exception=ValueError)
self.default = False if self.action == self.STORE_OR_FALSE else None

def process(self, opt, value, values, parser):
"""Handle option-as-value issues before actually processing option."""
Expand Down Expand Up @@ -1219,6 +1220,8 @@ def add_group_parser(self, opt_dict, description, prefix=None, otherdefaults=Non
if action in self.parser.option_class.BOOLEAN_ACTIONS:
args.append("--%s-%s" % (self.parser.option_class.ENABLE, opt_name))
args.append("--%s-%s" % (self.parser.option_class.DISABLE, opt_name))
elif action == self.parser.option_class.STORE_OR_FALSE:
args.append("--%s-%s" % (self.parser.option_class.DISABLE, opt_name))

# force passed_kwargs as final nameds
nameds.update(passed_kwargs)
Expand Down
19 changes: 19 additions & 0 deletions easybuild/base/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,15 @@ def get_stderr(self):
"""Return output captured from stderr until now."""
return sys.stderr.getvalue()

@contextmanager
def mocked_stdout(self):
"""Context manager to mock stdout"""
self.mock_stdout(True)
try:
yield sys.stdout
finally:
self.mock_stdout(False)

@contextmanager
def mocked_stdout_stderr(self, mock_stdout=True, mock_stderr=True):
"""Context manager to mock stdout and stderr"""
Expand All @@ -221,6 +230,16 @@ def mocked_stdout_stderr(self, mock_stdout=True, mock_stderr=True):
if mock_stderr:
self.mock_stderr(False)

@contextmanager
def saved_env(self):
"""Context manager to reset environment to state when it was entered"""
orig_env = os.environ.copy()
try:
yield
finally:
os.environ.clear()
os.environ.update(orig_env)

def tearDown(self):
"""Cleanup after running a test."""
self.mock_stdout(False)
Expand Down
2 changes: 1 addition & 1 deletion easybuild/base/wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def proxy(self, *args): # pylint:disable=unused-argument
# create proxies for wrapped object's double-underscore attributes
type.__init__(cls, name, bases, dct)
if cls.__wraps__:
ignore = set("__%s__" % n for n in cls.__ignore__.split())
ignore = {"__%s__" % n for n in cls.__ignore__.split()}
for name in dir(cls.__wraps__):
if name.startswith("__"):
if name not in ignore and name not in dct:
Expand Down
Loading