Skip to content

Commit 0454617

Browse files
Merge pull request #3526 from easybuilders/4.3.x
release EasyBuild v4.3.2
2 parents 1aeca6f + 51535c5 commit 0454617

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1543
-221
lines changed

.github/workflows/unit_tests.yml

Lines changed: 12 additions & 4 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
@@ -122,6 +130,9 @@ jobs:
122130
EASYBUILD_MODULE_SYNTAX: ${{matrix.module_syntax}}
123131
TEST_EASYBUILD_MODULE_SYNTAX: ${{matrix.module_syntax}}
124132
run: |
133+
# run tests *outside* of checked out easybuild-framework directory,
134+
# to ensure we're testing installed version (see previous step)
135+
cd $HOME
125136
# initialize environment for modules tool
126137
if [ -f $HOME/moduleshome ]; then export MODULESHOME=$(cat $HOME/moduleshome); fi
127138
source $(cat $HOME/mod_init); type module
@@ -153,15 +164,12 @@ jobs:
153164
# run test suite
154165
python -O -m test.framework.suite 2>&1 | tee test_framework_suite.log
155166
# try and make sure output of running tests is clean (no printed messages/warnings)
156-
IGNORE_PATTERNS="no GitHub token available|skipping SvnRepository test|requires Lmod as modules tool|stty: 'standard input': Inappropriate ioctl for device|CryptographyDeprecationWarning: Python 3.5|from cryptography.*default_backend"
167+
IGNORE_PATTERNS="no GitHub token available|skipping SvnRepository test|requires Lmod as modules tool|stty: 'standard input': Inappropriate ioctl for device|CryptographyDeprecationWarning: Python 3.5|from cryptography.*default_backend|CryptographyDeprecationWarning: Python 2"
157168
# '|| true' is needed to avoid that Travis stops the job on non-zero exit of grep (i.e. when there are no matches)
158169
PRINTED_MSG=$(egrep -v "${IGNORE_PATTERNS}" test_framework_suite.log | grep '\.\n*[A-Za-z]' || true)
159170
test "x$PRINTED_MSG" = "x" || (echo "ERROR: Found printed messages in output of test suite\n${PRINTED_MSG}" && exit 1)
160171
161172
- name: test bootstrap script
162-
# skip testing of bootstrap script with Python 3.9,
163-
# until an EasyBuild release that is compatible with Python 3.9 is available
164-
if: ${{ matrix.python != 3.9 }}
165173
run: |
166174
# (re)initialize environment for modules tool
167175
if [ -f $HOME/moduleshome ]; then export MODULESHOME=$(cat $HOME/moduleshome); fi

MANIFEST.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ include optcomplete.bash
44
recursive-include etc *
55
recursive-include easybuild *py
66
recursive-include easybuild/scripts *
7-
recursive-include test *py *eb
7+
recursive-include test *py *eb *yaml
88
recursive-include test/framework/modules *
99
recursive-include test/framework/sandbox/sources *
1010
include CONTRIBUTING.md

RELEASE_NOTES

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,41 @@ 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.2 (December 10th 2020)
7+
---------------------------
8+
9+
update/bugfix release
10+
11+
- add (experimental) support for specifying easyconfig files via an "easystack" file (#3479, #3511, #3515, #3517, #3520, #3521)
12+
- see also https://easybuild.readthedocs.io/en/latest/Easystack-files.html
13+
- add definition for new 'gobff' toolchain using BLIS and LibFLAME (#3505)
14+
- various enhancements, including:
15+
- add support for toolchain options like 'extra_cxxflags' to specify extra compiler options (#2193)
16+
- fix combination of --copy-ec and --from-pr (#3482)
17+
- enhance copy_files function: support single file target, error on empty input list, support verbose mode (#3483)
18+
- cache result of fetch_files_from_pr function (mainly to speed up framework test suite) (#3484)
19+
- add locate_files function to filetools module (#3485)
20+
- add support for %(module_name)s template value (#3497)
21+
- clarify input format for --cuda-compute-capabilities in 'eb --help' output (#3509)
22+
- add support for skiping unit tests (test step) via --skip-test-step (#3524)
23+
- various bug fixes, including:
24+
- also ignore vsc.* imports coming from from pkg_resources/__init__.py (setuptools) in fake vsc namespace (#3491)
25+
- don't pass username in github_api_get_request when no GitHub token is available (#3494)
26+
- also inject -rpath options for all entries in $LIBRARY_PATH in RPATH wrappers (#3495)
27+
- avoid TypeError being raised by list_toolchains (#3499)
28+
- check if PR is already merged in --merge-pr (#3502)
29+
- graciously handle wrong PR id in fetch_pr_data (#3503)
30+
- fix regression in apply_regex_substitutions: also accept list of paths to patch (#3507)
31+
- update installation procedure for EasyBuild in generated Singularity container recipes (#3510)
32+
- fix GitHub Actions workflow for test suite: run outside of repo checkout + also test bootstrap script with Python 3.9 (#3518)
33+
- bump cryptography from 2.9.2 to 3.2 for Python 2 in requirements.txt (#3519)
34+
- fix 'eb --help=rst' when running with Python 3 (#3525)
35+
- other changes:
36+
- exclude test configurations with Lmod 7 and Python 3, except for Python 3.6 (#3496)
37+
- significantly speed up parsing of easyconfig files by only extracting comments from an easyconfig file when they're actually needed (#3498)
38+
- don't include file/ldd/readelf commands run during RPATH sanity check in --trace output (#3508)
39+
40+
641
v4.3.1 (October 29th 2020)
742
--------------------------
843

easybuild/framework/easyblock.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2139,10 +2139,11 @@ def build_step(self):
21392139

21402140
def test_step(self):
21412141
"""Run unit tests provided by software (if any)."""
2142-
if self.cfg['runtest']:
2142+
unit_test_cmd = self.cfg['runtest']
2143+
if unit_test_cmd:
21432144

2144-
self.log.debug("Trying to execute %s as a command for running unit tests...")
2145-
(out, _) = run_cmd(self.cfg['runtest'], log_all=True, simple=False)
2145+
self.log.debug("Trying to execute %s as a command for running unit tests...", unit_test_cmd)
2146+
(out, _) = run_cmd(unit_test_cmd, log_all=True, simple=False)
21462147

21472148
return out
21482149

@@ -2460,7 +2461,7 @@ def sanity_check_rpath(self, rpath_dirs=None):
24602461
for path in [os.path.join(dirpath, x) for x in os.listdir(dirpath)]:
24612462
self.log.debug("Sanity checking RPATH for %s", path)
24622463

2463-
out, ec = run_cmd("file %s" % path, simple=False)
2464+
out, ec = run_cmd("file %s" % path, simple=False, trace=False)
24642465
if ec:
24652466
fails.append("Failed to run 'file %s': %s" % (path, out))
24662467

@@ -2470,7 +2471,7 @@ def sanity_check_rpath(self, rpath_dirs=None):
24702471
# ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, not stripped
24712472
if "dynamically linked" in out:
24722473
# check whether all required libraries are found via 'ldd'
2473-
out, ec = run_cmd("ldd %s" % path, simple=False)
2474+
out, ec = run_cmd("ldd %s" % path, simple=False, trace=False)
24742475
if ec:
24752476
fail_msg = "Failed to run 'ldd %s': %s" % (path, out)
24762477
self.log.warning(fail_msg)
@@ -2483,7 +2484,7 @@ def sanity_check_rpath(self, rpath_dirs=None):
24832484
self.log.debug("Output of 'ldd %s' checked, looks OK", path)
24842485

24852486
# check whether RPATH section in 'readelf -d' output is there
2486-
out, ec = run_cmd("readelf -d %s" % path, simple=False)
2487+
out, ec = run_cmd("readelf -d %s" % path, simple=False, trace=False)
24872488
if ec:
24882489
fail_msg = "Failed to run 'readelf %s': %s" % (path, out)
24892490
self.log.warning(fail_msg)
@@ -3310,6 +3311,10 @@ def build_and_install_one(ecdict, init_env):
33103311
_log.debug("Skip set to %s" % skip)
33113312
app.cfg['skip'] = skip
33123313

3314+
if build_option('skip_test_step'):
3315+
_log.debug('Adding test_step to skipped steps')
3316+
app.cfg.update('skipsteps', TEST_STEP, allow_duplicate=False)
3317+
33133318
# build easyconfig
33143319
errormsg = '(no error)'
33153320
# timing info

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

easybuild/framework/easyconfig/tools.py

Lines changed: 72 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,9 @@
5353
from easybuild.tools.build_log import EasyBuildError, print_msg, print_warning
5454
from easybuild.tools.config import build_option
5555
from easybuild.tools.environment import restore_env
56-
from easybuild.tools.filetools import find_easyconfigs, is_patch_file, read_file, resolve_path, which, write_file
57-
from easybuild.tools.github import fetch_easyconfigs_from_pr, download_repo
56+
from easybuild.tools.filetools import find_easyconfigs, is_patch_file, locate_files
57+
from easybuild.tools.filetools import read_file, resolve_path, which, write_file
58+
from easybuild.tools.github import fetch_easyconfigs_from_pr, fetch_files_from_pr, download_repo
5859
from easybuild.tools.multidiff import multidiff
5960
from easybuild.tools.py2vs3 import OrderedDict
6061
from easybuild.tools.toolchain.toolchain import is_system_toolchain
@@ -348,44 +349,13 @@ def det_easyconfig_paths(orig_paths):
348349
ec_files = [path for path in pr_files if path.endswith('.eb')]
349350

350351
if ec_files and robot_path:
351-
# look for easyconfigs with relative paths in robot search path,
352-
# unless they were found at the given relative paths
353-
354-
# determine which easyconfigs files need to be found, if any
355-
ecs_to_find = []
356-
for idx, ec_file in enumerate(ec_files):
357-
if ec_file == os.path.basename(ec_file) and not os.path.exists(ec_file):
358-
ecs_to_find.append((idx, ec_file))
359-
_log.debug("List of easyconfig files to find: %s" % ecs_to_find)
360-
361-
# find missing easyconfigs by walking paths in robot search path
362-
for path in robot_path:
363-
_log.debug("Looking for missing easyconfig files (%d left) in %s..." % (len(ecs_to_find), path))
364-
for (subpath, dirnames, filenames) in os.walk(path, topdown=True):
365-
for idx, orig_path in ecs_to_find[:]:
366-
if orig_path in filenames:
367-
full_path = os.path.join(subpath, orig_path)
368-
_log.info("Found %s in %s: %s" % (orig_path, path, full_path))
369-
ec_files[idx] = full_path
370-
# if file was found, stop looking for it (first hit wins)
371-
ecs_to_find.remove((idx, orig_path))
372-
373-
# stop os.walk insanity as soon as we have all we need (os.walk loop)
374-
if not ecs_to_find:
375-
break
376-
377-
# ignore subdirs specified to be ignored by replacing items in dirnames list used by os.walk
378-
dirnames[:] = [d for d in dirnames if d not in build_option('ignore_dirs')]
379-
380-
# ignore archived easyconfigs, unless specified otherwise
381-
if not build_option('consider_archived_easyconfigs'):
382-
dirnames[:] = [d for d in dirnames if d != EASYCONFIGS_ARCHIVE_DIR]
383-
384-
# stop os.walk insanity as soon as we have all we need (outer loop)
385-
if not ecs_to_find:
386-
break
387-
388-
return [os.path.abspath(ec_file) for ec_file in ec_files]
352+
ignore_subdirs = build_option('ignore_dirs')
353+
if not build_option('consider_archived_easyconfigs'):
354+
ignore_subdirs.append(EASYCONFIGS_ARCHIVE_DIR)
355+
356+
ec_files = locate_files(ec_files, robot_path, ignore_subdirs=ignore_subdirs)
357+
358+
return ec_files
389359

390360

391361
def parse_easyconfigs(paths, validate=True):
@@ -728,3 +698,65 @@ def avail_easyblocks():
728698
easyblock_mod_name, easyblocks[easyblock_mod_name]['loc'], path)
729699

730700
return easyblocks
701+
702+
703+
def det_copy_ec_specs(orig_paths, from_pr):
704+
"""Determine list of paths + target directory for --copy-ec."""
705+
706+
target_path, paths = None, []
707+
708+
# if only one argument is specified, use current directory as target directory
709+
if len(orig_paths) == 1:
710+
target_path = os.getcwd()
711+
paths = orig_paths[:]
712+
713+
# if multiple arguments are specified, assume that last argument is target location,
714+
# and remove that from list of paths to copy
715+
elif orig_paths:
716+
target_path = orig_paths[-1]
717+
paths = orig_paths[:-1]
718+
719+
# if --from-pr was used in combination with --copy-ec, some extra care must be taken
720+
if from_pr:
721+
# pull in the paths to all the changed files in the PR,
722+
# which includes easyconfigs but also patch files (& maybe more);
723+
# do this in a dedicated subdirectory of the working tmpdir,
724+
# to avoid potential trouble with already existing files in the working tmpdir
725+
# (note: we use a fixed subdirectory in the working tmpdir here rather than a unique random subdirectory,
726+
# to ensure that the caching for fetch_files_from_pr works across calls for the same PR)
727+
tmpdir = os.path.join(tempfile.gettempdir(), 'fetch_files_from_pr_%s' % from_pr)
728+
pr_paths = fetch_files_from_pr(pr=from_pr, path=tmpdir)
729+
730+
# assume that files need to be copied to current working directory for now
731+
target_path = os.getcwd()
732+
733+
if orig_paths:
734+
last_path = orig_paths[-1]
735+
736+
# check files touched by PR and see if the target directory for --copy-ec
737+
# corresponds to the name of one of these files;
738+
# if so we should copy the specified file(s) to the current working directory,
739+
# since interpreting the last argument as target location is very unlikely to be correct in this case
740+
pr_filenames = [os.path.basename(p) for p in pr_paths]
741+
if last_path in pr_filenames:
742+
paths = orig_paths[:]
743+
else:
744+
target_path = last_path
745+
# exclude last argument that is used as target location
746+
paths = orig_paths[:-1]
747+
748+
# if list of files to copy is empty at this point,
749+
# we simply copy *all* files touched by the PR
750+
if not paths:
751+
paths = pr_paths
752+
753+
# replace path for files touched by PR (no need to worry about others)
754+
for idx, path in enumerate(paths):
755+
filename = os.path.basename(path)
756+
pr_matches = [x for x in pr_paths if os.path.basename(x) == filename]
757+
if len(pr_matches) == 1:
758+
paths[idx] = pr_matches[0]
759+
elif pr_matches:
760+
raise EasyBuildError("Found multiple paths for %s in PR: %s", filename, pr_matches)
761+
762+
return paths, target_path

0 commit comments

Comments
 (0)