Skip to content

Commit ec88b9e

Browse files
authored
Merge branch 'easybuilders:develop' into ext_sources
2 parents 7f8d79e + 35c7f33 commit ec88b9e

File tree

14 files changed

+200
-60
lines changed

14 files changed

+200
-60
lines changed

.github/workflows/eb_command.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
runs-on: ubuntu-20.04
1515
strategy:
1616
matrix:
17-
python: [3.5, 3.6, 3.7, 3.8, 3.9, '3.10', '3.11']
17+
python: [3.6, 3.7, 3.8, 3.9, '3.10', '3.11']
1818
fail-fast: false
1919
steps:
2020
- uses: actions/checkout@v3

.github/workflows/linting.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
runs-on: ubuntu-20.04
1414
strategy:
1515
matrix:
16-
python-version: [3.5, 3.6, 3.7, 3.8, 3.9, '3.10', '3.11']
16+
python-version: [3.6, 3.7, 3.8, 3.9, '3.10', '3.11']
1717

1818
steps:
1919
- uses: actions/checkout@v3

.github/workflows/unit_tests.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,6 @@ jobs:
3737
lc_all: [""]
3838
include:
3939
# Test different Python 3 versions with Lmod 8.x
40-
- python: 3.5
41-
modules_tool: ${{needs.setup.outputs.lmod8}}
4240
- python: 3.7
4341
modules_tool: ${{needs.setup.outputs.lmod8}}
4442
- python: 3.8

easybuild/framework/easyblock.py

Lines changed: 23 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,7 +1074,7 @@ def make_builddir(self):
10741074
self.log.info("Overriding 'cleanupoldinstall' (to False), 'cleanupoldbuild' (to True) "
10751075
"and 'keeppreviousinstall' because we're building in the installation directory.")
10761076
# force cleanup before installation
1077-
if build_option('module_only'):
1077+
if build_option('module_only') or self.cfg['module_only']:
10781078
self.log.debug("Disabling cleanupoldbuild because we run as module-only")
10791079
self.cfg['cleanupoldbuild'] = False
10801080
else:
@@ -1145,7 +1145,7 @@ def make_dir(self, dir_name, clean, dontcreateinstalldir=False):
11451145
if self.cfg['keeppreviousinstall']:
11461146
self.log.info("Keeping old directory %s (hopefully you know what you are doing)", dir_name)
11471147
return
1148-
elif build_option('module_only'):
1148+
elif build_option('module_only') or self.cfg['module_only']:
11491149
self.log.info("Not touching existing directory %s in module-only mode...", dir_name)
11501150
elif clean:
11511151
remove_dir(dir_name)
@@ -1371,7 +1371,7 @@ def make_module_dep(self, unload_info=None):
13711371
multi_dep_mod_names[dep['name']].append(dep['short_mod_name'])
13721372

13731373
multi_dep_load_defaults = []
1374-
for depname, depmods in sorted(multi_dep_mod_names.items()):
1374+
for _, depmods in sorted(multi_dep_mod_names.items()):
13751375
stmt = self.module_generator.load_module(depmods[0], multi_dep_mods=depmods,
13761376
recursive_unload=recursive_unload,
13771377
depends_on=depends_on)
@@ -1775,10 +1775,7 @@ def make_extension_string(self, name_version_sep='-', ext_sep=', ', sort=True):
17751775
return ext_sep.join(exts_list)
17761776

17771777
def prepare_for_extensions(self):
1778-
"""
1779-
Also do this before (eg to set the template)
1780-
"""
1781-
pass
1778+
"""Ran before installing extensions (eg to set templates)"""
17821779

17831780
def skip_extensions(self):
17841781
"""
@@ -2120,7 +2117,7 @@ def guess_start_dir(self):
21202117
start_dir = ''
21212118
# do not use the specified 'start_dir' when running as --module-only as
21222119
# the directory will not exist (extract_step is skipped)
2123-
if self.start_dir and not build_option('module_only'):
2120+
if self.start_dir and not build_option('module_only') and not self.cfg['module_only']:
21242121
start_dir = self.start_dir
21252122

21262123
if not os.path.isabs(start_dir):
@@ -2204,9 +2201,9 @@ def handle_iterate_opts(self):
22042201
self.log.info("Current iteration index: %s", self.iter_idx)
22052202

22062203
# pop first element from all iterative easyconfig parameters as next value to use
2207-
for opt in self.iter_opts:
2208-
if len(self.iter_opts[opt]) > self.iter_idx:
2209-
self.cfg[opt] = self.iter_opts[opt][self.iter_idx]
2204+
for opt, value in self.iter_opts.items():
2205+
if len(value) > self.iter_idx:
2206+
self.cfg[opt] = value[self.iter_idx]
22102207
else:
22112208
self.cfg[opt] = '' # empty list => empty option as next value
22122209
self.log.debug("Next value for %s: %s" % (opt, str(self.cfg[opt])))
@@ -2218,12 +2215,12 @@ def post_iter_step(self):
22182215
"""Restore options that were iterated over"""
22192216
# disable templating, since we're messing about with values in self.cfg
22202217
with self.cfg.disable_templating():
2221-
for opt in self.iter_opts:
2222-
self.cfg[opt] = self.iter_opts[opt]
2218+
for opt, value in self.iter_opts.items():
2219+
self.cfg[opt] = value
22232220

22242221
# also need to take into account extensions, since those were iterated over as well
22252222
for ext in self.ext_instances:
2226-
ext.cfg[opt] = self.iter_opts[opt]
2223+
ext.cfg[opt] = value
22272224

22282225
self.log.debug("Restored value of '%s' that was iterated over: %s", opt, self.cfg[opt])
22292226

@@ -2355,7 +2352,7 @@ def check_readiness_step(self):
23552352
self.log.info("No module %s found. Not skipping anything." % self.full_mod_name)
23562353

23572354
# remove existing module file under --force (but only if --skip is not used)
2358-
elif build_option('force') or build_option('rebuild'):
2355+
elif (build_option('force') or build_option('rebuild')) and not build_option('dump_env_script'):
23592356
self.remove_module_file()
23602357

23612358
def fetch_step(self, skip_checksums=False):
@@ -2613,7 +2610,7 @@ def patch_step(self, beginpath=None, patches=None):
26132610
copy_patch = 'copy' in patch and 'sourcepath' not in patch
26142611

26152612
self.log.debug("Source index: %s; patch level: %s; source path suffix: %s; copy patch: %s",
2616-
srcind, level, srcpathsuffix, copy)
2613+
srcind, level, srcpathsuffix, copy_patch)
26172614

26182615
if beginpath is None:
26192616
try:
@@ -2757,10 +2754,7 @@ def _test_step(self):
27572754
self.report_test_failure(err)
27582755

27592756
def stage_install_step(self):
2760-
"""
2761-
Install in a stage directory before actual installation.
2762-
"""
2763-
pass
2757+
"""Install in a stage directory before actual installation."""
27642758

27652759
def install_step(self):
27662760
"""Install built software (abstract method)."""
@@ -3254,7 +3248,7 @@ def sanity_check_linked_shared_libs(self, subdirs=None):
32543248
required_libs.extend(self.cfg['required_linked_shared_libs'])
32553249

32563250
# early return if there are no banned/required libraries
3257-
if not (banned_libs + required_libs):
3251+
if not banned_libs + required_libs:
32583252
self.log.info("No banned/required libraries specified")
32593253
return []
32603254
else:
@@ -3801,7 +3795,7 @@ def make_module_step(self, fake=False):
38013795
try:
38023796
self.make_devel_module()
38033797
except EasyBuildError as error:
3804-
if build_option('module_only'):
3798+
if build_option('module_only') or self.cfg['module_only']:
38053799
self.log.info("Using --module-only so can recover from error: %s", error)
38063800
else:
38073801
raise error
@@ -3909,7 +3903,7 @@ def skip_step(self, step, skippable):
39093903
"""Dedice whether or not to skip the specified step."""
39103904
skip = False
39113905
force = build_option('force')
3912-
module_only = build_option('module_only')
3906+
module_only = build_option('module_only') or self.cfg['module_only']
39133907
sanity_check_only = build_option('sanity_check_only')
39143908
skip_extensions = build_option('skip_extensions')
39153909
skip_test_step = build_option('skip_test_step')
@@ -4469,7 +4463,7 @@ def copy_easyblocks_for_reprod(easyblock_instances, reprod_dir):
44694463
else:
44704464
easyblock_paths.add(easyblock_path)
44714465
for easyblock_path in easyblock_paths:
4472-
easyblock_basedir, easyblock_filename = os.path.split(easyblock_path)
4466+
easyblock_filename = os.path.basename(easyblock_path)
44734467
copy_file(easyblock_path, os.path.join(reprod_easyblock_dir, easyblock_filename))
44744468
_log.info("Dumped easyblock %s required for reproduction to %s", easyblock_filename, reprod_easyblock_dir)
44754469

@@ -4600,10 +4594,7 @@ def build_easyconfigs(easyconfigs, output_dir, test_results):
46004594

46014595

46024596
class StopException(Exception):
4603-
"""
4604-
StopException class definition.
4605-
"""
4606-
pass
4597+
"""Exception thrown to stop running steps"""
46074598

46084599

46094600
def inject_checksums_to_json(ecs, checksum_type):
@@ -4651,14 +4642,14 @@ def inject_checksums_to_json(ecs, checksum_type):
46514642

46524643
# actually inject new checksums or overwrite existing ones (if --force)
46534644
existing_checksums = app.get_checksums_from_json(always_read=True)
4654-
for filename in checksums:
4645+
for filename, checksum in checksums.items():
46554646
if filename not in existing_checksums:
4656-
existing_checksums[filename] = checksums[filename]
4647+
existing_checksums[filename] = checksum
46574648
# don't do anything if the checksum already exist and is the same
4658-
elif checksums[filename] != existing_checksums[filename]:
4649+
elif checksum != existing_checksums[filename]:
46594650
if build_option('force'):
46604651
print_warning("Found existing checksums for %s, overwriting them (due to --force)..." % ec_fn)
4661-
existing_checksums[filename] = checksums[filename]
4652+
existing_checksums[filename] = checksum
46624653
else:
46634654
raise EasyBuildError("Found existing checksum for %s, use --force to overwrite them" % filename)
46644655

easybuild/framework/easyconfig/default.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@
109109
'hidden': [False, "Install module file as 'hidden' by prefixing its version with '.'", BUILD],
110110
'installopts': ['', 'Extra options for installation', BUILD],
111111
'maxparallel': [None, 'Max degree of parallelism', BUILD],
112+
'module_only': [False, 'Only generate module file', BUILD],
112113
'parallel': [None, ('Degree of parallelism for e.g. make (default: based on the number of '
113114
'cores, active cpuset and restrictions in ulimit)'), BUILD],
114115
'patches': [[], "List of patches to apply", BUILD],

easybuild/framework/easyconfig/easyconfig.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2026,12 +2026,14 @@ def resolve_template(value, tmpl_dict):
20262026
# '%(name)s' -> '%(name)s'
20272027
# '%%(name)s' -> '%%(name)s'
20282028
if '%' in value:
2029+
orig_value = value
20292030
value = re.sub(re.compile(r'(%)(?!%*\(\w+\)s)'), r'\1\1', value)
20302031

20312032
try:
20322033
value = value % tmpl_dict
20332034
except KeyError:
20342035
_log.warning("Unable to resolve template value %s with dict %s", value, tmpl_dict)
2036+
value = orig_value # Undo "%"-escaping
20352037
else:
20362038
# this block deals with references to objects and returns other references
20372039
# for reading this is ok, but for self['x'] = {}

easybuild/framework/extensioneasyblock.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -126,18 +126,23 @@ def _set_start_dir(self):
126126
elif ext_start_dir is None:
127127
# This may be on purpose, e.g. for Python WHL files which do not get extracted
128128
self.log.debug("Start dir is not set.")
129-
else:
129+
elif self.start_dir:
130130
# non-existing start dir means wrong input from user
131-
warn_msg = "Provided start dir (%s) for extension %s does not exist: %s" % (self.start_dir, self.name,
132-
ext_start_dir)
131+
raise EasyBuildError("Provided start dir (%s) for extension %s does not exist: %s",
132+
self.start_dir, self.name, ext_start_dir)
133+
else:
134+
warn_msg = 'Failed to determine start dir for extension %s: %s' % (self.name, ext_start_dir)
133135
self.log.warning(warn_msg)
134136
print_warning(warn_msg, silent=build_option('silent'))
135137

136138
def run(self, unpack_src=False):
137139
"""Common operations for extensions: unpacking sources, patching, ..."""
138140

139141
# unpack file if desired
140-
if unpack_src:
142+
if self.options.get('nosource', False):
143+
# If no source wanted use the start_dir from the main EC
144+
self.ext_dir = self.master.start_dir
145+
elif unpack_src:
141146
targetdir = os.path.join(self.master.builddir, remove_unwanted_chars(self.name))
142147
self.ext_dir = extract_file(self.src, targetdir, extra_options=self.unpack_options,
143148
change_into_dir=False, cmd=self.src_extract_cmd)
@@ -146,10 +151,9 @@ def run(self, unpack_src=False):
146151
# because start_dir value is usually a relative path (if it is set)
147152
change_dir(self.ext_dir)
148153

149-
self._set_start_dir()
154+
self._set_start_dir()
155+
if self.start_dir:
150156
change_dir(self.start_dir)
151-
else:
152-
self._set_start_dir()
153157

154158
# patch if needed
155159
EasyBlock.patch_step(self, beginpath=self.ext_dir)

easybuild/main.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@
8383
from easybuild.tools.repository.repository import init_repository
8484
from easybuild.tools.systemtools import check_easybuild_deps
8585
from easybuild.tools.testing import create_test_report, overall_test_report, regtest, session_state
86+
from easybuild.tools.version import EASYBLOCKS_VERSION, FRAMEWORK_VERSION, UNKNOWN_EASYBLOCKS_VERSION
87+
from easybuild.tools.version import different_major_versions
8688

8789

8890
_log = None
@@ -440,9 +442,9 @@ def process_eb_args(eb_args, eb_go, cfg_settings, modtool, testing, init_session
440442
dry_run_mode = options.dry_run or options.dry_run_short or options.missing_modules
441443

442444
keep_available_modules = any((
443-
forced, dry_run_mode, options.extended_dry_run, any_pr_option_set, options.copy_ec, options.inject_checksums,
444-
options.sanity_check_only, options.inject_checksums_to_json)
445-
)
445+
forced, dry_run_mode, any_pr_option_set, options.copy_ec, options.dump_env_script, options.extended_dry_run,
446+
options.inject_checksums, options.inject_checksums_to_json, options.sanity_check_only
447+
))
446448

447449
# skip modules that are already installed unless forced, or unless an option is used that warrants not skipping
448450
if not keep_available_modules:
@@ -618,6 +620,15 @@ def main(args=None, logfile=None, do_build=None, testing=False, modtool=None, pr
618620
(build_specs, _log, logfile, robot_path, search_query, eb_tmpdir, try_to_generate,
619621
from_pr_list, tweaked_ecs_paths) = cfg_settings
620622

623+
# compare running Framework and EasyBlocks versions
624+
if EASYBLOCKS_VERSION == UNKNOWN_EASYBLOCKS_VERSION:
625+
# most likely reason is running framework unit tests with no easyblocks installation
626+
# so log a warning, to avoid test related issues
627+
_log.warning("Unable to determine EasyBlocks version, so we'll assume it is not different from Framework")
628+
elif different_major_versions(FRAMEWORK_VERSION, EASYBLOCKS_VERSION):
629+
raise EasyBuildError("Framework (%s) and EasyBlock (%s) major versions are different." % (FRAMEWORK_VERSION,
630+
EASYBLOCKS_VERSION))
631+
621632
# load hook implementations (if any)
622633
hooks = load_hooks(options.hooks)
623634

easybuild/tools/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ def mk_full_default_path(name, prefix=DEFAULT_PREFIX):
278278
'debug',
279279
'debug_lmod',
280280
'dump_autopep8',
281+
'dump_env_script',
281282
'enforce_checksums',
282283
'experimental',
283284
'extended_dry_run',

easybuild/tools/github.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
from easybuild.tools.py2vs3 import HTTPError, URLError, ascii_letters, urlopen
6161
from easybuild.tools.systemtools import UNKNOWN, get_tool_version
6262
from easybuild.tools.utilities import nub, only_if_module_is_available
63+
from easybuild.tools.version import FRAMEWORK_VERSION, different_major_versions
6364

6465

6566
_log = fancylogger.getLogger('github', fname=False)
@@ -587,9 +588,39 @@ def fetch_files_from_pr(pr, path=None, github_user=None, github_account=None, gi
587588
else:
588589
raise EasyBuildError("Couldn't find path to patched file %s", full_path)
589590

591+
if github_repo == GITHUB_EASYCONFIGS_REPO:
592+
ver = _get_version_for_repo(os.path.join(final_path, 'setup.py'))
593+
elif github_repo == GITHUB_EASYBLOCKS_REPO:
594+
ver = _get_version_for_repo(os.path.join(final_path, 'easybuild', 'easyblocks', '__init__.py'))
595+
596+
if different_major_versions(FRAMEWORK_VERSION, ver):
597+
raise EasyBuildError("Framework (%s) is a different major version than used in %s/%s PR #%s (%s)",
598+
FRAMEWORK_VERSION, github_account, github_repo, pr, ver)
599+
590600
return files
591601

592602

603+
def _get_version_for_repo(filename):
604+
"""Extract version from filename."""
605+
_log.debug("Extract version from %s" % filename)
606+
607+
try:
608+
ver_line = ""
609+
with open(filename) as f:
610+
for line in f.readlines():
611+
if line.startswith("VERSION "):
612+
ver_line = line
613+
break
614+
615+
# version can be a string or LooseVersion
616+
res = re.search(r"""^VERSION = .*['"](.*)['"].?$""", ver_line)
617+
618+
_log.debug("PR target version is %s" % res.group(1))
619+
return res.group(1)
620+
except Exception:
621+
raise EasyBuildError("Couldn't determine version of PR from %s" % filename)
622+
623+
593624
def fetch_easyblocks_from_pr(pr, path=None, github_user=None):
594625
"""Fetch patched easyblocks for a particular PR."""
595626
return fetch_files_from_pr(pr, path, github_user, github_repo=GITHUB_EASYBLOCKS_REPO)

0 commit comments

Comments
 (0)