Skip to content

Commit e7a37c5

Browse files
authored
Merge pull request #3129 from Flamefire/fix_exts_filter
Fix or avoid warnings that commonly arise in build log
2 parents b1a46d3 + 1e13f9c commit e7a37c5

File tree

14 files changed

+204
-121
lines changed

14 files changed

+204
-121
lines changed

easybuild/framework/easyblock.py

Lines changed: 8 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
from easybuild.framework.easyconfig.style import MAX_LINE_LENGTH
6262
from easybuild.framework.easyconfig.tools import get_paths_for
6363
from easybuild.framework.easyconfig.templates import TEMPLATE_NAMES_EASYBLOCK_RUN_STEP, template_constant_dict
64+
from easybuild.framework.extension import resolve_exts_filter_template
6465
from easybuild.tools import config, filetools
6566
from easybuild.tools.build_details import get_build_stats
6667
from easybuild.tools.build_log import EasyBuildError, dry_run_msg, dry_run_warning, dry_run_set_dirs
@@ -1446,43 +1447,18 @@ def skip_extensions(self):
14461447

14471448
if not exts_filter or len(exts_filter) == 0:
14481449
raise EasyBuildError("Skipping of extensions, but no exts_filter set in easyconfig")
1449-
elif isinstance(exts_filter, string_type) or len(exts_filter) != 2:
1450-
raise EasyBuildError('exts_filter should be a list or tuple of ("command","input")')
1451-
cmdtmpl = exts_filter[0]
1452-
cmdinputtmpl = exts_filter[1]
14531450

14541451
res = []
14551452
for ext in self.exts:
1456-
name = ext['name']
1457-
if 'options' in ext and 'modulename' in ext['options']:
1458-
modname = ext['options']['modulename']
1459-
else:
1460-
modname = name
1461-
tmpldict = {
1462-
'ext_name': modname,
1463-
'ext_version': ext.get('version'),
1464-
'src': ext.get('source'),
1465-
}
1466-
1467-
try:
1468-
cmd = cmdtmpl % tmpldict
1469-
except KeyError as err:
1470-
msg = "KeyError occurred on completing extension filter template: %s; "
1471-
msg += "'name'/'version' keys are no longer supported, should use 'ext_name'/'ext_version' instead"
1472-
self.log.nosupport(msg % err, '2.0')
1473-
1474-
if cmdinputtmpl:
1475-
stdin = cmdinputtmpl % tmpldict
1476-
(cmdstdouterr, ec) = run_cmd(cmd, log_all=False, log_ok=False, simple=False, inp=stdin, regexp=False)
1477-
else:
1478-
(cmdstdouterr, ec) = run_cmd(cmd, log_all=False, log_ok=False, simple=False, regexp=False)
1453+
cmd, stdin = resolve_exts_filter_template(exts_filter, ext)
1454+
(cmdstdouterr, ec) = run_cmd(cmd, log_all=False, log_ok=False, simple=False, inp=stdin, regexp=False)
14791455
self.log.info("exts_filter result %s %s", cmdstdouterr, ec)
14801456
if ec:
1481-
self.log.info("Not skipping %s" % name)
1457+
self.log.info("Not skipping %s" % ext['name'])
14821458
self.log.debug("exit code: %s, stdout/err: %s" % (ec, cmdstdouterr))
14831459
res.append(ext)
14841460
else:
1485-
self.log.info("Skipping %s" % name)
1461+
self.log.info("Skipping %s" % ext['name'])
14861462
self.exts = res
14871463

14881464
#
@@ -1600,8 +1576,9 @@ def post_iter_step(self):
16001576

16011577
def det_iter_cnt(self):
16021578
"""Determine iteration count based on configure/build/install options that may be lists."""
1603-
iter_opt_counts = [len(self.cfg[opt]) for opt in ITERATE_OPTIONS
1604-
if opt not in ['builddependencies'] and isinstance(self.cfg[opt], (list, tuple))]
1579+
# Using get_ref to avoid resolving templates as their required attributes may not be available yet
1580+
iter_opt_counts = [len(self.cfg.get_ref(opt)) for opt in ITERATE_OPTIONS
1581+
if opt not in ['builddependencies'] and isinstance(self.cfg.get_ref(opt), (list, tuple))]
16051582

16061583
# we need to take into account that builddependencies is always a list
16071584
# we're only iterating over it if it's a list of lists

easybuild/framework/easyconfig/default.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@
167167
'exts_defaultclass': [None, "List of module for and name of the default extension class", EXTENSIONS],
168168
'exts_default_options': [{}, "List of default options for extensions", EXTENSIONS],
169169
'exts_filter': [None, ("Extension filter details: template for cmd and input to cmd "
170-
"(templates for name, version and src)."), EXTENSIONS],
170+
"(templates for ext_name, ext_version and src)."), EXTENSIONS],
171171
'exts_list': [[], 'List with extensions added to the base installation', EXTENSIONS],
172172

173173
# MODULES easyconfig parameters

easybuild/framework/easyconfig/easyconfig.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -816,16 +816,17 @@ def validate_iterate_opts_lists(self):
816816
for opt in ITERATE_OPTIONS:
817817

818818
# only when builddependencies is a list of lists are we iterating over them
819-
if opt == 'builddependencies' and not all(isinstance(e, list) for e in self[opt]):
819+
if opt == 'builddependencies' and not all(isinstance(e, list) for e in self.get_ref(opt)):
820820
continue
821821

822+
opt_value = self.get(opt, None, resolve=False)
822823
# anticipate changes in available easyconfig parameters (e.g. makeopts -> buildopts?)
823-
if self.get(opt, None) is None:
824+
if opt_value is None:
824825
raise EasyBuildError("%s not available in self.cfg (anymore)?!", opt)
825826

826827
# keep track of list, supply first element as first option to handle
827-
if isinstance(self[opt], (list, tuple)):
828-
opt_counts.append((opt, len(self[opt])))
828+
if isinstance(opt_value, (list, tuple)):
829+
opt_counts.append((opt, len(opt_value)))
829830

830831
# make sure that options that specify lists have the same length
831832
list_opt_lengths = [length for (opt, length) in opt_counts if length > 1]
@@ -1523,12 +1524,13 @@ def __setitem__(self, key, value):
15231524
key, value)
15241525

15251526
@handle_deprecated_or_replaced_easyconfig_parameters
1526-
def get(self, key, default=None):
1527+
def get(self, key, default=None, resolve=True):
15271528
"""
15281529
Gets the value of a key in the config, with 'default' as fallback.
1530+
:param resolve: if False, disables templating via calling get_ref, else resolves template values
15291531
"""
15301532
if key in self:
1531-
return self[key]
1533+
return self[key] if resolve else self.get_ref(key)
15321534
else:
15331535
return default
15341536

easybuild/framework/extension.py

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,47 @@
3838

3939
from easybuild.framework.easyconfig.easyconfig import resolve_template
4040
from easybuild.framework.easyconfig.templates import template_constant_dict
41-
from easybuild.tools.build_log import EasyBuildError
41+
from easybuild.tools.build_log import EasyBuildError, raise_nosupport
4242
from easybuild.tools.filetools import change_dir
4343
from easybuild.tools.run import run_cmd
44+
from easybuild.tools.py2vs3 import string_type
45+
46+
47+
def resolve_exts_filter_template(exts_filter, ext):
48+
"""
49+
Resolve the exts_filter tuple by replacing the template values using the extension
50+
:param exts_filter: Tuple of (command, input) using template values (ext_name, ext_version, src)
51+
:param ext: Instance of Extension or dictionary like with 'name' and optionally 'options', 'version', 'source' keys
52+
:return (cmd, input) as a tuple of strings
53+
"""
54+
55+
if isinstance(exts_filter, string_type) or len(exts_filter) != 2:
56+
raise EasyBuildError('exts_filter should be a list or tuple of ("command","input")')
57+
58+
cmd, cmdinput = exts_filter
59+
60+
if not isinstance(ext, dict):
61+
ext = {'name': ext.name, 'version': ext.version, 'src': ext.src, 'options': ext.options}
62+
63+
name = ext['name']
64+
if 'options' in ext and 'modulename' in ext['options']:
65+
modname = ext['options']['modulename']
66+
else:
67+
modname = name
68+
tmpldict = {
69+
'ext_name': modname,
70+
'ext_version': ext.get('version'),
71+
'src': ext.get('src'),
72+
}
73+
74+
try:
75+
cmd = cmd % tmpldict
76+
cmdinput = cmdinput % tmpldict if cmdinput else None
77+
except KeyError as err:
78+
msg = "KeyError occurred on completing extension filter template: %s; "
79+
msg += "'name'/'version' keys are no longer supported, should use 'ext_name'/'ext_version' instead"
80+
raise_nosupport(msg % err, '2.0')
81+
return cmd, cmdinput
4482

4583

4684
class Extension(object):
@@ -144,16 +182,11 @@ def sanity_check_step(self):
144182
if os.path.isdir(self.installdir):
145183
change_dir(self.installdir)
146184

147-
# disabling templating is required here to support legacy string templates like name/version
148-
self.cfg.enable_templating = False
149-
exts_filter = self.cfg['exts_filter']
150-
self.cfg.enable_templating = True
185+
# Get raw value to translate ext_name, ext_version, src
186+
exts_filter = self.cfg.get_ref('exts_filter')
151187

152-
if exts_filter is not None:
153-
cmd, inp = exts_filter
154-
else:
188+
if exts_filter is None:
155189
self.log.debug("no exts_filter setting found, skipping sanitycheck")
156-
cmd = None
157190

158191
if 'modulename' in self.options:
159192
modname = self.options['modulename']
@@ -165,22 +198,8 @@ def sanity_check_step(self):
165198
# allow skipping of sanity check by setting module name to False
166199
if modname is False:
167200
self.log.info("modulename set to False for '%s' extension, so skipping sanity check", self.name)
168-
elif cmd:
169-
template = {
170-
'ext_name': modname,
171-
'ext_version': self.version,
172-
'src': self.src,
173-
# the ones below are only there for legacy purposes
174-
# TODO deprecated, remove in v2.0
175-
# TODO same dict is used in easyblock.py skip_extensions, resolve this
176-
'name': modname,
177-
'version': self.version,
178-
}
179-
cmd = cmd % template
180-
181-
stdin = None
182-
if inp:
183-
stdin = inp % template
201+
elif exts_filter:
202+
cmd, stdin = resolve_exts_filter_template(exts_filter, self)
184203
# set log_ok to False so we can catch the error instead of run_cmd
185204
(output, ec) = run_cmd(cmd, log_ok=False, simple=False, regexp=False, inp=stdin)
186205

easybuild/framework/extensioneasyblock.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,9 @@ def sanity_check_step(self, exts_filter=None, custom_paths=None, custom_commands
116116
"""
117117
Custom sanity check for extensions, whether installed as stand-alone module or not
118118
"""
119-
if not self.cfg['exts_filter']:
119+
if not self.cfg.get_ref('exts_filter'):
120120
self.cfg['exts_filter'] = exts_filter
121-
self.log.debug("starting sanity check for extension with filter %s", self.cfg['exts_filter'])
121+
self.log.debug("starting sanity check for extension with filter %s", self.cfg.get_ref('exts_filter'))
122122

123123
# for stand-alone installations that were done for multiple dependency versions (via multi_deps),
124124
# we need to perform the extension sanity check for each of them, by loading the corresponding modules first

easybuild/tools/build_log.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@ def raise_easybuilderror(msg, *args):
9090
raise EasyBuildError(msg, *args)
9191

9292

93+
def raise_nosupport(msg, ver):
94+
"""Construct error message for no longer supported behaviour, and raise an EasyBuildError."""
95+
nosupport_msg = "NO LONGER SUPPORTED since v%s: %s; see %s for more information"
96+
raise_easybuilderror(nosupport_msg, ver, msg, DEPRECATED_DOC_URL)
97+
98+
9399
class EasyBuildLog(fancylogger.FancyLogger):
94100
"""
95101
The EasyBuild logger, with its own error and exception functions.
@@ -154,9 +160,8 @@ def log_callback_warning_and_print(msg):
154160
fancylogger.FancyLogger.deprecated(self, msg, ver, max_ver, *args, **kwargs)
155161

156162
def nosupport(self, msg, ver):
157-
"""Print error message for no longer supported behaviour, and raise an EasyBuildError."""
158-
nosupport_msg = "NO LONGER SUPPORTED since v%s: %s; see %s for more information"
159-
raise EasyBuildError(nosupport_msg, ver, msg, DEPRECATED_DOC_URL)
163+
"""Raise error message for no longer supported behaviour, and raise an EasyBuildError."""
164+
raise_nosupport(msg, ver)
160165

161166
def error(self, msg, *args, **kwargs):
162167
"""Print error message and raise an EasyBuildError."""

easybuild/tools/filetools.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -384,12 +384,14 @@ def extract_file(fn, dest, cmd=None, extra_options=None, overwrite=False, forced
384384
return find_base_dir()
385385

386386

387-
def which(cmd, retain_all=False, check_perms=True):
387+
def which(cmd, retain_all=False, check_perms=True, log_ok=True, log_error=True):
388388
"""
389389
Return (first) path in $PATH for specified command, or None if command is not found
390390
391391
:param retain_all: returns *all* locations to the specified command in $PATH, not just the first one
392392
:param check_perms: check whether candidate path has read/exec permissions before accepting it as a match
393+
:param log_ok: Log an info message where the command has been found (if any)
394+
:param log_error: Log a warning message when command hasn't been found
393395
"""
394396
if retain_all:
395397
res = []
@@ -401,7 +403,8 @@ def which(cmd, retain_all=False, check_perms=True):
401403
cmd_path = os.path.join(path, cmd)
402404
# only accept path if command is there
403405
if os.path.isfile(cmd_path):
404-
_log.info("Command %s found at %s", cmd, cmd_path)
406+
if log_ok:
407+
_log.info("Command %s found at %s", cmd, cmd_path)
405408
if check_perms:
406409
# check if read/executable permissions are available
407410
if not os.access(cmd_path, os.R_OK | os.X_OK):
@@ -413,7 +416,7 @@ def which(cmd, retain_all=False, check_perms=True):
413416
res = cmd_path
414417
break
415418

416-
if not res:
419+
if not res and log_error:
417420
_log.warning("Could not find command '%s' (with permissions to read/execute it) in $PATH (%s)" % (cmd, paths))
418421
return res
419422

easybuild/tools/modules.py

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -180,16 +180,17 @@ def __init__(self, mod_paths=None, testing=False):
180180
if mod_paths is not None:
181181
self.set_mod_paths(mod_paths)
182182

183-
# only use command path in environment variable if command in not available in $PATH
184-
if which(self.cmd) is None and env_cmd_path is not None:
185-
self.log.debug("Set %s command via environment variable %s: %s",
186-
self.NAME, self.COMMAND_ENVIRONMENT, self.cmd)
187-
self.cmd = env_cmd_path
188-
189-
# check whether paths obtained via $PATH and $LMOD_CMD are different
190-
elif which(self.cmd) != env_cmd_path:
191-
self.log.debug("Different paths found for %s command '%s' via which/$PATH and $%s: %s vs %s",
192-
self.NAME, self.COMMAND, self.COMMAND_ENVIRONMENT, self.cmd, env_cmd_path)
183+
if env_cmd_path:
184+
cmd_path = which(self.cmd, log_ok=False, log_error=False)
185+
# only use command path in environment variable if command in not available in $PATH
186+
if cmd_path is None:
187+
self.cmd = env_cmd_path
188+
self.log.debug("Set %s command via environment variable %s: %s",
189+
self.NAME, self.COMMAND_ENVIRONMENT, self.cmd)
190+
# check whether paths obtained via $PATH and $LMOD_CMD are different
191+
elif cmd_path != env_cmd_path:
192+
self.log.debug("Different paths found for %s command '%s' via which/$PATH and $%s: %s vs %s",
193+
self.NAME, self.COMMAND, self.COMMAND_ENVIRONMENT, cmd_path, env_cmd_path)
193194

194195
# make sure the module command was found
195196
if self.cmd is None:
@@ -284,7 +285,7 @@ def set_and_check_version(self):
284285

285286
def check_cmd_avail(self):
286287
"""Check whether modules tool command is available."""
287-
cmd_path = which(self.cmd)
288+
cmd_path = which(self.cmd, log_ok=False)
288289
if cmd_path is not None:
289290
self.cmd = cmd_path
290291
self.log.info("Full path for %s command is %s, so using it", self.NAME, self.cmd)
@@ -674,7 +675,7 @@ def modulefile_path(self, mod_name, strip_ext=False):
674675
:param strip_ext: strip (.lua) extension from module fileame (if present)"""
675676
# (possible relative) path is always followed by a ':', and may be prepended by whitespace
676677
# this works for both environment modules and Lmod
677-
modpath_re = re.compile('^\s*(?P<modpath>[^/\n]*/[^\s]+):$', re.M)
678+
modpath_re = re.compile(r'^\s*(?P<modpath>[^/\n]*/[^\s]+):$', re.M)
678679
modpath = self.get_value_from_modulefile(mod_name, modpath_re)
679680

680681
if strip_ext and modpath.endswith('.lua'):
@@ -939,7 +940,7 @@ def file_join(res):
939940
"""Helper function to compose joined path."""
940941
return os.path.join(*[x.strip('"') for x in res.groups()])
941942

942-
res = re.sub('\[\s+file\s+join\s+(.*)\s+(.*)\s+\]', file_join, res)
943+
res = re.sub(r'\[\s+file\s+join\s+(.*)\s+(.*)\s+\]', file_join, res)
943944

944945
# also interpret all $env(...) parts
945946
res = re.sub(r'\$env\((?P<key>[^)]*)\)', lambda res: os.getenv(res.group('key'), ''), res)
@@ -1158,7 +1159,7 @@ def run_module(self, *args, **kwargs):
11581159
# this is required for the DEISA variant of modulecmd.tcl which is commonly used
11591160
def tweak_stdout(txt):
11601161
"""Tweak stdout before it's exec'ed as Python code."""
1161-
modulescript_regex = "^exec\s+[\"'](?P<modulescript>/tmp/modulescript_[0-9_]+)[\"']$"
1162+
modulescript_regex = r"^exec\s+[\"'](?P<modulescript>/tmp/modulescript_[0-9_]+)[\"']$"
11621163
return re.sub(modulescript_regex, r"execfile('\1')", txt)
11631164

11641165
tweak_stdout_fn = None
@@ -1366,7 +1367,7 @@ def module_wrapper_exists(self, mod_name):
13661367

13671368
# first consider .modulerc.lua with Lmod 7.8 (or newer)
13681369
if StrictVersion(self.version) >= StrictVersion('7.8'):
1369-
mod_wrapper_regex_template = '^module_version\("(?P<wrapped_mod>.*)", "%s"\)$'
1370+
mod_wrapper_regex_template = r'^module_version\("(?P<wrapped_mod>.*)", "%s"\)$'
13701371
res = super(Lmod, self).module_wrapper_exists(mod_name, modulerc_fn='.modulerc.lua',
13711372
mod_wrapper_regex_template=mod_wrapper_regex_template)
13721373

easybuild/tools/systemtools.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@
114114
'AuthenticAMD': AMD,
115115
'GenuineIntel': INTEL,
116116
'IBM': IBM,
117+
# IBM POWER9
118+
'8335-GTH': IBM,
119+
'8335-GTX': IBM,
117120
}
118121
# ARM Cortex part numbers from the corresponding ARM Processor Technical Reference Manuals,
119122
# see http://infocenter.arm.com - Cortex-A series processors, Section "Main ID Register"
@@ -276,7 +279,7 @@ def get_cpu_vendor():
276279
if arch == X86_64:
277280
vendor_regex = re.compile(r"vendor_id\s+:\s*(\S+)")
278281
elif arch == POWER:
279-
vendor_regex = re.compile(r"model\s+:\s*(\w+)")
282+
vendor_regex = re.compile(r"model\s+:\s*((\w|-)+)")
280283
elif arch in [AARCH32, AARCH64]:
281284
vendor_regex = re.compile(r"CPU implementer\s+:\s*(\S+)")
282285

easybuild/tools/toolchain/options.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,12 @@ def option(self, name, templatedict=None):
8686
"""Return option value"""
8787
value = self.get(name, None)
8888
if value is None and name not in self.options_map:
89-
self.log.warning("option: option with name %s returns None" % name)
89+
msg = "option: option with name %s returns None" % name
90+
# Empty options starting with _opt_ are allowed, so don't warn
91+
if name.startswith('_opt_'):
92+
self.log.devel(msg)
93+
else:
94+
self.log.warning(msg)
9095
res = None
9196
elif name in self.options_map:
9297
res = self.options_map[name]

0 commit comments

Comments
 (0)