Skip to content

Commit f032b12

Browse files
authored
Merge pull request #1 from boegel/github
prefer templates with shortest 'key' in case of duplicate template values in EasyConfig.dump
2 parents af0d1db + ea06c41 commit f032b12

84 files changed

Lines changed: 1887 additions & 388 deletions

Some content is hidden

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

.travis.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ python: 2.6
33
env:
44
matrix:
55
# purposely specifying slowest builds first, to gain time overall
6-
- LMOD_VERSION=5.8
7-
- LMOD_VERSION=5.8 TEST_EASYBUILD_MODULE_SYNTAX=Tcl
6+
- LMOD_VERSION=6.6.3
7+
- LMOD_VERSION=6.6.3 TEST_EASYBUILD_MODULE_SYNTAX=Tcl
88
- LMOD_VERSION=7.7.16
99
- LMOD_VERSION=7.7.16 TEST_EASYBUILD_MODULE_SYNTAX=Tcl
1010
- ENV_MOD_VERSION=3.2.10 TEST_EASYBUILD_MODULES_TOOL=EnvironmentModulesC TEST_EASYBUILD_MODULE_SYNTAX=Tcl
@@ -19,7 +19,7 @@ matrix:
1919
include:
2020
# also test default configuration with Python 2.7
2121
- python: 2.7
22-
env: LMOD_VERSION=5.8
22+
env: LMOD_VERSION=6.6.3
2323
addons:
2424
apt:
2525
packages:
@@ -51,7 +51,7 @@ before_install:
5151
# autopep8 1.3.4 is last one to support Python 2.6
5252
- if [ "x$TRAVIS_PYTHON_VERSION" == 'x2.6' ]; then pip install 'autopep8<1.3.5'; else pip install autopep8; fi
5353
# optional Python packages for EasyBuild
54-
- pip install GC3Pie pycodestyle python-graph-dot python-hglib PyYAML
54+
- pip install GC3Pie pycodestyle python-graph-dot python-hglib PyYAML requests
5555
# git config is required to make actual git commits (cfr. tests for GitRepository)
5656
- git config --global user.name "Travis CI"
5757
- git config --global user.email "travis@travis-ci.org"

RELEASE_NOTES

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,26 @@ 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+
v3.6.2 (July 11th 2018)
7+
-----------------------
8+
9+
bugfix release
10+
- various enhancements, including:
11+
- add support for including environment variable that is resolved at "module load time" in user module path (#2395)
12+
- {RUNTIME_ENV::EXAMPLE} is replaced by value of $EXAMPLE when module is loaded
13+
- also support generating Docker container recipes & image via --containerize (still experimental) (#2479)
14+
- add support for specifying source URLs directly in 'sources' (#2520)
15+
- perform early 'raw' parse of provided easyconfig file to check for syntax error or faulty inputs (#2523)
16+
- add 'bitbucket_account' easyconfig parameter and template, and let BITBUCKET* templates use it (#2525)
17+
- various bug fixes, including:
18+
- take into account --filter-deps when re-loading build dependencies in extensions_step (#2516)
19+
- fix for offline use of bootstrap script: ignore errors when determining source URLs if source tarballs are provided (#2517)
20+
- fix error message that is raised for incorrect type of value in sanity_check_paths (#2524)
21+
- other changes:
22+
- move flake8 config into setup.cfg + fix style issues in easybuild/tools/options.py (#2511)
23+
- make test that verifies that BuildOptions does not support updating a bit more flexible (#2518)
24+
25+
626
v3.6.1 (May 28th 2018)
727
----------------------
828

easybuild/framework/easyblock.py

Lines changed: 111 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,13 @@
7171
from easybuild.tools.filetools import CHECKSUM_TYPE_MD5, CHECKSUM_TYPE_SHA256
7272
from easybuild.tools.filetools import adjust_permissions, apply_patch, back_up_file, change_dir, convert_name
7373
from easybuild.tools.filetools import compute_checksum, copy_file, derive_alt_pypi_url, diff_files, download_file
74-
from easybuild.tools.filetools import encode_class_name, extract_file, is_alt_pypi_url, mkdir, move_logs, read_file
75-
from easybuild.tools.filetools import remove_file, rmtree2, verify_checksum, weld_paths, write_file
74+
from easybuild.tools.filetools import encode_class_name, extract_file, is_alt_pypi_url, is_sha256_checksum, mkdir
75+
from easybuild.tools.filetools import move_logs, read_file, remove_file, rmtree2, verify_checksum, weld_paths
76+
from easybuild.tools.filetools import write_file
7677
from easybuild.tools.hooks import BUILD_STEP, CLEANUP_STEP, CONFIGURE_STEP, EXTENSIONS_STEP, FETCH_STEP, INSTALL_STEP
7778
from easybuild.tools.hooks import MODULE_STEP, PACKAGE_STEP, PATCH_STEP, PERMISSIONS_STEP, POSTPROC_STEP, PREPARE_STEP
78-
from easybuild.tools.hooks import READY_STEP, SANITYCHECK_STEP, SOURCE_STEP, TEST_STEP, TESTCASES_STEP, run_hook
79+
from easybuild.tools.hooks import READY_STEP, SANITYCHECK_STEP, SOURCE_STEP, TEST_STEP, TESTCASES_STEP
80+
from easybuild.tools.hooks import load_hooks, run_hook
7981
from easybuild.tools.run import run_cmd
8082
from easybuild.tools.jenkins import write_to_xml
8183
from easybuild.tools.module_generator import ModuleGeneratorLua, ModuleGeneratorTcl, module_generator, dependencies_for
@@ -122,7 +124,7 @@ def extra_options(extra=None):
122124
#
123125
# INIT
124126
#
125-
def __init__(self, ec, hooks=None):
127+
def __init__(self, ec):
126128
"""
127129
Initialize the EasyBlock instance.
128130
:param ec: a parsed easyconfig file (EasyConfig instance)
@@ -132,7 +134,7 @@ def __init__(self, ec, hooks=None):
132134
self.orig_workdir = os.getcwd()
133135

134136
# list of pre- and post-step hooks
135-
self.hooks = hooks or []
137+
self.hooks = load_hooks(build_option('hooks'))
136138

137139
# list of patch/source files, along with checksums
138140
self.patches = []
@@ -488,7 +490,7 @@ def fetch_extension_sources(self, skip_checksums=False):
488490
'options': ext_options,
489491
}
490492

491-
checksums = ext_options.get('checksums', None)
493+
checksums = ext_options.get('checksums', [])
492494

493495
if ext_options.get('source_tmpl', None):
494496
fn = resolve_template(ext_options['source_tmpl'], ext_src)
@@ -511,14 +513,15 @@ def fetch_extension_sources(self, skip_checksums=False):
511513
src_checksum = compute_checksum(src_fn, checksum_type=checksum_type)
512514
self.log.info("%s checksum for %s: %s", checksum_type, src_fn, src_checksum)
513515

514-
if checksums:
515-
fn_checksum = self.get_checksum_for(checksums, filename=src_fn, index=0)
516-
if verify_checksum(src_fn, fn_checksum):
517-
self.log.info('Checksum for extension source %s verified', fn)
518-
elif build_option('ignore_checksums'):
519-
print_warning("Ignoring failing checksum verification for %s" % fn)
520-
else:
521-
raise EasyBuildError('Checksum verification for extension source %s failed', fn)
516+
# verify checksum (if provided)
517+
self.log.debug('Verifying checksums for extension source...')
518+
fn_checksum = self.get_checksum_for(checksums, filename=src_fn, index=0)
519+
if verify_checksum(src_fn, fn_checksum):
520+
self.log.info('Checksum for extension source %s verified', fn)
521+
elif build_option('ignore_checksums'):
522+
print_warning("Ignoring failing checksum verification for %s" % fn)
523+
else:
524+
raise EasyBuildError('Checksum verification for extension source %s failed', fn)
522525

523526
ext_patches = self.fetch_patches(patch_specs=ext_options.get('patches', []), extension=True)
524527
if ext_patches:
@@ -533,16 +536,16 @@ def fetch_extension_sources(self, skip_checksums=False):
533536
checksum = compute_checksum(patch, checksum_type=checksum_type)
534537
self.log.info("%s checksum for %s: %s", checksum_type, patch, checksum)
535538

536-
if checksums:
537-
self.log.debug('Verifying checksums for extension patches...')
538-
for idx, patch in enumerate(ext_patches):
539-
checksum = self.get_checksum_for(checksums[1:], filename=patch, index=idx)
540-
if verify_checksum(patch, checksum):
541-
self.log.info('Checksum for extension patch %s verified', patch)
542-
elif build_option('ignore_checksums'):
543-
print_warning("Ignoring failing checksum verification for %s" % patch)
544-
else:
545-
raise EasyBuildError('Checksum for extension patch %s failed', patch)
539+
# verify checksum (if provided)
540+
self.log.debug('Verifying checksums for extension patches...')
541+
for idx, patch in enumerate(ext_patches):
542+
checksum = self.get_checksum_for(checksums[1:], filename=patch, index=idx)
543+
if verify_checksum(patch, checksum):
544+
self.log.info('Checksum for extension patch %s verified', patch)
545+
elif build_option('ignore_checksums'):
546+
print_warning("Ignoring failing checksum verification for %s" % patch)
547+
else:
548+
raise EasyBuildError('Checksum for extension patch %s failed', patch)
546549
else:
547550
self.log.debug('No patches found for extension %s.' % ext_name)
548551

@@ -1678,6 +1681,63 @@ def checksum_step(self):
16781681
else:
16791682
raise EasyBuildError("Checksum verification for %s using %s failed.", fil['path'], fil['checksum'])
16801683

1684+
def check_checksums_for(self, ent, sub='', source_cnt=None):
1685+
"""
1686+
Utility method: check whether checksums for all sources/patches are available, for given entity
1687+
"""
1688+
ec_fn = os.path.basename(self.cfg.path)
1689+
checksum_issues = []
1690+
1691+
sources = ent.get('sources', [])
1692+
patches = ent.get('patches', [])
1693+
checksums = ent.get('checksums', [])
1694+
1695+
if source_cnt is None:
1696+
source_cnt = len(sources)
1697+
patch_cnt, checksum_cnt = len(patches), len(checksums)
1698+
1699+
if (source_cnt + patch_cnt) != checksum_cnt:
1700+
if sub:
1701+
sub = "%s in %s" % (sub, ec_fn)
1702+
else:
1703+
sub = "in %s" % ec_fn
1704+
msg = "Checksums missing for one or more sources/patches %s: " % sub
1705+
msg += "found %d sources + %d patches " % (source_cnt, patch_cnt)
1706+
msg += "vs %d checksums" % checksum_cnt
1707+
checksum_issues.append(msg)
1708+
1709+
for fn, checksum in zip(sources + patches, checksums):
1710+
if not is_sha256_checksum(checksum):
1711+
msg = "Non-SHA256 checksum found for %s: %s" % (fn, checksum)
1712+
checksum_issues.append(msg)
1713+
1714+
return checksum_issues
1715+
1716+
def check_checksums(self):
1717+
"""
1718+
Check whether a SHA256 checksum is available for all sources & patches (incl. extensions).
1719+
1720+
:return: list of strings describing checksum issues (missing checksums, wrong checksum type, etc.)
1721+
"""
1722+
checksum_issues = []
1723+
1724+
# check whether a checksum if available for every source + patch
1725+
checksum_issues.extend(self.check_checksums_for(self.cfg))
1726+
1727+
# also check checksums for extensions
1728+
for ext in self.cfg['exts_list']:
1729+
# just skip extensions for which only a name is specified
1730+
# those are just there to check for things that are in the "standard library"
1731+
if not isinstance(ext, basestring):
1732+
ext_name = ext[0]
1733+
# take into account that extension may be a 2-tuple with just name/version
1734+
ext_opts = ext[2] if len(ext) == 3 else {}
1735+
# only a single source per extension is supported (see source_tmpl)
1736+
res = self.check_checksums_for(ext_opts, sub="of extension %s" % ext_name, source_cnt=1)
1737+
checksum_issues.extend(res)
1738+
1739+
return checksum_issues
1740+
16811741
def extract_step(self):
16821742
"""
16831743
Unpack the source files.
@@ -2337,6 +2397,17 @@ def cleanup_step(self):
23372397

23382398
self.restore_iterate_opts()
23392399

2400+
def invalidate_module_caches(self, modpath):
2401+
"""Helper method to invalidate module caches for specified module path."""
2402+
# invalidate relevant 'module avail'/'module show' cache entries
2403+
# consider both paths: for short module name, and subdir indicated by long module name
2404+
paths = [modpath]
2405+
if self.mod_subdir:
2406+
paths.append(os.path.join(modpath, self.mod_subdir))
2407+
2408+
for path in paths:
2409+
invalidate_module_caches_for(path)
2410+
23402411
def make_module_step(self, fake=False):
23412412
"""
23422413
Generate module file
@@ -2386,14 +2457,7 @@ def make_module_step(self, fake=False):
23862457
self.log.info(diff_msg)
23872458
print_msg(diff_msg, log=self.log)
23882459

2389-
# invalidate relevant 'module avail'/'module show' cache entries
2390-
# consider both paths: for short module name, and subdir indicated by long module name
2391-
paths = [modpath]
2392-
if self.mod_subdir:
2393-
paths.append(os.path.join(modpath, self.mod_subdir))
2394-
2395-
for path in paths:
2396-
invalidate_module_caches_for(path)
2460+
self.invalidate_module_caches(modpath)
23972461

23982462
# only update after generating final module file
23992463
if not fake:
@@ -2498,8 +2562,11 @@ def _skip_step(self, step, skippable):
24982562
force = build_option('force') or build_option('rebuild')
24992563
skip = False
25002564

2501-
# skip step if specified as individual (skippable) step
2502-
if skippable and (self.skip or step in self.cfg['skipsteps']):
2565+
# under --skip, sanity check is not skipped
2566+
cli_skip = self.skip and step != SANITYCHECK_STEP
2567+
2568+
# skip step if specified as individual (skippable) step, or if --skip is used
2569+
if skippable and (cli_skip or step in self.cfg['skipsteps']):
25032570
self.log.info("Skipping %s step (skip: %s, skipsteps: %s)", step, self.skip, self.cfg['skipsteps'])
25042571
skip = True
25052572

@@ -2645,7 +2712,7 @@ def install_step_spec(initial):
26452712
steps_part3 = [
26462713
(EXTENSIONS_STEP, 'taking care of extensions', [lambda x: x.extensions_step], False),
26472714
(POSTPROC_STEP, 'postprocessing', [lambda x: x.post_install_step], True),
2648-
(SANITYCHECK_STEP, 'sanity checking', [lambda x: x.sanity_check_step], False),
2715+
(SANITYCHECK_STEP, 'sanity checking', [lambda x: x.sanity_check_step], True),
26492716
(CLEANUP_STEP, 'cleaning up', [lambda x: x.cleanup_step], False),
26502717
(MODULE_STEP, 'creating module', [lambda x: x.make_module_step], False),
26512718
(PERMISSIONS_STEP, 'permissions', [lambda x: x.permissions_step], False),
@@ -2708,7 +2775,7 @@ def print_dry_run_note(loc, silent=True):
27082775
dry_run_msg(msg, silent=silent)
27092776

27102777

2711-
def build_and_install_one(ecdict, init_env, hooks=None):
2778+
def build_and_install_one(ecdict, init_env):
27122779
"""
27132780
Build the software
27142781
:param ecdict: dictionary contaning parsed easyconfig + metadata
@@ -2746,7 +2813,7 @@ def build_and_install_one(ecdict, init_env, hooks=None):
27462813
try:
27472814
app_class = get_easyblock_class(easyblock, name=name)
27482815

2749-
app = app_class(ecdict['ec'], hooks=hooks)
2816+
app = app_class(ecdict['ec'])
27502817
_log.info("Obtained application instance of for %s (easyblock: %s)" % (name, easyblock))
27512818
except EasyBuildError, err:
27522819
print_error("Failed to get application instance for %s (easyblock: %s): %s" % (name, easyblock, err.msg),
@@ -2807,13 +2874,14 @@ def build_and_install_one(ecdict, init_env, hooks=None):
28072874
buildstats = get_build_stats(app, start_time, build_option('command_line'))
28082875
_log.info("Build stats: %s" % buildstats)
28092876

2810-
if build_option("minimal_toolchains"):
2811-
# for reproducability we dump out the parsed easyconfig since the contents are affected when
2812-
# --minimal-toolchains (and --use-existing-modules) is used
2813-
# TODO --try-toolchain needs to be fixed so this doesn't play havoc with it's usability
2814-
reprod_spec = os.path.join(new_log_dir, 'reprod', ec_filename)
2877+
# for reproducability we dump out the fully processed easyconfig since the contents can be affected
2878+
# by subtoolchain resolution (and related options) and/or hooks
2879+
reprod_spec = os.path.join(new_log_dir, 'reprod', ec_filename)
2880+
try:
28152881
app.cfg.dump(reprod_spec)
2816-
_log.debug("Dumped easyconfig tweaked via --minimal-toolchains to %s", reprod_spec)
2882+
_log.info("Dumped fully processed easyconfig to %s", reprod_spec)
2883+
except NotImplementedError as err:
2884+
_log.warn("Unable to dumped fully processed easyconfig to %s: %s", reprod_spec, err)
28172885

28182886
try:
28192887
# upload easyconfig (and patch files) to central repository

0 commit comments

Comments
 (0)