Repository: easybuilders/easybuild-easyconfigs

Issue:
Qt5/5.13.1-GCCcore-8.3.0 depends on JasPer.
I'm trying to build Qt5/5.13.1-GCCcore-8.3.0 on RHEL8 and it fails on:

```
qjp2handler.cpp:47:10: fatal error: jasper/jasper.h: No such file or directory
 #include <jasper/jasper.h>
          ^~~~~~~~~~~~~~~~~
```

JasPer is not set as a dependency for Qt5/5.13.1-GCCcore-8.3.0 and there is no JasPer/2.0.14-GCCcore-8.3.0 available. I'm mostly wondering if this is a RHEL8 issue since this hasn't been seen before? There is a jasper-devel package available in the OS, but there are no osdeps either in the package.

For what its worth, JasPer/2.0.14 builds fine with GCCcore 8.3.0 and Qt5/5.13.1-GCCcore-8.3.0 builds fine once JasPer/2.0.14-GCCcore-8.3.0 is installed and added as a dependency.

Qt5/5.13.1-GCCcore-8.3.0 depends on JasPer.
I'm trying to build Qt5/5.13.1-GCCcore-8.3.0 on RHEL8 and it fails on:

```
qjp2handler.cpp:47:10: fatal error: jasper/jasper.h: No such file or directory
 #include <jasper/jasper.h>
          ^~~~~~~~~~~~~~~~~
```

JasPer is not set as a dependency for Qt5/5.13.1-GCCcore-8.3.0 and there is no JasPer/2.0.14-GCCcore-8.3.0 available. I'm mostly wondering if this is a RHEL8 issue since this hasn't been seen before? There is a jasper-devel package available in the OS, but there are no osdeps either in the package.

For what its worth, JasPer/2.0.14 builds fine with GCCcore 8.3.0 and Qt5/5.13.1-GCCcore-8.3.0 builds fine once JasPer/2.0.14-GCCcore-8.3.0 is installed and added as a dependency.

Linker problems with Bazel-0.25.2-GCCcore-8.2.0 on RHEL8
Building Bazel causes linker problems, similar to what has been seen when using ld.gold in other situations. Building with the SYSTEM toolchain works. The relevant part of the logs are:

```
ERROR: /tmp/eb-v7mb3x3f/bazel_bmkm1c5Q/out/external/bazel_tools/third_party/ijar/BUILD:74:1: Linking of rule '@bazel_tools//third_party/ijar:zipper' failed (Exit 1)
: gcc failed: error executing command
  (cd /tmp/eb-v7mb3x3f/bazel_bmkm1c5Q/out/execroot/io_bazel && \
  exec env - \
    LD_LIBRARY_PATH=/opt/uio/modules/rhel8/easybuild/software/Java/1.8.0_221/lib:/opt/uio/modules/rhel8/easybuild/software/binutils/2.31.1-GCCcore-8.2.0/lib:/opt/uio/modules/rhel
8/easybuild/software/zlib/1.2.11-GCCcore-8.2.0/lib:/opt/uio/modules/rhel8/easybuild/software/GCCcore/8.2.0/lib/gcc/x86_64-pc-linux-gnu/8.2.0:/opt/uio/modules/rhel8/easybuild/soft
ware/GCCcore/8.2.0/lib64:/opt/uio/modules/rhel8/easybuild/software/GCCcore/8.2.0/lib \
    PATH=/opt/uio/modules/rhel8/easybuild/software/Java/1.8.0_221:/opt/uio/modules/rhel8/easybuild/software/Java/1.8.0_221/bin:/opt/uio/modules/rhel8/easybuild/software/binutils/
2.31.1-GCCcore-8.2.0/bin:/opt/uio/modules/rhel8/easybuild/software/GCCcore/8.2.0/bin:/opt/uio/modules/rhel8/easybuild/packages/EasyBuild/4.0.1/bin:/site/bin:/site/common/hosts:/u
sr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin \
    PWD=/proc/self/cwd \
  /opt/uio/modules/rhel8/easybuild/software/GCCcore/8.2.0/bin/gcc -o bazel-out/host/bin/external/bazel_tools/third_party/ijar/zipper -Wl,-S '-fuse-ld=gold' -Wl,-no-as-needed -Wl,
-z,relro,-z,now -B/opt/uio/modules/rhel8/easybuild/software/GCCcore/8.2.0/bin -pass-exit-codes -Wl,--gc-sections -Wl,@bazel-out/host/bin/external/bazel_tools/third_party/ijar/zip
per-2.params)
Execution platform: @bazel_tools//platforms:host_platform
[868 / 903] 5 actions running
    //third_party/protobuf/3.6.1:protobuf_lite; 0s local
    //third_party/grpc:grpc++_base_unsecure; 0s local
    Compiling .../3.6.1/src/google/protobuf/io/zero_copy_stream.cc; 0s local
    Compiling .../grpc/src/core/lib/compression/message_compress.cc; 0s local
    Linking third_party/ijar/ijar [for host]; 0s local
^M^M^M^M^M^M/lib/../lib64/crti.o(.gnu.build.attributes+0x14): error: relocation refers to local symbol "" [1], which is defined in a discarded section
/lib/../lib64/crti.o(.gnu.build.attributes+0x1c): error: relocation refers to local symbol "" [1], which is defined in a discarded section
/lib/../lib64/crtn.o(.gnu.build.attributes+0x14): error: relocation refers to local symbol "" [1], which is defined in a discarded section
/lib/../lib64/crtn.o(.gnu.build.attributes+0x1c): error: relocation refers to local symbol "" [1], which is defined in a discarded section
/lib/../lib64/crt1.o(.gnu.build.attributes.hot+0x18): error: relocation refers to local symbol "" [2], which is defined in a discarded section
/lib/../lib64/crt1.o(.gnu.build.attributes.hot+0x20): error: relocation refers to local symbol "" [2], which is defined in a discarded section
/lib/../lib64/crt1.o(.gnu.build.attributes.unlikely+0x18): error: relocation refers to local symbol "" [3], which is defined in a discarded section
/lib/../lib64/crt1.o(.gnu.build.attributes.unlikely+0x20): error: relocation refers to local symbol "" [3], which is defined in a discarded section
/lib/../lib64/crt1.o(.gnu.build.attributes.hot+0x18): error: relocation refers to local symbol "" [4], which is defined in a discarded section
/lib/../lib64/crt1.o(.gnu.build.attributes.hot+0x20): error: relocation refers to local symbol "" [4], which is defined in a discarded section
/lib/../lib64/crt1.o(.gnu.build.attributes.unlikely+0x18): error: relocation refers to local symbol "" [5], which is defined in a discarded section
/lib/../lib64/crt1.o(.gnu.build.attributes.unlikely+0x20): error: relocation refers to local symbol "" [5], which is defined in a discarded section
/usr/lib64/libc_nonshared.a(elf-init.oS)(.gnu.build.attributes.hot+0x18): error: relocation refers to local symbol "" [4], which is defined in a discarded section
/usr/lib64/libc_nonshared.a(elf-init.oS)(.gnu.build.attributes.hot+0x20): error: relocation refers to local symbol "" [4], which is defined in a discarded section
/usr/lib64/libc_nonshared.a(elf-init.oS)(.gnu.build.attributes.unlikely+0x18): error: relocation refers to local symbol "" [7], which is defined in a discarded section
/usr/lib64/libc_nonshared.a(elf-init.oS)(.gnu.build.attributes.unlikely+0x20): error: relocation refers to local symbol "" [7], which is defined in a discarded section
collect2: error: ld returned 1 exit status
```
h5py 2.9.0 broken on ppc64le
h5py 2.9.0 is broken on ppc64le - https://github.com/h5py/h5py/issues/1222

Locally we've moved 2019a and 2019b to use 2.10.0 instead, which contains the fix that is mentioned in the h5py bug report.

Would you like me to submit a pull request for bumping h5py to 2.10.0 in 2019b?


Discussion:


I'm seeing a lot of similar linking issues elsewhere as well. This is from LLVM/7.0.1-GCCcore-8.2.0. The base platform is RHEL8:

```
cd /tmp/easybuild/build/LLVM/7.0.1/GCCcore-8.2.0/easybuild_obj/utils/PerfectShuffle && /mn/sarpanitu/modules/rhel8/easybuild/software/CMake/3.13.3-GCCcore-8.2.0
/bin/cmake -E cmake_link_script CMakeFiles/llvm-PerfectShuffle.dir/link.txt --verbose=1
/opt/uio/modules/rhel8/easybuild/software/GCCcore/8.2.0/bin/g++  -O2 -ftree-vectorize -march=x86-64 -mtune=generic -fno-math-errno -std=gnu++11 -fvisibility-inl
ines-hidden -Werror=date-time -std=c++11 -Wall -Wextra -Wno-unused-parameter -Wwrite-strings -Wcast-qual -Wno-missing-field-initializers -pedantic -Wno-long-lon
g -Wno-maybe-uninitialized -Wno-class-memaccess -Wdelete-non-virtual-dtor -Wno-comment -fdata-sections -O3 -DNDEBUG  -L/opt/uio/modules/rhel8/easybuild/software
/zlib/1.2.11-GCCcore-8.2.0/lib -L/opt/uio/modules/rhel8/easybuild/software/ncurses/6.1-GCCcore-8.2.0/lib -L/opt/uio/modules/rhel8/easybuild/software/Python/3.7.
2-GCCcore-8.2.0/lib -L/opt/uio/modules/rhel8/easybuild/software/binutils/2.31.1-GCCcore-8.2.0/lib -L/opt/uio/modules/rhel8/easybuild/software/GCCcore/8.2.0/lib6
4 -L/opt/uio/modules/rhel8/easybuild/software/GCCcore/8.2.0/lib -Wl,-allow-shlib-undefined  -Wl,-O3 -Wl,--gc-sections CMakeFiles/llvm-PerfectShuffle.dir/Perfect
Shuffle.cpp.o  -o ../../bin/llvm-PerfectShuffle -Wl,-rpath,"\$ORIGIN/../lib" -lpthread
/lib/../lib64/crti.o(.gnu.build.attributes+0x14): error: relocation refers to local symbol "" [1], which is defined in a discarded section
/lib/../lib64/crti.o(.gnu.build.attributes+0x1c): error: relocation refers to local symbol "" [1], which is defined in a discarded section
/lib/../lib64/crtn.o(.gnu.build.attributes+0x14): error: relocation refers to local symbol "" [1], which is defined in a discarded section
/lib/../lib64/crtn.o(.gnu.build.attributes+0x1c): error: relocation refers to local symbol "" [1], which is defined in a discarded section
/lib/../lib64/crt1.o(.gnu.build.attributes.hot+0x18): error: relocation refers to local symbol "" [2], which is defined in a discarded section
/lib/../lib64/crt1.o(.gnu.build.attributes.hot+0x20): error: relocation refers to local symbol "" [2], which is defined in a discarded section
/lib/../lib64/crt1.o(.gnu.build.attributes.unlikely+0x18): error: relocation refers to local symbol "" [3], which is defined in a discarded section
/lib/../lib64/crt1.o(.gnu.build.attributes.unlikely+0x20): error: relocation refers to local symbol "" [3], which is defined in a discarded section
/lib/../lib64/crt1.o(.gnu.build.attributes.hot+0x18): error: relocation refers to local symbol "" [4], which is defined in a discarded section
/lib/../lib64/crt1.o(.gnu.build.attributes.hot+0x20): error: relocation refers to local symbol "" [4], which is defined in a discarded section
/lib/../lib64/crt1.o(.gnu.build.attributes.unlikely+0x18): error: relocation refers to local symbol "" [5], which is defined in a discarded section
/lib/../lib64/crt1.o(.gnu.build.attributes.unlikely+0x20): error: relocation refers to local symbol "" [5], which is defined in a discarded section
/usr/lib64/libc_nonshared.a(elf-init.oS)(.gnu.build.attributes.hot+0x18): error: relocation refers to local symbol "" [4], which is defined in a discarded secti
on
/usr/lib64/libc_nonshared.a(elf-init.oS)(.gnu.build.attributes.hot+0x20): error: relocation refers to local symbol "" [4], which is defined in a discarded secti
on
/usr/lib64/libc_nonshared.a(elf-init.oS)(.gnu.build.attributes.unlikely+0x18): error: relocation refers to local symbol "" [7], which is defined in a discarded
section
/usr/lib64/libc_nonshared.a(elf-init.oS)(.gnu.build.attributes.unlikely+0x20): error: relocation refers to local symbol "" [7], which is defined in a discarded
section
collect2: error: ld returned 1 exit status
make[2]: *** [utils/PerfectShuffle/CMakeFiles/llvm-PerfectShuffle.dir/build.make:87: bin/llvm-PerfectShuffle] Error 1
make[2]: Leaving directory '/tmp/easybuild/build/LLVM/7.0.1/GCCcore-8.2.0/easybuild_obj'
make[1]: *** [CMakeFiles/Makefile2:8837: utils/PerfectShuffle/CMakeFiles/llvm-PerfectShuffle.dir/all] Error 2
[  0%] Building CXX object utils/TableGen/CMakeFiles/obj.llvm-tblgen.dir/CallingConvEmitter.cpp.o
```
@terjekv Can you provide some more information about your EasyBuild configuration and the system you're using?

Please provide the output of `eb --show-config` and `eb --show-system-info`.
This is RHEL8 with a fairly standardised setup, except that optarch is set to GENERIC.

[eb--show-system-info.txt](https://github.com/easybuilders/easybuild-easyconfigs/files/3805284/eb--show-system-info.txt)
[eb--show-config.txt](https://github.com/easybuilders/easybuild-easyconfigs/files/3805285/eb--show-config.txt)

@terjekv Are you willing to try and patch `binutils` according to the info in https://bugzilla.redhat.com/show_bug.cgi?id=1600431, and re-try to see if that fixes the problem you're seeing?

Not many people are using EasyBuild on RHEL8 yet, it's possible the issue only surfaces on a sufficiently recent RHEL-based OS...
I tried binutils from the fc29 rpm that was was referenced in that report, without change. I can patch the rhel8 version specifically to see if it makes a difference. 
@terjekv My suggestion was to patch the `binutils` that gets installed by EasyBuild, since that's the one we're using here...
@boegel Apologies for my acute brainfart. I adapted the patch from the Bugzilla and now Bazel builds with GCCcore-8.2.0. I am applying a similar patch to 2.32 as well, but even 2.30 seems to be affected (and thus GCCcore-7.3.0)...

I have attached the fairly trivial adaptations of the patches. Thank you so much for prodding me in the correct direction with this!

[binutils-2.31.1-gold-ignore-discarded-note-relocts.patch.txt](https://github.com/easybuilders/easybuild-easyconfigs/files/3806326/binutils-2.31.1-gold-ignore-discarded-note-relocts.patch.txt)
[binutils-2.32-gold-ignore-discarded-note-relocts.patch.txt](https://github.com/easybuilders/easybuild-easyconfigs/files/3806327/binutils-2.32-gold-ignore-discarded-note-relocts.patch.txt)


@terjekv Any idea whether these patches could cause problems on other systems? Are the changes in the patch already included in more recent `binutils` versions?
1) I am not in a position to say much about the consequences of stripping data that's already in a "discarded section". This is really something someone with much better understanding of linkers needs to answer. Sorry.  :-(

2) https://bugzilla.redhat.com/show_bug.cgi?id=1600431#c7 suggests that there are fixes in some streams, but for the latest binutils (2.32) that is used in the latest toolchains in EasyBuild, I had to apply the patch to allow for quite a lot of software to compile.

In other RHEL8 linker news, GCCcore-6.4.0 fails to link:
```
/tmp/eb-3l8U36/cc7sc8bS.s: Assembler messages:
/tmp/eb-3l8U36/cc7sc8bS.s:11: Error: unknown .loc sub-directive `view'
/tmp/eb-3l8U36/cc7sc8bS.s:11: Error: junk at end of line, first unrecognized character is `-'
/tmp/eb-3l8U36/cc7sc8bS.s:13: Error: unknown .loc sub-directive `view'
/tmp/eb-3l8U36/cc7sc8bS.s:13: Error: unknown pseudo-op: `.lvu1'
/tmp/eb-3l8U36/cc7sc8bS.s:14: Error: unknown .loc sub-directive `view'
/tmp/eb-3l8U36/cc7sc8bS.s:14: Error: unknown pseudo-op: `.lvu2'
```
To wit the answer, according to https://bugzilla.redhat.com/show_bug.cgi?id=1552529 is:
> That means you are trying to use f29 gcc with some older binutils (non-f29).  Don't do that.

Ouch.


Resolving patch:
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -44,7 +44,7 @@
 # recent setuptools versions will *TRANSFORM* something like 'X.Y.Zdev' into 'X.Y.Z.dev0', with a warning like
 #   UserWarning: Normalizing '2.4.0dev' to '2.4.0.dev0'
 # This causes problems further up the dependency chain...
-VERSION = '4.0.1'
+VERSION = '4.1.0'
 
 MAJ_VER = VERSION.split('.')[0]
 MAJMIN_VER = '.'.join(VERSION.split('.')[0:2])


Test patch:
diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml
new file mode 100644
--- /dev/null
+++ b/.github/workflows/unit_tests.yml
@@ -0,0 +1,129 @@
+name: easyconfigs unit tests
+on: [push, pull_request]
+jobs:
+  build:
+    runs-on: ubuntu-18.04
+    strategy:
+      matrix:
+        python: [2.7, 3.5, 3.6, 3.7]
+        modules_tool: [Lmod-6.6.3, Lmod-7.8.22, Lmod-8.1.14]
+        module_syntax: [Lua, Tcl]
+        # exclude some configurations: only test Tcl module syntax with Lmod 7.x and Python 2.7 & 3.5
+        exclude:
+          - modules_tool: Lmod-6.6.3
+            module_syntax: Tcl
+          - modules_tool: Lmod-8.1.14
+            module_syntax: Tcl
+          - python: 3.6
+            module_syntax: Tcl
+          - python: 3.7
+            module_syntax: Tcl
+      fail-fast: false
+    steps:
+    - uses: actions/checkout@v1
+
+    - name: set up Python
+      uses: actions/setup-python@v1
+      with:
+        python-version: ${{matrix.python}}
+        architecture: x64
+
+    - name: install OS & Python packages
+      run: |
+        sudo apt-get update
+        # for modules tool
+        sudo apt-get install lua5.2 liblua5.2-dev lua-filesystem lua-posix tcl tcl-dev
+        # fix for lua-posix packaging issue, see https://bugs.launchpad.net/ubuntu/+source/lua-posix/+bug/1752082
+        sudo ln -s /usr/lib/x86_64-linux-gnu/lua/5.2/posix_c.so /usr/lib/x86_64-linux-gnu/lua/5.2/posix.so
+        # for testing OpenMPI-system*eb we need to have Open MPI installed
+        sudo apt-get install libopenmpi-dev openmpi-bin
+        # required for test_dep_graph
+        pip install pep8 python-graph-core python-graph-dot
+
+    - name: install EasyBuild framework
+      run: |
+          cd $HOME
+          # first determine which branch of easybuild-framework repo to install
+          BRANCH=develop
+          if [ "x$GITHUB_BASE_REF" = 'xmaster' ]; then BRANCH=master; fi
+          if [ "x$GITHUB_BASE_REF" = 'x4.x' ]; then BRANCH=4.x; fi
+          echo "Using easybuild-framework branch $BRANCH (\$GITHUB_BASE_REF $GITHUB_BASE_REF)"
+
+          git clone -b $BRANCH --depth 10 --single-branch https://github.com/easybuilders/easybuild-framework.git
+          cd easybuild-framework; git log -n 1; cd -
+          pip install $PWD/easybuild-framework
+
+          git clone -b $BRANCH --depth 10 --single-branch https://github.com/hpcugent/easybuild-easyblocks.git
+          cd easybuild-easyblocks; git log -n 1; cd -
+          pip install $PWD/easybuild-easyblocks
+
+    - name: install modules tool
+      run: |
+          cd $HOME
+          # use install_eb_dep.sh script provided with easybuild-framework
+          export INSTALL_DEP=$(which install_eb_dep.sh)
+          echo "Found install_eb_dep.sh script: $INSTALL_DEP"
+
+          # install modules tool
+          source $INSTALL_DEP ${{matrix.modules_tool}} $HOME
+
+          # changes in environment are not passed to other steps, so need to create files...
+          echo $MOD_INIT > mod_init
+          echo $PATH > path
+          if [ ! -z $MODULESHOME ]; then echo $MODULESHOME > moduleshome; fi
+
+    - name: run test suite
+      env:
+        EB_VERBOSE: 1
+        EASYBUILD_MODULE_SYNTAX: ${{matrix.module_syntax}}
+      run: |
+          # pull in target so we can diff against it to obtain list of touched files
+          if [ "x$GITHUB_BASE_REF" != 'xmaster' ]; then git fetch -v origin ${GITHUB_BASE_REF}:${GITHUB_BASE_REF}; fi
+
+          # initialize environment for modules tool
+          if [ -f $HOME/moduleshome ]; then export MODULESHOME=$(cat $HOME/moduleshome); fi
+          source $(cat $HOME/mod_init); type module
+
+          # make sure 'eb' is available via $PATH, and that $PYTHONPATH is set (some tests expect that);
+          # also pick up changes to $PATH set by sourcing $MOD_INIT
+          WORKDIR=$GITHUB_WORKSPACE/easybuild-easyconfigs
+          export PATH=$WORKDIR/test/bin:$(cat $HOME/path)
+          export PYTHONPATH=$WORKDIR
+
+          # tell EasyBuild which modules tool is available
+          if [[ ${{matrix.modules_tool}} =~ ^modules-tcl- ]]; then
+            export EASYBUILD_MODULES_TOOL=EnvironmentModulesTcl
+          elif [[ ${{matrix.modules_tool}} =~ ^modules-3 ]]; then
+            export EASYBUILD_MODULES_TOOL=EnvironmentModulesC
+          elif [[ ${{matrix.modules_tool}} =~ ^modules-4 ]]; then
+            export EASYBUILD_MODULES_TOOL=EnvironmentModules
+          else
+            export EASYBUILD_MODULES_TOOL=Lmod
+          fi
+
+          eb --version
+          eb --show-config
+          # gather some useful info on test system
+          eb --show-system-info
+
+          # run test suite
+          python -O -m test.easyconfigs.suite
+
+          unset PYTHONPATH
+
+          # install easyconfigs via distribution package
+          python setup.py sdist
+          ls dist
+          pip install dist/easybuild-easyconfigs*tar.gz
+
+          # robot-paths value should not be empty, but have an entry that includes easybuild/easyconfigs subdir
+          eb --show-config | tee eb_show_config.out
+          grep "^robot-paths .*/easybuild/easyconfigs" eb_show_config.out
+          # check whether some specific easyconfig files are found
+          eb --search 'TensorFlow-1.14.*.eb' | tee eb_search_TF.out
+          grep '/TensorFlow-1.14.0-foss-2019a-Python-3.7.2.eb$' eb_search_TF.out
+          eb --search '^foss-2018b.eb' | tee eb_search_foss.out
+          grep '/foss-2018b.eb$' eb_search_foss.out
+
+          # try installing bzip2 with system toolchain (requires EB_bzip2 easyblock + easyconfig)
+          eb --prefix /tmp/$USER/$GITHUB_SHA bzip2-1.0.6.eb
diff --git a/test/easyconfigs/easyconfigs.py b/test/easyconfigs/easyconfigs.py
--- a/test/easyconfigs/easyconfigs.py
+++ b/test/easyconfigs/easyconfigs.py
@@ -53,7 +53,7 @@
 from easybuild.tools.filetools import change_dir, read_file, remove_file, write_file
 from easybuild.tools.module_naming_scheme.utilities import det_full_ec_version
 from easybuild.tools.modules import modules_tool
-from easybuild.tools.py2vs3 import HTTPError, URLError, string_type, urlopen
+from easybuild.tools.py2vs3 import string_type, urlopen
 from easybuild.tools.robot import check_conflicts, resolve_dependencies
 from easybuild.tools.run import run_cmd
 from easybuild.tools.options import set_tmpdir
@@ -106,17 +106,17 @@ def process_all_easyconfigs(self):
         specs = glob.glob('%s/*/*/*.eb' % easyconfigs_path)
 
         # parse all easyconfigs if they haven't been already
-        if not self.parsed_easyconfigs:
+        if not EasyConfigTest.parsed_easyconfigs:
             for spec in specs:
-                self.parsed_easyconfigs.extend(process_easyconfig(spec))
+                EasyConfigTest.parsed_easyconfigs.extend(process_easyconfig(spec))
 
         # filter out external modules
-        for ec in self.parsed_easyconfigs:
+        for ec in EasyConfigTest.parsed_easyconfigs:
             for dep in ec['dependencies'][:]:
                 if dep.get('external_module', False):
                     ec['dependencies'].remove(dep)
 
-        self.ordered_specs = resolve_dependencies(self.parsed_easyconfigs, modules_tool(), retain_all_deps=True)
+        EasyConfigTest.ordered_specs = resolve_dependencies(EasyConfigTest.parsed_easyconfigs, modules_tool(), retain_all_deps=True)
 
     def test_dep_graph(self):
         """Unit test that builds a full dependency graph."""
@@ -126,10 +126,10 @@ def test_dep_graph(self):
             (hn, fn) = tempfile.mkstemp(suffix='.dot')
             os.close(hn)
 
-            if self.ordered_specs is None:
+            if EasyConfigTest.ordered_specs is None:
                 self.process_all_easyconfigs()
 
-            dep_graph(fn, self.ordered_specs)
+            dep_graph(fn, EasyConfigTest.ordered_specs)
 
             remove_file(fn)
         else:
@@ -142,19 +142,272 @@ def test_conflicts(self):
             print("(skipped conflicts test)")
             return
 
-        if self.ordered_specs is None:
+        if EasyConfigTest.ordered_specs is None:
             self.process_all_easyconfigs()
 
-        self.assertFalse(check_conflicts(self.ordered_specs, modules_tool(), check_inter_ec_conflicts=False),
+        self.assertFalse(check_conflicts(EasyConfigTest.ordered_specs, modules_tool(), check_inter_ec_conflicts=False),
                          "No conflicts detected")
 
+    def check_dep_vars(self, dep, dep_vars):
+        """Check whether available variants of a particular dependency are acceptable or not."""
+
+        # 'guilty' until proven 'innocent'
+        res = False
+
+        # filter out wrapped Java versions
+        # i.e. if the version of one is a prefix of the version of the other one (e.g. 1.8 & 1.8.0_181)
+        if dep == 'Java':
+            dep_vars_to_check = sorted(dep_vars.keys())
+
+            retained_dep_vars = []
+
+            while dep_vars_to_check:
+                dep_var = dep_vars_to_check.pop()
+                dep_var_version = dep_var.split(';')[0]
+
+                # remove dep vars wrapped by current dep var
+                dep_vars_to_check = [x for x in dep_vars_to_check if not x.startswith(dep_var_version + '.')]
+
+                retained_dep_vars = [x for x in retained_dep_vars if not x.startswith(dep_var_version + '.')]
+
+                retained_dep_vars.append(dep_var)
+
+            for key in list(dep_vars.keys()):
+                if key not in retained_dep_vars:
+                    del dep_vars[key]
+
+        # filter out binutils with empty versionsuffix which is used to build toolchain compiler
+        if dep == 'binutils' and len(dep_vars) > 1:
+            empty_vsuff_vars = [v for v in dep_vars.keys() if v.endswith('versionsuffix: ')]
+            if len(empty_vsuff_vars) == 1:
+                dep_vars = dict((k, v) for (k, v) in dep_vars.items() if k != empty_vsuff_vars[0])
+
+        # multiple variants of HTSlib is OK as long as they are deps for a matching version of BCFtools
+        if dep == 'HTSlib' and len(dep_vars) > 1:
+            for key in list(dep_vars):
+                ecs = dep_vars[key]
+                # filter out HTSlib variants that are only used as dependency for BCFtools with same version
+                htslib_ver = re.search('^version: (?P<ver>[^;]+);', key).group('ver')
+                if all(ec.startswith('BCFtools-%s-' % htslib_ver) for ec in ecs):
+                    dep_vars.pop(key)
+
+        # multiple versions of Boost is OK as long as they are deps for a matching Boost.Python
+        if dep == 'Boost' and len(dep_vars) > 1:
+            for key in list(dep_vars):
+                ecs = dep_vars[key]
+                # filter out Boost variants that are only used as dependency for Boost.Python with same version
+                boost_ver = re.search('^version: (?P<ver>[^;]+);', key).group('ver')
+                if all(ec.startswith('Boost.Python-%s-' % boost_ver) for ec in ecs):
+                    dep_vars.pop(key)
+
+        # filter out FFTW and imkl with -serial versionsuffix which are used in non-MPI subtoolchains
+        if dep in ['FFTW', 'imkl']:
+            serial_vsuff_vars = [v for v in dep_vars.keys() if v.endswith('versionsuffix: -serial')]
+            if len(serial_vsuff_vars) == 1:
+                dep_vars = dict((k, v) for (k, v) in dep_vars.items() if k != serial_vsuff_vars[0])
+
+        # for some dependencies, we allow exceptions for software that depends on a particular version,
+        # as long as that's indicated by the versionsuffix
+        if dep in ['ASE', 'Boost', 'Java', 'Lua', 'PLUMED', 'R'] and len(dep_vars) > 1:
+            for key in list(dep_vars):
+                dep_ver = re.search('^version: (?P<ver>[^;]+);', key).group('ver')
+                # use version of Java wrapper rather than full Java version
+                if dep == 'Java':
+                    dep_ver = '.'.join(dep_ver.split('.')[:2])
+                # filter out dep version if all easyconfig filenames using it include specific dep version
+                if all(re.search('-%s-%s' % (dep, dep_ver), v) for v in dep_vars[key]):
+                    dep_vars.pop(key)
+                # always retain at least one dep variant
+                if len(dep_vars) == 1:
+                    break
+
+            # filter R dep for a specific version of Python 2.x
+            if dep == 'R' and len(dep_vars) > 1:
+                for key in list(dep_vars):
+                    if '; versionsuffix: -Python-2' in key:
+                        dep_vars.pop(key)
+                    # always retain at least one variant
+                    if len(dep_vars) == 1:
+                        break
+
+        # filter out variants that are specific to a particular version of CUDA
+        cuda_dep_vars = [v for v in dep_vars.keys() if '-CUDA' in v]
+        if len(dep_vars) > len(cuda_dep_vars):
+            for key in list(dep_vars):
+                if re.search('; versionsuffix: .*-CUDA-[0-9.]+', key):
+                    dep_vars.pop(key)
+
+        # some software packages require an old version of a particular dependency
+        old_dep_versions = {
+            # libxc 2.x or 3.x is required by ABINIT, AtomPAW, CP2K, GPAW, PySCF, WIEN2k
+            # (Qiskit depends on PySCF)
+            'libxc': (r'[23]\.', ['ABINIT-', 'AtomPAW-', 'CP2K-', 'GPAW-', 'PySCF-', 'Qiskit-', 'WIEN2k-']),
+            # OPERA requires SAMtools 0.x
+            'SAMtools': (r'0\.', ['ChimPipe-0.9.5', 'Cufflinks-2.2.1', 'OPERA-2.0.6']),
+            # Kraken 1.x requires Jellyfish 1.x (Roary & metaWRAP depend on Kraken 1.x)
+            'Jellyfish': (r'1\.', ['Kraken-1.', 'Roary-3.12.0', 'metaWRAP-1.2']),
+            # EMAN2 2.3 requires Boost(.Python) 1.64.0
+            'Boost': ('1.64.0;', ['Boost.Python-1.64.0-', 'EMAN2-2.3-']),
+            'Boost.Python': ('1.64.0;', ['EMAN2-2.3-']),
+        }
+        if dep in old_dep_versions and len(dep_vars) > 1:
+            for key in list(dep_vars):
+                version_pattern, parents = old_dep_versions[dep]
+                # filter out known old dependency versions
+                if re.search('^version: %s' % version_pattern, key):
+                    # only filter if the easyconfig using this dep variants is known
+                    if all(any(x.startswith(p) for p in parents) for x in dep_vars[key]):
+                        dep_vars.pop(key)
+
+        # only single variant is always OK
+        if len(dep_vars) == 1:
+            res = True
+
+        elif len(dep_vars) == 2 and dep in ['Python', 'Tkinter']:
+            # for Python & Tkinter, it's OK to have on 2.x and one 3.x version
+            v2_dep_vars = [x for x in dep_vars.keys() if x.startswith('version: 2.')]
+            v3_dep_vars = [x for x in dep_vars.keys() if x.startswith('version: 3.')]
+            if len(v2_dep_vars) == 1 and len(v3_dep_vars) == 1:
+                res = True
+
+        # two variants is OK if one is for Python 2.x and the other is for Python 3.x (based on versionsuffix)
+        elif len(dep_vars) == 2:
+            py2_dep_vars = [x for x in dep_vars.keys() if '; versionsuffix: -Python-2.' in x]
+            py3_dep_vars = [x for x in dep_vars.keys() if '; versionsuffix: -Python-3.' in x]
+            if len(py2_dep_vars) == 1 and len(py3_dep_vars) == 1:
+                res = True
+
+        return res
+
+    def test_check_dep_vars(self):
+        """Test check_dep_vars utility method."""
+
+        # one single dep version: OK
+        self.assertTrue(self.check_dep_vars('testdep', {
+            'version: 1.2.3; versionsuffix:': ['foo-1.2.3.eb', 'bar-4.5.6.eb'],
+        }))
+        self.assertTrue(self.check_dep_vars('testdep', {
+            'version: 1.2.3; versionsuffix: -test': ['foo-1.2.3.eb', 'bar-4.5.6.eb'],
+        }))
+
+        # two or more dep versions (no special case: not OK)
+        self.assertFalse(self.check_dep_vars('testdep', {
+            'version: 1.2.3; versionsuffix:': ['foo-1.2.3.eb'],
+            'version: 4.5.6; versionsuffix:': ['bar-4.5.6.eb'],
+        }))
+        self.assertFalse(self.check_dep_vars('testdep', {
+            'version: 0.0; versionsuffix:': ['foobar-0.0.eb'],
+            'version: 1.2.3; versionsuffix:': ['foo-1.2.3.eb'],
+            'version: 4.5.6; versionsuffix:': ['bar-4.5.6.eb'],
+        }))
+
+        # Java is a special case, with wrapped Java versions
+        self.assertTrue(self.check_dep_vars('Java', {
+            'version: 1.8.0_221; versionsuffix:': ['foo-1.2.3.eb'],
+            'version: 1.8; versionsuffix:': ['foo-1.2.3.eb'],
+        }))
+        # two Java wrappers is not OK
+        self.assertFalse(self.check_dep_vars('Java', {
+            'version: 1.8.0_221; versionsuffix:': ['foo-1.2.3.eb'],
+            'version: 1.8; versionsuffix:': ['foo-1.2.3.eb'],
+            'version: 11.0.2; versionsuffix:': ['bar-4.5.6.eb'],
+            'version: 11; versionsuffix:': ['bar-4.5.6.eb'],
+        }))
+        # OK to have two or more wrappers if versionsuffix is used to indicate exception
+        self.assertTrue(self.check_dep_vars('Java', {
+            'version: 1.8.0_221; versionsuffix:': ['foo-1.2.3.eb'],
+            'version: 1.8; versionsuffix:': ['foo-1.2.3.eb'],
+            'version: 11.0.2; versionsuffix:': ['bar-4.5.6-Java-11.eb'],
+            'version: 11; versionsuffix:': ['bar-4.5.6-Java-11.eb'],
+        }))
+        # versionsuffix must be there for all easyconfigs to indicate exception
+        self.assertFalse(self.check_dep_vars('Java', {
+            'version: 1.8.0_221; versionsuffix:': ['foo-1.2.3.eb'],
+            'version: 1.8; versionsuffix:': ['foo-1.2.3.eb'],
+            'version: 11.0.2; versionsuffix:': ['bar-4.5.6-Java-11.eb', 'bar-4.5.6.eb'],
+            'version: 11; versionsuffix:': ['bar-4.5.6-Java-11.eb', 'bar-4.5.6.eb'],
+        }))
+        self.assertTrue(self.check_dep_vars('Java', {
+            'version: 1.8.0_221; versionsuffix:': ['foo-1.2.3.eb'],
+            'version: 1.8; versionsuffix:': ['foo-1.2.3.eb'],
+            'version: 11.0.2; versionsuffix:': ['bar-4.5.6-Java-11.eb'],
+            'version: 11; versionsuffix:': ['bar-4.5.6-Java-11.eb'],
+            'version: 12.1.6; versionsuffix:': ['foobar-0.0-Java-12.eb'],
+            'version: 12; versionsuffix:': ['foobar-0.0-Java-12.eb'],
+        }))
+
+        # strange situation: odd number of Java versions
+        # not OK: two Java wrappers (and no versionsuffix to indicate exception)
+        self.assertFalse(self.check_dep_vars('Java', {
+            'version: 1.8.0_221; versionsuffix:': ['foo-1.2.3.eb'],
+            'version: 1.8; versionsuffix:': ['foo-1.2.3.eb'],
+            'version: 11; versionsuffix:': ['bar-4.5.6.eb'],
+        }))
+        # OK because of -Java-11 versionsuffix
+        self.assertTrue(self.check_dep_vars('Java', {
+            'version: 1.8.0_221; versionsuffix:': ['foo-1.2.3.eb'],
+            'version: 1.8; versionsuffix:': ['foo-1.2.3.eb'],
+            'version: 11; versionsuffix:': ['bar-4.5.6-Java-11.eb'],
+        }))
+        # not OK: two Java wrappers (and no versionsuffix to indicate exception)
+        self.assertFalse(self.check_dep_vars('Java', {
+            'version: 1.8; versionsuffix:': ['foo-1.2.3.eb'],
+            'version: 11.0.2; versionsuffix:': ['bar-4.5.6.eb'],
+            'version: 11; versionsuffix:': ['bar-4.5.6.eb'],
+        }))
+        # OK because of -Java-11 versionsuffix
+        self.assertTrue(self.check_dep_vars('Java', {
+            'version: 1.8; versionsuffix:': ['foo-1.2.3.eb'],
+            'version: 11.0.2; versionsuffix:': ['bar-4.5.6-Java-11.eb'],
+            'version: 11; versionsuffix:': ['bar-4.5.6-Java-11.eb'],
+        }))
+
+        # two different versions of Boost is not OK
+        self.assertFalse(self.check_dep_vars('Boost', {
+            'version: 1.64.0; versionsuffix:': ['foo-1.2.3.eb'],
+            'version: 1.70.0; versionsuffix:': ['foo-2.3.4.eb'],
+        }))
+
+        # a different Boost version that is only used as dependency for a matching Boost.Python is fine
+        self.assertTrue(self.check_dep_vars('Boost', {
+            'version: 1.64.0; versionsuffix:': ['Boost.Python-1.64.0-gompi-2019a.eb'],
+            'version: 1.70.0; versionsuffix:': ['foo-2.3.4.eb'],
+        }))
+        self.assertTrue(self.check_dep_vars('Boost', {
+            'version: 1.64.0; versionsuffix:': ['Boost.Python-1.64.0-gompi-2018b.eb'],
+            'version: 1.66.0; versionsuffix:': ['Boost.Python-1.66.0-gompi-2019a.eb'],
+            'version: 1.70.0; versionsuffix:': ['foo-2.3.4.eb'],
+        }))
+        self.assertFalse(self.check_dep_vars('Boost', {
+            'version: 1.64.0; versionsuffix:': ['Boost.Python-1.64.0-gompi-2019a.eb'],
+            'version: 1.66.0; versionsuffix:': ['foo-1.2.3.eb'],
+            'version: 1.70.0; versionsuffix:': ['foo-2.3.4.eb'],
+        }))
+
+        self.assertTrue(self.check_dep_vars('Boost', {
+            'version: 1.63.0; versionsuffix: -Python-2.7.14': ['EMAN2-2.21a-foss-2018a-Python-2.7.14-Boost-1.63.0.eb'],
+            'version: 1.64.0; versionsuffix:': ['Boost.Python-1.64.0-gompi-2018a.eb'],
+            'version: 1.66.0; versionsuffix:': ['BLAST+-2.7.1-foss-2018a.eb'],
+        }))
+
+        self.assertTrue(self.check_dep_vars('Boost', {
+            'version: 1.64.0; versionsuffix:': [
+                'Boost.Python-1.64.0-gompi-2019a.eb',
+                'EMAN2-2.3-foss-2019a-Python-2.7.15.eb',
+            ],
+            'version: 1.70.0; versionsuffix:': [
+                'BLAST+-2.9.0-gompi-2019a.eb',
+                'Boost.Python-1.70.0-gompi-2019a.eb',
+            ],
+        }))
+
     def test_dep_versions_per_toolchain_generation(self):
         """
         Check whether there's only one dependency version per toolchain generation actively used.
         This is enforced to try and limit the chance of running into conflicts when multiple modules built with
         the same toolchain are loaded together.
         """
-        if self.ordered_specs is None:
+        if EasyConfigTest.ordered_specs is None:
             self.process_all_easyconfigs()
 
         def get_deps_for(ec):
@@ -163,7 +416,7 @@ def get_deps_for(ec):
             for dep in ec['ec']['dependencies']:
                 dep_mod_name = dep['full_mod_name']
                 deps.append((dep['name'], dep['version'], dep['versionsuffix'], dep_mod_name))
-                res = [x for x in self.ordered_specs if x['full_mod_name'] == dep_mod_name]
+                res = [x for x in EasyConfigTest.ordered_specs if x['full_mod_name'] == dep_mod_name]
                 if len(res) == 1:
                     deps.extend(get_deps_for(res[0]))
                 else:
@@ -171,109 +424,6 @@ def get_deps_for(ec):
 
             return deps
 
-        def check_dep_vars(dep, dep_vars):
-            """Check whether available variants of a particular dependency are acceptable or not."""
-
-            # 'guilty' until proven 'innocent'
-            res = False
-
-            # filter out binutils with empty versionsuffix which is used to build toolchain compiler
-            if dep == 'binutils' and len(dep_vars) > 1:
-                empty_vsuff_vars = [v for v in dep_vars.keys() if v.endswith('versionsuffix: ')]
-                if len(empty_vsuff_vars) == 1:
-                    dep_vars = dict((k, v) for (k, v) in dep_vars.items() if k != empty_vsuff_vars[0])
-
-            # multiple variants of HTSlib is OK as long as they are deps for a matching version of BCFtools
-            elif dep == 'HTSlib' and len(dep_vars) > 1:
-                for key in list(dep_vars):
-                    ecs = dep_vars[key]
-                    # filter out HTSlib variants that are only used as dependency for BCFtools with same version
-                    htslib_ver = re.search('^version: (?P<ver>[^;]+);', key).group('ver')
-                    if all(ec.startswith('BCFtools-%s-' % htslib_ver) for ec in ecs):
-                        dep_vars.pop(key)
-
-            # filter out FFTW and imkl with -serial versionsuffix which are used in non-MPI subtoolchains
-            elif dep in ['FFTW', 'imkl']:
-                serial_vsuff_vars = [v for v in dep_vars.keys() if v.endswith('versionsuffix: -serial')]
-                if len(serial_vsuff_vars) == 1:
-                    dep_vars = dict((k, v) for (k, v) in dep_vars.items() if k != serial_vsuff_vars[0])
-
-            # for some dependencies, we allow exceptions for software that depends on a particular version,
-            # as long as that's indicated by the versionsuffix
-            elif dep in ['ASE', 'Boost', 'Java', 'Lua', 'PLUMED', 'R'] and len(dep_vars) > 1:
-                for key in list(dep_vars):
-                    dep_ver = re.search('^version: (?P<ver>[^;]+);', key).group('ver')
-                    # use version of Java wrapper rather than full Java version
-                    if dep == 'Java':
-                        dep_ver = '.'.join(dep_ver.split('.')[:2])
-                    # filter out dep version if all easyconfig filenames using it include specific dep version
-                    if all(re.search('-%s-%s' % (dep, dep_ver), v) for v in dep_vars[key]):
-                        dep_vars.pop(key)
-                    # always retain at least one dep variant
-                    if len(dep_vars) == 1:
-                        break
-
-                # filter R dep for a specific version of Python 2.x
-                if dep == 'R' and len(dep_vars) > 1:
-                    for key in list(dep_vars):
-                        if '; versionsuffix: -Python-2' in key:
-                            dep_vars.pop(key)
-                        # always retain at least one variant
-                        if len(dep_vars) == 1:
-                            break
-
-            # filter out Java 'wrapper'
-            # i.e. if the version of one is a prefix of the version of the other one (e.g. 1.8 & 1.8.0_181)
-            if dep == 'Java' and len(dep_vars) == 2:
-                key1, key2 = sorted(dep_vars.keys())
-                ver1, ver2 = [k.split(';')[0] for k in [key1, key2]]
-                if ver1.startswith(ver2):
-                    dep_vars.pop(key2)
-                elif ver2.startswith(ver1):
-                    dep_vars.pop(key1)
-
-            # filter out variants that are specific to a particular version of CUDA
-            cuda_dep_vars = [v for v in dep_vars.keys() if '-CUDA' in v]
-            if len(dep_vars) > len(cuda_dep_vars):
-                for key in list(dep_vars):
-                    if re.search('; versionsuffix: .*-CUDA-[0-9.]+', key):
-                        dep_vars.pop(key)
-
-            # some software packages require an old version of a particular dependency
-            old_dep_versions = {
-                # libxc (CP2K & ABINIT require libxc 2.x or 3.x)
-                'libxc': r'[23]\.',
-                # OPERA requires SAMtools 0.x
-                'SAMtools': r'0\.',
-                # Kraken 1.0 requires Jellyfish 1.x
-                'Jellyfish': r'1\.',
-            }
-            if dep in old_dep_versions and len(dep_vars) > 1:
-                for key in list(dep_vars):
-                    # filter out known old dependency versions
-                    if re.search('^version: %s' % old_dep_versions[dep], key):
-                        dep_vars.pop(key)
-
-            # only single variant is always OK
-            if len(dep_vars) == 1:
-                res = True
-
-            elif len(dep_vars) == 2 and dep in ['Python', 'Tkinter']:
-                # for Python & Tkinter, it's OK to have on 2.x and one 3.x version
-                v2_dep_vars = [x for x in dep_vars.keys() if x.startswith('version: 2.')]
-                v3_dep_vars = [x for x in dep_vars.keys() if x.startswith('version: 3.')]
-                if len(v2_dep_vars) == 1 and len(v3_dep_vars) == 1:
-                    res = True
-
-            # two variants is OK if one is for Python 2.x and the other is for Python 3.x (based on versionsuffix)
-            elif len(dep_vars) == 2:
-                py2_dep_vars = [x for x in dep_vars.keys() if '; versionsuffix: -Python-2.' in x]
-                py3_dep_vars = [x for x in dep_vars.keys() if '; versionsuffix: -Python-3.' in x]
-                if len(py2_dep_vars) == 1 and len(py3_dep_vars) == 1:
-                    res = True
-
-            return res
-
         # some software also follows <year>{a,b} versioning scheme,
         # which throws off the pattern matching done below for toolchain versions
         false_positives_regex = re.compile('^MATLAB-Engine-20[0-9][0-9][ab]')
@@ -285,7 +435,7 @@ def check_dep_vars(dep, dep_vars):
             regex = re.compile('^.*-(?P<tc_gen>%s).*\.eb$' % pattern)
 
             # collect variants for all dependencies of easyconfigs that use a toolchain that matches
-            for ec in self.ordered_specs:
+            for ec in EasyConfigTest.ordered_specs:
                 ec_file = os.path.basename(ec['spec'])
 
                 # take into account software which also follows a <year>{a,b} versioning scheme
@@ -307,7 +457,7 @@ def check_dep_vars(dep, dep_vars):
             for tc_gen in sorted(all_deps.keys()):
                 for dep in sorted(all_deps[tc_gen].keys()):
                     dep_vars = all_deps[tc_gen][dep]
-                    if not check_dep_vars(dep, dep_vars):
+                    if not self.check_dep_vars(dep, dep_vars):
                         multi_dep_vars.append(dep)
                         multi_dep_vars_msg += "\nfound %s variants of '%s' dependency " % (len(dep_vars), dep)
                         multi_dep_vars_msg += "in easyconfigs using '%s' toolchain generation\n* " % tc_gen
@@ -320,10 +470,10 @@ def check_dep_vars(dep, dep_vars):
     def test_sanity_check_paths(self):
         """Make sure specified sanity check paths adher to the requirements."""
 
-        if self.ordered_specs is None:
+        if EasyConfigTest.ordered_specs is None:
             self.process_all_easyconfigs()
 
-        for ec in self.parsed_easyconfigs:
+        for ec in EasyConfigTest.parsed_easyconfigs:
             ec_scp = ec['ec']['sanity_check_paths']
             if ec_scp != {}:
                 # if sanity_check_paths is specified (i.e., non-default), it must adher to the requirements
@@ -436,8 +586,8 @@ def check_sanity_check_paths(self, changed_ecs):
         """Make sure a custom sanity_check_paths value is specified for easyconfigs that use a generic easyblock."""
 
         # PythonBundle & PythonPackage already have a decent customised sanity_check_paths
-        # ModuleRC and Toolchain easyblocks doesn't install anything so there is nothing to check.
-        whitelist = ['CrayToolchain', 'ModuleRC', 'PythonBundle', 'PythonPackage', 'Toolchain']
+        # BuildEnv, ModuleRC and Toolchain easyblocks doesn't install anything so there is nothing to check.
+        whitelist = ['CrayToolchain', 'ModuleRC', 'PythonBundle', 'PythonPackage', 'Toolchain', 'BuildEnv']
         # GCC is just a bundle of GCCcore+binutils
         bundles_whitelist = ['GCC']
 
@@ -460,6 +610,10 @@ def check_https(self, changed_ecs):
         """Make sure https:// URL is used (if it exists) for homepage/source_urls (rather than http://)."""
 
         whitelist = [
+            'Kaiju',  # invalid certificate at https://kaiju.binf.ku.dk
+            'libxml2',  # https://xmlsoft.org works, but invalid certificate
+            'p4vasp',  # https://www.p4vasp.at doesn't work
+            'ITSTool',  # https://itstool.org/ doesn't work
             'UCX-',  # bad certificate for https://www.openucx.org
         ]
 
@@ -469,14 +623,18 @@ def check_https(self, changed_ecs):
         for ec in changed_ecs:
             ec_fn = os.path.basename(ec.path)
 
+            # skip whitelisted easyconfigs
             if any(ec_fn.startswith(x) for x in whitelist):
                 continue
 
-            for http_url in http_regex.findall(ec.rawtxt):
+            # ignore commented out lines in easyconfig files when checking for http:// URLs
+            ec_txt = '\n'.join(l for l in ec.rawtxt.split('\n') if not l.startswith('#'))
+
+            for http_url in http_regex.findall(ec_txt):
                 https_url = http_url.replace('http://', 'https://')
                 try:
                     https_url_works = bool(urlopen(https_url))
-                except (HTTPError, URLError):
+                except Exception:
                     https_url_works = False
 
                 if https_url_works:
@@ -488,14 +646,26 @@ def test_changed_files_pull_request(self):
         """Specific checks only done for the (easyconfig) files that were changed in a pull request."""
 
         # $TRAVIS_PULL_REQUEST should be a PR number, otherwise we're not running tests for a PR
-        if re.match('^[0-9]+$', os.environ.get('TRAVIS_PULL_REQUEST', '(none)')):
+        travis_pr_test = re.match('^[0-9]+$', os.environ.get('TRAVIS_PULL_REQUEST', '(none)'))
+
+        # when testing a PR in GitHub Actions, $GITHUB_EVENT_NAME will be set to 'pull_request'
+        github_pr_test = os.environ.get('GITHUB_EVENT_NAME') == 'pull_request'
+
+        if travis_pr_test or github_pr_test:
 
             # target branch should be anything other than 'master';
             # usually is 'develop', but could also be a release branch like '3.7.x'
-            travis_branch = os.environ.get('TRAVIS_BRANCH', None)
-            if travis_branch and travis_branch != 'master':
+            if travis_pr_test:
+                target_branch = os.environ.get('TRAVIS_BRANCH', None)
+            else:
+                target_branch = os.environ.get('GITHUB_BASE_REF', None)
+
+            if target_branch is None:
+                self.assertTrue(False, "Failed to determine target branch for current pull request.")
+
+            if target_branch != 'master':
 
-                if not self.parsed_easyconfigs:
+                if not EasyConfigTest.parsed_easyconfigs:
                     self.process_all_easyconfigs()
 
                 # relocate to top-level directory of repository to run 'git diff' command
@@ -503,7 +673,7 @@ def test_changed_files_pull_request(self):
                 cwd = change_dir(top_dir)
 
                 # get list of changed easyconfigs
-                cmd = "git diff --name-only --diff-filter=AM %s...HEAD" % travis_branch
+                cmd = "git diff --name-only --diff-filter=AM %s...HEAD" % target_branch
                 out, ec = run_cmd(cmd, simple=False)
                 changed_ecs_filenames = [os.path.basename(f) for f in out.strip().split('\n') if f.endswith('.eb')]
                 print("\nList of changed easyconfig files in this PR: %s" % '\n'.join(changed_ecs_filenames))
@@ -514,7 +684,7 @@ def test_changed_files_pull_request(self):
                 changed_ecs = []
                 for ec_fn in changed_ecs_filenames:
                     match = None
-                    for ec in self.parsed_easyconfigs:
+                    for ec in EasyConfigTest.parsed_easyconfigs:
                         if os.path.basename(ec['spec']) == ec_fn:
                             match = ec['ec']
                             break
@@ -569,7 +739,7 @@ def template_easyconfig_test(self, spec):
         ec = ecs[0]['ec']
 
         # cache the parsed easyconfig, to avoid that it is parsed again
-        self.parsed_easyconfigs.append(ecs[0])
+        EasyConfigTest.parsed_easyconfigs.append(ecs[0])
     else:
         self.assertTrue(False, "easyconfig %s does not contain blocks, yields only one parsed easyconfig" % spec)
 
@@ -711,8 +881,14 @@ def template_easyconfig_test(self, spec):
             # number of dependencies should remain the same
             self.assertEqual(len(orig_val), len(dumped_val))
             for orig_dep, dumped_dep in zip(orig_val, dumped_val):
-                # name/version should always match
-                self.assertEqual(orig_dep[:2], dumped_dep[:2])
+                # name should always match
+                self.assertEqual(orig_dep[0], dumped_dep[0])
+
+                # version should always match, or be a possibility from the version dict
+                if isinstance(orig_dep[1], dict):
+                    self.assertTrue(dumped_dep[1] in orig_dep[1].values())
+                else:
+                    self.assertEqual(orig_dep[1], dumped_dep[1])
 
                 # 3rd value is versionsuffix;
                 if len(dumped_dep) >= 3: