From ee73024d9c89ce4d528b56b1c29a9ed192daa03b Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 5 Apr 2024 20:21:21 +0200 Subject: [PATCH 01/37] bump version to 4.9.2dev --- easybuild/tools/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/tools/version.py b/easybuild/tools/version.py index 0e82ff2ce9..c01c8fc5c3 100644 --- a/easybuild/tools/version.py +++ b/easybuild/tools/version.py @@ -45,7 +45,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 = LooseVersion('4.9.1') +VERSION = LooseVersion('4.9.2.dev0') UNKNOWN = 'UNKNOWN' From 423213676391126db05cbcf242210dce9823ba1d Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Thu, 11 Apr 2024 17:35:59 +0200 Subject: [PATCH 02/37] Fix typo in patch_step logging --- easybuild/framework/easyblock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index d2727d61ee..b17806b7c2 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -2607,7 +2607,7 @@ def patch_step(self, beginpath=None, patches=None): copy_patch = 'copy' in patch and 'sourcepath' not in patch self.log.debug("Source index: %s; patch level: %s; source path suffix: %s; copy patch: %s", - srcind, level, srcpathsuffix, copy) + srcind, level, srcpathsuffix, copy_patch) if beginpath is None: try: From bdba1c2f85bccc9888a2f0fcb9a3546e1f0cfcb0 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Fri, 12 Apr 2024 17:12:33 +0200 Subject: [PATCH 03/37] Improve behavior when using extension with 'nosource:True' --- easybuild/framework/extensioneasyblock.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/easybuild/framework/extensioneasyblock.py b/easybuild/framework/extensioneasyblock.py index 69c824fe7e..86ea151cbb 100644 --- a/easybuild/framework/extensioneasyblock.py +++ b/easybuild/framework/extensioneasyblock.py @@ -123,7 +123,9 @@ def _set_start_dir(self): self.log.debug("Using extension start dir: %s", ext_start_dir) self.cfg['start_dir'] = ext_start_dir self.cfg.template_values['start_dir'] = ext_start_dir - elif ext_start_dir is None: + return ext_start_dir + + if ext_start_dir is None: # This may be on purpose, e.g. for Python WHL files which do not get extracted self.log.debug("Start dir is not set.") else: @@ -137,7 +139,10 @@ def run(self, unpack_src=False): """Common operations for extensions: unpacking sources, patching, ...""" # unpack file if desired - if unpack_src: + if self.options.get('nosource', False): + # If no source wanted use the start_dir from the main EC + self.ext_dir = self.master.start_dir + elif unpack_src: targetdir = os.path.join(self.master.builddir, remove_unwanted_chars(self.name)) self.ext_dir = extract_file(self.src, targetdir, extra_options=self.unpack_options, change_into_dir=False, cmd=self.src_extract_cmd) @@ -146,10 +151,9 @@ def run(self, unpack_src=False): # because start_dir value is usually a relative path (if it is set) change_dir(self.ext_dir) - self._set_start_dir() + start_dir = self._set_start_dir() + if start_dir: change_dir(self.start_dir) - else: - self._set_start_dir() # patch if needed EasyBlock.patch_step(self, beginpath=self.ext_dir) From 138d5bea9a4df2143fa1110f16d63a2619543708 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Fri, 12 Apr 2024 18:17:48 +0200 Subject: [PATCH 04/37] Throw error if non-existant startdir was provided Improve the current error that we couldn't change into that directory. --- easybuild/framework/extensioneasyblock.py | 16 ++++++++-------- test/framework/easyblock.py | 5 +---- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/easybuild/framework/extensioneasyblock.py b/easybuild/framework/extensioneasyblock.py index 86ea151cbb..efd7c349f7 100644 --- a/easybuild/framework/extensioneasyblock.py +++ b/easybuild/framework/extensioneasyblock.py @@ -123,15 +123,15 @@ def _set_start_dir(self): self.log.debug("Using extension start dir: %s", ext_start_dir) self.cfg['start_dir'] = ext_start_dir self.cfg.template_values['start_dir'] = ext_start_dir - return ext_start_dir - - if ext_start_dir is None: + elif ext_start_dir is None: # This may be on purpose, e.g. for Python WHL files which do not get extracted self.log.debug("Start dir is not set.") - else: + elif self.start_dir: # non-existing start dir means wrong input from user - warn_msg = "Provided start dir (%s) for extension %s does not exist: %s" % (self.start_dir, self.name, - ext_start_dir) + raise EasyBuildError("Provided start dir (%s) for extension %s does not exist: %s", + self.start_dir, self.name, ext_start_dir) + else: + warn_msg = 'Failed to determine start dir for extension %s: %s' % (self.name, ext_start_dir) self.log.warning(warn_msg) print_warning(warn_msg, silent=build_option('silent')) @@ -151,8 +151,8 @@ def run(self, unpack_src=False): # because start_dir value is usually a relative path (if it is set) change_dir(self.ext_dir) - start_dir = self._set_start_dir() - if start_dir: + self._set_start_dir() + if self.start_dir: change_dir(self.start_dir) # patch if needed diff --git a/test/framework/easyblock.py b/test/framework/easyblock.py index e6b54e0bc8..6ccd971141 100644 --- a/test/framework/easyblock.py +++ b/test/framework/easyblock.py @@ -2257,11 +2257,8 @@ def check_ext_start_dir(expected_start_dir, unpack_src=True): 'start_dir': 'nonexistingdir'}), ] with self.mocked_stdout_stderr(): - err_pattern = "Failed to change from .*barbar/barbar-0.0 to nonexistingdir.*" + err_pattern = r"Provided start dir \(nonexistingdir\) for extension barbar does not exist:.*" self.assertErrorRegex(EasyBuildError, err_pattern, check_ext_start_dir, 'whatever') - stderr = self.get_stderr() - warning_pattern = "WARNING: Provided start dir (nonexistingdir) for extension barbar does not exist" - self.assertIn(warning_pattern, stderr) # No error when using relative path in non-extracted source for some reason ec['ec']['exts_list'] = [ From 948ee8d661bff7d2f28ba111e5706a08fa845a13 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Fri, 12 Apr 2024 18:19:35 +0200 Subject: [PATCH 05/37] Fix tests for changed behavior and add test for nosource --- test/framework/easyblock.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/test/framework/easyblock.py b/test/framework/easyblock.py index 6ccd971141..7ab5674b97 100644 --- a/test/framework/easyblock.py +++ b/test/framework/easyblock.py @@ -2200,11 +2200,16 @@ def test_extension_set_start_dir(self): cwd = os.getcwd() self.assertExists(cwd) - def check_ext_start_dir(expected_start_dir, unpack_src=True): + def check_ext_start_dir(expected_start_dir, unpack_src=True, parent_startdir=None): """Check start dir.""" # make sure we're in an existing directory at the start change_dir(cwd) + eb = EasyBlock(ec['ec']) + if not os.path.exists(eb.builddir): + eb.make_builddir() # Required to exist for samefile + eb.cfg['start_dir'] = parent_startdir + eb.extensions_step(fetch=True, install=False) # extract sources of the extension ext = eb.ext_instances[-1] @@ -2212,6 +2217,8 @@ def check_ext_start_dir(expected_start_dir, unpack_src=True): if expected_start_dir is None: self.assertIsNone(ext.start_dir) + # Without a start dir we don't change the CWD + self.assertEqual(os.getcwd(), cwd) else: self.assertTrue(os.path.isabs(ext.start_dir)) if ext.start_dir != os.sep: @@ -2221,14 +2228,8 @@ def check_ext_start_dir(expected_start_dir, unpack_src=True): else: abs_expected_start_dir = os.path.join(eb.builddir, expected_start_dir) self.assertEqual(ext.start_dir, abs_expected_start_dir) - if not os.path.exists(eb.builddir): - eb.make_builddir() # Required to exist for samefile self.assertTrue(os.path.samefile(ext.start_dir, abs_expected_start_dir)) - if unpack_src: self.assertTrue(os.path.samefile(os.getcwd(), abs_expected_start_dir)) - else: - # When not unpacking we don't change the CWD - self.assertEqual(os.getcwd(), cwd) remove_dir(eb.builddir) ec['ec']['exts_defaultclass'] = 'DummyExtension' @@ -2288,6 +2289,15 @@ def check_ext_start_dir(expected_start_dir, unpack_src=True): check_ext_start_dir(os.sep, unpack_src=False) self.assertFalse(self.get_stderr()) + # Go to ECs start dir if nosource is used + ec['ec']['exts_list'] = [ + ('barbar', '0.0', { + 'nosource': True}), + ] + with self.mocked_stdout_stderr(): + check_ext_start_dir(self.test_prefix, parent_startdir=self.test_prefix) + self.assertFalse(self.get_stderr()) + def test_prepare_step(self): """Test prepare step (setting up build environment).""" test_easyconfigs = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'easyconfigs', 'test_ecs') From cd7750a946b8b5cbe34262345346ac4d1e98f6dc Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Sun, 14 Apr 2024 18:32:13 +0200 Subject: [PATCH 06/37] consider both easybuild-framework*.tar.gz and easybuild_framework*.tar.gz in CI workflows --- .github/workflows/container_tests.yml | 2 +- .github/workflows/container_tests_apptainer.yml | 2 +- .github/workflows/eb_command.yml | 2 +- .github/workflows/unit_tests.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/container_tests.yml b/.github/workflows/container_tests.yml index ef310a3816..540fafcea1 100644 --- a/.github/workflows/container_tests.yml +++ b/.github/workflows/container_tests.yml @@ -73,7 +73,7 @@ jobs: python setup.py sdist ls dist export PREFIX=/tmp/$USER/$GITHUB_SHA - pip install --prefix $PREFIX dist/easybuild-framework*tar.gz + pip install --prefix $PREFIX dist/easybuild[-_]framework*tar.gz pip install --prefix $PREFIX https://github.com/easybuilders/easybuild-easyblocks/archive/develop.tar.gz - name: run test diff --git a/.github/workflows/container_tests_apptainer.yml b/.github/workflows/container_tests_apptainer.yml index 77d2a4a395..f7040000f9 100644 --- a/.github/workflows/container_tests_apptainer.yml +++ b/.github/workflows/container_tests_apptainer.yml @@ -73,7 +73,7 @@ jobs: python setup.py sdist ls dist export PREFIX=/tmp/$USER/$GITHUB_SHA - pip install --prefix $PREFIX dist/easybuild-framework*tar.gz + pip install --prefix $PREFIX dist/easybuild[-_]framework*tar.gz pip install --prefix $PREFIX https://github.com/easybuilders/easybuild-easyblocks/archive/develop.tar.gz - name: run test diff --git a/.github/workflows/eb_command.yml b/.github/workflows/eb_command.yml index ff9a5eaa4f..ec4907d28c 100644 --- a/.github/workflows/eb_command.yml +++ b/.github/workflows/eb_command.yml @@ -68,7 +68,7 @@ jobs: python setup.py sdist ls dist export PREFIX=/tmp/$USER/$GITHUB_SHA - pip install --prefix $PREFIX dist/easybuild-framework*tar.gz + pip install --prefix $PREFIX dist/easybuild[-_]framework*tar.gz - name: run tests for 'eb' command env: diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index c79b515e42..07ac74fe1a 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -140,7 +140,7 @@ jobs: python setup.py sdist ls dist export PREFIX=/tmp/$USER/$GITHUB_SHA - pip install --prefix $PREFIX dist/easybuild-framework*tar.gz + pip install --prefix $PREFIX dist/easybuild[-_]framework*tar.gz - name: run test suite env: From c01db56615ac82dd135beeb2cd41288134b144a5 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Thu, 18 Apr 2024 10:32:46 +0200 Subject: [PATCH 07/37] Fix --dump-env-script with existing modules This option should only require `--force` when the env file exists. Otherwise, when the module already exists `--force` should not be required. In any case the module file must not be deleted. --- easybuild/framework/easyblock.py | 2 +- easybuild/main.py | 6 ++--- easybuild/tools/config.py | 1 + test/framework/options.py | 39 ++++++++++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index b17806b7c2..33110c61dd 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -2349,7 +2349,7 @@ def check_readiness_step(self): self.log.info("No module %s found. Not skipping anything." % self.full_mod_name) # remove existing module file under --force (but only if --skip is not used) - elif build_option('force') or build_option('rebuild'): + elif (build_option('force') or build_option('rebuild')) and not build_option('dump_env_script'): self.remove_module_file() def fetch_step(self, skip_checksums=False): diff --git a/easybuild/main.py b/easybuild/main.py index df5f23a460..42bd83dff5 100644 --- a/easybuild/main.py +++ b/easybuild/main.py @@ -440,9 +440,9 @@ def process_eb_args(eb_args, eb_go, cfg_settings, modtool, testing, init_session dry_run_mode = options.dry_run or options.dry_run_short or options.missing_modules keep_available_modules = any(( - forced, dry_run_mode, options.extended_dry_run, any_pr_option_set, options.copy_ec, options.inject_checksums, - options.sanity_check_only, options.inject_checksums_to_json) - ) + forced, dry_run_mode, any_pr_option_set, options.copy_ec, options.dump_env_script, options.extended_dry_run, + options.inject_checksums, options.inject_checksums_to_json, options.sanity_check_only + )) # skip modules that are already installed unless forced, or unless an option is used that warrants not skipping if not keep_available_modules: diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py index 3a5cbecb55..6bec64764c 100644 --- a/easybuild/tools/config.py +++ b/easybuild/tools/config.py @@ -278,6 +278,7 @@ def mk_full_default_path(name, prefix=DEFAULT_PREFIX): 'debug', 'debug_lmod', 'dump_autopep8', + 'dump_env_script', 'enforce_checksums', 'experimental', 'extended_dry_run', diff --git a/test/framework/options.py b/test/framework/options.py index b2f3da43c1..29b3b3a4d6 100644 --- a/test/framework/options.py +++ b/test/framework/options.py @@ -5249,6 +5249,45 @@ def test_dump_env_script(self): ]) self.assertEqual(out.strip(), expected_out) + def test_dump_env_script_existing_module(self): + toy_ec = 'toy-0.0.eb' + + os.chdir(self.test_prefix) + self._run_mock_eb([toy_ec, '--force'], do_build=True) + env_script = os.path.join(self.test_prefix, os.path.splitext(toy_ec)[0] + '.env') + test_module = os.path.join(self.test_installpath, 'modules', 'all', 'toy', '0.0') + if get_module_syntax() == 'Lua': + test_module += '.lua' + self.assertExists(test_module) + self.assertNotExists(env_script) + + args = [toy_ec, '--dump-env'] + os.chdir(self.test_prefix) + self._run_mock_eb(args, do_build=True, raise_error=True) + self.assertExists(env_script) + self.assertExists(test_module) + module_content = read_file(test_module) + env_file_content = read_file(env_script) + + error_msg = (r"Script\(s\) already exists, not overwriting them \(unless --force is used\): " + + os.path.basename(env_script)) + os.chdir(self.test_prefix) + self.assertErrorRegex(EasyBuildError, error_msg, self._run_mock_eb, args, do_build=True, raise_error=True) + self.assertExists(env_script) + self.assertExists(test_module) + # Unchanged module and env file + self.assertEqual(read_file(test_module), module_content) + self.assertEqual(read_file(env_script), env_file_content) + + args.append('--force') + os.chdir(self.test_prefix) + self._run_mock_eb(args, do_build=True, raise_error=True) + self.assertExists(env_script) + self.assertExists(test_module) + # Unchanged module and env file + self.assertEqual(read_file(test_module), module_content) + self.assertEqual(read_file(env_script), env_file_content) + def test_stop(self): """Test use of --stop.""" args = ['toy-0.0.eb', '--force', '--stop=configure'] From 45ad2280b76700362669c5726fb3c069de3bedc0 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Thu, 18 Apr 2024 10:47:35 +0200 Subject: [PATCH 08/37] Remove some stray code --- test/framework/easyblock.py | 2 +- test/framework/options.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/framework/easyblock.py b/test/framework/easyblock.py index e6b54e0bc8..8ea7bf43fe 100644 --- a/test/framework/easyblock.py +++ b/test/framework/easyblock.py @@ -1685,7 +1685,7 @@ def test_fetch_patches(self): self.assertEqual(eb.patches[1]['level'], 4) self.assertEqual(eb.patches[2]['name'], toy_patch) self.assertEqual(eb.patches[2]['sourcepath'], 'foobar') - self.assertEqual(eb.patches[3]['name'], 'toy-0.0.tar.gz'), + self.assertEqual(eb.patches[3]['name'], 'toy-0.0.tar.gz') self.assertEqual(eb.patches[3]['copy'], 'some/path') self.assertEqual(eb.patches[4]['name'], toy_patch) self.assertEqual(eb.patches[4]['level'], 0) diff --git a/test/framework/options.py b/test/framework/options.py index 29b3b3a4d6..3deaf8cf9c 100644 --- a/test/framework/options.py +++ b/test/framework/options.py @@ -4522,7 +4522,7 @@ def test_github_new_update_pr(self): res = [d for d in res if os.path.basename(d) != os.path.basename(git_working_dir)] if len(res) == 1: unstaged_file_full = os.path.join(res[0], unstaged_file) - self.assertNotExists(unstaged_file_full), "%s not found in %s" % (unstaged_file, res[0]) + self.assertNotExists(unstaged_file_full) else: self.fail("Found copy of easybuild-easyconfigs working copy") From 16367240bb95c65b24152539fefa5095404f03f9 Mon Sep 17 00:00:00 2001 From: Bart Oldeman Date: Mon, 22 Apr 2024 17:03:46 +0000 Subject: [PATCH 09/37] `get_software_libdir`: return dir if it's only 1 with libs There are situations e.g. with recent Gentoo where all 64-bit libraries are under `lib64`, but `lib` is seperate and used for other files and directories (e.g. `lib/python`), and for 32-bit libraries. `get_software_libdir` would fail for such. To avoid this: if `only_one` is `True` (default), *and* no specific file (`fs`) is specified (also default), *and* `lib` and `lib64` both exist and are separate, and only one of them has libraries (shared or static), it returns that one directory. --- easybuild/tools/modules.py | 10 ++++++++++ test/framework/modules.py | 7 ++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/easybuild/tools/modules.py b/easybuild/tools/modules.py index fc21a564b9..a24ddaa369 100644 --- a/easybuild/tools/modules.py +++ b/easybuild/tools/modules.py @@ -37,6 +37,7 @@ * Jens Timmerman (Ghent University) * David Brown (Pacific Northwest National Laboratory) """ +import glob import os import re import shlex @@ -52,6 +53,7 @@ from easybuild.tools.module_naming_scheme.mns import DEVEL_MODULE_SUFFIX from easybuild.tools.py2vs3 import subprocess_popen_text from easybuild.tools.run import run_cmd +from easybuild.tools.systemtools import get_shared_lib_ext from easybuild.tools.utilities import get_subclasses, nub # software root/version environment variable name prefixes @@ -1671,6 +1673,7 @@ def get_software_libdir(name, only_one=True, fs=None): Returns the library subdirectory, relative to software root. It fails if multiple library subdirs are found, unless only_one is False which yields a list of all library subdirs. + If only_one is True and fs is None, select the one subdirectory with shared or static libraries, if possible. :param name: name of the software package :param only_one: indicates whether only one lib path is expected to be found @@ -1703,6 +1706,13 @@ def get_software_libdir(name, only_one=True, fs=None): if len(res) == 1: res = res[0] else: + if fs is None: + # check if only one (exactly) has libraries + # this is needed for software with library archives in lib64 but other files/directories in lib + lib_glob = ["*.%s" % ext for ext in ["a", get_shared_lib_ext()]] + haslibs = [any(glob.glob(os.path.join(root, subdir, f)) for f in lib_glob) for subdir in res] + if haslibs[0] != haslibs[1]: + return res[int(not haslibs[0])] raise EasyBuildError("Multiple library subdirectories found for %s in %s: %s", name, root, ', '.join(res)) return res diff --git a/test/framework/modules.py b/test/framework/modules.py index 82d03f6440..bb11802db7 100644 --- a/test/framework/modules.py +++ b/test/framework/modules.py @@ -674,10 +674,15 @@ def test_get_software_root_version_libdir(self): os.environ.pop('EBROOT%s' % env_var_name) os.environ.pop('EBVERSION%s' % env_var_name) - # check expected result of get_software_libdir with multiple lib subdirs + # if only 'lib' has a library archive, use it root = os.path.join(tmpdir, name) mkdir(os.path.join(root, 'lib64')) os.environ['EBROOT%s' % env_var_name] = root + write_file(os.path.join(root, 'lib', 'foo.a'), 'foo') + self.assertEqual(get_software_libdir(name), 'lib') + + # check expected result of get_software_libdir with multiple lib subdirs + remove_file(os.path.join(root, 'lib', 'foo.a')) self.assertErrorRegex(EasyBuildError, "Multiple library subdirectories found.*", get_software_libdir, name) self.assertEqual(get_software_libdir(name, only_one=False), ['lib', 'lib64']) From ff82fd6602d5f9f8ef637f052a23a1fa649fc1f0 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Sat, 27 Apr 2024 13:44:29 +0200 Subject: [PATCH 10/37] Simplify easyblock.py - Remove `pass` statements where not required - Enhance docstrings - Use `.items` when values of dict is used (faster, clearer) - `basename` instead of `split()[-1]` --- easybuild/framework/easyblock.py | 41 +++++++++++++------------------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 33110c61dd..1f808c58e4 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -1365,7 +1365,7 @@ def make_module_dep(self, unload_info=None): multi_dep_mod_names[dep['name']].append(dep['short_mod_name']) multi_dep_load_defaults = [] - for depname, depmods in sorted(multi_dep_mod_names.items()): + for _, depmods in sorted(multi_dep_mod_names.items()): stmt = self.module_generator.load_module(depmods[0], multi_dep_mods=depmods, recursive_unload=recursive_unload, depends_on=depends_on) @@ -1769,10 +1769,7 @@ def make_extension_string(self, name_version_sep='-', ext_sep=', ', sort=True): return ext_sep.join(exts_list) def prepare_for_extensions(self): - """ - Also do this before (eg to set the template) - """ - pass + """Ran before installing extensions (eg to set templates)""" def skip_extensions(self): """ @@ -2198,9 +2195,9 @@ def handle_iterate_opts(self): self.log.info("Current iteration index: %s", self.iter_idx) # pop first element from all iterative easyconfig parameters as next value to use - for opt in self.iter_opts: - if len(self.iter_opts[opt]) > self.iter_idx: - self.cfg[opt] = self.iter_opts[opt][self.iter_idx] + for opt, value in self.iter_opts.items(): + if len(value) > self.iter_idx: + self.cfg[opt] = value[self.iter_idx] else: self.cfg[opt] = '' # empty list => empty option as next value self.log.debug("Next value for %s: %s" % (opt, str(self.cfg[opt]))) @@ -2212,12 +2209,12 @@ def post_iter_step(self): """Restore options that were iterated over""" # disable templating, since we're messing about with values in self.cfg with self.cfg.disable_templating(): - for opt in self.iter_opts: - self.cfg[opt] = self.iter_opts[opt] + for opt, value in self.iter_opts.items(): + self.cfg[opt] = value # also need to take into account extensions, since those were iterated over as well for ext in self.ext_instances: - ext.cfg[opt] = self.iter_opts[opt] + ext.cfg[opt] = value self.log.debug("Restored value of '%s' that was iterated over: %s", opt, self.cfg[opt]) @@ -2751,10 +2748,7 @@ def _test_step(self): self.report_test_failure(err) def stage_install_step(self): - """ - Install in a stage directory before actual installation. - """ - pass + """Install in a stage directory before actual installation.""" def install_step(self): """Install built software (abstract method).""" @@ -3248,7 +3242,7 @@ def sanity_check_linked_shared_libs(self, subdirs=None): required_libs.extend(self.cfg['required_linked_shared_libs']) # early return if there are no banned/required libraries - if not (banned_libs + required_libs): + if not banned_libs + required_libs: self.log.info("No banned/required libraries specified") return [] else: @@ -4463,7 +4457,7 @@ def copy_easyblocks_for_reprod(easyblock_instances, reprod_dir): else: easyblock_paths.add(easyblock_path) for easyblock_path in easyblock_paths: - easyblock_basedir, easyblock_filename = os.path.split(easyblock_path) + easyblock_filename = os.path.basename(easyblock_path) copy_file(easyblock_path, os.path.join(reprod_easyblock_dir, easyblock_filename)) _log.info("Dumped easyblock %s required for reproduction to %s", easyblock_filename, reprod_easyblock_dir) @@ -4594,10 +4588,7 @@ def build_easyconfigs(easyconfigs, output_dir, test_results): class StopException(Exception): - """ - StopException class definition. - """ - pass + """Exception thrown to stop running steps""" def inject_checksums_to_json(ecs, checksum_type): @@ -4645,14 +4636,14 @@ def inject_checksums_to_json(ecs, checksum_type): # actually inject new checksums or overwrite existing ones (if --force) existing_checksums = app.get_checksums_from_json(always_read=True) - for filename in checksums: + for filename, checksum in checksums.items(): if filename not in existing_checksums: - existing_checksums[filename] = checksums[filename] + existing_checksums[filename] = checksum # don't do anything if the checksum already exist and is the same - elif checksums[filename] != existing_checksums[filename]: + elif checksum != existing_checksums[filename]: if build_option('force'): print_warning("Found existing checksums for %s, overwriting them (due to --force)..." % ec_fn) - existing_checksums[filename] = checksums[filename] + existing_checksums[filename] = checksum else: raise EasyBuildError("Found existing checksum for %s, use --force to overwrite them" % filename) From 27a6e93bc23b2f5f4379cb57ceca4ce669824816 Mon Sep 17 00:00:00 2001 From: Simon Branford <4967+branfosj@users.noreply.github.com> Date: Sat, 27 Apr 2024 16:20:42 +0200 Subject: [PATCH 11/37] check easyblocks and framework major are the same --- easybuild/main.py | 6 ++++++ easybuild/tools/version.py | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/easybuild/main.py b/easybuild/main.py index 42bd83dff5..0f89335a98 100644 --- a/easybuild/main.py +++ b/easybuild/main.py @@ -83,6 +83,7 @@ from easybuild.tools.repository.repository import init_repository from easybuild.tools.systemtools import check_easybuild_deps from easybuild.tools.testing import create_test_report, overall_test_report, regtest, session_state +from easybuild.tools.version import FRAMEWORK_VERSION, EASYBLOCKS_VERSION, different_major_versions _log = None @@ -618,6 +619,11 @@ def main(args=None, logfile=None, do_build=None, testing=False, modtool=None, pr (build_specs, _log, logfile, robot_path, search_query, eb_tmpdir, try_to_generate, from_pr_list, tweaked_ecs_paths) = cfg_settings + # compare running Framework and EasyBlocks versions + if different_major_versions(FRAMEWORK_VERSION, EASYBLOCKS_VERSION): + raise EasyBuildError(f"Framework ({FRAMEWORK_VERSION}) and EasyBlock ({EASYBLOCKS_VERSION}) major versions " + "are different.") + # load hook implementations (if any) hooks = load_hooks(options.hooks) diff --git a/easybuild/tools/version.py b/easybuild/tools/version.py index c01c8fc5c3..d9b2ce42a7 100644 --- a/easybuild/tools/version.py +++ b/easybuild/tools/version.py @@ -103,3 +103,16 @@ def this_is_easybuild(): msg = msg.encode('ascii') return msg + + +def different_major_versions(v1, v2): + """Compare major versions""" + # Deal with version instances being either strings or LooseVersion + if not isinstance(v1, LooseVersion): + v1 = LooseVersion(v1) + if not isinstance(v2, LooseVersion): + v2 = LooseVersion(v2) + + if v1.version[0] == v2.version[0]: + return False + return True From a91b1a4b0ed94a853e00a870a32b0dceaf5cc9e6 Mon Sep 17 00:00:00 2001 From: Simon Branford <4967+branfosj@users.noreply.github.com> Date: Sat, 27 Apr 2024 16:29:28 +0200 Subject: [PATCH 12/37] must support python < 3.6 --- easybuild/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/easybuild/main.py b/easybuild/main.py index 0f89335a98..c93f1dc212 100644 --- a/easybuild/main.py +++ b/easybuild/main.py @@ -621,8 +621,8 @@ def main(args=None, logfile=None, do_build=None, testing=False, modtool=None, pr # compare running Framework and EasyBlocks versions if different_major_versions(FRAMEWORK_VERSION, EASYBLOCKS_VERSION): - raise EasyBuildError(f"Framework ({FRAMEWORK_VERSION}) and EasyBlock ({EASYBLOCKS_VERSION}) major versions " - "are different.") + raise EasyBuildError("Framework (%s) and EasyBlock (%s) major versions are different." % (FRAMEWORK_VERSION, + EASYBLOCKS_VERSION)) # load hook implementations (if any) hooks = load_hooks(options.hooks) From 31514bb9a4fa26ad3148340411cfbaf05cf1717f Mon Sep 17 00:00:00 2001 From: Simon Branford <4967+branfosj@users.noreply.github.com> Date: Sat, 27 Apr 2024 16:36:42 +0200 Subject: [PATCH 13/37] deal with unknown blocks version --- easybuild/main.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/easybuild/main.py b/easybuild/main.py index c93f1dc212..94bf7b46a8 100644 --- a/easybuild/main.py +++ b/easybuild/main.py @@ -620,7 +620,9 @@ def main(args=None, logfile=None, do_build=None, testing=False, modtool=None, pr from_pr_list, tweaked_ecs_paths) = cfg_settings # compare running Framework and EasyBlocks versions - if different_major_versions(FRAMEWORK_VERSION, EASYBLOCKS_VERSION): + if 'UNKNOWN' in EASYBLOCKS_VERSION: + print_msg('Unable to determine EasyBlocks version, so we'll assume it is not different from Framework') + elif different_major_versions(FRAMEWORK_VERSION, EASYBLOCKS_VERSION): raise EasyBuildError("Framework (%s) and EasyBlock (%s) major versions are different." % (FRAMEWORK_VERSION, EASYBLOCKS_VERSION)) From 5202f07081133fbe55510664b885452eb8f7675e Mon Sep 17 00:00:00 2001 From: Simon Branford <4967+branfosj@users.noreply.github.com> Date: Sat, 27 Apr 2024 16:37:16 +0200 Subject: [PATCH 14/37] " and ' mixing --- easybuild/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/main.py b/easybuild/main.py index 94bf7b46a8..1e44609d4e 100644 --- a/easybuild/main.py +++ b/easybuild/main.py @@ -621,7 +621,7 @@ def main(args=None, logfile=None, do_build=None, testing=False, modtool=None, pr # compare running Framework and EasyBlocks versions if 'UNKNOWN' in EASYBLOCKS_VERSION: - print_msg('Unable to determine EasyBlocks version, so we'll assume it is not different from Framework') + print_msg("Unable to determine EasyBlocks version, so we'll assume it is not different from Framework") elif different_major_versions(FRAMEWORK_VERSION, EASYBLOCKS_VERSION): raise EasyBuildError("Framework (%s) and EasyBlock (%s) major versions are different." % (FRAMEWORK_VERSION, EASYBLOCKS_VERSION)) From 2724c8e28c0f05179639bd69f56d65420633981c Mon Sep 17 00:00:00 2001 From: Simon Branford <4967+branfosj@users.noreply.github.com> Date: Sat, 27 Apr 2024 16:45:38 +0200 Subject: [PATCH 15/37] fix --- easybuild/main.py | 4 ++-- easybuild/tools/version.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/easybuild/main.py b/easybuild/main.py index 1e44609d4e..6dda9dc893 100644 --- a/easybuild/main.py +++ b/easybuild/main.py @@ -83,7 +83,7 @@ from easybuild.tools.repository.repository import init_repository from easybuild.tools.systemtools import check_easybuild_deps from easybuild.tools.testing import create_test_report, overall_test_report, regtest, session_state -from easybuild.tools.version import FRAMEWORK_VERSION, EASYBLOCKS_VERSION, different_major_versions +from easybuild.tools.version import EASYBLOCKS_VERSION, FRAMEWORK_VERSION, UNKNOWN_VERSION, different_major_versions _log = None @@ -620,7 +620,7 @@ def main(args=None, logfile=None, do_build=None, testing=False, modtool=None, pr from_pr_list, tweaked_ecs_paths) = cfg_settings # compare running Framework and EasyBlocks versions - if 'UNKNOWN' in EASYBLOCKS_VERSION: + if EASYBLOCKS_VERSION == UNKNOWN_VERSION: print_msg("Unable to determine EasyBlocks version, so we'll assume it is not different from Framework") elif different_major_versions(FRAMEWORK_VERSION, EASYBLOCKS_VERSION): raise EasyBuildError("Framework (%s) and EasyBlock (%s) major versions are different." % (FRAMEWORK_VERSION, diff --git a/easybuild/tools/version.py b/easybuild/tools/version.py index d9b2ce42a7..63275b69ff 100644 --- a/easybuild/tools/version.py +++ b/easybuild/tools/version.py @@ -47,6 +47,7 @@ # This causes problems further up the dependency chain... VERSION = LooseVersion('4.9.2.dev0') UNKNOWN = 'UNKNOWN' +UNKNOWN_VERSION = '0.0.UNKNOWN.EASYBLOCKS' def get_git_revision(): @@ -87,7 +88,7 @@ def get_git_revision(): try: from easybuild.easyblocks import VERBOSE_VERSION as EASYBLOCKS_VERSION except Exception: - EASYBLOCKS_VERSION = '0.0.UNKNOWN.EASYBLOCKS' # make sure it is smaller then anything + EASYBLOCKS_VERSION = UNKNOWN_VERSION # make sure it is smaller then anything def this_is_easybuild(): From 0a5c99f04d3d448974825e1d88f443c496a181e8 Mon Sep 17 00:00:00 2001 From: Simon Branford Date: Tue, 30 Apr 2024 19:39:43 +0100 Subject: [PATCH 16/37] address review --- easybuild/main.py | 3 ++- easybuild/tools/version.py | 8 +++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/easybuild/main.py b/easybuild/main.py index 6dda9dc893..4ea569bbd4 100644 --- a/easybuild/main.py +++ b/easybuild/main.py @@ -60,6 +60,7 @@ from easybuild.framework.easyconfig.tools import det_easyconfig_paths, dump_env_script, get_paths_for from easybuild.framework.easyconfig.tools import parse_easyconfigs, review_pr, run_contrib_checks, skip_available from easybuild.framework.easyconfig.tweak import obtain_ec_for, tweak +from easybuild.tools.build_log import print_warning from easybuild.tools.config import find_last_log, get_repository, get_repositorypath, build_option from easybuild.tools.containers.common import containerize from easybuild.tools.docs import list_software @@ -621,7 +622,7 @@ def main(args=None, logfile=None, do_build=None, testing=False, modtool=None, pr # compare running Framework and EasyBlocks versions if EASYBLOCKS_VERSION == UNKNOWN_VERSION: - print_msg("Unable to determine EasyBlocks version, so we'll assume it is not different from Framework") + print_warning("Unable to determine EasyBlocks version, so we'll assume it is not different from Framework") elif different_major_versions(FRAMEWORK_VERSION, EASYBLOCKS_VERSION): raise EasyBuildError("Framework (%s) and EasyBlock (%s) major versions are different." % (FRAMEWORK_VERSION, EASYBLOCKS_VERSION)) diff --git a/easybuild/tools/version.py b/easybuild/tools/version.py index 63275b69ff..3b0c45d2bf 100644 --- a/easybuild/tools/version.py +++ b/easybuild/tools/version.py @@ -109,11 +109,9 @@ def this_is_easybuild(): def different_major_versions(v1, v2): """Compare major versions""" # Deal with version instances being either strings or LooseVersion - if not isinstance(v1, LooseVersion): + if isinstance(v1, str): v1 = LooseVersion(v1) - if not isinstance(v2, LooseVersion): + if isinstance(v2, str): v2 = LooseVersion(v2) - if v1.version[0] == v2.version[0]: - return False - return True + return v1.version[0] != v2.version[0] From d8be5ca9cde44780bae46f76a791026ef2c8161b Mon Sep 17 00:00:00 2001 From: Simon Branford Date: Tue, 30 Apr 2024 19:40:48 +0100 Subject: [PATCH 17/37] remove unneeded import --- easybuild/main.py | 1 - 1 file changed, 1 deletion(-) diff --git a/easybuild/main.py b/easybuild/main.py index 4ea569bbd4..00b2899227 100644 --- a/easybuild/main.py +++ b/easybuild/main.py @@ -60,7 +60,6 @@ from easybuild.framework.easyconfig.tools import det_easyconfig_paths, dump_env_script, get_paths_for from easybuild.framework.easyconfig.tools import parse_easyconfigs, review_pr, run_contrib_checks, skip_available from easybuild.framework.easyconfig.tweak import obtain_ec_for, tweak -from easybuild.tools.build_log import print_warning from easybuild.tools.config import find_last_log, get_repository, get_repositorypath, build_option from easybuild.tools.containers.common import containerize from easybuild.tools.docs import list_software From 45a05454f64ec1963a63ce96a2c93110a12e6419 Mon Sep 17 00:00:00 2001 From: Simon Branford Date: Fri, 3 May 2024 09:38:52 +0100 Subject: [PATCH 18/37] paths --- easybuild/tools/github.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/easybuild/tools/github.py b/easybuild/tools/github.py index ae9b78b5ed..9a70109c00 100644 --- a/easybuild/tools/github.py +++ b/easybuild/tools/github.py @@ -587,6 +587,11 @@ def fetch_files_from_pr(pr, path=None, github_user=None, github_account=None, gi else: raise EasyBuildError("Couldn't find path to patched file %s", full_path) + if github_repo == GITHUB_EASYCONFIGS_REPO: + print('get ecs version', os.path.join(final_path, 'setup.py')) + elif github_repo == GITHUB_EASYBLOCKS_REPO: + print('get blocks version', sys.path, final_path) + return files From ee2719840d17b7a7c0f198f6ec6abe2d55156abf Mon Sep 17 00:00:00 2001 From: Simon Branford Date: Sun, 5 May 2024 16:14:18 +0100 Subject: [PATCH 19/37] install easyblocks for framework tests --- .github/workflows/unit_tests.yml | 12 ++++++++++++ .github/workflows/unit_tests_python2.yml | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 07ac74fe1a..d2c2dc3cbc 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -142,6 +142,18 @@ jobs: export PREFIX=/tmp/$USER/$GITHUB_SHA pip install --prefix $PREFIX dist/easybuild[-_]framework*tar.gz + - name: install easyblocks + run: | + cd $HOME + # first determine which branch of easybuild-easyblocks repo to install + BRANCH=develop + if [ "x$GITHUB_BASE_REF" = 'xmain' ]; then BRANCH=main; fi + if [ "x$GITHUB_BASE_REF" = 'x4.x' ]; then BRANCH=4.x; fi + echo "Using easybuild-easyblocks branch $BRANCH (\$GITHUB_BASE_REF $GITHUB_BASE_REF)" + git clone -b $BRANCH --depth 10 --single-branch https://github.com/easybuilders/easybuild-easyblocks.git + cd easybuild-easyblocks; git log -n 1; cd - + pip install $PWD/easybuild-easyblocks + - name: run test suite env: EB_VERBOSE: 1 diff --git a/.github/workflows/unit_tests_python2.yml b/.github/workflows/unit_tests_python2.yml index 1b921ee83c..252d5f7e6f 100644 --- a/.github/workflows/unit_tests_python2.yml +++ b/.github/workflows/unit_tests_python2.yml @@ -57,6 +57,18 @@ jobs: export PREFIX=/tmp/$USER/$GITHUB_SHA python2 -m pip install --prefix $PREFIX dist/easybuild-framework*tar.gz + - name: install easyblocks + run: | + cd $HOME + # first determine which branch of easybuild-easyblocks repo to install + BRANCH=develop + if [ "x$GITHUB_BASE_REF" = 'xmain' ]; then BRANCH=main; fi + if [ "x$GITHUB_BASE_REF" = 'x4.x' ]; then BRANCH=4.x; fi + echo "Using easybuild-easyblocks branch $BRANCH (\$GITHUB_BASE_REF $GITHUB_BASE_REF)" + git clone -b $BRANCH --depth 10 --single-branch https://github.com/easybuilders/easybuild-easyblocks.git + cd easybuild-easyblocks; git log -n 1; cd - + pip install $PWD/easybuild-easyblocks + - name: run test suite run: | # run tests *outside* of checked out easybuild-framework directory, From 9e9390458b35cff6a25a5036ab259425acf1e14c Mon Sep 17 00:00:00 2001 From: Simon Branford Date: Mon, 6 May 2024 13:10:36 +0100 Subject: [PATCH 20/37] log a warning, without adding a print statement when no easyblocks version determined --- .github/workflows/unit_tests.yml | 12 ------------ .github/workflows/unit_tests_python2.yml | 12 ------------ easybuild/main.py | 4 +++- 3 files changed, 3 insertions(+), 25 deletions(-) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index d2c2dc3cbc..07ac74fe1a 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -142,18 +142,6 @@ jobs: export PREFIX=/tmp/$USER/$GITHUB_SHA pip install --prefix $PREFIX dist/easybuild[-_]framework*tar.gz - - name: install easyblocks - run: | - cd $HOME - # first determine which branch of easybuild-easyblocks repo to install - BRANCH=develop - if [ "x$GITHUB_BASE_REF" = 'xmain' ]; then BRANCH=main; fi - if [ "x$GITHUB_BASE_REF" = 'x4.x' ]; then BRANCH=4.x; fi - echo "Using easybuild-easyblocks branch $BRANCH (\$GITHUB_BASE_REF $GITHUB_BASE_REF)" - git clone -b $BRANCH --depth 10 --single-branch https://github.com/easybuilders/easybuild-easyblocks.git - cd easybuild-easyblocks; git log -n 1; cd - - pip install $PWD/easybuild-easyblocks - - name: run test suite env: EB_VERBOSE: 1 diff --git a/.github/workflows/unit_tests_python2.yml b/.github/workflows/unit_tests_python2.yml index 252d5f7e6f..1b921ee83c 100644 --- a/.github/workflows/unit_tests_python2.yml +++ b/.github/workflows/unit_tests_python2.yml @@ -57,18 +57,6 @@ jobs: export PREFIX=/tmp/$USER/$GITHUB_SHA python2 -m pip install --prefix $PREFIX dist/easybuild-framework*tar.gz - - name: install easyblocks - run: | - cd $HOME - # first determine which branch of easybuild-easyblocks repo to install - BRANCH=develop - if [ "x$GITHUB_BASE_REF" = 'xmain' ]; then BRANCH=main; fi - if [ "x$GITHUB_BASE_REF" = 'x4.x' ]; then BRANCH=4.x; fi - echo "Using easybuild-easyblocks branch $BRANCH (\$GITHUB_BASE_REF $GITHUB_BASE_REF)" - git clone -b $BRANCH --depth 10 --single-branch https://github.com/easybuilders/easybuild-easyblocks.git - cd easybuild-easyblocks; git log -n 1; cd - - pip install $PWD/easybuild-easyblocks - - name: run test suite run: | # run tests *outside* of checked out easybuild-framework directory, diff --git a/easybuild/main.py b/easybuild/main.py index 00b2899227..00bc362666 100644 --- a/easybuild/main.py +++ b/easybuild/main.py @@ -621,7 +621,9 @@ def main(args=None, logfile=None, do_build=None, testing=False, modtool=None, pr # compare running Framework and EasyBlocks versions if EASYBLOCKS_VERSION == UNKNOWN_VERSION: - print_warning("Unable to determine EasyBlocks version, so we'll assume it is not different from Framework") + # most likely reason is running framework unit tests with no easyblocks installation + # so log a warning, to avoid test related issues + _log.warning("Unable to determine EasyBlocks version, so we'll assume it is not different from Framework") elif different_major_versions(FRAMEWORK_VERSION, EASYBLOCKS_VERSION): raise EasyBuildError("Framework (%s) and EasyBlock (%s) major versions are different." % (FRAMEWORK_VERSION, EASYBLOCKS_VERSION)) From 1ec53a50c6ab92e671c51629de87d63218b6f3b7 Mon Sep 17 00:00:00 2001 From: Simon Branford Date: Mon, 6 May 2024 13:59:27 +0100 Subject: [PATCH 21/37] version check on from pr --- easybuild/tools/github.py | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/easybuild/tools/github.py b/easybuild/tools/github.py index 9a70109c00..5691be973c 100644 --- a/easybuild/tools/github.py +++ b/easybuild/tools/github.py @@ -60,6 +60,7 @@ from easybuild.tools.py2vs3 import HTTPError, URLError, ascii_letters, urlopen from easybuild.tools.systemtools import UNKNOWN, get_tool_version from easybuild.tools.utilities import nub, only_if_module_is_available +from easybuild.tools.version import FRAMEWORK_VERSION, different_major_versions _log = fancylogger.getLogger('github', fname=False) @@ -588,13 +589,38 @@ def fetch_files_from_pr(pr, path=None, github_user=None, github_account=None, gi raise EasyBuildError("Couldn't find path to patched file %s", full_path) if github_repo == GITHUB_EASYCONFIGS_REPO: - print('get ecs version', os.path.join(final_path, 'setup.py')) + ver = _get_version_for_repo(os.path.join(final_path, 'setup.py')) elif github_repo == GITHUB_EASYBLOCKS_REPO: - print('get blocks version', sys.path, final_path) + ver = _get_version_for_repo(os.path.join(final_path, 'easybuild', 'easyblocks', '__init__.py')) + + if different_major_versions(FRAMEWORK_VERSION, ver): + raise EasyBuildError("Framework (%s) is a different major version than PR target (%s)." % (FRAMEWORK_VERSION, + ver)) return files +def _get_version_for_repo(filename): + """Extract version from filename.""" + _log.debug("Extract version from %s" % filename) + + try: + ver_line = "" + with open(filename) as f: + for line in f.readlines(): + if line.startswith("VERSION "): + ver_line = line + break + + # version can be a string or LooseVersion + res = re.search(r"""^VERSION = .*['"](.*)['"].?$""", ver_line) + + _log.debug("PR target version is %s" % res.group(1)) + return res.group(1) + except: + raise EasyBuildError("Couldn't determine version of PR from %s" % filename) + + def fetch_easyblocks_from_pr(pr, path=None, github_user=None): """Fetch patched easyblocks for a particular PR.""" return fetch_files_from_pr(pr, path, github_user, github_repo=GITHUB_EASYBLOCKS_REPO) From 0b18d9607401dc8a80a222fc12cc883bb99d18a9 Mon Sep 17 00:00:00 2001 From: Simon Branford Date: Mon, 6 May 2024 14:01:44 +0100 Subject: [PATCH 22/37] flake8 --- easybuild/tools/github.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/tools/github.py b/easybuild/tools/github.py index 5691be973c..0b5a2ff171 100644 --- a/easybuild/tools/github.py +++ b/easybuild/tools/github.py @@ -617,7 +617,7 @@ def _get_version_for_repo(filename): _log.debug("PR target version is %s" % res.group(1)) return res.group(1) - except: + except Exception: raise EasyBuildError("Couldn't determine version of PR from %s" % filename) From 7553dcdd4dc1c21923356043daadf684282924b9 Mon Sep 17 00:00:00 2001 From: Simon Branford <4967+branfosj@users.noreply.github.com> Date: Fri, 10 May 2024 18:29:44 +0100 Subject: [PATCH 23/37] do not run unit tests on Python 3.5 --- .github/workflows/eb_command.yml | 2 +- .github/workflows/linting.yml | 2 +- .github/workflows/unit_tests.yml | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/eb_command.yml b/.github/workflows/eb_command.yml index ec4907d28c..220258cd9f 100644 --- a/.github/workflows/eb_command.yml +++ b/.github/workflows/eb_command.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - python: [3.5, 3.6, 3.7, 3.8, 3.9, '3.10', '3.11'] + python: [3.6, 3.7, 3.8, 3.9, '3.10', '3.11'] fail-fast: false steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index d722761208..e7c25b22d7 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - python-version: [3.5, 3.6, 3.7, 3.8, 3.9, '3.10', '3.11'] + python-version: [3.6, 3.7, 3.8, 3.9, '3.10', '3.11'] steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 07ac74fe1a..c9f42891ec 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -37,8 +37,6 @@ jobs: lc_all: [""] include: # Test different Python 3 versions with Lmod 8.x - - python: 3.5 - modules_tool: ${{needs.setup.outputs.lmod8}} - python: 3.7 modules_tool: ${{needs.setup.outputs.lmod8}} - python: 3.8 From 31b1846b100ee217765d69a7abdc1826fe0fbe1a Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Tue, 14 May 2024 15:29:08 +0200 Subject: [PATCH 24/37] Add tests for (un)escaping when resolving templates --- test/framework/easyconfig.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/framework/easyconfig.py b/test/framework/easyconfig.py index 45e2852221..c1bbe0cf8d 100644 --- a/test/framework/easyconfig.py +++ b/test/framework/easyconfig.py @@ -3654,6 +3654,23 @@ def test_resolve_template(self): # '%(name)' is not a correct template spec (missing trailing 's') self.assertEqual(resolve_template('%(name)', tmpl_dict), '%(name)') + # Correct (un)escaping + values = ( + ('10%', '10%'), + ('%of', '%of'), + ('10%of', '10%of'), + ('%s', '%s'), + ('%%(name)s', '%(name)s'), + ('%%%(name)s', '%FooBar'), + ('%%%%(name)s', '%%(name)s'), + ) + for value, expected in values: + self.assertEqual(resolve_template(value, tmpl_dict), expected) + # Templates are resolved + value += ' %(name)s' + expected += ' FooBar' + self.assertEqual(resolve_template(value, tmpl_dict), expected) + def test_det_subtoolchain_version(self): """Test det_subtoolchain_version function""" _, all_tc_classes = search_toolchain('') From 7323bae811b9aa508049e92bc1456a030c1e38ee Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Tue, 14 May 2024 15:37:22 +0200 Subject: [PATCH 25/37] Fix resolved values in case of failure When the value cannot be resolved usually the original value is returned. However if it contains `%` signs there will be an escape step and that escaped value is returned. That makes it impossible to have values which can only later be resolved, like `cd %(startdir)s` in extensions which would become `cd %%(startdir)s` on the first resolve-attempt. --- easybuild/framework/easyconfig/easyconfig.py | 2 ++ test/framework/easyconfig.py | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/easybuild/framework/easyconfig/easyconfig.py b/easybuild/framework/easyconfig/easyconfig.py index 73182da7ac..2b6aaa751a 100644 --- a/easybuild/framework/easyconfig/easyconfig.py +++ b/easybuild/framework/easyconfig/easyconfig.py @@ -2026,12 +2026,14 @@ def resolve_template(value, tmpl_dict): # '%(name)s' -> '%(name)s' # '%%(name)s' -> '%%(name)s' if '%' in value: + orig_value = value value = re.sub(re.compile(r'(%)(?!%*\(\w+\)s)'), r'\1\1', value) try: value = value % tmpl_dict except KeyError: _log.warning("Unable to resolve template value %s with dict %s", value, tmpl_dict) + value = orig_value # Undo "%"-escaping else: # this block deals with references to objects and returns other references # for reading this is ok, but for self['x'] = {} diff --git a/test/framework/easyconfig.py b/test/framework/easyconfig.py index c1bbe0cf8d..4b63dc605b 100644 --- a/test/framework/easyconfig.py +++ b/test/framework/easyconfig.py @@ -3663,6 +3663,9 @@ def test_resolve_template(self): ('%%(name)s', '%(name)s'), ('%%%(name)s', '%FooBar'), ('%%%%(name)s', '%%(name)s'), + # It doesn't matter what is resolved + ('%%(invalid)s', '%(invalid)s'), + ('%%%%(invalid)s', '%%(invalid)s'), ) for value, expected in values: self.assertEqual(resolve_template(value, tmpl_dict), expected) @@ -3671,6 +3674,10 @@ def test_resolve_template(self): expected += ' FooBar' self.assertEqual(resolve_template(value, tmpl_dict), expected) + # On unknown values the value is returned unchanged + for value in ('%(invalid)s', '%(name)s %(invalid)s', '%%%(invalid)s', '% %(invalid)s', '%s %(invalid)s'): + self.assertEqual(resolve_template(value, tmpl_dict), value) + def test_det_subtoolchain_version(self): """Test det_subtoolchain_version function""" _, all_tc_classes = search_toolchain('') From 0319c02fa996322fa6ef9c6cb8397dedb5f431b7 Mon Sep 17 00:00:00 2001 From: Samuel Moors Date: Mon, 20 May 2024 15:44:05 +0200 Subject: [PATCH 26/37] add support for parameter module_only --- easybuild/framework/easyblock.py | 10 +++++----- easybuild/framework/easyconfig/default.py | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 33110c61dd..9dac204fdb 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -1068,7 +1068,7 @@ def make_builddir(self): self.log.info("Overriding 'cleanupoldinstall' (to False), 'cleanupoldbuild' (to True) " "and 'keeppreviousinstall' because we're building in the installation directory.") # force cleanup before installation - if build_option('module_only'): + if build_option('module_only') or self.cfg['module_only']: self.log.debug("Disabling cleanupoldbuild because we run as module-only") self.cfg['cleanupoldbuild'] = False else: @@ -1139,7 +1139,7 @@ def make_dir(self, dir_name, clean, dontcreateinstalldir=False): if self.cfg['keeppreviousinstall']: self.log.info("Keeping old directory %s (hopefully you know what you are doing)", dir_name) return - elif build_option('module_only'): + elif build_option('module_only') or self.cfg['module_only']: self.log.info("Not touching existing directory %s in module-only mode...", dir_name) elif clean: remove_dir(dir_name) @@ -2114,7 +2114,7 @@ def guess_start_dir(self): start_dir = '' # do not use the specified 'start_dir' when running as --module-only as # the directory will not exist (extract_step is skipped) - if self.start_dir and not build_option('module_only'): + if self.start_dir and not build_option('module_only') and not self.cfg['module_only']: start_dir = self.start_dir if not os.path.isabs(start_dir): @@ -3795,7 +3795,7 @@ def make_module_step(self, fake=False): try: self.make_devel_module() except EasyBuildError as error: - if build_option('module_only'): + if build_option('module_only') or self.cfg['module_only']: self.log.info("Using --module-only so can recover from error: %s", error) else: raise error @@ -3903,7 +3903,7 @@ def skip_step(self, step, skippable): """Dedice whether or not to skip the specified step.""" skip = False force = build_option('force') - module_only = build_option('module_only') + module_only = build_option('module_only') or self.cfg['module_only'] sanity_check_only = build_option('sanity_check_only') skip_extensions = build_option('skip_extensions') skip_test_step = build_option('skip_test_step') diff --git a/easybuild/framework/easyconfig/default.py b/easybuild/framework/easyconfig/default.py index 965e6db037..ce11d0457b 100644 --- a/easybuild/framework/easyconfig/default.py +++ b/easybuild/framework/easyconfig/default.py @@ -109,6 +109,7 @@ 'hidden': [False, "Install module file as 'hidden' by prefixing its version with '.'", BUILD], 'installopts': ['', 'Extra options for installation', BUILD], 'maxparallel': [None, 'Max degree of parallelism', BUILD], + 'module_only': [False, 'Only generate module file', BUILD], 'parallel': [None, ('Degree of parallelism for e.g. make (default: based on the number of ' 'cores, active cpuset and restrictions in ulimit)'), BUILD], 'patches': [[], "List of patches to apply", BUILD], From 64cde9e811ceca718fef21d58c11163abdfc81dd Mon Sep 17 00:00:00 2001 From: Samuel Moors Date: Thu, 23 May 2024 11:28:48 +0200 Subject: [PATCH 27/37] add test --- test/framework/options.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/framework/options.py b/test/framework/options.py index 3deaf8cf9c..38f4eea2e7 100644 --- a/test/framework/options.py +++ b/test/framework/options.py @@ -366,6 +366,16 @@ def test_skip(self): self.assertEqual(len(glob.glob(toy_mod_glob)), 1) + for toy_mod in glob.glob(toy_mod_glob): + remove_file(toy_mod) + + # check use of module_only parameter + test_ec_txt += "\nmodule_only = True\n" + write_file(test_ec, test_ec_txt) + self.eb_main(args, do_build=True, raise_error=True) + + self.assertEqual(len(glob.glob(toy_mod_glob)), 1) + def test_skip_test_step(self): """Test skipping testing the build (--skip-test-step).""" From 430189c2dfd39b5ea494b66b06ab6acfa347851d Mon Sep 17 00:00:00 2001 From: Samuel Moors Date: Tue, 28 May 2024 23:10:25 +0200 Subject: [PATCH 28/37] redifine args and test that no software was installed --- test/framework/options.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/test/framework/options.py b/test/framework/options.py index 38f4eea2e7..ba5e6602f4 100644 --- a/test/framework/options.py +++ b/test/framework/options.py @@ -366,16 +366,25 @@ def test_skip(self): self.assertEqual(len(glob.glob(toy_mod_glob)), 1) - for toy_mod in glob.glob(toy_mod_glob): - remove_file(toy_mod) - # check use of module_only parameter + remove_dir(os.path.join(self.test_installpath, 'modules', 'all', 'toy')) + remove_dir(os.path.join(self.test_installpath, 'software', 'toy', '0.0')) + args = [ + test_ec, + '--rebuild', + ] test_ec_txt += "\nmodule_only = True\n" write_file(test_ec, test_ec_txt) self.eb_main(args, do_build=True, raise_error=True) self.assertEqual(len(glob.glob(toy_mod_glob)), 1) + # check that no software was installed + installdir = os.path.join(self.test_installpath, 'software', 'toy', '0.0') + installdir_glob = glob.glob(os.path.join(installdir, '*')) + easybuild_dir = os.path.join(installdir, 'easybuild') + self.assertEqual(installdir_glob, [easybuild_dir]) + def test_skip_test_step(self): """Test skipping testing the build (--skip-test-step).""" From 4f7a4e44ce51baac570f7392ed3261c4036052a5 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 5 Jun 2024 14:00:49 +0200 Subject: [PATCH 29/37] improve error message on version mismatch when using --from-pr + rename UNKNOWN_VERSION to UNKNOWN_EASYBLOCKS_VERSION --- easybuild/main.py | 5 +++-- easybuild/tools/github.py | 4 ++-- easybuild/tools/version.py | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/easybuild/main.py b/easybuild/main.py index 00bc362666..451152f322 100644 --- a/easybuild/main.py +++ b/easybuild/main.py @@ -83,7 +83,8 @@ from easybuild.tools.repository.repository import init_repository from easybuild.tools.systemtools import check_easybuild_deps from easybuild.tools.testing import create_test_report, overall_test_report, regtest, session_state -from easybuild.tools.version import EASYBLOCKS_VERSION, FRAMEWORK_VERSION, UNKNOWN_VERSION, different_major_versions +from easybuild.tools.version import EASYBLOCKS_VERSION, FRAMEWORK_VERSION, UNKNOWN_EASYBLOCKS_VERSION +from easybuild.tools.version import different_major_versions _log = None @@ -620,7 +621,7 @@ def main(args=None, logfile=None, do_build=None, testing=False, modtool=None, pr from_pr_list, tweaked_ecs_paths) = cfg_settings # compare running Framework and EasyBlocks versions - if EASYBLOCKS_VERSION == UNKNOWN_VERSION: + if EASYBLOCKS_VERSION == UNKNOWN_EASYBLOCKS_VERSION: # most likely reason is running framework unit tests with no easyblocks installation # so log a warning, to avoid test related issues _log.warning("Unable to determine EasyBlocks version, so we'll assume it is not different from Framework") diff --git a/easybuild/tools/github.py b/easybuild/tools/github.py index 0b5a2ff171..6a8401d7ae 100644 --- a/easybuild/tools/github.py +++ b/easybuild/tools/github.py @@ -594,8 +594,8 @@ def fetch_files_from_pr(pr, path=None, github_user=None, github_account=None, gi ver = _get_version_for_repo(os.path.join(final_path, 'easybuild', 'easyblocks', '__init__.py')) if different_major_versions(FRAMEWORK_VERSION, ver): - raise EasyBuildError("Framework (%s) is a different major version than PR target (%s)." % (FRAMEWORK_VERSION, - ver)) + raise EasyBuildError("Framework (%s) is a different major version than used in %s/%s PR #%s (%s)", + FRAMEWORK_VERSION, github_account, github_repo, pr, ver) return files diff --git a/easybuild/tools/version.py b/easybuild/tools/version.py index 3b0c45d2bf..e29f6f0b29 100644 --- a/easybuild/tools/version.py +++ b/easybuild/tools/version.py @@ -47,7 +47,7 @@ # This causes problems further up the dependency chain... VERSION = LooseVersion('4.9.2.dev0') UNKNOWN = 'UNKNOWN' -UNKNOWN_VERSION = '0.0.UNKNOWN.EASYBLOCKS' +UNKNOWN_EASYBLOCKS_VERSION = '0.0.UNKNOWN.EASYBLOCKS' def get_git_revision(): @@ -88,7 +88,7 @@ def get_git_revision(): try: from easybuild.easyblocks import VERBOSE_VERSION as EASYBLOCKS_VERSION except Exception: - EASYBLOCKS_VERSION = UNKNOWN_VERSION # make sure it is smaller then anything + EASYBLOCKS_VERSION = UNKNOWN_EASYBLOCKS_VERSION # make sure it is smaller then anything def this_is_easybuild(): From 4bbba2613c7852c1a04c4f12f6d3af8738483395 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 6 Jun 2024 20:43:21 +0200 Subject: [PATCH 30/37] also consider $CRAY_PE_LIBSCI_PREFIX_DIR to determine installation prefix for cray-libsci (fixes #4536) --- easybuild/toolchains/linalg/libsci.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/easybuild/toolchains/linalg/libsci.py b/easybuild/toolchains/linalg/libsci.py index 07ea64ed82..6d7e93c16d 100644 --- a/easybuild/toolchains/linalg/libsci.py +++ b/easybuild/toolchains/linalg/libsci.py @@ -65,13 +65,20 @@ def _get_software_root(self, name, required=True): """Get install prefix for specified software name; special treatment for Cray modules.""" if name == 'cray-libsci': # Cray-provided LibSci module - env_var = 'CRAY_LIBSCI_PREFIX_DIR' - root = os.getenv(env_var, None) + root = None + # consider both $CRAY_LIBSCI_PREFIX_DIR and $CRAY_PE_LIBSCI_PREFIX_DIR, + # cfr. https://github.com/easybuilders/easybuild-framework/issues/4536 + env_vars = ('CRAY_LIBSCI_PREFIX_DIR', 'CRAY_PE_LIBSCI_PREFIX_DIR') + for env_var in env_vars: + root = os.getenv(env_var, None) + if root is not None: + self.log.debug("Obtained install prefix for %s via $%s: %s", name, env_var, root) + break + if root is None: if required: - raise EasyBuildError("Failed to determine install prefix for %s via $%s", name, env_var) - else: - self.log.debug("Obtained install prefix for %s via $%s: %s", name, env_var, root) + env_vars_str = ', '.join('$' + e for e in env_vars) + raise EasyBuildError("Failed to determine install prefix for %s via $%s", name, env_vars_str) else: root = super(LibSci, self)._get_software_root(name, required=required) From 94976eb7d443517ba802c6e969e881e7de7a8c4b Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 7 Jun 2024 12:32:57 +0200 Subject: [PATCH 31/37] symlink downloaded repo at specified commit when using --from-commit so easyconfigs for dependencies are found --- easybuild/tools/github.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/easybuild/tools/github.py b/easybuild/tools/github.py index ae9b78b5ed..4fd4486a1a 100644 --- a/easybuild/tools/github.py +++ b/easybuild/tools/github.py @@ -612,6 +612,13 @@ def fetch_files_from_commit(commit, files=None, path=None, github_account=None, if github_repo is None: github_repo = GITHUB_EASYCONFIGS_REPO + if github_repo == GITHUB_EASYCONFIGS_REPO: + easybuild_subdir = os.path.join('easybuild', 'easyconfigs') + elif github_repo == GITHUB_EASYBLOCKS_REPO: + easybuild_subdir = os.path.join('easybuild', 'easyblocks') + else: + raise EasyBuildError("Unknown repo: %s", github_repo) + if path is None: if github_repo == GITHUB_EASYCONFIGS_REPO: extra_ec_paths = build_option('extra_ec_paths') @@ -655,6 +662,12 @@ def fetch_files_from_commit(commit, files=None, path=None, github_account=None, else: raise EasyBuildError("Unknown repo: %s" % github_repo) + # symlink subdirectories of 'easybuild/easy{blocks,configs}' into path that gets added to robot search path + mkdir(path, parents=True) + dirpath = os.path.join(repo_commit, easybuild_subdir) + for subdir in os.listdir(dirpath): + symlink(os.path.join(dirpath, subdir), os.path.join(path, subdir)) + # copy specified files to directory where they're expected to be found file_paths = [] for file in files: From 76c511405eaad786cda798587fe086666e068687 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 7 Jun 2024 13:09:21 +0200 Subject: [PATCH 32/37] enhance test for --from-commit to also check whether easyconfigs required for dependencies are found + fix check in test_github_from_pr --- test/framework/options.py | 50 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/test/framework/options.py b/test/framework/options.py index 3deaf8cf9c..6263d8186f 100644 --- a/test/framework/options.py +++ b/test/framework/options.py @@ -1908,7 +1908,7 @@ def test_github_from_pr(self): # make sure that *only* these modules are listed, no others regex = re.compile(r"^ \* \[.\] .*/(?P.*) \(module: (?P.*)\)$", re.M) - self.assertTrue(sorted(regex.findall(outtxt)), sorted(modules)) + self.assertEqual(sorted(x[1] for x in regex.findall(outtxt)), sorted(x[1] for x in modules)) pr_tmpdir = os.path.join(tmpdir, r'eb-\S{6,8}', 'files_pr6424') regex = re.compile(r"Extended list of robot search paths with \['%s'\]:" % pr_tmpdir, re.M) @@ -1943,12 +1943,12 @@ def test_github_from_pr(self): # make sure that *only* these modules are listed, no others regex = re.compile(r"^ \* \[.\] .*/(?P.*) \(module: (?P.*)\)$", re.M) - self.assertTrue(sorted(regex.findall(outtxt)), sorted(modules)) + self.assertEqual(sorted(x[1] for x in regex.findall(outtxt)), sorted(x[1] for x in modules)) for pr in ('12150', '12366'): pr_tmpdir = os.path.join(tmpdir, r'eb-\S{6,8}', 'files_pr%s' % pr) regex = re.compile(r"Extended list of robot search paths with .*%s.*:" % pr_tmpdir, re.M) - self.assertTrue(regex.search(outtxt), "Found pattern %s in %s" % (regex.pattern, outtxt)) + self.assertTrue(regex.search(outtxt), "Found pattern '%s' in: %s" % (regex.pattern, outtxt)) except URLError as err: print("Ignoring URLError '%s' in test_from_pr" % err) @@ -2125,6 +2125,50 @@ def test_from_commit(self): print("Ignoring URLError '%s' in test_from_commit" % err) shutil.rmtree(tmpdir) + easyblock_template = '\n'.join([ + "from easybuild.framework.easyblock import EasyBlock", + "class %s(EasyBlock):", + " pass", + ]) + + # create fake custom easyblock for CMake that is required by easyconfig used in test below + easyblock_file = os.path.join(self.test_prefix, 'easyblocks', 'cmake.py') + write_file(easyblock_file, easyblock_template % 'EB_CMake') + + # also test with an easyconfig that requires additional easyconfigs to resolve dependencies, + # cfr. https://github.com/easybuilders/easybuild-framework/issues/4540; + # using commit that adds CMake-3.18.4.eb (which requires ncurses-6.2.eb), + # see https://github.com/easybuilders/easybuild-easyconfigs/pull/13156 + test_commit = '41eee3fe2e5102f52319481ca8dde16204dab590' + args = [ + '--from-commit=%s' % test_commit, + '--dry-run', + '--tmpdir=%s' % tmpdir, + '--include-easyblocks=' + os.path.join(self.test_prefix, 'easyblocks', '*.py'), + ] + try: + outtxt = self.eb_main(args, logfile=dummylogfn, raise_error=True) + modules = [ + (tmpdir, 'ncurses/6.2'), + (tmpdir, 'CMake/3.18.4'), + ] + for path_prefix, module in modules: + ec_fn = "%s.eb" % '-'.join(module.split('/')) + path = '.*%s' % os.path.dirname(path_prefix) + regex = re.compile(r"^ \* \[.\] %s.*%s \(module: %s\)$" % (path, ec_fn, module), re.M) + self.assertTrue(regex.search(outtxt), "Found pattern %s in %s" % (regex.pattern, outtxt)) + + # make sure that *only* these modules are listed, no others + regex = re.compile(r"^ \* \[.\] .*/(?P.*) \(module: (?P.*)\)$", re.M) + self.assertEqual(sorted(x[1] for x in regex.findall(outtxt)), sorted(x[1] for x in modules)) + + pr_tmpdir = os.path.join(tmpdir, r'eb-\S{6,8}', 'files_commit_%s' % test_commit) + regex = re.compile(r"Extended list of robot search paths with \['%s'\]:" % pr_tmpdir, re.M) + self.assertTrue(regex.search(outtxt), "Found pattern %s in %s" % (regex.pattern, outtxt)) + except URLError as err: + print("Ignoring URLError '%s' in test_from_commit" % err) + shutil.rmtree(tmpdir) + # must be run after test for --list-easyblocks, hence the '_xxx_' # cleaning up the imported easyblocks is quite difficult... def test_xxx_include_easyblocks_from_commit(self): From 171fdb81465d362fad0d223981edcb914c268fb2 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 7 Jun 2024 17:07:44 +0200 Subject: [PATCH 33/37] code style cleanup in get_software_libdir + enhance test --- easybuild/tools/modules.py | 15 +++++++++------ test/framework/modules.py | 13 +++++++++++-- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/easybuild/tools/modules.py b/easybuild/tools/modules.py index a24ddaa369..7318be4f4f 100644 --- a/easybuild/tools/modules.py +++ b/easybuild/tools/modules.py @@ -1706,13 +1706,16 @@ def get_software_libdir(name, only_one=True, fs=None): if len(res) == 1: res = res[0] else: - if fs is None: - # check if only one (exactly) has libraries + if fs is None and len(res) == 2: + # if both lib and lib64 were found, check if only one (exactly) has libraries; # this is needed for software with library archives in lib64 but other files/directories in lib - lib_glob = ["*.%s" % ext for ext in ["a", get_shared_lib_ext()]] - haslibs = [any(glob.glob(os.path.join(root, subdir, f)) for f in lib_glob) for subdir in res] - if haslibs[0] != haslibs[1]: - return res[int(not haslibs[0])] + lib_glob = ['*.%s' % ext for ext in ['a', get_shared_lib_ext()]] + has_libs = [any(glob.glob(os.path.join(root, subdir, f)) for f in lib_glob) for subdir in res] + if has_libs[0] and not has_libs[1]: + return res[0] + elif has_libs[1] and not has_libs[0]: + return res[1] + raise EasyBuildError("Multiple library subdirectories found for %s in %s: %s", name, root, ', '.join(res)) return res diff --git a/test/framework/modules.py b/test/framework/modules.py index bb11802db7..195bf0339c 100644 --- a/test/framework/modules.py +++ b/test/framework/modules.py @@ -51,6 +51,7 @@ from easybuild.tools.modules import curr_module_paths, get_software_libdir, get_software_root, get_software_version from easybuild.tools.modules import invalidate_module_caches_for, modules_tool, reset_module_caches from easybuild.tools.run import run_cmd +from easybuild.tools.systemtools import get_shared_lib_ext # number of modules included for testing purposes @@ -678,11 +679,19 @@ def test_get_software_root_version_libdir(self): root = os.path.join(tmpdir, name) mkdir(os.path.join(root, 'lib64')) os.environ['EBROOT%s' % env_var_name] = root - write_file(os.path.join(root, 'lib', 'foo.a'), 'foo') + write_file(os.path.join(root, 'lib', 'libfoo.a'), 'foo') self.assertEqual(get_software_libdir(name), 'lib') + remove_file(os.path.join(root, 'lib', 'libfoo.a')) + + # also check vice versa with *shared* library in lib64 + shlib_ext = get_shared_lib_ext() + write_file(os.path.join(root, 'lib64', 'libfoo.' + shlib_ext), 'foo') + self.assertEqual(get_software_libdir(name), 'lib64') + + remove_file(os.path.join(root, 'lib64', 'libfoo.' + shlib_ext)) + # check expected result of get_software_libdir with multiple lib subdirs - remove_file(os.path.join(root, 'lib', 'foo.a')) self.assertErrorRegex(EasyBuildError, "Multiple library subdirectories found.*", get_software_libdir, name) self.assertEqual(get_software_libdir(name, only_one=False), ['lib', 'lib64']) From d4fdbae7a66470a891004feaadc2ce9cae26d65d Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 7 Jun 2024 21:39:05 +0200 Subject: [PATCH 34/37] don't try to determine repo version when file that contains version doesn't exist --- easybuild/tools/github.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/easybuild/tools/github.py b/easybuild/tools/github.py index 9a6bc985b8..6d4a1300fc 100644 --- a/easybuild/tools/github.py +++ b/easybuild/tools/github.py @@ -589,13 +589,19 @@ def fetch_files_from_pr(pr, path=None, github_user=None, github_account=None, gi raise EasyBuildError("Couldn't find path to patched file %s", full_path) if github_repo == GITHUB_EASYCONFIGS_REPO: - ver = _get_version_for_repo(os.path.join(final_path, 'setup.py')) + ver_file = os.path.join(final_path, 'setup.py') elif github_repo == GITHUB_EASYBLOCKS_REPO: - ver = _get_version_for_repo(os.path.join(final_path, 'easybuild', 'easyblocks', '__init__.py')) - - if different_major_versions(FRAMEWORK_VERSION, ver): - raise EasyBuildError("Framework (%s) is a different major version than used in %s/%s PR #%s (%s)", - FRAMEWORK_VERSION, github_account, github_repo, pr, ver) + ver_file = os.path.join(final_path, 'easybuild', 'easyblocks', '__init__.py') + else: + raise EasyBuildError("Don't know how to determine version for repo %s", github_repo) + + # take into account that the file we need to determine repo version may not be available, + # for example when a closed PR is used (since then we only download files patched by the PR) + if os.path.exists(ver_file): + ver = _get_version_for_repo(ver_file) + if different_major_versions(FRAMEWORK_VERSION, ver): + raise EasyBuildError("Framework (%s) is a different major version than used in %s/%s PR #%s (%s)", + FRAMEWORK_VERSION, github_account, github_repo, pr, ver) return files From c9cf1aa84a39fbfe5eb91099f71b63aba38fee3a Mon Sep 17 00:00:00 2001 From: Sebastian Achilles Date: Mon, 10 Jun 2024 23:00:29 +0200 Subject: [PATCH 35/37] prepare release notes for EasyBuild v4.9.2 + bump version to 4.9.2 --- RELEASE_NOTES | 23 +++++++++++++++++++++++ easybuild/tools/version.py | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/RELEASE_NOTES b/RELEASE_NOTES index 07c9756e67..073289de0a 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -4,6 +4,29 @@ For more detailed information, please see the git log. These release notes can also be consulted at https://easybuild.readthedocs.io/en/latest/Release_notes.html. +v4.9.2 (11 June 2024) +--------------------- + +update/bugfix release + +- various enhancements, including: + - Improve behavior when using extension with 'nosource:True' (#4506) + - enhance `get_software_libdir` to return `lib` or `lib64` if only one of them contains library files (#4513) + - versions checks to avoid mixing major versions across the EasyBuild components (#4520) + - add support for easyconfig parameter `module_only` (#4537) +- various bug fixes, including: + - Fix typo in patch_step logging (#4505) + - consider both `easybuild-framework*.tar.gz` and `easybuild_framework*.tar.gz` in CI workflows (#4507) + - don't delete existing environment module files when using `--dump-env-script` with `--force` or `--rebuild` (#4512) + - do not run unit tests on Python 3.5 (#4530) + - Fix resolved (template) values in case of failure (#4532) + - also consider `$CRAY_PE_LIBSCI_PREFIX_DIR` to determine installation prefix for cray-libsci (#4551) + - symlink downloaded repo at specified commit when using `--from-commit` so easyconfigs for dependencies are found (#4552) + - don't try to determine repo version when file that contains version doesn't exist (#4553) +- other changes: + - code cleanup in `easyblock.py` (#4519) + + v4.9.1 (5 April 2024) --------------------- diff --git a/easybuild/tools/version.py b/easybuild/tools/version.py index e29f6f0b29..bbe7f386ad 100644 --- a/easybuild/tools/version.py +++ b/easybuild/tools/version.py @@ -45,7 +45,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 = LooseVersion('4.9.2.dev0') +VERSION = LooseVersion('4.9.2') UNKNOWN = 'UNKNOWN' UNKNOWN_EASYBLOCKS_VERSION = '0.0.UNKNOWN.EASYBLOCKS' From 43c9fb4713c9e243b8fb951b4e230e318311a37d Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 11 Jun 2024 07:54:22 +0200 Subject: [PATCH 36/37] minor tweaks to 4.9.2 release notes --- RELEASE_NOTES | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/RELEASE_NOTES b/RELEASE_NOTES index 073289de0a..82373cc16a 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -10,21 +10,20 @@ v4.9.2 (11 June 2024) update/bugfix release - various enhancements, including: - - Improve behavior when using extension with 'nosource:True' (#4506) - - enhance `get_software_libdir` to return `lib` or `lib64` if only one of them contains library files (#4513) - - versions checks to avoid mixing major versions across the EasyBuild components (#4520) - - add support for easyconfig parameter `module_only` (#4537) -- various bug fixes, including: - - Fix typo in patch_step logging (#4505) - - consider both `easybuild-framework*.tar.gz` and `easybuild_framework*.tar.gz` in CI workflows (#4507) - - don't delete existing environment module files when using `--dump-env-script` with `--force` or `--rebuild` (#4512) - - do not run unit tests on Python 3.5 (#4530) - - Fix resolved (template) values in case of failure (#4532) - - also consider `$CRAY_PE_LIBSCI_PREFIX_DIR` to determine installation prefix for cray-libsci (#4551) - - symlink downloaded repo at specified commit when using `--from-commit` so easyconfigs for dependencies are found (#4552) - - don't try to determine repo version when file that contains version doesn't exist (#4553) + - improve behavior when using extension which has 'nosource' enabled (#4506) + - enhance 'get_software_libdir' to return 'lib' or 'lib64' if only one of them contains library files (#4513) + - implement versions checks to avoid mixing major versions across the EasyBuild components (#4520, #4553) + - add support for easyconfig parameter 'module_only' (#4537) +- various bug fixes, including: + - fix typo in patch_step logging (#4505) + - consider both 'easybuild-framework*.tar.gz' and 'easybuild_framework*.tar.gz' in CI workflows (#4507) + - don't delete existing environment module files when using '--dump-env-script' with '--force' or '--rebuild' (#4512) + - fix resolved (template) values in case of failure (#4532) + - also consider '$CRAY_PE_LIBSCI_PREFIX_DIR' to determine installation prefix for cray-libsci (#4551) + - symlink downloaded repo at specified commit when using '--from-commit' so easyconfigs for dependencies are found (#4552) - other changes: - - code cleanup in `easyblock.py` (#4519) + - code cleanup in 'easyblock.py' (#4519) + - stop running unit tests on Python 3.5 (#4530) v4.9.1 (5 April 2024) From d9078288216efec344f7c1a9fc7f0fdf11f39d16 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 11 Jun 2024 20:23:34 +0200 Subject: [PATCH 37/37] bump release date for EasyBuild v4.9.2 to 12 June 2024 --- RELEASE_NOTES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE_NOTES b/RELEASE_NOTES index 82373cc16a..5a45be8a56 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -4,7 +4,7 @@ For more detailed information, please see the git log. These release notes can also be consulted at https://easybuild.readthedocs.io/en/latest/Release_notes.html. -v4.9.2 (11 June 2024) +v4.9.2 (12 June 2024) --------------------- update/bugfix release