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
3 changes: 2 additions & 1 deletion easybuild/tools/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -1111,9 +1111,10 @@ def load(self, modules, mod_paths=None, purge=False, init_env=None, allow_reload
self.check_module_path()

# extend $MODULEPATH if needed
curr_mod_paths = curr_module_paths()
for mod_path in mod_paths:
full_mod_path = os.path.join(install_path('mod'), build_option('suffix_modules_path'), mod_path)
if os.path.exists(full_mod_path):
if os.path.exists(full_mod_path) and full_mod_path not in curr_mod_paths:
Copy link
Member

Choose a reason for hiding this comment

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

This is potentially problematic I think...

Imagine that EasyBuild is configured to install modules into /apps/modules/all, and that eb is launched in an environment in which $MODULEPATH is set to /apps/manual-modules:/apps/modules/all.

In that case, we would expect that the modules that EasyBuild loads as dependencies for an installation come from /apps/modules/all, and would only be picked up from /apps/manual-modules if the required module is not found in /apps/modules/all.
Or at least, that's the current behavior: giving precedence to modules that were (assumed to be) installed with EasyBuild.

This changes that, since the order in $MODULEPATH as it was before launching EasyBuild would be retained, and that may surprise people depending on their setup, since it's a change in behavior...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the situation you describe, eb is changing MODULEPATH to make its modulepaths at the top. eb does not go through ModuleTool.load() to do that.

I have made the following test:

export MODULEPATH=/usr/share/Modules/modulefiles:/home/user/.local/easybuild/modules/all/Core:/home/user/.local/easybuild/modules/all
rm -rf ~/.local/easybuild
eb --module-naming-scheme=HierarchicalMNS --skip-sanity-check --robot --rebuild --module-only GLib-2.77.1-GCCcore-12.3.0.eb

Then looking at the produced log file for instrumented ModuleTool.set_mod_paths():

$ grep MODULEPATH /home/user/.local/easybuild/software/GLib/2.77.1-GCCcore-12.3.0/easybuild/easybuild-GLib-2.77.1-20250827.211211.log
os.environ['MODULEPATH'] = '/home/user/.local/easybuild/modules/all:/usr/share/Modules/modulefiles'
os.environ['MODULEPATH'] = '/home/user/.local/easybuild/modules/all/Core:/home/user/.local/easybuild/modules/all:/usr/share/Modules/modulefiles'
== 2025-08-27 21:12:09,913 modules.py:794 INFO $MODULEPATH after set_mod_paths: /home/user/.local/easybuild/modules/all/Core:/home/user/.local/easybuild/modules/all:/usr/share/Modules/modulefiles

So my external modulepath is moved at the back by eb.

self.prepend_module_path(full_mod_path)

loaded_modules = self.loaded_modules()
Expand Down
41 changes: 41 additions & 0 deletions test/framework/easyblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
@author: Maxime Boissonneault (Compute Canada)
@author: Jan Andre Reuter (Juelich Supercomputing Centre)
"""
import fileinput
import os
import re
import shutil
Expand Down Expand Up @@ -217,6 +218,46 @@ def test_load_module(self):
eb.close_log()
os.remove(eb.logfile)

# test HMNS module load when conflicting dependencies are available in both Core and
# toolchain-specific modulepaths
# see also https://github.com/easybuilders/easybuild-framework/issues/4986
test_ecs_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
'easyconfigs', 'test_ecs')
os.environ['EASYBUILD_MODULE_NAMING_SCHEME'] = 'HierarchicalMNS'
build_options = {
'generate_devel_module': True, # go through EasyBlock.fake_module_environment()
'robot_path': [test_ecs_path],
}
init_config(build_options=build_options)

# setup pre-built test modules under test install path
mod_prefix = os.path.join(self.test_installpath, 'modules', 'all')
mkdir(mod_prefix, parents=True)
for mod_subdir in ['Core', 'Compiler']:
src_mod_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
'modules', 'HierarchicalMNS', mod_subdir)
copy_dir(src_mod_path, os.path.join(mod_prefix, mod_subdir))

# tweak use statements in toolchain module to ensure correct paths
modfile = os.path.join(mod_prefix, 'Core', 'GCCcore', '12.3.0')
for line in fileinput.input(modfile, inplace=1):
line = re.sub(r"(module\s*use\s*)/tmp/modules/all",
r"\1%s/modules/all" % self.test_installpath,
line)
sys.stdout.write(line)

test_eb_file = os.path.join(test_ecs_path, 'g', 'GLib', 'GLib-2.77.1-GCCcore-12.3.0.eb')
eb = EasyBlock(EasyConfig(test_eb_file))

self.reset_modulepath([os.path.join(mod_prefix)])

with self.mocked_stdout_stderr():
eb.check_readiness_step()
eb.make_builddir()
eb.prepare_step()
eb.make_module_step()
eb.load_module()

def test_fake_module_load(self):
"""Testcase for fake module load"""
self.contents = '\n'.join([
Expand Down
23 changes: 23 additions & 0 deletions test/framework/easyconfigs/test_ecs/g/GCCcore/GCCcore-12.3.0.eb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# should be EB_GCC, but OK for testing purposes
easyblock = 'EB_toy'

name = "GCCcore"
version = '12.3.0'

homepage = 'http://gcc.gnu.org/'
description = """The GNU Compiler Collection includes front ends for C, C++, Objective-C, Fortran, Java, and Ada,
as well as libraries for these languages (libstdc++, libgcj,...)."""

toolchain = SYSTEM

source_urls = [
'http://ftpmirror.gnu.org/%(namelower)s/%(namelower)s-%(version)s', # GCC auto-resolving HTTP mirror
]

#gcc_name = 'GCC'

sources = [
SOURCELOWER_TAR_BZ2,
]

moduleclass = 'compiler'
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
easyblock = 'EB_toy'

name = 'GLib'
version = '2.77.1'

homepage = 'https://www.gtk.org/'
description = """GLib is one of the base libraries of the GTK+ project"""

toolchain = {'name': 'GCCcore', 'version': '12.3.0'}

source_urls = [FTPGNOME_SOURCE]
sources = [SOURCELOWER_TAR_XZ]

dependencies = [
('gettext', '0.21.1'),
('util-linux', '2.39'),
]

moduleclass = 'vis'
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
easyblock = 'EB_toy'

name = 'gettext'
version = '0.21.1'

homepage = 'https://www.gnu.org/software/gettext/'
description = """GNU 'gettext' is an important step for the GNU Translation Project, as it is an asset on which we may
build many other steps. This package offers to programmers, translators, and even users, a well integrated set of tools
and documentation"""

toolchain = {'name': 'GCCcore', 'version': '12.3.0'}

source_urls = [GNU_SOURCE]
sources = [SOURCE_TAR_GZ]

dependencies = [
('ncurses', '6.4'),
]

moduleclass = 'tools'
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
easyblock = 'EB_toy'

name = 'ncurses'
version = '6.4'

homepage = 'https://www.gnu.org/software/ncurses/'
description = """
The Ncurses (new curses) library is a free software emulation of curses in
System V Release 4.0, and more. It uses Terminfo format, supports pads and
color and multiple highlights and forms characters and function-key mapping,
and has all the other SYSV-curses enhancements over BSD Curses.
"""

toolchain = {'name': 'GCCcore', 'version': '12.3.0'}

source_urls = [GNU_SOURCE]
sources = [SOURCE_TAR_GZ]

moduleclass = 'devel'
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
easyblock = 'EB_toy'

name = 'util-linux'
version = '2.39'

homepage = 'https://www.kernel.org/pub/linux/utils/util-linux'

description = "Set of Linux utilities"

toolchain = {'name': 'GCCcore', 'version': '12.3.0'}

source_urls = ['%s/v%%(version_major_minor)s' % homepage]
sources = [SOURCELOWER_TAR_GZ]

dependencies = [
('ncurses', '6.4'),
]

moduleclass = 'tools'
6 changes: 3 additions & 3 deletions test/framework/filetools.py
Original file line number Diff line number Diff line change
Expand Up @@ -2641,7 +2641,7 @@ def test_index_functions(self):
# test with specified path with and without trailing '/'s
for path in [test_ecs, test_ecs + '/', test_ecs + '//']:
index = ft.create_index(path)
self.assertEqual(len(index), 95)
self.assertEqual(len(index), 100)

expected = [
os.path.join('b', 'bzip2', 'bzip2-1.0.6-GCC-4.9.2.eb'),
Expand Down Expand Up @@ -2691,7 +2691,7 @@ def test_index_functions(self):
regex = re.compile(r"^== found valid index for %s, so using it\.\.\.$" % ecs_dir)
self.assertTrue(regex.match(stdout.strip()), "Pattern '%s' matches with: %s" % (regex.pattern, stdout))

self.assertEqual(len(index), 26)
self.assertEqual(len(index), 29)
for fn in expected:
self.assertIn(fn, index)

Expand Down Expand Up @@ -2721,7 +2721,7 @@ def test_index_functions(self):
regex = re.compile(r"^== found valid index for %s, so using it\.\.\.$" % ecs_dir)
self.assertTrue(regex.match(stdout.strip()), "Pattern '%s' matches with: %s" % (regex.pattern, stdout))

self.assertEqual(len(index), 26)
self.assertEqual(len(index), 29)
for fn in expected:
self.assertIn(fn, index)

Expand Down
4 changes: 2 additions & 2 deletions test/framework/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@


# number of modules included for testing purposes
TEST_MODULES_COUNT = 111
TEST_MODULES_COUNT = 118


class ModulesTest(EnhancedTestCase):
Expand Down Expand Up @@ -429,7 +429,7 @@ def test_load(self):
ms = self.modtool.available()
# exclude modules not on the top level of a hierarchy
ms = [m for m in ms if not (m.startswith('Core') or m.startswith('Compiler/') or m.startswith('MPI/') or
m.startswith('CategorizedHMNS'))]
m.startswith('CategorizedHMNS') or m.startswith('HierarchicalMNS'))]

for m in ms:
self.modtool.load([m])
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#%Module
proc ModulesHelp { } {
puts stderr {

Description
===========
GLib is one of the base libraries of the GTK+ project


More information
================
- Homepage: https://www.gtk.org/
}
}

module-whatis {Description: GLib is one of the base libraries of the GTK+ project}
module-whatis {Homepage: https://www.gtk.org/}
module-whatis {URL: https://www.gtk.org/}

set root /tmp/software/GLib/2.77.1-GCCcore-12.3.0

conflict GLib

module load gettext/0.21.1

module load util-linux/2.39

setenv EBROOTGLIB "$root"
setenv EBVERSIONGLIB "2.77.1"
setenv EBDEVELGLIB "$root/easybuild/Compiler-GCCcore-12.3.0-GLib-2.77.1-easybuild-devel"

# Built with EasyBuild version 5.1.2.dev0-r5819168038d7bd74810f38f75acfced9fb41870e
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#%Module
proc ModulesHelp { } {
puts stderr {

Description
===========
GNU 'gettext' is an important step for the GNU Translation Project, as it is an asset on which we may
build many other steps. This package offers to programmers, translators, and even users, a well integrated set of tools
and documentation


More information
================
- Homepage: https://www.gnu.org/software/gettext/
}
}

module-whatis {Description: GNU 'gettext' is an important step for the GNU Translation Project, as it is an asset on which we may
build many other steps. This package offers to programmers, translators, and even users, a well integrated set of tools
and documentation}
module-whatis {Homepage: https://www.gnu.org/software/gettext/}
module-whatis {URL: https://www.gnu.org/software/gettext/}

set root /tmp/software/gettext/0.21.1-GCCcore-12.3.0

conflict gettext

module load ncurses/6.4

setenv EBROOTGETTEXT "$root"
setenv EBVERSIONGETTEXT "0.21.1"
setenv EBDEVELGETTEXT "$root/easybuild/Compiler-GCCcore-12.3.0-gettext-0.21.1-easybuild-devel"

# Built with EasyBuild version 5.1.2.dev0-r5819168038d7bd74810f38f75acfced9fb41870e
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#%Module
proc ModulesHelp { } {
puts stderr {

Description
===========
The Ncurses (new curses) library is a free software emulation of curses in
System V Release 4.0, and more. It uses Terminfo format, supports pads and
color and multiple highlights and forms characters and function-key mapping,
and has all the other SYSV-curses enhancements over BSD Curses.


More information
================
- Homepage: https://www.gnu.org/software/ncurses/
}
}

module-whatis {Description:
The Ncurses (new curses) library is a free software emulation of curses in
System V Release 4.0, and more. It uses Terminfo format, supports pads and
color and multiple highlights and forms characters and function-key mapping,
and has all the other SYSV-curses enhancements over BSD Curses.
}
module-whatis {Homepage: https://www.gnu.org/software/ncurses/}
module-whatis {URL: https://www.gnu.org/software/ncurses/}

set root /tmp/software/ncurses/6.4-GCCcore-12.3.0

conflict ncurses

setenv EBROOTNCURSES "$root"
setenv EBVERSIONNCURSES "6.4"
setenv EBDEVELNCURSES "$root/easybuild/Compiler-GCCcore-12.3.0-ncurses-6.4-easybuild-devel"

# Built with EasyBuild version 5.1.2.dev0-r5819168038d7bd74810f38f75acfced9fb41870e
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#%Module
proc ModulesHelp { } {
puts stderr {

Description
===========
Set of Linux utilities


More information
================
- Homepage: https://www.kernel.org/pub/linux/utils/util-linux
}
}

module-whatis {Description: Set of Linux utilities}
module-whatis {Homepage: https://www.kernel.org/pub/linux/utils/util-linux}
module-whatis {URL: https://www.kernel.org/pub/linux/utils/util-linux}

set root /tmp/software/util-linux/2.39-GCCcore-12.3.0

conflict util-linux

module load ncurses/6.4

setenv EBROOTUTILMINLINUX "$root"
setenv EBVERSIONUTILMINLINUX "2.39"
setenv EBDEVELUTILMINLINUX "$root/easybuild/Compiler-GCCcore-12.3.0-util-linux-2.39-easybuild-devel"

# Built with EasyBuild version 5.1.2.dev0-r5819168038d7bd74810f38f75acfced9fb41870e
31 changes: 31 additions & 0 deletions test/framework/modules/HierarchicalMNS/Core/GCCcore/12.3.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#%Module
proc ModulesHelp { } {
puts stderr {

Description
===========
The GNU Compiler Collection includes front ends for C, C++, Objective-C, Fortran, Java, and Ada,
as well as libraries for these languages (libstdc++, libgcj,...).


More information
================
- Homepage: https://gcc.gnu.org/
}
}

module-whatis {Description: The GNU Compiler Collection includes front ends for C, C++, Objective-C, Fortran, Java, and Ada,
as well as libraries for these languages (libstdc++, libgcj,...).}
module-whatis {Homepage: https://gcc.gnu.org/}
module-whatis {URL: https://gcc.gnu.org/}

set root /tmp/software/GCCcore/12.3.0

conflict GCCcore
module use /tmp/modules/all/Compiler/GCCcore/12.3.0

setenv EBROOTGCCCORE "$root"
setenv EBVERSIONGCCCORE "12.3.0"
setenv EBDEVELGCCCORE "$root/easybuild/Core-GCCcore-12.3.0-easybuild-devel"

# Built with EasyBuild version 5.1.2.dev0-r5819168038d7bd74810f38f75acfced9fb41870e
Loading