Skip to content

Commit eae7aaf

Browse files
committed
Merge branch 'develop' into copy-ec-from-pr
2 parents e32d815 + 000e79e commit eae7aaf

25 files changed

Lines changed: 705 additions & 198 deletions

.github/workflows/unit_tests.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ jobs:
4343
python: 3.8
4444
- modules_tool: modules-4.1.4
4545
python: 3.9
46+
- modules_tool: Lmod-7.8.22
47+
python: 3.5
48+
- modules_tool: Lmod-7.8.22
49+
python: 3.7
50+
- modules_tool: Lmod-7.8.22
51+
python: 3.8
52+
- modules_tool: Lmod-7.8.22
53+
python: 3.9
4654
fail-fast: false
4755
steps:
4856
- uses: actions/checkout@v2

RELEASE_NOTES

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,33 @@ For more detailed information, please see the git log.
33

44
These release notes can also be consulted at https://easybuild.readthedocs.io/en/latest/Release_notes.html.
55

6+
v4.3.1 (October 29th 2020)
7+
--------------------------
8+
9+
update/bugfix release
10+
11+
- various enhancements, including:
12+
- further GCC toolchain fixes for aarch64 (#3433)
13+
- take into account --include-easyblocks-from-pr when uploading test reports (#3446)
14+
- add path to pkg-config files in sysroot to $PKG_CONFIG_PATH when --sysroot is specified (#3451)
15+
- add support for NVHPC compiler + toolchain (based on PGI) (#3454)
16+
- check for _VERSION and _PREFIX Cray environment variables with both software and module name (#3460)
17+
- allow including easyblocks from multiple PRs (#3480, #3481)
18+
- various bug fixes, including:
19+
- avoid UnicodeDecodeError in apply_regex_substitutions when patching files that include non-UTF-8 characters (#3450)
20+
- avoid appending lib stubs pattern to RPATH filter over and over again (#3452)
21+
- fix missing string template on error for incorrect extension 'sources' value (#3461)
22+
- fix compatibility with Python 3.9 by renaming fancy root logger (#3465)
23+
- also remove empty checksums list specified in easyconfig file when using --inject-checksums (#3466)
24+
- avoid confusing error log message when determining impi version while trying to define value for %(mpi_cmd_prefix)s template (#3474)
25+
- unset $LD_LIBRARY_PATH when checking for OS dependencies with 'rpm' & co (#3477)
26+
- don't change directory in download_repo function in tools.github (#3486)
27+
- take source_urls, checksums, patches into account when extension source is specified via 'sources' (#3487)
28+
- other changes:
29+
- consider $EB_INSTALLPYTHON in 'eb' command to specify 'python' command to use for running EasyBuild (#3428)
30+
- use only the sub folder name for createSubmoduleDeps script (#3464)
31+
32+
633
v4.3.0 (September 13th 2020)
734
----------------------------
835

easybuild/framework/easyblock.py

Lines changed: 82 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,8 @@ def fetch_extension_sources(self, skip_checksums=False):
502502
if self.dry_run:
503503
self.dry_run_msg("\nList of sources/patches for extensions:")
504504

505+
force_download = build_option('force_download') in [FORCE_DOWNLOAD_ALL, FORCE_DOWNLOAD_SOURCES]
506+
505507
for ext in exts_list:
506508
if (isinstance(ext, list) or isinstance(ext, tuple)) and ext:
507509

@@ -539,18 +541,17 @@ def fetch_extension_sources(self, skip_checksums=False):
539541
# resolve templates in extension options
540542
ext_options = resolve_template(ext_options, template_values)
541543

544+
source_urls = ext_options.get('source_urls', [])
542545
checksums = ext_options.get('checksums', [])
543546

544-
# use default template for name of source file if none is specified
545-
default_source_tmpl = resolve_template('%(name)s-%(version)s.tar.gz', template_values)
546-
fn = ext_options.get('source_tmpl', default_source_tmpl)
547-
548547
if ext_options.get('nosource', None):
549-
exts_sources.append(ext_src)
548+
self.log.debug("No sources for extension %s, as indicated by 'nosource'", ext_name)
550549

551550
elif ext_options.get('sources', None):
552551
sources = ext_options['sources']
553552

553+
# only a single source file is supported for extensions currently,
554+
# see https://github.com/easybuilders/easybuild-framework/issues/3463
554555
if isinstance(sources, list):
555556
if len(sources) == 1:
556557
source = sources[0]
@@ -560,67 +561,90 @@ def fetch_extension_sources(self, skip_checksums=False):
560561
else:
561562
source = sources
562563

564+
# always pass source spec as dict value to fetch_source method,
565+
# mostly so we can inject stuff like source URLs
566+
if isinstance(source, string_type):
567+
source = {'filename': source}
568+
elif not isinstance(source, dict):
569+
raise EasyBuildError("Incorrect value type for source of extension %s: %s",
570+
ext_name, source)
571+
572+
# if no custom source URLs are specified in sources spec,
573+
# inject the ones specified for this extension
574+
if 'source_urls' not in source:
575+
source['source_urls'] = source_urls
576+
563577
src = self.fetch_source(source, checksums, extension=True)
564-
# Copy 'path' entry to 'src' for use with extensions
565-
ext_src.update({'src': src['path']})
566-
exts_sources.append(ext_src)
567-
else:
568-
source_urls = ext_options.get('source_urls', [])
569-
force_download = build_option('force_download') in [FORCE_DOWNLOAD_ALL, FORCE_DOWNLOAD_SOURCES]
570578

571-
src_fn = self.obtain_file(fn, extension=True, urls=source_urls, force_download=force_download)
579+
# copy 'path' entry to 'src' for use with extensions
580+
ext_src.update({'src': src['path']})
572581

573-
if src_fn:
574-
ext_src.update({'src': src_fn})
582+
else:
583+
# use default template for name of source file if none is specified
584+
default_source_tmpl = resolve_template('%(name)s-%(version)s.tar.gz', template_values)
585+
586+
# if no sources are specified via 'sources', fall back to 'source_tmpl'
587+
src_fn = ext_options.get('source_tmpl', default_source_tmpl)
588+
src_path = self.obtain_file(src_fn, extension=True, urls=source_urls,
589+
force_download=force_download)
590+
if src_path:
591+
ext_src.update({'src': src_path})
592+
else:
593+
raise EasyBuildError("Source for extension %s not found.", ext)
575594

576-
if not skip_checksums:
577-
# report both MD5 and SHA256 checksums, since both are valid default checksum types
595+
# verify checksum for extension sources
596+
if 'src' in ext_src and not skip_checksums:
597+
src_path = ext_src['src']
598+
src_fn = os.path.basename(src_path)
599+
600+
# report both MD5 and SHA256 checksums, since both are valid default checksum types
601+
for checksum_type in (CHECKSUM_TYPE_MD5, CHECKSUM_TYPE_SHA256):
602+
src_checksum = compute_checksum(src_path, checksum_type=checksum_type)
603+
self.log.info("%s checksum for %s: %s", checksum_type, src_path, src_checksum)
604+
605+
# verify checksum (if provided)
606+
self.log.debug('Verifying checksums for extension source...')
607+
fn_checksum = self.get_checksum_for(checksums, index=0)
608+
if verify_checksum(src_path, fn_checksum):
609+
self.log.info('Checksum for extension source %s verified', src_fn)
610+
elif build_option('ignore_checksums'):
611+
print_warning("Ignoring failing checksum verification for %s" % src_fn)
612+
else:
613+
raise EasyBuildError('Checksum verification for extension source %s failed', src_fn)
614+
615+
# locate extension patches (if any), and verify checksums
616+
ext_patches = self.fetch_patches(patch_specs=ext_options.get('patches', []), extension=True)
617+
if ext_patches:
618+
self.log.debug('Found patches for extension %s: %s', ext_name, ext_patches)
619+
ext_src.update({'patches': ext_patches})
620+
621+
if not skip_checksums:
622+
for patch in ext_patches:
623+
patch = patch['path']
624+
# report both MD5 and SHA256 checksums,
625+
# since both are valid default checksum types
578626
for checksum_type in (CHECKSUM_TYPE_MD5, CHECKSUM_TYPE_SHA256):
579-
src_checksum = compute_checksum(src_fn, checksum_type=checksum_type)
580-
self.log.info("%s checksum for %s: %s", checksum_type, src_fn, src_checksum)
581-
582-
# verify checksum (if provided)
583-
self.log.debug('Verifying checksums for extension source...')
584-
fn_checksum = self.get_checksum_for(checksums, index=0)
585-
if verify_checksum(src_fn, fn_checksum):
586-
self.log.info('Checksum for extension source %s verified', fn)
627+
checksum = compute_checksum(patch, checksum_type=checksum_type)
628+
self.log.info("%s checksum for %s: %s", checksum_type, patch, checksum)
629+
630+
# verify checksum (if provided)
631+
self.log.debug('Verifying checksums for extension patches...')
632+
for idx, patch in enumerate(ext_patches):
633+
patch = patch['path']
634+
patch_fn = os.path.basename(patch)
635+
636+
checksum = self.get_checksum_for(checksums[1:], index=idx)
637+
if verify_checksum(patch, checksum):
638+
self.log.info('Checksum for extension patch %s verified', patch_fn)
587639
elif build_option('ignore_checksums'):
588-
print_warning("Ignoring failing checksum verification for %s" % fn)
640+
print_warning("Ignoring failing checksum verification for %s" % patch_fn)
589641
else:
590-
raise EasyBuildError('Checksum verification for extension source %s failed', fn)
591-
592-
ext_patches = self.fetch_patches(patch_specs=ext_options.get('patches', []), extension=True)
593-
if ext_patches:
594-
self.log.debug('Found patches for extension %s: %s' % (ext_name, ext_patches))
595-
ext_src.update({'patches': ext_patches})
596-
597-
if not skip_checksums:
598-
for patch in ext_patches:
599-
patch = patch['path']
600-
# report both MD5 and SHA256 checksums,
601-
# since both are valid default checksum types
602-
for checksum_type in (CHECKSUM_TYPE_MD5, CHECKSUM_TYPE_SHA256):
603-
checksum = compute_checksum(patch, checksum_type=checksum_type)
604-
self.log.info("%s checksum for %s: %s", checksum_type, patch, checksum)
605-
606-
# verify checksum (if provided)
607-
self.log.debug('Verifying checksums for extension patches...')
608-
for idx, patch in enumerate(ext_patches):
609-
patch = patch['path']
610-
checksum = self.get_checksum_for(checksums[1:], index=idx)
611-
if verify_checksum(patch, checksum):
612-
self.log.info('Checksum for extension patch %s verified', patch)
613-
elif build_option('ignore_checksums'):
614-
print_warning("Ignoring failing checksum verification for %s" % patch)
615-
else:
616-
raise EasyBuildError('Checksum for extension patch %s failed', patch)
617-
else:
618-
self.log.debug('No patches found for extension %s.' % ext_name)
619-
620-
exts_sources.append(ext_src)
642+
raise EasyBuildError("Checksum verification for extension patch %s failed",
643+
patch_fn)
644+
else:
645+
self.log.debug('No patches found for extension %s.' % ext_name)
621646

622-
else:
623-
raise EasyBuildError("Source for extension %s not found.", ext)
647+
exts_sources.append(ext_src)
624648

625649
elif isinstance(ext, string_type):
626650
exts_sources.append({'name': ext})

easybuild/framework/easyconfig/easyconfig.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1251,9 +1251,9 @@ def probe_external_module_metadata(self, mod_name, existing_metadata=None):
12511251
soft_name = soft_name[len(cray_prefix):]
12521252

12531253
# determine software name to use in names of environment variables (upper case, '-' becomes '_')
1254-
soft_name_in_mod_name = convert_name(soft_name.replace('-', '_'), upper=True)
1254+
soft_name_env_var_infix = convert_name(soft_name.replace('-', '_'), upper=True)
12551255

1256-
var_name_pairs = [
1256+
var_name_pairs_templates = [
12571257
('CRAY_%s_PREFIX', 'CRAY_%s_VERSION'),
12581258
('CRAY_%s_PREFIX_DIR', 'CRAY_%s_VERSION'),
12591259
('CRAY_%s_DIR', 'CRAY_%s_VERSION'),
@@ -1264,10 +1264,20 @@ def probe_external_module_metadata(self, mod_name, existing_metadata=None):
12641264
('%s_HOME', '%s_VERSION'),
12651265
]
12661266

1267-
for prefix_var_name, version_var_name in var_name_pairs:
1268-
prefix_var_name = prefix_var_name % soft_name_in_mod_name
1269-
version_var_name = version_var_name % soft_name_in_mod_name
1267+
def mk_var_name_pair(var_name_pair, name):
1268+
"""Complete variable name pair template using provided name."""
1269+
return (var_name_pair[0] % name, var_name_pair[1] % name)
1270+
1271+
var_name_pairs = [mk_var_name_pair(x, soft_name_env_var_infix) for x in var_name_pairs_templates]
1272+
1273+
# also consider name based on module name for environment variables to check
1274+
# for example, for the cray-netcdf-hdf5parallel module we should also check $CRAY_NETCDF_HDF5PARALLEL_VERSION
1275+
mod_name_env_var_infix = convert_name(mod_name.split('/')[0].replace('-', '_'), upper=True)
12701276

1277+
if mod_name_env_var_infix != soft_name_env_var_infix:
1278+
var_name_pairs.extend([mk_var_name_pair(x, mod_name_env_var_infix) for x in var_name_pairs_templates])
1279+
1280+
for prefix_var_name, version_var_name in var_name_pairs:
12711281
prefix = self.modules_tool.get_setenv_value_from_modulefile(mod_name, prefix_var_name)
12721282
version = self.modules_tool.get_setenv_value_from_modulefile(mod_name, version_var_name)
12731283

easybuild/framework/easyconfig/format/format.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -612,12 +612,17 @@ def __init__(self):
612612
raise EasyBuildError('Invalid version number %s (incorrect length)', self.VERSION)
613613

614614
self.rawtext = None # text version of the easyconfig
615-
self.comments = {} # comments in easyconfig file
615+
self._comments = {} # comments in easyconfig file
616616
self.header = None # easyconfig header (e.g., format version, license, ...)
617617
self.docstring = None # easyconfig docstring (e.g., author, maintainer, ...)
618618

619619
self.specs = {}
620620

621+
@property
622+
def comments(self):
623+
"""Return comments in easyconfig file"""
624+
return self._comments
625+
621626
def set_specifications(self, specs):
622627
"""Set specifications."""
623628
self.log.debug('Set copy of specs %s' % specs)

easybuild/framework/easyconfig/format/one.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,8 @@ def parse(self, txt):
135135
"""
136136
Pre-process txt to extract header, docstring and pyheader, with non-indented section markers enforced.
137137
"""
138-
super(FormatOneZero, self).parse(txt, strict_section_markers=True)
138+
self.rawcontent = txt
139+
super(FormatOneZero, self).parse(self.rawcontent, strict_section_markers=True)
139140

140141
def _reformat_line(self, param_name, param_val, outer=False, addlen=0):
141142
"""
@@ -356,14 +357,24 @@ def dump(self, ecfg, default_values, templ_const, templ_val, toolchain_hierarchy
356357

357358
return '\n'.join(dump)
358359

360+
@property
361+
def comments(self):
362+
"""
363+
Return comments (and extract them first if needed).
364+
"""
365+
if not self._comments:
366+
self.extract_comments(self.rawcontent)
367+
368+
return self._comments
369+
359370
def extract_comments(self, rawtxt):
360371
"""
361372
Extract comments from raw content.
362373
363374
Discriminates between comment header, comments above a line (parameter definition), and inline comments.
364375
Inline comments on items of iterable values are also extracted.
365376
"""
366-
self.comments = {
377+
self._comments = {
367378
'above': {}, # comments above a parameter definition
368379
'header': [], # header comment lines
369380
'inline': {}, # inline comments

easybuild/framework/easyconfig/parser.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,6 @@ def __init__(self, filename=None, format_version=None, rawcontent=None,
110110
else:
111111
raise EasyBuildError("Neither filename nor rawcontent provided to EasyConfigParser")
112112

113-
self._formatter.extract_comments(self.rawcontent)
114-
115113
def process(self, filename=None):
116114
"""Create an instance"""
117115
self._read(filename=filename)

easybuild/framework/easyconfig/templates.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
# derived from easyconfig, but not from ._config directly
4848
TEMPLATE_NAMES_EASYCONFIG = [
4949
('arch', "System architecture (e.g. x86_64, aarch64, ppc64le, ...)"),
50+
('module_name', "Module name"),
5051
('nameletter', "First letter of software name"),
5152
('toolchain_name', "Toolchain name"),
5253
('toolchain_version', "Toolchain version"),
@@ -72,8 +73,8 @@
7273
]
7374
# values taken from the EasyBlock before each step
7475
TEMPLATE_NAMES_EASYBLOCK_RUN_STEP = [
75-
('installdir', "Installation directory"),
7676
('builddir', "Build directory"),
77+
('installdir', "Installation directory"),
7778
]
7879
# software names for which to define <pref>ver and <pref>shortver templates
7980
TEMPLATE_SOFTWARE_VERSIONS = [
@@ -208,6 +209,10 @@ def template_constant_dict(config, ignore=None, skip_lower=None, toolchain=None)
208209
softname = config['name']
209210
if softname is not None:
210211
template_values['nameletter'] = softname[0]
212+
213+
elif name[0] == 'module_name':
214+
template_values['module_name'] = getattr(config, 'short_mod_name', None)
215+
211216
else:
212217
raise EasyBuildError("Undefined name %s from TEMPLATE_NAMES_EASYCONFIG", name)
213218

0 commit comments

Comments
 (0)