Skip to content

Commit

Permalink
Merge pull request #1 from boegel/github
Browse files Browse the repository at this point in the history
prefer templates with shortest 'key' in case of duplicate template values in EasyConfig.dump
  • Loading branch information
SethosII authored Sep 17, 2018
2 parents af0d1db + ea06c41 commit f032b12
Show file tree
Hide file tree
Showing 84 changed files with 1,887 additions and 388 deletions.
8 changes: 4 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ python: 2.6
env:
matrix:
# purposely specifying slowest builds first, to gain time overall
- LMOD_VERSION=5.8
- LMOD_VERSION=5.8 TEST_EASYBUILD_MODULE_SYNTAX=Tcl
- LMOD_VERSION=6.6.3
- LMOD_VERSION=6.6.3 TEST_EASYBUILD_MODULE_SYNTAX=Tcl
- LMOD_VERSION=7.7.16
- LMOD_VERSION=7.7.16 TEST_EASYBUILD_MODULE_SYNTAX=Tcl
- ENV_MOD_VERSION=3.2.10 TEST_EASYBUILD_MODULES_TOOL=EnvironmentModulesC TEST_EASYBUILD_MODULE_SYNTAX=Tcl
Expand All @@ -19,7 +19,7 @@ matrix:
include:
# also test default configuration with Python 2.7
- python: 2.7
env: LMOD_VERSION=5.8
env: LMOD_VERSION=6.6.3
addons:
apt:
packages:
Expand Down Expand Up @@ -51,7 +51,7 @@ before_install:
# autopep8 1.3.4 is last one to support Python 2.6
- if [ "x$TRAVIS_PYTHON_VERSION" == 'x2.6' ]; then pip install 'autopep8<1.3.5'; else pip install autopep8; fi
# optional Python packages for EasyBuild
- pip install GC3Pie pycodestyle python-graph-dot python-hglib PyYAML
- pip install GC3Pie pycodestyle python-graph-dot python-hglib PyYAML requests
# git config is required to make actual git commits (cfr. tests for GitRepository)
- git config --global user.name "Travis CI"
- git config --global user.email "travis@travis-ci.org"
Expand Down
20 changes: 20 additions & 0 deletions RELEASE_NOTES
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,26 @@ For more detailed information, please see the git log.

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

v3.6.2 (July 11th 2018)
-----------------------

bugfix release
- various enhancements, including:
- add support for including environment variable that is resolved at "module load time" in user module path (#2395)
- {RUNTIME_ENV::EXAMPLE} is replaced by value of $EXAMPLE when module is loaded
- also support generating Docker container recipes & image via --containerize (still experimental) (#2479)
- add support for specifying source URLs directly in 'sources' (#2520)
- perform early 'raw' parse of provided easyconfig file to check for syntax error or faulty inputs (#2523)
- add 'bitbucket_account' easyconfig parameter and template, and let BITBUCKET* templates use it (#2525)
- various bug fixes, including:
- take into account --filter-deps when re-loading build dependencies in extensions_step (#2516)
- fix for offline use of bootstrap script: ignore errors when determining source URLs if source tarballs are provided (#2517)
- fix error message that is raised for incorrect type of value in sanity_check_paths (#2524)
- other changes:
- move flake8 config into setup.cfg + fix style issues in easybuild/tools/options.py (#2511)
- make test that verifies that BuildOptions does not support updating a bit more flexible (#2518)


v3.6.1 (May 28th 2018)
----------------------

Expand Down
154 changes: 111 additions & 43 deletions easybuild/framework/easyblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,13 @@
from easybuild.tools.filetools import CHECKSUM_TYPE_MD5, CHECKSUM_TYPE_SHA256
from easybuild.tools.filetools import adjust_permissions, apply_patch, back_up_file, change_dir, convert_name
from easybuild.tools.filetools import compute_checksum, copy_file, derive_alt_pypi_url, diff_files, download_file
from easybuild.tools.filetools import encode_class_name, extract_file, is_alt_pypi_url, mkdir, move_logs, read_file
from easybuild.tools.filetools import remove_file, rmtree2, verify_checksum, weld_paths, write_file
from easybuild.tools.filetools import encode_class_name, extract_file, is_alt_pypi_url, is_sha256_checksum, mkdir
from easybuild.tools.filetools import move_logs, read_file, remove_file, rmtree2, verify_checksum, weld_paths
from easybuild.tools.filetools import write_file
from easybuild.tools.hooks import BUILD_STEP, CLEANUP_STEP, CONFIGURE_STEP, EXTENSIONS_STEP, FETCH_STEP, INSTALL_STEP
from easybuild.tools.hooks import MODULE_STEP, PACKAGE_STEP, PATCH_STEP, PERMISSIONS_STEP, POSTPROC_STEP, PREPARE_STEP
from easybuild.tools.hooks import READY_STEP, SANITYCHECK_STEP, SOURCE_STEP, TEST_STEP, TESTCASES_STEP, run_hook
from easybuild.tools.hooks import READY_STEP, SANITYCHECK_STEP, SOURCE_STEP, TEST_STEP, TESTCASES_STEP
from easybuild.tools.hooks import load_hooks, run_hook
from easybuild.tools.run import run_cmd
from easybuild.tools.jenkins import write_to_xml
from easybuild.tools.module_generator import ModuleGeneratorLua, ModuleGeneratorTcl, module_generator, dependencies_for
Expand Down Expand Up @@ -122,7 +124,7 @@ def extra_options(extra=None):
#
# INIT
#
def __init__(self, ec, hooks=None):
def __init__(self, ec):
"""
Initialize the EasyBlock instance.
:param ec: a parsed easyconfig file (EasyConfig instance)
Expand All @@ -132,7 +134,7 @@ def __init__(self, ec, hooks=None):
self.orig_workdir = os.getcwd()

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

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

checksums = ext_options.get('checksums', None)
checksums = ext_options.get('checksums', [])

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

if checksums:
fn_checksum = self.get_checksum_for(checksums, filename=src_fn, index=0)
if verify_checksum(src_fn, fn_checksum):
self.log.info('Checksum for extension source %s verified', fn)
elif build_option('ignore_checksums'):
print_warning("Ignoring failing checksum verification for %s" % fn)
else:
raise EasyBuildError('Checksum verification for extension source %s failed', fn)
# verify checksum (if provided)
self.log.debug('Verifying checksums for extension source...')
fn_checksum = self.get_checksum_for(checksums, filename=src_fn, index=0)
if verify_checksum(src_fn, fn_checksum):
self.log.info('Checksum for extension source %s verified', fn)
elif build_option('ignore_checksums'):
print_warning("Ignoring failing checksum verification for %s" % fn)
else:
raise EasyBuildError('Checksum verification for extension source %s failed', fn)

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

if checksums:
self.log.debug('Verifying checksums for extension patches...')
for idx, patch in enumerate(ext_patches):
checksum = self.get_checksum_for(checksums[1:], filename=patch, index=idx)
if verify_checksum(patch, checksum):
self.log.info('Checksum for extension patch %s verified', patch)
elif build_option('ignore_checksums'):
print_warning("Ignoring failing checksum verification for %s" % patch)
else:
raise EasyBuildError('Checksum for extension patch %s failed', patch)
# verify checksum (if provided)
self.log.debug('Verifying checksums for extension patches...')
for idx, patch in enumerate(ext_patches):
checksum = self.get_checksum_for(checksums[1:], filename=patch, index=idx)
if verify_checksum(patch, checksum):
self.log.info('Checksum for extension patch %s verified', patch)
elif build_option('ignore_checksums'):
print_warning("Ignoring failing checksum verification for %s" % patch)
else:
raise EasyBuildError('Checksum for extension patch %s failed', patch)
else:
self.log.debug('No patches found for extension %s.' % ext_name)

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

def check_checksums_for(self, ent, sub='', source_cnt=None):
"""
Utility method: check whether checksums for all sources/patches are available, for given entity
"""
ec_fn = os.path.basename(self.cfg.path)
checksum_issues = []

sources = ent.get('sources', [])
patches = ent.get('patches', [])
checksums = ent.get('checksums', [])

if source_cnt is None:
source_cnt = len(sources)
patch_cnt, checksum_cnt = len(patches), len(checksums)

if (source_cnt + patch_cnt) != checksum_cnt:
if sub:
sub = "%s in %s" % (sub, ec_fn)
else:
sub = "in %s" % ec_fn
msg = "Checksums missing for one or more sources/patches %s: " % sub
msg += "found %d sources + %d patches " % (source_cnt, patch_cnt)
msg += "vs %d checksums" % checksum_cnt
checksum_issues.append(msg)

for fn, checksum in zip(sources + patches, checksums):
if not is_sha256_checksum(checksum):
msg = "Non-SHA256 checksum found for %s: %s" % (fn, checksum)
checksum_issues.append(msg)

return checksum_issues

def check_checksums(self):
"""
Check whether a SHA256 checksum is available for all sources & patches (incl. extensions).
:return: list of strings describing checksum issues (missing checksums, wrong checksum type, etc.)
"""
checksum_issues = []

# check whether a checksum if available for every source + patch
checksum_issues.extend(self.check_checksums_for(self.cfg))

# also check checksums for extensions
for ext in self.cfg['exts_list']:
# just skip extensions for which only a name is specified
# those are just there to check for things that are in the "standard library"
if not isinstance(ext, basestring):
ext_name = ext[0]
# take into account that extension may be a 2-tuple with just name/version
ext_opts = ext[2] if len(ext) == 3 else {}
# only a single source per extension is supported (see source_tmpl)
res = self.check_checksums_for(ext_opts, sub="of extension %s" % ext_name, source_cnt=1)
checksum_issues.extend(res)

return checksum_issues

def extract_step(self):
"""
Unpack the source files.
Expand Down Expand Up @@ -2337,6 +2397,17 @@ def cleanup_step(self):

self.restore_iterate_opts()

def invalidate_module_caches(self, modpath):
"""Helper method to invalidate module caches for specified module path."""
# invalidate relevant 'module avail'/'module show' cache entries
# consider both paths: for short module name, and subdir indicated by long module name
paths = [modpath]
if self.mod_subdir:
paths.append(os.path.join(modpath, self.mod_subdir))

for path in paths:
invalidate_module_caches_for(path)

def make_module_step(self, fake=False):
"""
Generate module file
Expand Down Expand Up @@ -2386,14 +2457,7 @@ def make_module_step(self, fake=False):
self.log.info(diff_msg)
print_msg(diff_msg, log=self.log)

# invalidate relevant 'module avail'/'module show' cache entries
# consider both paths: for short module name, and subdir indicated by long module name
paths = [modpath]
if self.mod_subdir:
paths.append(os.path.join(modpath, self.mod_subdir))

for path in paths:
invalidate_module_caches_for(path)
self.invalidate_module_caches(modpath)

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

# skip step if specified as individual (skippable) step
if skippable and (self.skip or step in self.cfg['skipsteps']):
# under --skip, sanity check is not skipped
cli_skip = self.skip and step != SANITYCHECK_STEP

# skip step if specified as individual (skippable) step, or if --skip is used
if skippable and (cli_skip or step in self.cfg['skipsteps']):
self.log.info("Skipping %s step (skip: %s, skipsteps: %s)", step, self.skip, self.cfg['skipsteps'])
skip = True

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


def build_and_install_one(ecdict, init_env, hooks=None):
def build_and_install_one(ecdict, init_env):
"""
Build the software
:param ecdict: dictionary contaning parsed easyconfig + metadata
Expand Down Expand Up @@ -2746,7 +2813,7 @@ def build_and_install_one(ecdict, init_env, hooks=None):
try:
app_class = get_easyblock_class(easyblock, name=name)

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

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

try:
# upload easyconfig (and patch files) to central repository
Expand Down
Loading

0 comments on commit f032b12

Please sign in to comment.