From 0b0b07f24bea443acdc83dbae36872f3007eeafb Mon Sep 17 00:00:00 2001 From: Ward Poelmans Date: Sun, 22 Jun 2014 15:04:09 +0200 Subject: [PATCH 001/298] add script to clean up gists The script cleans up gists from closed/merged PR's. --- easybuild/scripts/clean_gists.py | 99 ++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100755 easybuild/scripts/clean_gists.py diff --git a/easybuild/scripts/clean_gists.py b/easybuild/scripts/clean_gists.py new file mode 100755 index 0000000000..066d4a53f2 --- /dev/null +++ b/easybuild/scripts/clean_gists.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python +## +# Copyright 2014 Ward Poelmans +# +# http://github.com/hpcugent/easybuild +# +# EasyBuild is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation v2. +# +# EasyBuild is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with EasyBuild. If not, see . +## +""" +This script cleans up old gists created by easybuild. It checks if the gists was +created from a pull-request and if that PR is closed/merged, it will delete the gist. +You need a github token for this. The script uses the same username and token +as easybuild. Optionally, you can specify a different github username. + +Usage: ./clean_gists.py [] + + +@author: Ward Poelmans +""" + + +import re +import sys + +from vsc.utils import fancylogger +from vsc.utils.rest import RestClient +from easybuild.tools.github import GITHUB_API_URL, HTTP_STATUS_OK, GITHUB_EASYCONFIGS_REPO, GITHUB_EB_MAIN, fetch_github_token +from easybuild.tools.options import EasyBuildOptions + +HTTP_DELETE_OK = 204 + + +def main(username=None): + """the main function""" + fancylogger.setLogLevelInfo() + fancylogger.logToScreen(enable=True, stdout=True) + log = fancylogger.getLogger() + + if username is None: + eb_go = EasyBuildOptions(envvar_prefix='EASYBUILD', go_args=[]) + username = eb_go.options.github_user + + if username is None: + log.error("Could not find a github username") + else: + log.info("Using username = %s" % username) + + token = fetch_github_token(username) + + gh = RestClient(GITHUB_API_URL, username=username, token=token) + # ToDo: add support for pagination + status, gists = gh.gists.get(per_page=100) + + if status != HTTP_STATUS_OK: + log.error("Failed to get a lists of gists for user %s: error code %s, message = %s" % + (username, status, gists)) + else: + log.info("Found %s gists" % len(gists)) + + regex = re.compile("(EasyBuild test report for easyconfigs|EasyBuild log for failed build of).*PR #([0-9]+)") + + for gist in gists: + re_pr_num = regex.search(gist["description"]) + if re_pr_num: + pr_num = re_pr_num.group(2) + log.info("Found Easybuild test report for PR #%s" % pr_num) + status, pr = gh.repos[GITHUB_EB_MAIN][GITHUB_EASYCONFIGS_REPO].pulls[pr_num].get() + + if status != HTTP_STATUS_OK: + log.error("Failed to get pull-request #%s: error code %s, message = %s" % + (pr_num, status, pr)) + + if pr["state"] == "closed": + log.debug("Found gist of closed PR #%s" % pr_num) + + status, del_gist = gh.gists[gist["id"]].delete() + + if status != HTTP_DELETE_OK: + log.error("Unable to remove gist (id=%s): error code %s, message = %s" % + (gist["id"], status, del_gist)) + else: + log.info("Delete gist from closed PR #%s" % pr_num) + + +if __name__ == '__main__': + if len(sys.argv) == 2: + main(username=sys.argv[1]) + else: + main() From c428da3a34461a6b8d41a4a2943cbb444974f87e Mon Sep 17 00:00:00 2001 From: pescobar Date: Fri, 27 Jun 2014 16:05:39 +0200 Subject: [PATCH 002/298] replaced sse3 option in amd by mavx option --- easybuild/toolchains/compiler/inteliccifort.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/toolchains/compiler/inteliccifort.py b/easybuild/toolchains/compiler/inteliccifort.py index c224278eeb..ac8b75e65e 100644 --- a/easybuild/toolchains/compiler/inteliccifort.py +++ b/easybuild/toolchains/compiler/inteliccifort.py @@ -70,7 +70,7 @@ class IntelIccIfort(Compiler): COMPILER_OPTIMAL_ARCHITECTURE_OPTION = { systemtools.INTEL : 'xHOST', - systemtools.AMD : 'msse3', + systemtools.AMD : 'mavx', } COMPILER_CC = 'icc' From 10998b2b0debdc8ba4ecfaf5656951d6c7cf75f7 Mon Sep 17 00:00:00 2001 From: Ward Poelmans Date: Wed, 16 Jul 2014 12:21:42 +0200 Subject: [PATCH 003/298] clean_gists.py: added options --- easybuild/scripts/clean_gists.py | 39 +++++++++++++++++++------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/easybuild/scripts/clean_gists.py b/easybuild/scripts/clean_gists.py index 066d4a53f2..df5f2d0452 100755 --- a/easybuild/scripts/clean_gists.py +++ b/easybuild/scripts/clean_gists.py @@ -22,33 +22,43 @@ You need a github token for this. The script uses the same username and token as easybuild. Optionally, you can specify a different github username. -Usage: ./clean_gists.py [] - - @author: Ward Poelmans """ import re -import sys from vsc.utils import fancylogger +from vsc.utils.generaloption import simple_option from vsc.utils.rest import RestClient -from easybuild.tools.github import GITHUB_API_URL, HTTP_STATUS_OK, GITHUB_EASYCONFIGS_REPO, GITHUB_EB_MAIN, fetch_github_token +from easybuild.tools.github import GITHUB_API_URL, HTTP_STATUS_OK, GITHUB_EASYCONFIGS_REPO +from easybuild.tools.github import GITHUB_EB_MAIN, fetch_github_token from easybuild.tools.options import EasyBuildOptions HTTP_DELETE_OK = 204 -def main(username=None): +def main(): """the main function""" fancylogger.setLogLevelInfo() fancylogger.logToScreen(enable=True, stdout=True) log = fancylogger.getLogger() - if username is None: + options = { + 'github-user': ('Your github username to use', None, 'store', None, 'g'), + 'closed-pr': ('Delete all gists from closed pull-requests', None, 'store_true', True, 'p'), + 'all': ('Delete all gists from Easybuild ', None, 'store_true', False, 'a'), + 'orphans': ('Delete all gists without a pull-request', None, 'store_true', False, 'o'), + } + + go = simple_option(options) + + if go.options.github_user is None: eb_go = EasyBuildOptions(envvar_prefix='EASYBUILD', go_args=[]) username = eb_go.options.github_user + log.debug("Fetch github username from easybuild, found: %s" % username) + else: + username = go.options.github_user if username is None: log.error("Could not find a github username") @@ -67,12 +77,12 @@ def main(username=None): else: log.info("Found %s gists" % len(gists)) - regex = re.compile("(EasyBuild test report for easyconfigs|EasyBuild log for failed build of).*PR #([0-9]+)") + regex = re.compile(r"(EasyBuild test report|EasyBuild log for failed build).+?(?:PR #(?P[0-9]+))?\)?$") for gist in gists: re_pr_num = regex.search(gist["description"]) - if re_pr_num: - pr_num = re_pr_num.group(2) + if re_pr_num and re_pr_num.group("PR"): + pr_num = re_pr_num.group("PR") log.info("Found Easybuild test report for PR #%s" % pr_num) status, pr = gh.repos[GITHUB_EB_MAIN][GITHUB_EASYCONFIGS_REPO].pulls[pr_num].get() @@ -80,8 +90,8 @@ def main(username=None): log.error("Failed to get pull-request #%s: error code %s, message = %s" % (pr_num, status, pr)) - if pr["state"] == "closed": - log.debug("Found gist of closed PR #%s" % pr_num) + if pr["state"] == "closed" and (go.options.closed_pr or go.options.all): + log.debug("Found report from closed PR #%s" % pr_num) status, del_gist = gh.gists[gist["id"]].delete() @@ -93,7 +103,4 @@ def main(username=None): if __name__ == '__main__': - if len(sys.argv) == 2: - main(username=sys.argv[1]) - else: - main() + main() From 5b278cd9feffe2cbeab91953475fe7c79b555f70 Mon Sep 17 00:00:00 2001 From: Ward Poelmans Date: Wed, 16 Jul 2014 22:55:57 +0200 Subject: [PATCH 004/298] clean_gists: implement all options Everything should work now --- easybuild/scripts/clean_gists.py | 75 +++++++++++++++++++++----------- 1 file changed, 49 insertions(+), 26 deletions(-) diff --git a/easybuild/scripts/clean_gists.py b/easybuild/scripts/clean_gists.py index df5f2d0452..4c9adc44d4 100755 --- a/easybuild/scripts/clean_gists.py +++ b/easybuild/scripts/clean_gists.py @@ -40,30 +40,33 @@ def main(): """the main function""" - fancylogger.setLogLevelInfo() fancylogger.logToScreen(enable=True, stdout=True) + fancylogger.setLogLevelInfo() log = fancylogger.getLogger() options = { 'github-user': ('Your github username to use', None, 'store', None, 'g'), - 'closed-pr': ('Delete all gists from closed pull-requests', None, 'store_true', True, 'p'), + 'closed-pr': ('Delete all gists from closed pull-requests', None, 'store_true', False, 'p'), 'all': ('Delete all gists from Easybuild ', None, 'store_true', False, 'a'), 'orphans': ('Delete all gists without a pull-request', None, 'store_true', False, 'o'), } go = simple_option(options) + if not (go.options.all or go.options.closed_pr or go.options.orphans): + log.error("Please tell me what to do?") + if go.options.github_user is None: eb_go = EasyBuildOptions(envvar_prefix='EASYBUILD', go_args=[]) username = eb_go.options.github_user - log.debug("Fetch github username from easybuild, found: %s" % username) + log.debug("Fetch github username from easybuild, found: %s", username) else: username = go.options.github_user if username is None: log.error("Could not find a github username") else: - log.info("Using username = %s" % username) + log.info("Using username = %s", username) token = fetch_github_token(username) @@ -72,34 +75,54 @@ def main(): status, gists = gh.gists.get(per_page=100) if status != HTTP_STATUS_OK: - log.error("Failed to get a lists of gists for user %s: error code %s, message = %s" % - (username, status, gists)) + log.error("Failed to get a lists of gists for user %s: error code %s, message = %s", + username, status, gists) else: - log.info("Found %s gists" % len(gists)) + log.info("Found %s gists", len(gists)) - regex = re.compile(r"(EasyBuild test report|EasyBuild log for failed build).+?(?:PR #(?P[0-9]+))?\)?$") + regex = re.compile(r"(EasyBuild test report|EasyBuild log for failed build).*?(?:PR #(?P[0-9]+))?\)?$") + + pr_cache = {} + num_deleted = 0 for gist in gists: re_pr_num = regex.search(gist["description"]) - if re_pr_num and re_pr_num.group("PR"): - pr_num = re_pr_num.group("PR") - log.info("Found Easybuild test report for PR #%s" % pr_num) - status, pr = gh.repos[GITHUB_EB_MAIN][GITHUB_EASYCONFIGS_REPO].pulls[pr_num].get() + delete_gist = False - if status != HTTP_STATUS_OK: - log.error("Failed to get pull-request #%s: error code %s, message = %s" % - (pr_num, status, pr)) - - if pr["state"] == "closed" and (go.options.closed_pr or go.options.all): - log.debug("Found report from closed PR #%s" % pr_num) - - status, del_gist = gh.gists[gist["id"]].delete() - - if status != HTTP_DELETE_OK: - log.error("Unable to remove gist (id=%s): error code %s, message = %s" % - (gist["id"], status, del_gist)) - else: - log.info("Delete gist from closed PR #%s" % pr_num) + if re_pr_num: + log.debug("Found a Easybuild gist (id=%s)", gist["id"]) + pr_num = re_pr_num.group("PR") + if go.options.all: + delete_gist = True + elif pr_num and go.options.closed_pr: + log.debug("Found Easybuild test report for PR #%s", pr_num) + + if pr_num not in pr_cache: + status, pr = gh.repos[GITHUB_EB_MAIN][GITHUB_EASYCONFIGS_REPO].pulls[pr_num].get() + if status != HTTP_STATUS_OK: + log.error("Failed to get pull-request #%s: error code %s, message = %s", + pr_num, status, pr) + pr_cache[pr_num] = pr["state"] + + if pr_cache[pr_num] == "closed": + log.debug("Found report from closed PR #%s (id=%s)", pr_num, gist["id"]) + delete_gist = True + + elif not pr_num and go.options.orphans: + log.debug("Found Easybuild test report without PR (id=%s)", gist["id"]) + delete_gist = True + + if delete_gist: + status, del_gist = gh.gists[gist["id"]].delete() + + if status != HTTP_DELETE_OK: + log.error("Unable to remove gist (id=%s): error code %s, message = %s", + gist["id"], status, del_gist) + else: + log.info("Delete gist with id=%s", gist["id"]) + num_deleted += 1 + + log.info("Deleted %s gists", num_deleted) if __name__ == '__main__': From 751e401403cbf22da1711f63e309a503548b8883 Mon Sep 17 00:00:00 2001 From: Ward Poelmans Date: Mon, 4 Aug 2014 21:27:47 +0200 Subject: [PATCH 005/298] clean_gists: delete closed pr gists by default --- easybuild/scripts/clean_gists.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/scripts/clean_gists.py b/easybuild/scripts/clean_gists.py index 4c9adc44d4..ba847e6198 100755 --- a/easybuild/scripts/clean_gists.py +++ b/easybuild/scripts/clean_gists.py @@ -46,7 +46,7 @@ def main(): options = { 'github-user': ('Your github username to use', None, 'store', None, 'g'), - 'closed-pr': ('Delete all gists from closed pull-requests', None, 'store_true', False, 'p'), + 'closed-pr': ('Delete all gists from closed pull-requests', None, 'store_true', True, 'p'), 'all': ('Delete all gists from Easybuild ', None, 'store_true', False, 'a'), 'orphans': ('Delete all gists without a pull-request', None, 'store_true', False, 'o'), } From 66a5f3cadb974a639cd5fc63ccb4032daccfeebe Mon Sep 17 00:00:00 2001 From: Ward Poelmans Date: Thu, 11 Sep 2014 16:52:15 +0200 Subject: [PATCH 006/298] clean_gists: fix bug when description is empty --- easybuild/scripts/clean_gists.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/easybuild/scripts/clean_gists.py b/easybuild/scripts/clean_gists.py index ba847e6198..5a7933d038 100755 --- a/easybuild/scripts/clean_gists.py +++ b/easybuild/scripts/clean_gists.py @@ -86,6 +86,8 @@ def main(): num_deleted = 0 for gist in gists: + if not gist["description"]: + continue re_pr_num = regex.search(gist["description"]) delete_gist = False From cfbc988cf57a26dd4fb29e71766f77f0c8cbd15f Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 19 Sep 2014 10:32:15 +0200 Subject: [PATCH 007/298] add debug logging in modpath_extensions_for --- easybuild/tools/modules.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/easybuild/tools/modules.py b/easybuild/tools/modules.py index d0083bc294..52ef1d54d9 100644 --- a/easybuild/tools/modules.py +++ b/easybuild/tools/modules.py @@ -590,6 +590,8 @@ def modpath_extensions_for(self, mod_names): Determine dictionary with $MODULEPATH extensions for specified modules. Modules with an empty list of $MODULEPATH extensions are included. """ + self.log.debug("Determining $MODULEPATH extensions for modules %s" % mod_names) + # copy environment so we can restore it orig_env = os.environ.copy() @@ -599,6 +601,7 @@ def modpath_extensions_for(self, mod_names): useregex = re.compile(r"^\s*module\s+use\s+(\S+)", re.M) exts = useregex.findall(modtxt) + self.log.debug("Found $MODULEPATH extensions for %s: %s" % (mod_name, exts)) modpath_exts.update({mod_name: exts}) if exts: From 918230596b98d3a72f1e51be6cd0b486707b968e Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 19 Sep 2014 16:20:36 +0200 Subject: [PATCH 008/298] add Python version check in 'eb' command --- eb | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/eb b/eb index 4bb70df087..c67fe08284 100755 --- a/eb +++ b/eb @@ -39,6 +39,37 @@ # @author: Pieter De Baets (Ghent University) # @author: Jens Timmerman (Ghent University) +# Python 2.4 or more recent 2.x required +REQ_MAJ_PYVER=2 +REQ_MIN_PYVER=4 +REQ_PYVER=${REQ_MAJ_PYVER}.${REQ_MIN_PYVER} + +# make sure Python version being used is compatible +pyver=`python -V 2>&1 | cut -f2 -d' '` +pyver_maj=`echo $pyver | cut -f1 -d'.'` +pyver_min=`echo $pyver | cut -f2 -d'.'` + +if [ $pyver_maj -ne $REQ_MAJ_PYVER ] +then + echo "ERROR: EasyBuild is currently only compatible with Python v${REQ_MAJ_PYVER}.x, found v${pyver}" 1>&2 + exit 1 +fi +if [ $pyver_min -lt $REQ_MIN_PYVER ] +then + echo "ERROR: EasyBuild requires Python v${REQ_PYVER} or a more recent v${REQ_MAJ_PYVER}.x, found v${pyver}." 1>&2 + exit 2 +fi + +# support for Python versions older than v2.6 is deprecated +OK_MIN_PYVER=6 +if [ $pyver_min -lt $OK_MIN_PYVER ] +then + OK_PYVER=${REQ_MAJ_PYVER}.${OK_MIN_PYVER} + echo -n "WARNING: Running EasyBuild with a Python version prior to v${OK_PYVER} is deprecated, " 1>&2 + echo "found Python v$pyver which will no longer be supported in EasyBuild v2.0." 1>&2 +fi + + main_script_base_path="easybuild/main.py" python_search_path_cmd="python -c \"import sys; print ' '.join(sys.path)\"" From 022409a8862ad7de996ae046c342f3a3800040e7 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Sat, 20 Sep 2014 00:25:27 +0200 Subject: [PATCH 009/298] fix path_to_top_of_module_tree to consider multiple paths to the top of the module tree --- easybuild/tools/modules.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/easybuild/tools/modules.py b/easybuild/tools/modules.py index 52ef1d54d9..b2e75295b2 100644 --- a/easybuild/tools/modules.py +++ b/easybuild/tools/modules.py @@ -659,40 +659,40 @@ def path_to_top_of_module_tree(self, top_paths, mod_name, full_mod_subdir, deps, modpath_exts = dict([(k, v) for k, v in self.modpath_extensions_for(deps).items() if v]) self.log.debug("Non-empty lists of module path extensions for dependencies: %s" % modpath_exts) - path = [] + mods_to_top = [] + full_mod_subdirs = [] for dep in modpath_exts: # if a $MODULEPATH extension is identical to where this module will be installed, we have a hit # use os.path.samefile when comparing paths to avoid issues with resolved symlinks full_modpath_exts = modpath_exts[dep] if path_matches(full_mod_subdir, full_modpath_exts): # full path to module subdir of dependency is simply path to module file without (short) module name - full_mod_subdir = self.modulefile_path(dep)[:-len(dep)-1] + full_mod_subdirs.append(self.modulefile_path(dep)[:-len(dep)-1]) - path.append(dep) - tup = (dep, full_mod_subdir, full_modpath_exts) + mods_to_top.append(dep) + tup = (dep, full_mod_subdirs[-1], full_modpath_exts) self.log.debug("Found module to top of module tree: %s (subdir: %s, modpath extensions %s)" % tup) - # no need to continue further, we found the module that extends $MODULEPATH with module subdir - break - if full_modpath_exts: # load module for this dependency, since it may extend $MODULEPATH to make dependencies available # this is required to obtain the corresponding module file paths (via 'module show') self.load([dep]) - if path: - # remove retained dependency from the list, since we're climbing up the module tree - modpath_exts.pop(path[-1]) + # restore original environment (modules may have been loaded above) + modify_env(os.environ, orig_env) + + path = mods_to_top[:] + if mods_to_top: + # remove retained dependencies from the list, since we're climbing up the module tree + remaining_modpath_exts = dict([(m, modpath_exts[m]) for m in modpath_exts if not m in mods_to_top]) - self.log.debug("Path to top from %s extended to %s, so recursing to find way to the top" % (mod_name, path)) - path.extend(self.path_to_top_of_module_tree(top_paths, path[-1], full_mod_subdir, None, - modpath_exts=modpath_exts)) + self.log.debug("Path to top from %s extended to %s, so recursing to find way to the top" % (mod_name, mods_to_top)) + for mod_name, full_mod_subdir in zip(mods_to_top, full_mod_subdirs): + path.extend(self.path_to_top_of_module_tree(top_paths, mod_name, full_mod_subdir, None, + modpath_exts=remaining_modpath_exts)) else: self.log.debug("Path not extended, we must have reached the top of the module tree") - # restore original environment (modules may have been loaded above) - modify_env(os.environ, orig_env) - self.log.debug("Path to top of module tree from %s: %s" % (mod_name, path)) return path From 0dcde88ce3926a94c3be11fe64f081174a2190a1 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Sat, 20 Sep 2014 01:57:24 +0200 Subject: [PATCH 010/298] add unit test for checking load statements in imkl module under HierarchicalMNS --- test/framework/easyblock.py | 44 ++++++++++++++++++- test/framework/easyconfigs/GCC-4.8.3.eb | 28 ++++++++++++ .../easyconfigs/icc-2013.5.192-GCC-4.8.3.eb | 23 ++++++++++ .../iccifort-2013.5.192-GCC-4.8.3.eb | 17 +++++++ .../easyconfigs/ifort-2013.5.192-GCC-4.8.3.eb | 23 ++++++++++ .../easyconfigs/iimpi-5.5.3-GCC-4.8.3.eb | 21 +++++++++ .../imkl-11.1.2.144-iimpi-5.5.3-GCC-4.8.3.eb | 22 ++++++++++ ...4.1.3.049-iccifort-2013.5.192-GCC-4.8.3.eb | 19 ++++++++ test/framework/module_generator.py | 1 + test/framework/modules.py | 1 + .../Compiler/intel/2013.5.192/impi/4.1.3.049 | 30 +++++++++++++ test/framework/modules/Core/GCC/4.8.3 | 30 +++++++++++++ .../modules/Core/icc/2013.5.192-GCC-4.8.3 | 31 +++++++++++++ .../Core/iccifort/2013.5.192-GCC-4.8.3 | 24 ++++++++++ .../modules/Core/ifort/2013.5.192-GCC-4.8.3 | 31 +++++++++++++ .../modules/Core/iimpi/5.5.3-GCC-4.8.3 | 26 +++++++++++ test/framework/utilities.py | 8 +++- 17 files changed, 376 insertions(+), 3 deletions(-) create mode 100644 test/framework/easyconfigs/GCC-4.8.3.eb create mode 100644 test/framework/easyconfigs/icc-2013.5.192-GCC-4.8.3.eb create mode 100644 test/framework/easyconfigs/iccifort-2013.5.192-GCC-4.8.3.eb create mode 100644 test/framework/easyconfigs/ifort-2013.5.192-GCC-4.8.3.eb create mode 100644 test/framework/easyconfigs/iimpi-5.5.3-GCC-4.8.3.eb create mode 100644 test/framework/easyconfigs/imkl-11.1.2.144-iimpi-5.5.3-GCC-4.8.3.eb create mode 100644 test/framework/easyconfigs/impi-4.1.3.049-iccifort-2013.5.192-GCC-4.8.3.eb create mode 100644 test/framework/modules/Compiler/intel/2013.5.192/impi/4.1.3.049 create mode 100644 test/framework/modules/Core/GCC/4.8.3 create mode 100644 test/framework/modules/Core/icc/2013.5.192-GCC-4.8.3 create mode 100644 test/framework/modules/Core/iccifort/2013.5.192-GCC-4.8.3 create mode 100644 test/framework/modules/Core/ifort/2013.5.192-GCC-4.8.3 create mode 100644 test/framework/modules/Core/iimpi/5.5.3-GCC-4.8.3 diff --git a/test/framework/easyblock.py b/test/framework/easyblock.py index 6dcd861e96..1107f8049b 100644 --- a/test/framework/easyblock.py +++ b/test/framework/easyblock.py @@ -44,7 +44,9 @@ from easybuild.framework.extensioneasyblock import ExtensionEasyBlock from easybuild.tools import config from easybuild.tools.build_log import EasyBuildError -from easybuild.tools.filetools import mkdir, write_file +from easybuild.tools.environment import modify_env +from easybuild.tools.filetools import mkdir, read_file, write_file +from easybuild.tools.modules import modules_tool class EasyBlockTest(EnhancedTestCase): @@ -465,6 +467,46 @@ def test_check_readiness(self): shutil.rmtree(tmpdir) + def test_exclude_path_to_top_of_module_tree(self): + """ + Make sure that modules under the HierarchicalMNS are correct, + w.r.t. not including any load statements for modules that build up the path to the top of the module tree. + """ + self.orig_module_naming_scheme = config.get_module_naming_scheme() + test_ecs_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs') + all_stops = [x[0] for x in EasyBlock.get_steps()] + build_options = { + 'check_osdeps': False, + 'robot_path': [test_ecs_path], + 'valid_stops': all_stops, + 'validate': False, + } + os.environ['EASYBUILD_MODULE_NAMING_SCHEME'] = 'HierarchicalMNS' + init_config(build_options=build_options) + self.setup_hierarchical_modules() + modtool = modules_tool() + + ec = EasyConfig(os.path.join(test_ecs_path, 'imkl-11.1.2.144-iimpi-5.5.3-GCC-4.8.3.eb')) + eb = EasyBlock(ec) + + modfile_prefix = os.path.join(self.test_installpath, 'modules', 'all', 'MPI', 'intel', '2013.5.192', 'impi', '4.1.3.049', 'imkl') + mkdir(os.path.dirname(modfile_prefix), parents=True) + eb.toolchain.prepare() + modpath = eb.make_module_step() + modfile_path = os.path.join(modpath, 'MPI', 'intel', '2013.5.192', 'impi', '4.1.3.049', 'imkl', '11.1.2.144') + modtxt = read_file(modfile_path) + + # for imkl on top of iimpi toolchain with HierarchicalMNS, no module load statements should be included at all + # not for the toolchain or any of the toolchain components, + # since both icc/ifort and impi form the path to the top of the module tree + for imkl_dep in ['icc', 'ifort', 'impi', 'iccifort', 'iimpi']: + tup = (imkl_dep, modfile_path, modtxt) + failmsg = "No 'module load' statement found for '%s' not found in module %s: %s" % tup + self.assertFalse(re.search("module load %s" % imkl_dep, modtxt), failmsg) + + os.environ['EASYBUILD_MODULE_NAMING_SCHEME'] = self.orig_module_naming_scheme + init_config(build_options=build_options) + def tearDown(self): """ make sure to remove the temporary file """ super(EasyBlockTest, self).tearDown() diff --git a/test/framework/easyconfigs/GCC-4.8.3.eb b/test/framework/easyconfigs/GCC-4.8.3.eb new file mode 100644 index 0000000000..c62aa710d1 --- /dev/null +++ b/test/framework/easyconfigs/GCC-4.8.3.eb @@ -0,0 +1,28 @@ +name = "GCC" +version = '4.8.3' + +homepage = 'http://gcc.gnu.org/' +description = """The GNU Compiler Collection includes front ends for C, C++, Objective-C, Fortran, Java, and Ada, + as well as libraries for these languages (libstdc++, libgcj,...).""" + +toolchain = {'name': 'dummy', 'version': 'dummy'} + +source_urls = [ + 'http://ftpmirror.gnu.org/%(namelower)s/%(namelower)s-%(version)s', # GCC auto-resolving HTTP mirror + 'http://ftpmirror.gnu.org/gmp', # idem for GMP + 'http://ftpmirror.gnu.org/mpfr', # idem for MPFR + 'http://www.multiprecision.org/mpc/download', # MPC official +] +sources = [ + SOURCELOWER_TAR_GZ, + 'gmp-5.1.3.tar.bz2', + 'mpfr-3.1.2.tar.gz', + 'mpc-1.0.1.tar.gz', +] + +languages = ['c', 'c++', 'fortran', 'lto'] + +# building GCC sometimes fails if make parallelism is too high, so let's limit it +maxparallel = 4 + +moduleclass = 'compiler' diff --git a/test/framework/easyconfigs/icc-2013.5.192-GCC-4.8.3.eb b/test/framework/easyconfigs/icc-2013.5.192-GCC-4.8.3.eb new file mode 100644 index 0000000000..23b5d632b0 --- /dev/null +++ b/test/framework/easyconfigs/icc-2013.5.192-GCC-4.8.3.eb @@ -0,0 +1,23 @@ +name = 'icc' +version = '2013.5.192' + +homepage = 'http://software.intel.com/en-us/intel-compilers/' +description = "C and C++ compiler from Intel" + +toolchain = {'name': 'dummy', 'version': 'dummy'} + +sources = ['l_ccompxe_%(version)s.tgz'] + +gcc = 'GCC' +gccver = '4.8.3' +versionsuffix = '-%s-%s' % (gcc, gccver) + +dependencies = [(gcc, gccver)] + +dontcreateinstalldir = 'True' + +# license file +import os +license_file = os.path.join(os.getenv('HOME'), "licenses", "intel", "license.lic") + +moduleclass = 'compiler' diff --git a/test/framework/easyconfigs/iccifort-2013.5.192-GCC-4.8.3.eb b/test/framework/easyconfigs/iccifort-2013.5.192-GCC-4.8.3.eb new file mode 100644 index 0000000000..9a152f81fe --- /dev/null +++ b/test/framework/easyconfigs/iccifort-2013.5.192-GCC-4.8.3.eb @@ -0,0 +1,17 @@ +easyblock = "Toolchain" + +name = 'iccifort' +version = '2013.5.192' +versionsuffix = '-GCC-4.8.3' + +homepage = 'http://software.intel.com/en-us/intel-cluster-toolkit-compiler/' +description = """Intel C, C++ and Fortran compilers""" + +toolchain = {'name': 'dummy', 'version': 'dummy'} + +dependencies = [ + ('icc', version, versionsuffix), + ('ifort', version, versionsuffix), +] + +moduleclass = 'toolchain' diff --git a/test/framework/easyconfigs/ifort-2013.5.192-GCC-4.8.3.eb b/test/framework/easyconfigs/ifort-2013.5.192-GCC-4.8.3.eb new file mode 100644 index 0000000000..cba97b45a2 --- /dev/null +++ b/test/framework/easyconfigs/ifort-2013.5.192-GCC-4.8.3.eb @@ -0,0 +1,23 @@ +name = 'ifort' +version = '2013.5.192' + +homepage = 'http://software.intel.com/en-us/intel-compilers/' +description = "Fortran compiler from Intel" + +toolchain = {'name': 'dummy', 'version': 'dummy'} + +sources = ['l_fcompxe_%(version)s.tgz'] + +gcc = 'GCC' +gccver = '4.8.3' +versionsuffix = '-%s-%s' % (gcc, gccver) + +dependencies = [(gcc, gccver)] + +dontcreateinstalldir = 'True' + +# license file +import os +license_file = os.path.join(os.getenv('HOME'), "licenses", "intel", "license.lic") + +moduleclass = 'compiler' diff --git a/test/framework/easyconfigs/iimpi-5.5.3-GCC-4.8.3.eb b/test/framework/easyconfigs/iimpi-5.5.3-GCC-4.8.3.eb new file mode 100644 index 0000000000..221f1fb36d --- /dev/null +++ b/test/framework/easyconfigs/iimpi-5.5.3-GCC-4.8.3.eb @@ -0,0 +1,21 @@ +easyblock = "Toolchain" + +name = 'iimpi' +version = '5.5.3' +versionsuffix = '-GCC-4.8.3' + +homepage = 'http://software.intel.com/en-us/intel-cluster-toolkit-compiler/' +description = """Intel C/C++ and Fortran compilers, alongside Intel MPI.""" + +toolchain = {'name': 'dummy', 'version': 'dummy'} + +suff = '5.192' +compver = '2013.%s' % suff + +dependencies = [ # version/released + ('icc', compver, versionsuffix), # 28 Apr 2014 + ('ifort', compver, versionsuffix), # 28 Apr 2014 + ('impi', '4.1.3.049', '', ('iccifort', '%s%s' % (compver, versionsuffix))), # 06 Mar 2014 +] + +moduleclass = 'toolchain' diff --git a/test/framework/easyconfigs/imkl-11.1.2.144-iimpi-5.5.3-GCC-4.8.3.eb b/test/framework/easyconfigs/imkl-11.1.2.144-iimpi-5.5.3-GCC-4.8.3.eb new file mode 100644 index 0000000000..2114374474 --- /dev/null +++ b/test/framework/easyconfigs/imkl-11.1.2.144-iimpi-5.5.3-GCC-4.8.3.eb @@ -0,0 +1,22 @@ +name = 'imkl' +version = '11.1.2.144' + +homepage = 'http://software.intel.com/en-us/intel-mkl/' +description = """Intel Math Kernel Library is a library of highly optimized, + extensively threaded math routines for science, engineering, and financial + applications that require maximum performance. Core math functions include + BLAS, LAPACK, ScaLAPACK, Sparse Solvers, Fast Fourier Transforms, Vector Math, and more.""" + +toolchain = {'name': 'iimpi', 'version': '5.5.3-GCC-4.8.3'} + +sources = ['l_mkl_%(version)s.tgz'] + +dontcreateinstalldir = 'True' + +interfaces = True + +# license file +import os +license_file = os.path.join(os.getenv('HOME'), "licenses", "intel", "license.lic") + +moduleclass = 'numlib' diff --git a/test/framework/easyconfigs/impi-4.1.3.049-iccifort-2013.5.192-GCC-4.8.3.eb b/test/framework/easyconfigs/impi-4.1.3.049-iccifort-2013.5.192-GCC-4.8.3.eb new file mode 100644 index 0000000000..ddba0fe1d6 --- /dev/null +++ b/test/framework/easyconfigs/impi-4.1.3.049-iccifort-2013.5.192-GCC-4.8.3.eb @@ -0,0 +1,19 @@ +name = 'impi' +version = '4.1.3.049' + +homepage = 'http://software.intel.com/en-us/intel-mpi-library/' +description = """The Intel(R) MPI Library for Linux* OS is a multi-fabric message + passing library based on ANL MPICH2 and OSU MVAPICH2. The Intel MPI Library for + Linux OS implements the Message Passing Interface, version 2 (MPI-2) specification.""" + +toolchain = {'name': 'iccifort', 'version': '2013.5.192-GCC-4.8.3'} + +sources = ['l_mpi_p_%(version)s.tgz'] + +dontcreateinstalldir = 'True' + +# license file +import os +license_file = os.path.join(os.getenv('HOME'), "licenses", "intel", "license.lic") + +moduleclass = 'mpi' diff --git a/test/framework/module_generator.py b/test/framework/module_generator.py index 4e12e71427..9d0c46188b 100644 --- a/test/framework/module_generator.py +++ b/test/framework/module_generator.py @@ -419,6 +419,7 @@ def test_ec(ecfile, short_modname, mod_subdir, modpath_exts, init_modpaths): for ecfile, mns_vals in test_ecs.items(): test_ec(ecfile, *mns_vals) + def suite(): """ returns all the testcases in this module """ return TestLoader().loadTestsFromTestCase(ModuleGeneratorTest) diff --git a/test/framework/modules.py b/test/framework/modules.py index ae8ab63f6f..46b7453ba8 100644 --- a/test/framework/modules.py +++ b/test/framework/modules.py @@ -283,6 +283,7 @@ def test_path_to_top_of_module_tree(self): path = modtool.path_to_top_of_module_tree(init_modpaths, 'FFTW/3.3.3', full_mod_subdir, deps) self.assertEqual(path, ['OpenMPI/1.6.4', 'GCC/4.7.2']) + def suite(): """ returns all the testcases in this module """ return TestLoader().loadTestsFromTestCase(ModulesTest) diff --git a/test/framework/modules/Compiler/intel/2013.5.192/impi/4.1.3.049 b/test/framework/modules/Compiler/intel/2013.5.192/impi/4.1.3.049 new file mode 100644 index 0000000000..6b65daae4f --- /dev/null +++ b/test/framework/modules/Compiler/intel/2013.5.192/impi/4.1.3.049 @@ -0,0 +1,30 @@ +#%Module + +proc ModulesHelp { } { + puts stderr { The Intel(R) MPI Library for Linux* OS is a multi-fabric message + passing library based on ANL MPICH2 and OSU MVAPICH2. The Intel MPI Library for + Linux OS implements the Message Passing Interface, version 2 (MPI-2) specification. - Homepage: http://software.intel.com/en-us/intel-mpi-library/ + } +} + +module-whatis {Description: The Intel(R) MPI Library for Linux* OS is a multi-fabric message + passing library based on ANL MPICH2 and OSU MVAPICH2. The Intel MPI Library for + Linux OS implements the Message Passing Interface, version 2 (MPI-2) specification. - Homepage: http://software.intel.com/en-us/intel-mpi-library/} + +set root /tmp/software/Compiler/intel/2013.5.192/impi/4.1.3.049 + +conflict impi +module use /tmp/modules/all/MPI/intel/2013.5.192/impi/4.1.3.049 +prepend-path CPATH $root/include64 +prepend-path LD_LIBRARY_PATH $root/lib64 +prepend-path LIBRARY_PATH $root/lib64 +prepend-path PATH $root/bin64 + +setenv EBROOTIMPI "$root" +setenv EBVERSIONIMPI "4.1.3.049" +setenv EBDEVELIMPI "$root/easybuild/Compiler-intel-2013.5.192-impi-4.1.3.049-easybuild-devel" + +prepend-path INTEL_LICENSE_FILE /tmp/license.lic +setenv I_MPI_ROOT $root + +# Built with EasyBuild version 1.16.0dev diff --git a/test/framework/modules/Core/GCC/4.8.3 b/test/framework/modules/Core/GCC/4.8.3 new file mode 100644 index 0000000000..dded358b87 --- /dev/null +++ b/test/framework/modules/Core/GCC/4.8.3 @@ -0,0 +1,30 @@ +#%Module + +proc ModulesHelp { } { + puts stderr { The GNU Compiler Collection includes front ends for C, C++, Objective-C, Fortran, Java, and Ada, + as well as libraries for these languages (libstdc++, libgcj,...). - Homepage: http://gcc.gnu.org/ + } +} + +module-whatis {Description: The GNU Compiler Collection includes front ends for C, C++, Objective-C, Fortran, Java, and Ada, + as well as libraries for these languages (libstdc++, libgcj,...). - Homepage: http://gcc.gnu.org/} + +set root /tmp/problem1086/software/Core/GCC/4.8.3 + +conflict GCC +module use /tmp/problem1086/modules/all/Compiler/GCC/4.8.3 +prepend-path CPATH $root/include +prepend-path LD_LIBRARY_PATH $root/lib +prepend-path LD_LIBRARY_PATH $root/lib64 +prepend-path LD_LIBRARY_PATH $root/lib/gcc/x86_64-unknown-linux-gnu/4.8.3 +prepend-path LIBRARY_PATH $root/lib +prepend-path LIBRARY_PATH $root/lib64 +prepend-path MANPATH $root/share/man +prepend-path PATH $root/bin + +setenv EBROOTGCC "$root" +setenv EBVERSIONGCC "4.8.3" +setenv EBDEVELGCC "$root/easybuild/Core-GCC-4.8.3-easybuild-devel" + + +# Built with EasyBuild version 1.16.0dev diff --git a/test/framework/modules/Core/icc/2013.5.192-GCC-4.8.3 b/test/framework/modules/Core/icc/2013.5.192-GCC-4.8.3 new file mode 100644 index 0000000000..6ae62b5684 --- /dev/null +++ b/test/framework/modules/Core/icc/2013.5.192-GCC-4.8.3 @@ -0,0 +1,31 @@ +#%Module + +proc ModulesHelp { } { + puts stderr { C and C++ compiler from Intel - Homepage: http://software.intel.com/en-us/intel-compilers/ + } +} + +module-whatis {Description: C and C++ compiler from Intel - Homepage: http://software.intel.com/en-us/intel-compilers/} + +set root /tmp/software/Core/icc/2013.5.192-GCC-4.8.3 + +conflict icc +module use /tmp/modules/all/Compiler/intel/2013.5.192 +module load GCC/4.8.3 + +prepend-path IDB_HOME $root/bin/intel64 +prepend-path LD_LIBRARY_PATH $root/compiler/lib +prepend-path LD_LIBRARY_PATH $root/compiler/lib/intel64 +prepend-path MANPATH $root/man +prepend-path MANPATH $root/man/en_US +prepend-path PATH $root/bin +prepend-path PATH $root/bin/intel64 + +setenv EBROOTICC "$root" +setenv EBVERSIONICC "2013.5.192" +setenv EBDEVELICC "$root/easybuild/Core-icc-2013.5.192-GCC-4.8.3-easybuild-devel" + +prepend-path INTEL_LICENSE_FILE /tmp/license.lic +prepend-path NLSPATH $root/idb/intel64/locale/%l_%t/%N + +# Built with EasyBuild version 1.16.0dev diff --git a/test/framework/modules/Core/iccifort/2013.5.192-GCC-4.8.3 b/test/framework/modules/Core/iccifort/2013.5.192-GCC-4.8.3 new file mode 100644 index 0000000000..db05126002 --- /dev/null +++ b/test/framework/modules/Core/iccifort/2013.5.192-GCC-4.8.3 @@ -0,0 +1,24 @@ +#%Module + +proc ModulesHelp { } { + puts stderr { Intel C, C++ and Fortran compilers - Homepage: http://software.intel.com/en-us/intel-cluster-toolkit-compiler/ + } +} + +module-whatis {Description: Intel C, C++ and Fortran compilers - Homepage: http://software.intel.com/en-us/intel-cluster-toolkit-compiler/} + +set root /tmp/software/Core/iccifort/2013.5.192-GCC-4.8.3 + +conflict iccifort + +module load icc/2013.5.192-GCC-4.8.3 + +module load ifort/2013.5.192-GCC-4.8.3 + + +setenv EBROOTICCIFORT "$root" +setenv EBVERSIONICCIFORT "2013.5.192" +setenv EBDEVELICCIFORT "$root/easybuild/Core-iccifort-2013.5.192-GCC-4.8.3-easybuild-devel" + + +# Built with EasyBuild version 1.16.0dev diff --git a/test/framework/modules/Core/ifort/2013.5.192-GCC-4.8.3 b/test/framework/modules/Core/ifort/2013.5.192-GCC-4.8.3 new file mode 100644 index 0000000000..192c8082f9 --- /dev/null +++ b/test/framework/modules/Core/ifort/2013.5.192-GCC-4.8.3 @@ -0,0 +1,31 @@ +#%Module + +proc ModulesHelp { } { + puts stderr { Fortran compiler from Intel - Homepage: http://software.intel.com/en-us/intel-compilers/ + } +} + +module-whatis {Description: Fortran compiler from Intel - Homepage: http://software.intel.com/en-us/intel-compilers/} + +set root /tmp/software/Core/ifort/2013.5.192-GCC-4.8.3 + +conflict ifort +module use /tmp/modules/all/Compiler/intel/2013.5.192 +module load GCC/4.8.3 + +prepend-path IDB_HOME $root/bin/intel64 +prepend-path LD_LIBRARY_PATH $root/compiler/lib +prepend-path LD_LIBRARY_PATH $root/compiler/lib/intel64 +prepend-path MANPATH $root/man +prepend-path MANPATH $root/man/en_US +prepend-path PATH $root/bin +prepend-path PATH $root/bin/intel64 + +setenv EBROOTIFORT "$root" +setenv EBVERSIONIFORT "2013.5.192" +setenv EBDEVELIFORT "$root/easybuild/Core-ifort-2013.5.192-GCC-4.8.3-easybuild-devel" + +prepend-path INTEL_LICENSE_FILE /tmp/license.lic +prepend-path NLSPATH $root/idb/intel64/locale/%l_%t/%N + +# Built with EasyBuild version 1.16.0dev diff --git a/test/framework/modules/Core/iimpi/5.5.3-GCC-4.8.3 b/test/framework/modules/Core/iimpi/5.5.3-GCC-4.8.3 new file mode 100644 index 0000000000..ad5b080f00 --- /dev/null +++ b/test/framework/modules/Core/iimpi/5.5.3-GCC-4.8.3 @@ -0,0 +1,26 @@ +#%Module + +proc ModulesHelp { } { + puts stderr { Intel C/C++ and Fortran compilers, alongside Intel MPI. - Homepage: http://software.intel.com/en-us/intel-cluster-toolkit-compiler/ + } +} + +module-whatis {Description: Intel C/C++ and Fortran compilers, alongside Intel MPI. - Homepage: http://software.intel.com/en-us/intel-cluster-toolkit-compiler/} + +set root /tmp/software/Core/iimpi/5.5.3-GCC-4.8.3 + +conflict iimpi + +module load icc/2013.5.192-GCC-4.8.3 + +module load ifort/2013.5.192-GCC-4.8.3 + +module load impi/4.1.3.049 + + +setenv EBROOTIIMPI "$root" +setenv EBVERSIONIIMPI "5.5.3" +setenv EBDEVELIIMPI "$root/easybuild/Core-iimpi-5.5.3-GCC-4.8.3-easybuild-devel" + + +# Built with EasyBuild version 1.16.0dev diff --git a/test/framework/utilities.py b/test/framework/utilities.py index 0aa37bdd63..e4237bc05c 100644 --- a/test/framework/utilities.py +++ b/test/framework/utilities.py @@ -202,13 +202,17 @@ def setup_hierarchical_modules(self): # make sure only modules in a hierarchical scheme are available, mixing modules installed with # a flat scheme like EasyBuildMNS and a hierarhical one like HierarchicalMNS doesn't work - self.reset_modulepath([os.path.join(mod_prefix, 'Core')]) + self.reset_modulepath([mod_prefix, os.path.join(mod_prefix, 'Core')]) - # tweak use statements in GCC/OpenMPI modules to ensure correct paths + # tweak use statements in modules to ensure correct paths mpi_pref = os.path.join(mod_prefix, 'MPI', 'GCC', '4.7.2', 'OpenMPI', '1.6.4') for modfile in [ os.path.join(mod_prefix, 'Core', 'GCC', '4.7.2'), + os.path.join(mod_prefix, 'Core', 'GCC', '4.8.3'), + os.path.join(mod_prefix, 'Core', 'icc', '2013.5.192-GCC-4.8.3'), + os.path.join(mod_prefix, 'Core', 'ifort', '2013.5.192-GCC-4.8.3'), os.path.join(mod_prefix, 'Compiler', 'GCC', '4.7.2', 'OpenMPI', '1.6.4'), + os.path.join(mod_prefix, 'Compiler', 'intel', '2013.5.192', 'impi', '4.1.3.049'), os.path.join(mpi_pref, 'FFTW', '3.3.3'), os.path.join(mpi_pref, 'OpenBLAS', '0.2.6-LAPACK-3.4.2'), os.path.join(mpi_pref, 'ScaLAPACK', '2.0.2-OpenBLAS-0.2.6-LAPACK-3.4.2'), From 502384fe76b1669efd37e808838180b6601b1ffb Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Sat, 20 Sep 2014 09:35:58 +0200 Subject: [PATCH 011/298] fix added unit test for Tcl-based module tools: don't use test modules that unload recursively and make 'purge' fail --- test/framework/easyblock.py | 4 ++-- test/framework/modules/Core/icc/2013.5.192-GCC-4.8.3 | 5 ++++- .../modules/Core/iccifort/2013.5.192-GCC-4.8.3 | 8 ++++++-- .../modules/Core/ifort/2013.5.192-GCC-4.8.3 | 5 ++++- test/framework/modules/Core/iimpi/5.5.3-GCC-4.8.3 | 12 +++++++++--- 5 files changed, 25 insertions(+), 9 deletions(-) diff --git a/test/framework/easyblock.py b/test/framework/easyblock.py index 1107f8049b..80a3a6e51a 100644 --- a/test/framework/easyblock.py +++ b/test/framework/easyblock.py @@ -489,8 +489,8 @@ def test_exclude_path_to_top_of_module_tree(self): ec = EasyConfig(os.path.join(test_ecs_path, 'imkl-11.1.2.144-iimpi-5.5.3-GCC-4.8.3.eb')) eb = EasyBlock(ec) - modfile_prefix = os.path.join(self.test_installpath, 'modules', 'all', 'MPI', 'intel', '2013.5.192', 'impi', '4.1.3.049', 'imkl') - mkdir(os.path.dirname(modfile_prefix), parents=True) + modfile_prefix = os.path.join(self.test_installpath, 'modules', 'all', 'MPI', 'intel', '2013.5.192', 'impi', '4.1.3.049') + mkdir(modfile_prefix, parents=True) eb.toolchain.prepare() modpath = eb.make_module_step() modfile_path = os.path.join(modpath, 'MPI', 'intel', '2013.5.192', 'impi', '4.1.3.049', 'imkl', '11.1.2.144') diff --git a/test/framework/modules/Core/icc/2013.5.192-GCC-4.8.3 b/test/framework/modules/Core/icc/2013.5.192-GCC-4.8.3 index 6ae62b5684..d01107fd85 100644 --- a/test/framework/modules/Core/icc/2013.5.192-GCC-4.8.3 +++ b/test/framework/modules/Core/icc/2013.5.192-GCC-4.8.3 @@ -11,7 +11,10 @@ set root /tmp/software/Core/icc/2013.5.192-GCC-4.8.3 conflict icc module use /tmp/modules/all/Compiler/intel/2013.5.192 -module load GCC/4.8.3 + +if { ![is-loaded GCC/4.8.3] } { + module load GCC/4.8.3 +} prepend-path IDB_HOME $root/bin/intel64 prepend-path LD_LIBRARY_PATH $root/compiler/lib diff --git a/test/framework/modules/Core/iccifort/2013.5.192-GCC-4.8.3 b/test/framework/modules/Core/iccifort/2013.5.192-GCC-4.8.3 index db05126002..2375404a69 100644 --- a/test/framework/modules/Core/iccifort/2013.5.192-GCC-4.8.3 +++ b/test/framework/modules/Core/iccifort/2013.5.192-GCC-4.8.3 @@ -11,9 +11,13 @@ set root /tmp/software/Core/iccifort/2013.5.192-GCC-4.8.3 conflict iccifort -module load icc/2013.5.192-GCC-4.8.3 +if { ![is-loaded icc/2013.5.192-GCC-4.8.3] } { + module load icc/2013.5.192-GCC-4.8.3 +} -module load ifort/2013.5.192-GCC-4.8.3 +if { ![is-loaded ifort/2013.5.192-GCC-4.8.3] } { + module load ifort/2013.5.192-GCC-4.8.3 +} setenv EBROOTICCIFORT "$root" diff --git a/test/framework/modules/Core/ifort/2013.5.192-GCC-4.8.3 b/test/framework/modules/Core/ifort/2013.5.192-GCC-4.8.3 index 192c8082f9..02c64f8bf8 100644 --- a/test/framework/modules/Core/ifort/2013.5.192-GCC-4.8.3 +++ b/test/framework/modules/Core/ifort/2013.5.192-GCC-4.8.3 @@ -11,7 +11,10 @@ set root /tmp/software/Core/ifort/2013.5.192-GCC-4.8.3 conflict ifort module use /tmp/modules/all/Compiler/intel/2013.5.192 -module load GCC/4.8.3 + +if { ![is-loaded GCC/4.8.3] } { + module load GCC/4.8.3 +} prepend-path IDB_HOME $root/bin/intel64 prepend-path LD_LIBRARY_PATH $root/compiler/lib diff --git a/test/framework/modules/Core/iimpi/5.5.3-GCC-4.8.3 b/test/framework/modules/Core/iimpi/5.5.3-GCC-4.8.3 index ad5b080f00..3bb0b860cb 100644 --- a/test/framework/modules/Core/iimpi/5.5.3-GCC-4.8.3 +++ b/test/framework/modules/Core/iimpi/5.5.3-GCC-4.8.3 @@ -11,11 +11,17 @@ set root /tmp/software/Core/iimpi/5.5.3-GCC-4.8.3 conflict iimpi -module load icc/2013.5.192-GCC-4.8.3 +if { ![is-loaded icc/2013.5.192-GCC-4.8.3] } { + module load icc/2013.5.192-GCC-4.8.3 +} -module load ifort/2013.5.192-GCC-4.8.3 +if { ![is-loaded ifort/2013.5.192-GCC-4.8.3] } { + module load ifort/2013.5.192-GCC-4.8.3 +} -module load impi/4.1.3.049 +if { ![is-loaded impi/4.1.3.049] } { + module load impi/4.1.3.049 +} setenv EBROOTIIMPI "$root" From 99bdab5c65e6cb3e3fbc4c7373ab21b2f1d120e8 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Sat, 20 Sep 2014 10:03:52 +0200 Subject: [PATCH 012/298] fix broken tests --- test/framework/easyblock.py | 5 +++-- test/framework/modules.py | 2 +- test/framework/scripts.py | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/test/framework/easyblock.py b/test/framework/easyblock.py index 80a3a6e51a..f5c4401fb3 100644 --- a/test/framework/easyblock.py +++ b/test/framework/easyblock.py @@ -489,8 +489,9 @@ def test_exclude_path_to_top_of_module_tree(self): ec = EasyConfig(os.path.join(test_ecs_path, 'imkl-11.1.2.144-iimpi-5.5.3-GCC-4.8.3.eb')) eb = EasyBlock(ec) - modfile_prefix = os.path.join(self.test_installpath, 'modules', 'all', 'MPI', 'intel', '2013.5.192', 'impi', '4.1.3.049') - mkdir(modfile_prefix, parents=True) + modfile_prefix = os.path.join(self.test_installpath, 'modules', 'all') + mkdir(os.path.join(modfile_prefix, 'Compiler', 'GCC', '4.8.3'), parents=True) + mkdir(os.path.join(modfile_prefix, 'MPI', 'intel', '2013.5.192', 'impi', '4.1.3.049'), parents=True) eb.toolchain.prepare() modpath = eb.make_module_step() modfile_path = os.path.join(modpath, 'MPI', 'intel', '2013.5.192', 'impi', '4.1.3.049', 'imkl', '11.1.2.144') diff --git a/test/framework/modules.py b/test/framework/modules.py index 46b7453ba8..21f0a5407c 100644 --- a/test/framework/modules.py +++ b/test/framework/modules.py @@ -43,7 +43,7 @@ # number of modules included for testing purposes -TEST_MODULES_COUNT = 44 +TEST_MODULES_COUNT = 50 class ModulesTest(EnhancedTestCase): diff --git a/test/framework/scripts.py b/test/framework/scripts.py index f9704b7937..9712938abf 100644 --- a/test/framework/scripts.py +++ b/test/framework/scripts.py @@ -65,13 +65,13 @@ def test_generate_software_list(self): out, ec = run_cmd(cmd, simple=False) # make sure output is kind of what we expect it to be - regex = r"Supported Packages \(12 " + regex = r"Supported Packages \(17 " self.assertTrue(re.search(regex, out), "Pattern '%s' found in output: %s" % (regex, out)) per_letter = { 'F': '1', # FFTW 'G': '4', # GCC, gompi, goolf, gzip 'H': '1', # hwloc - 'I': '2', # ictce, impi + 'I': '7', # icc, iccifort, ictce, ifort, iimpi, imkl, impi 'O': '2', # OpenMPI, OpenBLAS 'S': '1', # ScaLAPACK 'T': '1', # toy From 591725c45dd484aa0d5097465add1cf0a695b00c Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Sat, 20 Sep 2014 10:05:29 +0200 Subject: [PATCH 013/298] cleanup in GCC test module --- test/framework/modules/Core/GCC/4.8.3 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/framework/modules/Core/GCC/4.8.3 b/test/framework/modules/Core/GCC/4.8.3 index dded358b87..4c88e02254 100644 --- a/test/framework/modules/Core/GCC/4.8.3 +++ b/test/framework/modules/Core/GCC/4.8.3 @@ -9,10 +9,10 @@ proc ModulesHelp { } { module-whatis {Description: The GNU Compiler Collection includes front ends for C, C++, Objective-C, Fortran, Java, and Ada, as well as libraries for these languages (libstdc++, libgcj,...). - Homepage: http://gcc.gnu.org/} -set root /tmp/problem1086/software/Core/GCC/4.8.3 +set root /tmp/gsoftware/Core/GCC/4.8.3 conflict GCC -module use /tmp/problem1086/modules/all/Compiler/GCC/4.8.3 +module use /tmp/gmodules/all/Compiler/GCC/4.8.3 prepend-path CPATH $root/include prepend-path LD_LIBRARY_PATH $root/lib prepend-path LD_LIBRARY_PATH $root/lib64 From 27dc61e3f2e49c80a80c94cb02b62ac46b67bbd4 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Sat, 20 Sep 2014 10:22:33 +0200 Subject: [PATCH 014/298] fix typo in GCC test module --- test/framework/modules/Core/GCC/4.8.3 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/framework/modules/Core/GCC/4.8.3 b/test/framework/modules/Core/GCC/4.8.3 index 4c88e02254..4ddfb7dc18 100644 --- a/test/framework/modules/Core/GCC/4.8.3 +++ b/test/framework/modules/Core/GCC/4.8.3 @@ -9,10 +9,10 @@ proc ModulesHelp { } { module-whatis {Description: The GNU Compiler Collection includes front ends for C, C++, Objective-C, Fortran, Java, and Ada, as well as libraries for these languages (libstdc++, libgcj,...). - Homepage: http://gcc.gnu.org/} -set root /tmp/gsoftware/Core/GCC/4.8.3 +set root /tmp/software/Core/GCC/4.8.3 conflict GCC -module use /tmp/gmodules/all/Compiler/GCC/4.8.3 +module use /tmp/modules/all/Compiler/GCC/4.8.3 prepend-path CPATH $root/include prepend-path LD_LIBRARY_PATH $root/lib prepend-path LD_LIBRARY_PATH $root/lib64 From c24cb0fccc9001273660fc0f5b19bf88700cdd50 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Mon, 22 Sep 2014 18:26:52 +0200 Subject: [PATCH 015/298] fix remarks --- easybuild/framework/easyblock.py | 8 ++++---- easybuild/tools/environment.py | 8 +++++++- easybuild/tools/modules.py | 15 ++++++++------- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 3a5bf95e63..32bbe6a4a9 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -57,7 +57,7 @@ from easybuild.tools.build_log import EasyBuildError, print_error, print_msg from easybuild.tools.config import build_option, build_path, get_log_filename, get_repository, get_repositorypath from easybuild.tools.config import install_path, log_path, read_only_installdir, source_paths -from easybuild.tools.environment import modify_env +from easybuild.tools.environment import restore_env from easybuild.tools.filetools import DEFAULT_CHECKSUM from easybuild.tools.filetools import adjust_permissions, apply_patch, convert_name, download_file, encode_class_name from easybuild.tools.filetools import extract_file, mkdir, read_file, rmtree2 @@ -997,7 +997,7 @@ def clean_up_fake_module(self, fake_mod_data): self.log.warning("Not unloading module, since self.full_mod_name is not set.") # restore original environment - modify_env(os.environ, orig_env) + restore_env(orig_env) def load_dependency_modules(self): """Load dependency modules.""" @@ -1851,7 +1851,7 @@ def build_and_install_one(module, orig_environ): # restore original environment _log.info("Resetting environment") filetools.errors_found_in_log = 0 - modify_env(os.environ, orig_environ) + restore_env(orig_environ) cwd = os.getcwd() @@ -2054,7 +2054,7 @@ def perform_step(step, obj, method, logfile): # start with a clean slate os.chdir(base_dir) - modify_env(os.environ, base_env) + restore_env(base_env) steps = EasyBlock.get_steps(iteration_count=app.det_iter_cnt()) diff --git a/easybuild/tools/environment.py b/easybuild/tools/environment.py index 073c43829d..384c65febd 100644 --- a/easybuild/tools/environment.py +++ b/easybuild/tools/environment.py @@ -102,7 +102,6 @@ def restore_env_vars(env_keys): """ Restore the environment by setting the keys in the env_keys dict again with their old value """ - for key in env_keys: if env_keys[key] is not None: _log.info("Restoring environment variable %s (value: %s)" % (key, env_keys[key])) @@ -150,3 +149,10 @@ def modify_env(old, new): _log.debug("Key in old environment found that is not in new one: %s (%s)" % (key, old[key])) os.unsetenv(key) del os.environ[key] + + +def restore_env(env): + """ + Restore active environment based on specified dictionary. + """ + modify_env(os.environ, env) diff --git a/easybuild/tools/modules.py b/easybuild/tools/modules.py index b2e75295b2..8dbef28852 100644 --- a/easybuild/tools/modules.py +++ b/easybuild/tools/modules.py @@ -47,7 +47,7 @@ from easybuild.tools.build_log import EasyBuildError from easybuild.tools.config import build_option, get_modules_tool, install_path -from easybuild.tools.environment import modify_env +from easybuild.tools.environment import restore_env from easybuild.tools.filetools import convert_name, mkdir, read_file, path_matches, which from easybuild.tools.module_naming_scheme import DEVEL_MODULE_SUFFIX from easybuild.tools.run import run_cmd @@ -377,7 +377,7 @@ def load(self, modules, mod_paths=None, purge=False, orig_env=None): self.purge() # restore original environment if provided if orig_env is not None: - modify_env(os.environ, orig_env) + restore_env(orig_env) # make sure $MODULEPATH is set correctly after purging self.check_module_path() @@ -610,7 +610,7 @@ def modpath_extensions_for(self, mod_names): self.load([mod_name]) # restore original environment (modules may have been loaded above) - modify_env(os.environ, orig_env) + restore_env(orig_env) return modpath_exts @@ -667,10 +667,11 @@ def path_to_top_of_module_tree(self, top_paths, mod_name, full_mod_subdir, deps, full_modpath_exts = modpath_exts[dep] if path_matches(full_mod_subdir, full_modpath_exts): # full path to module subdir of dependency is simply path to module file without (short) module name - full_mod_subdirs.append(self.modulefile_path(dep)[:-len(dep)-1]) + full_mod_subdir = self.modulefile_path(dep)[:-len(dep)-1] + full_mod_subdirs.append(full_mod_subdir) mods_to_top.append(dep) - tup = (dep, full_mod_subdirs[-1], full_modpath_exts) + tup = (dep, full_mod_subdir, full_modpath_exts) self.log.debug("Found module to top of module tree: %s (subdir: %s, modpath extensions %s)" % tup) if full_modpath_exts: @@ -679,12 +680,12 @@ def path_to_top_of_module_tree(self, top_paths, mod_name, full_mod_subdir, deps, self.load([dep]) # restore original environment (modules may have been loaded above) - modify_env(os.environ, orig_env) + restore_env(orig_env) path = mods_to_top[:] if mods_to_top: # remove retained dependencies from the list, since we're climbing up the module tree - remaining_modpath_exts = dict([(m, modpath_exts[m]) for m in modpath_exts if not m in mods_to_top]) + remaining_modpath_exts = dict([m for m in modpath_exts.items() if not m[0] in mods_to_top]) self.log.debug("Path to top from %s extended to %s, so recursing to find way to the top" % (mod_name, mods_to_top)) for mod_name, full_mod_subdir in zip(mods_to_top, full_mod_subdirs): From 38302bc7bdea368500528b53be62e66d60d8b299 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Mon, 22 Sep 2014 20:04:34 +0200 Subject: [PATCH 016/298] bump version to v1.15.1, update release notes --- RELEASE_NOTES | 9 +++++++++ easybuild/tools/version.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/RELEASE_NOTES b/RELEASE_NOTES index 2186d01915..6e7de30e18 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -1,6 +1,15 @@ This file contains a description of the major changes to the easybuild-framework EasyBuild package. For more detailed information, please see the git log. +v1.15.1 (September 23rd 2014) +----------------------------- + +bugfix release +- take into account that multiple modules may be extending $MODULEPATH with the same path, + when determining path to top of module tree (see #1047) + - this bug caused a load statement for either icc or ifort to be included in higher-level + modules installed with an Intel-based compiler toolchain, under the HierarchicalMNS module naming scheme + v1.15.0 (September 12th 2014) ----------------------------- diff --git a/easybuild/tools/version.py b/easybuild/tools/version.py index f527eaecee..dcdeea01fd 100644 --- a/easybuild/tools/version.py +++ b/easybuild/tools/version.py @@ -37,7 +37,7 @@ # note: release candidates should be versioned as a pre-release, e.g. "1.1rc1" # 1.1-rc1 would indicate a post-release, i.e., and update of 1.1, so beware! -VERSION = LooseVersion("1.16.0dev") +VERSION = LooseVersion("1.15.1") UNKNOWN = "UNKNOWN" def get_git_revision(): From 6b141bbd2ae308c28eabb692feef5d280af21723 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 23 Sep 2014 15:18:05 +0200 Subject: [PATCH 017/298] fix HierarchicalMNS for cgoolf and goolfc toolchains --- .../tools/module_naming_scheme/hierarchical_mns.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/easybuild/tools/module_naming_scheme/hierarchical_mns.py b/easybuild/tools/module_naming_scheme/hierarchical_mns.py index d88f794aae..62dde03264 100644 --- a/easybuild/tools/module_naming_scheme/hierarchical_mns.py +++ b/easybuild/tools/module_naming_scheme/hierarchical_mns.py @@ -80,15 +80,22 @@ def det_toolchain_compilers_name_version(self, tc_comps): elif len(tc_comps) == 1: res = (tc_comps[0]['name'], tc_comps[0]['version']) else: - tc_comp_names = [comp['name'] for comp in tc_comps] - if set(tc_comp_names) == set(['icc', 'ifort']): + comp_versions = dict([(comp['name'], comp['version']) for comp in tc_comps]) + comp_names = comp_versions.keys() + if set(comp_names) == set(['icc', 'ifort']): tc_comp_name = 'intel' if tc_comps[0]['version'] == tc_comps[1]['version']: tc_comp_ver = tc_comps[0]['version'] else: self.log.error("Bumped into different versions for toolchain compilers: %s" % tc_comps) + elif set(comp_names) == set(['Clang', 'GCC']): + tc_comp_name = 'ClangGCC' + tc_comp_ver = '%s-%s' % (comp_versions['Clang'], comp_versions['GCC']) + elif set(comp_names) == set(['GCC', 'CUDA']): + tc_comp_name = 'GCC-CUDA' + tc_comp_ver = '%s-%s' % (comp_versions['GCC'], comp_versions['CUDA']) else: - self.log.error("Unknown set of toolchain compilers, module naming scheme needs to be enhanced first.") + self.log.error("Unknown set of toolchain compilers, module naming scheme needs work: %s" % comp_names) res = (tc_comp_name, tc_comp_ver) return res From f76e1a58a54db345a6def5a8148f1e8cebcf4048 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 23 Sep 2014 15:18:10 +0200 Subject: [PATCH 018/298] add definition of iompi toolchain --- easybuild/toolchains/iompi.py | 40 +++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 easybuild/toolchains/iompi.py diff --git a/easybuild/toolchains/iompi.py b/easybuild/toolchains/iompi.py new file mode 100644 index 0000000000..fd757a3c92 --- /dev/null +++ b/easybuild/toolchains/iompi.py @@ -0,0 +1,40 @@ +## +# Copyright 2012-2014 Ghent University +# +# This file is part of EasyBuild, +# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en), +# with support of Ghent University (http://ugent.be/hpc), +# the Flemish Supercomputer Centre (VSC) (https://vscentrum.be/nl/en), +# the Hercules foundation (http://www.herculesstichting.be/in_English) +# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en). +# +# http://github.com/hpcugent/easybuild +# +# EasyBuild is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation v2. +# +# EasyBuild is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with EasyBuild. If not, see . +## +""" +EasyBuild support for iompi compiler toolchain (includes Intel compilers (icc, ifort) and OpenMPI. + +@author: Stijn De Weirdt (Ghent University) +@author: Kenneth Hoste (Ghent University) +""" + +from easybuild.toolchains.compiler.inteliccifort import IntelIccIfort +from easybuild.toolchains.mpi.openmpi import OpenMPI + + +class Iompi(IntelIccIfort, OpenMPI): + """ + Compiler toolchain with Intel compilers (icc/ifort) and OpenMPI. + """ + NAME = 'iompi' From 6ea8bbb1726557ce21794044f05b19085d6971ec Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 23 Sep 2014 17:28:36 +0200 Subject: [PATCH 019/298] clean up code in HierarchicalMNS.det_toolchain_compilers_name_version --- .../module_naming_scheme/hierarchical_mns.py | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/easybuild/tools/module_naming_scheme/hierarchical_mns.py b/easybuild/tools/module_naming_scheme/hierarchical_mns.py index 62dde03264..f4113fbe71 100644 --- a/easybuild/tools/module_naming_scheme/hierarchical_mns.py +++ b/easybuild/tools/module_naming_scheme/hierarchical_mns.py @@ -82,18 +82,15 @@ def det_toolchain_compilers_name_version(self, tc_comps): else: comp_versions = dict([(comp['name'], comp['version']) for comp in tc_comps]) comp_names = comp_versions.keys() - if set(comp_names) == set(['icc', 'ifort']): - tc_comp_name = 'intel' - if tc_comps[0]['version'] == tc_comps[1]['version']: - tc_comp_ver = tc_comps[0]['version'] - else: - self.log.error("Bumped into different versions for toolchain compilers: %s" % tc_comps) - elif set(comp_names) == set(['Clang', 'GCC']): - tc_comp_name = 'ClangGCC' - tc_comp_ver = '%s-%s' % (comp_versions['Clang'], comp_versions['GCC']) - elif set(comp_names) == set(['GCC', 'CUDA']): - tc_comp_name = 'GCC-CUDA' - tc_comp_ver = '%s-%s' % (comp_versions['GCC'], comp_versions['CUDA']) + name_version_templates = { + 'icc,ifort': ('intel', '%(icc)s'), + 'Clang,GCC': ('Clang-GCC', '%(Clang)s-%(GCC)s'), + 'CUDA,GCC': ('GCC-CUDA', '%(GCC)s-%(CUDA)s'), + } + key = ','.join(sorted(comp_names)) + if key in name_version_templates: + tc_comp_name, tc_comp_ver_tmpl = name_version_templates[key] + tc_comp_ver = tc_comp_ver_tmpl % comp_versions else: self.log.error("Unknown set of toolchain compilers, module naming scheme needs work: %s" % comp_names) res = (tc_comp_name, tc_comp_ver) From 0775a2139d152a23ae37ea9ab62415969b5a26d6 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 23 Sep 2014 17:57:25 +0200 Subject: [PATCH 020/298] fix remarks --- .../module_naming_scheme/hierarchical_mns.py | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/easybuild/tools/module_naming_scheme/hierarchical_mns.py b/easybuild/tools/module_naming_scheme/hierarchical_mns.py index f4113fbe71..5efc55a2ee 100644 --- a/easybuild/tools/module_naming_scheme/hierarchical_mns.py +++ b/easybuild/tools/module_naming_scheme/hierarchical_mns.py @@ -43,6 +43,13 @@ MODULECLASS_COMPILER = 'compiler' MODULECLASS_MPI = 'mpi' +# note: names in keys are ordered alphabetically +COMP_NAME_VERSION_TEMPLATES = { + 'icc,ifort': ('intel', '%(icc)s'), + 'Clang,GCC': ('Clang-GCC', '%(Clang)s-%(GCC)s'), + 'CUDA,GCC': ('GCC-CUDA', '%(GCC)s-%(CUDA)s'), +} + class HierarchicalMNS(ModuleNamingScheme): """Class implementing an example hierarchical module naming scheme.""" @@ -82,15 +89,14 @@ def det_toolchain_compilers_name_version(self, tc_comps): else: comp_versions = dict([(comp['name'], comp['version']) for comp in tc_comps]) comp_names = comp_versions.keys() - name_version_templates = { - 'icc,ifort': ('intel', '%(icc)s'), - 'Clang,GCC': ('Clang-GCC', '%(Clang)s-%(GCC)s'), - 'CUDA,GCC': ('GCC-CUDA', '%(GCC)s-%(CUDA)s'), - } key = ','.join(sorted(comp_names)) - if key in name_version_templates: - tc_comp_name, tc_comp_ver_tmpl = name_version_templates[key] + if key in COMP_NAME_VERSION_TEMPLATES: + tc_comp_name, tc_comp_ver_tmpl = COMP_NAME_VERSION_TEMPLATES[key] tc_comp_ver = tc_comp_ver_tmpl % comp_versions + # make sure that icc/ifort versions match + if tc_comp_name == 'intel': + if comp_versions['icc'] != comp_versions['ifort']: + self.log.error("Bumped into different versions for Intel compilers: %s" % comp_versions) else: self.log.error("Unknown set of toolchain compilers, module naming scheme needs work: %s" % comp_names) res = (tc_comp_name, tc_comp_ver) From 14e489df1c09b465adc3e6e80f15cfde799ff76d Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 23 Sep 2014 18:05:38 +0200 Subject: [PATCH 021/298] fix minor remark --- easybuild/tools/module_naming_scheme/hierarchical_mns.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/easybuild/tools/module_naming_scheme/hierarchical_mns.py b/easybuild/tools/module_naming_scheme/hierarchical_mns.py index 5efc55a2ee..420fb67a38 100644 --- a/easybuild/tools/module_naming_scheme/hierarchical_mns.py +++ b/easybuild/tools/module_naming_scheme/hierarchical_mns.py @@ -94,8 +94,7 @@ def det_toolchain_compilers_name_version(self, tc_comps): tc_comp_name, tc_comp_ver_tmpl = COMP_NAME_VERSION_TEMPLATES[key] tc_comp_ver = tc_comp_ver_tmpl % comp_versions # make sure that icc/ifort versions match - if tc_comp_name == 'intel': - if comp_versions['icc'] != comp_versions['ifort']: + if tc_comp_name == 'intel' and comp_versions['icc'] != comp_versions['ifort']: self.log.error("Bumped into different versions for Intel compilers: %s" % comp_versions) else: self.log.error("Unknown set of toolchain compilers, module naming scheme needs work: %s" % comp_names) From db628f4f0016dff998dc9576e60c3b5d4d846160 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 23 Sep 2014 19:22:31 +0200 Subject: [PATCH 022/298] fix indent --- easybuild/tools/module_naming_scheme/hierarchical_mns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/tools/module_naming_scheme/hierarchical_mns.py b/easybuild/tools/module_naming_scheme/hierarchical_mns.py index 420fb67a38..c415d61d72 100644 --- a/easybuild/tools/module_naming_scheme/hierarchical_mns.py +++ b/easybuild/tools/module_naming_scheme/hierarchical_mns.py @@ -95,7 +95,7 @@ def det_toolchain_compilers_name_version(self, tc_comps): tc_comp_ver = tc_comp_ver_tmpl % comp_versions # make sure that icc/ifort versions match if tc_comp_name == 'intel' and comp_versions['icc'] != comp_versions['ifort']: - self.log.error("Bumped into different versions for Intel compilers: %s" % comp_versions) + self.log.error("Bumped into different versions for Intel compilers: %s" % comp_versions) else: self.log.error("Unknown set of toolchain compilers, module naming scheme needs work: %s" % comp_names) res = (tc_comp_name, tc_comp_ver) From fa33d36a0f839f4ad3ea05ec6cec7d3bc57ac7b6 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 24 Sep 2014 10:10:37 +0200 Subject: [PATCH 023/298] extend release notes w.r.t. PR #1049 --- RELEASE_NOTES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASE_NOTES b/RELEASE_NOTES index 6e7de30e18..7c587db954 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -9,6 +9,8 @@ bugfix release when determining path to top of module tree (see #1047) - this bug caused a load statement for either icc or ifort to be included in higher-level modules installed with an Intel-based compiler toolchain, under the HierarchicalMNS module naming scheme +- make HierarchicalMNS module naming scheme compatible with cgoolf and goolfc toolchain (#1049) +- add definition of iompi (sub)toolchain to make iomkl toolchain compatible with HierarchicalMNS (#1049) v1.15.0 (September 12th 2014) ----------------------------- From 77d499b6e3cb5e84a88589a7f17aef3477cf5fbd Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 25 Sep 2014 15:32:02 +0200 Subject: [PATCH 024/298] fix version to v1.16.0dev --- easybuild/tools/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/tools/version.py b/easybuild/tools/version.py index dcdeea01fd..f527eaecee 100644 --- a/easybuild/tools/version.py +++ b/easybuild/tools/version.py @@ -37,7 +37,7 @@ # note: release candidates should be versioned as a pre-release, e.g. "1.1rc1" # 1.1-rc1 would indicate a post-release, i.e., and update of 1.1, so beware! -VERSION = LooseVersion("1.15.1") +VERSION = LooseVersion("1.16.0dev") UNKNOWN = "UNKNOWN" def get_git_revision(): From e6b477781c9c1f7fb6c9ea4fb128d689693f04f2 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 1 Oct 2014 17:16:04 +0200 Subject: [PATCH 025/298] fix $MODULEPATH extensions for Clang/CUDA, include versionsuffix in module subdir --- .../module_naming_scheme/hierarchical_mns.py | 36 +++++++++++++++---- .../easyconfigs/CUDA-5.5.22-GCC-4.8.2.eb | 29 +++++++++++++++ test/framework/easyconfigs/GCC-4.8.2.eb | 28 +++++++++++++++ .../framework/easyconfigs/ifort-2013.3.163.eb | 17 +++++++++ test/framework/module_generator.py | 4 +++ 5 files changed, 107 insertions(+), 7 deletions(-) create mode 100644 test/framework/easyconfigs/CUDA-5.5.22-GCC-4.8.2.eb create mode 100644 test/framework/easyconfigs/GCC-4.8.2.eb create mode 100644 test/framework/easyconfigs/ifort-2013.3.163.eb diff --git a/easybuild/tools/module_naming_scheme/hierarchical_mns.py b/easybuild/tools/module_naming_scheme/hierarchical_mns.py index c415d61d72..27ba3de274 100644 --- a/easybuild/tools/module_naming_scheme/hierarchical_mns.py +++ b/easybuild/tools/module_naming_scheme/hierarchical_mns.py @@ -30,6 +30,7 @@ """ import os +import re from vsc.utils import fancylogger from easybuild.tools.module_naming_scheme import ModuleNamingScheme @@ -132,17 +133,38 @@ def det_modpath_extensions(self, ec): Examples: Compiler/GCC/4.8.3 (for GCC/4.8.3 module), MPI/GCC/4.8.3/OpenMPI/1.6.5 (for OpenMPI/1.6.5 module) """ modclass = ec['moduleclass'] + tc_comps = det_toolchain_compilers(ec) + tc_comp_info = self.det_toolchain_compilers_name_version(tc_comps) paths = [] - if modclass == MODULECLASS_COMPILER: - if ec['name'] in ['icc', 'ifort']: - compdir = 'intel' + if modclass == MODULECLASS_COMPILER or ec['name'] in ['CUDA']: + # obtain list of compilers based on that extend $MODULEPATH in some way other than / + extend_comps = [] + # exclude GCC for which / is used as $MODULEPATH extension + excluded_comps = ['GCC'] + for comps in COMP_NAME_VERSION_TEMPLATES.keys(): + extend_comps.extend([comp for comp in comps.split(',') if comp not in excluded_comps]) + + comp_name_ver = None + if ec['name'] in extend_comps: + for key in COMP_NAME_VERSION_TEMPLATES: + if re.search(".*%s.*" % ec['name'], key): + comp_name, comp_ver_tmpl = COMP_NAME_VERSION_TEMPLATES[key] + comp_versions = {ec['name']: ec['version'] + ec['versionsuffix']} + if ec['name'] == 'ifort': + # 'icc' key should be provided since it's the only one used in the template + comp_versions.update({'icc': ec['version'] + ec['versionsuffix']}) + if tc_comp_info is not None: + # also provide toolchain version for non-dummy toolchains + comp_versions.update({tc_comp_info[0]: tc_comp_info[1]}) + comp_name_ver = [comp_name, comp_ver_tmpl % comp_versions] + break else: - compdir = ec['name'] - paths.append(os.path.join(COMPILER, compdir, ec['version'])) + comp_name_ver = [ec['name'], ec['version'] + ec['versionsuffix']] + + paths.append(os.path.join(COMPILER, *comp_name_ver)) + elif modclass == MODULECLASS_MPI: - tc_comps = det_toolchain_compilers(ec) - tc_comp_info = self.det_toolchain_compilers_name_version(tc_comps) if tc_comp_info is None: tup = (ec['toolchain'], ec['name'], ec['version']) error_msg = ("No compiler available in toolchain %s used to install MPI library %s v%s, " diff --git a/test/framework/easyconfigs/CUDA-5.5.22-GCC-4.8.2.eb b/test/framework/easyconfigs/CUDA-5.5.22-GCC-4.8.2.eb new file mode 100644 index 0000000000..a8e69945a9 --- /dev/null +++ b/test/framework/easyconfigs/CUDA-5.5.22-GCC-4.8.2.eb @@ -0,0 +1,29 @@ +## +# This file is an EasyBuild reciPY as per https://github.com/hpcugent/easybuild +# +# Copyright:: Copyright 2012-2013 Cyprus Institute / CaSToRC, University of Luxembourg / LCSB, Ghent University +# Authors:: George Tsouloupas , Fotis Georgatos , Kenneth Hoste +# License:: MIT/GPL +# $Id$ +# +# This work implements a part of the HPCBIOS project and is a component of the policy: +# http://hpcbios.readthedocs.org/en/latest/HPCBIOS_2012-99.html +## + +name = 'CUDA' +version = '5.5.22' + +homepage = 'https://developer.nvidia.com/cuda-toolkit' +description = """CUDA (formerly Compute Unified Device Architecture) is a parallel + computing platform and programming model created by NVIDIA and implemented by the + graphics processing units (GPUs) that they produce. CUDA gives developers access + to the virtual instruction set and memory of the parallel computational elements in CUDA GPUs.""" + +toolchain = {'name': 'GCC', 'version': '4.8.2'} + +# eg. http://developer.download.nvidia.com/compute/cuda/5_5/rel/installers/cuda_5.5.22_linux_64.run +source_urls = ['http://developer.download.nvidia.com/compute/cuda/5_5/rel/installers/'] + +sources = ['%(namelower)s_%(version)s_linux_64.run'] + +moduleclass = 'system' diff --git a/test/framework/easyconfigs/GCC-4.8.2.eb b/test/framework/easyconfigs/GCC-4.8.2.eb new file mode 100644 index 0000000000..cef0802601 --- /dev/null +++ b/test/framework/easyconfigs/GCC-4.8.2.eb @@ -0,0 +1,28 @@ +name = "GCC" +version = '4.8.2' + +homepage = 'http://gcc.gnu.org/' +description = """The GNU Compiler Collection includes front ends for C, C++, Objective-C, Fortran, Java, and Ada, + as well as libraries for these languages (libstdc++, libgcj,...).""" + +toolchain = {'name': 'dummy', 'version': 'dummy'} + +source_urls = [ + 'http://ftpmirror.gnu.org/%(namelower)s/%(namelower)s-%(version)s', # GCC auto-resolving HTTP mirror + 'http://ftpmirror.gnu.org/gmp', # idem for GMP + 'http://ftpmirror.gnu.org/mpfr', # idem for MPFR + 'http://www.multiprecision.org/mpc/download', # MPC official +] +sources = [ + SOURCELOWER_TAR_GZ, + 'gmp-5.1.3.tar.bz2', + 'mpfr-3.1.2.tar.gz', + 'mpc-1.0.1.tar.gz', +] + +languages = ['c', 'c++', 'fortran', 'lto'] + +# building GCC sometimes fails if make parallelism is too high, so let's limit it +maxparallel = 4 + +moduleclass = 'compiler' diff --git a/test/framework/easyconfigs/ifort-2013.3.163.eb b/test/framework/easyconfigs/ifort-2013.3.163.eb new file mode 100644 index 0000000000..4efd890d23 --- /dev/null +++ b/test/framework/easyconfigs/ifort-2013.3.163.eb @@ -0,0 +1,17 @@ +name = 'ifort' +version = '2013.3.163' + +homepage = 'http://software.intel.com/en-us/intel-compilers/' +description = "Fortran compiler from Intel" + +toolchain = {'name': 'dummy', 'version': 'dummy'} + +sources = ['l_fcompxe_%s.tgz' % version] + +dontcreateinstalldir = 'True' + +# license file +import os +license_file = os.path.join(os.getenv('HOME'), "licenses", "intel", "license.lic") + +moduleclass = 'compiler' diff --git a/test/framework/module_generator.py b/test/framework/module_generator.py index 9d0c46188b..cd8cddb439 100644 --- a/test/framework/module_generator.py +++ b/test/framework/module_generator.py @@ -393,11 +393,15 @@ def test_ec(ecfile, short_modname, mod_subdir, modpath_exts, init_modpaths): init_config(build_options=build_options) # format: easyconfig_file: (short_mod_name, mod_subdir, modpath_extensions) + iccver = '2013.5.192-GCC-4.8.3' test_ecs = { 'GCC-4.7.2.eb': ('GCC/4.7.2', 'Core', ['Compiler/GCC/4.7.2'], ['Core']), 'OpenMPI-1.6.4-GCC-4.7.2.eb': ('OpenMPI/1.6.4', 'Compiler/GCC/4.7.2', ['MPI/GCC/4.7.2/OpenMPI/1.6.4'], ['Core']), 'gzip-1.5-goolf-1.4.10.eb': ('gzip/1.5', 'MPI/GCC/4.7.2/OpenMPI/1.6.4', [], ['Core']), 'goolf-1.4.10.eb': ('goolf/1.4.10', 'Core', [], ['Core']), + 'icc-2013.5.192-GCC-4.8.3.eb': ('icc/%s' % iccver, 'Core', ['Compiler/intel/%s' % iccver], ['Core']), + 'ifort-2013.3.163.eb': ('ifort/2013.3.163', 'Core', ['Compiler/intel/2013.3.163'], ['Core']), + 'CUDA-5.5.22-GCC-4.8.2.eb': ('CUDA/5.5.22', 'Compiler/GCC/4.8.2', ['Compiler/GCC-CUDA/4.8.2-5.5.22'], ['Core']), } for ecfile, mns_vals in test_ecs.items(): test_ec(ecfile, *mns_vals) From 17d548a7a36410be55b4d1c5c4c4b2920613a42d Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 2 Oct 2014 12:17:04 +0200 Subject: [PATCH 026/298] fix tiny remark --- easybuild/tools/module_naming_scheme/hierarchical_mns.py | 1 + 1 file changed, 1 insertion(+) diff --git a/easybuild/tools/module_naming_scheme/hierarchical_mns.py b/easybuild/tools/module_naming_scheme/hierarchical_mns.py index 27ba3de274..184c596c43 100644 --- a/easybuild/tools/module_naming_scheme/hierarchical_mns.py +++ b/easybuild/tools/module_naming_scheme/hierarchical_mns.py @@ -157,6 +157,7 @@ def det_modpath_extensions(self, ec): if tc_comp_info is not None: # also provide toolchain version for non-dummy toolchains comp_versions.update({tc_comp_info[0]: tc_comp_info[1]}) + comp_name_ver = [comp_name, comp_ver_tmpl % comp_versions] break else: From 0b2066221329e6644f37953080a3178f006d7671 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 2 Oct 2014 12:17:50 +0200 Subject: [PATCH 027/298] fix unit tests broken due to shellshock patch --- easybuild/tools/modules.py | 18 +++++++++++++----- test/framework/modulestool.py | 17 ++++++++--------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/easybuild/tools/modules.py b/easybuild/tools/modules.py index 8dbef28852..c3c2c2b81a 100644 --- a/easybuild/tools/modules.py +++ b/easybuild/tools/modules.py @@ -137,12 +137,14 @@ class ModulesTool(object): __metaclass__ = Singleton - def __init__(self, mod_paths=None): + def __init__(self, mod_paths=None, testing=False): """ Create a ModulesTool object @param mod_paths: A list of paths where the modules can be located @type mod_paths: list """ + # this can/should be set to True during testing + self.testing = testing self.log = fancylogger.getLogger(self.__class__.__name__, fname=False) self.mod_paths = None @@ -172,9 +174,6 @@ def __init__(self, mod_paths=None): self.check_module_function(allow_mismatch=build_option('allow_modules_tool_mismatch')) self.set_and_check_version() - # this can/should be set to True during testing - self.testing = False - def buildstats(self): """Return tuple with data to be included in buildstats""" return (self.__class__.__name__, self.cmd, self.version) @@ -229,11 +228,20 @@ def check_cmd_avail(self): def check_module_function(self, allow_mismatch=False, regex=None): """Check whether selected module tool matches 'module' function definition.""" - out, ec = run_cmd("type module", simple=False, log_ok=False, log_all=False) + if self.testing: + # grab 'module' function definition from environment if it's there; only during testing + if 'module' in os.environ: + out, ec = os.environ['module'], 0 + else: + out, ec = None, 1 + else: + out, ec = run_cmd("type module", simple=False, log_ok=False, log_all=False) + if regex is None: regex = r".*%s" % os.path.basename(self.cmd) mod_cmd_re = re.compile(regex, re.M) mod_details = "pattern '%s' (%s)" % (mod_cmd_re.pattern, self.__class__.__name__) + if ec == 0: if mod_cmd_re.search(out): self.log.debug("Found pattern '%s' in defined 'module' function." % mod_cmd_re.pattern) diff --git a/test/framework/modulestool.py b/test/framework/modulestool.py index ad8c28de27..82e6cf39d4 100644 --- a/test/framework/modulestool.py +++ b/test/framework/modulestool.py @@ -76,7 +76,7 @@ def test_mock(self): os.environ['module'] = "() { eval `/bin/echo $*`\n}" # ue empty mod_path list, otherwise the install_path is called - mmt = MockModulesTool(mod_paths=[]) + mmt = MockModulesTool(mod_paths=[], testing=True) # the version of the MMT is the commandline option self.assertEqual(mmt.version, StrictVersion(MockModulesTool.VERSION_OPTION)) @@ -91,7 +91,7 @@ def test_environment_command(self): os.environ['module'] = "() { %s $*\n}" % BrokenMockModulesTool.COMMAND try: - bmmt = BrokenMockModulesTool(mod_paths=[]) + bmmt = BrokenMockModulesTool(mod_paths=[], testing=True) # should never get here self.assertTrue(False, 'BrokenMockModulesTool should fail') except EasyBuildError, err: @@ -100,7 +100,7 @@ def test_environment_command(self): os.environ[BrokenMockModulesTool.COMMAND_ENVIRONMENT] = MockModulesTool.COMMAND os.environ['module'] = "() { /bin/echo $*\n}" BrokenMockModulesTool._instances.pop(BrokenMockModulesTool, None) - bmmt = BrokenMockModulesTool(mod_paths=[]) + bmmt = BrokenMockModulesTool(mod_paths=[], testing=True) cmd_abspath = which(MockModulesTool.COMMAND) self.assertEqual(bmmt.version, StrictVersion(MockModulesTool.VERSION_OPTION)) @@ -112,7 +112,7 @@ def test_environment_command(self): def test_module_mismatch(self): """Test whether mismatch detection between modules tool and 'module' function works.""" # redefine 'module' function (deliberate mismatch with used module command in MockModulesTool) - os.environ['module'] = "() { eval `/Users/kehoste/Modules/$MODULE_VERSION/bin/modulecmd bash $*`\n}" + os.environ['module'] = "() { eval `/tmp/Modules/$MODULE_VERSION/bin/modulecmd bash $*`\n}" self.assertErrorRegex(EasyBuildError, ".*pattern .* not found in defined 'module' function", MockModulesTool) # check whether escaping error by allowing mismatch via build options works @@ -123,7 +123,7 @@ def test_module_mismatch(self): fancylogger.logToFile(self.logfile) - mt = MockModulesTool() + mt = MockModulesTool(testing=True) f = open(self.logfile, 'r') logtxt = f.read() f.close() @@ -133,13 +133,13 @@ def test_module_mismatch(self): # redefine 'module' function with correct module command os.environ['module'] = "() { eval `/bin/echo $*`\n}" MockModulesTool._instances.pop(MockModulesTool) - mt = MockModulesTool() + mt = MockModulesTool(testing=True) self.assertTrue(isinstance(mt.loaded_modules(), list)) # dummy usage # a warning should be logged if the 'module' function is undefined del os.environ['module'] MockModulesTool._instances.pop(MockModulesTool) - mt = MockModulesTool() + mt = MockModulesTool(testing=True) f = open(self.logfile, 'r') logtxt = f.read() f.close() @@ -173,8 +173,7 @@ def test_lmod_specific(self): # initialize Lmod modules tool, pass full path to 'lmod' via $LMOD_CMD os.environ['LMOD_CMD'] = lmod_abspath - lmod = Lmod() - lmod.testing = True + lmod = Lmod(testing=True) # obtain list of availabe modules, should be non-empty self.assertTrue(lmod.available(), "List of available modules obtained using Lmod is non-empty") From 30063544c538c4e49a4289535a4477780f629f7e Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 2 Oct 2014 12:20:57 +0200 Subject: [PATCH 028/298] fix broken test --- test/framework/scripts.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/framework/scripts.py b/test/framework/scripts.py index 9712938abf..1776f8cdc1 100644 --- a/test/framework/scripts.py +++ b/test/framework/scripts.py @@ -65,9 +65,10 @@ def test_generate_software_list(self): out, ec = run_cmd(cmd, simple=False) # make sure output is kind of what we expect it to be - regex = r"Supported Packages \(17 " + regex = r"Supported Packages \(18 " self.assertTrue(re.search(regex, out), "Pattern '%s' found in output: %s" % (regex, out)) per_letter = { + 'C': '1', # CUDA 'F': '1', # FFTW 'G': '4', # GCC, gompi, goolf, gzip 'H': '1', # hwloc From 0aab07da44463597bdf700a8e19e20ae3a3fb3d5 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 2 Oct 2014 13:23:23 +0200 Subject: [PATCH 029/298] fix case where testing is not enabled in modulestool.py tests --- test/framework/modulestool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/framework/modulestool.py b/test/framework/modulestool.py index 82e6cf39d4..80c745fb31 100644 --- a/test/framework/modulestool.py +++ b/test/framework/modulestool.py @@ -113,7 +113,7 @@ def test_module_mismatch(self): """Test whether mismatch detection between modules tool and 'module' function works.""" # redefine 'module' function (deliberate mismatch with used module command in MockModulesTool) os.environ['module'] = "() { eval `/tmp/Modules/$MODULE_VERSION/bin/modulecmd bash $*`\n}" - self.assertErrorRegex(EasyBuildError, ".*pattern .* not found in defined 'module' function", MockModulesTool) + self.assertErrorRegex(EasyBuildError, ".*pattern .* not found in defined 'module' function", MockModulesTool, testing=True) # check whether escaping error by allowing mismatch via build options works build_options = { From b092c5f8ef52c0f6efe8384046a6af9acadaec59 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 2 Oct 2014 14:05:09 +0200 Subject: [PATCH 030/298] fix long line --- test/framework/modulestool.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/framework/modulestool.py b/test/framework/modulestool.py index 80c745fb31..5b92f8822a 100644 --- a/test/framework/modulestool.py +++ b/test/framework/modulestool.py @@ -113,7 +113,8 @@ def test_module_mismatch(self): """Test whether mismatch detection between modules tool and 'module' function works.""" # redefine 'module' function (deliberate mismatch with used module command in MockModulesTool) os.environ['module'] = "() { eval `/tmp/Modules/$MODULE_VERSION/bin/modulecmd bash $*`\n}" - self.assertErrorRegex(EasyBuildError, ".*pattern .* not found in defined 'module' function", MockModulesTool, testing=True) + error_regex = ".*pattern .* not found in defined 'module' function" + self.assertErrorRegex(EasyBuildError, error_regex, MockModulesTool, testing=True) # check whether escaping error by allowing mismatch via build options works build_options = { From 49da67ceb96340b22a3884a4486b97631caecaf6 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 2 Oct 2014 14:06:01 +0200 Subject: [PATCH 031/298] fix broken test that verifies mismatch on 'module' function --- easybuild/main.py | 2 +- easybuild/tools/modules.py | 4 ++-- easybuild/tools/testing.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/easybuild/main.py b/easybuild/main.py index b250876641..a572ac2c05 100644 --- a/easybuild/main.py +++ b/easybuild/main.py @@ -276,7 +276,7 @@ def main(testing_data=(None, None, None)): }) # obtain list of loaded modules, build options must be initialized first - modlist = session_module_list() + modlist = session_module_list(testing=testing) init_session_state.update({'module_list': modlist}) _log.debug("Initial session state: %s" % init_session_state) diff --git a/easybuild/tools/modules.py b/easybuild/tools/modules.py index c3c2c2b81a..7111fd872b 100644 --- a/easybuild/tools/modules.py +++ b/easybuild/tools/modules.py @@ -973,7 +973,7 @@ def avail_modules_tools(): return class_dict -def modules_tool(mod_paths=None): +def modules_tool(mod_paths=None, testing=False): """ Return interface to modules tool (environment modules (C, Tcl), or Lmod) """ @@ -981,7 +981,7 @@ def modules_tool(mod_paths=None): modules_tool = get_modules_tool() if modules_tool is not None: modules_tool_class = avail_modules_tools().get(modules_tool) - return modules_tool_class(mod_paths=mod_paths) + return modules_tool_class(mod_paths=mod_paths, testing=testing) else: return None diff --git a/easybuild/tools/testing.py b/easybuild/tools/testing.py index b572921f7e..fa579e2107 100644 --- a/easybuild/tools/testing.py +++ b/easybuild/tools/testing.py @@ -157,9 +157,9 @@ def session_state(): } -def session_module_list(): +def session_module_list(testing=False): """Get list of loaded modules ('module list').""" - modtool = modules_tool() + modtool = modules_tool(testing=testing) return modtool.list() From 4b19da1ab4451f58f2f7283c855d58c6648aec10 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 2 Oct 2014 14:08:09 +0200 Subject: [PATCH 032/298] fix remark --- easybuild/tools/module_naming_scheme/hierarchical_mns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/tools/module_naming_scheme/hierarchical_mns.py b/easybuild/tools/module_naming_scheme/hierarchical_mns.py index 184c596c43..9eee1439cc 100644 --- a/easybuild/tools/module_naming_scheme/hierarchical_mns.py +++ b/easybuild/tools/module_naming_scheme/hierarchical_mns.py @@ -148,7 +148,7 @@ def det_modpath_extensions(self, ec): comp_name_ver = None if ec['name'] in extend_comps: for key in COMP_NAME_VERSION_TEMPLATES: - if re.search(".*%s.*" % ec['name'], key): + if ec['name'] in key.split(','): comp_name, comp_ver_tmpl = COMP_NAME_VERSION_TEMPLATES[key] comp_versions = {ec['name']: ec['version'] + ec['versionsuffix']} if ec['name'] == 'ifort': From 178769b065ff15e7bd91f4a79cba27b1642bcb01 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 2 Oct 2014 12:17:50 +0200 Subject: [PATCH 033/298] fix unit tests broken due to shellshock patch --- easybuild/tools/modules.py | 18 +++++++++++++----- test/framework/modulestool.py | 17 ++++++++--------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/easybuild/tools/modules.py b/easybuild/tools/modules.py index 8dbef28852..c3c2c2b81a 100644 --- a/easybuild/tools/modules.py +++ b/easybuild/tools/modules.py @@ -137,12 +137,14 @@ class ModulesTool(object): __metaclass__ = Singleton - def __init__(self, mod_paths=None): + def __init__(self, mod_paths=None, testing=False): """ Create a ModulesTool object @param mod_paths: A list of paths where the modules can be located @type mod_paths: list """ + # this can/should be set to True during testing + self.testing = testing self.log = fancylogger.getLogger(self.__class__.__name__, fname=False) self.mod_paths = None @@ -172,9 +174,6 @@ def __init__(self, mod_paths=None): self.check_module_function(allow_mismatch=build_option('allow_modules_tool_mismatch')) self.set_and_check_version() - # this can/should be set to True during testing - self.testing = False - def buildstats(self): """Return tuple with data to be included in buildstats""" return (self.__class__.__name__, self.cmd, self.version) @@ -229,11 +228,20 @@ def check_cmd_avail(self): def check_module_function(self, allow_mismatch=False, regex=None): """Check whether selected module tool matches 'module' function definition.""" - out, ec = run_cmd("type module", simple=False, log_ok=False, log_all=False) + if self.testing: + # grab 'module' function definition from environment if it's there; only during testing + if 'module' in os.environ: + out, ec = os.environ['module'], 0 + else: + out, ec = None, 1 + else: + out, ec = run_cmd("type module", simple=False, log_ok=False, log_all=False) + if regex is None: regex = r".*%s" % os.path.basename(self.cmd) mod_cmd_re = re.compile(regex, re.M) mod_details = "pattern '%s' (%s)" % (mod_cmd_re.pattern, self.__class__.__name__) + if ec == 0: if mod_cmd_re.search(out): self.log.debug("Found pattern '%s' in defined 'module' function." % mod_cmd_re.pattern) diff --git a/test/framework/modulestool.py b/test/framework/modulestool.py index ad8c28de27..82e6cf39d4 100644 --- a/test/framework/modulestool.py +++ b/test/framework/modulestool.py @@ -76,7 +76,7 @@ def test_mock(self): os.environ['module'] = "() { eval `/bin/echo $*`\n}" # ue empty mod_path list, otherwise the install_path is called - mmt = MockModulesTool(mod_paths=[]) + mmt = MockModulesTool(mod_paths=[], testing=True) # the version of the MMT is the commandline option self.assertEqual(mmt.version, StrictVersion(MockModulesTool.VERSION_OPTION)) @@ -91,7 +91,7 @@ def test_environment_command(self): os.environ['module'] = "() { %s $*\n}" % BrokenMockModulesTool.COMMAND try: - bmmt = BrokenMockModulesTool(mod_paths=[]) + bmmt = BrokenMockModulesTool(mod_paths=[], testing=True) # should never get here self.assertTrue(False, 'BrokenMockModulesTool should fail') except EasyBuildError, err: @@ -100,7 +100,7 @@ def test_environment_command(self): os.environ[BrokenMockModulesTool.COMMAND_ENVIRONMENT] = MockModulesTool.COMMAND os.environ['module'] = "() { /bin/echo $*\n}" BrokenMockModulesTool._instances.pop(BrokenMockModulesTool, None) - bmmt = BrokenMockModulesTool(mod_paths=[]) + bmmt = BrokenMockModulesTool(mod_paths=[], testing=True) cmd_abspath = which(MockModulesTool.COMMAND) self.assertEqual(bmmt.version, StrictVersion(MockModulesTool.VERSION_OPTION)) @@ -112,7 +112,7 @@ def test_environment_command(self): def test_module_mismatch(self): """Test whether mismatch detection between modules tool and 'module' function works.""" # redefine 'module' function (deliberate mismatch with used module command in MockModulesTool) - os.environ['module'] = "() { eval `/Users/kehoste/Modules/$MODULE_VERSION/bin/modulecmd bash $*`\n}" + os.environ['module'] = "() { eval `/tmp/Modules/$MODULE_VERSION/bin/modulecmd bash $*`\n}" self.assertErrorRegex(EasyBuildError, ".*pattern .* not found in defined 'module' function", MockModulesTool) # check whether escaping error by allowing mismatch via build options works @@ -123,7 +123,7 @@ def test_module_mismatch(self): fancylogger.logToFile(self.logfile) - mt = MockModulesTool() + mt = MockModulesTool(testing=True) f = open(self.logfile, 'r') logtxt = f.read() f.close() @@ -133,13 +133,13 @@ def test_module_mismatch(self): # redefine 'module' function with correct module command os.environ['module'] = "() { eval `/bin/echo $*`\n}" MockModulesTool._instances.pop(MockModulesTool) - mt = MockModulesTool() + mt = MockModulesTool(testing=True) self.assertTrue(isinstance(mt.loaded_modules(), list)) # dummy usage # a warning should be logged if the 'module' function is undefined del os.environ['module'] MockModulesTool._instances.pop(MockModulesTool) - mt = MockModulesTool() + mt = MockModulesTool(testing=True) f = open(self.logfile, 'r') logtxt = f.read() f.close() @@ -173,8 +173,7 @@ def test_lmod_specific(self): # initialize Lmod modules tool, pass full path to 'lmod' via $LMOD_CMD os.environ['LMOD_CMD'] = lmod_abspath - lmod = Lmod() - lmod.testing = True + lmod = Lmod(testing=True) # obtain list of availabe modules, should be non-empty self.assertTrue(lmod.available(), "List of available modules obtained using Lmod is non-empty") From 92e7764c8719bf10cc5450ece75a96e39c11a315 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 2 Oct 2014 13:23:23 +0200 Subject: [PATCH 034/298] fix case where testing is not enabled in modulestool.py tests --- test/framework/modulestool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/framework/modulestool.py b/test/framework/modulestool.py index 82e6cf39d4..80c745fb31 100644 --- a/test/framework/modulestool.py +++ b/test/framework/modulestool.py @@ -113,7 +113,7 @@ def test_module_mismatch(self): """Test whether mismatch detection between modules tool and 'module' function works.""" # redefine 'module' function (deliberate mismatch with used module command in MockModulesTool) os.environ['module'] = "() { eval `/tmp/Modules/$MODULE_VERSION/bin/modulecmd bash $*`\n}" - self.assertErrorRegex(EasyBuildError, ".*pattern .* not found in defined 'module' function", MockModulesTool) + self.assertErrorRegex(EasyBuildError, ".*pattern .* not found in defined 'module' function", MockModulesTool, testing=True) # check whether escaping error by allowing mismatch via build options works build_options = { From c081f928631354ccf9a436c1bbbeae9dabc980b6 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 2 Oct 2014 14:05:09 +0200 Subject: [PATCH 035/298] fix long line --- test/framework/modulestool.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/framework/modulestool.py b/test/framework/modulestool.py index 80c745fb31..5b92f8822a 100644 --- a/test/framework/modulestool.py +++ b/test/framework/modulestool.py @@ -113,7 +113,8 @@ def test_module_mismatch(self): """Test whether mismatch detection between modules tool and 'module' function works.""" # redefine 'module' function (deliberate mismatch with used module command in MockModulesTool) os.environ['module'] = "() { eval `/tmp/Modules/$MODULE_VERSION/bin/modulecmd bash $*`\n}" - self.assertErrorRegex(EasyBuildError, ".*pattern .* not found in defined 'module' function", MockModulesTool, testing=True) + error_regex = ".*pattern .* not found in defined 'module' function" + self.assertErrorRegex(EasyBuildError, error_regex, MockModulesTool, testing=True) # check whether escaping error by allowing mismatch via build options works build_options = { From b0c9a06b98e116108d5f5bd7ecdfedc7f7f47733 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 2 Oct 2014 14:06:01 +0200 Subject: [PATCH 036/298] fix broken test that verifies mismatch on 'module' function --- easybuild/main.py | 2 +- easybuild/tools/modules.py | 4 ++-- easybuild/tools/testing.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/easybuild/main.py b/easybuild/main.py index b250876641..a572ac2c05 100644 --- a/easybuild/main.py +++ b/easybuild/main.py @@ -276,7 +276,7 @@ def main(testing_data=(None, None, None)): }) # obtain list of loaded modules, build options must be initialized first - modlist = session_module_list() + modlist = session_module_list(testing=testing) init_session_state.update({'module_list': modlist}) _log.debug("Initial session state: %s" % init_session_state) diff --git a/easybuild/tools/modules.py b/easybuild/tools/modules.py index c3c2c2b81a..7111fd872b 100644 --- a/easybuild/tools/modules.py +++ b/easybuild/tools/modules.py @@ -973,7 +973,7 @@ def avail_modules_tools(): return class_dict -def modules_tool(mod_paths=None): +def modules_tool(mod_paths=None, testing=False): """ Return interface to modules tool (environment modules (C, Tcl), or Lmod) """ @@ -981,7 +981,7 @@ def modules_tool(mod_paths=None): modules_tool = get_modules_tool() if modules_tool is not None: modules_tool_class = avail_modules_tools().get(modules_tool) - return modules_tool_class(mod_paths=mod_paths) + return modules_tool_class(mod_paths=mod_paths, testing=testing) else: return None diff --git a/easybuild/tools/testing.py b/easybuild/tools/testing.py index b572921f7e..fa579e2107 100644 --- a/easybuild/tools/testing.py +++ b/easybuild/tools/testing.py @@ -157,9 +157,9 @@ def session_state(): } -def session_module_list(): +def session_module_list(testing=False): """Get list of loaded modules ('module list').""" - modtool = modules_tool() + modtool = modules_tool(testing=testing) return modtool.list() From ab1b84d735314e59a11e0d6017e85c3292b53a12 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 3 Oct 2014 10:53:15 +0200 Subject: [PATCH 037/298] add definition of gimpi toolchain --- easybuild/tools/gimpi.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 easybuild/tools/gimpi.py diff --git a/easybuild/tools/gimpi.py b/easybuild/tools/gimpi.py new file mode 100644 index 0000000000..0ac2345266 --- /dev/null +++ b/easybuild/tools/gimpi.py @@ -0,0 +1,38 @@ +## +# Copyright 2012-2014 Ghent University +# +# This file is part of EasyBuild, +# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en), +# with support of Ghent University (http://ugent.be/hpc), +# the Flemish Supercomputer Centre (VSC) (https://vscentrum.be/nl/en), +# the Hercules foundation (http://www.herculesstichting.be/in_English) +# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en). +# +# http://github.com/hpcugent/easybuild +# +# EasyBuild is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation v2. +# +# EasyBuild is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with EasyBuild. If not, see . +## +""" +EasyBuild support for gimpi compiler toolchain (includes GCC and Intel MPI). + +@author: Stijn De Weirdt (Ghent University) +@author: Kenneth Hoste (Ghent University) +""" + +from easybuild.toolchains.compiler.gcc import Gcc +from easybuild.toolchains.mpi.intelmpi import IntelMPI + + +class Gimpi(Gcc, IntelMPI): + """Compiler toolchain with GCC and Intel MPI.""" + NAME = 'gimpi' From 5d213ca479e1b49a643638ed2d50f5e84c1f6395 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 3 Oct 2014 11:06:08 +0200 Subject: [PATCH 038/298] move gimpi toolchain definition to correct location --- easybuild/{tools => toolchains}/gimpi.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename easybuild/{tools => toolchains}/gimpi.py (100%) diff --git a/easybuild/tools/gimpi.py b/easybuild/toolchains/gimpi.py similarity index 100% rename from easybuild/tools/gimpi.py rename to easybuild/toolchains/gimpi.py From 20423602cd5f08fd70dcbfdd21a95a08ccfbc59b Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 3 Oct 2014 14:13:53 +0200 Subject: [PATCH 039/298] don't override COMPILER_MODULE_NAME obtained from ClangGCC in Clang-based toolchains --- easybuild/toolchains/cgmpich.py | 1 - easybuild/toolchains/cgmvapich2.py | 1 - easybuild/toolchains/cgompi.py | 1 - 3 files changed, 3 deletions(-) diff --git a/easybuild/toolchains/cgmpich.py b/easybuild/toolchains/cgmpich.py index a85f500257..af3a4e9dbf 100644 --- a/easybuild/toolchains/cgmpich.py +++ b/easybuild/toolchains/cgmpich.py @@ -38,4 +38,3 @@ class Cgmpich(ClangGcc, Mpich): """Compiler toolchain with Clang, GFortran and MPICH.""" NAME = 'cgmpich' - COMPILER_MODULE_NAME = ['ClangGCC'] diff --git a/easybuild/toolchains/cgmvapich2.py b/easybuild/toolchains/cgmvapich2.py index d3a4b596c2..61a764c40e 100644 --- a/easybuild/toolchains/cgmvapich2.py +++ b/easybuild/toolchains/cgmvapich2.py @@ -38,4 +38,3 @@ class Cgmvapich2(ClangGcc, Mvapich2): """Compiler toolchain with Clang, GFortran and MVAPICH2.""" NAME = 'cgmvapich2' - COMPILER_MODULE_NAME = ['ClangGCC'] diff --git a/easybuild/toolchains/cgompi.py b/easybuild/toolchains/cgompi.py index 9fe8105313..b39c6a0c23 100644 --- a/easybuild/toolchains/cgompi.py +++ b/easybuild/toolchains/cgompi.py @@ -38,4 +38,3 @@ class Cgompi(ClangGcc, OpenMPI): """Compiler toolchain with Clang, GFortran and OpenMPI.""" NAME = 'cgompi' - COMPILER_MODULE_NAME = ['ClangGCC'] From fc91b81821fe0d90b7adfa876a2fbaadc5a1832c Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 3 Oct 2014 15:49:57 +0200 Subject: [PATCH 040/298] fix Clang-based test modules --- test/framework/modules/cgompi/1.1.6 | 8 ++++++-- test/framework/modules/cgoolf/1.1.6 | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/test/framework/modules/cgompi/1.1.6 b/test/framework/modules/cgompi/1.1.6 index 22bd608d51..33367e0a50 100644 --- a/test/framework/modules/cgompi/1.1.6 +++ b/test/framework/modules/cgompi/1.1.6 @@ -13,8 +13,12 @@ set root /user/scratch/gent/vsc400/vsc40023/easybuild_REGTEST/SL6/sandybridge conflict cgompi -if { ![is-loaded ClangGCC/1.1.2] } { - module load ClangGCC/1.1.2 +if { ![is-loaded GCC/4.7.2] } { + module load GCC/4.7.2 +} + +if { ![is-loaded Clang/3.2-GCC-4.7.2] } { + module load Clang/3.2-GCC-4.7.2 } if { ![is-loaded OpenMPI/1.6.4-ClangGCC-1.1.2] } { diff --git a/test/framework/modules/cgoolf/1.1.6 b/test/framework/modules/cgoolf/1.1.6 index 477da80d19..7ce5aa2cc0 100644 --- a/test/framework/modules/cgoolf/1.1.6 +++ b/test/framework/modules/cgoolf/1.1.6 @@ -13,8 +13,12 @@ set root /user/scratch/gent/vsc400/vsc40023/easybuild_REGTEST/SL6/sandybridge conflict cgoolf -if { ![is-loaded ClangGCC/1.1.2] } { - module load ClangGCC/1.1.2 +if { ![is-loaded GCC/4.7.2] } { + module load GCC/4.7.2 +} + +if { ![is-loaded Clang/3.2-GCC-4.7.2] } { + module load Clang/3.2-GCC-4.7.2 } if { ![is-loaded OpenMPI/1.6.4-ClangGCC-1.1.2] } { From 5ceb479a3afacae53999eb718be602d1049cb2c5 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 3 Oct 2014 10:53:15 +0200 Subject: [PATCH 041/298] add definition of gimpi toolchain --- easybuild/tools/gimpi.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 easybuild/tools/gimpi.py diff --git a/easybuild/tools/gimpi.py b/easybuild/tools/gimpi.py new file mode 100644 index 0000000000..0ac2345266 --- /dev/null +++ b/easybuild/tools/gimpi.py @@ -0,0 +1,38 @@ +## +# Copyright 2012-2014 Ghent University +# +# This file is part of EasyBuild, +# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en), +# with support of Ghent University (http://ugent.be/hpc), +# the Flemish Supercomputer Centre (VSC) (https://vscentrum.be/nl/en), +# the Hercules foundation (http://www.herculesstichting.be/in_English) +# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en). +# +# http://github.com/hpcugent/easybuild +# +# EasyBuild is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation v2. +# +# EasyBuild is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with EasyBuild. If not, see . +## +""" +EasyBuild support for gimpi compiler toolchain (includes GCC and Intel MPI). + +@author: Stijn De Weirdt (Ghent University) +@author: Kenneth Hoste (Ghent University) +""" + +from easybuild.toolchains.compiler.gcc import Gcc +from easybuild.toolchains.mpi.intelmpi import IntelMPI + + +class Gimpi(Gcc, IntelMPI): + """Compiler toolchain with GCC and Intel MPI.""" + NAME = 'gimpi' From 7bc6ea3f0a372cd54f1a844c70fbfed682fb57cc Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 3 Oct 2014 11:06:08 +0200 Subject: [PATCH 042/298] move gimpi toolchain definition to correct location --- easybuild/{tools => toolchains}/gimpi.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename easybuild/{tools => toolchains}/gimpi.py (100%) diff --git a/easybuild/tools/gimpi.py b/easybuild/toolchains/gimpi.py similarity index 100% rename from easybuild/tools/gimpi.py rename to easybuild/toolchains/gimpi.py From 0472c7148a0e3560f40485db4ca153578c2f04f6 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 3 Oct 2014 17:52:18 +0200 Subject: [PATCH 043/298] fix blatently wrong code in path_to_top_of_module_tree function --- easybuild/tools/modules.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/easybuild/tools/modules.py b/easybuild/tools/modules.py index 7111fd872b..30893436b9 100644 --- a/easybuild/tools/modules.py +++ b/easybuild/tools/modules.py @@ -675,11 +675,11 @@ def path_to_top_of_module_tree(self, top_paths, mod_name, full_mod_subdir, deps, full_modpath_exts = modpath_exts[dep] if path_matches(full_mod_subdir, full_modpath_exts): # full path to module subdir of dependency is simply path to module file without (short) module name - full_mod_subdir = self.modulefile_path(dep)[:-len(dep)-1] - full_mod_subdirs.append(full_mod_subdir) + dep_full_mod_subdir = self.modulefile_path(dep)[:-len(dep)-1] + full_mod_subdirs.append(dep_full_mod_subdir) mods_to_top.append(dep) - tup = (dep, full_mod_subdir, full_modpath_exts) + tup = (dep, dep_full_mod_subdir, full_modpath_exts) self.log.debug("Found module to top of module tree: %s (subdir: %s, modpath extensions %s)" % tup) if full_modpath_exts: From 192b989b15e590d01e96b1346a084bc1af899541 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 3 Oct 2014 18:20:19 +0200 Subject: [PATCH 044/298] enhance unit test to catch missed bug --- test/framework/easyblock.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/test/framework/easyblock.py b/test/framework/easyblock.py index f5c4401fb3..b6281e965e 100644 --- a/test/framework/easyblock.py +++ b/test/framework/easyblock.py @@ -486,24 +486,32 @@ def test_exclude_path_to_top_of_module_tree(self): self.setup_hierarchical_modules() modtool = modules_tool() - ec = EasyConfig(os.path.join(test_ecs_path, 'imkl-11.1.2.144-iimpi-5.5.3-GCC-4.8.3.eb')) - eb = EasyBlock(ec) - modfile_prefix = os.path.join(self.test_installpath, 'modules', 'all') mkdir(os.path.join(modfile_prefix, 'Compiler', 'GCC', '4.8.3'), parents=True) mkdir(os.path.join(modfile_prefix, 'MPI', 'intel', '2013.5.192', 'impi', '4.1.3.049'), parents=True) - eb.toolchain.prepare() - modpath = eb.make_module_step() - modfile_path = os.path.join(modpath, 'MPI', 'intel', '2013.5.192', 'impi', '4.1.3.049', 'imkl', '11.1.2.144') - modtxt = read_file(modfile_path) - # for imkl on top of iimpi toolchain with HierarchicalMNS, no module load statements should be included at all + impi_modfile_path = os.path.join('Compiler', 'intel', '2013.5.192', 'impi', '4.1.3.049') + imkl_modfile_path = os.path.join('MPI', 'intel', '2013.5.192', 'impi', '4.1.3.049', 'imkl', '11.1.2.144') + + # example: for imkl on top of iimpi toolchain with HierarchicalMNS, no module load statements should be included # not for the toolchain or any of the toolchain components, # since both icc/ifort and impi form the path to the top of the module tree - for imkl_dep in ['icc', 'ifort', 'impi', 'iccifort', 'iimpi']: - tup = (imkl_dep, modfile_path, modtxt) - failmsg = "No 'module load' statement found for '%s' not found in module %s: %s" % tup - self.assertFalse(re.search("module load %s" % imkl_dep, modtxt), failmsg) + tests = [ + ('impi-4.1.3.049-iccifort-2013.5.192-GCC-4.8.3.eb', impi_modfile_path, ['icc', 'ifort', 'iccifort']), + ('imkl-11.1.2.144-iimpi-5.5.3-GCC-4.8.3.eb', imkl_modfile_path, ['icc', 'ifort', 'impi', 'iccifort', 'iimpi']), + ] + for ec_file, modfile_path, excluded_deps in tests: + ec = EasyConfig(os.path.join(test_ecs_path, ec_file)) + eb = EasyBlock(ec) + eb.toolchain.prepare() + modpath = eb.make_module_step() + modfile_path = os.path.join(modpath, modfile_path) + modtxt = read_file(modfile_path) + + for imkl_dep in excluded_deps: + tup = (imkl_dep, modfile_path, modtxt) + failmsg = "No 'module load' statement found for '%s' not found in module %s: %s" % tup + self.assertFalse(re.search("module load %s" % imkl_dep, modtxt), failmsg) os.environ['EASYBUILD_MODULE_NAMING_SCHEME'] = self.orig_module_naming_scheme init_config(build_options=build_options) From 629c090fab13959724a2768685b90b556f1dd674 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 3 Oct 2014 17:52:18 +0200 Subject: [PATCH 045/298] fix blatently wrong code in path_to_top_of_module_tree function --- easybuild/tools/modules.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/easybuild/tools/modules.py b/easybuild/tools/modules.py index 7111fd872b..30893436b9 100644 --- a/easybuild/tools/modules.py +++ b/easybuild/tools/modules.py @@ -675,11 +675,11 @@ def path_to_top_of_module_tree(self, top_paths, mod_name, full_mod_subdir, deps, full_modpath_exts = modpath_exts[dep] if path_matches(full_mod_subdir, full_modpath_exts): # full path to module subdir of dependency is simply path to module file without (short) module name - full_mod_subdir = self.modulefile_path(dep)[:-len(dep)-1] - full_mod_subdirs.append(full_mod_subdir) + dep_full_mod_subdir = self.modulefile_path(dep)[:-len(dep)-1] + full_mod_subdirs.append(dep_full_mod_subdir) mods_to_top.append(dep) - tup = (dep, full_mod_subdir, full_modpath_exts) + tup = (dep, dep_full_mod_subdir, full_modpath_exts) self.log.debug("Found module to top of module tree: %s (subdir: %s, modpath extensions %s)" % tup) if full_modpath_exts: From b1c7a37fcd57ecb3d0e22946d8d5a000110a1faa Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 3 Oct 2014 18:20:19 +0200 Subject: [PATCH 046/298] enhance unit test to catch missed bug --- test/framework/easyblock.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/test/framework/easyblock.py b/test/framework/easyblock.py index f5c4401fb3..b6281e965e 100644 --- a/test/framework/easyblock.py +++ b/test/framework/easyblock.py @@ -486,24 +486,32 @@ def test_exclude_path_to_top_of_module_tree(self): self.setup_hierarchical_modules() modtool = modules_tool() - ec = EasyConfig(os.path.join(test_ecs_path, 'imkl-11.1.2.144-iimpi-5.5.3-GCC-4.8.3.eb')) - eb = EasyBlock(ec) - modfile_prefix = os.path.join(self.test_installpath, 'modules', 'all') mkdir(os.path.join(modfile_prefix, 'Compiler', 'GCC', '4.8.3'), parents=True) mkdir(os.path.join(modfile_prefix, 'MPI', 'intel', '2013.5.192', 'impi', '4.1.3.049'), parents=True) - eb.toolchain.prepare() - modpath = eb.make_module_step() - modfile_path = os.path.join(modpath, 'MPI', 'intel', '2013.5.192', 'impi', '4.1.3.049', 'imkl', '11.1.2.144') - modtxt = read_file(modfile_path) - # for imkl on top of iimpi toolchain with HierarchicalMNS, no module load statements should be included at all + impi_modfile_path = os.path.join('Compiler', 'intel', '2013.5.192', 'impi', '4.1.3.049') + imkl_modfile_path = os.path.join('MPI', 'intel', '2013.5.192', 'impi', '4.1.3.049', 'imkl', '11.1.2.144') + + # example: for imkl on top of iimpi toolchain with HierarchicalMNS, no module load statements should be included # not for the toolchain or any of the toolchain components, # since both icc/ifort and impi form the path to the top of the module tree - for imkl_dep in ['icc', 'ifort', 'impi', 'iccifort', 'iimpi']: - tup = (imkl_dep, modfile_path, modtxt) - failmsg = "No 'module load' statement found for '%s' not found in module %s: %s" % tup - self.assertFalse(re.search("module load %s" % imkl_dep, modtxt), failmsg) + tests = [ + ('impi-4.1.3.049-iccifort-2013.5.192-GCC-4.8.3.eb', impi_modfile_path, ['icc', 'ifort', 'iccifort']), + ('imkl-11.1.2.144-iimpi-5.5.3-GCC-4.8.3.eb', imkl_modfile_path, ['icc', 'ifort', 'impi', 'iccifort', 'iimpi']), + ] + for ec_file, modfile_path, excluded_deps in tests: + ec = EasyConfig(os.path.join(test_ecs_path, ec_file)) + eb = EasyBlock(ec) + eb.toolchain.prepare() + modpath = eb.make_module_step() + modfile_path = os.path.join(modpath, modfile_path) + modtxt = read_file(modfile_path) + + for imkl_dep in excluded_deps: + tup = (imkl_dep, modfile_path, modtxt) + failmsg = "No 'module load' statement found for '%s' not found in module %s: %s" % tup + self.assertFalse(re.search("module load %s" % imkl_dep, modtxt), failmsg) os.environ['EASYBUILD_MODULE_NAMING_SCHEME'] = self.orig_module_naming_scheme init_config(build_options=build_options) From bf1ba6c649e0d9cadafa7becd59e0b5dec62c3a4 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 1 Oct 2014 17:16:04 +0200 Subject: [PATCH 047/298] fix $MODULEPATH extensions for Clang/CUDA, include versionsuffix in module subdir --- .../module_naming_scheme/hierarchical_mns.py | 36 +++++++++++++++---- .../easyconfigs/CUDA-5.5.22-GCC-4.8.2.eb | 29 +++++++++++++++ test/framework/easyconfigs/GCC-4.8.2.eb | 28 +++++++++++++++ .../framework/easyconfigs/ifort-2013.3.163.eb | 17 +++++++++ test/framework/module_generator.py | 4 +++ 5 files changed, 107 insertions(+), 7 deletions(-) create mode 100644 test/framework/easyconfigs/CUDA-5.5.22-GCC-4.8.2.eb create mode 100644 test/framework/easyconfigs/GCC-4.8.2.eb create mode 100644 test/framework/easyconfigs/ifort-2013.3.163.eb diff --git a/easybuild/tools/module_naming_scheme/hierarchical_mns.py b/easybuild/tools/module_naming_scheme/hierarchical_mns.py index c415d61d72..27ba3de274 100644 --- a/easybuild/tools/module_naming_scheme/hierarchical_mns.py +++ b/easybuild/tools/module_naming_scheme/hierarchical_mns.py @@ -30,6 +30,7 @@ """ import os +import re from vsc.utils import fancylogger from easybuild.tools.module_naming_scheme import ModuleNamingScheme @@ -132,17 +133,38 @@ def det_modpath_extensions(self, ec): Examples: Compiler/GCC/4.8.3 (for GCC/4.8.3 module), MPI/GCC/4.8.3/OpenMPI/1.6.5 (for OpenMPI/1.6.5 module) """ modclass = ec['moduleclass'] + tc_comps = det_toolchain_compilers(ec) + tc_comp_info = self.det_toolchain_compilers_name_version(tc_comps) paths = [] - if modclass == MODULECLASS_COMPILER: - if ec['name'] in ['icc', 'ifort']: - compdir = 'intel' + if modclass == MODULECLASS_COMPILER or ec['name'] in ['CUDA']: + # obtain list of compilers based on that extend $MODULEPATH in some way other than / + extend_comps = [] + # exclude GCC for which / is used as $MODULEPATH extension + excluded_comps = ['GCC'] + for comps in COMP_NAME_VERSION_TEMPLATES.keys(): + extend_comps.extend([comp for comp in comps.split(',') if comp not in excluded_comps]) + + comp_name_ver = None + if ec['name'] in extend_comps: + for key in COMP_NAME_VERSION_TEMPLATES: + if re.search(".*%s.*" % ec['name'], key): + comp_name, comp_ver_tmpl = COMP_NAME_VERSION_TEMPLATES[key] + comp_versions = {ec['name']: ec['version'] + ec['versionsuffix']} + if ec['name'] == 'ifort': + # 'icc' key should be provided since it's the only one used in the template + comp_versions.update({'icc': ec['version'] + ec['versionsuffix']}) + if tc_comp_info is not None: + # also provide toolchain version for non-dummy toolchains + comp_versions.update({tc_comp_info[0]: tc_comp_info[1]}) + comp_name_ver = [comp_name, comp_ver_tmpl % comp_versions] + break else: - compdir = ec['name'] - paths.append(os.path.join(COMPILER, compdir, ec['version'])) + comp_name_ver = [ec['name'], ec['version'] + ec['versionsuffix']] + + paths.append(os.path.join(COMPILER, *comp_name_ver)) + elif modclass == MODULECLASS_MPI: - tc_comps = det_toolchain_compilers(ec) - tc_comp_info = self.det_toolchain_compilers_name_version(tc_comps) if tc_comp_info is None: tup = (ec['toolchain'], ec['name'], ec['version']) error_msg = ("No compiler available in toolchain %s used to install MPI library %s v%s, " diff --git a/test/framework/easyconfigs/CUDA-5.5.22-GCC-4.8.2.eb b/test/framework/easyconfigs/CUDA-5.5.22-GCC-4.8.2.eb new file mode 100644 index 0000000000..a8e69945a9 --- /dev/null +++ b/test/framework/easyconfigs/CUDA-5.5.22-GCC-4.8.2.eb @@ -0,0 +1,29 @@ +## +# This file is an EasyBuild reciPY as per https://github.com/hpcugent/easybuild +# +# Copyright:: Copyright 2012-2013 Cyprus Institute / CaSToRC, University of Luxembourg / LCSB, Ghent University +# Authors:: George Tsouloupas , Fotis Georgatos , Kenneth Hoste +# License:: MIT/GPL +# $Id$ +# +# This work implements a part of the HPCBIOS project and is a component of the policy: +# http://hpcbios.readthedocs.org/en/latest/HPCBIOS_2012-99.html +## + +name = 'CUDA' +version = '5.5.22' + +homepage = 'https://developer.nvidia.com/cuda-toolkit' +description = """CUDA (formerly Compute Unified Device Architecture) is a parallel + computing platform and programming model created by NVIDIA and implemented by the + graphics processing units (GPUs) that they produce. CUDA gives developers access + to the virtual instruction set and memory of the parallel computational elements in CUDA GPUs.""" + +toolchain = {'name': 'GCC', 'version': '4.8.2'} + +# eg. http://developer.download.nvidia.com/compute/cuda/5_5/rel/installers/cuda_5.5.22_linux_64.run +source_urls = ['http://developer.download.nvidia.com/compute/cuda/5_5/rel/installers/'] + +sources = ['%(namelower)s_%(version)s_linux_64.run'] + +moduleclass = 'system' diff --git a/test/framework/easyconfigs/GCC-4.8.2.eb b/test/framework/easyconfigs/GCC-4.8.2.eb new file mode 100644 index 0000000000..cef0802601 --- /dev/null +++ b/test/framework/easyconfigs/GCC-4.8.2.eb @@ -0,0 +1,28 @@ +name = "GCC" +version = '4.8.2' + +homepage = 'http://gcc.gnu.org/' +description = """The GNU Compiler Collection includes front ends for C, C++, Objective-C, Fortran, Java, and Ada, + as well as libraries for these languages (libstdc++, libgcj,...).""" + +toolchain = {'name': 'dummy', 'version': 'dummy'} + +source_urls = [ + 'http://ftpmirror.gnu.org/%(namelower)s/%(namelower)s-%(version)s', # GCC auto-resolving HTTP mirror + 'http://ftpmirror.gnu.org/gmp', # idem for GMP + 'http://ftpmirror.gnu.org/mpfr', # idem for MPFR + 'http://www.multiprecision.org/mpc/download', # MPC official +] +sources = [ + SOURCELOWER_TAR_GZ, + 'gmp-5.1.3.tar.bz2', + 'mpfr-3.1.2.tar.gz', + 'mpc-1.0.1.tar.gz', +] + +languages = ['c', 'c++', 'fortran', 'lto'] + +# building GCC sometimes fails if make parallelism is too high, so let's limit it +maxparallel = 4 + +moduleclass = 'compiler' diff --git a/test/framework/easyconfigs/ifort-2013.3.163.eb b/test/framework/easyconfigs/ifort-2013.3.163.eb new file mode 100644 index 0000000000..4efd890d23 --- /dev/null +++ b/test/framework/easyconfigs/ifort-2013.3.163.eb @@ -0,0 +1,17 @@ +name = 'ifort' +version = '2013.3.163' + +homepage = 'http://software.intel.com/en-us/intel-compilers/' +description = "Fortran compiler from Intel" + +toolchain = {'name': 'dummy', 'version': 'dummy'} + +sources = ['l_fcompxe_%s.tgz' % version] + +dontcreateinstalldir = 'True' + +# license file +import os +license_file = os.path.join(os.getenv('HOME'), "licenses", "intel", "license.lic") + +moduleclass = 'compiler' diff --git a/test/framework/module_generator.py b/test/framework/module_generator.py index 9d0c46188b..cd8cddb439 100644 --- a/test/framework/module_generator.py +++ b/test/framework/module_generator.py @@ -393,11 +393,15 @@ def test_ec(ecfile, short_modname, mod_subdir, modpath_exts, init_modpaths): init_config(build_options=build_options) # format: easyconfig_file: (short_mod_name, mod_subdir, modpath_extensions) + iccver = '2013.5.192-GCC-4.8.3' test_ecs = { 'GCC-4.7.2.eb': ('GCC/4.7.2', 'Core', ['Compiler/GCC/4.7.2'], ['Core']), 'OpenMPI-1.6.4-GCC-4.7.2.eb': ('OpenMPI/1.6.4', 'Compiler/GCC/4.7.2', ['MPI/GCC/4.7.2/OpenMPI/1.6.4'], ['Core']), 'gzip-1.5-goolf-1.4.10.eb': ('gzip/1.5', 'MPI/GCC/4.7.2/OpenMPI/1.6.4', [], ['Core']), 'goolf-1.4.10.eb': ('goolf/1.4.10', 'Core', [], ['Core']), + 'icc-2013.5.192-GCC-4.8.3.eb': ('icc/%s' % iccver, 'Core', ['Compiler/intel/%s' % iccver], ['Core']), + 'ifort-2013.3.163.eb': ('ifort/2013.3.163', 'Core', ['Compiler/intel/2013.3.163'], ['Core']), + 'CUDA-5.5.22-GCC-4.8.2.eb': ('CUDA/5.5.22', 'Compiler/GCC/4.8.2', ['Compiler/GCC-CUDA/4.8.2-5.5.22'], ['Core']), } for ecfile, mns_vals in test_ecs.items(): test_ec(ecfile, *mns_vals) From cab3ccd7d2c44be64c1d82b66150c2b188ab7f16 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 2 Oct 2014 12:17:04 +0200 Subject: [PATCH 048/298] fix tiny remark --- easybuild/tools/module_naming_scheme/hierarchical_mns.py | 1 + 1 file changed, 1 insertion(+) diff --git a/easybuild/tools/module_naming_scheme/hierarchical_mns.py b/easybuild/tools/module_naming_scheme/hierarchical_mns.py index 27ba3de274..184c596c43 100644 --- a/easybuild/tools/module_naming_scheme/hierarchical_mns.py +++ b/easybuild/tools/module_naming_scheme/hierarchical_mns.py @@ -157,6 +157,7 @@ def det_modpath_extensions(self, ec): if tc_comp_info is not None: # also provide toolchain version for non-dummy toolchains comp_versions.update({tc_comp_info[0]: tc_comp_info[1]}) + comp_name_ver = [comp_name, comp_ver_tmpl % comp_versions] break else: From 671108be46c99096846aa5552d56217a88029822 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 2 Oct 2014 12:20:57 +0200 Subject: [PATCH 049/298] fix broken test --- test/framework/scripts.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/framework/scripts.py b/test/framework/scripts.py index 9712938abf..1776f8cdc1 100644 --- a/test/framework/scripts.py +++ b/test/framework/scripts.py @@ -65,9 +65,10 @@ def test_generate_software_list(self): out, ec = run_cmd(cmd, simple=False) # make sure output is kind of what we expect it to be - regex = r"Supported Packages \(17 " + regex = r"Supported Packages \(18 " self.assertTrue(re.search(regex, out), "Pattern '%s' found in output: %s" % (regex, out)) per_letter = { + 'C': '1', # CUDA 'F': '1', # FFTW 'G': '4', # GCC, gompi, goolf, gzip 'H': '1', # hwloc From bd0a01604d0401ae5540a89a9485fbd7b168b771 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 2 Oct 2014 14:08:09 +0200 Subject: [PATCH 050/298] fix remark --- easybuild/tools/module_naming_scheme/hierarchical_mns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/tools/module_naming_scheme/hierarchical_mns.py b/easybuild/tools/module_naming_scheme/hierarchical_mns.py index 184c596c43..9eee1439cc 100644 --- a/easybuild/tools/module_naming_scheme/hierarchical_mns.py +++ b/easybuild/tools/module_naming_scheme/hierarchical_mns.py @@ -148,7 +148,7 @@ def det_modpath_extensions(self, ec): comp_name_ver = None if ec['name'] in extend_comps: for key in COMP_NAME_VERSION_TEMPLATES: - if re.search(".*%s.*" % ec['name'], key): + if ec['name'] in key.split(','): comp_name, comp_ver_tmpl = COMP_NAME_VERSION_TEMPLATES[key] comp_versions = {ec['name']: ec['version'] + ec['versionsuffix']} if ec['name'] == 'ifort': From 6fd20c792cc1b75f84ca5bbfea5c62cb71bf35b5 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 3 Oct 2014 14:13:53 +0200 Subject: [PATCH 051/298] don't override COMPILER_MODULE_NAME obtained from ClangGCC in Clang-based toolchains --- easybuild/toolchains/cgmpich.py | 1 - easybuild/toolchains/cgmvapich2.py | 1 - easybuild/toolchains/cgompi.py | 1 - 3 files changed, 3 deletions(-) diff --git a/easybuild/toolchains/cgmpich.py b/easybuild/toolchains/cgmpich.py index a85f500257..af3a4e9dbf 100644 --- a/easybuild/toolchains/cgmpich.py +++ b/easybuild/toolchains/cgmpich.py @@ -38,4 +38,3 @@ class Cgmpich(ClangGcc, Mpich): """Compiler toolchain with Clang, GFortran and MPICH.""" NAME = 'cgmpich' - COMPILER_MODULE_NAME = ['ClangGCC'] diff --git a/easybuild/toolchains/cgmvapich2.py b/easybuild/toolchains/cgmvapich2.py index d3a4b596c2..61a764c40e 100644 --- a/easybuild/toolchains/cgmvapich2.py +++ b/easybuild/toolchains/cgmvapich2.py @@ -38,4 +38,3 @@ class Cgmvapich2(ClangGcc, Mvapich2): """Compiler toolchain with Clang, GFortran and MVAPICH2.""" NAME = 'cgmvapich2' - COMPILER_MODULE_NAME = ['ClangGCC'] diff --git a/easybuild/toolchains/cgompi.py b/easybuild/toolchains/cgompi.py index 9fe8105313..b39c6a0c23 100644 --- a/easybuild/toolchains/cgompi.py +++ b/easybuild/toolchains/cgompi.py @@ -38,4 +38,3 @@ class Cgompi(ClangGcc, OpenMPI): """Compiler toolchain with Clang, GFortran and OpenMPI.""" NAME = 'cgompi' - COMPILER_MODULE_NAME = ['ClangGCC'] From 8899c3c7af6e20637fdc191833291d1820db9075 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 3 Oct 2014 15:49:57 +0200 Subject: [PATCH 052/298] fix Clang-based test modules --- test/framework/modules/cgompi/1.1.6 | 8 ++++++-- test/framework/modules/cgoolf/1.1.6 | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/test/framework/modules/cgompi/1.1.6 b/test/framework/modules/cgompi/1.1.6 index 22bd608d51..33367e0a50 100644 --- a/test/framework/modules/cgompi/1.1.6 +++ b/test/framework/modules/cgompi/1.1.6 @@ -13,8 +13,12 @@ set root /user/scratch/gent/vsc400/vsc40023/easybuild_REGTEST/SL6/sandybridge conflict cgompi -if { ![is-loaded ClangGCC/1.1.2] } { - module load ClangGCC/1.1.2 +if { ![is-loaded GCC/4.7.2] } { + module load GCC/4.7.2 +} + +if { ![is-loaded Clang/3.2-GCC-4.7.2] } { + module load Clang/3.2-GCC-4.7.2 } if { ![is-loaded OpenMPI/1.6.4-ClangGCC-1.1.2] } { diff --git a/test/framework/modules/cgoolf/1.1.6 b/test/framework/modules/cgoolf/1.1.6 index 477da80d19..7ce5aa2cc0 100644 --- a/test/framework/modules/cgoolf/1.1.6 +++ b/test/framework/modules/cgoolf/1.1.6 @@ -13,8 +13,12 @@ set root /user/scratch/gent/vsc400/vsc40023/easybuild_REGTEST/SL6/sandybridge conflict cgoolf -if { ![is-loaded ClangGCC/1.1.2] } { - module load ClangGCC/1.1.2 +if { ![is-loaded GCC/4.7.2] } { + module load GCC/4.7.2 +} + +if { ![is-loaded Clang/3.2-GCC-4.7.2] } { + module load Clang/3.2-GCC-4.7.2 } if { ![is-loaded OpenMPI/1.6.4-ClangGCC-1.1.2] } { From fc5f7758d522f04978dae872fcdc0d8afc39446f Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Mon, 6 Oct 2014 11:57:32 +0200 Subject: [PATCH 053/298] bump version to v1.15.2dev --- easybuild/tools/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/tools/version.py b/easybuild/tools/version.py index dcdeea01fd..e2b1ab5964 100644 --- a/easybuild/tools/version.py +++ b/easybuild/tools/version.py @@ -37,7 +37,7 @@ # note: release candidates should be versioned as a pre-release, e.g. "1.1rc1" # 1.1-rc1 would indicate a post-release, i.e., and update of 1.1, so beware! -VERSION = LooseVersion("1.15.1") +VERSION = LooseVersion("1.15.2dev") UNKNOWN = "UNKNOWN" def get_git_revision(): From 6d9a6ef628dda83e38fdf1758183c4fe65e7692d Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Mon, 6 Oct 2014 17:32:52 +0200 Subject: [PATCH 054/298] also take verionsuffix into account when determining toolchain info --- easybuild/tools/module_naming_scheme/hierarchical_mns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/tools/module_naming_scheme/hierarchical_mns.py b/easybuild/tools/module_naming_scheme/hierarchical_mns.py index 9eee1439cc..666dfa1ad0 100644 --- a/easybuild/tools/module_naming_scheme/hierarchical_mns.py +++ b/easybuild/tools/module_naming_scheme/hierarchical_mns.py @@ -88,7 +88,7 @@ def det_toolchain_compilers_name_version(self, tc_comps): elif len(tc_comps) == 1: res = (tc_comps[0]['name'], tc_comps[0]['version']) else: - comp_versions = dict([(comp['name'], comp['version']) for comp in tc_comps]) + comp_versions = dict([(comp['name'], comp['version'] + comp['versionsuffix']) for comp in tc_comps]) comp_names = comp_versions.keys() key = ','.join(sorted(comp_names)) if key in COMP_NAME_VERSION_TEMPLATES: From 97bf3f7712451df01eaaa51a23423df79dfdcffe Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Mon, 6 Oct 2014 17:45:34 +0200 Subject: [PATCH 055/298] enhance unit tests to check on use of versionsuffix in module subdirs --- test/framework/module_generator.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/framework/module_generator.py b/test/framework/module_generator.py index cd8cddb439..c67b629db2 100644 --- a/test/framework/module_generator.py +++ b/test/framework/module_generator.py @@ -394,6 +394,8 @@ def test_ec(ecfile, short_modname, mod_subdir, modpath_exts, init_modpaths): # format: easyconfig_file: (short_mod_name, mod_subdir, modpath_extensions) iccver = '2013.5.192-GCC-4.8.3' + impi_ec = 'impi-4.1.3.049-iccifort-2013.5.192-GCC-4.8.3.eb' + imkl_ec = 'imkl-11.1.2.144-iimpi-5.5.3-GCC-4.8.3.eb' test_ecs = { 'GCC-4.7.2.eb': ('GCC/4.7.2', 'Core', ['Compiler/GCC/4.7.2'], ['Core']), 'OpenMPI-1.6.4-GCC-4.7.2.eb': ('OpenMPI/1.6.4', 'Compiler/GCC/4.7.2', ['MPI/GCC/4.7.2/OpenMPI/1.6.4'], ['Core']), @@ -402,6 +404,8 @@ def test_ec(ecfile, short_modname, mod_subdir, modpath_exts, init_modpaths): 'icc-2013.5.192-GCC-4.8.3.eb': ('icc/%s' % iccver, 'Core', ['Compiler/intel/%s' % iccver], ['Core']), 'ifort-2013.3.163.eb': ('ifort/2013.3.163', 'Core', ['Compiler/intel/2013.3.163'], ['Core']), 'CUDA-5.5.22-GCC-4.8.2.eb': ('CUDA/5.5.22', 'Compiler/GCC/4.8.2', ['Compiler/GCC-CUDA/4.8.2-5.5.22'], ['Core']), + impi_ec: ('impi/4.1.3.049', 'Compiler/intel/%s' % iccver, ['MPI/intel/%s/impi/4.1.3.049' % iccver], ['Core']), + imkl_ec: ('imkl/11.1.2.144', 'MPI/intel/%s/impi/4.1.3.049' % iccver, [], ['Core']), } for ecfile, mns_vals in test_ecs.items(): test_ec(ecfile, *mns_vals) From 79a25323ad9c34e3cd4f6f24d1445fd4bbc5ade1 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Mon, 6 Oct 2014 17:32:52 +0200 Subject: [PATCH 056/298] also take verionsuffix into account when determining toolchain info --- easybuild/tools/module_naming_scheme/hierarchical_mns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/tools/module_naming_scheme/hierarchical_mns.py b/easybuild/tools/module_naming_scheme/hierarchical_mns.py index 9eee1439cc..666dfa1ad0 100644 --- a/easybuild/tools/module_naming_scheme/hierarchical_mns.py +++ b/easybuild/tools/module_naming_scheme/hierarchical_mns.py @@ -88,7 +88,7 @@ def det_toolchain_compilers_name_version(self, tc_comps): elif len(tc_comps) == 1: res = (tc_comps[0]['name'], tc_comps[0]['version']) else: - comp_versions = dict([(comp['name'], comp['version']) for comp in tc_comps]) + comp_versions = dict([(comp['name'], comp['version'] + comp['versionsuffix']) for comp in tc_comps]) comp_names = comp_versions.keys() key = ','.join(sorted(comp_names)) if key in COMP_NAME_VERSION_TEMPLATES: From 4db3050e2732fed13f750f2d8632a37844a13de2 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Mon, 6 Oct 2014 17:45:34 +0200 Subject: [PATCH 057/298] enhance unit tests to check on use of versionsuffix in module subdirs --- test/framework/module_generator.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/framework/module_generator.py b/test/framework/module_generator.py index cd8cddb439..c67b629db2 100644 --- a/test/framework/module_generator.py +++ b/test/framework/module_generator.py @@ -394,6 +394,8 @@ def test_ec(ecfile, short_modname, mod_subdir, modpath_exts, init_modpaths): # format: easyconfig_file: (short_mod_name, mod_subdir, modpath_extensions) iccver = '2013.5.192-GCC-4.8.3' + impi_ec = 'impi-4.1.3.049-iccifort-2013.5.192-GCC-4.8.3.eb' + imkl_ec = 'imkl-11.1.2.144-iimpi-5.5.3-GCC-4.8.3.eb' test_ecs = { 'GCC-4.7.2.eb': ('GCC/4.7.2', 'Core', ['Compiler/GCC/4.7.2'], ['Core']), 'OpenMPI-1.6.4-GCC-4.7.2.eb': ('OpenMPI/1.6.4', 'Compiler/GCC/4.7.2', ['MPI/GCC/4.7.2/OpenMPI/1.6.4'], ['Core']), @@ -402,6 +404,8 @@ def test_ec(ecfile, short_modname, mod_subdir, modpath_exts, init_modpaths): 'icc-2013.5.192-GCC-4.8.3.eb': ('icc/%s' % iccver, 'Core', ['Compiler/intel/%s' % iccver], ['Core']), 'ifort-2013.3.163.eb': ('ifort/2013.3.163', 'Core', ['Compiler/intel/2013.3.163'], ['Core']), 'CUDA-5.5.22-GCC-4.8.2.eb': ('CUDA/5.5.22', 'Compiler/GCC/4.8.2', ['Compiler/GCC-CUDA/4.8.2-5.5.22'], ['Core']), + impi_ec: ('impi/4.1.3.049', 'Compiler/intel/%s' % iccver, ['MPI/intel/%s/impi/4.1.3.049' % iccver], ['Core']), + imkl_ec: ('imkl/11.1.2.144', 'MPI/intel/%s/impi/4.1.3.049' % iccver, [], ['Core']), } for ecfile, mns_vals in test_ecs.items(): test_ec(ecfile, *mns_vals) From 94c0fbeb1709485f1df85e324991089eda64509d Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Mon, 6 Oct 2014 18:40:22 +0200 Subject: [PATCH 058/298] correct other tests w.r.t. including versionsuffix in module subdir --- test/framework/easyblock.py | 6 +++--- .../{2013.5.192 => 2013.5.192-GCC-4.8.3}/impi/4.1.3.049 | 0 test/framework/modules/Core/icc/2013.5.192-GCC-4.8.3 | 2 +- test/framework/modules/Core/ifort/2013.5.192-GCC-4.8.3 | 2 +- test/framework/utilities.py | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) rename test/framework/modules/Compiler/intel/{2013.5.192 => 2013.5.192-GCC-4.8.3}/impi/4.1.3.049 (100%) diff --git a/test/framework/easyblock.py b/test/framework/easyblock.py index b6281e965e..ee959f59b6 100644 --- a/test/framework/easyblock.py +++ b/test/framework/easyblock.py @@ -488,10 +488,10 @@ def test_exclude_path_to_top_of_module_tree(self): modfile_prefix = os.path.join(self.test_installpath, 'modules', 'all') mkdir(os.path.join(modfile_prefix, 'Compiler', 'GCC', '4.8.3'), parents=True) - mkdir(os.path.join(modfile_prefix, 'MPI', 'intel', '2013.5.192', 'impi', '4.1.3.049'), parents=True) + mkdir(os.path.join(modfile_prefix, 'MPI', 'intel', '2013.5.192-GCC-4.8.3', 'impi', '4.1.3.049'), parents=True) - impi_modfile_path = os.path.join('Compiler', 'intel', '2013.5.192', 'impi', '4.1.3.049') - imkl_modfile_path = os.path.join('MPI', 'intel', '2013.5.192', 'impi', '4.1.3.049', 'imkl', '11.1.2.144') + impi_modfile_path = os.path.join('Compiler', 'intel', '2013.5.192-GCC-4.8.3', 'impi', '4.1.3.049') + imkl_modfile_path = os.path.join('MPI', 'intel', '2013.5.192-GCC-4.8.3', 'impi', '4.1.3.049', 'imkl', '11.1.2.144') # example: for imkl on top of iimpi toolchain with HierarchicalMNS, no module load statements should be included # not for the toolchain or any of the toolchain components, diff --git a/test/framework/modules/Compiler/intel/2013.5.192/impi/4.1.3.049 b/test/framework/modules/Compiler/intel/2013.5.192-GCC-4.8.3/impi/4.1.3.049 similarity index 100% rename from test/framework/modules/Compiler/intel/2013.5.192/impi/4.1.3.049 rename to test/framework/modules/Compiler/intel/2013.5.192-GCC-4.8.3/impi/4.1.3.049 diff --git a/test/framework/modules/Core/icc/2013.5.192-GCC-4.8.3 b/test/framework/modules/Core/icc/2013.5.192-GCC-4.8.3 index d01107fd85..4428aa9709 100644 --- a/test/framework/modules/Core/icc/2013.5.192-GCC-4.8.3 +++ b/test/framework/modules/Core/icc/2013.5.192-GCC-4.8.3 @@ -10,7 +10,7 @@ module-whatis {Description: C and C++ compiler from Intel - Homepage: http://sof set root /tmp/software/Core/icc/2013.5.192-GCC-4.8.3 conflict icc -module use /tmp/modules/all/Compiler/intel/2013.5.192 +module use /tmp/modules/all/Compiler/intel/2013.5.192-GCC-4.8.3 if { ![is-loaded GCC/4.8.3] } { module load GCC/4.8.3 diff --git a/test/framework/modules/Core/ifort/2013.5.192-GCC-4.8.3 b/test/framework/modules/Core/ifort/2013.5.192-GCC-4.8.3 index 02c64f8bf8..750f0a4df9 100644 --- a/test/framework/modules/Core/ifort/2013.5.192-GCC-4.8.3 +++ b/test/framework/modules/Core/ifort/2013.5.192-GCC-4.8.3 @@ -10,7 +10,7 @@ module-whatis {Description: Fortran compiler from Intel - Homepage: http://softw set root /tmp/software/Core/ifort/2013.5.192-GCC-4.8.3 conflict ifort -module use /tmp/modules/all/Compiler/intel/2013.5.192 +module use /tmp/modules/all/Compiler/intel/2013.5.192-GCC-4.8.3 if { ![is-loaded GCC/4.8.3] } { module load GCC/4.8.3 diff --git a/test/framework/utilities.py b/test/framework/utilities.py index e4237bc05c..83621c75ea 100644 --- a/test/framework/utilities.py +++ b/test/framework/utilities.py @@ -212,7 +212,7 @@ def setup_hierarchical_modules(self): os.path.join(mod_prefix, 'Core', 'icc', '2013.5.192-GCC-4.8.3'), os.path.join(mod_prefix, 'Core', 'ifort', '2013.5.192-GCC-4.8.3'), os.path.join(mod_prefix, 'Compiler', 'GCC', '4.7.2', 'OpenMPI', '1.6.4'), - os.path.join(mod_prefix, 'Compiler', 'intel', '2013.5.192', 'impi', '4.1.3.049'), + os.path.join(mod_prefix, 'Compiler', 'intel', '2013.5.192-GCC-4.8.3', 'impi', '4.1.3.049'), os.path.join(mpi_pref, 'FFTW', '3.3.3'), os.path.join(mpi_pref, 'OpenBLAS', '0.2.6-LAPACK-3.4.2'), os.path.join(mpi_pref, 'ScaLAPACK', '2.0.2-OpenBLAS-0.2.6-LAPACK-3.4.2'), From 19057ef1a151bbc9b11cdc4eb93783afe27cca35 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Mon, 6 Oct 2014 18:40:22 +0200 Subject: [PATCH 059/298] correct other tests w.r.t. including versionsuffix in module subdir --- test/framework/easyblock.py | 6 +++--- .../{2013.5.192 => 2013.5.192-GCC-4.8.3}/impi/4.1.3.049 | 0 test/framework/modules/Core/icc/2013.5.192-GCC-4.8.3 | 2 +- test/framework/modules/Core/ifort/2013.5.192-GCC-4.8.3 | 2 +- test/framework/utilities.py | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) rename test/framework/modules/Compiler/intel/{2013.5.192 => 2013.5.192-GCC-4.8.3}/impi/4.1.3.049 (100%) diff --git a/test/framework/easyblock.py b/test/framework/easyblock.py index b6281e965e..ee959f59b6 100644 --- a/test/framework/easyblock.py +++ b/test/framework/easyblock.py @@ -488,10 +488,10 @@ def test_exclude_path_to_top_of_module_tree(self): modfile_prefix = os.path.join(self.test_installpath, 'modules', 'all') mkdir(os.path.join(modfile_prefix, 'Compiler', 'GCC', '4.8.3'), parents=True) - mkdir(os.path.join(modfile_prefix, 'MPI', 'intel', '2013.5.192', 'impi', '4.1.3.049'), parents=True) + mkdir(os.path.join(modfile_prefix, 'MPI', 'intel', '2013.5.192-GCC-4.8.3', 'impi', '4.1.3.049'), parents=True) - impi_modfile_path = os.path.join('Compiler', 'intel', '2013.5.192', 'impi', '4.1.3.049') - imkl_modfile_path = os.path.join('MPI', 'intel', '2013.5.192', 'impi', '4.1.3.049', 'imkl', '11.1.2.144') + impi_modfile_path = os.path.join('Compiler', 'intel', '2013.5.192-GCC-4.8.3', 'impi', '4.1.3.049') + imkl_modfile_path = os.path.join('MPI', 'intel', '2013.5.192-GCC-4.8.3', 'impi', '4.1.3.049', 'imkl', '11.1.2.144') # example: for imkl on top of iimpi toolchain with HierarchicalMNS, no module load statements should be included # not for the toolchain or any of the toolchain components, diff --git a/test/framework/modules/Compiler/intel/2013.5.192/impi/4.1.3.049 b/test/framework/modules/Compiler/intel/2013.5.192-GCC-4.8.3/impi/4.1.3.049 similarity index 100% rename from test/framework/modules/Compiler/intel/2013.5.192/impi/4.1.3.049 rename to test/framework/modules/Compiler/intel/2013.5.192-GCC-4.8.3/impi/4.1.3.049 diff --git a/test/framework/modules/Core/icc/2013.5.192-GCC-4.8.3 b/test/framework/modules/Core/icc/2013.5.192-GCC-4.8.3 index d01107fd85..4428aa9709 100644 --- a/test/framework/modules/Core/icc/2013.5.192-GCC-4.8.3 +++ b/test/framework/modules/Core/icc/2013.5.192-GCC-4.8.3 @@ -10,7 +10,7 @@ module-whatis {Description: C and C++ compiler from Intel - Homepage: http://sof set root /tmp/software/Core/icc/2013.5.192-GCC-4.8.3 conflict icc -module use /tmp/modules/all/Compiler/intel/2013.5.192 +module use /tmp/modules/all/Compiler/intel/2013.5.192-GCC-4.8.3 if { ![is-loaded GCC/4.8.3] } { module load GCC/4.8.3 diff --git a/test/framework/modules/Core/ifort/2013.5.192-GCC-4.8.3 b/test/framework/modules/Core/ifort/2013.5.192-GCC-4.8.3 index 02c64f8bf8..750f0a4df9 100644 --- a/test/framework/modules/Core/ifort/2013.5.192-GCC-4.8.3 +++ b/test/framework/modules/Core/ifort/2013.5.192-GCC-4.8.3 @@ -10,7 +10,7 @@ module-whatis {Description: Fortran compiler from Intel - Homepage: http://softw set root /tmp/software/Core/ifort/2013.5.192-GCC-4.8.3 conflict ifort -module use /tmp/modules/all/Compiler/intel/2013.5.192 +module use /tmp/modules/all/Compiler/intel/2013.5.192-GCC-4.8.3 if { ![is-loaded GCC/4.8.3] } { module load GCC/4.8.3 diff --git a/test/framework/utilities.py b/test/framework/utilities.py index e4237bc05c..83621c75ea 100644 --- a/test/framework/utilities.py +++ b/test/framework/utilities.py @@ -212,7 +212,7 @@ def setup_hierarchical_modules(self): os.path.join(mod_prefix, 'Core', 'icc', '2013.5.192-GCC-4.8.3'), os.path.join(mod_prefix, 'Core', 'ifort', '2013.5.192-GCC-4.8.3'), os.path.join(mod_prefix, 'Compiler', 'GCC', '4.7.2', 'OpenMPI', '1.6.4'), - os.path.join(mod_prefix, 'Compiler', 'intel', '2013.5.192', 'impi', '4.1.3.049'), + os.path.join(mod_prefix, 'Compiler', 'intel', '2013.5.192-GCC-4.8.3', 'impi', '4.1.3.049'), os.path.join(mpi_pref, 'FFTW', '3.3.3'), os.path.join(mpi_pref, 'OpenBLAS', '0.2.6-LAPACK-3.4.2'), os.path.join(mpi_pref, 'ScaLAPACK', '2.0.2-OpenBLAS-0.2.6-LAPACK-3.4.2'), From e999c837eb493f178c148a2803deab1764f86041 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 7 Oct 2014 10:30:21 +0200 Subject: [PATCH 060/298] bump version to v1.15.2, update release notes --- RELEASE_NOTES | 12 ++++++++++++ easybuild/tools/version.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/RELEASE_NOTES b/RELEASE_NOTES index 7c587db954..fe1835fc19 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -1,6 +1,18 @@ This file contains a description of the major changes to the easybuild-framework EasyBuild package. For more detailed information, please see the git log. +v1.15.2 (October 7th 2014) +-------------------------- + +bugfix release +- fix $MODULEPATH extensions for Clang/CUDA, to make goolfc/cgoolf compatible with HierarchicalMNS (#1050) +- include versionsuffix in module subdirectory with HierarchicalMNS (#1050, #1055) +- fix unit tests which were broken with bash patched for ShellShock bug (#1051) +- add definition of gimpi toolchain, required to make gimkl toolchain compatible with HierarchicalMNS (#1052) +- don't override COMPILER_MODULE_NAME obtained from ClangGCC in Clang-based toolchains (#1053) +- fix wrong code in path_to_top_of_module_tree function (#1054) + - because of this, load statements for compilers were potentially included in higher-level modules under HierarchicalMNS + v1.15.1 (September 23rd 2014) ----------------------------- diff --git a/easybuild/tools/version.py b/easybuild/tools/version.py index e2b1ab5964..07bd0969ce 100644 --- a/easybuild/tools/version.py +++ b/easybuild/tools/version.py @@ -37,7 +37,7 @@ # note: release candidates should be versioned as a pre-release, e.g. "1.1rc1" # 1.1-rc1 would indicate a post-release, i.e., and update of 1.1, so beware! -VERSION = LooseVersion("1.15.2dev") +VERSION = LooseVersion("1.15.2") UNKNOWN = "UNKNOWN" def get_git_revision(): From cfd4830433a0b657334985eab7380a8e1e9b74a1 Mon Sep 17 00:00:00 2001 From: ocaisa Date: Fri, 10 Oct 2014 14:06:09 +0200 Subject: [PATCH 061/298] Added support for versionprefix to HMNS Hopefully, this should cover the support of versionprefix in HMNS --- .../module_naming_scheme/hierarchical_mns.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/easybuild/tools/module_naming_scheme/hierarchical_mns.py b/easybuild/tools/module_naming_scheme/hierarchical_mns.py index 666dfa1ad0..7c7167ecfc 100644 --- a/easybuild/tools/module_naming_scheme/hierarchical_mns.py +++ b/easybuild/tools/module_naming_scheme/hierarchical_mns.py @@ -55,7 +55,7 @@ class HierarchicalMNS(ModuleNamingScheme): """Class implementing an example hierarchical module naming scheme.""" - REQUIRED_KEYS = ['name', 'version', 'versionsuffix', 'toolchain', 'moduleclass'] + REQUIRED_KEYS = ['name', 'versionprefix', 'version', 'versionsuffix', 'toolchain', 'moduleclass'] def requires_toolchain_details(self): """ @@ -76,7 +76,7 @@ def det_short_module_name(self, ec): Determine short module name, i.e. the name under which modules will be exposed to users. Examples: GCC/4.8.3, OpenMPI/1.6.5, OpenBLAS/0.2.9, HPL/2.1, Python/2.7.5 """ - return os.path.join(ec['name'], ec['version'] + ec['versionsuffix']) + return os.path.join(ec['name'], ec['versionprefix'] + ec['version'] + ec['versionsuffix']) def det_toolchain_compilers_name_version(self, tc_comps): """ @@ -88,7 +88,7 @@ def det_toolchain_compilers_name_version(self, tc_comps): elif len(tc_comps) == 1: res = (tc_comps[0]['name'], tc_comps[0]['version']) else: - comp_versions = dict([(comp['name'], comp['version'] + comp['versionsuffix']) for comp in tc_comps]) + comp_versions = dict([(comp['name'], comp['versionsuffix'] + comp['version'] + comp['versionsuffix']) for comp in tc_comps]) comp_names = comp_versions.keys() key = ','.join(sorted(comp_names)) if key in COMP_NAME_VERSION_TEMPLATES: @@ -122,7 +122,7 @@ def det_module_subdir(self, ec): subdir = os.path.join(COMPILER, tc_comp_name, tc_comp_ver) else: # compiler-MPI toolchain => MPI//// namespace - tc_mpi_fullver = tc_mpi['version'] + tc_mpi['versionsuffix'] + tc_mpi_fullver = tc_mpi['versionprefix'] + tc_mpi['version'] + tc_mpi['versionsuffix'] subdir = os.path.join(MPI, tc_comp_name, tc_comp_ver, tc_mpi['name'], tc_mpi_fullver) return subdir @@ -150,10 +150,10 @@ def det_modpath_extensions(self, ec): for key in COMP_NAME_VERSION_TEMPLATES: if ec['name'] in key.split(','): comp_name, comp_ver_tmpl = COMP_NAME_VERSION_TEMPLATES[key] - comp_versions = {ec['name']: ec['version'] + ec['versionsuffix']} + comp_versions = {ec['name']: ec['versionprefix'] + ec['version'] + ec['versionsuffix']} if ec['name'] == 'ifort': # 'icc' key should be provided since it's the only one used in the template - comp_versions.update({'icc': ec['version'] + ec['versionsuffix']}) + comp_versions.update({'icc': ec['versionprefix'] + ec['version'] + ec['versionsuffix']}) if tc_comp_info is not None: # also provide toolchain version for non-dummy toolchains comp_versions.update({tc_comp_info[0]: tc_comp_info[1]}) @@ -161,7 +161,7 @@ def det_modpath_extensions(self, ec): comp_name_ver = [comp_name, comp_ver_tmpl % comp_versions] break else: - comp_name_ver = [ec['name'], ec['version'] + ec['versionsuffix']] + comp_name_ver = [ec['name'], ec['versionprefix'] + ec['version'] + ec['versionsuffix']] paths.append(os.path.join(COMPILER, *comp_name_ver)) @@ -173,7 +173,7 @@ def det_modpath_extensions(self, ec): self.log.error(error_msg) else: tc_comp_name, tc_comp_ver = tc_comp_info - fullver = ec['version'] + ec['versionsuffix'] + fullver = ec['versionprefix'] + ec['version'] + ec['versionsuffix'] paths.append(os.path.join(MPI, tc_comp_name, tc_comp_ver, ec['name'], fullver)) return paths From a2efe67a156cf69490640b977ef9bf182e9df8ec Mon Sep 17 00:00:00 2001 From: ocaisa Date: Fri, 10 Oct 2014 14:12:43 +0200 Subject: [PATCH 062/298] Typo when pasting suffix should have been prefix --- easybuild/tools/module_naming_scheme/hierarchical_mns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/tools/module_naming_scheme/hierarchical_mns.py b/easybuild/tools/module_naming_scheme/hierarchical_mns.py index 7c7167ecfc..cae623137d 100644 --- a/easybuild/tools/module_naming_scheme/hierarchical_mns.py +++ b/easybuild/tools/module_naming_scheme/hierarchical_mns.py @@ -88,7 +88,7 @@ def det_toolchain_compilers_name_version(self, tc_comps): elif len(tc_comps) == 1: res = (tc_comps[0]['name'], tc_comps[0]['version']) else: - comp_versions = dict([(comp['name'], comp['versionsuffix'] + comp['version'] + comp['versionsuffix']) for comp in tc_comps]) + comp_versions = dict([(comp['name'], comp['versionprefix'] + comp['version'] + comp['versionsuffix']) for comp in tc_comps]) comp_names = comp_versions.keys() key = ','.join(sorted(comp_names)) if key in COMP_NAME_VERSION_TEMPLATES: From 490e8096e3fe38819ddc8438a56fff898c5edad4 Mon Sep 17 00:00:00 2001 From: ocaisa Date: Fri, 10 Oct 2014 14:32:50 +0200 Subject: [PATCH 063/298] Added utility to determine full version name Stitch together the version name in utility and replace occurences --- .../module_naming_scheme/hierarchical_mns.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/easybuild/tools/module_naming_scheme/hierarchical_mns.py b/easybuild/tools/module_naming_scheme/hierarchical_mns.py index cae623137d..fccf652f1e 100644 --- a/easybuild/tools/module_naming_scheme/hierarchical_mns.py +++ b/easybuild/tools/module_naming_scheme/hierarchical_mns.py @@ -64,7 +64,10 @@ def requires_toolchain_details(self): """ return True - def det_full_module_name(self, ec): + def det_full_version(self, ec): + """Determine full version, taking into account version prefix/suffix.""" + return ec['versionprefix'] + ec['version'] + ec['versionsuffix'] + def det_full_module_name(self, ec): """ Determine full module name, relative to the top of the module path. Examples: Core/GCC/4.8.3, Compiler/GCC/4.8.3/OpenMPI/1.6.5, MPI/GCC/4.8.3/OpenMPI/1.6.5/HPL/2.1 @@ -76,7 +79,7 @@ def det_short_module_name(self, ec): Determine short module name, i.e. the name under which modules will be exposed to users. Examples: GCC/4.8.3, OpenMPI/1.6.5, OpenBLAS/0.2.9, HPL/2.1, Python/2.7.5 """ - return os.path.join(ec['name'], ec['versionprefix'] + ec['version'] + ec['versionsuffix']) + return os.path.join(ec['name'], self.det_full_version(ec)) def det_toolchain_compilers_name_version(self, tc_comps): """ @@ -88,7 +91,7 @@ def det_toolchain_compilers_name_version(self, tc_comps): elif len(tc_comps) == 1: res = (tc_comps[0]['name'], tc_comps[0]['version']) else: - comp_versions = dict([(comp['name'], comp['versionprefix'] + comp['version'] + comp['versionsuffix']) for comp in tc_comps]) + comp_versions = dict([(comp['name'], self.det_full_version(comp)) for comp in tc_comps]) comp_names = comp_versions.keys() key = ','.join(sorted(comp_names)) if key in COMP_NAME_VERSION_TEMPLATES: @@ -122,7 +125,7 @@ def det_module_subdir(self, ec): subdir = os.path.join(COMPILER, tc_comp_name, tc_comp_ver) else: # compiler-MPI toolchain => MPI//// namespace - tc_mpi_fullver = tc_mpi['versionprefix'] + tc_mpi['version'] + tc_mpi['versionsuffix'] + tc_mpi_fullver = self.det_full_version(tc_mpi) subdir = os.path.join(MPI, tc_comp_name, tc_comp_ver, tc_mpi['name'], tc_mpi_fullver) return subdir @@ -150,10 +153,10 @@ def det_modpath_extensions(self, ec): for key in COMP_NAME_VERSION_TEMPLATES: if ec['name'] in key.split(','): comp_name, comp_ver_tmpl = COMP_NAME_VERSION_TEMPLATES[key] - comp_versions = {ec['name']: ec['versionprefix'] + ec['version'] + ec['versionsuffix']} + comp_versions = {ec['name']: self.det_full_version(ec)} if ec['name'] == 'ifort': # 'icc' key should be provided since it's the only one used in the template - comp_versions.update({'icc': ec['versionprefix'] + ec['version'] + ec['versionsuffix']}) + comp_versions.update({'icc': self.det_full_version(ec)}) if tc_comp_info is not None: # also provide toolchain version for non-dummy toolchains comp_versions.update({tc_comp_info[0]: tc_comp_info[1]}) @@ -161,7 +164,7 @@ def det_modpath_extensions(self, ec): comp_name_ver = [comp_name, comp_ver_tmpl % comp_versions] break else: - comp_name_ver = [ec['name'], ec['versionprefix'] + ec['version'] + ec['versionsuffix']] + comp_name_ver = [ec['name'], self.det_full_version(ec)] paths.append(os.path.join(COMPILER, *comp_name_ver)) @@ -173,7 +176,7 @@ def det_modpath_extensions(self, ec): self.log.error(error_msg) else: tc_comp_name, tc_comp_ver = tc_comp_info - fullver = ec['versionprefix'] + ec['version'] + ec['versionsuffix'] + fullver = self.det_full_version(ec) paths.append(os.path.join(MPI, tc_comp_name, tc_comp_ver, ec['name'], fullver)) return paths From 83c98865b61bcb9ffa20bbd249cc79b54dfe8807 Mon Sep 17 00:00:00 2001 From: ocaisa Date: Fri, 10 Oct 2014 14:36:46 +0200 Subject: [PATCH 064/298] Typo --- .../tools/module_naming_scheme/hierarchical_mns.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/easybuild/tools/module_naming_scheme/hierarchical_mns.py b/easybuild/tools/module_naming_scheme/hierarchical_mns.py index fccf652f1e..d3fd0a490b 100644 --- a/easybuild/tools/module_naming_scheme/hierarchical_mns.py +++ b/easybuild/tools/module_naming_scheme/hierarchical_mns.py @@ -64,10 +64,7 @@ def requires_toolchain_details(self): """ return True - def det_full_version(self, ec): - """Determine full version, taking into account version prefix/suffix.""" - return ec['versionprefix'] + ec['version'] + ec['versionsuffix'] - def det_full_module_name(self, ec): + def det_full_module_name(self, ec): """ Determine full module name, relative to the top of the module path. Examples: Core/GCC/4.8.3, Compiler/GCC/4.8.3/OpenMPI/1.6.5, MPI/GCC/4.8.3/OpenMPI/1.6.5/HPL/2.1 @@ -81,6 +78,12 @@ def det_short_module_name(self, ec): """ return os.path.join(ec['name'], self.det_full_version(ec)) + def det_full_version(self, ec): + """Determine full version, taking into account version prefix/suffix.""" + + return ec['versionprefix'] + ec['version'] + ec['versionsuffix'] + + def det_toolchain_compilers_name_version(self, tc_comps): """ Determine toolchain compiler tag, for given list of compilers. From 198704b9c976e5c12bd48db49e2a068beb2dfd2c Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 10 Oct 2014 16:02:56 +0200 Subject: [PATCH 065/298] versionprefix is not always available, only use it when it's defined --- easybuild/tools/module_naming_scheme/hierarchical_mns.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/easybuild/tools/module_naming_scheme/hierarchical_mns.py b/easybuild/tools/module_naming_scheme/hierarchical_mns.py index d3fd0a490b..42297c1ba2 100644 --- a/easybuild/tools/module_naming_scheme/hierarchical_mns.py +++ b/easybuild/tools/module_naming_scheme/hierarchical_mns.py @@ -55,7 +55,7 @@ class HierarchicalMNS(ModuleNamingScheme): """Class implementing an example hierarchical module naming scheme.""" - REQUIRED_KEYS = ['name', 'versionprefix', 'version', 'versionsuffix', 'toolchain', 'moduleclass'] + REQUIRED_KEYS = ['name', 'version', 'versionsuffix', 'toolchain', 'moduleclass'] def requires_toolchain_details(self): """ @@ -80,9 +80,9 @@ def det_short_module_name(self, ec): def det_full_version(self, ec): """Determine full version, taking into account version prefix/suffix.""" - - return ec['versionprefix'] + ec['version'] + ec['versionsuffix'] - + # versionprefix is not always available (e.g., for toolchains) + versionprefix = ec.get('versionprefix', '') + return versionprefix + ec['version'] + ec['versionsuffix'] def det_toolchain_compilers_name_version(self, tc_comps): """ From 54bf7bbbe81e507f5a2830b5a21bc8886d2f2f4a Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 10 Oct 2014 16:04:36 +0200 Subject: [PATCH 066/298] keep 'versionprefix' as a required key --- easybuild/tools/module_naming_scheme/hierarchical_mns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/tools/module_naming_scheme/hierarchical_mns.py b/easybuild/tools/module_naming_scheme/hierarchical_mns.py index 42297c1ba2..22a0342965 100644 --- a/easybuild/tools/module_naming_scheme/hierarchical_mns.py +++ b/easybuild/tools/module_naming_scheme/hierarchical_mns.py @@ -55,7 +55,7 @@ class HierarchicalMNS(ModuleNamingScheme): """Class implementing an example hierarchical module naming scheme.""" - REQUIRED_KEYS = ['name', 'version', 'versionsuffix', 'toolchain', 'moduleclass'] + REQUIRED_KEYS = ['name', 'versionprefix', 'version', 'versionsuffix', 'toolchain', 'moduleclass'] def requires_toolchain_details(self): """ From feda536440d30c8427e9961a6bb4b97ba533a8ac Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Mon, 13 Oct 2014 21:54:41 +0200 Subject: [PATCH 067/298] refactor main.py, flesh out functions from main function --- easybuild/framework/easyblock.py | 3 +- easybuild/main.py | 534 ++++++++++++++++++------------- easybuild/tools/build_log.py | 26 ++ easybuild/tools/config.py | 8 + easybuild/tools/github.py | 7 +- 5 files changed, 347 insertions(+), 231 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 32bbe6a4a9..f988295cfe 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -38,7 +38,6 @@ import copy import glob -import re import os import shutil import stat @@ -74,6 +73,7 @@ from easybuild.tools.utilities import remove_unwanted_chars from easybuild.tools.version import this_is_easybuild, VERBOSE_VERSION, VERSION + _log = fancylogger.getLogger('easyblock') @@ -1982,6 +1982,7 @@ def build_and_install_one(module, orig_environ): return (success, application_log, errormsg) + def get_easyblock_instance(easyconfig): """ Get an instance for this easyconfig diff --git a/easybuild/main.py b/easybuild/main.py index a572ac2c05..bd5e2f0331 100644 --- a/easybuild/main.py +++ b/easybuild/main.py @@ -39,14 +39,12 @@ import os import subprocess import sys -import tempfile import traceback -from vsc.utils import fancylogger from vsc.utils.missing import any # IMPORTANT this has to be the first easybuild import as it customises the logging # expect missing log output when this not the case! -from easybuild.tools.build_log import EasyBuildError, print_msg, print_error +from easybuild.tools.build_log import EasyBuildError, init_logging, print_msg, print_error, stop_logging import easybuild.tools.config as config import easybuild.tools.options as eboptions @@ -55,7 +53,7 @@ from easybuild.framework.easyconfig.tools import dep_graph, get_paths_for, print_dry_run from easybuild.framework.easyconfig.tools import resolve_dependencies, skip_available from easybuild.framework.easyconfig.tweak import obtain_path, tweak -from easybuild.tools.config import get_repository, module_classes, get_repositorypath, set_tmpdir +from easybuild.tools.config import build_option, get_repository, module_classes, get_repositorypath, set_tmpdir from easybuild.tools.filetools import cleanup, find_easyconfigs, search_file, write_file from easybuild.tools.github import fetch_easyconfigs_from_pr from easybuild.tools.options import process_software_build_specs @@ -63,161 +61,68 @@ from easybuild.tools.repository.repository import init_repository from easybuild.tools.testing import create_test_report, post_easyconfigs_pr_test_report, upload_test_report_as_gist from easybuild.tools.testing import regtest, session_module_list, session_state -from easybuild.tools.toolchain import DUMMY_TOOLCHAIN_NAME from easybuild.tools.version import this_is_easybuild # from a single location _log = None -def build_and_install_software(ecs, init_session_state, exit_on_failure=True): - """Build and install software for all provided parsed easyconfig files.""" - # obtain a copy of the starting environment so each build can start afresh - # we shouldn't use the environment from init_session_state, since relevant env vars might have been set since - # e.g. via easyconfig.handle_allowed_system_deps - orig_environ = copy.deepcopy(os.environ) - - res = [] - for ec in ecs: - ec_res = {} - try: - (ec_res['success'], app_log, err) = build_and_install_one(ec, orig_environ) - ec_res['log_file'] = app_log - if not ec_res['success']: - ec_res['err'] = EasyBuildError(err) - except Exception, err: - # purposely catch all exceptions - ec_res['success'] = False - ec_res['err'] = err - ec_res['traceback'] = traceback.format_exc() - - # keep track of success/total count - if ec_res['success']: - test_msg = "Successfully built %s" % ec['spec'] - else: - test_msg = "Build of %s failed" % ec['spec'] - if 'err' in ec_res: - test_msg += " (err: %s)" % ec_res['err'] - - # dump test report next to log file - test_report_txt = create_test_report(test_msg, [(ec, ec_res)], init_session_state) - if 'log_file' in ec_res: - test_report_fp = "%s_test_report.md" % '.'.join(ec_res['log_file'].split('.')[:-1]) - write_file(test_report_fp, test_report_txt) - - if not ec_res['success'] and exit_on_failure: - if 'traceback' in ec_res: - _log.error(ec_res['traceback']) - else: - _log.error(test_msg) - - res.append((ec, ec_res)) - - return res - - -def main(testing_data=(None, None, None)): - """ - Main function: - @arg options: a tuple: (options, paths, logger, logfile, hn) as defined in parse_options - This function will: - - read easyconfig - - build software - """ - - # purposely session state very early, to avoid modules loaded by EasyBuild meddling in - init_session_state = session_state() - - # disallow running EasyBuild as root - if os.getuid() == 0: - sys.stderr.write("ERROR: You seem to be running EasyBuild with root privileges.\n" - "That's not wise, so let's end this here.\n" - "Exiting.\n") - sys.exit(1) - - # steer behavior when testing main - testing = testing_data[0] is not None - args, logfile, do_build = testing_data - - # initialise options - eb_go = eboptions.parse_options(args=args) - options = eb_go.options - orig_paths = eb_go.args - eb_config = eb_go.generate_cmd_line(add_default=True) - init_session_state.update({'easybuild_configuration': eb_config}) - - # set umask (as early as possible) - if options.umask is not None: - new_umask = int(options.umask, 8) - old_umask = os.umask(new_umask) +def log_start(eb_command_line, eb_tmpdir): + """Log startup info.""" + _log.info(this_is_easybuild()) - # set temporary directory to use - eb_tmpdir = set_tmpdir(options.tmpdir) + # log used command line + _log.info("Command line: %s" % (' '.join(eb_command_line))) - # initialise logging for main - if options.logtostdout: - fancylogger.logToScreen(enable=True, stdout=True) - else: - if logfile is None: - # mkstemp returns (fd,filename), fd is from os.open, not regular open! - fd, logfile = tempfile.mkstemp(suffix='.log', prefix='easybuild-') - os.close(fd) - - fancylogger.logToFile(logfile) - print_msg('temporary log file in case of crash %s' % (logfile), log=None, silent=testing) + _log.info("Using %s as temporary directory" % eb_tmpdir) - global _log - _log = fancylogger.getLogger(fname=False) - if options.umask is not None: - _log.info("umask set to '%s' (used to be '%s')" % (oct(new_umask), oct(old_umask))) +def alt_easyconfig_paths(tmpdir, tweaked_ecs=False, from_pr=False): + """Obtain alternative paths for easyconfig files.""" + # prepend robot path with location where tweaked easyconfigs will be placed + tweaked_ecs_path = None + if tweaked_ecs: + tweaked_ecs_path = os.path.join(tmpdir, 'tweaked_easyconfigs') - # hello world! - _log.info(this_is_easybuild()) + pr_path = None + if from_pr: + # extend robot search path with location where files touch in PR will be downloaded to + pr_path = os.path.join(tmpdir, "files_pr%s" % from_pr) - # how was EB called? - eb_command_line = eb_go.generate_cmd_line() + eb_go.args - _log.info("Command line: %s" % (" ".join(eb_command_line))) + return tweaked_ecs_path, pr_path - _log.info("Using %s as temporary directory" % eb_tmpdir) - if not options.robot is None: - if options.robot: - _log.info("Using robot path(s): %s" % options.robot) +def det_robot_path(robot_option, easyconfigs_paths, tweaked_ecs_path, pr_path, auto_robot=False): + """Determine robot path.""" + # do not use robot option directly, it's not a list instance (and it shouldn't be modified) + robot_path = [] + if not robot_option is None: + if robot_option: + robot_path = list(robot_option) + _log.info("Using robot path(s): %s" % robot_path) else: + # if options.robot is not None and False, easyconfigs pkg install path could not be found (see options.py) _log.error("No robot paths specified, and unable to determine easybuild-easyconfigs install path.") - # do not pass options.robot, it's not a list instance (and it shouldn't be modified) - robot_path = [] - if options.robot: - robot_path = list(options.robot) - - # determine easybuild-easyconfigs package install path - easyconfigs_paths = get_paths_for("easyconfigs", robot_path=robot_path) - # keep track of paths for install easyconfigs, so we can obtain find specified easyconfigs - easyconfigs_pkg_full_paths = easyconfigs_paths[:] - if not easyconfigs_paths: - _log.warning("Failed to determine install path for easybuild-easyconfigs package.") - - # process software build specifications (if any), i.e. - # software name/version, toolchain name/version, extra patches, ... - (try_to_generate, build_specs) = process_software_build_specs(options) - - # specified robot paths are preferred over installed easyconfig files - # --try-X and --dep-graph both require --robot, so enable it with path of installed easyconfigs - if robot_path or try_to_generate or options.dep_graph: + if auto_robot: robot_path.extend(easyconfigs_paths) - easyconfigs_paths = robot_path[:] _log.info("Extended list of robot paths with paths for installed easyconfigs: %s" % robot_path) - # prepend robot path with location where tweaked easyconfigs will be placed - tweaked_ecs_path = None - if try_to_generate and build_specs: - tweaked_ecs_path = os.path.join(eb_tmpdir, 'tweaked_easyconfigs') + if tweaked_ecs_path is not None: robot_path.insert(0, tweaked_ecs_path) + _log.info("Prepended list of robot search paths with %s: %s" % (tweaked_ecs_path, robot_path)) + + if pr_path is not None: + robot_path.insert(0, pr_path) + _log.info("Prepended list of robot search paths with %s: %s" % (pr_path, robot_path)) + + return robot_path + +def configure(options, config_options_dict, build_options): + """Configure EasyBuild.""" # initialise the easybuild configuration - config.init(options, eb_go.get_options_by_section('config')) + config.init(options, config_options_dict) # building a dependency graph implies force, so that all dependencies are retained # and also skips validation of easyconfigs (e.g. checking os dependencies) @@ -230,22 +135,15 @@ def main(testing_data=(None, None, None)): if options.dep_graph or options.dry_run or options.dry_run_short: options.ignore_osdeps = True - pr_path = None - if options.from_pr: - # extend robot search path with location where files touch in PR will be downloaded to - pr_path = os.path.join(eb_tmpdir, "files_pr%s" % options.from_pr) - robot_path.insert(0, pr_path) - _log.info("Prepended list of robot search paths with %s: %s" % (pr_path, robot_path)) - - config.init_build_options({ + build_options.update({ 'aggregate_regtest': options.aggregate_regtest, 'allow_modules_tool_mismatch': options.allow_modules_tool_mismatch, 'check_osdeps': not options.ignore_osdeps, 'filter_deps': options.filter_deps, 'cleanup_builddir': options.cleanup_builddir, - 'command_line': eb_command_line, 'debug': options.debug, 'dry_run': options.dry_run or options.dry_run_short, + 'dump_test_report': options.dump_test_report, 'easyblock': options.easyblock, 'experimental': options.experimental, 'force': options.force, @@ -259,9 +157,7 @@ def main(testing_data=(None, None, None)): 'recursive_mod_unload': options.recursive_module_unload, 'regtest_output_dir': options.regtest_output_dir, 'retain_all_deps': retain_all_deps, - 'robot_path': robot_path, 'sequential': options.sequential, - 'silent': testing, 'set_gid_bit': options.set_gid_bit, 'skip': options.skip, 'skip_test_cases': options.skip_test_cases, @@ -270,42 +166,53 @@ def main(testing_data=(None, None, None)): 'suffix_modules_path': options.suffix_modules_path, 'test_report_env_filter': options.test_report_env_filter, 'umask': options.umask, + 'upload_test_report': options.upload_test_report, 'valid_module_classes': module_classes(), 'valid_stops': [x[0] for x in EasyBlock.get_steps()], 'validate': not options.force, }) + config.init_build_options(build_options) - # obtain list of loaded modules, build options must be initialized first - modlist = session_module_list(testing=testing) - init_session_state.update({'module_list': modlist}) - _log.debug("Initial session state: %s" % init_session_state) - # search for easyconfigs - if options.search or options.search_short: - search_path = [os.getcwd()] - if easyconfigs_paths: - search_path = easyconfigs_paths - query = options.search or options.search_short - ignore_dirs = config.build_option('ignore_dirs') - silent = config.build_option('silent') - search_file(search_path, query, short=not options.search, ignore_dirs=ignore_dirs, silent=silent) +def search(query, short=False): + """Search for easyconfigs, if a query is provided.""" + search_path = [os.getcwd()] + robot_path = build_option('robot_path') + if robot_path: + search_path = robot_path + ignore_dirs = config.build_option('ignore_dirs') + silent = config.build_option('silent') + search_file(search_path, query, short=short, ignore_dirs=ignore_dirs, silent=silent) + +def det_easyconfig_paths(orig_paths, from_pr=None, easyconfigs_pkg_paths=None): + """ + Determine paths to easyconfig files. + @param orig_paths: list of original easyconfig paths + @param from_pr: pull request number to fetch easyconfigs from + @param easyconfigs_pkg_paths: paths to installed easyconfigs package + """ paths = [] + + if easyconfigs_pkg_paths is None: + easyconfigs_pkg_paths = [] + build_specs = build_option('build_specs') + ignore_dirs = build_option('ignore_dirs') + robot_path = build_option('robot_path') + testing = build_option('testing') + try_to_generate = build_option('try_to_generate') + if len(orig_paths) == 0: - if options.from_pr: - pr_files = fetch_easyconfigs_from_pr(options.from_pr, path=pr_path, github_user=options.github_user) + if from_pr: + pr_files = fetch_easyconfigs_from_pr(from_pr) paths = [(path, False) for path in pr_files if path.endswith('.eb')] elif 'name' in build_specs: - paths = [obtain_path(build_specs, easyconfigs_paths, try_to_generate=try_to_generate, + paths = [obtain_path(build_specs, robot_path, try_to_generate=try_to_generate, exit_on_error=not testing)] - elif not any([options.aggregate_regtest, options.search, options.search_short, options.regtest]): - print_error(("Please provide one or multiple easyconfig files, or use software build " - "options to make EasyBuild search for easyconfigs"), - log=_log, opt_parser=eb_go.parser, exit_on_error=not testing) else: # look for easyconfigs with relative paths in easybuild-easyconfigs package, # unless they were found at the given relative paths - if easyconfigs_pkg_full_paths: + if easyconfigs_pkg_paths: # determine which easyconfigs files need to be found, if any ecs_to_find = [] for idx, orig_path in enumerate(orig_paths): @@ -314,7 +221,7 @@ def main(testing_data=(None, None, None)): _log.debug("List of easyconfig files to find: %s" % ecs_to_find) # find missing easyconfigs by walking paths with installed easyconfig files - for path in easyconfigs_pkg_full_paths: + for path in easyconfigs_pkg_paths: _log.debug("Looking for missing easyconfig files (%d left) in %s..." % (len(ecs_to_find), path)) for (subpath, dirnames, filenames) in os.walk(path, topdown=True): for idx, orig_path in ecs_to_find[:]: @@ -330,7 +237,7 @@ def main(testing_data=(None, None, None)): break # ignore subdirs specified to be ignored by replacing items in dirnames list used by os.walk - dirnames[:] = [d for d in dirnames if not d in options.ignore_dirs] + dirnames[:] = [d for d in dirnames if not d in ignore_dirs] # stop os.walk insanity as soon as we have all we need (paths loop) if len(ecs_to_find) == 0: @@ -339,22 +246,19 @@ def main(testing_data=(None, None, None)): # indicate that specified paths do not contain generated easyconfig files paths = [(path, False) for path in orig_paths] - _log.debug("Paths: %s" % paths) + return paths - # run regtest - if options.regtest or options.aggregate_regtest: - _log.info("Running regression test") - if paths: - ec_paths = [path[0] for path in paths] - else: # fallback: easybuild-easyconfigs install path - ec_paths = easyconfigs_pkg_full_paths - regtest_ok = regtest(ec_paths) - if not regtest_ok: - _log.info("Regression test failed (partially)!") - sys.exit(31) # exit -> 3x1t -> 31 +def read_easyconfigs(paths): + """ + Read/parse easyconfigs + @params paths: paths to easyconfigs + """ + build_specs = build_option('build_specs') + ignore_dirs = build_option('ignore_dirs') + try_to_generate = build_option('try_to_generate') + tweaked_ecs_path = build_option('tweaked_ecs_path') - # read easyconfig files easyconfigs = [] generated_ecs = False for (path, generated) in paths: @@ -363,9 +267,8 @@ def main(testing_data=(None, None, None)): generated_ecs |= generated if not os.path.exists(path): print_error("Can't find path %s" % path) - try: - ec_files = find_easyconfigs(path, ignore_dirs=options.ignore_dirs) + ec_files = find_easyconfigs(path, ignore_dirs=ignore_dirs) for ec_file in ec_files: # only pass build specs when not generating easyconfig files if try_to_generate: @@ -382,13 +285,222 @@ def main(testing_data=(None, None, None)): if try_to_generate and build_specs and not generated_ecs: easyconfigs = tweak(easyconfigs, build_specs, targetdir=tweaked_ecs_path) - # before building starts, take snapshot of environment (watch out -t option!) - os.chdir(os.environ['PWD']) + return easyconfigs + + +def submit_jobs(ordered_ecs, cmd_line_opts, testing=False): + """ + Submit jobs. + @param ordered_ecs: list of easyconfigs, in the order they should be processed + @param cmd_line_opts: list of command line options (in 'longopt=value' form) + """ + curdir = os.getcwd() + + # the options to ignore (help options can't reach here) + ignore_opts = ['robot', 'job'] + + # generate_cmd_line returns the options in form --longopt=value + opts = [x for x in cmd_line_opts if not x.split('=')[0] in ['--%s' % y for y in ignore_opts]] + + quoted_opts = subprocess.list2cmdline(opts) + + command = "unset TMPDIR && cd %s && eb %%(spec)s %s" % (curdir, quoted_opts) + _log.info("Command template for jobs: %s" % command) + if not testing: + jobs = build_easyconfigs_in_parallel(command, ordered_ecs) + txt = ["List of submitted jobs:"] + txt.extend(["%s (%s): %s" % (job.name, job.module, job.jobid) for job in jobs]) + txt.append("(%d jobs submitted)" % len(jobs)) + + print_msg("Submitted parallel build jobs, exiting now: %s" % '\n'.join(txt), log=_log) + + +def build_and_install_software(ecs, init_session_state, exit_on_failure=True): + """Build and install software for all provided parsed easyconfig files.""" + # obtain a copy of the starting environment so each build can start afresh + # we shouldn't use the environment from init_session_state, since relevant env vars might have been set since + # e.g. via easyconfig.handle_allowed_system_deps + orig_environ = copy.deepcopy(os.environ) + + res = [] + for ec in ecs: + ec_res = {} + try: + (ec_res['success'], app_log, err) = build_and_install_one(ec, orig_environ) + ec_res['log_file'] = app_log + if not ec_res['success']: + ec_res['err'] = EasyBuildError(err) + except Exception, err: + # purposely catch all exceptions + ec_res['success'] = False + ec_res['err'] = err + ec_res['traceback'] = traceback.format_exc() + + # keep track of success/total count + if ec_res['success']: + test_msg = "Successfully built %s" % ec['spec'] + else: + test_msg = "Build of %s failed" % ec['spec'] + if 'err' in ec_res: + test_msg += " (err: %s)" % ec_res['err'] + + # dump test report next to log file + test_report_txt = create_test_report(test_msg, [(ec, ec_res)], init_session_state) + if 'log_file' in ec_res: + test_report_fp = "%s_test_report.md" % '.'.join(ec_res['log_file'].split('.')[:-1]) + write_file(test_report_fp, test_report_txt) + + if not ec_res['success'] and exit_on_failure: + if 'traceback' in ec_res: + _log.error(ec_res['traceback']) + else: + _log.error(test_msg) + + res.append((ec, ec_res)) + + return res + + +def test_report(ecs_with_res, orig_cnt, success, msg, init_session_state): + """ + Upload/dump test report + @param ecs_with_res: processed easyconfigs with build result (success/failure) + @param orig_cnt: number of original easyconfig paths + @param success: boolean indicating whether all builds were successful + @param msg: message to be included in test report + @param init_session_state: initial session state info to include in test report + """ + dump_path = build_option('dump_test_report') + pr_nr = build_option('from_pr') + upload = build_option('upload_test_report') + + if upload: + msg = msg + " (%d easyconfigs in this PR)" % orig_cnt + test_report = create_test_report(msg, ecs_with_res, init_session_state, pr_nr=pr_nr, gist_log=True) + if pr_nr: + # upload test report to gist and issue a comment in the PR to notify + msg = post_easyconfigs_pr_test_report(pr_nr, test_report, msg, init_session_state, success) + print_msg(msg) + else: + # only upload test report as a gist + gist_url = upload_test_report_as_gist(test_report) + print_msg("Test report uploaded to %s" % gist_url) + else: + test_report = create_test_report(msg, ecs_with_res, init_session_state) + _log.debug("Test report: %s" % test_report) + if dump_path is not None: + write_file(dump_path, test_report) + _log.info("Test report dumped to %s" % dump_path) + + +def main(testing_data=(None, None, None)): + """ + Main function: parse command line options, and act accordingly. + @param testing_data: tuple with command line arguments, log file and boolean indicating whether or not to build + """ + # purposely session state very early, to avoid modules loaded by EasyBuild meddling in + init_session_state = session_state() + + # steer behavior when testing main + testing = testing_data[0] is not None + args, logfile, do_build = testing_data + + # initialise options + eb_go = eboptions.parse_options(args=args) + options = eb_go.options + orig_paths = eb_go.args + + # set umask (as early as possible) + if options.umask is not None: + new_umask = int(options.umask, 8) + old_umask = os.umask(new_umask) + + # set temporary directory to use + eb_tmpdir = set_tmpdir(options.tmpdir) + + # initialise logging for main + global _log + _log, logfile = init_logging(logfile, logtostdout=options.logtostdout, testing=testing) + + # disallow running EasyBuild as root + if os.getuid() == 0: + _log.error("You seem to be running EasyBuild with root privileges which is not wise, so let's end this here.") + + # log startup info + eb_cmd_line = eb_go.generate_cmd_line() + eb_go.args + log_start(eb_cmd_line, eb_tmpdir) + + if options.umask is not None: + _log.info("umask set to '%s' (used to be '%s')" % (oct(new_umask), oct(old_umask))) + + # determine easybuild-easyconfigs package install path + easyconfigs_pkg_paths = get_paths_for("easyconfigs") + if not easyconfigs_pkg_paths: + _log.warning("Failed to determine install path for easybuild-easyconfigs package.") + + # process software build specifications (if any), i.e. + # software name/version, toolchain name/version, extra patches, ... + (try_to_generate, build_specs) = process_software_build_specs(options) + + # determine robot path + # --try-X, --dep-graph, --search use robot path for searching, so enable it with path of installed easyconfigs + tweaked_ecs = try_to_generate and build_specs + tweaked_ecs_path, pr_path = alt_easyconfig_paths(eb_tmpdir, tweaked_ecs=tweaked_ecs, from_pr=options.from_pr) + auto_robot = try_to_generate or options.dep_graph or options.search or options.search_short + robot_path = det_robot_path(options.robot, easyconfigs_pkg_paths, tweaked_ecs_path, pr_path, auto_robot=auto_robot) + _log.debug("Full robot path: %s" % robot_path) + + # configure & initialize build options + config_options_dict = eb_go.get_options_by_section('config') + build_options = { + 'build_specs': build_specs, + 'command_line': eb_cmd_line, + 'pr_path': pr_path, + 'robot_path': robot_path, + 'silent': testing, + 'testing': testing, + 'try_to_generate': try_to_generate, + 'tweaked_ecs_path': tweaked_ecs_path, + } + configure(options, config_options_dict, build_options) + + # update session state + eb_config = eb_go.generate_cmd_line(add_default=True) + modlist = session_module_list(testing=testing) # build options must be initialized first before 'module list' works + init_session_state.update({'easybuild_configuration': eb_config}) + init_session_state.update({'module_list': modlist}) + _log.debug("Initial session state: %s" % init_session_state) + + # search for easyconfigs, if a query is specified + query = options.search or options.search_short + if query: + search(query, short=not options.search) + + # determine paths to easyconfigs + paths = det_easyconfig_paths(orig_paths, options.from_pr, easyconfigs_pkg_paths) + if not paths and not any([options.aggregate_regtest, options.search, options.search_short, options.regtest]): + print_error(("Please provide one or multiple easyconfig files, or use software build " + "options to make EasyBuild search for easyconfigs"), + log=_log, opt_parser=eb_go.parser, exit_on_error=not testing) + _log.debug("Paths: %s" % paths) + + # run regtest + if options.regtest or options.aggregate_regtest: + _log.info("Running regression test") + # fallback: easybuild-easyconfigs install path + regtest_ok = regtest([path[0] for path in paths] or easyconfigs_pkg_paths) + if not regtest_ok: + _log.info("Regression test failed (partially)!") + sys.exit(31) # exit -> 3x1t -> 31 + + # read easyconfig files + easyconfigs = read_easyconfigs(paths) # dry_run: print all easyconfigs and dependencies, and whether they are already built if options.dry_run or options.dry_run_short: print_dry_run(easyconfigs, short=not options.dry_run, build_specs=build_specs) + # cleanup and exit after dry run, searching easyconfigs or submitting regression test if any([options.dry_run, options.dry_run_short, options.regtest, options.search, options.search_short]): cleanup(logfile, eb_tmpdir, testing) sys.exit(0) @@ -411,27 +523,10 @@ def main(testing_data=(None, None, None)): dep_graph(options.dep_graph, ordered_ecs) sys.exit(0) - # submit build as job(s) and exit + # submit build as job(s), clean up and exit if options.job: - curdir = os.getcwd() - - # the options to ignore (help options can't reach here) - ignore_opts = ['robot', 'job'] - - # generate_cmd_line returns the options in form --longopt=value - opts = [x for x in eb_go.generate_cmd_line() if not x.split('=')[0] in ['--%s' % y for y in ignore_opts]] - - quoted_opts = subprocess.list2cmdline(opts) - - command = "unset TMPDIR && cd %s && eb %%(spec)s %s" % (curdir, quoted_opts) - _log.info("Command template for jobs: %s" % command) + submit_jobs(ordered_ecs, eb_go.generate_cmd_line(), testing=testing) if not testing: - jobs = build_easyconfigs_in_parallel(command, ordered_ecs) - txt = ["List of submitted jobs:"] - txt.extend(["%s (%s): %s" % (job.name, job.module, job.jobid) for job in jobs]) - txt.append("(%d jobs submitted)" % len(jobs)) - - print_msg("Submitted parallel build jobs, exiting now: %s" % '\n'.join(txt), log=_log) cleanup(logfile, eb_tmpdir, testing) sys.exit(0) @@ -449,24 +544,8 @@ def main(testing_data=(None, None, None)): repo = init_repository(get_repository(), get_repositorypath()) repo.cleanup() - # report back in PR in case of testing - if options.upload_test_report: - msg = success_msg + " (%d easyconfigs in this PR)" % len(paths) - test_report = create_test_report(msg, ecs_with_res, init_session_state, pr_nr=options.from_pr, gist_log=True) - if options.from_pr: - # upload test report to gist and issue a comment in the PR to notify - msg = post_easyconfigs_pr_test_report(options.from_pr, test_report, success_msg, init_session_state, overall_success) - print_msg(msg) - else: - # only upload test report as a gist - gist_url = upload_test_report_as_gist(test_report) - print_msg("Test report uploaded to %s" % gist_url) - else: - test_report = create_test_report(success_msg, ecs_with_res, init_session_state) - _log.debug("Test report: %s" % test_report) - if options.dump_test_report is not None: - write_file(options.dump_test_report, test_report) - _log.info("Test report dumped to %s" % options.dump_test_report) + # dump/upload overall test report + test_report(ecs_with_res, len(paths), overall_success, success_msg, init_session_state) print_msg(success_msg, log=_log, silent=testing) @@ -475,11 +554,8 @@ def main(testing_data=(None, None, None)): if 'original_spec' in ec and os.path.isfile(ec['spec']): os.remove(ec['spec']) - # cleanup tmp log file, unless one build failed (individual logs are located in eb_tmpdir path) - if options.logtostdout: - fancylogger.logToScreen(enable=False, stdout=True) - else: - fancylogger.logToFile(logfile, enable=False) + # stop logging and cleanup tmp log file, unless one build failed (individual logs are located in eb_tmpdir path) + stop_logging(logfile, logtostdout=options.logtostdout) if overall_success: cleanup(logfile, eb_tmpdir, testing) diff --git a/easybuild/tools/build_log.py b/easybuild/tools/build_log.py index c08ffbf71c..e4873068ae 100644 --- a/easybuild/tools/build_log.py +++ b/easybuild/tools/build_log.py @@ -33,6 +33,7 @@ """ import os import sys +import tempfile from copy import copy from vsc.utils import fancylogger @@ -134,6 +135,31 @@ def exception(self, msg, *args): _init_easybuildlog = fancylogger.getLogger(fname=False) +def init_logging(logfile, logtostdout=False, testing=False): + """Initialize logging.""" + if logtostdout: + fancylogger.logToScreen(enable=True, stdout=True) + else: + if logfile is None: + # mkstemp returns (fd,filename), fd is from os.open, not regular open! + fd, logfile = tempfile.mkstemp(suffix='.log', prefix='easybuild-') + os.close(fd) + + fancylogger.logToFile(logfile) + print_msg('temporary log file in case of crash %s' % (logfile), log=None, silent=testing) + + log = fancylogger.getLogger(fname=False) + + return log, logfile + + +def stop_logging(logfile, logtostdout=False): + """Stop logging.""" + if logtostdout: + fancylogger.logToScreen(enable=False, stdout=True) + fancylogger.logToFile(logfile, enable=False) + + def get_log(name=None): """ Generate logger object diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py index d595b79803..e3f67779f7 100644 --- a/easybuild/tools/config.py +++ b/easybuild/tools/config.py @@ -71,15 +71,18 @@ DEFAULT_BUILD_OPTIONS = { 'aggregate_regtest': None, 'allow_modules_tool_mismatch': False, + 'build_specs': None, 'check_osdeps': True, 'filter_deps': None, 'cleanup_builddir': True, 'command_line': None, 'debug': False, 'dry_run': False, + 'dump_test_report': None, 'easyblock': None, 'experimental': False, 'force': False, + 'from_pr': None, 'github_user': None, 'group': None, 'hidden': False, @@ -87,6 +90,7 @@ 'modules_footer': None, 'only_blocks': None, 'optarch': None, + 'pr_path': None, 'recursive_mod_unload': False, 'regtest_output_dir': None, 'retain_all_deps': False, @@ -100,7 +104,11 @@ 'stop': None, 'suffix_modules_path': None, 'test_report_env_filter': None, + 'testing': False, + 'try_to_generate': False, + 'tweaked_ecs_path': None, 'umask': None, + 'upload_test_report': False, 'valid_module_classes': None, 'valid_stops': None, 'validate': True, diff --git a/easybuild/tools/github.py b/easybuild/tools/github.py index ae544e28b4..89319ad2ce 100644 --- a/easybuild/tools/github.py +++ b/easybuild/tools/github.py @@ -30,7 +30,6 @@ """ import base64 import os -import re import socket import tempfile import urllib @@ -56,6 +55,7 @@ _log.warning("Failed to import from 'vsc.utils.rest' Python module: %s" % err) HAVE_GITHUB_API = False +from easybuild.tools.config import build_option from easybuild.tools.filetools import det_patched_files, mkdir @@ -193,6 +193,11 @@ class GithubError(Exception): def fetch_easyconfigs_from_pr(pr, path=None, github_user=None): """Fetch patched easyconfig files for a particular PR.""" + if github_user is None: + github_user = build_option('github_user') + if path is None: + path = build_option('pr_path') + def download(url, path=None): """Download file from specified URL to specified path.""" if path is not None: From fc4a0cdc90d24a7c22e79eca7c908ce594f16d19 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 14 Oct 2014 11:13:23 +0200 Subject: [PATCH 068/298] move function out of main.py, introduce easybuild.tools.robot module --- easybuild/framework/easyconfig/tools.py | 281 ++++++++------------ easybuild/framework/easyconfig/tweak.py | 2 +- easybuild/main.py | 325 +++--------------------- easybuild/tools/build_log.py | 3 +- easybuild/tools/config.py | 65 ++++- easybuild/tools/filetools.py | 1 - easybuild/tools/options.py | 15 +- easybuild/tools/parallelbuild.py | 30 ++- easybuild/tools/robot.py | 255 +++++++++++++++++++ easybuild/tools/testing.py | 39 ++- test/framework/parallelbuild.py | 3 +- test/framework/robot.py | 15 +- test/framework/utilities.py | 2 +- 13 files changed, 548 insertions(+), 488 deletions(-) create mode 100644 easybuild/tools/robot.py diff --git a/easybuild/framework/easyconfig/tools.py b/easybuild/framework/easyconfig/tools.py index 6a7c48a062..f020231150 100644 --- a/easybuild/framework/easyconfig/tools.py +++ b/easybuild/framework/easyconfig/tools.py @@ -66,12 +66,11 @@ graph_errors.append("Failed to import graphviz: try yum install graphviz-python, or apt-get install python-pygraphviz") from easybuild.framework.easyconfig.easyconfig import ActiveMNS -from easybuild.framework.easyconfig.easyconfig import process_easyconfig, robot_find_easyconfig -from easybuild.tools.build_log import EasyBuildError, print_msg +from easybuild.framework.easyconfig.easyconfig import process_easyconfig +from easybuild.tools.build_log import EasyBuildError, print_error, print_msg from easybuild.tools.config import build_option -from easybuild.tools.filetools import det_common_path_prefix, run_cmd, write_file -from easybuild.tools.module_naming_scheme.easybuild_mns import EasyBuildMNS -from easybuild.tools.module_naming_scheme.utilities import det_full_ec_version, det_hidden_modname +from easybuild.tools.filetools import find_easyconfigs, run_cmd, search_file, write_file +from easybuild.tools.github import fetch_easyconfigs_from_pr from easybuild.tools.modules import modules_tool from easybuild.tools.ordereddict import OrderedDict from easybuild.tools.utilities import quote_str @@ -123,174 +122,6 @@ def find_resolved_modules(unprocessed, avail_modules): return ordered_ecs, new_unprocessed, new_avail_modules -def resolve_dependencies(unprocessed, build_specs=None, retain_all_deps=False): - """ - Work through the list of easyconfigs to determine an optimal order - @param unprocessed: list of easyconfigs - @param build_specs: dictionary specifying build specifications (e.g. version, toolchain, ...) - """ - - robot = build_option('robot_path') - - retain_all_deps = build_option('retain_all_deps') or retain_all_deps - if retain_all_deps: - # assume that no modules are available when forced, to retain all dependencies - avail_modules = [] - _log.info("Forcing all dependencies to be retained.") - else: - # Get a list of all available modules (format: [(name, installversion), ...]) - avail_modules = modules_tool().available() - - if len(avail_modules) == 0: - _log.warning("No installed modules. Your MODULEPATH is probably incomplete: %s" % os.getenv('MODULEPATH')) - - ordered_ecs = [] - # all available modules can be used for resolving dependencies except those that will be installed - being_installed = [p['full_mod_name'] for p in unprocessed] - avail_modules = [m for m in avail_modules if not m in being_installed] - - _log.debug('unprocessed before resolving deps: %s' % unprocessed) - - # resolve all dependencies, put a safeguard in place to avoid an infinite loop (shouldn't occur though) - irresolvable = [] - loopcnt = 0 - maxloopcnt = 10000 - while unprocessed: - # make sure this stops, we really don't want to get stuck in an infinite loop - loopcnt += 1 - if loopcnt > maxloopcnt: - tup = (maxloopcnt, unprocessed, irresolvable) - msg = "Maximum loop cnt %s reached, so quitting (unprocessed: %s, irresolvable: %s)" % tup - _log.error(msg) - - # first try resolving dependencies without using external dependencies - last_processed_count = -1 - while len(avail_modules) > last_processed_count: - last_processed_count = len(avail_modules) - more_ecs, unprocessed, avail_modules = find_resolved_modules(unprocessed, avail_modules) - for ec in more_ecs: - if not ec['full_mod_name'] in [x['full_mod_name'] for x in ordered_ecs]: - ordered_ecs.append(ec) - - # robot: look for existing dependencies, add them - if robot and unprocessed: - - # rely on EasyBuild module naming scheme when resolving dependencies, since we know that will - # generate sensible module names that include the necessary information for the resolution to work - # (name, version, toolchain, versionsuffix) - being_installed = [EasyBuildMNS().det_full_module_name(p['ec']) for p in unprocessed] - - additional = [] - for i, entry in enumerate(unprocessed): - # do not choose an entry that is being installed in the current run - # if they depend, you probably want to rebuild them using the new dependency - deps = entry['dependencies'] - candidates = [d for d in deps if not EasyBuildMNS().det_full_module_name(d) in being_installed] - if len(candidates) > 0: - cand_dep = candidates[0] - # find easyconfig, might not find any - _log.debug("Looking for easyconfig for %s" % str(cand_dep)) - # note: robot_find_easyconfig may return None - path = robot_find_easyconfig(cand_dep['name'], det_full_ec_version(cand_dep)) - - if path is None: - # no easyconfig found for dependency, add to list of irresolvable dependencies - if cand_dep not in irresolvable: - _log.debug("Irresolvable dependency found: %s" % cand_dep) - irresolvable.append(cand_dep) - # remove irresolvable dependency from list of dependencies so we can continue - entry['dependencies'].remove(cand_dep) - else: - _log.info("Robot: resolving dependency %s with %s" % (cand_dep, path)) - # build specs should not be passed down to resolved dependencies, - # to avoid that e.g. --try-toolchain trickles down into the used toolchain itself - hidden = cand_dep.get('hidden', False) - processed_ecs = process_easyconfig(path, validate=not retain_all_deps, hidden=hidden) - - # ensure that selected easyconfig provides required dependency - mods = [spec['ec'].full_mod_name for spec in processed_ecs] - dep_mod_name = ActiveMNS().det_full_module_name(cand_dep) - if not dep_mod_name in mods: - tup = (path, dep_mod_name, mods) - _log.error("easyconfig file %s does not contain module %s (mods: %s)" % tup) - - for ec in processed_ecs: - if not ec in unprocessed + additional: - additional.append(ec) - _log.debug("Added %s as dependency of %s" % (ec, entry)) - else: - mod_name = EasyBuildMNS().det_full_module_name(entry['ec']) - _log.debug("No more candidate dependencies to resolve for %s" % mod_name) - - # add additional (new) easyconfigs to list of stuff to process - unprocessed.extend(additional) - - elif not robot: - # no use in continuing if robot is not enabled, dependencies won't be resolved anyway - irresolvable = [dep for x in unprocessed for dep in x['dependencies']] - break - - if irresolvable: - _log.warning("Irresolvable dependencies (details): %s" % irresolvable) - irresolvable_mods_eb = [EasyBuildMNS().det_full_module_name(dep) for dep in irresolvable] - _log.warning("Irresolvable dependencies (EasyBuild module names): %s" % ', '.join(irresolvable_mods_eb)) - irresolvable_mods = [ActiveMNS().det_full_module_name(dep) for dep in irresolvable] - _log.error('Irresolvable dependencies encountered: %s' % ', '.join(irresolvable_mods)) - - _log.info("Dependency resolution complete, building as follows:\n%s" % ordered_ecs) - return ordered_ecs - - -def print_dry_run(easyconfigs, short=False, build_specs=None): - """ - Print dry run information - @param easyconfigs: list of easyconfig files - @param short: print short output (use a variable for the common prefix) - @param build_specs: dictionary specifying build specifications (e.g. version, toolchain, ...) - """ - lines = [] - if build_option('robot_path') is None: - lines.append("Dry run: printing build status of easyconfigs") - all_specs = easyconfigs - else: - lines.append("Dry run: printing build status of easyconfigs and dependencies") - all_specs = resolve_dependencies(easyconfigs, build_specs=build_specs, retain_all_deps=True) - - unbuilt_specs = skip_available(all_specs, testing=True) - dry_run_fmt = " * [%1s] %s (module: %s)" # markdown compatible (list of items with checkboxes in front) - - listed_ec_paths = [spec['spec'] for spec in easyconfigs] - - var_name = 'CFGS' - common_prefix = det_common_path_prefix([spec['spec'] for spec in all_specs]) - # only allow short if common prefix is long enough - short = short and common_prefix is not None and len(common_prefix) > len(var_name) * 2 - for spec in all_specs: - if spec in unbuilt_specs: - ans = ' ' - elif build_option('force') and spec['spec'] in listed_ec_paths: - ans = 'F' - else: - ans = 'x' - - if spec['ec'].short_mod_name != spec['ec'].full_mod_name: - mod = "%s | %s" % (spec['ec'].mod_subdir, spec['ec'].short_mod_name) - else: - mod = spec['ec'].full_mod_name - - if short: - item = os.path.join('$%s' % var_name, spec['spec'][len(common_prefix) + 1:]) - else: - item = spec['spec'] - lines.append(dry_run_fmt % (ans, item, mod)) - - if short: - # insert after 'Dry run:' message - lines.insert(1, "%s=%s" % (var_name, common_prefix)) - silent = build_option('silent') - print_msg('\n'.join(lines), log=_log, silent=silent, prefix=False) - - def _dep_graph(fn, specs, silent=False): """ Create a dependency graph for the given easyconfigs. @@ -385,6 +216,110 @@ def get_paths_for(subdir="easyconfigs", robot_path=None): return paths +def alt_easyconfig_paths(tmpdir, tweaked_ecs=False, from_pr=False): + """Obtain alternative paths for easyconfig files.""" + # prepend robot path with location where tweaked easyconfigs will be placed + tweaked_ecs_path = None + if tweaked_ecs: + tweaked_ecs_path = os.path.join(tmpdir, 'tweaked_easyconfigs') + + pr_path = None + if from_pr: + # extend robot search path with location where files touch in PR will be downloaded to + pr_path = os.path.join(tmpdir, "files_pr%s" % from_pr) + + return tweaked_ecs_path, pr_path + + +def det_easyconfig_paths(orig_paths, from_pr=None, easyconfigs_pkg_paths=None): + """ + Determine paths to easyconfig files. + @param orig_paths: list of original easyconfig paths + @param from_pr: pull request number to fetch easyconfigs from + @param easyconfigs_pkg_paths: paths to installed easyconfigs package + """ + paths = [] + + if easyconfigs_pkg_paths is None: + easyconfigs_pkg_paths = [] + ignore_dirs = build_option('ignore_dirs') + + if len(orig_paths) == 0: + if from_pr: + pr_files = fetch_easyconfigs_from_pr(from_pr) + paths = [(path, False) for path in pr_files if path.endswith('.eb')] + else: + # look for easyconfigs with relative paths in easybuild-easyconfigs package, + # unless they were found at the given relative paths + if easyconfigs_pkg_paths: + # determine which easyconfigs files need to be found, if any + ecs_to_find = [] + for idx, orig_path in enumerate(orig_paths): + if orig_path == os.path.basename(orig_path) and not os.path.exists(orig_path): + ecs_to_find.append((idx, orig_path)) + _log.debug("List of easyconfig files to find: %s" % ecs_to_find) + + # find missing easyconfigs by walking paths with installed easyconfig files + for path in easyconfigs_pkg_paths: + _log.debug("Looking for missing easyconfig files (%d left) in %s..." % (len(ecs_to_find), path)) + for (subpath, dirnames, filenames) in os.walk(path, topdown=True): + for idx, orig_path in ecs_to_find[:]: + if orig_path in filenames: + full_path = os.path.join(subpath, orig_path) + _log.info("Found %s in %s: %s" % (orig_path, path, full_path)) + orig_paths[idx] = full_path + # if file was found, stop looking for it (first hit wins) + ecs_to_find.remove((idx, orig_path)) + + # stop os.walk insanity as soon as we have all we need (os.walk loop) + if len(ecs_to_find) == 0: + break + + # ignore subdirs specified to be ignored by replacing items in dirnames list used by os.walk + dirnames[:] = [d for d in dirnames if not d in ignore_dirs] + + # stop os.walk insanity as soon as we have all we need (paths loop) + if len(ecs_to_find) == 0: + break + + # indicate that specified paths do not contain generated easyconfig files + paths = [(path, False) for path in orig_paths] + + return paths + + +def parse_easyconfigs(paths): + """ + Parse easyconfig files + @params paths: paths to easyconfigs + """ + build_specs = build_option('build_specs') + ignore_dirs = build_option('ignore_dirs') + try_to_generate = build_option('try_to_generate') + + easyconfigs = [] + generated_ecs = False + for (path, generated) in paths: + path = os.path.abspath(path) + # keep track of whether any files were generated + generated_ecs |= generated + if not os.path.exists(path): + print_error("Can't find path %s" % path) + try: + ec_files = find_easyconfigs(path, ignore_dirs=ignore_dirs) + for ec_file in ec_files: + # only pass build specs when not generating easyconfig files + if try_to_generate: + ecs = process_easyconfig(ec_file) + else: + ecs = process_easyconfig(ec_file, build_specs=build_specs) + easyconfigs.extend(ecs) + except IOError, err: + _log.error("Processing easyconfigs in path %s failed: %s" % (path, err)) + + return easyconfigs, generated_ecs + + def stats_to_str(stats): """ Pretty print build statistics to string. diff --git a/easybuild/framework/easyconfig/tweak.py b/easybuild/framework/easyconfig/tweak.py index c2bfbe8374..6e0e50c290 100644 --- a/easybuild/framework/easyconfig/tweak.py +++ b/easybuild/framework/easyconfig/tweak.py @@ -44,9 +44,9 @@ from easybuild.tools.build_log import print_error, print_msg, print_warning from easybuild.framework.easyconfig.easyconfig import EasyConfig, create_paths, process_easyconfig -from easybuild.framework.easyconfig.tools import resolve_dependencies from easybuild.tools.filetools import read_file, write_file from easybuild.tools.module_naming_scheme.utilities import det_full_ec_version +from easybuild.tools.robot import resolve_dependencies from easybuild.tools.toolchain import DUMMY_TOOLCHAIN_NAME from easybuild.tools.utilities import quote_str diff --git a/easybuild/main.py b/easybuild/main.py index bd5e2f0331..19772ac4c1 100644 --- a/easybuild/main.py +++ b/easybuild/main.py @@ -37,7 +37,6 @@ """ import copy import os -import subprocess import sys import traceback from vsc.utils.missing import any @@ -49,19 +48,18 @@ import easybuild.tools.config as config import easybuild.tools.options as eboptions from easybuild.framework.easyblock import EasyBlock, build_and_install_one -from easybuild.framework.easyconfig.easyconfig import process_easyconfig -from easybuild.framework.easyconfig.tools import dep_graph, get_paths_for, print_dry_run -from easybuild.framework.easyconfig.tools import resolve_dependencies, skip_available +from easybuild.framework.easyconfig.tools import alt_easyconfig_paths, dep_graph, det_easyconfig_paths +from easybuild.framework.easyconfig.tools import get_paths_for, parse_easyconfigs +from easybuild.framework.easyconfig.tools import skip_available from easybuild.framework.easyconfig.tweak import obtain_path, tweak -from easybuild.tools.config import build_option, get_repository, module_classes, get_repositorypath, set_tmpdir -from easybuild.tools.filetools import cleanup, find_easyconfigs, search_file, write_file -from easybuild.tools.github import fetch_easyconfigs_from_pr +from easybuild.tools.config import get_repository, get_repositorypath, set_tmpdir +from easybuild.tools.filetools import cleanup, write_file from easybuild.tools.options import process_software_build_specs -from easybuild.tools.parallelbuild import build_easyconfigs_in_parallel +from easybuild.tools.robot import det_robot_path, print_dry_run, resolve_dependencies, search_easyconfigs +from easybuild.tools.parallelbuild import submit_jobs from easybuild.tools.repository.repository import init_repository -from easybuild.tools.testing import create_test_report, post_easyconfigs_pr_test_report, upload_test_report_as_gist -from easybuild.tools.testing import regtest, session_module_list, session_state -from easybuild.tools.version import this_is_easybuild # from a single location +from easybuild.tools.testing import create_test_report, overall_test_report, regtest, session_module_list, session_state +from easybuild.tools.version import this_is_easybuild _log = None @@ -77,244 +75,6 @@ def log_start(eb_command_line, eb_tmpdir): _log.info("Using %s as temporary directory" % eb_tmpdir) -def alt_easyconfig_paths(tmpdir, tweaked_ecs=False, from_pr=False): - """Obtain alternative paths for easyconfig files.""" - # prepend robot path with location where tweaked easyconfigs will be placed - tweaked_ecs_path = None - if tweaked_ecs: - tweaked_ecs_path = os.path.join(tmpdir, 'tweaked_easyconfigs') - - pr_path = None - if from_pr: - # extend robot search path with location where files touch in PR will be downloaded to - pr_path = os.path.join(tmpdir, "files_pr%s" % from_pr) - - return tweaked_ecs_path, pr_path - - -def det_robot_path(robot_option, easyconfigs_paths, tweaked_ecs_path, pr_path, auto_robot=False): - """Determine robot path.""" - # do not use robot option directly, it's not a list instance (and it shouldn't be modified) - robot_path = [] - if not robot_option is None: - if robot_option: - robot_path = list(robot_option) - _log.info("Using robot path(s): %s" % robot_path) - else: - # if options.robot is not None and False, easyconfigs pkg install path could not be found (see options.py) - _log.error("No robot paths specified, and unable to determine easybuild-easyconfigs install path.") - - if auto_robot: - robot_path.extend(easyconfigs_paths) - _log.info("Extended list of robot paths with paths for installed easyconfigs: %s" % robot_path) - - if tweaked_ecs_path is not None: - robot_path.insert(0, tweaked_ecs_path) - _log.info("Prepended list of robot search paths with %s: %s" % (tweaked_ecs_path, robot_path)) - - if pr_path is not None: - robot_path.insert(0, pr_path) - _log.info("Prepended list of robot search paths with %s: %s" % (pr_path, robot_path)) - - return robot_path - - -def configure(options, config_options_dict, build_options): - """Configure EasyBuild.""" - # initialise the easybuild configuration - config.init(options, config_options_dict) - - # building a dependency graph implies force, so that all dependencies are retained - # and also skips validation of easyconfigs (e.g. checking os dependencies) - retain_all_deps = False - if options.dep_graph: - _log.info("Enabling force to generate dependency graph.") - options.force = True - retain_all_deps = True - - if options.dep_graph or options.dry_run or options.dry_run_short: - options.ignore_osdeps = True - - build_options.update({ - 'aggregate_regtest': options.aggregate_regtest, - 'allow_modules_tool_mismatch': options.allow_modules_tool_mismatch, - 'check_osdeps': not options.ignore_osdeps, - 'filter_deps': options.filter_deps, - 'cleanup_builddir': options.cleanup_builddir, - 'debug': options.debug, - 'dry_run': options.dry_run or options.dry_run_short, - 'dump_test_report': options.dump_test_report, - 'easyblock': options.easyblock, - 'experimental': options.experimental, - 'force': options.force, - 'github_user': options.github_user, - 'group': options.group, - 'hidden': options.hidden, - 'ignore_dirs': options.ignore_dirs, - 'modules_footer': options.modules_footer, - 'only_blocks': options.only_blocks, - 'optarch': options.optarch, - 'recursive_mod_unload': options.recursive_module_unload, - 'regtest_output_dir': options.regtest_output_dir, - 'retain_all_deps': retain_all_deps, - 'sequential': options.sequential, - 'set_gid_bit': options.set_gid_bit, - 'skip': options.skip, - 'skip_test_cases': options.skip_test_cases, - 'sticky_bit': options.sticky_bit, - 'stop': options.stop, - 'suffix_modules_path': options.suffix_modules_path, - 'test_report_env_filter': options.test_report_env_filter, - 'umask': options.umask, - 'upload_test_report': options.upload_test_report, - 'valid_module_classes': module_classes(), - 'valid_stops': [x[0] for x in EasyBlock.get_steps()], - 'validate': not options.force, - }) - config.init_build_options(build_options) - - -def search(query, short=False): - """Search for easyconfigs, if a query is provided.""" - search_path = [os.getcwd()] - robot_path = build_option('robot_path') - if robot_path: - search_path = robot_path - ignore_dirs = config.build_option('ignore_dirs') - silent = config.build_option('silent') - search_file(search_path, query, short=short, ignore_dirs=ignore_dirs, silent=silent) - - -def det_easyconfig_paths(orig_paths, from_pr=None, easyconfigs_pkg_paths=None): - """ - Determine paths to easyconfig files. - @param orig_paths: list of original easyconfig paths - @param from_pr: pull request number to fetch easyconfigs from - @param easyconfigs_pkg_paths: paths to installed easyconfigs package - """ - paths = [] - - if easyconfigs_pkg_paths is None: - easyconfigs_pkg_paths = [] - build_specs = build_option('build_specs') - ignore_dirs = build_option('ignore_dirs') - robot_path = build_option('robot_path') - testing = build_option('testing') - try_to_generate = build_option('try_to_generate') - - if len(orig_paths) == 0: - if from_pr: - pr_files = fetch_easyconfigs_from_pr(from_pr) - paths = [(path, False) for path in pr_files if path.endswith('.eb')] - elif 'name' in build_specs: - paths = [obtain_path(build_specs, robot_path, try_to_generate=try_to_generate, - exit_on_error=not testing)] - else: - # look for easyconfigs with relative paths in easybuild-easyconfigs package, - # unless they were found at the given relative paths - if easyconfigs_pkg_paths: - # determine which easyconfigs files need to be found, if any - ecs_to_find = [] - for idx, orig_path in enumerate(orig_paths): - if orig_path == os.path.basename(orig_path) and not os.path.exists(orig_path): - ecs_to_find.append((idx, orig_path)) - _log.debug("List of easyconfig files to find: %s" % ecs_to_find) - - # find missing easyconfigs by walking paths with installed easyconfig files - for path in easyconfigs_pkg_paths: - _log.debug("Looking for missing easyconfig files (%d left) in %s..." % (len(ecs_to_find), path)) - for (subpath, dirnames, filenames) in os.walk(path, topdown=True): - for idx, orig_path in ecs_to_find[:]: - if orig_path in filenames: - full_path = os.path.join(subpath, orig_path) - _log.info("Found %s in %s: %s" % (orig_path, path, full_path)) - orig_paths[idx] = full_path - # if file was found, stop looking for it (first hit wins) - ecs_to_find.remove((idx, orig_path)) - - # stop os.walk insanity as soon as we have all we need (os.walk loop) - if len(ecs_to_find) == 0: - break - - # ignore subdirs specified to be ignored by replacing items in dirnames list used by os.walk - dirnames[:] = [d for d in dirnames if not d in ignore_dirs] - - # stop os.walk insanity as soon as we have all we need (paths loop) - if len(ecs_to_find) == 0: - break - - # indicate that specified paths do not contain generated easyconfig files - paths = [(path, False) for path in orig_paths] - - return paths - - -def read_easyconfigs(paths): - """ - Read/parse easyconfigs - @params paths: paths to easyconfigs - """ - build_specs = build_option('build_specs') - ignore_dirs = build_option('ignore_dirs') - try_to_generate = build_option('try_to_generate') - tweaked_ecs_path = build_option('tweaked_ecs_path') - - easyconfigs = [] - generated_ecs = False - for (path, generated) in paths: - path = os.path.abspath(path) - # keep track of whether any files were generated - generated_ecs |= generated - if not os.path.exists(path): - print_error("Can't find path %s" % path) - try: - ec_files = find_easyconfigs(path, ignore_dirs=ignore_dirs) - for ec_file in ec_files: - # only pass build specs when not generating easyconfig files - if try_to_generate: - ecs = process_easyconfig(ec_file) - else: - ecs = process_easyconfig(ec_file, build_specs=build_specs) - easyconfigs.extend(ecs) - except IOError, err: - _log.error("Processing easyconfigs in path %s failed: %s" % (path, err)) - - # tweak obtained easyconfig files, if requested - # don't try and tweak anything if easyconfigs were generated, since building a full dep graph will fail - # if easyconfig files for the dependencies are not available - if try_to_generate and build_specs and not generated_ecs: - easyconfigs = tweak(easyconfigs, build_specs, targetdir=tweaked_ecs_path) - - return easyconfigs - - -def submit_jobs(ordered_ecs, cmd_line_opts, testing=False): - """ - Submit jobs. - @param ordered_ecs: list of easyconfigs, in the order they should be processed - @param cmd_line_opts: list of command line options (in 'longopt=value' form) - """ - curdir = os.getcwd() - - # the options to ignore (help options can't reach here) - ignore_opts = ['robot', 'job'] - - # generate_cmd_line returns the options in form --longopt=value - opts = [x for x in cmd_line_opts if not x.split('=')[0] in ['--%s' % y for y in ignore_opts]] - - quoted_opts = subprocess.list2cmdline(opts) - - command = "unset TMPDIR && cd %s && eb %%(spec)s %s" % (curdir, quoted_opts) - _log.info("Command template for jobs: %s" % command) - if not testing: - jobs = build_easyconfigs_in_parallel(command, ordered_ecs) - txt = ["List of submitted jobs:"] - txt.extend(["%s (%s): %s" % (job.name, job.module, job.jobid) for job in jobs]) - txt.append("(%d jobs submitted)" % len(jobs)) - - print_msg("Submitted parallel build jobs, exiting now: %s" % '\n'.join(txt), log=_log) - - def build_and_install_software(ecs, init_session_state, exit_on_failure=True): """Build and install software for all provided parsed easyconfig files.""" # obtain a copy of the starting environment so each build can start afresh @@ -361,38 +121,6 @@ def build_and_install_software(ecs, init_session_state, exit_on_failure=True): return res -def test_report(ecs_with_res, orig_cnt, success, msg, init_session_state): - """ - Upload/dump test report - @param ecs_with_res: processed easyconfigs with build result (success/failure) - @param orig_cnt: number of original easyconfig paths - @param success: boolean indicating whether all builds were successful - @param msg: message to be included in test report - @param init_session_state: initial session state info to include in test report - """ - dump_path = build_option('dump_test_report') - pr_nr = build_option('from_pr') - upload = build_option('upload_test_report') - - if upload: - msg = msg + " (%d easyconfigs in this PR)" % orig_cnt - test_report = create_test_report(msg, ecs_with_res, init_session_state, pr_nr=pr_nr, gist_log=True) - if pr_nr: - # upload test report to gist and issue a comment in the PR to notify - msg = post_easyconfigs_pr_test_report(pr_nr, test_report, msg, init_session_state, success) - print_msg(msg) - else: - # only upload test report as a gist - gist_url = upload_test_report_as_gist(test_report) - print_msg("Test report uploaded to %s" % gist_url) - else: - test_report = create_test_report(msg, ecs_with_res, init_session_state) - _log.debug("Test report: %s" % test_report) - if dump_path is not None: - write_file(dump_path, test_report) - _log.info("Test report dumped to %s" % dump_path) - - def main(testing_data=(None, None, None)): """ Main function: parse command line options, and act accordingly. @@ -458,11 +186,12 @@ def main(testing_data=(None, None, None)): 'pr_path': pr_path, 'robot_path': robot_path, 'silent': testing, - 'testing': testing, 'try_to_generate': try_to_generate, - 'tweaked_ecs_path': tweaked_ecs_path, + 'valid_stops': [x[0] for x in EasyBlock.get_steps()], } - configure(options, config_options_dict, build_options) + # initialise the EasyBuild configuration & build options + config.init(options, config_options_dict) + config.init_build_options(build_options=build_options, cmdline_options=options) # update session state eb_config = eb_go.generate_cmd_line(add_default=True) @@ -474,14 +203,18 @@ def main(testing_data=(None, None, None)): # search for easyconfigs, if a query is specified query = options.search or options.search_short if query: - search(query, short=not options.search) + search_easyconfigs(query, short=not options.search) # determine paths to easyconfigs paths = det_easyconfig_paths(orig_paths, options.from_pr, easyconfigs_pkg_paths) - if not paths and not any([options.aggregate_regtest, options.search, options.search_short, options.regtest]): - print_error(("Please provide one or multiple easyconfig files, or use software build " - "options to make EasyBuild search for easyconfigs"), - log=_log, opt_parser=eb_go.parser, exit_on_error=not testing) + if not paths: + if 'name' in build_specs: + paths = [obtain_path(build_specs, robot_path, try_to_generate=try_to_generate, + exit_on_error=not testing)] + elif not any([options.aggregate_regtest, options.search, options.search_short, options.regtest]): + print_error(("Please provide one or multiple easyconfig files, or use software build " + "options to make EasyBuild search for easyconfigs"), + log=_log, opt_parser=eb_go.parser, exit_on_error=not testing) _log.debug("Paths: %s" % paths) # run regtest @@ -494,7 +227,13 @@ def main(testing_data=(None, None, None)): sys.exit(31) # exit -> 3x1t -> 31 # read easyconfig files - easyconfigs = read_easyconfigs(paths) + easyconfigs, generated_ecs = parse_easyconfigs(paths) + + # tweak obtained easyconfig files, if requested + # don't try and tweak anything if easyconfigs were generated, since building a full dep graph will fail + # if easyconfig files for the dependencies are not available + if try_to_generate and build_specs and not generated_ecs: + easyconfigs = tweak(easyconfigs, build_specs, targetdir=tweaked_ecs_path) # dry_run: print all easyconfigs and dependencies, and whether they are already built if options.dry_run or options.dry_run_short: @@ -545,7 +284,7 @@ def main(testing_data=(None, None, None)): repo.cleanup() # dump/upload overall test report - test_report(ecs_with_res, len(paths), overall_success, success_msg, init_session_state) + overall_test_report(ecs_with_res, len(paths), overall_success, success_msg, init_session_state) print_msg(success_msg, log=_log, silent=testing) @@ -559,9 +298,9 @@ def main(testing_data=(None, None, None)): if overall_success: cleanup(logfile, eb_tmpdir, testing) + if __name__ == "__main__": try: main() except EasyBuildError, e: - sys.stderr.write('ERROR: %s\n' % e.msg) - sys.exit(1) + print_error("ERROR: %s\n" % e.msg) diff --git a/easybuild/tools/build_log.py b/easybuild/tools/build_log.py index e4873068ae..3ceb28bae3 100644 --- a/easybuild/tools/build_log.py +++ b/easybuild/tools/build_log.py @@ -190,10 +190,9 @@ def print_error(message, log=None, exitCode=1, opt_parser=None, exit_on_error=Tr """ if exit_on_error: if not silent: - print_msg("ERROR: %s\n" % message) if opt_parser: opt_parser.print_shorthelp() - print_msg("ERROR: %s\n" % message) + sys.stderr.write("ERROR: %s\n" % message) sys.exit(exitCode) elif log is not None: log.error(message) diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py index e3f67779f7..16899f1d06 100644 --- a/easybuild/tools/config.py +++ b/easybuild/tools/config.py @@ -104,9 +104,7 @@ 'stop': None, 'suffix_modules_path': None, 'test_report_env_filter': None, - 'testing': False, 'try_to_generate': False, - 'tweaked_ecs_path': None, 'umask': None, 'upload_test_report': False, 'valid_module_classes': None, @@ -329,9 +327,7 @@ def init(options, config_options_dict): Variables are read in this order of preference: generaloption > legacy environment > legacy config file """ tmpdict = {} - if SUPPORT_OLDSTYLE: - _log.deprecated('oldstyle init with modifications to support oldstyle options', '2.0') tmpdict.update(oldstyle_init(options.config)) @@ -373,12 +369,67 @@ def init(options, config_options_dict): _log.debug("Config variables: %s" % variables) -def init_build_options(build_options=None): +def init_build_options(build_options=None, cmdline_options=None): """Initialize build options.""" + # building a dependency graph implies force, so that all dependencies are retained + # and also skips validation of easyconfigs (e.g. checking os dependencies) + + active_build_options = {} + + if cmdline_options is not None: + retain_all_deps = False + if cmdline_options.dep_graph: + _log.info("Enabling force to generate dependency graph.") + cmdline_options.force = True + retain_all_deps = True + + if cmdline_options.dep_graph or cmdline_options.dry_run or cmdline_options.dry_run_short: + _log.info("Ignoring OS dependencies for --dep-graph/--dry-run") + cmdline_options.ignore_osdeps = True + + active_build_options.update({ + 'aggregate_regtest': cmdline_options.aggregate_regtest, + 'allow_modules_tool_mismatch': cmdline_options.allow_modules_tool_mismatch, + 'check_osdeps': not cmdline_options.ignore_osdeps, + 'filter_deps': cmdline_options.filter_deps, + 'cleanup_builddir': cmdline_options.cleanup_builddir, + 'debug': cmdline_options.debug, + 'dry_run': cmdline_options.dry_run or cmdline_options.dry_run_short, + 'dump_test_report': cmdline_options.dump_test_report, + 'easyblock': cmdline_options.easyblock, + 'experimental': cmdline_options.experimental, + 'force': cmdline_options.force, + 'github_user': cmdline_options.github_user, + 'group': cmdline_options.group, + 'hidden': cmdline_options.hidden, + 'ignore_dirs': cmdline_options.ignore_dirs, + 'modules_footer': cmdline_options.modules_footer, + 'only_blocks': cmdline_options.only_blocks, + 'optarch': cmdline_options.optarch, + 'recursive_mod_unload': cmdline_options.recursive_module_unload, + 'regtest_output_dir': cmdline_options.regtest_output_dir, + 'retain_all_deps': retain_all_deps, + 'sequential': cmdline_options.sequential, + 'set_gid_bit': cmdline_options.set_gid_bit, + 'skip': cmdline_options.skip, + 'skip_test_cases': cmdline_options.skip_test_cases, + 'sticky_bit': cmdline_options.sticky_bit, + 'stop': cmdline_options.stop, + 'suffix_modules_path': cmdline_options.suffix_modules_path, + 'test_report_env_filter': cmdline_options.test_report_env_filter, + 'umask': cmdline_options.umask, + 'upload_test_report': cmdline_options.upload_test_report, + 'validate': not cmdline_options.force, + 'valid_module_classes': module_classes(), + }) + + if build_options is not None: + active_build_options.update(build_options) + # seed in defaults to make sure all build options are defined, and that build_option() doesn't fail on valid keys bo = copy.deepcopy(DEFAULT_BUILD_OPTIONS) - if build_options is not None: - bo.update(build_options) + bo.update(active_build_options) + # BuildOptions is a singleton, so any future calls to BuildOptions will yield the same instance return BuildOptions(bo) diff --git a/easybuild/tools/filetools.py b/easybuild/tools/filetools.py index 91b417fddb..4e1ece1bc9 100644 --- a/easybuild/tools/filetools.py +++ b/easybuild/tools/filetools.py @@ -33,7 +33,6 @@ @author: Toon Willems (Ghent University) @author: Ward Poelmans (Ghent University) """ -import errno import os import re import shutil diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index 4d15565caf..915f8909bb 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -174,7 +174,6 @@ def override_options(self): None, 'store_true', False, 'p'), 'set-gid-bit': ("Set group ID bit on newly created directories", None, 'store_true', False), 'sticky-bit': ("Set sticky bit on newly created directories", None, 'store_true', False), - 'skip-test-cases': ("Skip running test cases", None, 'store_true', False, 't'), 'umask': ("umask to use (e.g. '022'); non-user write permissions on install directories are removed", None, 'store', None), 'optarch': ("Set architecture optimization, overriding native architecture optimizations", @@ -277,6 +276,20 @@ def informative_options(self): self.log.debug("informative_options: descr %s opts %s" % (descr, opts)) self.add_group_parser(opts, descr) + def testing_options(self): + # testing options + descr = ("Testing options", "Run application-specific test cases known to EasyBuild") + + opts = OrderedDict({ + 'list-test-cases': ("List known test cases", None, 'store_true', False), + 'run-test-cases': ("Run (specified) test cases (note: requires module to be available already)", + None, 'store_or_None', False, 't'), + 'skip-test-cases': ("Skip running test cases after build/install", None, 'store_true', False), + }) + + self.log.debug("testing_options: descr %s opts %s" % (descr, opts)) + self.add_group_parser(opts, descr) + def regtest_options(self): # regression test options descr = ("Regression test options", "Run and control an EasyBuild regression test.") diff --git a/easybuild/tools/parallelbuild.py b/easybuild/tools/parallelbuild.py index 92967dae82..0e0743026e 100644 --- a/easybuild/tools/parallelbuild.py +++ b/easybuild/tools/parallelbuild.py @@ -34,11 +34,12 @@ """ import math import os +import subprocess import easybuild.tools.config as config from easybuild.framework.easyblock import get_easyblock_instance from easybuild.framework.easyconfig.easyconfig import ActiveMNS -from easybuild.tools.build_log import EasyBuildError +from easybuild.tools.build_log import EasyBuildError, print_msg from easybuild.tools.config import get_repository, get_repositorypath from easybuild.tools.module_naming_scheme.utilities import det_full_ec_version from easybuild.tools.pbs_job import PbsJob, connect_to_server, disconnect_from_server, get_ppn @@ -118,6 +119,33 @@ def tokey(dep): return jobs +def submit_jobs(ordered_ecs, cmd_line_opts, testing=False): + """ + Submit jobs. + @param ordered_ecs: list of easyconfigs, in the order they should be processed + @param cmd_line_opts: list of command line options (in 'longopt=value' form) + """ + curdir = os.getcwd() + + # the options to ignore (help options can't reach here) + ignore_opts = ['robot', 'job'] + + # generate_cmd_line returns the options in form --longopt=value + opts = [x for x in cmd_line_opts if not x.split('=')[0] in ['--%s' % y for y in ignore_opts]] + + quoted_opts = subprocess.list2cmdline(opts) + + command = "unset TMPDIR && cd %s && eb %%(spec)s %s" % (curdir, quoted_opts) + _log.info("Command template for jobs: %s" % command) + if not testing: + jobs = build_easyconfigs_in_parallel(command, ordered_ecs) + txt = ["List of submitted jobs:"] + txt.extend(["%s (%s): %s" % (job.name, job.module, job.jobid) for job in jobs]) + txt.append("(%d jobs submitted)" % len(jobs)) + + print_msg("Submitted parallel build jobs, exiting now: %s" % '\n'.join(txt), log=_log) + + def create_job(build_command, easyconfig, output_dir=None, conn=None, ppn=None): """ Creates a job, to build a *single* easyconfig diff --git a/easybuild/tools/robot.py b/easybuild/tools/robot.py new file mode 100644 index 0000000000..ef2cad1e77 --- /dev/null +++ b/easybuild/tools/robot.py @@ -0,0 +1,255 @@ +# # +# Copyright 2009-2014 Ghent University +# +# This file is part of EasyBuild, +# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en), +# with support of Ghent University (http://ugent.be/hpc), +# the Flemish Supercomputer Centre (VSC) (https://vscentrum.be/nl/en), +# the Hercules foundation (http://www.herculesstichting.be/in_English) +# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en). +# +# http://github.com/hpcugent/easybuild +# +# EasyBuild is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation v2. +# +# EasyBuild is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with EasyBuild. If not, see . +# # +""" +Dependency resolution functionality, a.k.a. robot. + +@author: Stijn De Weirdt (Ghent University) +@author: Dries Verdegem (Ghent University) +@author: Kenneth Hoste (Ghent University) +@author: Pieter De Baets (Ghent University) +@author: Jens Timmerman (Ghent University) +@author: Toon Willems (Ghent University) +@author: Ward Poelmans (Ghent University) +""" +import os +from vsc.utils import fancylogger + +from easybuild.framework.easyconfig.easyconfig import ActiveMNS, process_easyconfig, robot_find_easyconfig +from easybuild.framework.easyconfig.tools import find_resolved_modules, skip_available +from easybuild.tools.build_log import print_msg +from easybuild.tools.config import build_option +from easybuild.tools.filetools import det_common_path_prefix, search_file +from easybuild.tools.module_naming_scheme.easybuild_mns import EasyBuildMNS +from easybuild.tools.module_naming_scheme.utilities import det_full_ec_version +from easybuild.tools.modules import modules_tool + + +_log = fancylogger.getLogger('tools.robot', fname=False) + + +def det_robot_path(robot_option, easyconfigs_paths, tweaked_ecs_path, pr_path, auto_robot=False): + """Determine robot path.""" + # do not use robot option directly, it's not a list instance (and it shouldn't be modified) + robot_path = [] + if not robot_option is None: + if robot_option: + robot_path = list(robot_option) + _log.info("Using robot path(s): %s" % robot_path) + else: + # if options.robot is not None and False, easyconfigs pkg install path could not be found (see options.py) + _log.error("No robot paths specified, and unable to determine easybuild-easyconfigs install path.") + + if auto_robot: + robot_path.extend(easyconfigs_paths) + _log.info("Extended list of robot paths with paths for installed easyconfigs: %s" % robot_path) + + if tweaked_ecs_path is not None: + robot_path.insert(0, tweaked_ecs_path) + _log.info("Prepended list of robot search paths with %s: %s" % (tweaked_ecs_path, robot_path)) + + if pr_path is not None: + robot_path.insert(0, pr_path) + _log.info("Prepended list of robot search paths with %s: %s" % (pr_path, robot_path)) + + return robot_path + + +def print_dry_run(easyconfigs, short=False, build_specs=None): + """ + Print dry run information + @param easyconfigs: list of easyconfig files + @param short: print short output (use a variable for the common prefix) + @param build_specs: dictionary specifying build specifications (e.g. version, toolchain, ...) + """ + lines = [] + if build_option('robot_path') is None: + lines.append("Dry run: printing build status of easyconfigs") + all_specs = easyconfigs + else: + lines.append("Dry run: printing build status of easyconfigs and dependencies") + all_specs = resolve_dependencies(easyconfigs, build_specs=build_specs, retain_all_deps=True) + + unbuilt_specs = skip_available(all_specs, testing=True) + dry_run_fmt = " * [%1s] %s (module: %s)" # markdown compatible (list of items with checkboxes in front) + + listed_ec_paths = [spec['spec'] for spec in easyconfigs] + + var_name = 'CFGS' + common_prefix = det_common_path_prefix([spec['spec'] for spec in all_specs]) + # only allow short if common prefix is long enough + short = short and common_prefix is not None and len(common_prefix) > len(var_name) * 2 + for spec in all_specs: + if spec in unbuilt_specs: + ans = ' ' + elif build_option('force') and spec['spec'] in listed_ec_paths: + ans = 'F' + else: + ans = 'x' + + if spec['ec'].short_mod_name != spec['ec'].full_mod_name: + mod = "%s | %s" % (spec['ec'].mod_subdir, spec['ec'].short_mod_name) + else: + mod = spec['ec'].full_mod_name + + if short: + item = os.path.join('$%s' % var_name, spec['spec'][len(common_prefix) + 1:]) + else: + item = spec['spec'] + lines.append(dry_run_fmt % (ans, item, mod)) + + if short: + # insert after 'Dry run:' message + lines.insert(1, "%s=%s" % (var_name, common_prefix)) + silent = build_option('silent') + print_msg('\n'.join(lines), log=_log, silent=silent, prefix=False) + + +def resolve_dependencies(unprocessed, build_specs=None, retain_all_deps=False): + """ + Work through the list of easyconfigs to determine an optimal order + @param unprocessed: list of easyconfigs + @param build_specs: dictionary specifying build specifications (e.g. version, toolchain, ...) + """ + + robot = build_option('robot_path') + + retain_all_deps = build_option('retain_all_deps') or retain_all_deps + if retain_all_deps: + # assume that no modules are available when forced, to retain all dependencies + avail_modules = [] + _log.info("Forcing all dependencies to be retained.") + else: + # Get a list of all available modules (format: [(name, installversion), ...]) + avail_modules = modules_tool().available() + + if len(avail_modules) == 0: + _log.warning("No installed modules. Your MODULEPATH is probably incomplete: %s" % os.getenv('MODULEPATH')) + + ordered_ecs = [] + # all available modules can be used for resolving dependencies except those that will be installed + being_installed = [p['full_mod_name'] for p in unprocessed] + avail_modules = [m for m in avail_modules if not m in being_installed] + + _log.debug('unprocessed before resolving deps: %s' % unprocessed) + + # resolve all dependencies, put a safeguard in place to avoid an infinite loop (shouldn't occur though) + irresolvable = [] + loopcnt = 0 + maxloopcnt = 10000 + while unprocessed: + # make sure this stops, we really don't want to get stuck in an infinite loop + loopcnt += 1 + if loopcnt > maxloopcnt: + tup = (maxloopcnt, unprocessed, irresolvable) + msg = "Maximum loop cnt %s reached, so quitting (unprocessed: %s, irresolvable: %s)" % tup + _log.error(msg) + + # first try resolving dependencies without using external dependencies + last_processed_count = -1 + while len(avail_modules) > last_processed_count: + last_processed_count = len(avail_modules) + more_ecs, unprocessed, avail_modules = find_resolved_modules(unprocessed, avail_modules) + for ec in more_ecs: + if not ec['full_mod_name'] in [x['full_mod_name'] for x in ordered_ecs]: + ordered_ecs.append(ec) + + # robot: look for existing dependencies, add them + if robot and unprocessed: + + # rely on EasyBuild module naming scheme when resolving dependencies, since we know that will + # generate sensible module names that include the necessary information for the resolution to work + # (name, version, toolchain, versionsuffix) + being_installed = [EasyBuildMNS().det_full_module_name(p['ec']) for p in unprocessed] + + additional = [] + for i, entry in enumerate(unprocessed): + # do not choose an entry that is being installed in the current run + # if they depend, you probably want to rebuild them using the new dependency + deps = entry['dependencies'] + candidates = [d for d in deps if not EasyBuildMNS().det_full_module_name(d) in being_installed] + if len(candidates) > 0: + cand_dep = candidates[0] + # find easyconfig, might not find any + _log.debug("Looking for easyconfig for %s" % str(cand_dep)) + # note: robot_find_easyconfig may return None + path = robot_find_easyconfig(cand_dep['name'], det_full_ec_version(cand_dep)) + + if path is None: + # no easyconfig found for dependency, add to list of irresolvable dependencies + if cand_dep not in irresolvable: + _log.debug("Irresolvable dependency found: %s" % cand_dep) + irresolvable.append(cand_dep) + # remove irresolvable dependency from list of dependencies so we can continue + entry['dependencies'].remove(cand_dep) + else: + _log.info("Robot: resolving dependency %s with %s" % (cand_dep, path)) + # build specs should not be passed down to resolved dependencies, + # to avoid that e.g. --try-toolchain trickles down into the used toolchain itself + hidden = cand_dep.get('hidden', False) + processed_ecs = process_easyconfig(path, validate=not retain_all_deps, hidden=hidden) + + # ensure that selected easyconfig provides required dependency + mods = [spec['ec'].full_mod_name for spec in processed_ecs] + dep_mod_name = ActiveMNS().det_full_module_name(cand_dep) + if not dep_mod_name in mods: + tup = (path, dep_mod_name, mods) + _log.error("easyconfig file %s does not contain module %s (mods: %s)" % tup) + + for ec in processed_ecs: + if not ec in unprocessed + additional: + additional.append(ec) + _log.debug("Added %s as dependency of %s" % (ec, entry)) + else: + mod_name = EasyBuildMNS().det_full_module_name(entry['ec']) + _log.debug("No more candidate dependencies to resolve for %s" % mod_name) + + # add additional (new) easyconfigs to list of stuff to process + unprocessed.extend(additional) + + elif not robot: + # no use in continuing if robot is not enabled, dependencies won't be resolved anyway + irresolvable = [dep for x in unprocessed for dep in x['dependencies']] + break + + if irresolvable: + _log.warning("Irresolvable dependencies (details): %s" % irresolvable) + irresolvable_mods_eb = [EasyBuildMNS().det_full_module_name(dep) for dep in irresolvable] + _log.warning("Irresolvable dependencies (EasyBuild module names): %s" % ', '.join(irresolvable_mods_eb)) + irresolvable_mods = [ActiveMNS().det_full_module_name(dep) for dep in irresolvable] + _log.error('Irresolvable dependencies encountered: %s' % ', '.join(irresolvable_mods)) + + _log.info("Dependency resolution complete, building as follows:\n%s" % ordered_ecs) + return ordered_ecs + + +def search_easyconfigs(query, short=False): + """Search for easyconfigs, if a query is provided.""" + search_path = [os.getcwd()] + robot_path = build_option('robot_path') + if robot_path: + search_path = robot_path + ignore_dirs = build_option('ignore_dirs') + silent = build_option('silent') + search_file(search_path, query, short=short, ignore_dirs=ignore_dirs, silent=silent) diff --git a/easybuild/tools/testing.py b/easybuild/tools/testing.py index fa579e2107..0914b8c856 100644 --- a/easybuild/tools/testing.py +++ b/easybuild/tools/testing.py @@ -42,15 +42,16 @@ import easybuild.tools.config as config from easybuild.framework.easyblock import build_easyconfigs -from easybuild.framework.easyconfig.tools import process_easyconfig, resolve_dependencies +from easybuild.framework.easyconfig.tools import process_easyconfig from easybuild.framework.easyconfig.tools import skip_available -from easybuild.tools.build_log import EasyBuildError +from easybuild.tools.build_log import EasyBuildError, print_msg from easybuild.tools.config import build_option -from easybuild.tools.filetools import find_easyconfigs, mkdir, read_file +from easybuild.tools.filetools import find_easyconfigs, mkdir, read_file, write_file from easybuild.tools.github import create_gist, post_comment_in_issue from easybuild.tools.jenkins import aggregate_xml_in_dirs from easybuild.tools.modules import modules_tool from easybuild.tools.parallelbuild import build_easyconfigs_in_parallel +from easybuild.tools.robot import resolve_dependencies from easybuild.tools.systemtools import get_system_info from easybuild.tools.version import FRAMEWORK_VERSION, EASYBLOCKS_VERSION from vsc.utils import fancylogger @@ -299,3 +300,35 @@ def post_easyconfigs_pr_test_report(pr_nr, test_report, msg, init_session_state, msg = "Test report uploaded to %s and mentioned in a comment in easyconfigs PR#%s" % (gist_url, pr_nr) return msg + + +def overall_test_report(ecs_with_res, orig_cnt, success, msg, init_session_state): + """ + Upload/dump overall test report + @param ecs_with_res: processed easyconfigs with build result (success/failure) + @param orig_cnt: number of original easyconfig paths + @param success: boolean indicating whether all builds were successful + @param msg: message to be included in test report + @param init_session_state: initial session state info to include in test report + """ + dump_path = build_option('dump_test_report') + pr_nr = build_option('from_pr') + upload = build_option('upload_test_report') + + if upload: + msg = msg + " (%d easyconfigs in this PR)" % orig_cnt + test_report = create_test_report(msg, ecs_with_res, init_session_state, pr_nr=pr_nr, gist_log=True) + if pr_nr: + # upload test report to gist and issue a comment in the PR to notify + msg = post_easyconfigs_pr_test_report(pr_nr, test_report, msg, init_session_state, success) + print_msg(msg) + else: + # only upload test report as a gist + gist_url = upload_test_report_as_gist(test_report) + print_msg("Test report uploaded to %s" % gist_url) + else: + test_report = create_test_report(msg, ecs_with_res, init_session_state) + _log.debug("Test report: %s" % test_report) + if dump_path is not None: + write_file(dump_path, test_report) + _log.info("Test report dumped to %s" % dump_path) diff --git a/test/framework/parallelbuild.py b/test/framework/parallelbuild.py index 395b4c127a..637d56061e 100644 --- a/test/framework/parallelbuild.py +++ b/test/framework/parallelbuild.py @@ -32,9 +32,10 @@ from unittest import TestLoader, main from vsc.utils.fancylogger import setLogLevelDebug, logToScreen -from easybuild.framework.easyconfig.tools import process_easyconfig, resolve_dependencies +from easybuild.framework.easyconfig.tools import process_easyconfig from easybuild.tools import config, parallelbuild from easybuild.tools.parallelbuild import PbsJob, build_easyconfigs_in_parallel +from easybuild.tools.robot import resolve_dependencies def mock(*args, **kwargs): diff --git a/test/framework/robot.py b/test/framework/robot.py index 6b83103e42..7beee4cca1 100644 --- a/test/framework/robot.py +++ b/test/framework/robot.py @@ -35,13 +35,16 @@ from unittest import main as unittestmain import easybuild.framework.easyconfig.tools as ectools -from easybuild.framework.easyconfig.tools import resolve_dependencies, skip_available +import easybuild.tools.robot as robot +from easybuild.framework.easyconfig.tools import skip_available from easybuild.tools import config, modules from easybuild.tools.build_log import EasyBuildError +from easybuild.tools.robot import resolve_dependencies from test.framework.utilities import find_full_path ORIG_MODULES_TOOL = modules.modules_tool -ORIG_MAIN_MODULES_TOOL = ectools.modules_tool +ORIG_ECTOOLS_MODULES_TOOL = ectools.modules_tool +ORIG_ROBOT_MODULES_TOOL = robot.modules_tool ORIG_MODULE_FUNCTION = os.environ.get('module', None) @@ -82,6 +85,7 @@ def setUp(self): # replace Modules class with something we have control over config.modules_tool = mock_module ectools.modules_tool = mock_module + robot.modules_tool = mock_module os.environ['module'] = "() { eval `/bin/echo $*`\n}" self.base_easyconfig_dir = find_full_path(os.path.join("test", "framework", "easyconfigs")) @@ -147,7 +151,9 @@ def test_resolve_dependencies(self): self.assertEqual(len(res), 4) # hidden dep toy/.0.0-deps (+1) depends on (fake) ictce/4.1.13 (+1) self.assertEqual('gzip/1.4', res[0]['full_mod_name']) self.assertEqual('foo/1.2.3', res[-1]['full_mod_name']) - self.assertTrue('toy/.0.0-deps' in [ec['full_mod_name'] for ec in res]) + full_mod_names = [ec['full_mod_name'] for ec in res] + self.assertTrue('toy/.0.0-deps' in full_mod_names) + self.assertTrue('ictce/4.1.13' in full_mod_names) # here we have included a dependency in the easyconfig list easyconfig['full_mod_name'] = 'gzip/1.4' @@ -276,7 +282,8 @@ def tearDown(self): super(RobotTest, self).tearDown() config.modules_tool = ORIG_MODULES_TOOL - ectools.modules_tool = ORIG_MAIN_MODULES_TOOL + ectools.modules_tool = ORIG_ECTOOLS_MODULES_TOOL + robot.modules_tool = ORIG_ROBOT_MODULES_TOOL if ORIG_MODULE_FUNCTION is not None: os.environ['module'] = ORIG_MODULE_FUNCTION else: diff --git a/test/framework/utilities.py b/test/framework/utilities.py index 83621c75ea..39ab091e3f 100644 --- a/test/framework/utilities.py +++ b/test/framework/utilities.py @@ -252,7 +252,7 @@ def init_config(args=None, build_options=None): } if 'suffix_modules_path' not in build_options: build_options.update({'suffix_modules_path': GENERAL_CLASS}) - config.init_build_options(build_options) + config.init_build_options(build_options=build_options) return eb_go.options From ac0198dd362e65f7958a34a3a8d3bfa1e157324f Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 14 Oct 2014 14:33:39 +0200 Subject: [PATCH 069/298] cleanup det_easyconfig_paths in easyconfig.tools --- easybuild/framework/easyconfig/tools.py | 79 ++++++++++++------------- easybuild/main.py | 3 +- 2 files changed, 39 insertions(+), 43 deletions(-) diff --git a/easybuild/framework/easyconfig/tools.py b/easybuild/framework/easyconfig/tools.py index f020231150..0e5d6359a3 100644 --- a/easybuild/framework/easyconfig/tools.py +++ b/easybuild/framework/easyconfig/tools.py @@ -218,14 +218,14 @@ def get_paths_for(subdir="easyconfigs", robot_path=None): def alt_easyconfig_paths(tmpdir, tweaked_ecs=False, from_pr=False): """Obtain alternative paths for easyconfig files.""" - # prepend robot path with location where tweaked easyconfigs will be placed + # path where tweaked easyconfigs will be placed tweaked_ecs_path = None if tweaked_ecs: tweaked_ecs_path = os.path.join(tmpdir, 'tweaked_easyconfigs') + # path where files touched in PR will be downloaded to pr_path = None if from_pr: - # extend robot search path with location where files touch in PR will be downloaded to pr_path = os.path.join(tmpdir, "files_pr%s" % from_pr) return tweaked_ecs_path, pr_path @@ -238,54 +238,51 @@ def det_easyconfig_paths(orig_paths, from_pr=None, easyconfigs_pkg_paths=None): @param from_pr: pull request number to fetch easyconfigs from @param easyconfigs_pkg_paths: paths to installed easyconfigs package """ - paths = [] - if easyconfigs_pkg_paths is None: easyconfigs_pkg_paths = [] ignore_dirs = build_option('ignore_dirs') - if len(orig_paths) == 0: - if from_pr: - pr_files = fetch_easyconfigs_from_pr(from_pr) - paths = [(path, False) for path in pr_files if path.endswith('.eb')] - else: + ec_files = [] + if not orig_paths and from_pr: + pr_files = fetch_easyconfigs_from_pr(from_pr) + ec_files = [path for path in pr_files if path.endswith('.eb')] + elif orig_paths and easyconfigs_pkg_paths: # look for easyconfigs with relative paths in easybuild-easyconfigs package, # unless they were found at the given relative paths - if easyconfigs_pkg_paths: - # determine which easyconfigs files need to be found, if any - ecs_to_find = [] - for idx, orig_path in enumerate(orig_paths): - if orig_path == os.path.basename(orig_path) and not os.path.exists(orig_path): - ecs_to_find.append((idx, orig_path)) - _log.debug("List of easyconfig files to find: %s" % ecs_to_find) - - # find missing easyconfigs by walking paths with installed easyconfig files - for path in easyconfigs_pkg_paths: - _log.debug("Looking for missing easyconfig files (%d left) in %s..." % (len(ecs_to_find), path)) - for (subpath, dirnames, filenames) in os.walk(path, topdown=True): - for idx, orig_path in ecs_to_find[:]: - if orig_path in filenames: - full_path = os.path.join(subpath, orig_path) - _log.info("Found %s in %s: %s" % (orig_path, path, full_path)) - orig_paths[idx] = full_path - # if file was found, stop looking for it (first hit wins) - ecs_to_find.remove((idx, orig_path)) - - # stop os.walk insanity as soon as we have all we need (os.walk loop) - if len(ecs_to_find) == 0: - break - - # ignore subdirs specified to be ignored by replacing items in dirnames list used by os.walk - dirnames[:] = [d for d in dirnames if not d in ignore_dirs] - - # stop os.walk insanity as soon as we have all we need (paths loop) - if len(ecs_to_find) == 0: + ec_files = orig_paths[:] + + # determine which easyconfigs files need to be found, if any + ecs_to_find = [] + for idx, ec_file in enumerate(ec_files): + if ec_file == os.path.basename(ec_file) and not os.path.exists(ec_file): + ecs_to_find.append((idx, ec_file)) + _log.debug("List of easyconfig files to find: %s" % ecs_to_find) + + # find missing easyconfigs by walking paths with installed easyconfig files + for path in easyconfigs_pkg_paths: + _log.debug("Looking for missing easyconfig files (%d left) in %s..." % (len(ecs_to_find), path)) + for (subpath, dirnames, filenames) in os.walk(path, topdown=True): + for idx, orig_path in ecs_to_find[:]: + if orig_path in filenames: + full_path = os.path.join(subpath, orig_path) + _log.info("Found %s in %s: %s" % (orig_path, path, full_path)) + ec_files[idx] = full_path + # if file was found, stop looking for it (first hit wins) + ecs_to_find.remove((idx, orig_path)) + + # stop os.walk insanity as soon as we have all we need (os.walk loop) + if not ecs_to_find: break - # indicate that specified paths do not contain generated easyconfig files - paths = [(path, False) for path in orig_paths] + # ignore subdirs specified to be ignored by replacing items in dirnames list used by os.walk + dirnames[:] = [d for d in dirnames if not d in ignore_dirs] - return paths + # stop os.walk insanity as soon as we have all we need (outer loop) + if not ecs_to_find: + break + + # indicate that specified paths do not contain generated easyconfig files + return [(ec_file, False) for ec_file in ec_files] def parse_easyconfigs(paths): diff --git a/easybuild/main.py b/easybuild/main.py index 19772ac4c1..0716162816 100644 --- a/easybuild/main.py +++ b/easybuild/main.py @@ -49,8 +49,7 @@ import easybuild.tools.options as eboptions from easybuild.framework.easyblock import EasyBlock, build_and_install_one from easybuild.framework.easyconfig.tools import alt_easyconfig_paths, dep_graph, det_easyconfig_paths -from easybuild.framework.easyconfig.tools import get_paths_for, parse_easyconfigs -from easybuild.framework.easyconfig.tools import skip_available +from easybuild.framework.easyconfig.tools import get_paths_for, parse_easyconfigs, skip_available from easybuild.framework.easyconfig.tweak import obtain_path, tweak from easybuild.tools.config import get_repository, get_repositorypath, set_tmpdir from easybuild.tools.filetools import cleanup, write_file From 9498086e405557a31b03e68239e6228913bdfe13 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 14 Oct 2014 15:58:33 +0200 Subject: [PATCH 070/298] keep track of original work dir in EasyBlock, change back to it before installing extensions --- easybuild/framework/easyblock.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index f988295cfe..c8cba7d388 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -111,6 +111,8 @@ def __init__(self, ec): Initialize the EasyBlock instance. @param ec: a parsed easyconfig file (EasyConfig instance) """ + # keep track of original working directory, so we can go back there + self.orig_workdir = os.getcwd() # list of patch/source files, along with checksums self.patches = [] @@ -912,7 +914,6 @@ def make_module_req(self): if os.path.exists(self.installdir): try: - cwd = os.getcwd() os.chdir(self.installdir) except OSError, err: self.log.error("Failed to change to %s: %s" % (self.installdir, err)) @@ -924,9 +925,9 @@ def make_module_req(self): if paths: txt += self.moduleGenerator.prepend_paths(key, paths) try: - os.chdir(cwd) + os.chdir(self.orig_workdir) except OSError, err: - self.log.error("Failed to change back to %s: %s" % (cwd, err)) + self.log.error("Failed to change back to %s: %s" % (self.orig_workdir, err)) else: txt = "" return txt @@ -1376,8 +1377,8 @@ def extensions_step(self, fetch=False): for ext in self.exts: self.log.debug("Starting extension %s" % ext['name']) - # always go back to build dir to avoid running stuff from a dir that no longer exists - os.chdir(self.builddir) + # always go back to original work dir to avoid running stuff from a dir that no longer exists + os.chdir(self.orig_workdir) inst = None @@ -1622,7 +1623,7 @@ def cleanup_step(self): """ if not self.build_in_installdir and build_option('cleanup_builddir'): try: - os.chdir(build_path()) # make sure we're out of the dir we're removing + os.chdir(self.orig_workdir) # make sure we're out of the dir we're removing self.log.info("Cleaning up builddir %s (in %s)" % (self.builddir, os.getcwd())) @@ -1675,8 +1676,7 @@ def test_cases_step(self): Run provided test cases. """ for test in self.cfg['tests']: - # Current working dir no longer exists - os.chdir(self.installdir) + os.chdir(self.orig_workdir) if os.path.isabs(test): path = test else: From 13e244495571176a7fc777e947008f690252137e Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 14 Oct 2014 15:58:47 +0200 Subject: [PATCH 071/298] fix use of print_error in main --- easybuild/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/main.py b/easybuild/main.py index 0716162816..c6fff468c0 100644 --- a/easybuild/main.py +++ b/easybuild/main.py @@ -302,4 +302,4 @@ def main(testing_data=(None, None, None)): try: main() except EasyBuildError, e: - print_error("ERROR: %s\n" % e.msg) + print_error(e.msg) From 7fa1d54a427311a01b27b30fa8b89f5cef7d9b99 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 14 Oct 2014 16:38:57 +0200 Subject: [PATCH 072/298] rework listing of build options and retrieving them from parsed cmdline options --- easybuild/tools/config.py | 139 ++++++++++++++++++-------------------- 1 file changed, 66 insertions(+), 73 deletions(-) diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py index 16899f1d06..837f1fe9b8 100644 --- a/easybuild/tools/config.py +++ b/easybuild/tools/config.py @@ -67,49 +67,64 @@ 'subdir_software': 'software', } - -DEFAULT_BUILD_OPTIONS = { - 'aggregate_regtest': None, - 'allow_modules_tool_mismatch': False, - 'build_specs': None, - 'check_osdeps': True, - 'filter_deps': None, - 'cleanup_builddir': True, - 'command_line': None, - 'debug': False, - 'dry_run': False, - 'dump_test_report': None, - 'easyblock': None, - 'experimental': False, - 'force': False, - 'from_pr': None, - 'github_user': None, - 'group': None, - 'hidden': False, - 'ignore_dirs': None, - 'modules_footer': None, - 'only_blocks': None, - 'optarch': None, - 'pr_path': None, - 'recursive_mod_unload': False, - 'regtest_output_dir': None, - 'retain_all_deps': False, - 'robot_path': None, - 'sequential': False, - 'set_gid_bit': False, - 'silent': False, - 'skip': None, - 'skip_test_cases': False, - 'sticky_bit': False, - 'stop': None, - 'suffix_modules_path': None, - 'test_report_env_filter': None, - 'try_to_generate': False, - 'umask': None, - 'upload_test_report': False, - 'valid_module_classes': None, - 'valid_stops': None, - 'validate': True, +# build options that have a perfectly matching command line option, listed by default value +BUILD_OPTIONS_CMDLINE = { + None: [ + 'aggregate_regtest', + 'dump_test_report', + 'easyblock', + 'filter_deps', + 'from_pr', + 'github_user', + 'group', + 'ignore_dirs', + 'modules_footer', + 'only_blocks', + 'optarch', + 'regtest_output_dir', + 'skip', + 'stop', + 'suffix_modules_path', + 'test_report_env_filter', + 'umask', + ], + False: [ + 'allow_modules_tool_mismatch', + 'debug', + 'experimental', + 'force', + 'hidden', + 'recursive_mod_unload', + 'sequential', + 'set_gid_bit', + 'skip_test_cases', + 'sticky_bit', + 'upload_test_report', + ], + True: [ + 'cleanup_builddir', + ], +} +# build option that do not have a perfectly matching command line option +BUILD_OPTIONS_OTHER = { + None: [ + 'build_specs', + 'command_line', + 'pr_path', + 'robot_path', + 'valid_module_classes', + 'valid_stops', + ], + False: [ + 'dry_run', + 'retain_all_deps', + 'silent', + 'try_to_generate', + ], + True: [ + 'check_osdeps', + 'validate', + ], } @@ -226,7 +241,7 @@ class BuildOptions(FrozenDictKnownKeys): # singleton metaclass: only one instance is created __metaclass__ = Singleton - KNOWN_KEYS = DEFAULT_BUILD_OPTIONS.keys() + KNOWN_KEYS = [k for kss in [BUILD_OPTIONS_CMDLINE, BUILD_OPTIONS_OTHER] for ks in kss.values() for k in ks] def get_user_easybuild_dir(): @@ -387,38 +402,13 @@ def init_build_options(build_options=None, cmdline_options=None): _log.info("Ignoring OS dependencies for --dep-graph/--dry-run") cmdline_options.ignore_osdeps = True + cmdline_build_option_names = [k for k in ks for ks in BUILD_OPTIONS_CMDLINE.values()] + active_build_options.update(dict([(key, getattr(cmdline_options, key)) for key in cmdline_build_option_names])) + # other options which can be derived but have no perfectly matching cmdline option active_build_options.update({ - 'aggregate_regtest': cmdline_options.aggregate_regtest, - 'allow_modules_tool_mismatch': cmdline_options.allow_modules_tool_mismatch, 'check_osdeps': not cmdline_options.ignore_osdeps, - 'filter_deps': cmdline_options.filter_deps, - 'cleanup_builddir': cmdline_options.cleanup_builddir, - 'debug': cmdline_options.debug, 'dry_run': cmdline_options.dry_run or cmdline_options.dry_run_short, - 'dump_test_report': cmdline_options.dump_test_report, - 'easyblock': cmdline_options.easyblock, - 'experimental': cmdline_options.experimental, - 'force': cmdline_options.force, - 'github_user': cmdline_options.github_user, - 'group': cmdline_options.group, - 'hidden': cmdline_options.hidden, - 'ignore_dirs': cmdline_options.ignore_dirs, - 'modules_footer': cmdline_options.modules_footer, - 'only_blocks': cmdline_options.only_blocks, - 'optarch': cmdline_options.optarch, - 'recursive_mod_unload': cmdline_options.recursive_module_unload, - 'regtest_output_dir': cmdline_options.regtest_output_dir, 'retain_all_deps': retain_all_deps, - 'sequential': cmdline_options.sequential, - 'set_gid_bit': cmdline_options.set_gid_bit, - 'skip': cmdline_options.skip, - 'skip_test_cases': cmdline_options.skip_test_cases, - 'sticky_bit': cmdline_options.sticky_bit, - 'stop': cmdline_options.stop, - 'suffix_modules_path': cmdline_options.suffix_modules_path, - 'test_report_env_filter': cmdline_options.test_report_env_filter, - 'umask': cmdline_options.umask, - 'upload_test_report': cmdline_options.upload_test_report, 'validate': not cmdline_options.force, 'valid_module_classes': module_classes(), }) @@ -427,7 +417,10 @@ def init_build_options(build_options=None, cmdline_options=None): active_build_options.update(build_options) # seed in defaults to make sure all build options are defined, and that build_option() doesn't fail on valid keys - bo = copy.deepcopy(DEFAULT_BUILD_OPTIONS) + bo = {} + for build_options_by_default in [BUILD_OPTIONS_CMDLINE, BUILD_OPTIONS_OTHER]: + for default in build_options_by_default: + bo.update(dict([(opt, default) for opt in build_options_by_default[default]])) bo.update(active_build_options) # BuildOptions is a singleton, so any future calls to BuildOptions will yield the same instance From e7c326fd23d312d541b5efb9ff327f239d151bc1 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 14 Oct 2014 16:42:36 +0200 Subject: [PATCH 073/298] retract including testing options for now --- easybuild/tools/options.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index 915f8909bb..4d15565caf 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -174,6 +174,7 @@ def override_options(self): None, 'store_true', False, 'p'), 'set-gid-bit': ("Set group ID bit on newly created directories", None, 'store_true', False), 'sticky-bit': ("Set sticky bit on newly created directories", None, 'store_true', False), + 'skip-test-cases': ("Skip running test cases", None, 'store_true', False, 't'), 'umask': ("umask to use (e.g. '022'); non-user write permissions on install directories are removed", None, 'store', None), 'optarch': ("Set architecture optimization, overriding native architecture optimizations", @@ -276,20 +277,6 @@ def informative_options(self): self.log.debug("informative_options: descr %s opts %s" % (descr, opts)) self.add_group_parser(opts, descr) - def testing_options(self): - # testing options - descr = ("Testing options", "Run application-specific test cases known to EasyBuild") - - opts = OrderedDict({ - 'list-test-cases': ("List known test cases", None, 'store_true', False), - 'run-test-cases': ("Run (specified) test cases (note: requires module to be available already)", - None, 'store_or_None', False, 't'), - 'skip-test-cases': ("Skip running test cases after build/install", None, 'store_true', False), - }) - - self.log.debug("testing_options: descr %s opts %s" % (descr, opts)) - self.add_group_parser(opts, descr) - def regtest_options(self): # regression test options descr = ("Regression test options", "Run and control an EasyBuild regression test.") From a5f401c374581684ae37de15f17013af35ab539e Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 14 Oct 2014 17:11:34 +0200 Subject: [PATCH 074/298] resolve remaining remarks --- easybuild/main.py | 12 ++++++++---- easybuild/tools/config.py | 5 +++-- easybuild/tools/parallelbuild.py | 14 ++++++++------ easybuild/tools/robot.py | 9 ++++----- easybuild/tools/testing.py | 11 +++++++---- 5 files changed, 30 insertions(+), 21 deletions(-) diff --git a/easybuild/main.py b/easybuild/main.py index c6fff468c0..763b28aaa1 100644 --- a/easybuild/main.py +++ b/easybuild/main.py @@ -54,7 +54,7 @@ from easybuild.tools.config import get_repository, get_repositorypath, set_tmpdir from easybuild.tools.filetools import cleanup, write_file from easybuild.tools.options import process_software_build_specs -from easybuild.tools.robot import det_robot_path, print_dry_run, resolve_dependencies, search_easyconfigs +from easybuild.tools.robot import det_robot_path, dry_run, resolve_dependencies, search_easyconfigs from easybuild.tools.parallelbuild import submit_jobs from easybuild.tools.repository.repository import init_repository from easybuild.tools.testing import create_test_report, overall_test_report, regtest, session_module_list, session_state @@ -236,7 +236,8 @@ def main(testing_data=(None, None, None)): # dry_run: print all easyconfigs and dependencies, and whether they are already built if options.dry_run or options.dry_run_short: - print_dry_run(easyconfigs, short=not options.dry_run, build_specs=build_specs) + txt = dry_run(easyconfigs, short=not options.dry_run, build_specs=build_specs) + print_msg(txt, log=_log, silent=testing, prefix=False) # cleanup and exit after dry run, searching easyconfigs or submitting regression test if any([options.dry_run, options.dry_run_short, options.regtest, options.search, options.search_short]): @@ -263,8 +264,9 @@ def main(testing_data=(None, None, None)): # submit build as job(s), clean up and exit if options.job: - submit_jobs(ordered_ecs, eb_go.generate_cmd_line(), testing=testing) + job_info_txt = submit_jobs(ordered_ecs, eb_go.generate_cmd_line(), testing=testing) if not testing: + print_msg("Submitted parallel build jobs, exiting now: %s" % job_info_txt) cleanup(logfile, eb_tmpdir, testing) sys.exit(0) @@ -283,7 +285,9 @@ def main(testing_data=(None, None, None)): repo.cleanup() # dump/upload overall test report - overall_test_report(ecs_with_res, len(paths), overall_success, success_msg, init_session_state) + test_report_msg = overall_test_report(ecs_with_res, len(paths), overall_success, success_msg, init_session_state) + if test_report_msg is not None: + print_msg(test_report_msg) print_msg(success_msg, log=_log, silent=testing) diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py index 837f1fe9b8..8de818bbf1 100644 --- a/easybuild/tools/config.py +++ b/easybuild/tools/config.py @@ -94,7 +94,6 @@ 'experimental', 'force', 'hidden', - 'recursive_mod_unload', 'sequential', 'set_gid_bit', 'skip_test_cases', @@ -117,6 +116,7 @@ ], False: [ 'dry_run', + 'recursive_mod_unload', 'retain_all_deps', 'silent', 'try_to_generate', @@ -402,12 +402,13 @@ def init_build_options(build_options=None, cmdline_options=None): _log.info("Ignoring OS dependencies for --dep-graph/--dry-run") cmdline_options.ignore_osdeps = True - cmdline_build_option_names = [k for k in ks for ks in BUILD_OPTIONS_CMDLINE.values()] + cmdline_build_option_names = [k for ks in BUILD_OPTIONS_CMDLINE.values() for k in ks] active_build_options.update(dict([(key, getattr(cmdline_options, key)) for key in cmdline_build_option_names])) # other options which can be derived but have no perfectly matching cmdline option active_build_options.update({ 'check_osdeps': not cmdline_options.ignore_osdeps, 'dry_run': cmdline_options.dry_run or cmdline_options.dry_run_short, + 'recursive_mod_unload': cmdline_options.recursive_module_unload, 'retain_all_deps': retain_all_deps, 'validate': not cmdline_options.force, 'valid_module_classes': module_classes(), diff --git a/easybuild/tools/parallelbuild.py b/easybuild/tools/parallelbuild.py index 0e0743026e..ac52b20b10 100644 --- a/easybuild/tools/parallelbuild.py +++ b/easybuild/tools/parallelbuild.py @@ -137,13 +137,15 @@ def submit_jobs(ordered_ecs, cmd_line_opts, testing=False): command = "unset TMPDIR && cd %s && eb %%(spec)s %s" % (curdir, quoted_opts) _log.info("Command template for jobs: %s" % command) - if not testing: + job_info_lines = [] + if testing: + _log.debug("Skipping actual submission of jobs since testing mode is enabled") + else: jobs = build_easyconfigs_in_parallel(command, ordered_ecs) - txt = ["List of submitted jobs:"] - txt.extend(["%s (%s): %s" % (job.name, job.module, job.jobid) for job in jobs]) - txt.append("(%d jobs submitted)" % len(jobs)) - - print_msg("Submitted parallel build jobs, exiting now: %s" % '\n'.join(txt), log=_log) + job_info_lines = ["List of submitted jobs:"] + job_info_lines.extend(["%s (%s): %s" % (job.name, job.module, job.jobid) for job in jobs]) + job_info_lines.append("(%d jobs submitted)" % len(jobs)) + return '\n'.join(job_info_lines) def create_job(build_command, easyconfig, output_dir=None, conn=None, ppn=None): diff --git a/easybuild/tools/robot.py b/easybuild/tools/robot.py index ef2cad1e77..f17a888842 100644 --- a/easybuild/tools/robot.py +++ b/easybuild/tools/robot.py @@ -38,7 +38,6 @@ from easybuild.framework.easyconfig.easyconfig import ActiveMNS, process_easyconfig, robot_find_easyconfig from easybuild.framework.easyconfig.tools import find_resolved_modules, skip_available -from easybuild.tools.build_log import print_msg from easybuild.tools.config import build_option from easybuild.tools.filetools import det_common_path_prefix, search_file from easybuild.tools.module_naming_scheme.easybuild_mns import EasyBuildMNS @@ -53,7 +52,7 @@ def det_robot_path(robot_option, easyconfigs_paths, tweaked_ecs_path, pr_path, a """Determine robot path.""" # do not use robot option directly, it's not a list instance (and it shouldn't be modified) robot_path = [] - if not robot_option is None: + if robot_option is not None: if robot_option: robot_path = list(robot_option) _log.info("Using robot path(s): %s" % robot_path) @@ -76,7 +75,7 @@ def det_robot_path(robot_option, easyconfigs_paths, tweaked_ecs_path, pr_path, a return robot_path -def print_dry_run(easyconfigs, short=False, build_specs=None): +def dry_run(easyconfigs, short=False, build_specs=None): """ Print dry run information @param easyconfigs: list of easyconfig files @@ -122,8 +121,7 @@ def print_dry_run(easyconfigs, short=False, build_specs=None): if short: # insert after 'Dry run:' message lines.insert(1, "%s=%s" % (var_name, common_prefix)) - silent = build_option('silent') - print_msg('\n'.join(lines), log=_log, silent=silent, prefix=False) + return '\n'.join(lines) def resolve_dependencies(unprocessed, build_specs=None, retain_all_deps=False): @@ -131,6 +129,7 @@ def resolve_dependencies(unprocessed, build_specs=None, retain_all_deps=False): Work through the list of easyconfigs to determine an optimal order @param unprocessed: list of easyconfigs @param build_specs: dictionary specifying build specifications (e.g. version, toolchain, ...) + @param retain_all_deps: boolean indicating whether all dependencies should be retained, regardless of availability """ robot = build_option('robot_path') diff --git a/easybuild/tools/testing.py b/easybuild/tools/testing.py index 0914b8c856..838736e6bf 100644 --- a/easybuild/tools/testing.py +++ b/easybuild/tools/testing.py @@ -44,7 +44,7 @@ from easybuild.framework.easyblock import build_easyconfigs from easybuild.framework.easyconfig.tools import process_easyconfig from easybuild.framework.easyconfig.tools import skip_available -from easybuild.tools.build_log import EasyBuildError, print_msg +from easybuild.tools.build_log import EasyBuildError from easybuild.tools.config import build_option from easybuild.tools.filetools import find_easyconfigs, mkdir, read_file, write_file from easybuild.tools.github import create_gist, post_comment_in_issue @@ -320,15 +320,18 @@ def overall_test_report(ecs_with_res, orig_cnt, success, msg, init_session_state test_report = create_test_report(msg, ecs_with_res, init_session_state, pr_nr=pr_nr, gist_log=True) if pr_nr: # upload test report to gist and issue a comment in the PR to notify - msg = post_easyconfigs_pr_test_report(pr_nr, test_report, msg, init_session_state, success) - print_msg(msg) + txt = post_easyconfigs_pr_test_report(pr_nr, test_report, msg, init_session_state, success) else: # only upload test report as a gist gist_url = upload_test_report_as_gist(test_report) - print_msg("Test report uploaded to %s" % gist_url) + txt = "Test report uploaded to %s" % gist_url else: test_report = create_test_report(msg, ecs_with_res, init_session_state) + txt = None _log.debug("Test report: %s" % test_report) + if dump_path is not None: write_file(dump_path, test_report) _log.info("Test report dumped to %s" % dump_path) + + return txt From 74d83a04a5c89cab7189ca195d8991e29c3a6e77 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 15 Oct 2014 09:43:37 +0200 Subject: [PATCH 075/298] fix bug in det_easyconfig_paths introduced by refactoring --- easybuild/framework/easyconfig/tools.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/easybuild/framework/easyconfig/tools.py b/easybuild/framework/easyconfig/tools.py index 0e5d6359a3..6ccdd7d264 100644 --- a/easybuild/framework/easyconfig/tools.py +++ b/easybuild/framework/easyconfig/tools.py @@ -242,14 +242,15 @@ def det_easyconfig_paths(orig_paths, from_pr=None, easyconfigs_pkg_paths=None): easyconfigs_pkg_paths = [] ignore_dirs = build_option('ignore_dirs') - ec_files = [] - if not orig_paths and from_pr: + ec_files = orig_paths[:] + + if not ec_files and from_pr: pr_files = fetch_easyconfigs_from_pr(from_pr) ec_files = [path for path in pr_files if path.endswith('.eb')] - elif orig_paths and easyconfigs_pkg_paths: + + elif ec_files and easyconfigs_pkg_paths: # look for easyconfigs with relative paths in easybuild-easyconfigs package, # unless they were found at the given relative paths - ec_files = orig_paths[:] # determine which easyconfigs files need to be found, if any ecs_to_find = [] From 732ea8cc9dc4599d5032030ee8b0be24363a0991 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 15 Oct 2014 15:50:29 +0200 Subject: [PATCH 076/298] get rid of using print_msg/print_error in easyconfigs.tools module --- easybuild/framework/easyconfig/tools.py | 11 +++++------ easybuild/main.py | 6 +++++- easybuild/tools/robot.py | 5 +++-- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/easybuild/framework/easyconfig/tools.py b/easybuild/framework/easyconfig/tools.py index 6ccdd7d264..1b4d75acc4 100644 --- a/easybuild/framework/easyconfig/tools.py +++ b/easybuild/framework/easyconfig/tools.py @@ -67,7 +67,7 @@ from easybuild.framework.easyconfig.easyconfig import ActiveMNS from easybuild.framework.easyconfig.easyconfig import process_easyconfig -from easybuild.tools.build_log import EasyBuildError, print_error, print_msg +from easybuild.tools.build_log import EasyBuildError from easybuild.tools.config import build_option from easybuild.tools.filetools import find_easyconfigs, run_cmd, search_file, write_file from easybuild.tools.github import fetch_easyconfigs_from_pr @@ -75,10 +75,11 @@ from easybuild.tools.ordereddict import OrderedDict from easybuild.tools.utilities import quote_str + _log = fancylogger.getLogger('easyconfig.tools', fname=False) -def skip_available(easyconfigs, testing=False): +def skip_available(easyconfigs): """Skip building easyconfigs for existing modules.""" modtool = modules_tool() module_names = [ec['full_mod_name'] for ec in easyconfigs] @@ -86,9 +87,7 @@ def skip_available(easyconfigs, testing=False): retained_easyconfigs = [] for ec, mod_name, mod_exists in zip(easyconfigs, module_names, modules_exist): if mod_exists: - msg = "%s is already installed (module found), skipping" % mod_name - print_msg(msg, log=_log, silent=testing) - _log.info(msg) + _log.info("%s is already installed (module found), skipping" % mod_name) else: _log.debug("%s is not installed yet, so retaining it" % mod_name) retained_easyconfigs.append(ec) @@ -302,7 +301,7 @@ def parse_easyconfigs(paths): # keep track of whether any files were generated generated_ecs |= generated if not os.path.exists(path): - print_error("Can't find path %s" % path) + _log.error("Can't find path %s" % path) try: ec_files = find_easyconfigs(path, ignore_dirs=ignore_dirs) for ec_file in ec_files: diff --git a/easybuild/main.py b/easybuild/main.py index 763b28aaa1..0022bd4e9d 100644 --- a/easybuild/main.py +++ b/easybuild/main.py @@ -246,7 +246,11 @@ def main(testing_data=(None, None, None)): # skip modules that are already installed unless forced if not options.force: - easyconfigs = skip_available(easyconfigs, testing=testing) + retained_ecs = skip_available(easyconfigs) + if not testing: + for skipped_ec in [ec for ec in easyconfigs if ec not in retained_ecs]: + print_msg("%s is already installed (module found), skipping" % skipped_ec['full_mod_name']) + easyconfigs = retained_ecs # determine an order that will allow all specs in the set to build if len(easyconfigs) > 0: diff --git a/easybuild/tools/robot.py b/easybuild/tools/robot.py index f17a888842..b4d3b630c1 100644 --- a/easybuild/tools/robot.py +++ b/easybuild/tools/robot.py @@ -90,7 +90,7 @@ def dry_run(easyconfigs, short=False, build_specs=None): lines.append("Dry run: printing build status of easyconfigs and dependencies") all_specs = resolve_dependencies(easyconfigs, build_specs=build_specs, retain_all_deps=True) - unbuilt_specs = skip_available(all_specs, testing=True) + unbuilt_specs = skip_available(all_specs) dry_run_fmt = " * [%1s] %s (module: %s)" # markdown compatible (list of items with checkboxes in front) listed_ec_paths = [spec['spec'] for spec in easyconfigs] @@ -133,8 +133,9 @@ def resolve_dependencies(unprocessed, build_specs=None, retain_all_deps=False): """ robot = build_option('robot_path') - + # retain all dependencies if specified by either the resp. build option or the dedicated named argument retain_all_deps = build_option('retain_all_deps') or retain_all_deps + if retain_all_deps: # assume that no modules are available when forced, to retain all dependencies avail_modules = [] From 43b88ce20c00ae35c83c0415c8cc49e478dad46e Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 15 Oct 2014 16:10:28 +0200 Subject: [PATCH 077/298] define and use constant EASYCONFIGS_PKG_SUBDIR --- easybuild/framework/easyblock.py | 8 +++++--- easybuild/framework/easyconfig/__init__.py | 3 +++ easybuild/framework/easyconfig/tools.py | 3 ++- easybuild/main.py | 3 ++- easybuild/tools/options.py | 3 ++- easybuild/tools/parallelbuild.py | 2 +- 6 files changed, 15 insertions(+), 7 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index c8cba7d388..662daf8bf8 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -48,8 +48,10 @@ import easybuild.tools.environment as env from easybuild.tools import config, filetools -from easybuild.framework.easyconfig.easyconfig import (EasyConfig, ActiveMNS, ITERATE_OPTIONS, - fetch_parameter_from_easyconfig_file, get_class_for, get_easyblock_class, get_module_path, resolve_template) +from easybuild.framework.easyconfig import EASYCONFIGS_PKG_SUBDIR +from easybuild.framework.easyconfig.easyconfig import EasyConfig, ActiveMNS, ITERATE_OPTIONS +from easybuild.framework.easyconfig.easyconfig import fetch_parameter_from_easyconfig_file, get_class_for +from easybuild.framework.easyconfig.easyconfig import get_easyblock_class, get_module_path, resolve_template from easybuild.framework.easyconfig.tools import get_paths_for from easybuild.framework.easyconfig.templates import TEMPLATE_NAMES_EASYBLOCK_RUN_STEP from easybuild.tools.build_details import get_build_stats @@ -474,7 +476,7 @@ def obtain_file(self, filename, extension=False, urls=None): common_filepaths = [] if self.robot_path: common_filepaths.extend(self.robot_path) - common_filepaths.extend(get_paths_for("easyconfigs", robot_path=self.robot_path)) + common_filepaths.extend(get_paths_for(subdir=EASYCONFIGS_PKG_SUBDIR, robot_path=self.robot_path)) for path in ebpath + common_filepaths + srcpaths: # create list of candidate filepaths diff --git a/easybuild/framework/easyconfig/__init__.py b/easybuild/framework/easyconfig/__init__.py index b8f643bab3..24e58f0208 100644 --- a/easybuild/framework/easyconfig/__init__.py +++ b/easybuild/framework/easyconfig/__init__.py @@ -31,5 +31,8 @@ from easybuild.framework.easyconfig.default import ALL_CATEGORIES globals().update(ALL_CATEGORIES) +# subdirectory (of 'easybuild' dir) in which easyconfig files are located in a package +EASYCONFIGS_PKG_SUBDIR = 'easyconfigs' + # is used in some tools from easybuild.framework.easyconfig.easyconfig import EasyConfig diff --git a/easybuild/framework/easyconfig/tools.py b/easybuild/framework/easyconfig/tools.py index 1b4d75acc4..804155b847 100644 --- a/easybuild/framework/easyconfig/tools.py +++ b/easybuild/framework/easyconfig/tools.py @@ -65,6 +65,7 @@ except ImportError, err: graph_errors.append("Failed to import graphviz: try yum install graphviz-python, or apt-get install python-pygraphviz") +from easybuild.framework.easyconfig import EASYCONFIGS_PKG_SUBDIR from easybuild.framework.easyconfig.easyconfig import ActiveMNS from easybuild.framework.easyconfig.easyconfig import process_easyconfig from easybuild.tools.build_log import EasyBuildError @@ -175,7 +176,7 @@ def dep_graph(*args, **kwargs): _log.error("%s\nerr: %s" % (msg, err)) -def get_paths_for(subdir="easyconfigs", robot_path=None): +def get_paths_for(subdir=EASYCONFIGS_PKG_SUBDIR, robot_path=None): """ Return a list of absolute paths where the specified subdir can be found, determined by the PYTHONPATH """ diff --git a/easybuild/main.py b/easybuild/main.py index 0022bd4e9d..d06ff04b13 100644 --- a/easybuild/main.py +++ b/easybuild/main.py @@ -48,6 +48,7 @@ import easybuild.tools.config as config import easybuild.tools.options as eboptions from easybuild.framework.easyblock import EasyBlock, build_and_install_one +from easybuild.framework.easyconfig import EASYCONFIGS_PKG_SUBDIR from easybuild.framework.easyconfig.tools import alt_easyconfig_paths, dep_graph, det_easyconfig_paths from easybuild.framework.easyconfig.tools import get_paths_for, parse_easyconfigs, skip_available from easybuild.framework.easyconfig.tweak import obtain_path, tweak @@ -161,7 +162,7 @@ def main(testing_data=(None, None, None)): _log.info("umask set to '%s' (used to be '%s')" % (oct(new_umask), oct(old_umask))) # determine easybuild-easyconfigs package install path - easyconfigs_pkg_paths = get_paths_for("easyconfigs") + easyconfigs_pkg_paths = get_paths_for(subdir=EASYCONFIGS_PKG_SUBDIR) if not easyconfigs_pkg_paths: _log.warning("Failed to determine install path for easybuild-easyconfigs package.") diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index 4d15565caf..a346cb8e56 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -40,6 +40,7 @@ from vsc.utils.missing import nub from easybuild.framework.easyblock import EasyBlock +from easybuild.framework.easyconfig import EASYCONFIGS_PKG_SUBDIR from easybuild.framework.easyconfig.constants import constant_documentation from easybuild.framework.easyconfig.default import convert_to_help from easybuild.framework.easyconfig.easyconfig import get_easyblock_class @@ -80,7 +81,7 @@ def basic_options(self): strictness_options = [run.IGNORE, run.WARN, run.ERROR] try: - default_robot_path = get_paths_for("easyconfigs", robot_path=None)[0] + default_robot_path = get_paths_for(subdir=EASYCONFIGS_PKG_SUBDIR, robot_path=None)[0] except: self.log.warning("basic_options: unable to determine default easyconfig path") default_robot_path = False # False as opposed to None, since None is used for indicating that --robot was used diff --git a/easybuild/tools/parallelbuild.py b/easybuild/tools/parallelbuild.py index ac52b20b10..8af42cffeb 100644 --- a/easybuild/tools/parallelbuild.py +++ b/easybuild/tools/parallelbuild.py @@ -39,7 +39,7 @@ import easybuild.tools.config as config from easybuild.framework.easyblock import get_easyblock_instance from easybuild.framework.easyconfig.easyconfig import ActiveMNS -from easybuild.tools.build_log import EasyBuildError, print_msg +from easybuild.tools.build_log import EasyBuildError from easybuild.tools.config import get_repository, get_repositorypath from easybuild.tools.module_naming_scheme.utilities import det_full_ec_version from easybuild.tools.pbs_job import PbsJob, connect_to_server, disconnect_from_server, get_ppn From 81d358d35b0674a1b85584448161e70b53743320 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 15 Oct 2014 17:11:17 +0200 Subject: [PATCH 078/298] fix remarks --- easybuild/framework/easyconfig/tweak.py | 30 +++---------------------- easybuild/main.py | 27 +++++++++++++++++++--- easybuild/tools/robot.py | 16 +++++++------ 3 files changed, 36 insertions(+), 37 deletions(-) diff --git a/easybuild/framework/easyconfig/tweak.py b/easybuild/framework/easyconfig/tweak.py index 6e0e50c290..a5c2be47d1 100644 --- a/easybuild/framework/easyconfig/tweak.py +++ b/easybuild/framework/easyconfig/tweak.py @@ -42,7 +42,6 @@ from vsc.utils import fancylogger from vsc.utils.missing import nub -from easybuild.tools.build_log import print_error, print_msg, print_warning from easybuild.framework.easyconfig.easyconfig import EasyConfig, create_paths, process_easyconfig from easybuild.tools.filetools import read_file, write_file from easybuild.tools.module_naming_scheme.utilities import det_full_ec_version @@ -222,7 +221,7 @@ def __repr__(self): _log.debug("Contents of tweaked easyconfig file:\n%s" % ectxt) # come up with suiting file name for tweaked easyconfig file if none was specified - if not target_fn: + if target_fn is None: fn = None try: # obtain temporary filename @@ -525,7 +524,7 @@ def unique(l): # GENERATE # if no file path was specified, generate a file name - if not fp: + if fp is None: cfg = { 'version': ver, 'toolchain': {'name': tcname, 'version': tcver}, @@ -543,7 +542,7 @@ def unique(l): return (True, fp) -def obtain_ec_for(specs, paths, fp): +def obtain_ec_for(specs, paths, fp=None): """ Obtain an easyconfig file to the given specifications. @@ -595,26 +594,3 @@ def obtain_ec_for(specs, paths, fp): return res else: _log.error("No easyconfig found for requested software, and also failed to generate one.") - - -def obtain_path(specs, paths, try_to_generate=False, exit_on_error=True, silent=False): - """Obtain a path for an easyconfig that matches the given specifications.""" - - # if no easyconfig files/paths were provided, but we did get a software name, - # we can try and find a suitable easyconfig ourselves, or generate one if we can - (generated, fn) = obtain_ec_for(specs, paths, None) - if not generated: - return (fn, generated) - else: - # if an easyconfig was generated, make sure we're allowed to use it - if try_to_generate: - print_msg("Generated an easyconfig file %s, going to use it now..." % fn, silent=silent) - return (fn, generated) - else: - try: - os.remove(fn) - except OSError, err: - print_warning("Failed to remove generated easyconfig file %s: %s" % (fn, err)) - print_error(("Unable to find an easyconfig for the given specifications: %s; " - "to make EasyBuild try to generate a matching easyconfig, " - "use the --try-X options ") % specs, log=_log, exit_on_error=exit_on_error) diff --git a/easybuild/main.py b/easybuild/main.py index d06ff04b13..6c323db342 100644 --- a/easybuild/main.py +++ b/easybuild/main.py @@ -51,7 +51,7 @@ from easybuild.framework.easyconfig import EASYCONFIGS_PKG_SUBDIR from easybuild.framework.easyconfig.tools import alt_easyconfig_paths, dep_graph, det_easyconfig_paths from easybuild.framework.easyconfig.tools import get_paths_for, parse_easyconfigs, skip_available -from easybuild.framework.easyconfig.tweak import obtain_path, tweak +from easybuild.framework.easyconfig.tweak import obtain_ec_for, tweak from easybuild.tools.config import get_repository, get_repositorypath, set_tmpdir from easybuild.tools.filetools import cleanup, write_file from easybuild.tools.options import process_software_build_specs @@ -75,6 +75,27 @@ def log_start(eb_command_line, eb_tmpdir): _log.info("Using %s as temporary directory" % eb_tmpdir) +def find_easyconfigs_by_specs(build_specs, robot_path, try_to_generate, testing=False): + """Find easyconfigs by build specifications.""" + generated, ec_file = obtain_ec_for(build_specs, robot_path, None) + if generated: + if try_to_generate: + print_msg("Generated an easyconfig file %s, going to use it now..." % ec_file, silent=testing) + else: + # (try to) cleanup + try: + os.remove(ec_file) + except OSError, err: + _log.warning("Failed to remove generated easyconfig file %s: %s" % (ec_file, err)) + + # don't use a generated easyconfig unless generation was requested (using a --try-X option) + print_error(("Unable to find an easyconfig for the given specifications: %s; " + "to make EasyBuild try to generate a matching easyconfig, " + "use the --try-X options ") % build_specs, log=_log) + + return [(ec_file, generated)] + + def build_and_install_software(ecs, init_session_state, exit_on_failure=True): """Build and install software for all provided parsed easyconfig files.""" # obtain a copy of the starting environment so each build can start afresh @@ -209,8 +230,8 @@ def main(testing_data=(None, None, None)): paths = det_easyconfig_paths(orig_paths, options.from_pr, easyconfigs_pkg_paths) if not paths: if 'name' in build_specs: - paths = [obtain_path(build_specs, robot_path, try_to_generate=try_to_generate, - exit_on_error=not testing)] + # try to obtain or generate an easyconfig file via build specifications if a software name is provided + paths = find_easyconfigs_by_specs(build_specs, robot_path, try_to_generate, testing=testing) elif not any([options.aggregate_regtest, options.search, options.search_short, options.regtest]): print_error(("Please provide one or multiple easyconfig files, or use software build " "options to make EasyBuild search for easyconfigs"), diff --git a/easybuild/tools/robot.py b/easybuild/tools/robot.py index b4d3b630c1..17a97754a1 100644 --- a/easybuild/tools/robot.py +++ b/easybuild/tools/robot.py @@ -77,9 +77,9 @@ def det_robot_path(robot_option, easyconfigs_paths, tweaked_ecs_path, pr_path, a def dry_run(easyconfigs, short=False, build_specs=None): """ - Print dry run information - @param easyconfigs: list of easyconfig files - @param short: print short output (use a variable for the common prefix) + Compose dry run overview for supplied easyconfigs ([ ] for unavailable, [x] for available, [F] for forced) + @param easyconfigs: list of parsed easyconfigs (EasyConfig instances) + @param short: use short format for overview: use a variable for common prefixes @param build_specs: dictionary specifying build specifications (e.g. version, toolchain, ...) """ lines = [] @@ -129,7 +129,8 @@ def resolve_dependencies(unprocessed, build_specs=None, retain_all_deps=False): Work through the list of easyconfigs to determine an optimal order @param unprocessed: list of easyconfigs @param build_specs: dictionary specifying build specifications (e.g. version, toolchain, ...) - @param retain_all_deps: boolean indicating whether all dependencies should be retained, regardless of availability + @param retain_all_deps: boolean indicating whether all dependencies must be retained, regardless of availability; + retain all deps when True, check matching build option when False """ robot = build_option('robot_path') @@ -184,12 +185,12 @@ def resolve_dependencies(unprocessed, build_specs=None, retain_all_deps=False): being_installed = [EasyBuildMNS().det_full_module_name(p['ec']) for p in unprocessed] additional = [] - for i, entry in enumerate(unprocessed): + for entry in unprocessed: # do not choose an entry that is being installed in the current run # if they depend, you probably want to rebuild them using the new dependency deps = entry['dependencies'] candidates = [d for d in deps if not EasyBuildMNS().det_full_module_name(d) in being_installed] - if len(candidates) > 0: + if candidates: cand_dep = candidates[0] # find easyconfig, might not find any _log.debug("Looking for easyconfig for %s" % str(cand_dep)) @@ -246,10 +247,11 @@ def resolve_dependencies(unprocessed, build_specs=None, retain_all_deps=False): def search_easyconfigs(query, short=False): """Search for easyconfigs, if a query is provided.""" - search_path = [os.getcwd()] robot_path = build_option('robot_path') if robot_path: search_path = robot_path + else: + search_path = [os.getcwd()] ignore_dirs = build_option('ignore_dirs') silent = build_option('silent') search_file(search_path, query, short=short, ignore_dirs=ignore_dirs, silent=silent) From 441239e7183783b85929cab685bd8638bf91e816 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 15 Oct 2014 17:37:29 +0200 Subject: [PATCH 079/298] fix skip_available calls in robot unit test --- test/framework/robot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/framework/robot.py b/test/framework/robot.py index 7beee4cca1..6562817c27 100644 --- a/test/framework/robot.py +++ b/test/framework/robot.py @@ -231,7 +231,7 @@ def test_resolve_dependencies(self): # build that are listed but already have a module available are not retained without force build_options.update({'force': False}) init_config(build_options=build_options) - newecs = skip_available(ecs, testing=True) # skip available builds since force is not enabled + newecs = skip_available(ecs) # skip available builds since force is not enabled res = resolve_dependencies(newecs) self.assertEqual(len(res), 2) self.assertEqual('goolf/1.4.10', res[0]['full_mod_name']) @@ -241,7 +241,7 @@ def test_resolve_dependencies(self): build_options.update({'retain_all_deps': True}) init_config(build_options=build_options) ecs = [deepcopy(easyconfig_dep)] - newecs = skip_available(ecs, testing=True) # skip available builds since force is not enabled + newecs = skip_available(ecs) # skip available builds since force is not enabled res = resolve_dependencies(newecs) self.assertEqual(len(res), 9) self.assertEqual('GCC/4.7.2', res[0]['full_mod_name']) From 585c34a98bd85584d6a2ed04e20c1f5e4b4cd24c Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 15 Oct 2014 18:37:52 +0200 Subject: [PATCH 080/298] fix remark, avoid assigning build options to variables if they're only used once --- easybuild/framework/easyconfig/tools.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/easybuild/framework/easyconfig/tools.py b/easybuild/framework/easyconfig/tools.py index 804155b847..57b045428a 100644 --- a/easybuild/framework/easyconfig/tools.py +++ b/easybuild/framework/easyconfig/tools.py @@ -240,7 +240,6 @@ def det_easyconfig_paths(orig_paths, from_pr=None, easyconfigs_pkg_paths=None): """ if easyconfigs_pkg_paths is None: easyconfigs_pkg_paths = [] - ignore_dirs = build_option('ignore_dirs') ec_files = orig_paths[:] @@ -276,7 +275,7 @@ def det_easyconfig_paths(orig_paths, from_pr=None, easyconfigs_pkg_paths=None): break # ignore subdirs specified to be ignored by replacing items in dirnames list used by os.walk - dirnames[:] = [d for d in dirnames if not d in ignore_dirs] + dirnames[:] = [d for d in dirnames if not d in build_option('ignore_dirs')] # stop os.walk insanity as soon as we have all we need (outer loop) if not ecs_to_find: @@ -291,10 +290,6 @@ def parse_easyconfigs(paths): Parse easyconfig files @params paths: paths to easyconfigs """ - build_specs = build_option('build_specs') - ignore_dirs = build_option('ignore_dirs') - try_to_generate = build_option('try_to_generate') - easyconfigs = [] generated_ecs = False for (path, generated) in paths: @@ -304,13 +299,13 @@ def parse_easyconfigs(paths): if not os.path.exists(path): _log.error("Can't find path %s" % path) try: - ec_files = find_easyconfigs(path, ignore_dirs=ignore_dirs) + ec_files = find_easyconfigs(path, ignore_dirs=build_option('ignore_dirs')) for ec_file in ec_files: # only pass build specs when not generating easyconfig files - if try_to_generate: - ecs = process_easyconfig(ec_file) - else: - ecs = process_easyconfig(ec_file, build_specs=build_specs) + kwargs = {} + if not build_option('try_to_generate'): + kwargs['build_specs'] = build_option('build_specs') + ecs = process_easyconfig(ec_file, **kwargs) easyconfigs.extend(ecs) except IOError, err: _log.error("Processing easyconfigs in path %s failed: %s" % (path, err)) From 3e9e5cb79b8f07c088795f3b1317d9a60a751fc3 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 15 Oct 2014 18:38:27 +0200 Subject: [PATCH 081/298] fix broken toy unit test --- test/framework/toy_build.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/framework/toy_build.py b/test/framework/toy_build.py index 09b16d6ad9..5f5b31ebe2 100644 --- a/test/framework/toy_build.py +++ b/test/framework/toy_build.py @@ -519,18 +519,19 @@ def test_allow_system_deps(self): def test_toy_hierarchical(self): """Test toy build under example hierarchical module naming scheme.""" + test_easyconfigs = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs') self.setup_hierarchical_modules() mod_prefix = os.path.join(self.test_installpath, 'modules', 'all') args = [ - os.path.join(os.path.dirname(__file__), 'easyconfigs', 'toy-0.0.eb'), + os.path.join(test_easyconfigs, 'toy-0.0.eb'), '--sourcepath=%s' % self.test_sourcepath, '--buildpath=%s' % self.test_buildpath, '--installpath=%s' % self.test_installpath, '--debug', '--unittest-file=%s' % self.logfile, '--force', - '--robot=%s' % os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs'), + '--robot=%s' % test_easyconfigs, '--module-naming-scheme=HierarchicalMNS', ] @@ -625,11 +626,11 @@ def test_toy_hierarchical(self): # no dependencies or toolchain => no module load statements in module file modtxt = read_file(toy_module_path) modpath_extension = os.path.join(mod_prefix, 'Compiler', 'toy', '0.0') - self.assertTrue(re.search("^module\s*use\s*%s" % modpath_extension, modtxt, re.M)) + self.assertTrue(re.search(r"^module\s*use\s*%s" % modpath_extension, modtxt, re.M)) os.remove(toy_module_path) # building a toolchain module should also work - args = ['gompi-1.4.10.eb'] + args[1:] + args[0] = os.path.join(test_easyconfigs, 'gompi-1.4.10.eb') modules_tool().purge() self.eb_main(args, logfile=self.dummylogfn, do_build=True, verbose=True, raise_error=True) From 3def91f2df125f18a9163a9b2ac82cdf434ee3ff Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 15 Oct 2014 21:38:47 +0200 Subject: [PATCH 082/298] fix broken gompi toolchain installation unit test --- .../sandbox/easybuild/easyblocks/generic/toolchain.py | 9 ++++++++- test/framework/toy_build.py | 4 +++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/test/framework/sandbox/easybuild/easyblocks/generic/toolchain.py b/test/framework/sandbox/easybuild/easyblocks/generic/toolchain.py index db67440c90..7ba202cbdc 100644 --- a/test/framework/sandbox/easybuild/easyblocks/generic/toolchain.py +++ b/test/framework/sandbox/easybuild/easyblocks/generic/toolchain.py @@ -31,4 +31,11 @@ class Toolchain(EasyBlock): """Dummy support for toolchains.""" - pass + def configure_step(self): + pass + def build_step(self): + pass + def install_step(self): + pass + def sanity_check_step(self): + pass diff --git a/test/framework/toy_build.py b/test/framework/toy_build.py index 5f5b31ebe2..1b8371b379 100644 --- a/test/framework/toy_build.py +++ b/test/framework/toy_build.py @@ -632,7 +632,9 @@ def test_toy_hierarchical(self): # building a toolchain module should also work args[0] = os.path.join(test_easyconfigs, 'gompi-1.4.10.eb') modules_tool().purge() - self.eb_main(args, logfile=self.dummylogfn, do_build=True, verbose=True, raise_error=True) + self.eb_main(args, logfile=self.dummylogfn, do_build=True, verbose=True, raise_error=False) + gompi_module_path = os.path.join(mod_prefix, 'Core', 'gompi', '1.4.10') + self.assertTrue(os.path.exists(gompi_module_path)) def test_toy_advanced(self): """Test toy build with extensions and non-dummy toolchain.""" From 61d1f38730d9de2c870d4f6446220d0c51b338b1 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 16 Oct 2014 19:02:11 +0200 Subject: [PATCH 083/298] fix picking required version if it's available, clean up in tweak.py module --- easybuild/framework/easyconfig/tweak.py | 65 +++++++++---------------- 1 file changed, 24 insertions(+), 41 deletions(-) diff --git a/easybuild/framework/easyconfig/tweak.py b/easybuild/framework/easyconfig/tweak.py index a5c2be47d1..b72a55214b 100644 --- a/easybuild/framework/easyconfig/tweak.py +++ b/easybuild/framework/easyconfig/tweak.py @@ -254,11 +254,14 @@ def __repr__(self): def pick_version(req_ver, avail_vers): """Pick version based on an optionally desired version and available versions. - If a desired version is specifed, the most recent version that is less recent - than the desired version will be picked; else, the most recent version will be picked. + If a desired version is specifed, the most recent version that is less recent than or equal to + the desired version will be picked; else, the most recent version will be picked. - This function returns both the version to be used, which is equal to the desired version + This function returns both the version to be used, which is equal to the required version if it was specified, and the version picked that matches that closest. + + @param req_ver: required version + @param avail_vers: list of available versions """ if not avail_vers: @@ -267,20 +270,18 @@ def pick_version(req_ver, avail_vers): selected_ver = None if req_ver: # if a desired version is specified, - # retain the most recent version that's less recent than the desired version - + # retain the most recent version that's less recent or equal than the desired version ver = req_ver if len(avail_vers) == 1: selected_ver = avail_vers[0] else: - retained_vers = [v for v in avail_vers if v < LooseVersion(ver)] + retained_vers = [v for v in avail_vers if v <= LooseVersion(ver)] if retained_vers: selected_ver = retained_vers[-1] else: # if no versions are available that are less recent, take the least recent version selected_ver = sorted([LooseVersion(v) for v in avail_vers])[0] - else: # if no desired version is specified, just use last version ver = avail_vers[-1] @@ -289,6 +290,20 @@ def pick_version(req_ver, avail_vers): return (ver, selected_ver) +def find_matching_easyconfigs(name, installver, paths): + """Find easyconfigs that match specified name/installversion in specified list of paths.""" + ec_files = [] + for path in paths: + patterns = create_paths(path, name, installver) + for pattern in patterns: + more_ec_files = glob.glob(pattern) + _log.debug("Including files that match glob pattern '%s': %s" % (pattern, more_ec_files)) + ec_files.extend(more_ec_files) + + # only retain unique easyconfig paths + return nub(ec_files) + + def select_or_generate_ec(fp, paths, specs): """ Select or generate an easyconfig file with the given requirements, from existing easyconfig files. @@ -318,7 +333,6 @@ def select_or_generate_ec(fp, paths, specs): handled_params = ['name'] # find ALL available easyconfig files for specified software - ec_files = [] cfg = { 'version': '*', 'toolchain': {'name': DUMMY_TOOLCHAIN_NAME, 'version': '*'}, @@ -326,10 +340,8 @@ def select_or_generate_ec(fp, paths, specs): 'versionsuffix': '*', } installver = det_full_ec_version(cfg) - for path in paths: - patterns = create_paths(path, name, installver) - for pattern in patterns: - ec_files.extend(glob.glob(pattern)) + ec_files = find_matching_easyconfigs(name, installver, paths) + _log.debug("Unique ec_files: %s" % ec_files) # we need at least one config file to start from if len(ec_files) == 0: @@ -346,10 +358,6 @@ def select_or_generate_ec(fp, paths, specs): if len(ec_files) == 0: _log.error("No easyconfig files found for software %s, and no templates available. I'm all out of ideas." % name) - # only retain unique easyconfig files - ec_files = nub(ec_files) - _log.debug("Unique ec_files: %s" % ec_files) - ecs_and_files = [(EasyConfig(f, validate=False), f) for f in ec_files] # TOOLCHAIN NAME @@ -562,31 +570,6 @@ def obtain_ec_for(specs, paths, fp=None): if not paths: _log.error("No paths to look for easyconfig files, specify a path with --robot.") - # create glob patterns based on supplied info - - # figure out the install version - cfg = { - 'version': specs.get('version', '*'), - 'toolchain': { - 'name': specs.get('toolchain_name', '*'), - 'version': specs.get('toolchain_version', '*'), - }, - 'versionprefix': specs.get('versionprefix', '*'), - 'versionsuffix': specs.get('versionsuffix', '*'), - } - installver = det_full_ec_version(cfg) - - # find easyconfigs that match a pattern - easyconfig_files = [] - for path in paths: - patterns = create_paths(path, specs['name'], installver) - for pattern in patterns: - easyconfig_files.extend(glob.glob(pattern)) - - cnt = len(easyconfig_files) - - _log.debug("List of obtained easyconfig files (%d): %s" % (cnt, easyconfig_files)) - # select best easyconfig, or try to generate one that fits the requirements res = select_or_generate_ec(fp, paths, specs) From dbfa9e45f2cc607181c9c2411a4e8bcc3aefd873 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 17 Oct 2014 09:26:07 +0200 Subject: [PATCH 084/298] fix bug: include easyconfigs pkg paths whenever --robot is used --- easybuild/framework/easyconfig/tools.py | 2 +- easybuild/tools/robot.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/easybuild/framework/easyconfig/tools.py b/easybuild/framework/easyconfig/tools.py index 57b045428a..b01e500201 100644 --- a/easybuild/framework/easyconfig/tools.py +++ b/easybuild/framework/easyconfig/tools.py @@ -205,7 +205,7 @@ def get_paths_for(subdir=EASYCONFIGS_PKG_SUBDIR, robot_path=None): # look for desired subdirs for path in path_list: path = os.path.join(path, "easybuild", subdir) - _log.debug("Looking for easybuild/%s in path %s" % (subdir, path)) + _log.debug("Checking for easybuild/%s at %s" % (subdir, path)) try: if os.path.exists(path): paths.append(os.path.abspath(path)) diff --git a/easybuild/tools/robot.py b/easybuild/tools/robot.py index 17a97754a1..b478571485 100644 --- a/easybuild/tools/robot.py +++ b/easybuild/tools/robot.py @@ -48,7 +48,7 @@ _log = fancylogger.getLogger('tools.robot', fname=False) -def det_robot_path(robot_option, easyconfigs_paths, tweaked_ecs_path, pr_path, auto_robot=False): +def det_robot_path(robot_option, easyconfigs_pkg_paths, tweaked_ecs_path, pr_path, auto_robot=False): """Determine robot path.""" # do not use robot option directly, it's not a list instance (and it shouldn't be modified) robot_path = [] @@ -60,8 +60,8 @@ def det_robot_path(robot_option, easyconfigs_paths, tweaked_ecs_path, pr_path, a # if options.robot is not None and False, easyconfigs pkg install path could not be found (see options.py) _log.error("No robot paths specified, and unable to determine easybuild-easyconfigs install path.") - if auto_robot: - robot_path.extend(easyconfigs_paths) + if robot_path or auto_robot: + robot_path.extend(easyconfigs_pkg_paths) _log.info("Extended list of robot paths with paths for installed easyconfigs: %s" % robot_path) if tweaked_ecs_path is not None: From debcc7b7380c9b036a47d80ad3bbef914874159f Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 17 Oct 2014 11:33:06 +0200 Subject: [PATCH 085/298] fix remarks --- easybuild/framework/easyconfig/tweak.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/easybuild/framework/easyconfig/tweak.py b/easybuild/framework/easyconfig/tweak.py index b72a55214b..ebc233b085 100644 --- a/easybuild/framework/easyconfig/tweak.py +++ b/easybuild/framework/easyconfig/tweak.py @@ -291,12 +291,18 @@ def pick_version(req_ver, avail_vers): def find_matching_easyconfigs(name, installver, paths): - """Find easyconfigs that match specified name/installversion in specified list of paths.""" + """ + Find easyconfigs that match specified name/installversion in specified list of paths. + + @param name: software name + @param installver: software install version (which includes version, toolchain, versionprefix/suffix, ...) + @param paths: list of paths to search easyconfigs in + """ ec_files = [] for path in paths: patterns = create_paths(path, name, installver) for pattern in patterns: - more_ec_files = glob.glob(pattern) + more_ec_files = filter(os.path.isfile, glob.glob(pattern)) _log.debug("Including files that match glob pattern '%s': %s" % (pattern, more_ec_files)) ec_files.extend(more_ec_files) From 712ae87a1bd0a0041c859c0588c227c90ee24092 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 17 Oct 2014 11:56:03 +0200 Subject: [PATCH 086/298] add unit test module for tweak.py --- test/framework/suite.py | 3 +- test/framework/tweak.py | 116 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 test/framework/tweak.py diff --git a/test/framework/suite.py b/test/framework/suite.py index 61b3ac30b4..1ed29b598a 100644 --- a/test/framework/suite.py +++ b/test/framework/suite.py @@ -69,6 +69,7 @@ import test.framework.toolchain as tc import test.framework.toolchainvariables as tcv import test.framework.toy_build as t +import test.framework.tweak as tw import test.framework.variables as v @@ -95,7 +96,7 @@ # call suite() for each module and then run them all # note: make sure the options unit tests run first, to avoid running some of them with a readily initialized config -tests = [o, r, ef, ev, ebco, ep, e, mg, m, mt, f, run, a, robot, b, v, g, tcv, tc, t, c, s, l, f_c, sc] +tests = [o, r, ef, ev, ebco, ep, e, mg, m, mt, f, run, a, robot, b, v, g, tcv, tc, t, c, s, l, f_c, sc, tw] SUITE = unittest.TestSuite([x.suite() for x in tests]) diff --git a/test/framework/tweak.py b/test/framework/tweak.py new file mode 100644 index 0000000000..061a0bcf8b --- /dev/null +++ b/test/framework/tweak.py @@ -0,0 +1,116 @@ +## +# Copyright 2014 Ghent University +# +# This file is part of EasyBuild, +# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en), +# with support of Ghent University (http://ugent.be/hpc), +# the Flemish Supercomputer Centre (VSC) (https://vscentrum.be/nl/en), +# the Hercules foundation (http://www.herculesstichting.be/in_English) +# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en). +# +# http://github.com/hpcugent/easybuild +# +# EasyBuild is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation v2. +# +# EasyBuild is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with EasyBuild. If not, see . +## +""" +Unit tests for framework/easyconfig/tweak.py + +@author: Kenneth Hoste (Ghent University) +""" +import os +from test.framework.utilities import EnhancedTestCase +from unittest import TestLoader, main + +from easybuild.framework.easyconfig.tweak import find_matching_easyconfigs, obtain_ec_for, pick_version + + +class TweakTest(EnhancedTestCase): + """Tests for tweak functionality.""" + def test_pick_version(self): + """Test pick_version function.""" + # if required version is not available, the most recent version less than or equal should be returned + self.assertEqual(('1.4', '1.0'), pick_version('1.4', ['0.5', '1.0', '1.5'])) + + # if required version is available, that should be what's returned + self.assertEqual(('1.0', '1.0'), pick_version('1.0', ['0.5', '1.0', '1.5'])) + + def test_find_matching_easyconfigs(self): + """Test find_matching_easyconfigs function.""" + test_easyconfigs_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs') + for (name, installver) in [('GCC', '4.8.2'), ('gzip', '1.5-goolf-1.4.10')]: + ecs = find_matching_easyconfigs(name, installver, [test_easyconfigs_path]) + self.assertTrue(len(ecs) == 1 and ecs[0].endswith('/%s-%s.eb' % (name, installver))) + + ecs = find_matching_easyconfigs('GCC', '*', [test_easyconfigs_path]) + gccvers = ['4.6.3', '4.6.4', '4.7.2', '4.8.2', '4.8.3'] + self.assertEqual(len(ecs), len(gccvers)) + ecs_basename = [os.path.basename(ec) for ec in ecs] + for gccver in gccvers: + gcc_ec = 'GCC-%s.eb' % gccver + self.assertTrue(gcc_ec in ecs_basename, "%s is included in %s" % (gcc_ec, ecs_basename)) + + def test_obtain_ec_for(self): + """Test obtain_ec_for function.""" + test_easyconfigs_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs') + # find existing easyconfigs + specs = { + 'name': 'GCC', + 'version': '4.6.4', + } + (generated, ec_file) = obtain_ec_for(specs, [test_easyconfigs_path]) + self.assertFalse(generated) + self.assertEqual(os.path.basename(ec_file), 'GCC-4.6.4.eb') + + specs = { + 'name': 'ScaLAPACK', + 'version': '2.0.2', + 'toolchain_name': 'gompi', + 'toolchain_version': '1.4.10', + 'versionsuffix': '-OpenBLAS-0.2.6-LAPACK-3.4.2', + } + (generated, ec_file) = obtain_ec_for(specs, [test_easyconfigs_path]) + self.assertFalse(generated) + self.assertEqual(os.path.basename(ec_file), 'ScaLAPACK-2.0.2-gompi-1.4.10-OpenBLAS-0.2.6-LAPACK-3.4.2.eb') + + specs = { + 'name': 'ifort', + 'versionsuffix': '-GCC-4.8.3', + } + (generated, ec_file) = obtain_ec_for(specs, [test_easyconfigs_path]) + self.assertFalse(generated) + self.assertEqual(os.path.basename(ec_file), 'ifort-2013.5.192-GCC-4.8.3.eb') + + # latest version if not specified + specs = { + 'name': 'GCC', + } + (generated, ec_file) = obtain_ec_for(specs, [test_easyconfigs_path]) + self.assertFalse(generated) + self.assertEqual(os.path.basename(ec_file), 'GCC-4.8.3.eb') + + # generate non-existing easyconfig + specs = { + 'name': 'GCC', + 'version': '5.4.3', + } + (generated, ec_file) = obtain_ec_for(specs, [test_easyconfigs_path]) + self.assertTrue(generated) + self.assertEqual(os.path.basename(ec_file), 'GCC-5.4.3.eb') + + +def suite(): + """ return all the tests in this file """ + return TestLoader().loadTestsFromTestCase(TweakTest) + +if __name__ == '__main__': + main() From a6f302a4bfb1815195ae9990a1bebfcfba2c370b Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 17 Oct 2014 15:53:06 +0200 Subject: [PATCH 087/298] take available hidden modules into account in dependency resolution --- easybuild/framework/easyconfig/tools.py | 10 ++++++++-- easybuild/tools/robot.py | 3 ++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/easybuild/framework/easyconfig/tools.py b/easybuild/framework/easyconfig/tools.py index 57b045428a..c6acf35f05 100644 --- a/easybuild/framework/easyconfig/tools.py +++ b/easybuild/framework/easyconfig/tools.py @@ -95,19 +95,25 @@ def skip_available(easyconfigs): return retained_easyconfigs -def find_resolved_modules(unprocessed, avail_modules): +def find_resolved_modules(unprocessed, avail_modules, retain_all_deps=False): """ Find easyconfigs in 1st argument which can be fully resolved using modules specified in 2nd argument """ ordered_ecs = [] new_avail_modules = avail_modules[:] new_unprocessed = [] + modtool = modules_tool() for ec in unprocessed: new_ec = ec.copy() deps = [] for dep in new_ec['dependencies']: - if not ActiveMNS().det_full_module_name(dep) in new_avail_modules: + full_mod_name = ActiveMNS().det_full_module_name(dep) + dep_resolved = full_mod_name in new_avail_modules + if not retain_all_deps: + # hidden modules need special care, since they may not be included in list of available modules + dep_resolved |= dep['hidden'] and modtool.exist([full_mod_name])[0] + if not dep_resolved: deps.append(dep) new_ec['dependencies'] = deps diff --git a/easybuild/tools/robot.py b/easybuild/tools/robot.py index 17a97754a1..966fe166eb 100644 --- a/easybuild/tools/robot.py +++ b/easybuild/tools/robot.py @@ -171,7 +171,8 @@ def resolve_dependencies(unprocessed, build_specs=None, retain_all_deps=False): last_processed_count = -1 while len(avail_modules) > last_processed_count: last_processed_count = len(avail_modules) - more_ecs, unprocessed, avail_modules = find_resolved_modules(unprocessed, avail_modules) + res = find_resolved_modules(unprocessed, avail_modules, retain_all_deps=retain_all_deps) + more_ecs, unprocessed, avail_modules = res for ec in more_ecs: if not ec['full_mod_name'] in [x['full_mod_name'] for x in ordered_ecs]: ordered_ecs.append(ec) From 85a52e27dff9eddd8763e79db4997ba749bb21b3 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 17 Oct 2014 16:01:14 +0200 Subject: [PATCH 088/298] enhance robot unit test to check for resolve_dependencies behavior with existing hidden modules --- test/framework/robot.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/test/framework/robot.py b/test/framework/robot.py index 6562817c27..a4680624e4 100644 --- a/test/framework/robot.py +++ b/test/framework/robot.py @@ -63,8 +63,8 @@ def available(self, *args): return self.avail_modules def show(self, modname): - """Dummy implementation of show, which includes full path to (existing) module files.""" - if modname in self.avail_modules: + """Dummy implementation of show, which includes full path to (available or hidden) module files.""" + if modname in self.avail_modules or os.path.basename(modname).startswith('.'): txt = ' %s:' % os.path.join('/tmp', modname) else: txt = 'Module %s not found' % modname @@ -124,6 +124,7 @@ def test_resolve_dependencies(self): 'versionsuffix': '', 'toolchain': {'name': 'dummy', 'version': 'dummy'}, 'dummy': True, + 'hidden': False, }], 'parsed': True, } @@ -135,7 +136,7 @@ def test_resolve_dependencies(self): self.assertEqual('gzip/1.4', res[0]['full_mod_name']) self.assertEqual('foo/1.2.3', res[-1]['full_mod_name']) - # hidden dependencies are found too + # hidden dependencies are found too, but only retained if they're not available (or forced to be retained hidden_dep = { 'name': 'toy', 'version': '0.0', @@ -147,7 +148,14 @@ def test_resolve_dependencies(self): easyconfig_moredeps = deepcopy(easyconfig_dep) easyconfig_moredeps['dependencies'].append(hidden_dep) easyconfig_moredeps['hiddendependencies'] = [hidden_dep] + + # toy/.0.0-deps is available and thus should be omitted res = resolve_dependencies([deepcopy(easyconfig_moredeps)]) + self.assertEqual(len(res), 2) + full_mod_names = [ec['full_mod_name'] for ec in res] + self.assertFalse('toy/.0.0-deps' in full_mod_names) + + res = resolve_dependencies([deepcopy(easyconfig_moredeps)], retain_all_deps=True) self.assertEqual(len(res), 4) # hidden dep toy/.0.0-deps (+1) depends on (fake) ictce/4.1.13 (+1) self.assertEqual('gzip/1.4', res[0]['full_mod_name']) self.assertEqual('foo/1.2.3', res[-1]['full_mod_name']) @@ -177,6 +185,7 @@ def test_resolve_dependencies(self): 'versionsuffix': '', 'toolchain': {'name': 'GCC', 'version': '4.6.3'}, 'dummy': True, + 'hidden': False, }] ecs = [deepcopy(easyconfig_dep)] build_options.update({'robot_path': self.base_easyconfig_dir}) @@ -203,6 +212,7 @@ def test_resolve_dependencies(self): 'versionsuffix': '', 'toolchain': {'name': 'dummy', 'version': 'dummy'}, 'dummy': True, + 'hidden': False, }] ecs = [deepcopy(easyconfig_dep)] res = resolve_dependencies(ecs) @@ -265,6 +275,7 @@ def test_resolve_dependencies(self): 'versionsuffix': '', 'toolchain': {'name': 'dummy', 'version': 'dummy'}, 'dummy': True, + 'hidden': False, }] ecs = [deepcopy(easyconfig_dep)] res = resolve_dependencies([deepcopy(easyconfig_dep)]) From 3698c338240c384c8568626701eff70586667d8d Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 17 Oct 2014 20:40:03 +0200 Subject: [PATCH 089/298] clear easyconfig files cache in between tests --- test/framework/utilities.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/framework/utilities.py b/test/framework/utilities.py index 39ab091e3f..e34cb37ae5 100644 --- a/test/framework/utilities.py +++ b/test/framework/utilities.py @@ -232,6 +232,7 @@ def cleanup(): # empty caches tc_utils._initial_toolchain_instances.clear() easyconfig._easyconfigs_cache.clear() + easyconfig._easyconfig_files_cache.clear() mns_toolchain._toolchain_details_cache.clear() From 43e9abd7756a7697dc391cc13381c3c0311d26bc Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 17 Oct 2014 20:40:43 +0200 Subject: [PATCH 090/298] enhance short dry run test to check easyconfigs pkg robot fallback path --- test/framework/options.py | 47 ++++++++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/test/framework/options.py b/test/framework/options.py index 6fab6cf364..4f4f54dc40 100644 --- a/test/framework/options.py +++ b/test/framework/options.py @@ -42,7 +42,7 @@ from easybuild.framework.easyconfig import MANDATORY, MODULES, OTHER, TOOLCHAIN from easybuild.tools.build_log import EasyBuildError from easybuild.tools.environment import modify_env -from easybuild.tools.filetools import read_file, write_file +from easybuild.tools.filetools import mkdir, read_file, write_file from easybuild.tools.modules import modules_tool from easybuild.tools.options import EasyBuildOptions from easybuild.tools.version import VERSION @@ -550,16 +550,15 @@ def test_search(self): os.remove(dummylogfn) def test_dry_run(self): - """Test dry runs.""" - + """Test dry run (long format).""" fd, dummylogfn = tempfile.mkstemp(prefix='easybuild-dummy', suffix='.log') os.close(fd) args = [ os.path.join(os.path.dirname(__file__), 'easyconfigs', 'gzip-1.4-GCC-4.6.3.eb'), '--dry-run', - '--robot=%s' % os.path.join(os.path.dirname(__file__), 'easyconfigs'), '--unittest-file=%s' % self.logfile, + '--robot=%s' % os.path.join(os.path.dirname(__file__), 'easyconfigs'), ] outtxt = self.eb_main(args, logfile=dummylogfn) @@ -573,12 +572,29 @@ def test_dry_run(self): regex = re.compile(r" \* \[%s\] \S+%s \(module: %s\)" % (mark, ec, mod), re.M) self.assertTrue(regex.search(outtxt), "Found match for pattern %s in '%s'" % (regex.pattern, outtxt)) + def test_dry_run_short(self): + """Test dry run (short format).""" + fd, dummylogfn = tempfile.mkstemp(prefix='easybuild-dummy', suffix='.log') + os.close(fd) + + # copy test easyconfigs to easybuild/easyconfigs subdirectory of temp directory + # to check with easyconfigs install path is auto-included in robot path + tmpdir = tempfile.mkdtemp(prefix='easybuild-easyconfigs-pkg-install-path') + mkdir(os.path.join(tmpdir, 'easybuild'), parents=True) + + test_ecs_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs') + shutil.copytree(test_ecs_dir, os.path.join(tmpdir, 'easybuild', 'easyconfigs')) + + orig_sys_path = sys.path[:] + sys.path.append(tmpdir) + for dry_run_arg in ['-D', '--dry-run-short']: open(self.logfile, 'w').write('') args = [ - os.path.join(os.path.dirname(__file__), 'easyconfigs', 'gzip-1.4-GCC-4.6.3.eb'), + os.path.join(tmpdir, 'easybuild', 'easyconfigs', 'gzip-1.4-GCC-4.6.3.eb'), dry_run_arg, - '--robot=%s' % os.path.join(os.path.dirname(__file__), 'easyconfigs'), + # purposely specifying senseless dir, to test auto-inclusion of easyconfigs pkg path in robot path + '--robot=%s' % os.path.join(tmpdir, 'robot_decoy'), '--unittest-file=%s' % self.logfile, ] outtxt = self.eb_main(args, logfile=dummylogfn) @@ -597,6 +613,10 @@ def test_dry_run(self): if os.path.exists(dummylogfn): os.remove(dummylogfn) + # cleanup + shutil.rmtree(tmpdir) + sys.path[:] = orig_sys_path + def test_try_robot_force(self): """ Test correct behavior for combination of --try-toolchain --robot --force. @@ -1012,13 +1032,15 @@ def test_allow_modules_tool_mismatch(self): def test_try(self): """Test whether --try options are taken into account.""" - ec_file = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'easyconfigs', 'toy-0.0.eb') + ecs_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'easyconfigs') + ec_file = os.path.join(ecs_path, 'toy-0.0.eb') args = [ ec_file, '--sourcepath=%s' % self.test_sourcepath, '--buildpath=%s' % self.test_buildpath, '--installpath=%s' % self.test_installpath, '--dry-run', + '--robot=%s' % ecs_path, ] test_cases = [ @@ -1054,7 +1076,7 @@ def test_recursive_try(self): tweaked_toy_ec = os.path.join(self.test_buildpath, 'toy-0.0-tweaked.eb') shutil.copy2(os.path.join(ecs_path, 'toy-0.0.eb'), tweaked_toy_ec) f = open(tweaked_toy_ec, 'a') - f.write("dependencies = [('gzip', '1.4')]") # add fictious dependency + f.write("dependencies = [('gzip', '1.4')]\n") # add fictious dependency f.close() sourcepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'sandbox', 'sources') @@ -1092,13 +1114,18 @@ def test_recursive_try(self): #mod_regex = re.compile("%s \(module: .*%s\)$" % (ec, mod), re.M) self.assertTrue(mod_regex.search(outtxt), "Pattern %s found in %s" % (mod_regex.pattern, outtxt)) + # clear fictious dependency + f = open(tweaked_toy_ec, 'a') + f.write("dependencies = []\n") + f.close() + # no recursive try if --(try-)software(-X) is involved for extra_args in [['--try-software-version=1.2.3'], ['--software-version=1.2.3']]: outtxt = self.eb_main(args + extra_args, raise_error=True) - for mod in ['toy/1.2.3-gompi-1.4.10', 'gzip/1.4-gompi-1.4.10', 'gompi/1.4.10', 'GCC/4.7.2']: + for mod in ['toy/1.2.3-gompi-1.4.10', 'gompi/1.4.10', 'GCC/4.7.2']: mod_regex = re.compile("\(module: %s\)$" % mod, re.M) self.assertTrue(mod_regex.search(outtxt), "Pattern %s found in %s" % (mod_regex.pattern, outtxt)) - for mod in ['gzip/1.2.3-gompi-1.4.10']: + for mod in ['gompi/1.2.3', 'GCC/1.2.3']: mod_regex = re.compile("\(module: %s\)$" % mod, re.M) self.assertFalse(mod_regex.search(outtxt), "Pattern %s found in %s" % (mod_regex.pattern, outtxt)) From bdb84d72c3e8942736ca02f27413b9fbc7cb7c8d Mon Sep 17 00:00:00 2001 From: Jens Timmerman Date: Tue, 21 Oct 2014 15:27:34 +0200 Subject: [PATCH 091/298] added a check for the http return code + download progress report * Check the http return code before starting a download, urllib.retrieve does not do this by default * show a download report ' X kb downloaded of XXX total kb (XX% complete) xxx kbps' --- easybuild/tools/filetools.py | 37 +++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/easybuild/tools/filetools.py b/easybuild/tools/filetools.py index 4e1ece1bc9..7e47a5b0f3 100644 --- a/easybuild/tools/filetools.py +++ b/easybuild/tools/filetools.py @@ -253,10 +253,44 @@ def download_file(filename, url, path): downloaded = False attempt_cnt = 0 + # use this functions's scope for the variable we share with our inner function + download_file.last_time = time.time() + download_file.last_block = 0 + # internal function to report on download progress + def report(block, blocksize, bytesize): + """ + This is a reporthook for urlretrieve, it takes 3 integers as arguments + the current downloaded block, the total ammount of blocks, and the blocksize + logs the download progress every 10 seconds + """ + if download_file.last_time + 10 < time.time(): + newblocks = block - download_file.last_block + download_file.last_block = block + + _log.info('download report: %d kb of %d kb (%d %%, %d kbps)', block * blocksize, bytesize, int(block * blocksize * 100 / bytesize), + (blocksize * newblocks) / 1024 // (time.time() - download_file.last_time)) + + download_file.last_time = time.time() + + # try downloading three times max. while not downloaded and attempt_cnt < 3: + # get http response code first before downloading file + urlfile = urllib.urlopen(url) + response_code = urlfile.getcode() + urlfile.close() + + _log.debug('http response code for given url: %d', response_code) + if response_code == 404: + _log.warning('url %s was not found (404), not trying again', url) + return None + + download_file.last_time = time.time() + download_file.last_block = 0 + + (_, httpmsg) = urllib.urlretrieve(url, path, reporthook=report) + _log.debug('headers of download: %s', httpmsg) - (_, httpmsg) = urllib.urlretrieve(url, path) if httpmsg.type == "text/html" and not filename.endswith('.html'): _log.warning("HTML file downloaded but not expecting it, so assuming invalid download.") @@ -273,6 +307,7 @@ def download_file(filename, url, path): attempt_cnt += 1 _log.warning("Downloading failed at attempt %s, retrying..." % attempt_cnt) + # failed to download after multiple attempts return None From 8494e9b3efdb182c01edc11cfe4dfa5f933c9827 Mon Sep 17 00:00:00 2001 From: pescobar Date: Wed, 22 Oct 2014 22:01:24 +0200 Subject: [PATCH 092/298] scape modloadmsg --- easybuild/tools/module_generator.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/easybuild/tools/module_generator.py b/easybuild/tools/module_generator.py index 2d51d7fcd9..3751d2da5d 100644 --- a/easybuild/tools/module_generator.py +++ b/easybuild/tools/module_generator.py @@ -33,6 +33,7 @@ @author: Fotis Georgatos (Uni.Lu) """ import os +import re import tempfile from vsc.utils import fancylogger @@ -209,6 +210,7 @@ def msg_on_load(self, msg): """ Add a message that should be printed when loading the module. """ + msg = re.escape(msg) return '\n'.join([ "", "if [ module-info mode load ] {", From 4e31c1babbc2f48406ba71d771116b443db4c0d9 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 23 Oct 2014 19:56:00 +0200 Subject: [PATCH 093/298] include info log msg with name/location of used easyblock --- easybuild/framework/easyblock.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 662daf8bf8..a9b199e823 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -38,6 +38,7 @@ import copy import glob +import inspect import os import shutil import stat @@ -113,6 +114,7 @@ def __init__(self, ec): Initialize the EasyBlock instance. @param ec: a parsed easyconfig file (EasyConfig instance) """ + # keep track of original working directory, so we can go back there self.orig_workdir = os.getcwd() @@ -167,6 +169,12 @@ def __init__(self, ec): # list of loaded modules self.loaded_modules = [] + # iterate configure/build/options + self.iter_opts = {} + + # sanity check fail error messages to report (if any) + self.sanity_check_fail_msgs = [] + # robot path self.robot_path = build_option('robot_path') @@ -176,14 +184,9 @@ def __init__(self, ec): # keep track of original environment, so we restore it if needed self.orig_environ = copy.deepcopy(os.environ) - # at the end of __init__, initialise the logging + # initialize logger self._init_log() - - # iterate configure/build/options - self.iter_opts = {} - - # sanity check fail error messages to report (if any) - self.sanity_check_fail_msgs = [] + self.log.info("This is easyblock %s at %s" % (self.__class__.__name__, inspect.getmodule(self))) # should we keep quiet? self.silent = build_option('silent') From a8383ea68762f0bee4ea5ca52b36e84800c97e4a Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 23 Oct 2014 20:11:25 +0200 Subject: [PATCH 094/298] enhance easyblock unit tests to check for 'This is easyblock' log msg --- test/framework/easyblock.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/framework/easyblock.py b/test/framework/easyblock.py index ee959f59b6..52a028b934 100644 --- a/test/framework/easyblock.py +++ b/test/framework/easyblock.py @@ -115,6 +115,12 @@ def check_extra_options_format(extra_options): sys.stdout.close() sys.stdout = stdoutorig + # check whether 'This is easyblock' log message is there + tup = ('EasyBlock', 'easybuild.framework.easyblock', 'easybuild/framework/easyblock.pyc') + eb_log_msg_re = re.compile(r"INFO This is easyblock %s at " % tup, re.M) + logtxt = read_file(eb.logfile) + self.assertTrue(eb_log_msg_re.search(logtxt), "Pattern '%s' found in: %s" % (eb_log_msg_re.pattern, logtxt)) + # test extensioneasyblock, as extension exeb1 = ExtensionEasyBlock(eb, {'name': 'foo', 'version': '0.0'}) self.assertEqual(exeb1.cfg['name'], 'foo') @@ -383,6 +389,12 @@ def test_get_easyblock_instance(self): eb = get_easyblock_instance(ec) self.assertTrue(isinstance(eb, EB_toy)) + # check whether 'This is easyblock' log message is there + tup = ('EB_toy', 'easybuild.easyblocks.toy', '.*test/framework/sandbox/easybuild/easyblocks/toy.pyc') + eb_log_msg_re = re.compile(r"INFO This is easyblock %s at " % tup, re.M) + logtxt = read_file(eb.logfile) + self.assertTrue(eb_log_msg_re.search(logtxt), "Pattern '%s' found in: %s" % (eb_log_msg_re.pattern, logtxt)) + def test_obtain_file(self): """Test obtain_file method.""" toy_tarball = 'toy-0.0.tar.gz' From 62cf114b2520b57cd6c6b4824a8cbe08cfbc6cf8 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 23 Oct 2014 20:23:57 +0200 Subject: [PATCH 095/298] check for number of available sources when determining where to apply patches --- easybuild/framework/easyblock.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 662daf8bf8..9395f0bbed 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -1271,8 +1271,13 @@ def patch_step(self, beginpath=None): srcpathsuffix = patch['copy'] copy = True - if not beginpath: - beginpath = self.src[srcind]['finalpath'] + if beginpath is None: + src_cnt = len(self.src) + if srcind < src_cnt: + beginpath = self.src[srcind]['finalpath'] + else: + tup = (patch['name'], srcind, src_cnt, self.src) + self.log.error("Can't apply patch %s to source at index %s, only %s sources listed: %s" % tup) src = os.path.abspath("%s/%s" % (beginpath, srcpathsuffix)) From df2b5acfd7a6260206150134561151b615db22a7 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 23 Oct 2014 20:30:06 +0200 Subject: [PATCH 096/298] add unit test for patch_step --- test/framework/easyblock.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/framework/easyblock.py b/test/framework/easyblock.py index ee959f59b6..c5d60415b5 100644 --- a/test/framework/easyblock.py +++ b/test/framework/easyblock.py @@ -516,6 +516,25 @@ def test_exclude_path_to_top_of_module_tree(self): os.environ['EASYBUILD_MODULE_NAMING_SCHEME'] = self.orig_module_naming_scheme init_config(build_options=build_options) + def test_patch_step(self): + """Test patch step.""" + ec = process_easyconfig(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'easyconfigs', 'toy-0.0.eb'))[0] + orig_sources = ec['ec']['sources'][:] + + # test applying patches without sources + ec['ec']['sources'] = [] + eb = EasyBlock(ec['ec']) + eb.fetch_step() + eb.extract_step() + self.assertErrorRegex(EasyBuildError, '.*', eb.patch_step) + + # test actual patching of unpacked sources + ec['ec']['sources'] = orig_sources + eb = EasyBlock(ec['ec']) + eb.fetch_step() + eb.extract_step() + eb.patch_step() + def tearDown(self): """ make sure to remove the temporary file """ super(EasyBlockTest, self).tearDown() From d4e13da74f4581276e00138b4e40d3c7be19f1be Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 24 Oct 2014 08:13:57 +0200 Subject: [PATCH 097/298] fix 'This is easyblock' regex pattern --- test/framework/easyblock.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/framework/easyblock.py b/test/framework/easyblock.py index 52a028b934..5ba74eae66 100644 --- a/test/framework/easyblock.py +++ b/test/framework/easyblock.py @@ -116,7 +116,7 @@ def check_extra_options_format(extra_options): sys.stdout = stdoutorig # check whether 'This is easyblock' log message is there - tup = ('EasyBlock', 'easybuild.framework.easyblock', 'easybuild/framework/easyblock.pyc') + tup = ('EasyBlock', 'easybuild.framework.easyblock', 'easybuild/framework/easyblock.pyc*') eb_log_msg_re = re.compile(r"INFO This is easyblock %s at " % tup, re.M) logtxt = read_file(eb.logfile) self.assertTrue(eb_log_msg_re.search(logtxt), "Pattern '%s' found in: %s" % (eb_log_msg_re.pattern, logtxt)) @@ -390,7 +390,7 @@ def test_get_easyblock_instance(self): self.assertTrue(isinstance(eb, EB_toy)) # check whether 'This is easyblock' log message is there - tup = ('EB_toy', 'easybuild.easyblocks.toy', '.*test/framework/sandbox/easybuild/easyblocks/toy.pyc') + tup = ('EB_toy', 'easybuild.easyblocks.toy', '.*test/framework/sandbox/easybuild/easyblocks/toy.pyc*') eb_log_msg_re = re.compile(r"INFO This is easyblock %s at " % tup, re.M) logtxt = read_file(eb.logfile) self.assertTrue(eb_log_msg_re.search(logtxt), "Pattern '%s' found in: %s" % (eb_log_msg_re.pattern, logtxt)) From 39d9a2925559c142da16ea628d25aa2ce27f9174 Mon Sep 17 00:00:00 2001 From: Alan O'Cais Date: Fri, 24 Oct 2014 09:45:53 +0200 Subject: [PATCH 098/298] Added a toolchain for Parastation MPICH and GCC (+ full toolchain) --- easybuild/toolchains/gpsmpi.py | 36 ++++++++++++++++++++++++++++ easybuild/toolchains/gpsolf.py | 43 ++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100755 easybuild/toolchains/gpsmpi.py create mode 100755 easybuild/toolchains/gpsolf.py diff --git a/easybuild/toolchains/gpsmpi.py b/easybuild/toolchains/gpsmpi.py new file mode 100755 index 0000000000..4d79d8cde6 --- /dev/null +++ b/easybuild/toolchains/gpsmpi.py @@ -0,0 +1,36 @@ +## +# Copyright 2012-2014 Ghent University +# +# This file is part of EasyBuild, +# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en), +# with support of Ghent University (http://ugent.be/hpc), +# the Flemish Supercomputer Centre (VSC) (https://vscentrum.be/nl/en), +# the Hercules foundation (http://www.herculesstichting.be/in_English) +# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en). +# +# http://github.com/hpcugent/easybuild +# +# EasyBuild is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation v2. +# +# EasyBuild is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with EasyBuild. If not, see . +## +""" +EasyBuild support for gpsmpi compiler toolchain (includes GCC and Parastation MPICH). + +""" + +from easybuild.toolchains.compiler.gcc import Gcc +from easybuild.toolchains.mpi.mpich import Mpich + + +class Gpsmpi(Gcc, Mpich): + """Compiler toolchain with GCC and Parastation MPICH.""" + NAME = 'gpsmpi' diff --git a/easybuild/toolchains/gpsolf.py b/easybuild/toolchains/gpsolf.py new file mode 100755 index 0000000000..1e10e97926 --- /dev/null +++ b/easybuild/toolchains/gpsolf.py @@ -0,0 +1,43 @@ +## +# Copyright 2013-2014 Ghent University +# +# This file is triple-licensed under GPLv2 (see below), MIT, and +# BSD three-clause licenses. +# +# This file is part of EasyBuild, +# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en), +# with support of Ghent University (http://ugent.be/hpc), +# the Flemish Supercomputer Centre (VSC) (https://vscentrum.be/nl/en), +# the Hercules foundation (http://www.herculesstichting.be/in_English) +# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en). +# +# http://github.com/hpcugent/easybuild +# +# EasyBuild is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation v2. +# +# EasyBuild is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with EasyBuild. If not, see . +## +""" +EasyBuild support for gmpolf compiler toolchain (includes GCC, Parastation MPICH, OpenBLAS, LAPACK, ScaLAPACK and FFTW). + +""" + +from easybuild.toolchains.compiler.gcc import Gcc +from easybuild.toolchains.fft.fftw import Fftw +from easybuild.toolchains.linalg.openblas import OpenBLAS +from easybuild.toolchains.linalg.scalapack import ScaLAPACK +from easybuild.toolchains.mpi.mpich import Mpich + + +class Gpsolf(Gcc, Mpich, OpenBLAS, ScaLAPACK, Fftw): +@author: Bart Verleye (University of Auckland) + """Compiler toolchain with GCC, Parastation MPICH, OpenBLAS, ScaLAPACK and FFTW.""" + NAME = 'gpsolf' From 347af62f800f1a52818dd51faa87ef38ab1a8854 Mon Sep 17 00:00:00 2001 From: ocaisa Date: Fri, 24 Oct 2014 09:51:11 +0200 Subject: [PATCH 099/298] Update gpsolf.py --- easybuild/toolchains/gpsolf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/toolchains/gpsolf.py b/easybuild/toolchains/gpsolf.py index 1e10e97926..6bffb8974c 100755 --- a/easybuild/toolchains/gpsolf.py +++ b/easybuild/toolchains/gpsolf.py @@ -38,6 +38,6 @@ class Gpsolf(Gcc, Mpich, OpenBLAS, ScaLAPACK, Fftw): -@author: Bart Verleye (University of Auckland) + """Compiler toolchain with GCC, Parastation MPICH, OpenBLAS, ScaLAPACK and FFTW.""" NAME = 'gpsolf' From f496f85fec0a5e1dcf725b1bfb354ddb03eca3f8 Mon Sep 17 00:00:00 2001 From: Alan O'Cais Date: Fri, 24 Oct 2014 09:58:12 +0200 Subject: [PATCH 100/298] Added support for Parastation MPICH toolchains with Intel compilers --- easybuild/toolchains/intel-para.py | 44 ++++++++++++++++++++++++++++++ easybuild/toolchains/ipsmpi.py | 39 ++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 easybuild/toolchains/intel-para.py create mode 100755 easybuild/toolchains/ipsmpi.py diff --git a/easybuild/toolchains/intel-para.py b/easybuild/toolchains/intel-para.py new file mode 100644 index 0000000000..f146c45ad3 --- /dev/null +++ b/easybuild/toolchains/intel-para.py @@ -0,0 +1,44 @@ +## +# Copyright 2012-2013 Ghent University +# +# This file is part of EasyBuild, +# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en), +# with support of Ghent University (http://ugent.be/hpc), +# the Flemish Supercomputer Centre (VSC) (https://vscentrum.be/nl/en), +# the Hercules foundation (http://www.herculesstichting.be/in_English) +# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en). +# +# http://github.com/hpcugent/easybuild +# +# EasyBuild is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation v2. +# +# EasyBuild is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with EasyBuild. If not, see . +## +""" +EasyBuild support for intel compiler toolchain (includes Intel compilers (icc, ifort), Parastation MPICH, +Intel Math Kernel Library (MKL), and Intel FFTW wrappers). + +""" + +from easybuild.toolchains.compiler.inteliccifort import IntelIccIfort +from easybuild.toolchains.fft.intelfftw import IntelFFTW +from easybuild.toolchains.mpi.mpich import Mpich +from easybuild.toolchains.linalg.intelmkl import IntelMKL + + +class IntelPara(IntelIccIfort, Mpich, IntelMKL, IntelFFTW): + """ + Compiler toolchain with Intel compilers (icc/ifort), Parastation MPICH, + Intel Math Kernel Library (MKL) and Intel FFTW wrappers. + """ + NAME = 'intel-para' + BLACS_LIB = ["mkl_blacs_intelmpi%(lp64)s"] + diff --git a/easybuild/toolchains/ipsmpi.py b/easybuild/toolchains/ipsmpi.py new file mode 100755 index 0000000000..f94fb27b3f --- /dev/null +++ b/easybuild/toolchains/ipsmpi.py @@ -0,0 +1,39 @@ +## +# Copyright 2012-2014 Ghent University +# +# This file is part of EasyBuild, +# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en), +# with support of Ghent University (http://ugent.be/hpc), +# the Flemish Supercomputer Centre (VSC) (https://vscentrum.be/nl/en), +# the Hercules foundation (http://www.herculesstichting.be/in_English) +# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en). +# +# http://github.com/hpcugent/easybuild +# +# EasyBuild is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation v2. +# +# EasyBuild is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with EasyBuild. If not, see . +## +""" +EasyBuild support for intel compiler toolchain (includes Intel compilers (icc, ifort), Parastation MPICH2). + +""" + +from easybuild.toolchains.compiler.inteliccifort import IntelIccIfort +from easybuild.toolchains.mpi.mpich import Mpich + + + +class Ipsmpi(IntelIccIfort, Mpich): + """ + Compiler toolchain with Intel compilers (icc/ifort), Parastation MPICH. + """ + NAME = 'ipsmpi' From e31c7c1f5d3fcb1c253c57ccd949abcb6dc3b623 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Fri, 24 Oct 2014 11:54:19 +0200 Subject: [PATCH 101/298] just escape chars in CHARS_TO_ESCAPE --- easybuild/tools/module_generator.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/easybuild/tools/module_generator.py b/easybuild/tools/module_generator.py index 3751d2da5d..1675dca286 100644 --- a/easybuild/tools/module_generator.py +++ b/easybuild/tools/module_generator.py @@ -47,6 +47,8 @@ _log = fancylogger.getLogger('module_generator', fname=False) +# chars we want to escape in the generated modulefiles +CHARS_TO_ESCAPE = ["$"] class ModuleGenerator(object): """ @@ -210,7 +212,8 @@ def msg_on_load(self, msg): """ Add a message that should be printed when loading the module. """ - msg = re.escape(msg) + for element in CHARS_TO_ESCAPE: + msg = re.sub(r'((? Date: Fri, 24 Oct 2014 13:31:58 +0200 Subject: [PATCH 102/298] Added generic support for Intel plus MPICH. Built ParaStation MPI specific toolchains using these templates. --- easybuild/toolchains/impich.py | 38 ++++++++++++++++++++++++++++++++++ easybuild/toolchains/ipsmpi.py | 9 ++++---- 2 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 easybuild/toolchains/impich.py diff --git a/easybuild/toolchains/impich.py b/easybuild/toolchains/impich.py new file mode 100644 index 0000000000..efe9c513a2 --- /dev/null +++ b/easybuild/toolchains/impich.py @@ -0,0 +1,38 @@ +## +# Copyright 2013-2014 Ghent University +# +# This file is part of EasyBuild, +# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en), +# with support of Ghent University (http://ugent.be/hpc), +# the Flemish Supercomputer Centre (VSC) (https://vscentrum.be/nl/en), +# the Hercules foundation (http://www.herculesstichting.be/in_English) +# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en). +# +# http://github.com/hpcugent/easybuild +# +# EasyBuild is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation v2. +# +# EasyBuild is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with EasyBuild. If not, see . +## +""" +EasyBuild support for impich compiler toolchain (includes Intel compilers (icc, ifort), MPICH. + +""" + +from easybuild.toolchains.compiler.inteliccifort import IntelIccIfort +from easybuild.toolchains.mpi.mpich import Mpich + + +class Impich(IntelIccIfort, Mpich): + """ + Compiler toolchain with Intel compilers (icc/ifort), MPICH. + """ + NAME = 'impich' diff --git a/easybuild/toolchains/ipsmpi.py b/easybuild/toolchains/ipsmpi.py index f94fb27b3f..eb27704e1d 100755 --- a/easybuild/toolchains/ipsmpi.py +++ b/easybuild/toolchains/ipsmpi.py @@ -23,17 +23,18 @@ # along with EasyBuild. If not, see . ## """ -EasyBuild support for intel compiler toolchain (includes Intel compilers (icc, ifort), Parastation MPICH2). +EasyBuild support for intel compiler toolchain (includes Intel compilers (icc, ifort), Parastation MPICH). """ -from easybuild.toolchains.compiler.inteliccifort import IntelIccIfort -from easybuild.toolchains.mpi.mpich import Mpich +from easybuild.toolchains.impich import Impich -class Ipsmpi(IntelIccIfort, Mpich): +class Ipsmpi(Impich): """ Compiler toolchain with Intel compilers (icc/ifort), Parastation MPICH. """ NAME = 'ipsmpi' + # Use Parastation naming + MPI_MODULE_NAME = ["psmpi"] From 73c361f5c4eb0419d9281d0534a26bd977f22823 Mon Sep 17 00:00:00 2001 From: Alan O'Cais Date: Fri, 24 Oct 2014 13:36:50 +0200 Subject: [PATCH 103/298] Added forgotten updates for last commit --- easybuild/toolchains/impmkl.py | 9 ++++----- easybuild/toolchains/intel-para.py | 6 +++--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/easybuild/toolchains/impmkl.py b/easybuild/toolchains/impmkl.py index ba0f1521b6..383753d1db 100644 --- a/easybuild/toolchains/impmkl.py +++ b/easybuild/toolchains/impmkl.py @@ -23,20 +23,19 @@ # along with EasyBuild. If not, see . ## """ -EasyBuild support for impmkl compiler toolchain (includes Intel compilers (icc, ifort), MPICH2, +EasyBuild support for impmkl compiler toolchain (includes Intel compilers (icc, ifort), MPICH, Intel Math Kernel Library (MKL) , and Intel FFTW wrappers. @author: Kenneth Hoste (Ghent University) """ -from easybuild.toolchains.compiler.inteliccifort import IntelIccIfort +from easybuild.toolchains.impich import Impich from easybuild.toolchains.fft.intelfftw import IntelFFTW -from easybuild.toolchains.mpi.mpich2 import Mpich2 from easybuild.toolchains.linalg.intelmkl import IntelMKL -class Impmkl(IntelIccIfort, Mpich2, IntelMKL, IntelFFTW): +class Impmkl(Impich, IntelMKL, IntelFFTW): """ - Compiler toolchain with Intel compilers (icc/ifort), MPICH2, + Compiler toolchain with Intel compilers (icc/ifort), MPICH, Intel Math Kernel Library (MKL) and Intel FFTW wrappers. """ NAME = 'impmkl' diff --git a/easybuild/toolchains/intel-para.py b/easybuild/toolchains/intel-para.py index f146c45ad3..9ea7a259e1 100644 --- a/easybuild/toolchains/intel-para.py +++ b/easybuild/toolchains/intel-para.py @@ -28,17 +28,17 @@ """ -from easybuild.toolchains.compiler.inteliccifort import IntelIccIfort +from easybuild.toolchains.ipsmpi import Ipsmpi from easybuild.toolchains.fft.intelfftw import IntelFFTW -from easybuild.toolchains.mpi.mpich import Mpich from easybuild.toolchains.linalg.intelmkl import IntelMKL -class IntelPara(IntelIccIfort, Mpich, IntelMKL, IntelFFTW): +class IntelPara(Ipsmpi, IntelMKL, IntelFFTW): """ Compiler toolchain with Intel compilers (icc/ifort), Parastation MPICH, Intel Math Kernel Library (MKL) and Intel FFTW wrappers. """ NAME = 'intel-para' + # Parastation MPI needs to be matched with the IntelMPI blacs library BLACS_LIB = ["mkl_blacs_intelmpi%(lp64)s"] From 8644a8a3ebd238c896dca71c1684a062f8c502ae Mon Sep 17 00:00:00 2001 From: ocaisa Date: Fri, 24 Oct 2014 13:39:18 +0200 Subject: [PATCH 104/298] Update ipsmpi.py --- easybuild/toolchains/ipsmpi.py | 1 - 1 file changed, 1 deletion(-) diff --git a/easybuild/toolchains/ipsmpi.py b/easybuild/toolchains/ipsmpi.py index eb27704e1d..8b99018284 100755 --- a/easybuild/toolchains/ipsmpi.py +++ b/easybuild/toolchains/ipsmpi.py @@ -30,7 +30,6 @@ from easybuild.toolchains.impich import Impich - class Ipsmpi(Impich): """ Compiler toolchain with Intel compilers (icc/ifort), Parastation MPICH. From d1060a33114335a4d1d663ab2d752afb13494ba2 Mon Sep 17 00:00:00 2001 From: Alan O'Cais Date: Fri, 24 Oct 2014 13:50:48 +0200 Subject: [PATCH 105/298] Clean up support for GCC and MPICH, add specific toolchains for Parastation MPI --- easybuild/toolchains/gmpich.py | 36 ++++++++++++++++++++++++++++++++++ easybuild/toolchains/gpsmpi.py | 5 +++-- easybuild/toolchains/gpsolf.py | 6 ++---- 3 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 easybuild/toolchains/gmpich.py diff --git a/easybuild/toolchains/gmpich.py b/easybuild/toolchains/gmpich.py new file mode 100644 index 0000000000..a3d7c56a2b --- /dev/null +++ b/easybuild/toolchains/gmpich.py @@ -0,0 +1,36 @@ +## +# Copyright 2012-2014 Ghent University +# +# This file is part of EasyBuild, +# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en), +# with support of Ghent University (http://ugent.be/hpc), +# the Flemish Supercomputer Centre (VSC) (https://vscentrum.be/nl/en), +# the Hercules foundation (http://www.herculesstichting.be/in_English) +# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en). +# +# http://github.com/hpcugent/easybuild +# +# EasyBuild is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation v2. +# +# EasyBuild is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with EasyBuild. If not, see . +## +""" +EasyBuild support for gmpich compiler toolchain (includes GCC and MPICH). + +""" + +from easybuild.toolchains.compiler.gcc import Gcc +from easybuild.toolchains.mpi.mpich import Mpich + + +class Gmpich(Gcc, Mpich): + """Compiler toolchain with GCC and MPICH.""" + NAME = 'gmpich' diff --git a/easybuild/toolchains/gpsmpi.py b/easybuild/toolchains/gpsmpi.py index 4d79d8cde6..75a4174e77 100755 --- a/easybuild/toolchains/gpsmpi.py +++ b/easybuild/toolchains/gpsmpi.py @@ -27,10 +27,11 @@ """ -from easybuild.toolchains.compiler.gcc import Gcc -from easybuild.toolchains.mpi.mpich import Mpich +from easybuild.toolchains.gmpich import Gmpich class Gpsmpi(Gcc, Mpich): """Compiler toolchain with GCC and Parastation MPICH.""" NAME = 'gpsmpi' + # Use Parastation naming + MPI_MODULE_NAME = ["psmpi"] diff --git a/easybuild/toolchains/gpsolf.py b/easybuild/toolchains/gpsolf.py index 1e10e97926..45e51b4b07 100755 --- a/easybuild/toolchains/gpsolf.py +++ b/easybuild/toolchains/gpsolf.py @@ -30,14 +30,12 @@ """ -from easybuild.toolchains.compiler.gcc import Gcc +from easybuild.toolchains.gpsmpi import Gpsmpi from easybuild.toolchains.fft.fftw import Fftw from easybuild.toolchains.linalg.openblas import OpenBLAS from easybuild.toolchains.linalg.scalapack import ScaLAPACK -from easybuild.toolchains.mpi.mpich import Mpich -class Gpsolf(Gcc, Mpich, OpenBLAS, ScaLAPACK, Fftw): -@author: Bart Verleye (University of Auckland) +class Gpsolf(Gmpich, OpenBLAS, ScaLAPACK, Fftw): """Compiler toolchain with GCC, Parastation MPICH, OpenBLAS, ScaLAPACK and FFTW.""" NAME = 'gpsolf' From 602d9eb7060872bf47c79c29bb8823cca7325cf9 Mon Sep 17 00:00:00 2001 From: Alan O'Cais Date: Fri, 24 Oct 2014 13:59:38 +0200 Subject: [PATCH 106/298] Make sure we are pushing the correct classes --- easybuild/toolchains/gpsmpi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/toolchains/gpsmpi.py b/easybuild/toolchains/gpsmpi.py index 75a4174e77..b1dc8e0ab5 100755 --- a/easybuild/toolchains/gpsmpi.py +++ b/easybuild/toolchains/gpsmpi.py @@ -30,7 +30,7 @@ from easybuild.toolchains.gmpich import Gmpich -class Gpsmpi(Gcc, Mpich): +class Gpsmpi(Gmpich): """Compiler toolchain with GCC and Parastation MPICH.""" NAME = 'gpsmpi' # Use Parastation naming From df1614e80d615bd09679af1352828575b848adeb Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 28 Oct 2014 18:58:19 +0100 Subject: [PATCH 107/298] fix remark w.r.t. 'This is easyblock' log message --- easybuild/framework/easyblock.py | 5 ++++- test/framework/easyblock.py | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index a9b199e823..e479265a59 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -186,7 +186,6 @@ def __init__(self, ec): # initialize logger self._init_log() - self.log.info("This is easyblock %s at %s" % (self.__class__.__name__, inspect.getmodule(self))) # should we keep quiet? self.silent = build_option('silent') @@ -222,6 +221,10 @@ def _init_log(self): self.log.info(this_is_easybuild()) + this_module = inspect.getmodule(self) + tup = (self.__class__.__name__, this_module.__name__, this_module.__file__) + self.log.info("This is easyblock %s from module %s (%s)" % tup) + def close_log(self): """ Shutdown the logger. diff --git a/test/framework/easyblock.py b/test/framework/easyblock.py index 5ba74eae66..6d4bfce241 100644 --- a/test/framework/easyblock.py +++ b/test/framework/easyblock.py @@ -116,8 +116,8 @@ def check_extra_options_format(extra_options): sys.stdout = stdoutorig # check whether 'This is easyblock' log message is there - tup = ('EasyBlock', 'easybuild.framework.easyblock', 'easybuild/framework/easyblock.pyc*') - eb_log_msg_re = re.compile(r"INFO This is easyblock %s at " % tup, re.M) + tup = ('EasyBlock', 'easybuild.framework.easyblock', '.*easybuild/framework/easyblock.pyc*') + eb_log_msg_re = re.compile(r"INFO This is easyblock %s from module %s (%s)" % tup, re.M) logtxt = read_file(eb.logfile) self.assertTrue(eb_log_msg_re.search(logtxt), "Pattern '%s' found in: %s" % (eb_log_msg_re.pattern, logtxt)) @@ -391,7 +391,7 @@ def test_get_easyblock_instance(self): # check whether 'This is easyblock' log message is there tup = ('EB_toy', 'easybuild.easyblocks.toy', '.*test/framework/sandbox/easybuild/easyblocks/toy.pyc*') - eb_log_msg_re = re.compile(r"INFO This is easyblock %s at " % tup, re.M) + eb_log_msg_re = re.compile(r"INFO This is easyblock %s from module %s (%s)" % tup, re.M) logtxt = read_file(eb.logfile) self.assertTrue(eb_log_msg_re.search(logtxt), "Pattern '%s' found in: %s" % (eb_log_msg_re.pattern, logtxt)) From 732e8e859f3201e24acac94ac5f05611d08d838e Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 28 Oct 2014 21:18:36 +0100 Subject: [PATCH 108/298] remarks fixed --- easybuild/framework/easyblock.py | 41 ++++++++++++++++---------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 9395f0bbed..fb05e78ba3 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -1259,33 +1259,32 @@ def patch_step(self, beginpath=None): for patch in self.patches: self.log.info("Applying patch %s" % patch['name']) - copy = False - # default: patch first source - srcind = 0 - if 'source' in patch: - srcind = patch['source'] - srcpathsuffix = '' - if 'sourcepath' in patch: - srcpathsuffix = patch['sourcepath'] - elif 'copy' in patch: - srcpathsuffix = patch['copy'] - copy = True + # patch source at specified index (first source if not specified) + srcind = patch.get('source', 0) + # if patch level is specified, use that (otherwise let apply_patch derive patch level) + level = patch.get('level', None) + # determine suffix of source path to apply patch in (if any) + srcpathsuffix = patch.get('sourcepath', patch.get('copy', '')) + # determine whether 'patch' file should be copied rather than applied + copy_patch = 'copy' in patch and not 'sourcepath' in patch + + tup = (srcind, level, srcpathsuffix, copy) + self.log.debug("Source index: %s; patch level: %s; source path suffix: %s; copy patch: %s" % tup) if beginpath is None: - src_cnt = len(self.src) - if srcind < src_cnt: + try: beginpath = self.src[srcind]['finalpath'] - else: - tup = (patch['name'], srcind, src_cnt, self.src) - self.log.error("Can't apply patch %s to source at index %s, only %s sources listed: %s" % tup) + self.log.debug("Determine begin path for patch %s: %s" % (patch['name'], beginpath)) + except IndexError, err: + tup = (patch['name'], srcind, self.src, err) + self.log.error("Can't apply patch %s to source at index %s of list %s: %s" % tup) + else: + self.log.debug("Using specified begin path for patch %s: %s" % (patch['name'], beginpath)) src = os.path.abspath("%s/%s" % (beginpath, srcpathsuffix)) + self.log.debug("Applying patch %s in path %s" % (patch, src)) - level = None - if 'level' in patch: - level = patch['level'] - - if not apply_patch(patch['path'], src, copy=copy, level=level): + if not apply_patch(patch['path'], src, copy=copy_patch, level=level): self.log.error("Applying patch %s failed" % patch['name']) def prepare_step(self): From 2a975aa86d86dcd07b7beb426d2f0abb4b4b8b86 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 29 Oct 2014 09:11:51 +0100 Subject: [PATCH 109/298] fix tests that break when installed easyconfigs pkg is present --- easybuild/main.py | 6 +++--- test/framework/options.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/easybuild/main.py b/easybuild/main.py index 6c323db342..5be168f372 100644 --- a/easybuild/main.py +++ b/easybuild/main.py @@ -89,9 +89,9 @@ def find_easyconfigs_by_specs(build_specs, robot_path, try_to_generate, testing= _log.warning("Failed to remove generated easyconfig file %s: %s" % (ec_file, err)) # don't use a generated easyconfig unless generation was requested (using a --try-X option) - print_error(("Unable to find an easyconfig for the given specifications: %s; " - "to make EasyBuild try to generate a matching easyconfig, " - "use the --try-X options ") % build_specs, log=_log) + _log.error(("Unable to find an easyconfig for the given specifications: %s; " + "to make EasyBuild try to generate a matching easyconfig, " + "use the --try-X options ") % build_specs) return [(ec_file, generated)] diff --git a/test/framework/options.py b/test/framework/options.py index 4f4f54dc40..507e3368fe 100644 --- a/test/framework/options.py +++ b/test/framework/options.py @@ -578,7 +578,7 @@ def test_dry_run_short(self): os.close(fd) # copy test easyconfigs to easybuild/easyconfigs subdirectory of temp directory - # to check with easyconfigs install path is auto-included in robot path + # to check whether easyconfigs install path is auto-included in robot path tmpdir = tempfile.mkdtemp(prefix='easybuild-easyconfigs-pkg-install-path') mkdir(os.path.join(tmpdir, 'easybuild'), parents=True) @@ -586,7 +586,7 @@ def test_dry_run_short(self): shutil.copytree(test_ecs_dir, os.path.join(tmpdir, 'easybuild', 'easyconfigs')) orig_sys_path = sys.path[:] - sys.path.append(tmpdir) + sys.path.insert(0, tmpdir) # prepend to give it preference over possible other installed easyconfigs pkgs for dry_run_arg in ['-D', '--dry-run-short']: open(self.logfile, 'w').write('') From 8e5887c3c258d0b4c599eba442de32d7e53e6183 Mon Sep 17 00:00:00 2001 From: pescobar Date: Wed, 29 Oct 2014 09:48:48 +0100 Subject: [PATCH 110/298] CHARS_TO_SCAPE is a class var. Escape using .join instead of a loop --- easybuild/tools/module_generator.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/easybuild/tools/module_generator.py b/easybuild/tools/module_generator.py index 1675dca286..32690fe1e9 100644 --- a/easybuild/tools/module_generator.py +++ b/easybuild/tools/module_generator.py @@ -47,13 +47,15 @@ _log = fancylogger.getLogger('module_generator', fname=False) -# chars we want to escape in the generated modulefiles -CHARS_TO_ESCAPE = ["$"] class ModuleGenerator(object): """ Class for generating module files. """ + + # chars we want to escape in the generated modulefiles + CHARS_TO_ESCAPE = ["$"] + def __init__(self, application, fake=False): self.app = application self.fake = fake @@ -212,8 +214,7 @@ def msg_on_load(self, msg): """ Add a message that should be printed when loading the module. """ - for element in CHARS_TO_ESCAPE: - msg = re.sub(r'((? Date: Wed, 29 Oct 2014 11:40:46 +0100 Subject: [PATCH 111/298] style fix in module_generator.py --- easybuild/tools/module_generator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/easybuild/tools/module_generator.py b/easybuild/tools/module_generator.py index 32690fe1e9..f83d3a55ee 100644 --- a/easybuild/tools/module_generator.py +++ b/easybuild/tools/module_generator.py @@ -214,7 +214,8 @@ def msg_on_load(self, msg): """ Add a message that should be printed when loading the module. """ - msg = re.sub(r'((? Date: Wed, 29 Oct 2014 11:41:22 +0100 Subject: [PATCH 112/298] enhance msg_on_load unit test to verify escaping of '$' --- test/framework/module_generator.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/test/framework/module_generator.py b/test/framework/module_generator.py index c67b629db2..8e606c3ed1 100644 --- a/test/framework/module_generator.py +++ b/test/framework/module_generator.py @@ -171,8 +171,15 @@ def test_alias(self): def test_load_msg(self): """Test including a load message in the module file.""" - tcl_load_msg = '\nif [ module-info mode load ] {\n puts stderr "test"\n}\n' - self.assertEqual(tcl_load_msg, self.modgen.msg_on_load('test')) + tcl_load_msg = '\n'.join([ + '', + "if [ module-info mode load ] {", + " puts stderr \"test \\$test \\$test", + "test \\$foo \\$bar\"", + "}", + '', + ]) + self.assertEqual(tcl_load_msg, self.modgen.msg_on_load('test $test \\$test\ntest $foo \\$bar')) def test_tcl_footer(self): """Test including a Tcl footer.""" From c081147b201e5cd54788daaed2824c31c26252a0 Mon Sep 17 00:00:00 2001 From: Jens Timmerman Date: Wed, 29 Oct 2014 13:13:03 +0100 Subject: [PATCH 113/298] addressed remarks --- easybuild/tools/filetools.py | 37 ++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/easybuild/tools/filetools.py b/easybuild/tools/filetools.py index 7e47a5b0f3..a0369a3869 100644 --- a/easybuild/tools/filetools.py +++ b/easybuild/tools/filetools.py @@ -257,19 +257,25 @@ def download_file(filename, url, path): download_file.last_time = time.time() download_file.last_block = 0 # internal function to report on download progress - def report(block, blocksize, bytesize): + def report(block, blocksize, filesize): """ - This is a reporthook for urlretrieve, it takes 3 integers as arguments - the current downloaded block, the total ammount of blocks, and the blocksize - logs the download progress every 10 seconds + This is a reporthook for urlretrieve, it takes 3 integers as arguments: + the current downloaded block, the size in bytes of one block and the total size of the downlad. + This efectively logs the download progress every 10 seconds with loglevel info. """ if download_file.last_time + 10 < time.time(): newblocks = block - download_file.last_block download_file.last_block = block + total_download = block * blocksize + percentage = int(block * blocksize * 100 / filesize) + kbps = (blocksize * newblocks) / 1024 // (time.time() - download_file.last_time) - _log.info('download report: %d kb of %d kb (%d %%, %d kbps)', block * blocksize, bytesize, int(block * blocksize * 100 / bytesize), - (blocksize * newblocks) / 1024 // (time.time() - download_file.last_time)) - + if filesize <= 0: + # content length isn't always set + _log.info('download report: %d kb downloaded (%d kbps)', total_download, kbps) + else: + _log.info('download report: %d kb of %d kb (%d %%, %d kbps)', total_download, filesize, percentage, kbps) + download_file.last_time = time.time() @@ -288,9 +294,20 @@ def report(block, blocksize, bytesize): download_file.last_time = time.time() download_file.last_block = 0 - (_, httpmsg) = urllib.urlretrieve(url, path, reporthook=report) - _log.debug('headers of download: %s', httpmsg) - + try: + (_, httpmsg) = urllib.urlretrieve(url, path, reporthook=report) + except ContentTooShortError: + _log.warning( + "Expected file of %d bytes, but download size dit not match, removing file and retrying", + int(httpmsg.dict['content-length']), + ) + try: + os.remove(path) + except OSError, err: + _log.error("Failed to remove downloaded file:" % err) + # try again + attempt_cnt += 1 + continue if httpmsg.type == "text/html" and not filename.endswith('.html'): _log.warning("HTML file downloaded but not expecting it, so assuming invalid download.") From 898d61f658d10c36bd7aca6045038706a7cb40bd Mon Sep 17 00:00:00 2001 From: Alan O'Cais Date: Wed, 29 Oct 2014 13:54:12 +0100 Subject: [PATCH 114/298] changed permissions --- easybuild/toolchains/gpsmpi.py | 0 easybuild/toolchains/gpsolf.py | 0 easybuild/toolchains/iimpi.py | 0 easybuild/toolchains/intel.py | 0 4 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 easybuild/toolchains/gpsmpi.py mode change 100755 => 100644 easybuild/toolchains/gpsolf.py mode change 100755 => 100644 easybuild/toolchains/iimpi.py mode change 100755 => 100644 easybuild/toolchains/intel.py diff --git a/easybuild/toolchains/gpsmpi.py b/easybuild/toolchains/gpsmpi.py old mode 100755 new mode 100644 diff --git a/easybuild/toolchains/gpsolf.py b/easybuild/toolchains/gpsolf.py old mode 100755 new mode 100644 diff --git a/easybuild/toolchains/iimpi.py b/easybuild/toolchains/iimpi.py old mode 100755 new mode 100644 diff --git a/easybuild/toolchains/intel.py b/easybuild/toolchains/intel.py old mode 100755 new mode 100644 From f2c4c6e3d65a84cf2139655bf54e65316aef0f5f Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 29 Oct 2014 18:46:01 +0100 Subject: [PATCH 115/298] enforce that hiddendependencies is a subset of dependencies --- easybuild/framework/easyconfig/easyconfig.py | 23 ++++++++++++++++++++ test/framework/easyblock.py | 3 ++- test/framework/easyconfig.py | 2 +- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/easybuild/framework/easyconfig/easyconfig.py b/easybuild/framework/easyconfig/easyconfig.py index 3fea3e6c3e..1b6a42bc8b 100644 --- a/easybuild/framework/easyconfig/easyconfig.py +++ b/easybuild/framework/easyconfig/easyconfig.py @@ -339,6 +339,9 @@ def validate(self, check_osdeps=True): self.log.info("Checking licenses") self.validate_license() + self.log.info("Checking whether list of hidden dependencies is a subset of list of dependencies") + self.validate_hiddendeps() + def validate_license(self): """Validate the license""" lic = self._config['software_license'][0] @@ -407,6 +410,26 @@ def validate_iterate_opts_lists(self): return True + def validate_hiddendeps(self): + """ + Validate that list of hidden dependencies is a subset of the list of dependencies. + The list of dependencies is adjusted to only include non-hidden dependencies. + """ + dep_mod_names = [dep['full_mod_name'] for dep in self['dependencies']] + + for hidden_dep in self['hiddendependencies']: + # check whether hidden dep is a listed dep using *visible* module name, not hidden one + visible_mod_name = ActiveMNS().det_full_module_name(hidden_dep, force_visible=True) + if visible_mod_name in dep_mod_names: + self['dependencies'] = [d for d in self['dependencies'] if d['full_mod_name'] != visible_mod_name] + self.log.debug("Removed dependency matching hidden dependency %s" % hidden_dep) + else: + # hidden dependencies must also be included in list of dependencies; + # this is done to try and make easyconfigs portable w.r.t. site-specific policies with minimal effort, + # i.e. by simply removing the 'hiddendependencies' specification + tup = (visible_mod_name, dep_mod_names) + self.log.error("Hidden dependency with visible module name %s not in list of dependencies: %s" % tup) + def dependencies(self): """ Returns an array of parsed dependencies (after filtering, if requested) diff --git a/test/framework/easyblock.py b/test/framework/easyblock.py index 41df64dd44..91a7441d18 100644 --- a/test/framework/easyblock.py +++ b/test/framework/easyblock.py @@ -274,6 +274,7 @@ def test_make_module_step(self): version = "3.14" deps = [('GCC', '4.6.4')] hiddendeps = [('toy', '0.0-deps')] + alldeps = deps + hiddendeps # hidden deps must be included in list of deps modextravars = {'PI': '3.1415', 'FOO': 'bar'} modextrapaths = {'PATH': 'pibin', 'CPATH': 'pi/include'} self.contents = '\n'.join([ @@ -282,7 +283,7 @@ def test_make_module_step(self): 'homepage = "http://example.com"', 'description = "test easyconfig"', "toolchain = {'name': 'dummy', 'version': 'dummy'}", - "dependencies = %s" % str(deps), + "dependencies = %s" % str(alldeps), "hiddendependencies = %s" % str(hiddendeps), "builddependencies = [('OpenMPI', '1.6.4-GCC-4.6.4')]", "modextravars = %s" % str(modextravars), diff --git a/test/framework/easyconfig.py b/test/framework/easyconfig.py index c78ec426a3..d922848751 100644 --- a/test/framework/easyconfig.py +++ b/test/framework/easyconfig.py @@ -566,7 +566,7 @@ def test_obtain_easyconfig(self): new_patches = ['two.patch', 'three.patch'] specs.update({ 'patches': new_patches[:], - 'dependencies': [('foo', '1.2.3'), ('bar', '666', '-bleh', ('gompi', '1.4.10'))], + 'dependencies': [('foo', '1.2.3'), ('bar', '666', '-bleh', ('gompi', '1.4.10')), ('test', '3.2.1')], 'hiddendependencies': [('test', '3.2.1')], }) parsed_deps = [ From 61882851a963b075f7cebce7168bee42569808fe Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 29 Oct 2014 20:42:25 +0100 Subject: [PATCH 116/298] fix test easyconfig w.r.t. requirement that hiddendeps is subset of deps --- test/framework/easyconfigs/gzip-1.4-GCC-4.6.3.eb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/framework/easyconfigs/gzip-1.4-GCC-4.6.3.eb b/test/framework/easyconfigs/gzip-1.4-GCC-4.6.3.eb index b59e6160d2..f47c7482a5 100644 --- a/test/framework/easyconfigs/gzip-1.4-GCC-4.6.3.eb +++ b/test/framework/easyconfigs/gzip-1.4-GCC-4.6.3.eb @@ -26,6 +26,7 @@ sources = ['%s-%s.tar.gz'%(name,version)] source_urls = [GNU_SOURCE] hiddendependencies = [('toy', '0.0', '-deps', True)] +dependencies = hiddendependencies # hidden deps must be included in list of deps # make sure the gzip and gunzip binaries are available after installation sanity_check_paths = { From 0eafcbd4b3f7fc278ee486e42c3f3a81dd496c2e Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 30 Oct 2014 07:47:01 +0100 Subject: [PATCH 117/298] report all hidden deps not included in list of deps, enhance unit test --- easybuild/framework/easyconfig/easyconfig.py | 8 ++++++-- test/framework/easyconfig.py | 11 ++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/easybuild/framework/easyconfig/easyconfig.py b/easybuild/framework/easyconfig/easyconfig.py index 1b6a42bc8b..c58e7349a5 100644 --- a/easybuild/framework/easyconfig/easyconfig.py +++ b/easybuild/framework/easyconfig/easyconfig.py @@ -417,6 +417,7 @@ def validate_hiddendeps(self): """ dep_mod_names = [dep['full_mod_name'] for dep in self['dependencies']] + faulty_deps = [] for hidden_dep in self['hiddendependencies']: # check whether hidden dep is a listed dep using *visible* module name, not hidden one visible_mod_name = ActiveMNS().det_full_module_name(hidden_dep, force_visible=True) @@ -427,8 +428,11 @@ def validate_hiddendeps(self): # hidden dependencies must also be included in list of dependencies; # this is done to try and make easyconfigs portable w.r.t. site-specific policies with minimal effort, # i.e. by simply removing the 'hiddendependencies' specification - tup = (visible_mod_name, dep_mod_names) - self.log.error("Hidden dependency with visible module name %s not in list of dependencies: %s" % tup) + faulty_deps.append(visible_mod_name) + + if faulty_deps: + tup = (faulty_deps, dep_mod_names) + self.log.error("Hidden dependencies with visible module names %s not in list of dependencies: %s" % tup) def dependencies(self): """ diff --git a/test/framework/easyconfig.py b/test/framework/easyconfig.py index d922848751..de25d9e82c 100644 --- a/test/framework/easyconfig.py +++ b/test/framework/easyconfig.py @@ -566,7 +566,7 @@ def test_obtain_easyconfig(self): new_patches = ['two.patch', 'three.patch'] specs.update({ 'patches': new_patches[:], - 'dependencies': [('foo', '1.2.3'), ('bar', '666', '-bleh', ('gompi', '1.4.10')), ('test', '3.2.1')], + 'dependencies': [('foo', '1.2.3'), ('bar', '666', '-bleh', ('gompi', '1.4.10'))], 'hiddendependencies': [('test', '3.2.1')], }) parsed_deps = [ @@ -601,6 +601,15 @@ def test_obtain_easyconfig(self): 'hidden': True, }, ] + + # hidden dependencies must be included in list of dependencies + res = obtain_ec_for(specs, [self.ec_dir], None) + self.assertEqual(res[0], True) + error_pattern = "Hidden dependencies with visible module names .* not in list of dependencies: .*" + self.assertErrorRegex(EasyBuildError, error_pattern, EasyConfig, res[1]) + + specs['dependencies'].append(('test', '3.2.1')) + res = obtain_ec_for(specs, [self.ec_dir], None) self.assertEqual(res[0], True) ec = EasyConfig(res[1]) From beed0effc858ef026817de4cab70391de6da8d5d Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 30 Oct 2014 10:01:27 +0100 Subject: [PATCH 118/298] add --robot-paths configure option --- easybuild/main.py | 12 ++++++------ easybuild/tools/options.py | 5 +++-- easybuild/tools/robot.py | 4 ++-- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/easybuild/main.py b/easybuild/main.py index 5be168f372..09ee05a31b 100644 --- a/easybuild/main.py +++ b/easybuild/main.py @@ -182,11 +182,6 @@ def main(testing_data=(None, None, None)): if options.umask is not None: _log.info("umask set to '%s' (used to be '%s')" % (oct(new_umask), oct(old_umask))) - # determine easybuild-easyconfigs package install path - easyconfigs_pkg_paths = get_paths_for(subdir=EASYCONFIGS_PKG_SUBDIR) - if not easyconfigs_pkg_paths: - _log.warning("Failed to determine install path for easybuild-easyconfigs package.") - # process software build specifications (if any), i.e. # software name/version, toolchain name/version, extra patches, ... (try_to_generate, build_specs) = process_software_build_specs(options) @@ -196,7 +191,7 @@ def main(testing_data=(None, None, None)): tweaked_ecs = try_to_generate and build_specs tweaked_ecs_path, pr_path = alt_easyconfig_paths(eb_tmpdir, tweaked_ecs=tweaked_ecs, from_pr=options.from_pr) auto_robot = try_to_generate or options.dep_graph or options.search or options.search_short - robot_path = det_robot_path(options.robot, easyconfigs_pkg_paths, tweaked_ecs_path, pr_path, auto_robot=auto_robot) + robot_path = det_robot_path(options.robot, options.robot_paths, tweaked_ecs_path, pr_path, auto_robot=auto_robot) _log.debug("Full robot path: %s" % robot_path) # configure & initialize build options @@ -226,6 +221,11 @@ def main(testing_data=(None, None, None)): if query: search_easyconfigs(query, short=not options.search) + # determine easybuild-easyconfigs package install path + easyconfigs_pkg_paths = get_paths_for(subdir=EASYCONFIGS_PKG_SUBDIR) + if not easyconfigs_pkg_paths: + _log.warning("Failed to determine install path for easybuild-easyconfigs package.") + # determine paths to easyconfigs paths = det_easyconfig_paths(orig_paths, options.from_pr, easyconfigs_pkg_paths) if not paths: diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index a346cb8e56..757b217b7f 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -96,8 +96,9 @@ def basic_options(self): 'job': ("Submit the build as a job", None, 'store_true', False), 'logtostdout': ("Redirect main log to stdout", None, 'store_true', False, 'l'), 'only-blocks': ("Only build listed blocks", None, 'extend', None, 'b', {'metavar': 'BLOCKS'}), - 'robot': ("Path(s) to search for easyconfigs for missing dependencies (colon-separated)" , - None, 'store_or_None', default_robot_path, 'r', {'metavar': 'PATH'}), + 'robot': ("Enable dependency resolution", None, 'store_or_None', None, {'metavar': 'PATH[:PATH][,PATH]'}), + 'robot-paths': ("Additional paths to consider by robot for easyconfigs (--robot paths get priority)", + None, 'store', [default_robot_path], {'metavar': 'PATH[,PATH]'}), 'skip': ("Skip existing software (useful for installing additional packages)", None, 'store_true', False, 'k'), 'stop': ("Stop the installation after certain step", 'choice', 'store_or_None', 'source', 's', all_stops), diff --git a/easybuild/tools/robot.py b/easybuild/tools/robot.py index b478571485..6ffbf77a7a 100644 --- a/easybuild/tools/robot.py +++ b/easybuild/tools/robot.py @@ -48,7 +48,7 @@ _log = fancylogger.getLogger('tools.robot', fname=False) -def det_robot_path(robot_option, easyconfigs_pkg_paths, tweaked_ecs_path, pr_path, auto_robot=False): +def det_robot_path(robot_option, robot_paths_option, tweaked_ecs_path, pr_path, auto_robot=False): """Determine robot path.""" # do not use robot option directly, it's not a list instance (and it shouldn't be modified) robot_path = [] @@ -61,7 +61,7 @@ def det_robot_path(robot_option, easyconfigs_pkg_paths, tweaked_ecs_path, pr_pat _log.error("No robot paths specified, and unable to determine easybuild-easyconfigs install path.") if robot_path or auto_robot: - robot_path.extend(easyconfigs_pkg_paths) + robot_path.extend(robot_paths_option) _log.info("Extended list of robot paths with paths for installed easyconfigs: %s" % robot_path) if tweaked_ecs_path is not None: From 9ca6c529eb92d6a8a7bdda2027b1a99a2c21fd5e Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 30 Oct 2014 10:46:36 +0100 Subject: [PATCH 119/298] enable use of --show_hidden for avail subcommand and recent Lmod versions --- easybuild/tools/modules.py | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/easybuild/tools/modules.py b/easybuild/tools/modules.py index 30893436b9..ce8353b0e2 100644 --- a/easybuild/tools/modules.py +++ b/easybuild/tools/modules.py @@ -197,6 +197,14 @@ def set_and_check_version(self): if res: self.version = res.group('version') self.log.info("Found version %s" % self.version) + + # make sure version is a valid StrictVersion (e.g., 5.7.3.1 is invalid), + # and replace 'rc' by 'b', to make StrictVersion treat it as a beta-release + self.version = self.version.replace('rc', 'b') + if len(self.version.split('.')) > 3: + self.version = '.'.join(self.version.split('.')[:3]) + + self.log.info("Converted actual version to '%s'" % self.version) else: self.log.error("Failed to determine version from option '%s' output: %s" % (self.VERSION_OPTION, txt)) except (OSError), err: @@ -205,12 +213,7 @@ def set_and_check_version(self): if self.REQ_VERSION is None: self.log.debug('No version requirement defined.') else: - # make sure version is a valid StrictVersion (e.g., 5.7.3.1 is invalid), - # and replace 'rc' by 'b', to make StrictVersion treat it as a beta-release - check_ver = self.version.replace('rc', 'b') - if len(check_ver.split('.')) > 3: - check_ver = '.'.join(check_ver.split('.')[:3]) - if StrictVersion(check_ver) < StrictVersion(self.REQ_VERSION): + if StrictVersion(self.version) < StrictVersion(self.REQ_VERSION): msg = "EasyBuild requires v%s >= v%s (no rc), found v%s" self.log.error(msg % (self.__class__.__name__, self.REQ_VERSION, self.version)) else: @@ -323,16 +326,19 @@ def check_module_path(self): self.use(mod_path) self.log.info("$MODULEPATH set based on list of module paths (via 'module use'): %s" % os.environ['MODULEPATH']) - def available(self, mod_name=None): + def available(self, mod_name=None, extra_args=None): """ Return a list of available modules for the given (partial) module name; use None to obtain a list of all available modules. @param mod_name: a (partial) module name for filtering (default: None) """ + if extra_args is None: + extra_args = [] if mod_name is None: mod_name = '' - mods = self.run_module('avail', mod_name) + args = ['avail'] + extra_args + [mod_name] + mods = self.run_module(*args) # sort list of modules in alphabetical order mods.sort(key=lambda m: m['mod_name']) @@ -818,7 +824,13 @@ def available(self, mod_name=None): @param name: a (partial) module name for filtering (default: None) """ - mods = super(Lmod, self).available(mod_name=mod_name) + extra_args = [] + if StrictVersion(self.version) >= StrictVersion('5.7.5'): + # make hidden modules visible for recent version of Lmod + extra_args = ['--show_hidden'] + + mods = super(Lmod, self).available(mod_name=mod_name, extra_args=extra_args) + # only retain actual modules, exclude module directories (which end with a '/') real_mods = [mod for mod in mods if not mod.endswith('/')] From 53f2bf9ef3fcfee09c2dae785673b9ed6bfeb6f3 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 30 Oct 2014 16:07:19 +0100 Subject: [PATCH 120/298] fix setting default for --robot-paths --- easybuild/tools/options.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index 757b217b7f..173bce5de9 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -80,11 +80,12 @@ def basic_options(self): all_stops = [x[0] for x in EasyBlock.get_steps()] strictness_options = [run.IGNORE, run.WARN, run.ERROR] - try: - default_robot_path = get_paths_for(subdir=EASYCONFIGS_PKG_SUBDIR, robot_path=None)[0] - except: + easyconfigs_pkg_paths = get_paths_for(subdir=EASYCONFIGS_PKG_SUBDIR, robot_path=None) + if easyconfigs_pkg_paths: + default_robot_paths = easyconfigs_pkg_paths + else: self.log.warning("basic_options: unable to determine default easyconfig path") - default_robot_path = False # False as opposed to None, since None is used for indicating that --robot was used + default_robot_paths = [] descr = ("Basic options", "Basic runtime options for EasyBuild.") @@ -98,7 +99,7 @@ def basic_options(self): 'only-blocks': ("Only build listed blocks", None, 'extend', None, 'b', {'metavar': 'BLOCKS'}), 'robot': ("Enable dependency resolution", None, 'store_or_None', None, {'metavar': 'PATH[:PATH][,PATH]'}), 'robot-paths': ("Additional paths to consider by robot for easyconfigs (--robot paths get priority)", - None, 'store', [default_robot_path], {'metavar': 'PATH[,PATH]'}), + None, 'store', default_robot_paths, {'metavar': 'PATH[,PATH]'}), 'skip': ("Skip existing software (useful for installing additional packages)", None, 'store_true', False, 'k'), 'stop': ("Stop the installation after certain step", 'choice', 'store_or_None', 'source', 's', all_stops), From ad3ae417ed547e9b8ab03ec8497d47b99b8472f5 Mon Sep 17 00:00:00 2001 From: Jens Timmerman Date: Thu, 30 Oct 2014 16:45:46 +0100 Subject: [PATCH 121/298] check to see if a valid response code was returned, could also be None when offline --- easybuild/tools/filetools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/easybuild/tools/filetools.py b/easybuild/tools/filetools.py index a0369a3869..56d202eea5 100644 --- a/easybuild/tools/filetools.py +++ b/easybuild/tools/filetools.py @@ -285,8 +285,8 @@ def report(block, blocksize, filesize): urlfile = urllib.urlopen(url) response_code = urlfile.getcode() urlfile.close() - - _log.debug('http response code for given url: %d', response_code) + if response_code: + _log.debug('http response code for given url: %d', response_code) if response_code == 404: _log.warning('url %s was not found (404), not trying again', url) return None From dcf40fc95abbe3d9e48be1a3d7a803f54c330efa Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 30 Oct 2014 18:43:22 +0100 Subject: [PATCH 122/298] fix default value for --robot --- easybuild/tools/options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index 173bce5de9..4a5258a8a2 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -97,7 +97,7 @@ def basic_options(self): 'job': ("Submit the build as a job", None, 'store_true', False), 'logtostdout': ("Redirect main log to stdout", None, 'store_true', False, 'l'), 'only-blocks': ("Only build listed blocks", None, 'extend', None, 'b', {'metavar': 'BLOCKS'}), - 'robot': ("Enable dependency resolution", None, 'store_or_None', None, {'metavar': 'PATH[:PATH][,PATH]'}), + 'robot': ("Enable dependency resolution", None, 'store_or_None', False, {'metavar': 'PATH[:PATH][,PATH]'}), 'robot-paths': ("Additional paths to consider by robot for easyconfigs (--robot paths get priority)", None, 'store', default_robot_paths, {'metavar': 'PATH[,PATH]'}), 'skip': ("Skip existing software (useful for installing additional packages)", From 4c6fed17d5b8aab65bfbddfa30d681a72c5c6738 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 31 Oct 2014 08:54:43 +0100 Subject: [PATCH 123/298] add tests for ListOfStrings behaviour --- test/framework/format_convert.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/framework/format_convert.py b/test/framework/format_convert.py index 4cb5e70f3b..84c5c0f067 100644 --- a/test/framework/format_convert.py +++ b/test/framework/format_convert.py @@ -61,6 +61,12 @@ def test_listofstrings(self): res = ListOfStrings(txt.replace(ListOfStrings.SEPARATOR_LIST, ListOfStrings.SEPARATOR_LIST + ' ')) self.assertEqual(res, dest) + # empty string yields a list with an empty string + self.assertEqual(ListOfStrings(''), ['']) + + # empty entries are retained + self.assertEqual(ListOfStrings('a,,b'), ['a', '', 'b']) + def test_dictofstrings(self): """Test dict of strings""" # test default separators From 8f533e6795a0b3b0a89a104bc9a7353f02cfdb67 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 31 Oct 2014 09:00:55 +0100 Subject: [PATCH 124/298] fix default value for --robot and --robot-paths + determining full list of robot paths --- easybuild/tools/options.py | 24 ++++++++++++++++-------- easybuild/tools/robot.py | 16 +++++++--------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index 4a5258a8a2..e8f8721c4b 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -82,10 +82,10 @@ def basic_options(self): easyconfigs_pkg_paths = get_paths_for(subdir=EASYCONFIGS_PKG_SUBDIR, robot_path=None) if easyconfigs_pkg_paths: - default_robot_paths = easyconfigs_pkg_paths + default_robot_paths = os.pathsep.join(easyconfigs_pkg_paths) else: self.log.warning("basic_options: unable to determine default easyconfig path") - default_robot_paths = [] + default_robot_paths = '' descr = ("Basic options", "Basic runtime options for EasyBuild.") @@ -97,9 +97,10 @@ def basic_options(self): 'job': ("Submit the build as a job", None, 'store_true', False), 'logtostdout': ("Redirect main log to stdout", None, 'store_true', False, 'l'), 'only-blocks': ("Only build listed blocks", None, 'extend', None, 'b', {'metavar': 'BLOCKS'}), - 'robot': ("Enable dependency resolution", None, 'store_or_None', False, {'metavar': 'PATH[:PATH][,PATH]'}), + 'robot': ("Enable dependency resolution, using easyconfigs in specified paths", + None, 'store_or_None', '', {'metavar': 'PATH[:PATH]'}), 'robot-paths': ("Additional paths to consider by robot for easyconfigs (--robot paths get priority)", - None, 'store', default_robot_paths, {'metavar': 'PATH[,PATH]'}), + None, 'store', default_robot_paths, {'metavar': 'PATH[:PATH]'}), 'skip': ("Skip existing software (useful for installing additional packages)", None, 'store_true', False, 'k'), 'stop': ("Stop the installation after certain step", 'choice', 'store_or_None', 'source', 's', all_stops), @@ -413,13 +414,20 @@ def _postprocess_config(self): if self.options.pretend: self.options.installpath = get_pretend_installpath() + # helper class to convert a string with colon-separated robot paths into a list of robot paths + class RobotPath(ListOfStrings): + SEPARATOR_LIST = os.pathsep + # explicit definition of __str__ is required for unknown reason related to the way Wrapper is defined + __str__ = ListOfStrings.__str__ + # split supplied list of robot paths to obtain a list if self.options.robot: - class RobotPath(ListOfStrings): - SEPARATOR_LIST = os.pathsep - # explicit definition of __str__ is required for unknown reason related to the way Wrapper is defined - __str__ = ListOfStrings.__str__ self.options.robot = RobotPath(self.options.robot) + if self.options.robot is not None: + self.options.robot = list(self.options.robot) + if self.options.robot_paths: + self.options.robot_paths = RobotPath(self.options.robot_paths) + self.options.robot_paths = list(self.options.robot_paths) def _postprocess_list_avail(self): """Create all the additional info that can be requested (exit at the end)""" diff --git a/easybuild/tools/robot.py b/easybuild/tools/robot.py index 6ffbf77a7a..06094056a1 100644 --- a/easybuild/tools/robot.py +++ b/easybuild/tools/robot.py @@ -50,24 +50,22 @@ def det_robot_path(robot_option, robot_paths_option, tweaked_ecs_path, pr_path, auto_robot=False): """Determine robot path.""" - # do not use robot option directly, it's not a list instance (and it shouldn't be modified) robot_path = [] + + # if --robot is enabled, use any paths specified to it if robot_option is not None: - if robot_option: - robot_path = list(robot_option) - _log.info("Using robot path(s): %s" % robot_path) - else: - # if options.robot is not None and False, easyconfigs pkg install path could not be found (see options.py) - _log.error("No robot paths specified, and unable to determine easybuild-easyconfigs install path.") + robot_path = robot_option + _log.info("Using robot path(s): %s" % robot_path) - if robot_path or auto_robot: + # if --robot is specified or should be enabled automagically, include --robot-paths too + if robot_option is not None or auto_robot: robot_path.extend(robot_paths_option) _log.info("Extended list of robot paths with paths for installed easyconfigs: %s" % robot_path) + # paths to tweaked easyconfigs or easyconfigs downloaded from a PR have priority if tweaked_ecs_path is not None: robot_path.insert(0, tweaked_ecs_path) _log.info("Prepended list of robot search paths with %s: %s" % (tweaked_ecs_path, robot_path)) - if pr_path is not None: robot_path.insert(0, pr_path) _log.info("Prepended list of robot search paths with %s: %s" % (pr_path, robot_path)) From 26fa0a1d594fd0d0ee57b140e5da191c4ae3dad6 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 31 Oct 2014 09:01:52 +0100 Subject: [PATCH 125/298] add unit test for --robot and --robot-paths interaction --- test/framework/options.py | 44 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/test/framework/options.py b/test/framework/options.py index 507e3368fe..00ff216d77 100644 --- a/test/framework/options.py +++ b/test/framework/options.py @@ -1233,6 +1233,50 @@ def toy(extra_args=None): tup = (filter_arg_regex.pattern, test_report_txt) self.assertTrue(filter_arg_regex.search(test_report_txt), "%s in %s" % tup) + def test_robot(self): + """Test --robot and --robot-paths command line options.""" + test_ecs_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs') + eb_file = os.path.join(test_ecs_path, 'gzip-1.4-GCC-4.6.3.eb') # includes 'toy' as a dependency + + # enable robot, but without passing path required to resolve toy dependency => FAIL + args = [ + eb_file, + '--robot', + '--dry-run', + ] + self.assertErrorRegex(EasyBuildError, 'Irresolvable dependencies', self.eb_main, args, raise_error=True) + + # add path to test easyconfigs to robot paths, so dependencies can be resolved + self.eb_main(args + ['--robot-paths=%s' % test_ecs_path], raise_error=True) + + # copy test easyconfigs to easybuild/easyconfigs subdirectory of temp directory + # to check whether easyconfigs install path is auto-included in robot path + tmpdir = tempfile.mkdtemp(prefix='easybuild-easyconfigs-pkg-install-path') + mkdir(os.path.join(tmpdir, 'easybuild'), parents=True) + shutil.copytree(test_ecs_path, os.path.join(tmpdir, 'easybuild', 'easyconfigs')) + + # prepend path to test easyconfigs into Python search path, so it gets picked up as --robot-paths default + orig_sys_path = sys.path[:] + sys.path.insert(0, tmpdir) + self.eb_main(args, raise_error=True) + + shutil.rmtree(tmpdir) + sys.path[:] = orig_sys_path + + # make sure that paths specified to --robot get preference over --robot-paths + args = [ + eb_file, + '--robot=%s' % test_ecs_path, + '--robot-paths=%s' % os.path.join(tmpdir, 'easybuild', 'easyconfigs'), + '--dry-run', + ] + outtxt = self.eb_main(args, raise_error=True) + + for ec in ['GCC-4.6.3.eb', 'ictce-4.1.13.eb', 'toy-0.0-deps.eb', 'gzip-1.4-GCC-4.6.3.eb']: + ec_regex = re.compile('^\s\*\s\[[xF ]\]\s%s' % os.path.join(test_ecs_path, ec), re.M) + self.assertTrue(ec_regex.search(outtxt), "Pattern %s found in %s" % (ec_regex.pattern, outtxt)) + + def suite(): """ returns all the testcases in this module """ return TestLoader().loadTestsFromTestCase(CommandLineOptionsTest) From 82e538b2bab329c5c7f7be28efdeefdbcc4a448f Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 31 Oct 2014 09:34:33 +0100 Subject: [PATCH 126/298] clean up if mess --- easybuild/main.py | 2 +- easybuild/tools/options.py | 13 ++++++------- easybuild/tools/robot.py | 16 ++++------------ 3 files changed, 11 insertions(+), 20 deletions(-) diff --git a/easybuild/main.py b/easybuild/main.py index 09ee05a31b..81efab95be 100644 --- a/easybuild/main.py +++ b/easybuild/main.py @@ -191,7 +191,7 @@ def main(testing_data=(None, None, None)): tweaked_ecs = try_to_generate and build_specs tweaked_ecs_path, pr_path = alt_easyconfig_paths(eb_tmpdir, tweaked_ecs=tweaked_ecs, from_pr=options.from_pr) auto_robot = try_to_generate or options.dep_graph or options.search or options.search_short - robot_path = det_robot_path(options.robot, options.robot_paths, tweaked_ecs_path, pr_path, auto_robot=auto_robot) + robot_path = det_robot_path(options.robot_paths, tweaked_ecs_path, pr_path, auto_robot=auto_robot) _log.debug("Full robot path: %s" % robot_path) # configure & initialize build options diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index e8f8721c4b..37c519bc6d 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -420,14 +420,13 @@ class RobotPath(ListOfStrings): # explicit definition of __str__ is required for unknown reason related to the way Wrapper is defined __str__ = ListOfStrings.__str__ - # split supplied list of robot paths to obtain a list if self.options.robot: - self.options.robot = RobotPath(self.options.robot) - if self.options.robot is not None: - self.options.robot = list(self.options.robot) - if self.options.robot_paths: - self.options.robot_paths = RobotPath(self.options.robot_paths) - self.options.robot_paths = list(self.options.robot_paths) + # paths specified to --robot have preference over --robot-paths + all_robot_paths = os.pathsep.join([self.options.robot, self.options.robot_paths]) + else: + all_robot_paths = self.options.robot_paths + # convert to a regular list, exclude empty strings + self.options.robot_paths = nub([x for x in list(RobotPath(all_robot_paths)) if x]) def _postprocess_list_avail(self): """Create all the additional info that can be requested (exit at the end)""" diff --git a/easybuild/tools/robot.py b/easybuild/tools/robot.py index 06094056a1..b5e93a7d1b 100644 --- a/easybuild/tools/robot.py +++ b/easybuild/tools/robot.py @@ -48,19 +48,11 @@ _log = fancylogger.getLogger('tools.robot', fname=False) -def det_robot_path(robot_option, robot_paths_option, tweaked_ecs_path, pr_path, auto_robot=False): +def det_robot_path(robot_paths_option, tweaked_ecs_path, pr_path, auto_robot=False): """Determine robot path.""" - robot_path = [] - - # if --robot is enabled, use any paths specified to it - if robot_option is not None: - robot_path = robot_option - _log.info("Using robot path(s): %s" % robot_path) - - # if --robot is specified or should be enabled automagically, include --robot-paths too - if robot_option is not None or auto_robot: - robot_path.extend(robot_paths_option) - _log.info("Extended list of robot paths with paths for installed easyconfigs: %s" % robot_path) + # always include all robot paths (combo of --robot and --robot-paths, in that order) + robot_path = robot_paths_option + _log.info("Using robot path(s): %s" % robot_path) # paths to tweaked easyconfigs or easyconfigs downloaded from a PR have priority if tweaked_ecs_path is not None: From 2aa65cae4658712be02367f6d42b053321a2bdd5 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 31 Oct 2014 09:43:15 +0100 Subject: [PATCH 127/298] fix remarks --- easybuild/tools/options.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index 37c519bc6d..94148f6bd3 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -84,7 +84,7 @@ def basic_options(self): if easyconfigs_pkg_paths: default_robot_paths = os.pathsep.join(easyconfigs_pkg_paths) else: - self.log.warning("basic_options: unable to determine default easyconfig path") + self.log.warning("basic_options: unable to determine easyconfigs pkg path for --robot-paths default") default_robot_paths = '' descr = ("Basic options", "Basic runtime options for EasyBuild.") @@ -420,13 +420,13 @@ class RobotPath(ListOfStrings): # explicit definition of __str__ is required for unknown reason related to the way Wrapper is defined __str__ = ListOfStrings.__str__ - if self.options.robot: + if self.options.robot is not None: # paths specified to --robot have preference over --robot-paths all_robot_paths = os.pathsep.join([self.options.robot, self.options.robot_paths]) else: all_robot_paths = self.options.robot_paths # convert to a regular list, exclude empty strings - self.options.robot_paths = nub([x for x in list(RobotPath(all_robot_paths)) if x]) + self.options.robot_paths = nub([x for x in RobotPath(all_robot_paths) if x]) def _postprocess_list_avail(self): """Create all the additional info that can be requested (exit at the end)""" From d28a668289de9979e099ddd4fb8116bfb4eeecf6 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 31 Oct 2014 10:03:46 +0100 Subject: [PATCH 128/298] disable dep resolution by default (+ add test), fix remarks --- easybuild/tools/config.py | 1 + easybuild/tools/options.py | 8 +++++--- easybuild/tools/robot.py | 5 ++--- test/framework/options.py | 8 ++++++++ 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py index 8de818bbf1..38ababafa5 100644 --- a/easybuild/tools/config.py +++ b/easybuild/tools/config.py @@ -82,6 +82,7 @@ 'only_blocks', 'optarch', 'regtest_output_dir', + 'robot', 'skip', 'stop', 'suffix_modules_path', diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index 94148f6bd3..3d3ed7f84e 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -420,11 +420,13 @@ class RobotPath(ListOfStrings): # explicit definition of __str__ is required for unknown reason related to the way Wrapper is defined __str__ = ListOfStrings.__str__ - if self.options.robot is not None: + if self.options.robot is None: + all_robot_paths = self.options.robot_paths + else: # paths specified to --robot have preference over --robot-paths all_robot_paths = os.pathsep.join([self.options.robot, self.options.robot_paths]) - else: - all_robot_paths = self.options.robot_paths + # avoid that options.robot is used for paths (since not everything is there) + self.options.robot = True # convert to a regular list, exclude empty strings self.options.robot_paths = nub([x for x in RobotPath(all_robot_paths) if x]) diff --git a/easybuild/tools/robot.py b/easybuild/tools/robot.py index b5e93a7d1b..467ac1e5b1 100644 --- a/easybuild/tools/robot.py +++ b/easybuild/tools/robot.py @@ -50,8 +50,7 @@ def det_robot_path(robot_paths_option, tweaked_ecs_path, pr_path, auto_robot=False): """Determine robot path.""" - # always include all robot paths (combo of --robot and --robot-paths, in that order) - robot_path = robot_paths_option + robot_path = robot_paths_option[:] _log.info("Using robot path(s): %s" % robot_path) # paths to tweaked easyconfigs or easyconfigs downloaded from a PR have priority @@ -123,7 +122,7 @@ def resolve_dependencies(unprocessed, build_specs=None, retain_all_deps=False): retain all deps when True, check matching build option when False """ - robot = build_option('robot_path') + robot = build_option('robot') and build_option('robot_path') # retain all dependencies if specified by either the resp. build option or the dedicated named argument retain_all_deps = build_option('retain_all_deps') or retain_all_deps diff --git a/test/framework/options.py b/test/framework/options.py index 00ff216d77..138884c947 100644 --- a/test/framework/options.py +++ b/test/framework/options.py @@ -1238,6 +1238,14 @@ def test_robot(self): test_ecs_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs') eb_file = os.path.join(test_ecs_path, 'gzip-1.4-GCC-4.6.3.eb') # includes 'toy' as a dependency + # dependency resolution is disabled by default, even if required paths are available + args = [ + eb_file, + '--robot-paths=%s' % test_ecs_path, + '--dry-run', + ] + self.assertErrorRegex(EasyBuildError, 'Irresolvable dependencies', self.eb_main, args, raise_error=True) + # enable robot, but without passing path required to resolve toy dependency => FAIL args = [ eb_file, From f8eba315f42df09b2adf69c74c708e0543f86f97 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 31 Oct 2014 10:37:56 +0100 Subject: [PATCH 129/298] laatste ronde --- easybuild/main.py | 7 +++++-- easybuild/tools/config.py | 2 +- easybuild/tools/robot.py | 2 +- easybuild/tools/testing.py | 2 +- test/framework/options.py | 13 ++++++++----- test/framework/robot.py | 2 +- 6 files changed, 17 insertions(+), 11 deletions(-) diff --git a/easybuild/main.py b/easybuild/main.py index 81efab95be..12991f2145 100644 --- a/easybuild/main.py +++ b/easybuild/main.py @@ -276,8 +276,11 @@ def main(testing_data=(None, None, None)): # determine an order that will allow all specs in the set to build if len(easyconfigs) > 0: - print_msg("resolving dependencies ...", log=_log, silent=testing) - ordered_ecs = resolve_dependencies(easyconfigs, build_specs=build_specs) + if options.robot: + print_msg("resolving dependencies ...", log=_log, silent=testing) + ordered_ecs = resolve_dependencies(easyconfigs, build_specs=build_specs) + else: + ordered_ecs = easyconfigs else: print_msg("No easyconfigs left to be built.", log=_log, silent=testing) ordered_ecs = [] diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py index 38ababafa5..3c4467ad3c 100644 --- a/easybuild/tools/config.py +++ b/easybuild/tools/config.py @@ -82,7 +82,6 @@ 'only_blocks', 'optarch', 'regtest_output_dir', - 'robot', 'skip', 'stop', 'suffix_modules_path', @@ -95,6 +94,7 @@ 'experimental', 'force', 'hidden', + 'robot', 'sequential', 'set_gid_bit', 'skip_test_cases', diff --git a/easybuild/tools/robot.py b/easybuild/tools/robot.py index 467ac1e5b1..89d625a8e6 100644 --- a/easybuild/tools/robot.py +++ b/easybuild/tools/robot.py @@ -122,7 +122,7 @@ def resolve_dependencies(unprocessed, build_specs=None, retain_all_deps=False): retain all deps when True, check matching build option when False """ - robot = build_option('robot') and build_option('robot_path') + robot = build_option('robot_path') # retain all dependencies if specified by either the resp. build option or the dedicated named argument retain_all_deps = build_option('retain_all_deps') or retain_all_deps diff --git a/easybuild/tools/testing.py b/easybuild/tools/testing.py index 838736e6bf..5c67e972e2 100644 --- a/easybuild/tools/testing.py +++ b/easybuild/tools/testing.py @@ -186,7 +186,7 @@ def create_test_report(msg, ecs_with_res, init_session_state, pr_nr=None, gist_l build_overview = [] for (ec, ec_res) in ecs_with_res: test_log = '' - if ec_res['success']: + if ec_res.get('success', False): test_result = 'SUCCESS' else: # compose test result string diff --git a/test/framework/options.py b/test/framework/options.py index 138884c947..4803299cb2 100644 --- a/test/framework/options.py +++ b/test/framework/options.py @@ -556,9 +556,9 @@ def test_dry_run(self): args = [ os.path.join(os.path.dirname(__file__), 'easyconfigs', 'gzip-1.4-GCC-4.6.3.eb'), - '--dry-run', + '--dry-run', # implies enabling dependency resolution '--unittest-file=%s' % self.logfile, - '--robot=%s' % os.path.join(os.path.dirname(__file__), 'easyconfigs'), + '--robot-paths=%s' % os.path.join(os.path.dirname(__file__), 'easyconfigs'), ] outtxt = self.eb_main(args, logfile=dummylogfn) @@ -1236,15 +1236,18 @@ def toy(extra_args=None): def test_robot(self): """Test --robot and --robot-paths command line options.""" test_ecs_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs') - eb_file = os.path.join(test_ecs_path, 'gzip-1.4-GCC-4.6.3.eb') # includes 'toy' as a dependency + eb_file = os.path.join(test_ecs_path, 'gzip-1.4-GCC-4.6.3.eb') # includes 'toy/.0.0-deps' as a dependency + + # hide test modules + self.reset_modulepath([]) # dependency resolution is disabled by default, even if required paths are available args = [ eb_file, '--robot-paths=%s' % test_ecs_path, - '--dry-run', ] - self.assertErrorRegex(EasyBuildError, 'Irresolvable dependencies', self.eb_main, args, raise_error=True) + error_regex ='no module .* found for dependency' + self.assertErrorRegex(EasyBuildError, error_regex, self.eb_main, args, raise_error=True, do_build=True) # enable robot, but without passing path required to resolve toy dependency => FAIL args = [ diff --git a/test/framework/robot.py b/test/framework/robot.py index 6562817c27..da3b4567ea 100644 --- a/test/framework/robot.py +++ b/test/framework/robot.py @@ -127,7 +127,7 @@ def test_resolve_dependencies(self): }], 'parsed': True, } - build_options.update({'robot_path': self.base_easyconfig_dir}) + build_options.update({'robot': True, 'robot_path': self.base_easyconfig_dir}) init_config(build_options=build_options) res = resolve_dependencies([deepcopy(easyconfig_dep)]) # dependency should be found, order should be correct From 87b2434b42bfe15b3e6a517faba1a666b70e859f Mon Sep 17 00:00:00 2001 From: pescobar Date: Mon, 3 Nov 2014 22:07:57 +0100 Subject: [PATCH 130/298] fixed typo in xHost option --- easybuild/toolchains/compiler/inteliccifort.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/easybuild/toolchains/compiler/inteliccifort.py b/easybuild/toolchains/compiler/inteliccifort.py index c224278eeb..b04d1a562a 100644 --- a/easybuild/toolchains/compiler/inteliccifort.py +++ b/easybuild/toolchains/compiler/inteliccifort.py @@ -56,7 +56,7 @@ class IntelIccIfort(Compiler): COMPILER_UNIQUE_OPTION_MAP = { 'i8': 'i8', 'r8': 'r8', - 'optarch': 'xHOST', + 'optarch': 'xHost', 'openmp': 'openmp', # both -openmp/-fopenmp are valid for enabling OpenMP 'strict': ['fp-speculation=strict', 'fp-model strict'], 'precise': ['fp-model precise'], @@ -69,7 +69,7 @@ class IntelIccIfort(Compiler): } COMPILER_OPTIMAL_ARCHITECTURE_OPTION = { - systemtools.INTEL : 'xHOST', + systemtools.INTEL : 'xHost', systemtools.AMD : 'msse3', } From 78f158b2614f3c83bdda0a00010808ea34ebb99a Mon Sep 17 00:00:00 2001 From: pescobar Date: Tue, 4 Nov 2014 10:41:46 +0100 Subject: [PATCH 131/298] switched from mavx to xHost --- easybuild/toolchains/compiler/inteliccifort.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/toolchains/compiler/inteliccifort.py b/easybuild/toolchains/compiler/inteliccifort.py index ac8b75e65e..045b80e6b3 100644 --- a/easybuild/toolchains/compiler/inteliccifort.py +++ b/easybuild/toolchains/compiler/inteliccifort.py @@ -70,7 +70,7 @@ class IntelIccIfort(Compiler): COMPILER_OPTIMAL_ARCHITECTURE_OPTION = { systemtools.INTEL : 'xHOST', - systemtools.AMD : 'mavx', + systemtools.AMD : 'xHost', } COMPILER_CC = 'icc' From ed756c80bac8f53693c1a1c27fd097889def2c1a Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 4 Nov 2014 19:03:40 +0100 Subject: [PATCH 132/298] clean up README --- README.rst | 82 ++++++++++++++++++++++++------------------------------ 1 file changed, 37 insertions(+), 45 deletions(-) diff --git a/README.rst b/README.rst index 2cd7efc455..f071e1049f 100644 --- a/README.rst +++ b/README.rst @@ -1,54 +1,46 @@ -Build status - *master branch (Python 2.4, Python 2.6, Python 2.7)* - -.. image:: https://jenkins1.ugent.be/view/EasyBuild/job/easybuild-framework_unit-test_hpcugent_master-python24/badge/icon - :target: https://jenkins1.ugent.be/view/EasyBuild/job/easybuild-framework_unit-test_hpcugent_master-python24/ -.. image:: https://jenkins1.ugent.be/view/EasyBuild/job/easybuild-framework_unit-test_hpcugent_master/badge/icon - :target: https://jenkins1.ugent.be/view/EasyBuild/job/easybuild-framework_unit-test_hpcugent_master/ -.. image:: https://jenkins1.ugent.be/view/EasyBuild/job/easybuild-framework_unit-test_hpcugent_master-python27/badge/icon - :target: https://jenkins1.ugent.be/view/EasyBuild/job/easybuild-framework_unit-test_hpcugent_master-python27/ - -Build status - *develop branch (Python 2.4, Python 2.6, Python 2.7)* - -.. image:: https://jenkins1.ugent.be/view/EasyBuild/job/easybuild-framework_unit-test_hpcugent_develop-python24/badge/icon - :target: https://jenkins1.ugent.be/view/EasyBuild/job/easybuild-framework_unit-test_hpcugent_develop-python24/ -.. image:: https://jenkins1.ugent.be/view/EasyBuild/job/easybuild-framework_unit-test_hpcugent_develop/badge/icon - :target: https://jenkins1.ugent.be/view/EasyBuild/job/easybuild-framework_unit-test_hpcugent_develop/ -.. image:: https://jenkins1.ugent.be/view/EasyBuild/job/easybuild-framework_unit-test_hpcugent_develop-python27/badge/icon - :target: https://jenkins1.ugent.be/view/EasyBuild/job/easybuild-framework_unit-test_hpcugent_develop-python27/ - EasyBuild: building software with ease -------------------------------------- -The easybuild-framework package is the basis for EasyBuild -(http://hpcugent.github.com/easybuild), a software build and -installation framework written in Python that allows you to install -software in a structured, repeatable and robust way. +.. image:: http://hpcugent.github.io/easybuild/images/easybuild_logo_small.png + :align: center + +`EasyBuild `_ is a software build +and installation framework that allows you to manage (scientific) software +on High Performance Computing (HPC) systems in an efficient way. -This package contains the EasyBuild framework that supports the -implementation and use of so-called easyblocks, that implement the -software install procedure for a particular (group of) software +The *easybuild-framework* package is the core of EasyBuild. It +supports the implementation and use of so-called easyblocks which +implement the software install procedure for a particular (group of) software package(s). -The code of the easybuild-framework package is hosted on GitHub, along +The EasyBuild documentation is available at http://easybuild.readthedocs.org/. + +The EasyBuild framework source code is hosted on GitHub, along with an issue tracker for bug reports and feature requests, see http://github.com/hpcugent/easybuild-framework. -The EasyBuild documentation is available on the GitHub wiki of the -easybuild meta-package, see -http://github.com/hpcugent/easybuild/wiki/Home. - -Related packages: -- easybuild-easyblocks -(http://pypi.python.org/pypi/easybuild-easyblocks): a collection of -easyblocks that implement support for building and installing (groups -of) software packages. - -- easybuild-easyconfigs -(http://pypi.python.org/pypi/easybuild-easyconfigs): a collection of -example easyconfig files that specify which software to build, and using -which build options; these easyconfigs will be well tested with the -latest compatible versions of the easybuild-framework and -easybuild-easyblocks packages. - -The code in the vsc directory originally comes from VSC-tools -(https://github.com/hpcugent/VSC-tools). +* build status - **master** branch *(Python 2.4, Python 2.6, Python 2.7)* + + .. image:: https://jenkins1.ugent.be/view/EasyBuild/job/easybuild-framework_unit-test_hpcugent_master-python24/badge/icon + :target: https://jenkins1.ugent.be/view/EasyBuild/job/easybuild-framework_unit-test_hpcugent_master-python24/ + .. image:: https://jenkins1.ugent.be/view/EasyBuild/job/easybuild-framework_unit-test_hpcugent_master/badge/icon + :target: https://jenkins1.ugent.be/view/EasyBuild/job/easybuild-framework_unit-test_hpcugent_master/ + .. image:: https://jenkins1.ugent.be/view/EasyBuild/job/easybuild-framework_unit-test_hpcugent_master-python27/badge/icon + :target: https://jenkins1.ugent.be/view/EasyBuild/job/easybuild-framework_unit-test_hpcugent_master-python27/ + +* build status - **develop** branch *(Python 2.4, Python 2.6, Python 2.7)* + + .. image:: https://jenkins1.ugent.be/view/EasyBuild/job/easybuild-framework_unit-test_hpcugent_develop-python24/badge/icon + :target: https://jenkins1.ugent.be/view/EasyBuild/job/easybuild-framework_unit-test_hpcugent_develop-python24/ + .. image:: https://jenkins1.ugent.be/view/EasyBuild/job/easybuild-framework_unit-test_hpcugent_develop/badge/icon + :target: https://jenkins1.ugent.be/view/EasyBuild/job/easybuild-framework_unit-test_hpcugent_develop/ + .. image:: https://jenkins1.ugent.be/view/EasyBuild/job/easybuild-framework_unit-test_hpcugent_develop-python27/badge/icon + :target: https://jenkins1.ugent.be/view/EasyBuild/job/easybuild-framework_unit-test_hpcugent_develop-python27/ + +Related packages: + +* `easybuild-easyblocks `_: a collection of easyblocks that implement support for building and installing (groups of) software packages. +* `easybuild-easyconfigs `_: a collection of example easyconfig files that specify which software to build, and using which build options; these easyconfigs will be well tested with the latest compatible versions of the easybuild-framework and easybuild-easyblocks packages. + +The code in the ``vsc`` directory originally comes from the *vsc-base* package +(https://github.com/hpcugent/vsc-base). From 67d4a90591d432e200281892ce5cdb4ce7e26c4c Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 4 Nov 2014 19:06:29 +0100 Subject: [PATCH 133/298] move build statuses to the bottom of the README --- README.rst | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/README.rst b/README.rst index f071e1049f..b2323a945c 100644 --- a/README.rst +++ b/README.rst @@ -19,7 +19,18 @@ The EasyBuild framework source code is hosted on GitHub, along with an issue tracker for bug reports and feature requests, see http://github.com/hpcugent/easybuild-framework. -* build status - **master** branch *(Python 2.4, Python 2.6, Python 2.7)* +Related packages: + +* `easybuild-easyblocks `_: a collection of easyblocks that implement support for building and installing (groups of) software packages. +* `easybuild-easyconfigs `_: a collection of example easyconfig files that specify which software to build, and using which build options; these easyconfigs will be well tested with the latest compatible versions of the easybuild-framework and easybuild-easyblocks packages. + +The code in the ``vsc`` directory originally comes from the *vsc-base* package +(https://github.com/hpcugent/vsc-base). + + +*Build status overview:* + +* **master** branch *(Python 2.4, Python 2.6, Python 2.7)* .. image:: https://jenkins1.ugent.be/view/EasyBuild/job/easybuild-framework_unit-test_hpcugent_master-python24/badge/icon :target: https://jenkins1.ugent.be/view/EasyBuild/job/easybuild-framework_unit-test_hpcugent_master-python24/ @@ -28,7 +39,7 @@ http://github.com/hpcugent/easybuild-framework. .. image:: https://jenkins1.ugent.be/view/EasyBuild/job/easybuild-framework_unit-test_hpcugent_master-python27/badge/icon :target: https://jenkins1.ugent.be/view/EasyBuild/job/easybuild-framework_unit-test_hpcugent_master-python27/ -* build status - **develop** branch *(Python 2.4, Python 2.6, Python 2.7)* +* **develop** branch *(Python 2.4, Python 2.6, Python 2.7)* .. image:: https://jenkins1.ugent.be/view/EasyBuild/job/easybuild-framework_unit-test_hpcugent_develop-python24/badge/icon :target: https://jenkins1.ugent.be/view/EasyBuild/job/easybuild-framework_unit-test_hpcugent_develop-python24/ @@ -36,11 +47,3 @@ http://github.com/hpcugent/easybuild-framework. :target: https://jenkins1.ugent.be/view/EasyBuild/job/easybuild-framework_unit-test_hpcugent_develop/ .. image:: https://jenkins1.ugent.be/view/EasyBuild/job/easybuild-framework_unit-test_hpcugent_develop-python27/badge/icon :target: https://jenkins1.ugent.be/view/EasyBuild/job/easybuild-framework_unit-test_hpcugent_develop-python27/ - -Related packages: - -* `easybuild-easyblocks `_: a collection of easyblocks that implement support for building and installing (groups of) software packages. -* `easybuild-easyconfigs `_: a collection of example easyconfig files that specify which software to build, and using which build options; these easyconfigs will be well tested with the latest compatible versions of the easybuild-framework and easybuild-easyblocks packages. - -The code in the ``vsc`` directory originally comes from the *vsc-base* package -(https://github.com/hpcugent/vsc-base). From b7f45442301ccd54a07d6e7a5f6eb7baaf04b112 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 4 Nov 2014 19:11:58 +0100 Subject: [PATCH 134/298] fix related repos links --- README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index b2323a945c..be8d116a59 100644 --- a/README.rst +++ b/README.rst @@ -19,10 +19,10 @@ The EasyBuild framework source code is hosted on GitHub, along with an issue tracker for bug reports and feature requests, see http://github.com/hpcugent/easybuild-framework. -Related packages: +Related repositories: -* `easybuild-easyblocks `_: a collection of easyblocks that implement support for building and installing (groups of) software packages. -* `easybuild-easyconfigs `_: a collection of example easyconfig files that specify which software to build, and using which build options; these easyconfigs will be well tested with the latest compatible versions of the easybuild-framework and easybuild-easyblocks packages. +* `easybuild-easyblocks `_: a collection of easyblocks that implement support for building and installing (groups of) software packages. +* `easybuild-easyconfigs `_: a collection of example easyconfig files that specify which software to build, and using which build options; these easyconfigs will be well tested with the latest compatible versions of the easybuild-framework and easybuild-easyblocks packages. The code in the ``vsc`` directory originally comes from the *vsc-base* package (https://github.com/hpcugent/vsc-base). From 8e8942e000d57dfc83776ddf19a8a59a9d985f03 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 4 Nov 2014 19:12:42 +0100 Subject: [PATCH 135/298] fix homepage URL --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index be8d116a59..418fe46b7d 100644 --- a/README.rst +++ b/README.rst @@ -4,7 +4,7 @@ EasyBuild: building software with ease .. image:: http://hpcugent.github.io/easybuild/images/easybuild_logo_small.png :align: center -`EasyBuild `_ is a software build +`EasyBuild `_ is a software build and installation framework that allows you to manage (scientific) software on High Performance Computing (HPC) systems in an efficient way. From a7d5b79edcb23a4e14d78ef6eee39bf486b909c3 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 4 Nov 2014 19:21:15 +0100 Subject: [PATCH 136/298] cosmetic changes --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 418fe46b7d..9a85c37a58 100644 --- a/README.rst +++ b/README.rst @@ -8,7 +8,7 @@ EasyBuild: building software with ease and installation framework that allows you to manage (scientific) software on High Performance Computing (HPC) systems in an efficient way. -The *easybuild-framework* package is the core of EasyBuild. It +The **easybuild-framework** package is the core of EasyBuild. It supports the implementation and use of so-called easyblocks which implement the software install procedure for a particular (group of) software package(s). From 054a8364daae76a043c615bbe152f00108677560 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 4 Nov 2014 20:36:55 +0100 Subject: [PATCH 137/298] fix (long) description in setup.py, more cosmetics --- README.rst | 17 ++++++++++++++--- setup.py | 11 +++-------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/README.rst b/README.rst index 9a85c37a58..3cc0398349 100644 --- a/README.rst +++ b/README.rst @@ -19,10 +19,21 @@ The EasyBuild framework source code is hosted on GitHub, along with an issue tracker for bug reports and feature requests, see http://github.com/hpcugent/easybuild-framework. -Related repositories: +Related Python packages: -* `easybuild-easyblocks `_: a collection of easyblocks that implement support for building and installing (groups of) software packages. -* `easybuild-easyconfigs `_: a collection of example easyconfig files that specify which software to build, and using which build options; these easyconfigs will be well tested with the latest compatible versions of the easybuild-framework and easybuild-easyblocks packages. +* **easybuild-easyblocks** + + * a collection of easyblocks that implement support for building and installing (groups of) software packages + * GitHub repository: http://github.com/hpcugent/easybuild-easyblocks + * package on PyPi: https://pypi.python.org/pypi/easybuild-easyblocks + +* **easybuild-easyconfigs** + + * a collection of example easyconfig files that specify which software to build, + and using which build options; these easyconfigs will be well tested + with the latest compatible versions of the easybuild-framework and easybuild-easyblocks packages + * GitHub repository: http://github.com/hpcugent/easybuild-easyconfigs + * PyPi: https://pypi.python.org/pypi/easybuild-easyconfigs The code in the ``vsc`` directory originally comes from the *vsc-base* package (https://github.com/hpcugent/vsc-base). diff --git a/setup.py b/setup.py index 5a707b3d82..119ec928e5 100644 --- a/setup.py +++ b/setup.py @@ -82,8 +82,8 @@ def find_rel_test(): version = str(VERSION), author = "EasyBuild community", author_email = "easybuild@lists.ugent.be", - description = """EasyBuild is a software installation framework in Python that allows you to \ -install software in a structured and robust way. + description = """EasyBuild is a software build and installation framework that allows you to \ +manage (scientific) software on High Performance Computing (HPC) systems in an efficient way. This package contains the EasyBuild framework, which supports the creation of custom easyblocks that \ implement support for installing particular (groups of) software packages.""", license = "GPLv2", @@ -96,12 +96,7 @@ def find_rel_test(): data_files = [ ('easybuild', ["easybuild/easybuild_config.py"]), ], - long_description = """This package contains the EasyBuild -framework, which supports the creation of custom easyblocks that -implement support for installing particular (groups of) software -packages. - -""" + read("README.rst"), + long_description = read('README.rst'), classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: Console", From 920fffe934452352f2215220b744657c73611fde Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 4 Nov 2014 20:40:01 +0100 Subject: [PATCH 138/298] fix description --- setup.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 119ec928e5..361191c59d 100644 --- a/setup.py +++ b/setup.py @@ -82,9 +82,7 @@ def find_rel_test(): version = str(VERSION), author = "EasyBuild community", author_email = "easybuild@lists.ugent.be", - description = """EasyBuild is a software build and installation framework that allows you to \ -manage (scientific) software on High Performance Computing (HPC) systems in an efficient way. -This package contains the EasyBuild framework, which supports the creation of custom easyblocks that \ + description = """The EasyBuild framework supports the creation of custom easyblocks that \ implement support for installing particular (groups of) software packages.""", license = "GPLv2", keywords = "software build building installation installing compilation HPC scientific", From a739cf1b82eb41c3a828245d86e48f6076124783 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 4 Nov 2014 20:59:47 +0100 Subject: [PATCH 139/298] drop title in README --- README.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.rst b/README.rst index 3cc0398349..5b375ff615 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,3 @@ -EasyBuild: building software with ease --------------------------------------- - .. image:: http://hpcugent.github.io/easybuild/images/easybuild_logo_small.png :align: center From c6669c85a6b01999eaaa1c600791dcc60e5dd270 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 5 Nov 2014 09:19:43 +0100 Subject: [PATCH 140/298] stop triggering legacy code (first draft) --- easybuild/framework/easyconfig/easyconfig.py | 20 +++++++-- easybuild/tools/config.py | 44 ++++++++++---------- easybuild/tools/options.py | 2 +- test/framework/config.py | 14 +++---- 4 files changed, 46 insertions(+), 34 deletions(-) diff --git a/easybuild/framework/easyconfig/easyconfig.py b/easybuild/framework/easyconfig/easyconfig.py index c58e7349a5..b85f75252b 100644 --- a/easybuild/framework/easyconfig/easyconfig.py +++ b/easybuild/framework/easyconfig/easyconfig.py @@ -788,9 +788,23 @@ def get_easyblock_class(easyblock, name=None): class_name = encode_class_name(name) # modulepath will be the namespace + encoded modulename (from the classname) modulepath = get_module_path(class_name) - if not os.path.exists("%s.py" % modulepath): - _log.deprecated("Determine module path based on software name", "2.0") - modulepath = get_module_path(name, decode=False) + modulepath_imported = False + try: + __import__(modulepath, globals(), locals(), ['']) + modulepath_imported = True + except ImportError, err: + _log.debug("Failed to import module '%s': %s" % (modulepath, err)) + + # check if determining module path based on software name would have resulted in a different module path + if modulepath_imported: + _log.debug("Module path '%s' found" % modulepath) + else: + _log.debug("No module path '%s' found" % modulepath) + modulepath_bis = get_module_path(name, decode=False) + _log.debug("Module path determined based on software name: %s" % modulepath_bis) + if modulepath_bis != modulepath: + _log.deprecated("Determine module path based on software name", "2.0") + modulepath = modulepath_bis # try and find easyblock try: diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py index 3c4467ad3c..f648d7c42b 100644 --- a/easybuild/tools/config.py +++ b/easybuild/tools/config.py @@ -53,6 +53,7 @@ # class constant to prepare migration to generaloption as only way of configuration (maybe for v2.X) SUPPORT_OLDSTYLE = True +DEFAULT_OLDSTYLE_CONFIG_FILE = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'easybuild_config.py') DEFAULT_LOGFILE_FORMAT = ("easybuild", "easybuild-%(name)s-%(version)s-%(date)s.%(time)s.log") @@ -157,14 +158,15 @@ ] +# note: keys are new style option names OLDSTYLE_ENVIRONMENT_VARIABLES = { - 'build_path': 'EASYBUILDBUILDPATH', - 'config_file': 'EASYBUILDCONFIG', - 'install_path': 'EASYBUILDINSTALLPATH', - 'log_format': 'EASYBUILDLOGFORMAT', - 'log_dir': 'EASYBUILDLOGDIR', - 'source_path': 'EASYBUILDSOURCEPATH', - 'test_output_path': 'EASYBUILDTESTOUTPUT', + 'buildpath': 'EASYBUILDBUILDPATH', + 'config': 'EASYBUILDCONFIG', + 'installpath': 'EASYBUILDINSTALLPATH', + 'logfile_format': 'EASYBUILDLOGFORMAT', + 'tmp_logdir': 'EASYBUILDLOGDIR', + 'sourcepath': 'EASYBUILDSOURCEPATH', + 'testoutput': 'EASYBUILDTESTOUTPUT', } @@ -188,9 +190,7 @@ def map_to_newstyle(adict): res = {} for key, val in adict.items(): if key in OLDSTYLE_NEWSTYLE_MAP: - newkey = OLDSTYLE_NEWSTYLE_MAP.get(key) - _log.deprecated("oldstyle key %s usage found, replacing with newkey %s" % (key, newkey), "2.0") - key = newkey + key = OLDSTYLE_NEWSTYLE_MAP[key] res[key] = val return res @@ -264,7 +264,7 @@ def get_default_oldstyle_configfile(): # - check environment variable EASYBUILDCONFIG # - next, check for an EasyBuild config in $HOME/.easybuild/config.py # - last, use default config file easybuild_config.py in main.py directory - config_env_var = OLDSTYLE_ENVIRONMENT_VARIABLES['config_file'] + config_env_var = OLDSTYLE_ENVIRONMENT_VARIABLES['config'] home_config_file = os.path.join(get_user_easybuild_dir(), "config.py") if os.getenv(config_env_var): _log.debug("Environment variable %s, so using that as config file." % config_env_var) @@ -275,11 +275,11 @@ def get_default_oldstyle_configfile(): else: # this should be easybuild.tools.config, the default config file is # part of framework in easybuild (ie in tool/..) - appPath = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) - config_file = os.path.join(appPath, "easybuild_config.py") - _log.debug("Falling back to default config: %s" % config_file) - - _log.deprecated("get_default_oldstyle_configfile oldstyle configfile %s used" % config_file, "2.0") + if os.path.exists(DEFAULT_OLDSTYLE_CONFIG_FILE): + config_file = DEFAULT_OLDSTYLE_CONFIG_FILE + _log.debug("Falling back to default config: %s" % config_file) + else: + config_file = None return config_file @@ -344,7 +344,10 @@ def init(options, config_options_dict): """ tmpdict = {} if SUPPORT_OLDSTYLE: - _log.deprecated('oldstyle init with modifications to support oldstyle options', '2.0') + if not os.path.samefile(options.config, DEFAULT_OLDSTYLE_CONFIG_FILE): + # only trip if an oldstyle config other than the default is used (via $EASYBUILDCONFIG or --config) + # we still need the oldstyle default config file to ensure legacy behavior, for now + _log.deprecated('use of oldstyle configuration file %s' % options.config, '2.0') tmpdict.update(oldstyle_init(options.config)) # add the DEFAULT_MODULECLASSES as default (behavior is now that this extends the default list) @@ -623,7 +626,6 @@ def oldstyle_init(filename, **kwargs): Variables are read in this order of preference: CLI option > environment > config file """ res = {} - _log.deprecated("oldstyle_init filename %s kwargs %s" % (filename, kwargs), "2.0") _log.debug('variables before oldstyle_init %s' % res) res.update(oldstyle_read_configuration(filename)) # config file @@ -641,8 +643,6 @@ def oldstyle_read_configuration(filename): """ Read variables from the config file """ - _log.deprecated("oldstyle_read_configuration filename %s" % filename, "2.0") - # import avail_repositories here to avoid cyclic dependencies # this block of code is going to be removed in EB v2.0 from easybuild.tools.repository.repository import avail_repositories @@ -660,8 +660,6 @@ def oldstyle_read_environment(env_vars=None, strict=False): Read variables from the environment - strict=True enforces that all possible environment variables are found """ - _log.deprecated(('Adapt code to use read_environment from easybuild.tools.utilities ' - 'and do not use oldstyle environment variables'), '2.0') if env_vars is None: env_vars = OLDSTYLE_ENVIRONMENT_VARIABLES result = {} @@ -669,7 +667,7 @@ def oldstyle_read_environment(env_vars=None, strict=False): env_var = env_vars[key] if env_var in os.environ: result[key] = os.environ[env_var] - _log.deprecated("Found oldstyle environment variable %s for %s: %s" % (env_var, key, result[key]), "2.0") + _log.deprecated("Use of oldstyle environment variable %s for %s: %s" % (env_var, key, result[key]), "2.0") elif strict: _log.error("Can't determine value for %s. Environment variable %s is missing" % (key, env_var)) else: diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index 3d3ed7f84e..52940845b9 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -205,7 +205,7 @@ def config_options(self): 'ignore-dirs': ("Directory names to ignore when searching for files/dirs", 'strlist', 'store', ['.git', '.svn']), 'installpath': ("Install path for software and modules", None, 'store', oldstyle_defaults['installpath']), - 'config': ("Path to EasyBuild config file", + 'config': ("Path to EasyBuild config file (DEPRECATED, use --configfiles instead!)", None, 'store', oldstyle_defaults['config'], 'C'), 'logfile-format': ("Directory name and format of the log file", 'strtuple', 'store', oldstyle_defaults['logfile_format'], {'metavar': 'DIR,FORMAT'}), diff --git a/test/framework/config.py b/test/framework/config.py index b28ec141cf..79fc500b8c 100644 --- a/test/framework/config.py +++ b/test/framework/config.py @@ -79,7 +79,7 @@ def configure(self, args=None): options = init_config(args=args) return options.config - def test_default_config(self): + def xtest_default_config(self): """Test default configuration.""" self.purge_environment() @@ -245,7 +245,7 @@ def test_legacy_config_file(self): self.purge_environment() cfg_fn = self.configure(args=[]) - self.assertTrue(cfg_fn.endswith('easybuild/easybuild_config.py')) + #self.assertTrue(cfg_fn.endswith('easybuild/easybuild_config.py')) configtxt = """ build_path = '%(buildpath)s' @@ -354,7 +354,7 @@ def test_legacy_config_file(self): self.assertEqual(log_file_format(), logtmpl) self.assertEqual(get_build_log_path(), tmplogdir) - def test_generaloption_config(self): + def xtest_generaloption_config(self): """Test new-style configuration (based on generaloption).""" self.purge_environment() @@ -413,7 +413,7 @@ def test_generaloption_config(self): del os.environ['EASYBUILD_PREFIX'] del os.environ['EASYBUILD_SUBDIR_SOFTWARE'] - def test_generaloption_config_file(self): + def xtest_generaloption_config_file(self): """Test use of new-style configuration file.""" self.purge_environment() @@ -475,7 +475,7 @@ def test_generaloption_config_file(self): del os.environ['EASYBUILD_CONFIGFILES'] - def test_set_tmpdir(self): + def xtest_set_tmpdir(self): """Test set_tmpdir config function.""" self.purge_environment() @@ -501,7 +501,7 @@ def test_set_tmpdir(self): modify_env(os.environ, self.orig_environ) tempfile.tempdir = None - def test_configuration_variables(self): + def xtest_configuration_variables(self): """Test usage of ConfigurationVariables.""" # delete instance of ConfigurationVariables ConfigurationVariables.__metaclass__._instances.pop(ConfigurationVariables, None) @@ -513,7 +513,7 @@ def test_configuration_variables(self): self.assertTrue(cv1 is cv2) self.assertTrue(cv1 is cv3) - def test_build_options(self): + def xtest_build_options(self): """Test usage of BuildOptions.""" # delete instance of BuildOptions BuildOptions.__metaclass__._instances.pop(BuildOptions, None) From 8f589699186429fd6c75cc3a32733843d60fbb3d Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 5 Nov 2014 19:01:12 +0100 Subject: [PATCH 141/298] deprecate self.moduleGenerator in favor of self.module_generator in EasyBlock --- easybuild/framework/easyblock.py | 48 +++++++++++++--------- easybuild/scripts/mk_tmpl_easyblock_for.py | 4 +- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 0c96f815b2..6d7bb5815c 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -137,7 +137,7 @@ def __init__(self, ec): # modules interface with default MODULEPATH self.modules_tool = modules_tool() # module generator - self.moduleGenerator = ModuleGenerator(self, fake=True) + self.module_generator = ModuleGenerator(self, fake=True) # modules footer self.modules_footer = None @@ -613,6 +613,14 @@ def short_mod_name(self): """ return self.cfg.short_mod_name + @property + def moduleGenerator(self): + """ + Module generator (DEPRECATED, use self.module_generator instead). + """ + _log.deprecated("Environment variable SOFTDEVEL* being relied on", "2.0") + return self.module_generator + # # DIRECTORY UTILITY FUNCTIONS # @@ -816,8 +824,8 @@ def make_module_dep(self): deps = [d for d in deps if d not in excluded_deps] self.log.debug("List of retained dependencies: %s" % deps) - loads = [self.moduleGenerator.load_module(d) for d in deps] - unloads = [self.moduleGenerator.unload_module(d) for d in deps[::-1]] + loads = [self.module_generator.load_module(d) for d in deps] + unloads = [self.module_generator.unload_module(d) for d in deps[::-1]] # Force unloading any other modules if self.cfg['moduleforceunload']: @@ -829,7 +837,7 @@ def make_module_description(self): """ Create the module description. """ - return self.moduleGenerator.get_description() + return self.module_generator.get_description() def make_module_extra(self): """ @@ -839,26 +847,26 @@ def make_module_extra(self): # EBROOT + EBVERSION + EBDEVEL environment_name = convert_name(self.name, upper=True) - txt += self.moduleGenerator.set_environment(ROOT_ENV_VAR_NAME_PREFIX + environment_name, "$root") - txt += self.moduleGenerator.set_environment(VERSION_ENV_VAR_NAME_PREFIX + environment_name, self.version) + txt += self.module_generator.set_environment(ROOT_ENV_VAR_NAME_PREFIX + environment_name, "$root") + txt += self.module_generator.set_environment(VERSION_ENV_VAR_NAME_PREFIX + environment_name, self.version) devel_path = os.path.join("$root", log_path(), ActiveMNS().det_devel_module_filename(self.cfg)) - txt += self.moduleGenerator.set_environment(DEVEL_ENV_VAR_NAME_PREFIX + environment_name, devel_path) + txt += self.module_generator.set_environment(DEVEL_ENV_VAR_NAME_PREFIX + environment_name, devel_path) txt += "\n" for (key, value) in self.cfg['modextravars'].items(): - txt += self.moduleGenerator.set_environment(key, value) + txt += self.module_generator.set_environment(key, value) for (key, value) in self.cfg['modextrapaths'].items(): if isinstance(value, basestring): value = [value] elif not isinstance(value, (tuple, list)): self.log.error("modextrapaths dict value %s (type: %s) is not a list or tuple" % (value, type(value))) - txt += self.moduleGenerator.prepend_paths(key, value) + txt += self.module_generator.prepend_paths(key, value) if self.cfg['modloadmsg']: - txt += self.moduleGenerator.msg_on_load(self.cfg['modloadmsg']) + txt += self.module_generator.msg_on_load(self.cfg['modloadmsg']) if self.cfg['modtclfooter']: - txt += self.moduleGenerator.add_tcl_footer(self.cfg['modtclfooter']) + txt += self.module_generator.add_tcl_footer(self.cfg['modtclfooter']) for (key, value) in self.cfg['modaliases'].items(): - txt += self.moduleGenerator.set_alias(key, value) + txt += self.module_generator.set_alias(key, value) self.log.debug("make_module_extra added this: %s" % txt) @@ -874,7 +882,7 @@ def make_module_extra_extensions(self): # set environment variable that specifies list of extensions if self.exts_all: exts_list = ','.join(['%s-%s' % (ext['name'], ext.get('version', '')) for ext in self.exts_all]) - txt += self.moduleGenerator.set_environment('EBEXTSLIST%s' % self.name.upper(), exts_list) + txt += self.module_generator.set_environment('EBEXTSLIST%s' % self.name.upper(), exts_list) return txt @@ -909,7 +917,7 @@ def make_module_extend_modpath(self): # module path extensions must exist, otherwise loading this module file will fail for modpath_extension in full_path_modpath_extensions: mkdir(modpath_extension, parents=True) - txt = self.moduleGenerator.use(full_path_modpath_extensions) + txt = self.module_generator.use(full_path_modpath_extensions) else: self.log.debug("Not including module path extensions, as specified.") return txt @@ -931,7 +939,7 @@ def make_module_req(self): for path in requirements[key]: paths = glob.glob(path) if paths: - txt += self.moduleGenerator.prepend_paths(key, paths) + txt += self.module_generator.prepend_paths(key, paths) try: os.chdir(self.orig_workdir) except OSError, err: @@ -1660,8 +1668,8 @@ def make_module_step(self, fake=False): """ Generate a module file. """ - self.moduleGenerator.set_fake(fake) - modpath = self.moduleGenerator.prepare() + self.module_generator.set_fake(fake) + modpath = self.module_generator.prepare() txt = '' txt += self.make_module_description() @@ -1671,12 +1679,12 @@ def make_module_step(self, fake=False): txt += self.make_module_extra() txt += self.make_module_footer() - write_file(self.moduleGenerator.filename, txt) + write_file(self.module_generator.filename, txt) - self.log.info("Module file %s written" % self.moduleGenerator.filename) + self.log.info("Module file %s written" % self.module_generator.filename) self.modules_tool.update() - self.moduleGenerator.create_symlinks() + self.module_generator.create_symlinks() if not fake: self.make_devel_module() diff --git a/easybuild/scripts/mk_tmpl_easyblock_for.py b/easybuild/scripts/mk_tmpl_easyblock_for.py index 8c25f14135..f2fcffbca6 100755 --- a/easybuild/scripts/mk_tmpl_easyblock_for.py +++ b/easybuild/scripts/mk_tmpl_easyblock_for.py @@ -208,8 +208,8 @@ def make_module_extra(self): txt = super(%(class_name)s, self).make_module_extra() - txt += self.moduleGenerator.set_environment("VARIABLE", 'value') - txt += self.moduleGenerator.prepend_paths("PATH_VAR", ['path1', 'path2']) + txt += self.module_generator.set_environment("VARIABLE", 'value') + txt += self.module_generator.prepend_paths("PATH_VAR", ['path1', 'path2']) return txt """ From 15aea89617839110f7d5195deef08527e955152a Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 5 Nov 2014 21:56:40 +0100 Subject: [PATCH 142/298] fix unit test that fails when installed easyconfigs package is available --- test/framework/options.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/framework/options.py b/test/framework/options.py index 4803299cb2..d52e934a38 100644 --- a/test/framework/options.py +++ b/test/framework/options.py @@ -676,11 +676,8 @@ def test_dry_run_hierarchical(self): '--ignore-osdeps', '--force', '--debug', + '--robot-paths=%s' % os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs'), ] - errmsg = r"No robot path specified, which is required when looking for easyconfigs \(use --robot\)" - self.assertErrorRegex(EasyBuildError, errmsg, self.eb_main, args, logfile=dummylogfn, raise_error=True) - - args.append('--robot=%s' % os.path.join(os.path.dirname(__file__), 'easyconfigs')) outtxt = self.eb_main(args, logfile=dummylogfn, verbose=True, raise_error=True) ecs_mods = [ From 34c61feb20b84cc891c38539f780a6131c1f0a3b Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 6 Nov 2014 13:16:14 +0100 Subject: [PATCH 143/298] clean up revamped implementation of download_file --- easybuild/tools/filetools.py | 111 ++++++++++++++++++----------------- test/framework/filetools.py | 3 + 2 files changed, 61 insertions(+), 53 deletions(-) diff --git a/easybuild/tools/filetools.py b/easybuild/tools/filetools.py index 56d202eea5..98350156a7 100644 --- a/easybuild/tools/filetools.py +++ b/easybuild/tools/filetools.py @@ -244,89 +244,94 @@ def det_common_path_prefix(paths): def download_file(filename, url, path): """Download a file from the given URL, to the specified path.""" - _log.debug("Downloading %s from %s to %s" % (filename, url, path)) + _log.debug("Trying to download %s from %s to %s", filename, url, path) # make sure directory exists basedir = os.path.dirname(path) mkdir(basedir, parents=True) - downloaded = False - attempt_cnt = 0 - - # use this functions's scope for the variable we share with our inner function - download_file.last_time = time.time() - download_file.last_block = 0 # internal function to report on download progress - def report(block, blocksize, filesize): + def report(blocks_read, blocksize, filesize): """ - This is a reporthook for urlretrieve, it takes 3 integers as arguments: - the current downloaded block, the size in bytes of one block and the total size of the downlad. - This efectively logs the download progress every 10 seconds with loglevel info. + Report hook for urlretrieve, which logs the download progress every 10 seconds with log level info. + @param blocks_read: number of blocks already read + @param blocksize: size of one block, in bytes + @param filesize: total size of the download (in number of blocks blocks) """ if download_file.last_time + 10 < time.time(): - newblocks = block - download_file.last_block - download_file.last_block = block - total_download = block * blocksize - percentage = int(block * blocksize * 100 / filesize) - kbps = (blocksize * newblocks) / 1024 // (time.time() - download_file.last_time) + newblocks = blocks_read - download_file.last_block + download_file.last_block = blocks_read + tot_time = time.time() - download_file.last_time if filesize <= 0: # content length isn't always set - _log.info('download report: %d kb downloaded (%d kbps)', total_download, kbps) + report_msg = "downloaded in %ss" % tot_time else: - _log.info('download report: %d kb of %d kb (%d %%, %d kbps)', total_download, filesize, percentage, kbps) - - download_file.last_time = time.time() + percent = blocks_read * blocksize * 100 // filesize + report_msg = "of %d kb downloaded in %ss [%d %%]" % (filesize / 1024.0, tot_time, percent) + downloaded_kbs = (blocks_read * blocksize) / 1024.0 + kbps = (blocksize * newblocks) / 1024 // tot_time + _log.info("Download report: %d kb %s (%d kbps)", downloaded_kbs, report_msg, kbps) - # try downloading three times max. + download_file.last_time = time.time() + + # try downloading, three times max. + downloaded = False + attempt_cnt = 0 while not downloaded and attempt_cnt < 3: # get http response code first before downloading file - urlfile = urllib.urlopen(url) - response_code = urlfile.getcode() - urlfile.close() + try: + urlfile = urllib.urlopen(url) + response_code = urlfile.getcode() + urlfile.close() + except IOError, err: + response_code = None + if response_code: _log.debug('http response code for given url: %d', response_code) - if response_code == 404: - _log.warning('url %s was not found (404), not trying again', url) - return None + # check for a 4xx response code which indicates a non-existing URL + if response_code // 100 == 4: + _log.warning('url %s was not found (%d), not trying again', response_code, url) + return None + # use this functions's scope for variables we share with inner function used as report hook for urlretrieve download_file.last_time = time.time() download_file.last_block = 0 + httpmsg = None try: (_, httpmsg) = urllib.urlretrieve(url, path, reporthook=report) - except ContentTooShortError: - _log.warning( - "Expected file of %d bytes, but download size dit not match, removing file and retrying", - int(httpmsg.dict['content-length']), - ) - try: - os.remove(path) - except OSError, err: - _log.error("Failed to remove downloaded file:" % err) - # try again - attempt_cnt += 1 - continue + _log.info("Downloaded file %s from url %s to %s", filename, url, path) + except IOError, err: + tup = (filename, url, err) + _log.warning("An error occured when downloadeding %s from %s (%s), removing file and retrying", *tup) + + if httpmsg: + if httpmsg.type == "text/html" and not filename.endswith('.html'): + _log.warning("HTML file downloaded but not expecting it, so assuming invalid download, retrying.") + else: + # successful download + downloaded = True - if httpmsg.type == "text/html" and not filename.endswith('.html'): - _log.warning("HTML file downloaded but not expecting it, so assuming invalid download.") - _log.debug("removing downloaded file %s from %s" % (filename, path)) + if not downloaded: + _log.debug("removing faulty downloaded file %s from %s", filename, path) try: - os.remove(path) + if os.path.exists(path): + os.remove(path) except OSError, err: - _log.error("Failed to remove downloaded file:" % err) - else: - _log.info("Downloading file %s from url %s: done" % (filename, url)) - downloaded = True - return path - - attempt_cnt += 1 - _log.warning("Downloading failed at attempt %s, retrying..." % attempt_cnt) + _log.error("Failed to remove downloaded file: %s", err) + attempt_cnt += 1 + _log.warning("Downloading failed at attempt %s, retrying...", attempt_cnt) - # failed to download after multiple attempts - return None + if downloaded: + _log.info("Successful download of file %s from url %s to path %s", filename, url, path) + return path + else: + # failed to download after multiple attempts + _log.warning("Too many failed download attempts, giving up") + return None def find_easyconfigs(path, ignore_dirs=None): diff --git a/test/framework/filetools.py b/test/framework/filetools.py index 651ea77c30..69ca9a17ab 100644 --- a/test/framework/filetools.py +++ b/test/framework/filetools.py @@ -194,6 +194,9 @@ def test_download_file(self): res = ft.download_file(fn, source_url, target_location) self.assertEqual(res, target_location) + # non-existing files result in None return value + self.assertEqual(ft.download_file(fn, 'file://nosuchfile', target_location), None) + def test_mkdir(self): """Test mkdir function.""" tmpdir = tempfile.mkdtemp() From 5899135e0e8de737c8bb15cb15e06e345b7dd786 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 6 Nov 2014 13:23:32 +0100 Subject: [PATCH 144/298] log HTTP response code fail --- easybuild/tools/filetools.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/easybuild/tools/filetools.py b/easybuild/tools/filetools.py index 98350156a7..47c9e04259 100644 --- a/easybuild/tools/filetools.py +++ b/easybuild/tools/filetools.py @@ -280,12 +280,13 @@ def report(blocks_read, blocksize, filesize): downloaded = False attempt_cnt = 0 while not downloaded and attempt_cnt < 3: - # get http response code first before downloading file + # get HTTP response code first before downloading file try: urlfile = urllib.urlopen(url) response_code = urlfile.getcode() urlfile.close() except IOError, err: + _log.warning("Failed to get HTTP response code for %s: %s", url, err) response_code = None if response_code: From ffbe43e48f446ff72697ba211f6dca1913f35def Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 6 Nov 2014 13:26:25 +0100 Subject: [PATCH 145/298] get rid of 'tup' trickery --- easybuild/tools/filetools.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/easybuild/tools/filetools.py b/easybuild/tools/filetools.py index 47c9e04259..aa1fe1debf 100644 --- a/easybuild/tools/filetools.py +++ b/easybuild/tools/filetools.py @@ -305,8 +305,7 @@ def report(blocks_read, blocksize, filesize): (_, httpmsg) = urllib.urlretrieve(url, path, reporthook=report) _log.info("Downloaded file %s from url %s to %s", filename, url, path) except IOError, err: - tup = (filename, url, err) - _log.warning("An error occured when downloadeding %s from %s (%s), removing file and retrying", *tup) + _log.warning("Error when downloading %s from %s (%s), removing file and retrying", filename, url, err) if httpmsg: if httpmsg.type == "text/html" and not filename.endswith('.html'): From c65df57e2ff222aa4c32c759f75bfc8be9c669a3 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 6 Nov 2014 13:39:31 +0100 Subject: [PATCH 146/298] minor fix in test --- test/framework/filetools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/framework/filetools.py b/test/framework/filetools.py index 69ca9a17ab..f7307e1b41 100644 --- a/test/framework/filetools.py +++ b/test/framework/filetools.py @@ -195,7 +195,7 @@ def test_download_file(self): self.assertEqual(res, target_location) # non-existing files result in None return value - self.assertEqual(ft.download_file(fn, 'file://nosuchfile', target_location), None) + self.assertEqual(ft.download_file(fn, os.path.join('file://', test_dir, 'nosuchfile'), target_location), None) def test_mkdir(self): """Test mkdir function.""" From b3636faf05a19575d4b5baa61f7a686a941d6ea8 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 6 Nov 2014 14:26:06 +0100 Subject: [PATCH 147/298] don't use os.path.join on URLs in tests --- test/framework/filetools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/framework/filetools.py b/test/framework/filetools.py index f7307e1b41..5a23905947 100644 --- a/test/framework/filetools.py +++ b/test/framework/filetools.py @@ -190,12 +190,12 @@ def test_download_file(self): target_location = os.path.join(self.test_buildpath, 'some', 'subdir', fn) # provide local file path as source URL test_dir = os.path.abspath(os.path.dirname(__file__)) - source_url = os.path.join('file://', test_dir, 'sandbox', 'sources', 'toy', fn) + source_url = 'file://%s/sandbox/sources/toy/%s' % (test_dir, fn) res = ft.download_file(fn, source_url, target_location) self.assertEqual(res, target_location) # non-existing files result in None return value - self.assertEqual(ft.download_file(fn, os.path.join('file://', test_dir, 'nosuchfile'), target_location), None) + self.assertEqual(ft.download_file(fn, 'file://%s/nosuchfile' % test_dir, target_location), None) def test_mkdir(self): """Test mkdir function.""" From 9057ee2ec547119dd6b707ef9ae09655c686b41d Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 6 Nov 2014 15:02:17 +0100 Subject: [PATCH 148/298] minor tweaks in download_file function --- easybuild/tools/filetools.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/easybuild/tools/filetools.py b/easybuild/tools/filetools.py index aa1fe1debf..4e9374bc98 100644 --- a/easybuild/tools/filetools.py +++ b/easybuild/tools/filetools.py @@ -286,14 +286,14 @@ def report(blocks_read, blocksize, filesize): response_code = urlfile.getcode() urlfile.close() except IOError, err: - _log.warning("Failed to get HTTP response code for %s: %s", url, err) + _log.warning("Failed to get HTTP response code for %s, retrying: %s", url, err) response_code = None if response_code: - _log.debug('http response code for given url: %d', response_code) + _log.debug('HTTP response code for given url: %d', response_code) # check for a 4xx response code which indicates a non-existing URL if response_code // 100 == 4: - _log.warning('url %s was not found (%d), not trying again', response_code, url) + _log.warning('url %s was not found (HTTP response %d), not trying again', url, response_code) return None # use this functions's scope for variables we share with inner function used as report hook for urlretrieve @@ -310,18 +310,18 @@ def report(blocks_read, blocksize, filesize): if httpmsg: if httpmsg.type == "text/html" and not filename.endswith('.html'): _log.warning("HTML file downloaded but not expecting it, so assuming invalid download, retrying.") + + _log.debug("removing faulty downloaded file %s from %s", filename, path) + try: + if os.path.exists(path): + os.remove(path) + except OSError, err: + _log.error("Failed to remove downloaded file: %s", err) else: # successful download downloaded = True if not downloaded: - _log.debug("removing faulty downloaded file %s from %s", filename, path) - try: - if os.path.exists(path): - os.remove(path) - except OSError, err: - _log.error("Failed to remove downloaded file: %s", err) - attempt_cnt += 1 _log.warning("Downloading failed at attempt %s, retrying...", attempt_cnt) From 096fe882ebb60adb79255895f060c8b489dd233d Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 6 Nov 2014 15:17:13 +0100 Subject: [PATCH 149/298] use inner function to remove faulty download --- easybuild/tools/filetools.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/easybuild/tools/filetools.py b/easybuild/tools/filetools.py index 4e9374bc98..33a666d0ad 100644 --- a/easybuild/tools/filetools.py +++ b/easybuild/tools/filetools.py @@ -276,6 +276,17 @@ def report(blocks_read, blocksize, filesize): download_file.last_time = time.time() + def cleanup_faulty_download(): + """ + Clean up faulty download. + """ + _log.debug("removing faulty downloaded file %s from %s", filename, path) + try: + if os.path.exists(path): + os.remove(path) + except OSError, err: + _log.error("Failed to remove downloaded file: %s", err) + # try downloading, three times max. downloaded = False attempt_cnt = 0 @@ -304,22 +315,16 @@ def report(blocks_read, blocksize, filesize): try: (_, httpmsg) = urllib.urlretrieve(url, path, reporthook=report) _log.info("Downloaded file %s from url %s to %s", filename, url, path) - except IOError, err: - _log.warning("Error when downloading %s from %s (%s), removing file and retrying", filename, url, err) - if httpmsg: if httpmsg.type == "text/html" and not filename.endswith('.html'): _log.warning("HTML file downloaded but not expecting it, so assuming invalid download, retrying.") - - _log.debug("removing faulty downloaded file %s from %s", filename, path) - try: - if os.path.exists(path): - os.remove(path) - except OSError, err: - _log.error("Failed to remove downloaded file: %s", err) + cleanup_faulty_download() else: # successful download downloaded = True + except IOError, err: + _log.warning("Error when downloading %s from %s (%s), removing file and retrying", filename, url, err) + cleanup_faulty_download() if not downloaded: attempt_cnt += 1 From a4485e6b787a96ee3e7ec97cd111812659e70e12 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 6 Nov 2014 16:38:17 +0100 Subject: [PATCH 150/298] flesh out remove_file --- easybuild/tools/filetools.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/easybuild/tools/filetools.py b/easybuild/tools/filetools.py index 33a666d0ad..1a56b739ec 100644 --- a/easybuild/tools/filetools.py +++ b/easybuild/tools/filetools.py @@ -171,6 +171,15 @@ def write_file(path, txt, append=False): _log.error("Failed to write to %s: %s" % (path, err)) +def remove_file(path): + """Remove file at specified path.""" + try: + if os.path.exists(path): + os.remove(path) + except OSError, err: + _log.error("Failed to remove downloaded file: %s", err) + + def extract_file(fn, dest, cmd=None, extra_options=None, overwrite=False): """ Given filename fn, try to extract in directory dest @@ -276,17 +285,6 @@ def report(blocks_read, blocksize, filesize): download_file.last_time = time.time() - def cleanup_faulty_download(): - """ - Clean up faulty download. - """ - _log.debug("removing faulty downloaded file %s from %s", filename, path) - try: - if os.path.exists(path): - os.remove(path) - except OSError, err: - _log.error("Failed to remove downloaded file: %s", err) - # try downloading, three times max. downloaded = False attempt_cnt = 0 @@ -317,14 +315,14 @@ def cleanup_faulty_download(): _log.info("Downloaded file %s from url %s to %s", filename, url, path) if httpmsg.type == "text/html" and not filename.endswith('.html'): - _log.warning("HTML file downloaded but not expecting it, so assuming invalid download, retrying.") - cleanup_faulty_download() + _log.warning("HTML file downloaded to %s, so assuming invalid download, retrying.", path) + remove_file(path) else: # successful download downloaded = True except IOError, err: - _log.warning("Error when downloading %s from %s (%s), removing file and retrying", filename, url, err) - cleanup_faulty_download() + _log.warning("Error when downloading from %s to %s (%s), removing it and retrying", url, path, err) + remove_file(path) if not downloaded: attempt_cnt += 1 From 50187c3f084a7086f2e96e88e33812251a607c52 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 6 Nov 2014 16:55:45 +0100 Subject: [PATCH 151/298] fix log message in remove_file function --- easybuild/tools/filetools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/tools/filetools.py b/easybuild/tools/filetools.py index 1a56b739ec..f9ce769b34 100644 --- a/easybuild/tools/filetools.py +++ b/easybuild/tools/filetools.py @@ -177,7 +177,7 @@ def remove_file(path): if os.path.exists(path): os.remove(path) except OSError, err: - _log.error("Failed to remove downloaded file: %s", err) + _log.error("Failed to remove %s: %s", path, err) def extract_file(fn, dest, cmd=None, extra_options=None, overwrite=False): From 60c52f4f1ad415582272519394ab8d0cacef16c8 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 7 Nov 2014 08:57:45 +0100 Subject: [PATCH 152/298] fix issue with getcode not being available in Py2.4 yet --- easybuild/tools/filetools.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/easybuild/tools/filetools.py b/easybuild/tools/filetools.py index f9ce769b34..18e8c754dc 100644 --- a/easybuild/tools/filetools.py +++ b/easybuild/tools/filetools.py @@ -290,15 +290,16 @@ def report(blocks_read, blocksize, filesize): attempt_cnt = 0 while not downloaded and attempt_cnt < 3: # get HTTP response code first before downloading file + response_code = None try: urlfile = urllib.urlopen(url) - response_code = urlfile.getcode() + if hasattr(urlfile, 'getcode'): # no getcode() in Py2.4 yet + response_code = urlfile.getcode() urlfile.close() except IOError, err: _log.warning("Failed to get HTTP response code for %s, retrying: %s", url, err) - response_code = None - if response_code: + if response_code is not None: _log.debug('HTTP response code for given url: %d', response_code) # check for a 4xx response code which indicates a non-existing URL if response_code // 100 == 4: From 0f596df75af50da0f58e7409993acff78e30efc3 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 7 Nov 2014 09:06:38 +0100 Subject: [PATCH 153/298] only log deprecation warning for non-empty set of extra_options of wrong type --- easybuild/framework/easyblock.py | 2 +- easybuild/framework/easyconfig/easyconfig.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 0c96f815b2..8f3bde180e 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -101,7 +101,7 @@ def extra_options(extra=None): extra = dict(extra) # to avoid breaking backward compatibility, we still need to return a list of tuples in EasyBuild v1.x - _log.deprecated("Returning list of tuples rather than a dict as return value of extra_options", '2.0') + # starting with EasyBuild v2.0, this will be changed to return the actual dict res = extra.items() return res diff --git a/easybuild/framework/easyconfig/easyconfig.py b/easybuild/framework/easyconfig/easyconfig.py index b85f75252b..8d871a6d7d 100644 --- a/easybuild/framework/easyconfig/easyconfig.py +++ b/easybuild/framework/easyconfig/easyconfig.py @@ -165,7 +165,8 @@ def __init__(self, path, extra_options=None, build_specs=None, validate=True, hi if not isinstance(self.extra_options, dict): if isinstance(self.extra_options, (list, tuple,)): typ = type(self.extra_options) - self.log.deprecated("Specified extra_options should be of type 'dict', found type '%s'" % typ, '2.0') + if extra_options: + self.log.deprecated("extra_options return value should be of type 'dict', found '%s'" % typ, '2.0') tup = (self.extra_options, type(self.extra_options)) self.log.debug("Converting extra_options value '%s' of type '%s' to a dict" % tup) self.extra_options = dict(self.extra_options) From 61dbab29347babe6d95a86610fb0f9e07adb4c53 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 7 Nov 2014 09:08:46 +0100 Subject: [PATCH 154/298] fix deprecation log msg for moduleGenerator --- easybuild/framework/easyblock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 6d7bb5815c..52f03cb5b2 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -618,7 +618,7 @@ def moduleGenerator(self): """ Module generator (DEPRECATED, use self.module_generator instead). """ - _log.deprecated("Environment variable SOFTDEVEL* being relied on", "2.0") + self.log.deprecated("self.moduleGenerator is replaced by self.module_generator", "2.0") return self.module_generator # From d71010f9178e77301c7f47b3cab5032f6447574a Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Mon, 10 Nov 2014 23:48:56 +0100 Subject: [PATCH 155/298] reinstate -r as short option for --robot --- easybuild/tools/options.py | 2 +- test/framework/options.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index 3d3ed7f84e..346399f93e 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -98,7 +98,7 @@ def basic_options(self): 'logtostdout': ("Redirect main log to stdout", None, 'store_true', False, 'l'), 'only-blocks': ("Only build listed blocks", None, 'extend', None, 'b', {'metavar': 'BLOCKS'}), 'robot': ("Enable dependency resolution, using easyconfigs in specified paths", - None, 'store_or_None', '', {'metavar': 'PATH[:PATH]'}), + None, 'store_or_None', '', 'r', {'metavar': 'PATH[:PATH]'}), 'robot-paths': ("Additional paths to consider by robot for easyconfigs (--robot paths get priority)", None, 'store', default_robot_paths, {'metavar': 'PATH[:PATH]'}), 'skip': ("Skip existing software (useful for installing additional packages)", diff --git a/test/framework/options.py b/test/framework/options.py index d52e934a38..6def5f9ae0 100644 --- a/test/framework/options.py +++ b/test/framework/options.py @@ -535,10 +535,11 @@ def test_search(self): args = [ search_arg, 'toy-0.0', - '--robot=%s' % os.path.join(os.path.dirname(__file__), 'easyconfigs'), + '-r', + os.path.join(os.path.dirname(__file__), 'easyconfigs'), '--unittest-file=%s' % self.logfile, ] - outtxt = self.eb_main(args, logfile=dummylogfn) + outtxt = self.eb_main(args, logfile=dummylogfn, raise_error=True, verbose=True) info_msg = r"Searching \(case-insensitive\) for 'toy-0.0' in" self.assertTrue(re.search(info_msg, outtxt), "Info message when searching for easyconfigs in '%s'" % outtxt) From 7dee490ff322955689add39254f87eff83ea64be Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 18 Nov 2014 08:31:56 +0100 Subject: [PATCH 156/298] update with vsc-base v1.9.9 --- vsc/README.md | 2 +- vsc/utils/fancylogger.py | 12 +- vsc/utils/generaloption.py | 233 +++++++++++++++++++++++++++---------- vsc/utils/missing.py | 113 ++++++++++++++++-- vsc/utils/rest.py | 6 +- vsc/utils/testing.py | 114 ++++++++++++++++++ 6 files changed, 400 insertions(+), 80 deletions(-) create mode 100644 vsc/utils/testing.py diff --git a/vsc/README.md b/vsc/README.md index c2fac06881..932369c661 100644 --- a/vsc/README.md +++ b/vsc/README.md @@ -1,3 +1,3 @@ Code from https://github.com/hpcugent/vsc-base -based on 95c2174a243874227dcc895d3e26c1b3b949ba22 (vsc-base v1.9.5) +based on 35fee9d3130a6b52bf83993e73d187e9d46c69bc (vsc-base v1.9.9) diff --git a/vsc/utils/fancylogger.py b/vsc/utils/fancylogger.py index b9576bbd68..1f0220df75 100644 --- a/vsc/utils/fancylogger.py +++ b/vsc/utils/fancylogger.py @@ -324,7 +324,7 @@ def thread_name(): return threading.currentThread().getName() -def getLogger(name=None, fname=True, clsname=False, fancyrecord=None): +def getLogger(name=None, fname=False, clsname=False, fancyrecord=None): """ returns a fancylogger if fname is True, the loggers name will be 'name[.classname].functionname' @@ -352,8 +352,10 @@ def getLogger(name=None, fname=True, clsname=False, fancyrecord=None): if os.environ.get('FANCYLOGGER_GETLOGGER_DEBUG', '0').lower() in ('1', 'yes', 'true', 'y'): print 'FANCYLOGGER_GETLOGGER_DEBUG', print 'name', name, 'fname', fname, 'fullname', fullname, - print 'parent_info verbose' - print "\n".join(l.get_parent_info("FANCYLOGGER_GETLOGGER_DEBUG")) + print "getRootLoggerName: ", getRootLoggerName() + if hasattr(l, 'get_parent_info'): + print 'parent_info verbose' + print "\n".join(l.get_parent_info("FANCYLOGGER_GETLOGGER_DEBUG")) sys.stdout.flush() return l @@ -683,8 +685,8 @@ def enableDefaultHandlers(): def getDetailsLogLevels(fancy=True): """ Return list of (name,loglevelname) pairs of existing loggers - - @param fancy: if True, returns only Fancylogger; if False, returns non-FancyLoggers, + + @param fancy: if True, returns only Fancylogger; if False, returns non-FancyLoggers, anything else, return all loggers """ func_map = { diff --git a/vsc/utils/generaloption.py b/vsc/utils/generaloption.py index 2dd49ed8ce..390e5fa79e 100644 --- a/vsc/utils/generaloption.py +++ b/vsc/utils/generaloption.py @@ -72,20 +72,38 @@ def set_columns(cols=None): os.environ['COLUMNS'] = "%s" % cols +def what_str_list_tuple(name): + """Given name, return separator, class and helptext wrt separator. + (Currently supports strlist, strtuple, pathlist, pathtuple) + """ + sep = ',' + helpsep = 'comma' + if name.startswith('path'): + sep = os.pathsep + helpsep = 'pathsep' + + klass = None + if name.endswith('list'): + klass = list + elif name.endswith('tuple'): + klass = tuple + + return sep, klass, helpsep + def check_str_list_tuple(option, opt, value): """ check function for strlist and strtuple type assumes value is comma-separated list returns list or tuple of strings """ - split = value.split(',') - if option.type == 'strlist': - return split - elif option.type == 'strtuple': - return tuple(split) - else: - err = _("check_strlist_strtuple: unsupported type %s" % option.type) + sep, klass, _ = what_str_list_tuple(option.type) + split = value.split(sep) + + if klass is None: + err = _gettext("check_strlist_strtuple: unsupported type %s" % option.type) raise OptionValueError(err) + else: + return klass(split) class ExtOption(CompleterOption): @@ -97,7 +115,10 @@ class ExtOption(CompleterOption): - confighelp : hook for configfile-style help messages - store_debuglog : turns on fancylogger debugloglevel - also: 'store_infolog', 'store_warninglog' - - extend : extend default list (or create new one if is None) + - add : add value to default (result is default + value) + - add_first : add default to value (result is value + default) + - extend : alias for add with strlist type + - type must support + (__add__) and one of negate (__neg__) or slicing (__getslice__) - date : convert into datetime.date - datetime : convert into datetime.datetime - regex: compile str in regexp @@ -105,13 +126,18 @@ class ExtOption(CompleterOption): - set default to None if no option passed, - set to default if option without value passed, - set to value if option with value passed + + Types: + - strlist, strtuple : convert comma-separated string in a list resp. tuple of strings + - pathlist, pathtuple : using os.pathsep, convert pathsep-separated string in a list resp. tuple of strings + - the path separator is OS-dependent """ EXTEND_SEPARATOR = ',' ENABLE = 'enable' # do nothing DISABLE = 'disable' # inverse action - EXTOPTION_EXTRA_OPTIONS = ('extend', 'date', 'datetime', 'regex',) + EXTOPTION_EXTRA_OPTIONS = ('date', 'datetime', 'regex', 'add', 'add_first',) EXTOPTION_STORE_OR = ('store_or_None',) # callback type EXTOPTION_LOG = ('store_debuglog', 'store_infolog', 'store_warninglog',) EXTOPTION_HELP = ('shorthelp', 'confighelp',) @@ -121,10 +147,9 @@ class ExtOption(CompleterOption): TYPED_ACTIONS = Option.TYPED_ACTIONS + EXTOPTION_EXTRA_OPTIONS + EXTOPTION_STORE_OR ALWAYS_TYPED_ACTIONS = Option.ALWAYS_TYPED_ACTIONS + EXTOPTION_EXTRA_OPTIONS - TYPE_CHECKER = dict([('strlist', check_str_list_tuple), - ('strtuple', check_str_list_tuple), - ] + Option.TYPE_CHECKER.items()) - TYPES = tuple(['strlist', 'strtuple'] + list(Option.TYPES)) + TYPE_STRLIST = ['%s%s' % (name, klass) for klass in ['list', 'tuple'] for name in ['str', 'path'] ] + TYPE_CHECKER = dict([(x, check_str_list_tuple) for x in TYPE_STRLIST] + Option.TYPE_CHECKER.items()) + TYPES = tuple(TYPE_STRLIST + list(Option.TYPES)) BOOLEAN_ACTIONS = ('store_true', 'store_false',) + EXTOPTION_LOG def __init__(self, *args, **kwargs): @@ -135,7 +160,11 @@ def __init__(self, *args, **kwargs): def _set_attrs(self, attrs): """overwrite _set_attrs to allow store_or callbacks""" Option._set_attrs(self, attrs) - if self.action in self.EXTOPTION_STORE_OR: + if self.action == 'extend': + # alias + self.action = 'add' + self.type = 'strlist' + elif self.action in self.EXTOPTION_STORE_OR: setattr(self, 'store_or', self.action) def store_or(option, opt_str, value, parser, *args, **kwargs): @@ -143,8 +172,7 @@ def store_or(option, opt_str, value, parser, *args, **kwargs): # see http://stackoverflow.com/questions/1229146/parsing-empty-options-in-python # ugly code, optparse is crap if parser.rargs and not parser.rargs[0].startswith('-'): - val = parser.rargs[0] - parser.rargs.pop(0) + val = option.check_value(opt_str, parser.rargs.pop(0)) else: val = kwargs.get('orig_default', None) @@ -157,13 +185,14 @@ def store_or(option, opt_str, value, parser, *args, **kwargs): self.type = 'string' self.callback = store_or - self.callback_kwargs = {'orig_default': copy.deepcopy(self.default), - } + self.callback_kwargs = { + 'orig_default': copy.deepcopy(self.default), + } self.action = 'callback' # act as callback if self.store_or == 'store_or_None': self.default = None else: - raise ValueError("_set_attrs: unknown store_or %s" % self.store_or) + self.log.raiseException("_set_attrs: unknown store_or %s" % self.store_or, exception=ValueError) def take_action(self, action, dest, opt, value, values, parser): """Extended take_action""" @@ -201,22 +230,29 @@ def take_action(self, action, dest, opt, value, values, parser): Option.take_action(self, action, dest, opt, value, values, parser) elif action in self.EXTOPTION_EXTRA_OPTIONS: - if action == "extend": - # comma separated list convert in list - lvalue = value.split(self.EXTEND_SEPARATOR) - values.ensure_value(dest, []).extend(lvalue) + if action in ("add", "add_first",): + # determine type from lvalue + # set default first + values.ensure_value(dest, type(value)()) + default = getattr(values, dest) + if not (hasattr(default, '__add__') and + (hasattr(default, '__neg__') or hasattr(default, '__getslice__'))): + msg = "Unsupported type %s for action %s (requires + and one of negate or slice)" + self.log.raiseException(msg % (type(default), action)) + if action == 'add': + lvalue = default + value + elif action == 'add_first': + lvalue = value + default elif action == "date": lvalue = date_parser(value) - setattr(values, dest, lvalue) elif action == "datetime": lvalue = datetime_parser(value) - setattr(values, dest, lvalue) elif action == "regex": lvalue = re.compile(r'' + value) - setattr(values, dest, lvalue) else: - raise(Exception("Unknown extended option action %s (known: %s)" % - (action, self.EXTOPTION_EXTRA_OPTIONS))) + msg = "Unknown extended option action %s (known: %s)" + self.log.raiseException(msg % (action, self.EXTOPTION_EXTRA_OPTIONS)) + setattr(values, dest, lvalue) else: Option.take_action(self, action, dest, opt, value, values, parser) @@ -291,7 +327,7 @@ def _process_short_opts(self, rargs, values): class ExtOptionGroup(OptionGroup): """An OptionGroup with support for configfile section names""" - RESERVED_SECTIONS = ['DEFAULT'] + RESERVED_SECTIONS = [ConfigParser.DEFAULTSECT] NO_SECTION = ('NO', 'SECTION') def __init__(self, *args, **kwargs): @@ -617,6 +653,9 @@ class GeneralOption(object): - go_useconfigfiles : use configfiles or not (default set by CONFIGFILES_USE) if True, an option --configfiles will be added - go_configfiles : list of configfiles to parse. Uses ConfigParser.read; last file wins + - go_configfiles_initenv : section dict of key/value dict; inserted before configfileparsing + As a special case, using all uppercase key in DEFAULT section with a case-sensitive + configparser can be used to set "constants" for easy interpolation in all sections. - go_loggername : name of logger, default classname - go_mainbeforedefault : set the main options before the default ones - go_autocompleter : dict with named options to pass to the autocomplete call (eg arg_completer) @@ -648,7 +687,8 @@ class GeneralOption(object): CONFIGFILES_INIT = [] # initial list of defaults, overwritten by go_configfiles options CONFIGFILES_IGNORE = [] CONFIGFILES_MAIN_SECTION = 'MAIN' # sectionname that contains the non-grouped/non-prefixed options - CONFIGFILE_PARSER = ConfigParser.ConfigParser + CONFIGFILE_PARSER = ConfigParser.SafeConfigParser + CONFIGFILE_CASESENSITIVE = True METAVAR_DEFAULT = True # generate a default metavar METAVAR_MAP = None # metvar, list of longopts map @@ -668,6 +708,7 @@ def __init__(self, **kwargs): self.no_system_exit = kwargs.pop('go_nosystemexit', None) # unit test option self.use_configfiles = kwargs.pop('go_useconfigfiles', self.CONFIGFILES_USE) # use or ignore config files self.configfiles = kwargs.pop('go_configfiles', self.CONFIGFILES_INIT) # configfiles to parse + configfiles_initenv = kwargs.pop('go_configfiles_initenv', None) # initial environment for configfiles to parse prefixloggername = kwargs.pop('go_prefixloggername', False) # name of logger is same as envvar prefix mainbeforedefault = kwargs.pop('go_mainbeforedefault', False) # Set the main options before the default ones autocompleter = kwargs.pop('go_autocompleter', {}) # Pass these options to the autocomplete call @@ -682,7 +723,7 @@ def __init__(self, **kwargs): self.parser = self.PARSER(**kwargs) self.parser.allow_interspersed_args = self.INTERSPERSED - self.configfile_parser = self.CONFIGFILE_PARSER() + self.configfile_parser = None self.configfile_remainder = {} loggername = self.__class__.__name__ @@ -717,6 +758,7 @@ def __init__(self, **kwargs): if not self.options is None: # None for eg usage/help + self.configfile_parser_init(initenv=configfiles_initenv) self.parseconfigfiles() self._set_default_loglevel() @@ -760,8 +802,8 @@ def _set_default_loglevel(self): def _make_configfiles_options(self): """Add configfiles option""" opts = { - 'configfiles': ("Parse (additional) configfiles", None, "extend", self.DEFAULT_CONFIGFILES), - 'ignoreconfigfiles': ("Ignore configfiles", None, "extend", self.DEFAULT_IGNORECONFIGFILES), + 'configfiles': ("Parse (additional) configfiles", "strlist", "add", self.DEFAULT_CONFIGFILES), + 'ignoreconfigfiles': ("Ignore configfiles", "strlist", "add", self.DEFAULT_IGNORECONFIGFILES), } descr = ['Configfile options', ''] self.log.debug("Add configfiles options descr %s opts %s (no prefix)" % (descr, opts)) @@ -880,16 +922,17 @@ def add_group_parser(self, opt_dict, description, prefix=None, otherdefaults=Non default = otherdefaults.get(key) extra_help = [] - if action in ("extend",) or typ in ('strlist', 'strtuple',): - extra_help.append("type comma-separated list") + if typ in ExtOption.TYPE_STRLIST: + sep, klass, helpsep = what_str_list_tuple(typ) + extra_help.append("type %s-separated %s" % (helpsep, klass.__name__)) elif typ is not None: extra_help.append("type %s" % typ) if default is not None: if len(str(default)) == 0: extra_help.append("def ''") # empty string - elif action in ("extend",) or typ in ('strlist', 'strtuple',): - extra_help.append("def %s" % ','.join(default)) + elif typ in ExtOption.TYPE_STRLIST: + extra_help.append("def %s" % sep.join(default)) else: extra_help.append("def %s" % default) @@ -983,14 +1026,13 @@ def parseoptions(self, options_list=None): try: (self.options, self.args) = self.parser.parse_args(options_list) except SystemExit, err: + try: + msg = err.message + except AttributeError: + # py2.4 + msg = str(err) + self.log.debug("parseoptions: parse_args err %s code %s" % (msg, err.code)) if self.no_system_exit: - try: - msg = err.message - except: - # py2.4 - msg = '_nomessage_' - self.log.debug("parseoptions: no_system_exit set after parse_args err %s code %s" % - (msg, err.code)) return else: sys.exit(err.code) @@ -1006,6 +1048,40 @@ def parseoptions(self, options_list=None): self.log.debug("Found options %s args %s" % (self.options, self.args)) + def configfile_parser_init(self, initenv=None): + """ + Initialise the confgiparser to use. + + @params initenv: insert initial environment into the configparser. + It is a dict of dicts; the first level key is the section name; + the 2nd level key,value is the key=value. + All section names, keys and values are converted to strings. + """ + self.configfile_parser = self.CONFIGFILE_PARSER() + + # make case sensitive + if self.CONFIGFILE_CASESENSITIVE: + self.log.debug('Initialise case sensitive configparser') + self.configfile_parser.optionxform = str + else: + self.log.debug('Initialise case insensitive configparser') + self.configfile_parser.optionxform = str.lower + + # insert the initenv in the parser + if initenv is None: + initenv = {} + + for name, section in initenv.items(): + name = str(name) + if name == ConfigParser.DEFAULTSECT: + # is protected/reserved (and hidden) + pass + elif not self.configfile_parser.has_section(name): + self.configfile_parser.add_section(name) + + for key, value in section.items(): + self.configfile_parser.set(name, str(key), str(value)) + def parseconfigfiles(self): """Parse configfiles""" if not self.use_configfiles: @@ -1051,7 +1127,8 @@ def parseconfigfiles(self): self.log.debug("parseconfigfiles: following files were parsed %s" % parsed_files) self.log.debug("parseconfigfiles: following files were NOT parsed %s" % [x for x in configfiles if not x in parsed_files]) - self.log.debug("parseconfigfiles: sections (w/o DEFAULT) %s" % self.configfile_parser.sections()) + self.log.debug("parseconfigfiles: sections (w/o %s) %s" % + (ConfigParser.DEFAULTSECT, self.configfile_parser.sections())) # walk through list of section names # - look for options set though config files @@ -1073,7 +1150,7 @@ def parseconfigfiles(self): if section not in cfg_sections_flat: self.log.debug("parseconfigfiles: found section %s, adding to remainder" % section) remainder = self.configfile_remainder.setdefault(section, {}) - # parse te remaining options, sections starting with 'raw_' + # parse the remaining options, sections starting with 'raw_' # as their name will be considered raw sections for opt, val in self.configfile_parser.items(section, raw=(section.startswith('raw_'))): remainder[opt] = val @@ -1098,8 +1175,16 @@ def parseconfigfiles(self): opt_name, opt_dest = self.make_options_option_name_and_destination(prefix, opt) actual_option = self.parser.get_option_by_long_name(opt_name) if actual_option is None: - self.log.raiseException('parseconfigfiles: no option corresponding with dest %s' % - opt_dest) + # don't fail on DEFAULT UPPERCASE options in case-sensitive mode. + in_def = self.configfile_parser.has_option(ConfigParser.DEFAULTSECT, opt) + if in_def and self.CONFIGFILE_CASESENSITIVE and opt == opt.upper(): + self.log.debug(('parseconfigfiles: no option corresponding with ' + 'opt %s dest %s in section %s but found all uppercase ' + 'in DEFAULT section. Skipping.') % (opt, opt_dest, section)) + continue + else: + self.log.raiseException(('parseconfigfiles: no option corresponding with ' + 'opt %s dest %s in section %s') % (opt, opt_dest, section)) configfile_options_default[opt_dest] = actual_option.default @@ -1254,8 +1339,8 @@ def dict_by_prefix(self, merge_empty_prefix=False): return subdict def generate_cmd_line(self, ignore=None, add_default=None): - """Create the commandline options that would create the current self.options - opt_name is destination + """Create the commandline options that would create the current self.options. + The result is sorted on the destination names. @param ignore : regex on destination @param add_default : print value that are equal to default @@ -1309,7 +1394,11 @@ def generate_cmd_line(self, ignore=None, add_default=None): else: self.log.debug("generate_cmd_line %s adding %s non-default value %s" % (action, opt_name, opt_value)) - args.append("--%s=%s" % (opt_name, shell_quote(opt_value))) + if typ in ExtOption.TYPE_STRLIST: + sep, _, _ = what_str_list_tuple(typ) + args.append("--%s=%s" % (opt_name, shell_quote(sep.join(opt_value)))) + else: + args.append("--%s=%s" % (opt_name, shell_quote(opt_value))) elif action in ("store_true", "store_false",) + ExtOption.EXTOPTION_LOG: # not default! self.log.debug("generate_cmd_line adding %s value %s. store action found" % @@ -1334,23 +1423,39 @@ def generate_cmd_line(self, ignore=None, add_default=None): (opt_name, action, default)) else: args.append("--%s" % opt_name) - elif action in ("extend",): - # comma separated - self.log.debug("generate_cmd_line adding %s value %s. extend action, return as comma-separated list" % - (opt_name, opt_value)) - + elif action in ("add", "add_first"): if default is not None: - # remove these. if default is set, extend extends the default! - for def_el in default: - opt_value.remove(def_el) + if hasattr(opt_value, '__neg__'): + if action == 'add_first': + opt_value = opt_value + -default + else: + opt_value = -default + opt_value + elif hasattr(opt_value, '__getslice__'): + if action == 'add_first': + opt_value = opt_value[:-len(default)] + else: + opt_value = opt_value[len(default):] - if len(opt_value) == 0: - self.log.debug('generate_cmd_line skipping.') + if typ in ExtOption.TYPE_STRLIST: + sep, klass, helpsep = what_str_list_tuple(typ) + restype = '%s-separated %s' % (helpsep, klass.__name__) + value = sep.join(opt_value) + else: + restype = 'string' + value = opt_value + + if not opt_value: + # empty strings, empty lists, 0 + self.log.debug('generate_cmd_line no value left, skipping.') continue - args.append("--%s=%s" % (opt_name, shell_quote(",".join(opt_value)))) - elif typ in ('strlist', 'strtuple',): - args.append("--%s=%s" % (opt_name, shell_quote(",".join(opt_value)))) + self.log.debug("generate_cmd_line adding %s value %s. %s action, return as %s" % + (opt_name, opt_value, action, restype)) + + args.append("--%s=%s" % (opt_name, shell_quote(value))) + elif typ in ExtOption.TYPE_STRLIST: + sep, _, _ = what_str_list_tuple(typ) + args.append("--%s=%s" % (opt_name, shell_quote(sep.join(opt_value)))) elif action in ("append",): # add multiple times self.log.debug("generate_cmd_line adding %s value %s. append action, return as multiple args" % diff --git a/vsc/utils/missing.py b/vsc/utils/missing.py index 9d4768fec0..683269cb00 100644 --- a/vsc/utils/missing.py +++ b/vsc/utils/missing.py @@ -40,14 +40,20 @@ @author: Andy Georges (Ghent University) @author: Stijn De Weirdt (Ghent University) """ +import os +import re import shlex import subprocess +import sys import time from vsc.utils import fancylogger from vsc.utils.frozendict import FrozenDict +_log = fancylogger.getLogger('vsc.utils.missing') + + def partial(func, *args, **keywords): """ Return a new partial object which when called will behave like func called with the positional arguments args @@ -289,15 +295,105 @@ def shell_unquote(x): return shlex.split(str(x))[0] -def get_subclasses(klass): - """Get all subclasses recursively""" - res = [] - for cl in klass.__subclasses__(): - res.extend(get_subclasses(cl)) - res.append(cl) +def get_class_for(modulepath, class_name): + """ + Get class for a given Python class name and Python module path. + + @param modulepath: Python module path (e.g., 'vsc.utils.generaloption') + @param class_name: Python class name (e.g., 'GeneralOption') + """ + # try to import specified module path, reraise ImportError if it occurs + try: + module = __import__(modulepath, globals(), locals(), ['']) + except ImportError, err: + raise ImportError(err) + # try to import specified class name from specified module path, throw ImportError if this fails + try: + klass = getattr(module, class_name) + except AttributeError, err: + raise ImportError("Failed to import %s from %s: %s" % (class_name, modulepath, err)) + return klass + + +def get_subclasses_dict(klass, include_base_class=False): + """Get dict with subclasses per classes, recursively from the specified base class.""" + res = {} + subclasses = klass.__subclasses__() + if include_base_class: + res.update({klass: subclasses}) + for subclass in subclasses: + # always include base class for recursive call + res.update(get_subclasses_dict(subclass, include_base_class=True)) return res +def get_subclasses(klass, include_base_class=False): + """Get list of all subclasses, recursively from the specified base class.""" + return get_subclasses_dict(klass, include_base_class=include_base_class).keys() + + +def modules_in_pkg_path(pkg_path): + """Return list of module files in specified package path.""" + # if the specified (relative) package path doesn't exist, try and determine the absolute path via sys.path + if not os.path.isabs(pkg_path) and not os.path.isdir(pkg_path): + _log.debug("Obtained non-existing relative package path '%s', will try to figure out absolute path" % pkg_path) + newpath = None + for sys_path_dir in sys.path: + abspath = os.path.join(sys_path_dir, pkg_path) + if os.path.isdir(abspath): + _log.debug("Found absolute path %s for package path %s, verifying it" % (abspath, pkg_path)) + # also make sure an __init__.py is in place in every subdirectory + is_pkg = True + subdir = '' + for pkg_path_dir in pkg_path.split(os.path.sep): + subdir = os.path.join(subdir, pkg_path_dir) + if not os.path.isfile(os.path.join(sys_path_dir, subdir, '__init__.py')): + is_pkg = False + tup = (subdir, abspath, pkg_path) + _log.debug("No __init__.py found in %s, %s is not a valid absolute path for pkg_path %s" % tup) + break + if is_pkg: + newpath = abspath + break + + if newpath is None: + # give up if we couldn't find an absolute path for the imported package + tup = (pkg_path, sys.path) + raise OSError("Can't browse package via non-existing relative path '%s', not found in sys.path (%s)" % tup) + else: + pkg_path = newpath + _log.debug("Found absolute package path %s" % pkg_path) + + module_regexp = re.compile(r"^(?P[^_].*|__init__)\.py$") + modules = [res.group('modname') for res in map(module_regexp.match, os.listdir(pkg_path)) if res] + _log.debug("List of modules for package in %s: %s" % (pkg_path, modules)) + return modules + + +def avail_subclasses_in(base_class, pkg_name, include_base_class=False): + """Determine subclasses for specificied base classes in modules in (only) specified packages.""" + + def try_import(name): + """Try import the specified package/module.""" + try: + # don't use return value of __import__ since it may not be the package itself but it's parent + __import__(name, globals()) + return sys.modules[name] + except ImportError, err: + raise ImportError("avail_subclasses_in: failed to import %s: %s" % (name, err)) + + # import all modules in package path(s) before determining subclasses + pkg = try_import(pkg_name) + for pkg_path in pkg.__path__: + for mod in modules_in_pkg_path(pkg_path): + # no need to directly import __init__ (already done by importing package) + if not mod.startswith('__init__'): + _log.debug("Importing module '%s' from package '%s'" % (mod, pkg_name)) + try_import('%s.%s' % (pkg_name, mod)) + + return get_subclasses_dict(base_class, include_base_class=include_base_class) + + class TryOrFail(object): """ Perform the function n times, catching each exception in the exception tuple except on the last try @@ -310,16 +406,15 @@ def __init__(self, n, exceptions=(Exception,), sleep=0): def __call__(self, function): def new_function(*args, **kwargs): - log = fancylogger.getLogger(function.__name__) for i in xrange(0, self.n): try: return function(*args, **kwargs) except self.exceptions, err: if i == self.n - 1: raise - log.exception("try_or_fail caught an exception - attempt %d: %s" % (i, err)) + _log.exception("try_or_fail caught an exception - attempt %d: %s" % (i, err)) if self.sleep > 0: - log.warning("try_or_fail is sleeping for %d seconds before the next attempt" % (self.sleep,)) + _log.warning("try_or_fail is sleeping for %d seconds before the next attempt" % (self.sleep,)) time.sleep(self.sleep) return new_function diff --git a/vsc/utils/rest.py b/vsc/utils/rest.py index 82d5835eee..18974a8167 100644 --- a/vsc/utils/rest.py +++ b/vsc/utils/rest.py @@ -42,7 +42,11 @@ import simplejson as json from vsc.utils import fancylogger -from vsc.utils.missing import partial + +try: + from functools import partial +except ImportError: + from vsc.utils.missing import partial class Client(object): diff --git a/vsc/utils/testing.py b/vsc/utils/testing.py new file mode 100644 index 0000000000..71807d5677 --- /dev/null +++ b/vsc/utils/testing.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python +## +# +# Copyright 2014-2014 Ghent University +# +# This file is part of vsc-base, +# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en), +# with support of Ghent University (http://ugent.be/hpc), +# the Flemish Supercomputer Centre (VSC) (https://vscentrum.be/nl/en), +# the Hercules foundation (http://www.herculesstichting.be/in_English) +# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en). +# +# http://github.com/hpcugent/vsc-base +# +# vsc-base is free software: you can redistribute it and/or modify +# it under the terms of the GNU Library General Public License as +# published by the Free Software Foundation, either version 2 of +# the License, or (at your option) any later version. +# +# vsc-base is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Library General Public License for more details. +# +# You should have received a copy of the GNU Library General Public License +# along with vsc-base. If not, see . +## +""" +Test utilities. + +@author: Kenneth Hoste (Ghent University) +""" + +import re +import sys +from cStringIO import StringIO +from unittest import TestCase + + +class EnhancedTestCase(TestCase): + """Enhanced test case, provides extra functionality (e.g. an assertErrorRegex method).""" + + def setUp(self): + """Prepare test case.""" + super(EnhancedTestCase, self).setUp() + self.orig_sys_stdout = sys.stdout + self.orig_sys_stderr = sys.stderr + + def convert_exception_to_str(self, err): + """Convert an Exception instance to a string.""" + msg = err + if hasattr(err, 'msg'): + msg = err.msg + elif hasattr(err, 'message'): + msg = err.message + if not msg: + # rely on str(msg) in case err.message is empty + msg = err + elif hasattr(err, 'args'): # KeyError in Python 2.4 only provides message via 'args' attribute + msg = err.args[0] + else: + msg = err + try: + res = str(msg) + except UnicodeEncodeError: + res = msg.encode('utf8', 'replace') + + return res + + def assertErrorRegex(self, error, regex, call, *args, **kwargs): + """ + Convenience method to match regex with the expected error message. + Example: self.assertErrorRegex(OSError, "No such file or directory", os.remove, '/no/such/file') + """ + try: + call(*args, **kwargs) + str_kwargs = ['='.join([k, str(v)]) for (k, v) in kwargs.items()] + str_args = ', '.join(map(str, args) + str_kwargs) + self.assertTrue(False, "Expected errors with %s(%s) call should occur" % (call.__name__, str_args)) + except error, err: + msg = self.convert_exception_to_str(err) + if isinstance(regex, basestring): + regex = re.compile(regex) + self.assertTrue(regex.search(msg), "Pattern '%s' is found in '%s'" % (regex.pattern, msg)) + + def mock_stdout(self, enable): + """Enable/disable mocking stdout.""" + sys.stdout.flush() + if enable: + sys.stdout = StringIO() + else: + sys.stdout = self.orig_sys_stdout + + def mock_stderr(self, enable): + """Enable/disable mocking stdout.""" + sys.stderr.flush() + if enable: + sys.stderr = StringIO() + else: + sys.stderr = self.orig_sys_stderr + + def get_stdout(self): + """Return output captured from stdout until now.""" + return sys.stdout.getvalue() + + def get_stderr(self): + """Return output captured from stderr until now.""" + return sys.stderr.getvalue() + + def tearDown(self): + """Cleanup after running a test.""" + self.mock_stdout(False) + self.mock_stderr(False) + super(EnhancedTestCase, self).tearDown() From 09f036770a59e8fa8ea504b15ee14091f0d08b13 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 18 Nov 2014 08:32:20 +0100 Subject: [PATCH 157/298] make --robot and --robot-paths pathlist-typed options, enhance unit tests --- easybuild/tools/options.py | 26 ++++++++------------------ test/framework/options.py | 20 +++++++++++++------- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index 346399f93e..3c8cae3ac8 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -82,10 +82,10 @@ def basic_options(self): easyconfigs_pkg_paths = get_paths_for(subdir=EASYCONFIGS_PKG_SUBDIR, robot_path=None) if easyconfigs_pkg_paths: - default_robot_paths = os.pathsep.join(easyconfigs_pkg_paths) + default_robot_paths = easyconfigs_pkg_paths else: self.log.warning("basic_options: unable to determine easyconfigs pkg path for --robot-paths default") - default_robot_paths = '' + default_robot_paths = [] descr = ("Basic options", "Basic runtime options for EasyBuild.") @@ -98,9 +98,9 @@ def basic_options(self): 'logtostdout': ("Redirect main log to stdout", None, 'store_true', False, 'l'), 'only-blocks': ("Only build listed blocks", None, 'extend', None, 'b', {'metavar': 'BLOCKS'}), 'robot': ("Enable dependency resolution, using easyconfigs in specified paths", - None, 'store_or_None', '', 'r', {'metavar': 'PATH[:PATH]'}), + 'pathlist', 'store_or_None', [], 'r'), 'robot-paths': ("Additional paths to consider by robot for easyconfigs (--robot paths get priority)", - None, 'store', default_robot_paths, {'metavar': 'PATH[:PATH]'}), + 'pathlist', 'store', default_robot_paths), 'skip': ("Skip existing software (useful for installing additional packages)", None, 'store_true', False, 'k'), 'stop': ("Stop the installation after certain step", 'choice', 'store_or_None', 'source', 's', all_stops), @@ -414,21 +414,11 @@ def _postprocess_config(self): if self.options.pretend: self.options.installpath = get_pretend_installpath() - # helper class to convert a string with colon-separated robot paths into a list of robot paths - class RobotPath(ListOfStrings): - SEPARATOR_LIST = os.pathsep - # explicit definition of __str__ is required for unknown reason related to the way Wrapper is defined - __str__ = ListOfStrings.__str__ - - if self.options.robot is None: - all_robot_paths = self.options.robot_paths - else: + if self.options.robot is not None: # paths specified to --robot have preference over --robot-paths - all_robot_paths = os.pathsep.join([self.options.robot, self.options.robot_paths]) - # avoid that options.robot is used for paths (since not everything is there) - self.options.robot = True - # convert to a regular list, exclude empty strings - self.options.robot_paths = nub([x for x in RobotPath(all_robot_paths) if x]) + # keep both values in sync if robot is enabled, which implies enabling dependency resolver + self.options.robot_paths = self.options.robot + self.options.robot_paths + self.options.robot = self.options.robot_paths def _postprocess_list_avail(self): """Create all the additional info that can be requested (exit at the end)""" diff --git a/test/framework/options.py b/test/framework/options.py index 6def5f9ae0..0ed83a5e74 100644 --- a/test/framework/options.py +++ b/test/framework/options.py @@ -261,11 +261,10 @@ def test_job(self): # use gzip-1.4.eb easyconfig file that comes with the tests eb_file = os.path.join(os.path.dirname(__file__), 'easyconfigs', 'gzip-1.4.eb') - # check log message with --job - for job_args in [ # options passed are reordered, so order here matters to make tests pass - ['--debug'], - ['--debug', '--stop=configure', '--try-software-name=foo'], - ]: + def check_args(job_args, passed_args=None): + """Check whether specified args yield expected result.""" + if passed_args is None: + passed_args = job_args[:] # clear log file write_file(self.logfile, '') @@ -276,13 +275,20 @@ def test_job(self): ] + job_args outtxt = self.eb_main(args) - job_msg = "INFO.* Command template for jobs: .* && eb %%\(spec\)s.* %s.*\n" % ' .*'.join(job_args) - assertmsg = "Info log message with job command template when using --job (job_msg: %s, outtxt: %s)" % (job_msg, outtxt) + job_msg = "INFO.* Command template for jobs: .* && eb %%\(spec\)s.* %s.*\n" % ' .*'.join(passed_args) + assertmsg = "Info log msg with job command template for --job (job_msg: %s, outtxt: %s)" % (job_msg, outtxt) self.assertTrue(re.search(job_msg, outtxt), assertmsg) modify_env(os.environ, self.orig_environ) tempfile.tempdir = None + # options passed are reordered, so order here matters to make tests pass + check_args(['--debug']) + check_args(['--debug', '--stop=configure', '--try-software-name=foo']) + check_args(['--debug', '--robot-paths=/tmp/foo:/tmp/bar']) + # --robot has preference over --robot-paths, --robot is not passed down + check_args(['--debug', '--robot-paths=/tmp/foo', '--robot=/tmp/bar'], passed_args=['--debug', '--robot-paths=/tmp/bar:/tmp/foo']) + # 'zzz' prefix in the test name is intentional to make this test run last, # since it fiddles with the logging infrastructure which may break things def test_zzz_logtostdout(self): From 41e7e6eb65e3277fa64678dcfd817e2a49323889 Mon Sep 17 00:00:00 2001 From: ocaisa Date: Tue, 18 Nov 2014 14:01:39 +0100 Subject: [PATCH 158/298] Regex in fetch_parameter_from_easyconfig_file flawed Was picking up white space after parameter which was breaking easyblock = 'value' --- easybuild/framework/easyconfig/easyconfig.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/framework/easyconfig/easyconfig.py b/easybuild/framework/easyconfig/easyconfig.py index 3fea3e6c3e..a735537855 100644 --- a/easybuild/framework/easyconfig/easyconfig.py +++ b/easybuild/framework/easyconfig/easyconfig.py @@ -698,7 +698,7 @@ def fetch_parameter_from_easyconfig_file(path, param): """Fetch parameter specification from given easyconfig file.""" # check whether easyblock is specified in easyconfig file # note: we can't rely on value for 'easyblock' in parsed easyconfig, it may be the default value - reg = re.compile(r"^\s*%s\s*=\s*(?P\S.*)\s*$" % param, re.M) + reg = re.compile(r"^\s*%s\s*=\s*(?P\S.*?)\s*$" % param, re.M) txt = read_file(path) res = reg.search(txt) if res: From 7480ae3cc88efb1c8523d5643e732c7c23c43857 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 20 Nov 2014 15:25:24 +0100 Subject: [PATCH 159/298] restore metavar's for --robot and --robot-paths --- easybuild/tools/options.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index 3c8cae3ac8..1098f59f1a 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -98,9 +98,9 @@ def basic_options(self): 'logtostdout': ("Redirect main log to stdout", None, 'store_true', False, 'l'), 'only-blocks': ("Only build listed blocks", None, 'extend', None, 'b', {'metavar': 'BLOCKS'}), 'robot': ("Enable dependency resolution, using easyconfigs in specified paths", - 'pathlist', 'store_or_None', [], 'r'), + 'pathlist', 'store_or_None', [], 'r', {'metavar': 'PATH[%sPATH]' % os.pathsep}), 'robot-paths': ("Additional paths to consider by robot for easyconfigs (--robot paths get priority)", - 'pathlist', 'store', default_robot_paths), + 'pathlist', 'store', default_robot_paths, {'metavar': 'PATH[%sPATH]' % os.pathsep}), 'skip': ("Skip existing software (useful for installing additional packages)", None, 'store_true', False, 'k'), 'stop': ("Stop the installation after certain step", 'choice', 'store_or_None', 'source', 's', all_stops), From 7ccd6add6e377c6a08c714f96bd4825f14e79718 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 20 Nov 2014 15:25:40 +0100 Subject: [PATCH 160/298] use get_class_for provided by vsc.utils.missing --- easybuild/framework/easyblock.py | 3 ++- easybuild/framework/easyconfig/easyconfig.py | 19 +------------------ 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 52f03cb5b2..99e005bb82 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -46,12 +46,13 @@ import traceback from distutils.version import LooseVersion from vsc.utils import fancylogger +from vsc.utils.missing import get_class_for import easybuild.tools.environment as env from easybuild.tools import config, filetools from easybuild.framework.easyconfig import EASYCONFIGS_PKG_SUBDIR from easybuild.framework.easyconfig.easyconfig import EasyConfig, ActiveMNS, ITERATE_OPTIONS -from easybuild.framework.easyconfig.easyconfig import fetch_parameter_from_easyconfig_file, get_class_for +from easybuild.framework.easyconfig.easyconfig import fetch_parameter_from_easyconfig_file from easybuild.framework.easyconfig.easyconfig import get_easyblock_class, get_module_path, resolve_template from easybuild.framework.easyconfig.tools import get_paths_for from easybuild.framework.easyconfig.templates import TEMPLATE_NAMES_EASYBLOCK_RUN_STEP diff --git a/easybuild/framework/easyconfig/easyconfig.py b/easybuild/framework/easyconfig/easyconfig.py index c58e7349a5..1571c1e6ba 100644 --- a/easybuild/framework/easyconfig/easyconfig.py +++ b/easybuild/framework/easyconfig/easyconfig.py @@ -40,7 +40,7 @@ import os import re from vsc.utils import fancylogger -from vsc.utils.missing import any, nub +from vsc.utils.missing import any, get_class_for, nub from vsc.utils.patterns import Singleton import easybuild.tools.environment as env @@ -734,23 +734,6 @@ def fetch_parameter_from_easyconfig_file(path, param): return None -def get_class_for(modulepath, class_name): - """ - Get class for a given class name and easyblock module path. - """ - # try to import specified module path, reraise ImportError if it occurs - try: - m = __import__(modulepath, globals(), locals(), ['']) - except ImportError, err: - raise ImportError(err) - # try to import specified class name from specified module path, throw ImportError if this fails - try: - c = getattr(m, class_name) - except AttributeError, err: - raise ImportError("Failed to import %s from %s: %s" % (class_name, modulepath, err)) - return c - - def get_easyblock_class(easyblock, name=None): """ Get class for a particular easyblock (or use default) From 29466b1dfcc452561f0ec55020c4866c138848ce Mon Sep 17 00:00:00 2001 From: ebgregory Date: Thu, 20 Nov 2014 17:25:53 +0100 Subject: [PATCH 161/298] adding support for MPICH (instead of just MPICH2) --- easybuild/tools/toolchain/mpi.py | 1 + 1 file changed, 1 insertion(+) diff --git a/easybuild/tools/toolchain/mpi.py b/easybuild/tools/toolchain/mpi.py index a04bb710ef..3cde846bc7 100644 --- a/easybuild/tools/toolchain/mpi.py +++ b/easybuild/tools/toolchain/mpi.py @@ -176,6 +176,7 @@ def mpi_cmd_for(self, cmd, nr_ranks): toolchain.INTELMPI: "mpirun %(mpdbf)s %(nodesfile)s -np %(nr_ranks)d %(cmd)s", # @UndefinedVariable toolchain.MVAPICH2: "mpirun -n %(nr_ranks)d %(cmd)s", # @UndefinedVariable toolchain.MPICH2: "mpirun -n %(nr_ranks)d %(cmd)s", # @UndefinedVariable + toolchain.MPICH: "mpirun -n %(nr_ranks)d %(cmd)s", # @UndefinedVariable } mpi_family = self.mpi_family() From 7acb5749e3922cdadd1e1d53124cfe9ebdd68262 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 28 Nov 2014 18:44:41 +0100 Subject: [PATCH 162/298] support use of %(DEFAULT_ROBOT_PATHS)s template in EasyBuild configuration files --- easybuild/tools/config.py | 4 ++-- easybuild/tools/options.py | 23 +++++++++++++++-------- test/framework/config.py | 18 +++++++++++++++++- 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py index 3c4467ad3c..48ee994d62 100644 --- a/easybuild/tools/config.py +++ b/easybuild/tools/config.py @@ -387,12 +387,12 @@ def init(options, config_options_dict): def init_build_options(build_options=None, cmdline_options=None): """Initialize build options.""" - # building a dependency graph implies force, so that all dependencies are retained - # and also skips validation of easyconfigs (e.g. checking os dependencies) active_build_options = {} if cmdline_options is not None: + # building a dependency graph implies force, so that all dependencies are retained + # and also skips validation of easyconfigs (e.g. checking os dependencies) retain_all_deps = False if cmdline_options.dep_graph: _log.info("Enabling force to generate dependency graph.") diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index 1098f59f1a..72398552a2 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -75,18 +75,25 @@ class EasyBuildOptions(GeneralOption): ALLOPTSMANDATORY = False # allow more than one argument + def __init__(self, *args, **kwargs): + """Constructor.""" + + self.default_robot_paths = get_paths_for(subdir=EASYCONFIGS_PKG_SUBDIR, robot_path=None) or [] + + # set up constants to seed into config files parser + go_cfg_initenv = { + 'DEFAULT': { + 'DEFAULT_ROBOT_PATHS': os.pathsep.join(self.default_robot_paths), + } + } + kwargs.setdefault('go_configfiles_initenv', {}).update(go_cfg_initenv) + super(EasyBuildOptions, self).__init__(*args, **kwargs) + def basic_options(self): """basic runtime options""" all_stops = [x[0] for x in EasyBlock.get_steps()] strictness_options = [run.IGNORE, run.WARN, run.ERROR] - easyconfigs_pkg_paths = get_paths_for(subdir=EASYCONFIGS_PKG_SUBDIR, robot_path=None) - if easyconfigs_pkg_paths: - default_robot_paths = easyconfigs_pkg_paths - else: - self.log.warning("basic_options: unable to determine easyconfigs pkg path for --robot-paths default") - default_robot_paths = [] - descr = ("Basic options", "Basic runtime options for EasyBuild.") opts = OrderedDict({ @@ -100,7 +107,7 @@ def basic_options(self): 'robot': ("Enable dependency resolution, using easyconfigs in specified paths", 'pathlist', 'store_or_None', [], 'r', {'metavar': 'PATH[%sPATH]' % os.pathsep}), 'robot-paths': ("Additional paths to consider by robot for easyconfigs (--robot paths get priority)", - 'pathlist', 'store', default_robot_paths, {'metavar': 'PATH[%sPATH]' % os.pathsep}), + 'pathlist', 'store', self.default_robot_paths, {'metavar': 'PATH[%sPATH]' % os.pathsep}), 'skip': ("Skip existing software (useful for installing additional packages)", None, 'store_true', False, 'k'), 'stop': ("Stop the installation after certain step", 'choice', 'store_or_None', 'source', 's', all_stops), diff --git a/test/framework/config.py b/test/framework/config.py index b28ec141cf..fb4cdd854b 100644 --- a/test/framework/config.py +++ b/test/framework/config.py @@ -31,6 +31,7 @@ import copy import os import shutil +import sys import tempfile from test.framework.utilities import EnhancedTestCase, init_config from unittest import TestLoader @@ -42,7 +43,7 @@ from easybuild.tools.config import log_file_format, set_tmpdir, BuildOptions, ConfigurationVariables from easybuild.tools.config import get_build_log_path, DEFAULT_PATH_SUBDIRS, init_build_options, build_option from easybuild.tools.environment import modify_env -from easybuild.tools.filetools import write_file +from easybuild.tools.filetools import mkdir, write_file from easybuild.tools.repository.filerepo import FileRepository from easybuild.tools.repository.repository import init_repository @@ -443,10 +444,22 @@ def test_generaloption_config_file(self): self.assertEqual(source_paths(), [os.path.join(os.getenv('HOME'), '.local', 'easybuild', 'sources')]) # default self.assertEqual(install_path(), os.path.join(testpath2, 'software')) # via config file + # copy test easyconfigs to easybuild/easyconfigs subdirectory of temp directory + # to check whether easyconfigs install path is auto-included in robot path + tmpdir = tempfile.mkdtemp(prefix='easybuild-easyconfigs-pkg-install-path') + mkdir(os.path.join(tmpdir, 'easybuild'), parents=True) + + test_ecs_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs') + shutil.copytree(test_ecs_dir, os.path.join(tmpdir, 'easybuild', 'easyconfigs')) + + orig_sys_path = sys.path[:] + sys.path.insert(0, tmpdir) # prepend to give it preference over possible other installed easyconfigs pkgs + # test with config file passed via environment variable cfgtxt = '\n'.join([ '[config]', 'buildpath = %s' % testpath1, + 'robot-paths = /tmp/foo:%(DEFAULT_ROBOT_PATHS)s', ]) write_file(config_file, cfgtxt) @@ -460,6 +473,8 @@ def test_generaloption_config_file(self): self.assertEqual(install_path(), os.path.join(os.getenv('HOME'), '.local', 'easybuild', 'software')) # default self.assertEqual(source_paths(), [testpath2]) # via command line self.assertEqual(build_path(), testpath1) # via config file + self.assertTrue('/tmp/foo' in options.robot_paths) + self.assertTrue(os.path.join(tmpdir, 'easybuild', 'easyconfigs') in options.robot_paths) testpath3 = os.path.join(self.tmpdir, 'testTHREE') os.environ['EASYBUILD_SOURCEPATH'] = testpath2 @@ -474,6 +489,7 @@ def test_generaloption_config_file(self): self.assertEqual(build_path(), testpath1) # via config file del os.environ['EASYBUILD_CONFIGFILES'] + sys.path[:] = orig_sys_path def test_set_tmpdir(self): """Test set_tmpdir config function.""" From a80ab71d30d5cb72a97c5e1872f6af8233c99c5c Mon Sep 17 00:00:00 2001 From: Markus Geimer Date: Sun, 30 Nov 2014 14:58:59 +0100 Subject: [PATCH 163/298] Fix MPICH vs. MPICH2 inconsistencies * rename gmpich2 toolchain to gmpich * adjusted gmpich and gmpolf toolchains to use MPICH instead of MPICH2 --- easybuild/toolchains/{gmpich2.py => gmpich.py} | 10 +++++----- easybuild/toolchains/gmpolf.py | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) rename easybuild/toolchains/{gmpich2.py => gmpich.py} (83%) diff --git a/easybuild/toolchains/gmpich2.py b/easybuild/toolchains/gmpich.py similarity index 83% rename from easybuild/toolchains/gmpich2.py rename to easybuild/toolchains/gmpich.py index caf6e0af5b..4527db29f2 100644 --- a/easybuild/toolchains/gmpich2.py +++ b/easybuild/toolchains/gmpich.py @@ -23,15 +23,15 @@ # along with EasyBuild. If not, see . ## """ -EasyBuild support for gmpich2 compiler toolchain (includes GCC and MPICH2). +EasyBuild support for gmpich compiler toolchain (includes GCC and MPICH). @author: Kenneth Hoste (Ghent University) """ from easybuild.toolchains.compiler.gcc import Gcc -from easybuild.toolchains.mpi.mpich2 import Mpich2 +from easybuild.toolchains.mpi.mpich import Mpich -class Gmpich2(Gcc, Mpich2): - """Compiler toolchain with GCC and MPICH2.""" - NAME = 'gmpich2' +class Gmpich(Gcc, Mpich): + """Compiler toolchain with GCC and MPICH.""" + NAME = 'gmpich' diff --git a/easybuild/toolchains/gmpolf.py b/easybuild/toolchains/gmpolf.py index 077290259e..35a0d0b899 100644 --- a/easybuild/toolchains/gmpolf.py +++ b/easybuild/toolchains/gmpolf.py @@ -36,9 +36,9 @@ from easybuild.toolchains.fft.fftw import Fftw from easybuild.toolchains.linalg.openblas import OpenBLAS from easybuild.toolchains.linalg.scalapack import ScaLAPACK -from easybuild.toolchains.mpi.mpich2 import Mpich2 +from easybuild.toolchains.mpi.mpich import Mpich -class Gmpolf(Gcc, Mpich2, OpenBLAS, ScaLAPACK, Fftw): - """Compiler toolchain with GCC, MPICH2, OpenBLAS, ScaLAPACK and FFTW.""" +class Gmpolf(Gcc, Mpich, OpenBLAS, ScaLAPACK, Fftw): + """Compiler toolchain with GCC, MPICH, OpenBLAS, ScaLAPACK and FFTW.""" NAME = 'gmpolf' From 439a1546df91378c1ceab9c00f0dc7628e011114 Mon Sep 17 00:00:00 2001 From: Markus Geimer Date: Tue, 2 Dec 2014 20:36:15 +0100 Subject: [PATCH 164/298] Re-added gmpich2 toolchain --- easybuild/toolchains/gmpich2.py | 37 +++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 easybuild/toolchains/gmpich2.py diff --git a/easybuild/toolchains/gmpich2.py b/easybuild/toolchains/gmpich2.py new file mode 100644 index 0000000000..caf6e0af5b --- /dev/null +++ b/easybuild/toolchains/gmpich2.py @@ -0,0 +1,37 @@ +## +# Copyright 2012-2014 Ghent University +# +# This file is part of EasyBuild, +# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en), +# with support of Ghent University (http://ugent.be/hpc), +# the Flemish Supercomputer Centre (VSC) (https://vscentrum.be/nl/en), +# the Hercules foundation (http://www.herculesstichting.be/in_English) +# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en). +# +# http://github.com/hpcugent/easybuild +# +# EasyBuild is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation v2. +# +# EasyBuild is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with EasyBuild. If not, see . +## +""" +EasyBuild support for gmpich2 compiler toolchain (includes GCC and MPICH2). + +@author: Kenneth Hoste (Ghent University) +""" + +from easybuild.toolchains.compiler.gcc import Gcc +from easybuild.toolchains.mpi.mpich2 import Mpich2 + + +class Gmpich2(Gcc, Mpich2): + """Compiler toolchain with GCC and MPICH2.""" + NAME = 'gmpich2' From 38f7895095207d16a83e0623d9c87684b551ff58 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 3 Dec 2014 08:38:03 +0100 Subject: [PATCH 165/298] sync with vsc-base --- vsc/README.md | 2 +- vsc/utils/generaloption.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/vsc/README.md b/vsc/README.md index 932369c661..b5efeb45be 100644 --- a/vsc/README.md +++ b/vsc/README.md @@ -1,3 +1,3 @@ Code from https://github.com/hpcugent/vsc-base -based on 35fee9d3130a6b52bf83993e73d187e9d46c69bc (vsc-base v1.9.9) +based on 2146be5301da34043adf4646169e5dfec88cd2f5 (vsc-base v1.9.9) diff --git a/vsc/utils/generaloption.py b/vsc/utils/generaloption.py index 390e5fa79e..e4548dc668 100644 --- a/vsc/utils/generaloption.py +++ b/vsc/utils/generaloption.py @@ -699,6 +699,7 @@ class GeneralOption(object): VERSION = None # set the version (will add --version) + DEFAULTSECT = ConfigParser.DEFAULTSECT DEFAULT_LOGLEVEL = None DEFAULT_CONFIGFILES = None DEFAULT_IGNORECONFIGFILES = None @@ -1073,7 +1074,7 @@ def configfile_parser_init(self, initenv=None): for name, section in initenv.items(): name = str(name) - if name == ConfigParser.DEFAULTSECT: + if name == self.DEFAULTSECT: # is protected/reserved (and hidden) pass elif not self.configfile_parser.has_section(name): @@ -1128,7 +1129,7 @@ def parseconfigfiles(self): self.log.debug("parseconfigfiles: following files were NOT parsed %s" % [x for x in configfiles if not x in parsed_files]) self.log.debug("parseconfigfiles: sections (w/o %s) %s" % - (ConfigParser.DEFAULTSECT, self.configfile_parser.sections())) + (self.DEFAULTSECT, self.configfile_parser.sections())) # walk through list of section names # - look for options set though config files @@ -1176,7 +1177,7 @@ def parseconfigfiles(self): actual_option = self.parser.get_option_by_long_name(opt_name) if actual_option is None: # don't fail on DEFAULT UPPERCASE options in case-sensitive mode. - in_def = self.configfile_parser.has_option(ConfigParser.DEFAULTSECT, opt) + in_def = self.configfile_parser.has_option(self.DEFAULTSECT, opt) if in_def and self.CONFIGFILE_CASESENSITIVE and opt == opt.upper(): self.log.debug(('parseconfigfiles: no option corresponding with ' 'opt %s dest %s in section %s but found all uppercase ' From 74528d96ae7d941c237a7b38905e8845734fd9d0 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 3 Dec 2014 08:38:56 +0100 Subject: [PATCH 166/298] fix remark, use DEFAULTSECT constant --- easybuild/tools/options.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index 72398552a2..e21105b82d 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -80,13 +80,18 @@ def __init__(self, *args, **kwargs): self.default_robot_paths = get_paths_for(subdir=EASYCONFIGS_PKG_SUBDIR, robot_path=None) or [] - # set up constants to seed into config files parser - go_cfg_initenv = { - 'DEFAULT': { + # set up constants to seed into config files parser, by section + go_cfg_constants = { + GeneralOption.DEFAULTSECT: { 'DEFAULT_ROBOT_PATHS': os.pathsep.join(self.default_robot_paths), } } - kwargs.setdefault('go_configfiles_initenv', {}).update(go_cfg_initenv) + + # update or define go_configfiles_initenv in named arguments to pass to parent constructor + go_cfg_initenv = kwargs.setdefault('go_configfiles_initenv', {}) + for section, constants in go_cfg_constants.items(): + go_cfg_initenv.setdefault(section, {}).update(constants) + super(EasyBuildOptions, self).__init__(*args, **kwargs) def basic_options(self): From 84fe650452b98d01919d7996b2ec57c78f7d2fe3 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 3 Dec 2014 10:02:36 +0100 Subject: [PATCH 167/298] add --avail-cfgfile-constants --- easybuild/tools/options.py | 34 ++++++++++++++++++++++++++++++---- test/framework/options.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index e21105b82d..bd0930e6eb 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -81,15 +81,17 @@ def __init__(self, *args, **kwargs): self.default_robot_paths = get_paths_for(subdir=EASYCONFIGS_PKG_SUBDIR, robot_path=None) or [] # set up constants to seed into config files parser, by section - go_cfg_constants = { + self.go_cfg_constants = { GeneralOption.DEFAULTSECT: { - 'DEFAULT_ROBOT_PATHS': os.pathsep.join(self.default_robot_paths), + 'DEFAULT_ROBOT_PATHS': (os.pathsep.join(self.default_robot_paths), + "List of default robot paths ('%s'-separated)" % os.pathsep), } } # update or define go_configfiles_initenv in named arguments to pass to parent constructor go_cfg_initenv = kwargs.setdefault('go_configfiles_initenv', {}) - for section, constants in go_cfg_constants.items(): + for section, constants in self.go_cfg_constants.items(): + constants = dict([(name, value) for (name, (value, _)) in constants.items()]) go_cfg_initenv.setdefault(section, {}).update(constants) super(EasyBuildOptions, self).__init__(*args, **kwargs) @@ -268,6 +270,8 @@ def informative_options(self): descr = ("Informative options", "Obtain information about EasyBuild.") opts = OrderedDict({ + 'avail-cfgfile-constants': ("Show all constants that can be used in configuration files", + None, 'store_true', False), 'avail-easyconfig-constants': ("Show all constants that can be used in easyconfigs", None, 'store_true', False), 'avail-easyconfig-licenses': ("Show all license constants that can be used in easyconfigs", @@ -381,7 +385,7 @@ def postprocess(self): # prepare for --list/--avail if any([self.options.avail_easyconfig_params, self.options.avail_easyconfig_templates, - self.options.list_easyblocks, self.options.list_toolchains, + self.options.list_easyblocks, self.options.list_toolchains, self.options.avail_cfgfile_constants, self.options.avail_easyconfig_constants, self.options.avail_easyconfig_licenses, self.options.avail_repositories, self.options.show_default_moduleclasses, self.options.avail_modules_tools, self.options.avail_module_naming_schemes, @@ -435,6 +439,11 @@ def _postprocess_config(self): def _postprocess_list_avail(self): """Create all the additional info that can be requested (exit at the end)""" msg = '' + + # dump supported configuration file constants + if self.options.avail_cfgfile_constants: + msg += self.avail_cfgfile_constants() + # dump possible easyconfig params if self.options.avail_easyconfig_params: msg += self.avail_easyconfig_params() @@ -481,6 +490,23 @@ def _postprocess_list_avail(self): print msg sys.exit(0) + def avail_cfgfile_constants(self): + """ + Return overview of constants supported in configuration files. + """ + lines = [ + "Constants available (only) in configuration files:", + "syntax: %(CONSTANT_NAME)s", + ] + for section in self.go_cfg_constants: + lines.append('') + if section != GeneralOption.DEFAULTSECT: + section_title = "only in '%s' section:" % section + lines.append(section_title) + for cst_name, (cst_value, cst_help) in sorted(self.go_cfg_constants[section].items()): + lines.append("* %s: %s [value: %s]" % (cst_name, cst_help, cst_value)) + return '\n'.join(lines) + def avail_easyconfig_params(self): """ Print the available easyconfig parameters, for the given easyblock. diff --git a/test/framework/options.py b/test/framework/options.py index 0ed83a5e74..17f3688640 100644 --- a/test/framework/options.py +++ b/test/framework/options.py @@ -453,6 +453,39 @@ def test_avail_lists(self): if os.path.exists(dummylogfn): os.remove(dummylogfn) + def test_avail_cfgfile_constants(self): + """Test --avail-cfgfile-constants.""" + fd, dummylogfn = tempfile.mkstemp(prefix='easybuild-dummy', suffix='.log') + os.close(fd) + + # copy test easyconfigs to easybuild/easyconfigs subdirectory of temp directory + # to check whether easyconfigs install path is auto-included in robot path + tmpdir = tempfile.mkdtemp(prefix='easybuild-easyconfigs-pkg-install-path') + mkdir(os.path.join(tmpdir, 'easybuild'), parents=True) + + test_ecs_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs') + shutil.copytree(test_ecs_dir, os.path.join(tmpdir, 'easybuild', 'easyconfigs')) + + orig_sys_path = sys.path[:] + sys.path.insert(0, tmpdir) # prepend to give it preference over possible other installed easyconfigs pkgs + + args = [ + '--avail-cfgfile-constants', + '--unittest-file=%s' % self.logfile, + ] + outtxt = self.eb_main(args, logfile=dummylogfn) + cfgfile_constants = { + 'DEFAULT_ROBOT_PATHS': os.path.join(tmpdir, 'easybuild', 'easyconfigs'), + } + for cst_name, cst_value in cfgfile_constants.items(): + cst_regex = re.compile("^\*\s%s:\s.*\s\[value: .*%s.*\]" % (cst_name, cst_value), re.M) + tup = (cst_regex.pattern, outtxt) + self.assertTrue(cst_regex.search(outtxt), "Pattern '%s' in --avail-cfgfile_constants output: %s" % tup) + + if os.path.exists(dummylogfn): + os.remove(dummylogfn) + sys.path[:] = orig_sys_path + def test_list_easyblocks(self): """Test listing easyblock hierarchy.""" From 9fbf6830c8cc3e752d0f41249ba4df10bb488f17 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 3 Dec 2014 13:37:34 +0100 Subject: [PATCH 168/298] fix links to docs --- CONTRIBUTING.md | 2 +- easybuild/scripts/bootstrap_eb.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 10649cddab..9d866e3a8c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -138,7 +138,7 @@ You might also want to look into [hub](https://github.com/defunkt/hub) for more ### Review process -A member of the EasyBuild team will then review your pull request, paying attention to what you're contributing, how you implemented it and [Code style](https://github.com/hpcugent/easybuild/wiki/Code-style). +A member of the EasyBuild team will then review your pull request, paying attention to what you're contributing, how you implemented it and [code style](http://easybuild.readthedocs.org/en/latest/Code_style.html). Most likely, some remarks will be made on your pull request. Note that this is nothing personal, we're just trying to keep the EasyBuild codebase as high quality as possible. Even when an EasyBuild team member makes changes, the same public review process is followed. diff --git a/easybuild/scripts/bootstrap_eb.py b/easybuild/scripts/bootstrap_eb.py index 91deb59323..3c8cc97141 100755 --- a/easybuild/scripts/bootstrap_eb.py +++ b/easybuild/scripts/bootstrap_eb.py @@ -431,7 +431,7 @@ def main(): info('') info("By default, EasyBuild will install software to $HOME/.local/easybuild.") info("To install software with EasyBuild to %s, make sure $EASYBUILD_INSTALLPATH is set accordingly." % install_path) - info("See https://github.com/hpcugent/easybuild/wiki/Configuration for details on configuring EasyBuild.") + info("See http://easybuild.readthedocs.org/en/latest/Configuration.html for details on configuring EasyBuild.") # template easyconfig file for EasyBuild EB_EC_FILE = """ From 70347a2737b2960a9386cdde866637a2853770ab Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 3 Dec 2014 14:19:41 +0100 Subject: [PATCH 169/298] fix remark --- easybuild/tools/options.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index bd0930e6eb..c2ffc8c74a 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -82,7 +82,7 @@ def __init__(self, *args, **kwargs): # set up constants to seed into config files parser, by section self.go_cfg_constants = { - GeneralOption.DEFAULTSECT: { + self.DEFAULTSECT: { 'DEFAULT_ROBOT_PATHS': (os.pathsep.join(self.default_robot_paths), "List of default robot paths ('%s'-separated)" % os.pathsep), } @@ -500,7 +500,7 @@ def avail_cfgfile_constants(self): ] for section in self.go_cfg_constants: lines.append('') - if section != GeneralOption.DEFAULTSECT: + if section != self.DEFAULTSECT: section_title = "only in '%s' section:" % section lines.append(section_title) for cst_name, (cst_value, cst_help) in sorted(self.go_cfg_constants[section].items()): From 8a409988e56ec0b175448f37a662d33513976a03 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 3 Dec 2014 14:47:49 +0100 Subject: [PATCH 170/298] code cleanup in toolchain/mpi.py --- easybuild/tools/toolchain/mpi.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/easybuild/tools/toolchain/mpi.py b/easybuild/tools/toolchain/mpi.py index 3cde846bc7..b2a4cdc84a 100644 --- a/easybuild/tools/toolchain/mpi.py +++ b/easybuild/tools/toolchain/mpi.py @@ -170,13 +170,14 @@ def mpi_cmd_for(self, cmd, nr_ranks): params = {'nr_ranks':nr_ranks, 'cmd':cmd} # different known mpirun commands + mpirun_n_cmd = "mpirun -n %(nr_ranks)d %(cmd)s" mpi_cmds = { - toolchain.OPENMPI: "mpirun -n %(nr_ranks)d %(cmd)s", # @UndefinedVariable + toolchain.OPENMPI: mpirun_n_cmd, # @UndefinedVariable toolchain.QLOGICMPI: "mpirun -H localhost -np %(nr_ranks)d %(cmd)s", # @UndefinedVariable toolchain.INTELMPI: "mpirun %(mpdbf)s %(nodesfile)s -np %(nr_ranks)d %(cmd)s", # @UndefinedVariable - toolchain.MVAPICH2: "mpirun -n %(nr_ranks)d %(cmd)s", # @UndefinedVariable - toolchain.MPICH2: "mpirun -n %(nr_ranks)d %(cmd)s", # @UndefinedVariable - toolchain.MPICH: "mpirun -n %(nr_ranks)d %(cmd)s", # @UndefinedVariable + toolchain.MVAPICH2: mpirun_n_cmd, # @UndefinedVariable + toolchain.MPICH: mpirun_n_cmd, # @UndefinedVariable + toolchain.MPICH2: mpirun_n_cmd, # @UndefinedVariable } mpi_family = self.mpi_family() From 0431e6844efc3bfd11d454df35d32d33b515ab63 Mon Sep 17 00:00:00 2001 From: Fotis Georgatos Date: Tue, 9 Dec 2014 20:27:51 +0100 Subject: [PATCH 171/298] bugfix headers, dance around identity fotis@cern.ch (NTUA) Signed-off-by: Fotis Georgatos --- easybuild/easybuild_config.py | 2 +- easybuild/framework/easyblock.py | 2 +- easybuild/framework/easyconfig/templates.py | 2 +- easybuild/framework/easyconfig/tools.py | 2 +- easybuild/framework/easyconfig/tweak.py | 2 +- easybuild/main.py | 2 +- easybuild/toolchains/gompic.py | 2 +- easybuild/tools/module_generator.py | 2 +- easybuild/tools/module_naming_scheme/utilities.py | 2 +- easybuild/tools/repository/filerepo.py | 2 +- easybuild/tools/repository/gitrepo.py | 2 +- easybuild/tools/repository/repository.py | 2 +- easybuild/tools/repository/svnrepo.py | 2 +- test/framework/easyconfigs/CUDA-5.5.22-GCC-4.8.2.eb | 4 ++-- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/easybuild/easybuild_config.py b/easybuild/easybuild_config.py index 0d90c875a1..5befd7bf58 100644 --- a/easybuild/easybuild_config.py +++ b/easybuild/easybuild_config.py @@ -34,7 +34,7 @@ @author: Pieter De Baets (Ghent University) @author: Jens Timmerman (Ghent University) @author: Toon Willems (Ghent University) -@author: Fotis Georgatos (University of Luxembourg) +@author: Fotis Georgatos (Uni.Lu, NTUA) """ # diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 99e005bb82..e6db49bec7 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -33,7 +33,7 @@ @author: Jens Timmerman (Ghent University) @author: Toon Willems (Ghent University) @author: Ward Poelmans (Ghent University) -@author: Fotis Georgatos (University of Luxembourg) +@author: Fotis Georgatos (Uni.Lu, NTUA) """ import copy diff --git a/easybuild/framework/easyconfig/templates.py b/easybuild/framework/easyconfig/templates.py index 42ec1038f8..57b973aca2 100644 --- a/easybuild/framework/easyconfig/templates.py +++ b/easybuild/framework/easyconfig/templates.py @@ -28,7 +28,7 @@ be used within an Easyconfig file. @author: Stijn De Weirdt (Ghent University) -@author: Fotis Georgatos (University of Luxembourg) +@author: Fotis Georgatos (Uni.Lu, NTUA) """ from vsc.utils import fancylogger diff --git a/easybuild/framework/easyconfig/tools.py b/easybuild/framework/easyconfig/tools.py index b1c237788c..a3c0599c07 100644 --- a/easybuild/framework/easyconfig/tools.py +++ b/easybuild/framework/easyconfig/tools.py @@ -33,7 +33,7 @@ @author: Pieter De Baets (Ghent University) @author: Jens Timmerman (Ghent University) @author: Toon Willems (Ghent University) -@author: Fotis Georgatos (University of Luxembourg) +@author: Fotis Georgatos (Uni.Lu, NTUA) """ import os diff --git a/easybuild/framework/easyconfig/tweak.py b/easybuild/framework/easyconfig/tweak.py index ebc233b085..ee4176bf52 100644 --- a/easybuild/framework/easyconfig/tweak.py +++ b/easybuild/framework/easyconfig/tweak.py @@ -31,7 +31,7 @@ @author: Pieter De Baets (Ghent University) @author: Jens Timmerman (Ghent University) @author: Toon Willems (Ghent University) -@author: Fotis Georgatos (University of Luxembourg) +@author: Fotis Georgatos (Uni.Lu, NTUA) """ import copy import glob diff --git a/easybuild/main.py b/easybuild/main.py index 12991f2145..a97ce592f1 100644 --- a/easybuild/main.py +++ b/easybuild/main.py @@ -33,7 +33,7 @@ @author: Jens Timmerman (Ghent University) @author: Toon Willems (Ghent University) @author: Ward Poelmans (Ghent University) -@author: Fotis Georgatos (University of Luxembourg) +@author: Fotis Georgatos (Uni.Lu, NTUA) """ import copy import os diff --git a/easybuild/toolchains/gompic.py b/easybuild/toolchains/gompic.py index b6b0b26edb..0a331d5daf 100644 --- a/easybuild/toolchains/gompic.py +++ b/easybuild/toolchains/gompic.py @@ -26,7 +26,7 @@ EasyBuild support for gompic compiler toolchain (includes GCC and OpenMPI and CUDA). @author: Kenneth Hoste (Ghent University) -@author: Fotis Georgatos (University of Luxembourg) +@author: Fotis Georgatos (Uni.Lu, NTUA) """ from easybuild.toolchains.gcccuda import GccCUDA diff --git a/easybuild/tools/module_generator.py b/easybuild/tools/module_generator.py index f83d3a55ee..ee8046caba 100644 --- a/easybuild/tools/module_generator.py +++ b/easybuild/tools/module_generator.py @@ -30,7 +30,7 @@ @author: Kenneth Hoste (Ghent University) @author: Pieter De Baets (Ghent University) @author: Jens Timmerman (Ghent University) -@author: Fotis Georgatos (Uni.Lu) +@author: Fotis Georgatos (Uni.Lu, NTUA) """ import os import re diff --git a/easybuild/tools/module_naming_scheme/utilities.py b/easybuild/tools/module_naming_scheme/utilities.py index 951d8015b3..8bd5053231 100644 --- a/easybuild/tools/module_naming_scheme/utilities.py +++ b/easybuild/tools/module_naming_scheme/utilities.py @@ -30,7 +30,7 @@ @author: Kenneth Hoste (Ghent University) @author: Pieter De Baets (Ghent University) @author: Jens Timmerman (Ghent University) -@author: Fotis Georgatos (Uni.Lu) +@author: Fotis Georgatos (Uni.Lu, NTUA) """ import os import string diff --git a/easybuild/tools/repository/filerepo.py b/easybuild/tools/repository/filerepo.py index 7e1b2f792e..4c1a58fe17 100644 --- a/easybuild/tools/repository/filerepo.py +++ b/easybuild/tools/repository/filerepo.py @@ -34,7 +34,7 @@ @author: Jens Timmerman (Ghent University) @author: Toon Willems (Ghent University) @author: Ward Poelmans (Ghent University) -@author: Fotis Georgatos (University of Luxembourg) +@author: Fotis Georgatos (Uni.Lu, NTUA) """ import os import time diff --git a/easybuild/tools/repository/gitrepo.py b/easybuild/tools/repository/gitrepo.py index f540e5ee1a..ab6a5f07ca 100644 --- a/easybuild/tools/repository/gitrepo.py +++ b/easybuild/tools/repository/gitrepo.py @@ -34,7 +34,7 @@ @author: Jens Timmerman (Ghent University) @author: Toon Willems (Ghent University) @author: Ward Poelmans (Ghent University) -@author: Fotis Georgatos (University of Luxembourg) +@author: Fotis Georgatos (Uni.Lu, NTUA) """ import getpass import os diff --git a/easybuild/tools/repository/repository.py b/easybuild/tools/repository/repository.py index 3775bacfba..a8dca02326 100644 --- a/easybuild/tools/repository/repository.py +++ b/easybuild/tools/repository/repository.py @@ -32,7 +32,7 @@ @author: Jens Timmerman (Ghent University) @author: Toon Willems (Ghent University) @author: Ward Poelmans (Ghent University) -@author: Fotis Georgatos (University of Luxembourg) +@author: Fotis Georgatos (Uni.Lu, NTUA) """ from vsc.utils import fancylogger from vsc.utils.missing import get_subclasses diff --git a/easybuild/tools/repository/svnrepo.py b/easybuild/tools/repository/svnrepo.py index cadb0e633b..31f38abdc9 100644 --- a/easybuild/tools/repository/svnrepo.py +++ b/easybuild/tools/repository/svnrepo.py @@ -34,7 +34,7 @@ @author: Jens Timmerman (Ghent University) @author: Toon Willems (Ghent University) @author: Ward Poelmans (Ghent University) -@author: Fotis Georgatos (University of Luxembourg) +@author: Fotis Georgatos (Uni.Lu, NTUA) """ import getpass import os diff --git a/test/framework/easyconfigs/CUDA-5.5.22-GCC-4.8.2.eb b/test/framework/easyconfigs/CUDA-5.5.22-GCC-4.8.2.eb index a8e69945a9..f4bf718e88 100644 --- a/test/framework/easyconfigs/CUDA-5.5.22-GCC-4.8.2.eb +++ b/test/framework/easyconfigs/CUDA-5.5.22-GCC-4.8.2.eb @@ -1,8 +1,8 @@ ## # This file is an EasyBuild reciPY as per https://github.com/hpcugent/easybuild # -# Copyright:: Copyright 2012-2013 Cyprus Institute / CaSToRC, University of Luxembourg / LCSB, Ghent University -# Authors:: George Tsouloupas , Fotis Georgatos , Kenneth Hoste +# Copyright:: Copyright 2012-2014 Cyprus Institute / CaSToRC, Uni.Lu/LCSB, NTUA, Ghent University +# Authors:: George Tsouloupas , Fotis Georgatos , Kenneth Hoste # License:: MIT/GPL # $Id$ # From 250d80e3fe29d515157740dca9aeb22f8587a68d Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 9 Dec 2014 22:14:44 +0100 Subject: [PATCH 172/298] don't hardcode queue names when submitting a job --- easybuild/tools/pbs_job.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/easybuild/tools/pbs_job.py b/easybuild/tools/pbs_job.py index 930c059af9..55774cd474 100644 --- a/easybuild/tools/pbs_job.py +++ b/easybuild/tools/pbs_job.py @@ -150,11 +150,8 @@ def __init__(self, script, name, env_vars=None, resources={}, conn=None, ppn=Non "walltime": "%s:00:00" % hours, "nodes": "1:ppn=%s" % cores } - # set queue based on the hours requested - if hours >= 12: - self.queue = 'long' - else: - self.queue = 'short' + # don't specify any queue name to submit to, use the default + self.queue = None # job id of this job self.jobid = None # list of dependencies for this job From a5ba98416f7a2e4e3f88d26e4a0a2be27dff716f Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 11 Dec 2014 15:17:49 +0100 Subject: [PATCH 173/298] filter out /dev/null entries in list of patch files for PR --- easybuild/tools/github.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/easybuild/tools/github.py b/easybuild/tools/github.py index 89319ad2ce..f7a1993e5d 100644 --- a/easybuild/tools/github.py +++ b/easybuild/tools/github.py @@ -251,7 +251,10 @@ def download(url, path=None): diff_txt = download(pr_data['diff_url']) patched_files = det_patched_files(txt=diff_txt, omit_ab_prefix=True) - _log.debug("List of patches files: %s" % patched_files) + _log.debug("List of patches files (before filtering): %s" % patched_files) + # filter out '/dev/null' entries (removed files) + patched_files = [pf for pf in patched_files if pf != '/dev/null'] + _log.debug("List of patches files (after filtering): %s" % patched_files) # obtain last commit # get all commits, increase to (max of) 100 per page From 25ef005545ddd6753405a227b224d03d55f3b367 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 11 Dec 2014 15:39:50 +0100 Subject: [PATCH 174/298] fix typo --- easybuild/tools/github.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/easybuild/tools/github.py b/easybuild/tools/github.py index f7a1993e5d..8ca6926d8f 100644 --- a/easybuild/tools/github.py +++ b/easybuild/tools/github.py @@ -251,10 +251,10 @@ def download(url, path=None): diff_txt = download(pr_data['diff_url']) patched_files = det_patched_files(txt=diff_txt, omit_ab_prefix=True) - _log.debug("List of patches files (before filtering): %s" % patched_files) + _log.debug("List of patched files (before filtering): %s" % patched_files) # filter out '/dev/null' entries (removed files) patched_files = [pf for pf in patched_files if pf != '/dev/null'] - _log.debug("List of patches files (after filtering): %s" % patched_files) + _log.debug("List of patched files (after filtering): %s" % patched_files) # obtain last commit # get all commits, increase to (max of) 100 per page From 359539ed581757d2f93c4abf0370519fe1c463de Mon Sep 17 00:00:00 2001 From: Ward Poelmans Date: Thu, 11 Dec 2014 16:54:49 +0100 Subject: [PATCH 175/298] Try both rpm and dpks when searching for OS dep --- easybuild/tools/systemtools.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/easybuild/tools/systemtools.py b/easybuild/tools/systemtools.py index 7cd2babc04..8564f4b8bf 100644 --- a/easybuild/tools/systemtools.py +++ b/easybuild/tools/systemtools.py @@ -391,17 +391,13 @@ def check_os_dependency(dep): # - uses rpm -q and dpkg -s --> can be run as non-root!! # - fallback on which # - should be extended to files later? - cmd = None - if get_os_name() in ['debian', 'ubuntu']: - if which('dpkg'): - cmd = "dpkg -s %s" % dep - else: - # OK for get_os_name() == redhat, fedora, RHEL, SL, centos - if which('rpm'): - cmd = "rpm -q %s" % dep - found = None - if cmd is not None: + if which('rpm'): + cmd = "rpm -q %s" % dep + found = run_cmd(cmd, simple=True, log_all=False, log_ok=False) + + if found is None and which('dpkg'): + cmd = "dpkg -s %s" % dep found = run_cmd(cmd, simple=True, log_all=False, log_ok=False) if found is None: From a8e3761b345947b2bab0c43b95687a95642b5978 Mon Sep 17 00:00:00 2001 From: Alan O'Cais Date: Thu, 11 Dec 2014 17:11:30 +0100 Subject: [PATCH 176/298] Modified intelmkl.py take account of required BLACS library for MPICH and MPICH2 and corrected toolchain to match --- easybuild/toolchains/iimpi.py | 0 easybuild/toolchains/intel-para.py | 2 -- easybuild/toolchains/intel.py | 0 easybuild/toolchains/ipsmpi.py | 0 easybuild/toolchains/linalg/intelmkl.py | 3 ++- 5 files changed, 2 insertions(+), 3 deletions(-) mode change 100755 => 100644 easybuild/toolchains/iimpi.py mode change 100755 => 100644 easybuild/toolchains/intel.py mode change 100755 => 100644 easybuild/toolchains/ipsmpi.py diff --git a/easybuild/toolchains/iimpi.py b/easybuild/toolchains/iimpi.py old mode 100755 new mode 100644 diff --git a/easybuild/toolchains/intel-para.py b/easybuild/toolchains/intel-para.py index 9ea7a259e1..35e23da99a 100644 --- a/easybuild/toolchains/intel-para.py +++ b/easybuild/toolchains/intel-para.py @@ -39,6 +39,4 @@ class IntelPara(Ipsmpi, IntelMKL, IntelFFTW): Intel Math Kernel Library (MKL) and Intel FFTW wrappers. """ NAME = 'intel-para' - # Parastation MPI needs to be matched with the IntelMPI blacs library - BLACS_LIB = ["mkl_blacs_intelmpi%(lp64)s"] diff --git a/easybuild/toolchains/intel.py b/easybuild/toolchains/intel.py old mode 100755 new mode 100644 diff --git a/easybuild/toolchains/ipsmpi.py b/easybuild/toolchains/ipsmpi.py old mode 100755 new mode 100644 diff --git a/easybuild/toolchains/linalg/intelmkl.py b/easybuild/toolchains/linalg/intelmkl.py index 2f1186f6ee..b994b1bc54 100644 --- a/easybuild/toolchains/linalg/intelmkl.py +++ b/easybuild/toolchains/linalg/intelmkl.py @@ -126,7 +126,8 @@ def _set_blacs_variables(self): "OpenMPI": '_openmpi', "IntelMPI": '_intelmpi', "MVAPICH2": '_intelmpi', - "MPICH2":'', + "MPICH2": '_intelmpi', + "MPICH": '_intelmpi', } try: self.BLACS_LIB_MAP.update({'mpi': mpimap[self.MPI_FAMILY]}) From 03b28ee9c2f58ff86fedbd3bb2f90203432011d2 Mon Sep 17 00:00:00 2001 From: Fotis Georgatos Date: Thu, 11 Dec 2014 19:03:51 +0100 Subject: [PATCH 177/298] add FG, in relation to EASYBLOCK_CLASS_PREFIX & STRING_ENCODING_CHARMAP Signed-off-by: Fotis Georgatos --- easybuild/tools/filetools.py | 1 + 1 file changed, 1 insertion(+) diff --git a/easybuild/tools/filetools.py b/easybuild/tools/filetools.py index 18e8c754dc..0ea45a2f31 100644 --- a/easybuild/tools/filetools.py +++ b/easybuild/tools/filetools.py @@ -32,6 +32,7 @@ @author: Jens Timmerman (Ghent University) @author: Toon Willems (Ghent University) @author: Ward Poelmans (Ghent University) +@author: Fotis Georgatos (Uni.Lu, NTUA) """ import os import re From 272ec8166cf2a2f2c92fa2b80d8443be8d2a7a13 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 11 Dec 2014 19:53:38 +0100 Subject: [PATCH 178/298] filter out patched files in det_patched_files --- easybuild/tools/filetools.py | 6 +++++- easybuild/tools/github.py | 5 +---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/easybuild/tools/filetools.py b/easybuild/tools/filetools.py index 18e8c754dc..af3dad333b 100644 --- a/easybuild/tools/filetools.py +++ b/easybuild/tools/filetools.py @@ -608,7 +608,11 @@ def det_patched_files(path=None, txt=None, omit_ab_prefix=False): patched_file = match.group('file') if not omit_ab_prefix and match.group('ab_prefix') is not None: patched_file = match.group('ab_prefix') + patched_file - patched_files.append(patched_file) + + if patched_file in ['/dev/null']: + _log.debug("Ignoring patched file %s" % patched_file) + else: + patched_files.append(patched_file) return patched_files diff --git a/easybuild/tools/github.py b/easybuild/tools/github.py index 8ca6926d8f..1ff47d1555 100644 --- a/easybuild/tools/github.py +++ b/easybuild/tools/github.py @@ -251,10 +251,7 @@ def download(url, path=None): diff_txt = download(pr_data['diff_url']) patched_files = det_patched_files(txt=diff_txt, omit_ab_prefix=True) - _log.debug("List of patched files (before filtering): %s" % patched_files) - # filter out '/dev/null' entries (removed files) - patched_files = [pf for pf in patched_files if pf != '/dev/null'] - _log.debug("List of patched files (after filtering): %s" % patched_files) + _log.debug("List of patched files: %s" % patched_files) # obtain last commit # get all commits, increase to (max of) 100 per page From c5495f4da58d4efedf6b46c16a4498a2ab757a9d Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 11 Dec 2014 22:13:15 +0100 Subject: [PATCH 179/298] avoid hardcoding MPI family strings, add reference to MKL link advisor w.r.t. BLACS lib --- easybuild/toolchains/linalg/intelmkl.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/easybuild/toolchains/linalg/intelmkl.py b/easybuild/toolchains/linalg/intelmkl.py index b994b1bc54..af8cca4739 100644 --- a/easybuild/toolchains/linalg/intelmkl.py +++ b/easybuild/toolchains/linalg/intelmkl.py @@ -33,6 +33,11 @@ from easybuild.toolchains.compiler.inteliccifort import TC_CONSTANT_INTELCOMP from easybuild.toolchains.compiler.gcc import TC_CONSTANT_GCC +from easybuild.toolchains.mpi.intelmpi import TC_CONSTANT_INTELMPI +from easybuild.toolchains.mpi.mpich import TC_CONSTANT_MPICH +from easybuild.toolchains.mpi.mpich2 import TC_CONSTANT_MPICH2 +from easybuild.toolchains.mpi.mvapich2 import TC_CONSTANT_MVAPICH2 +from easybuild.toolchains.mpi.openmpi import TC_CONSTANT_OPENMPI from easybuild.tools.toolchain.linalg import LinAlg @@ -123,11 +128,14 @@ def _set_blas_variables(self): def _set_blacs_variables(self): mpimap = { - "OpenMPI": '_openmpi', - "IntelMPI": '_intelmpi', - "MVAPICH2": '_intelmpi', - "MPICH2": '_intelmpi', - "MPICH": '_intelmpi', + TC_CONSTANT_OPENMPI: '_openmpi', + TC_CONSTANT_INTELMPI: '_intelmpi', + TC_CONSTANT_MVAPICH2: '_intelmpi', + # use intelmpi MKL blacs library for both MPICH v2 and v3 + # cfr. https://software.intel.com/en-us/articles/intel-mkl-link-line-advisor + # note: MKL link advisor uses 'MPICH' for MPICH v1 + TC_CONSTANT_MPICH2: '_intelmpi', + TC_CONSTANT_MPICH: '_intelmpi', } try: self.BLACS_LIB_MAP.update({'mpi': mpimap[self.MPI_FAMILY]}) From 0cefd7e51967ffd52f27f97ca0b43f9486e80fc4 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 12 Dec 2014 08:26:47 +0100 Subject: [PATCH 180/298] deprecate fallback to ConfigureMake easyblock --- easybuild/framework/easyconfig/default.py | 3 ++- easybuild/framework/easyconfig/easyconfig.py | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/easybuild/framework/easyconfig/default.py b/easybuild/framework/easyconfig/default.py index 56b8143f7e..d9934e092e 100644 --- a/easybuild/framework/easyconfig/default.py +++ b/easybuild/framework/easyconfig/default.py @@ -92,7 +92,8 @@ 'buildopts': ['', 'Extra options passed to make step (default already has -j X)', BUILD], 'checksums': [[], "Checksums for sources and patches", BUILD], 'configopts': ['', 'Extra options passed to configure (default already has --prefix)', BUILD], - 'easyblock': ['ConfigureMake', "EasyBlock to use for building", BUILD], + 'easyblock': [None, "EasyBlock to use for building; if set to None, an easyblock is selected " + "based on the software name", BUILD], 'easybuild_version': [None, "EasyBuild-version this spec-file was written for", BUILD], 'installopts': ['', 'Extra options for installation', BUILD], 'maxparallel': [None, 'Max degree of parallelism', BUILD], diff --git a/easybuild/framework/easyconfig/easyconfig.py b/easybuild/framework/easyconfig/easyconfig.py index 7705559ce3..e0352fa002 100644 --- a/easybuild/framework/easyconfig/easyconfig.py +++ b/easybuild/framework/easyconfig/easyconfig.py @@ -81,6 +81,8 @@ 'premakeopts': ('prebuildopts', '2.0'), } +DEFAULT_EASYBLOCK = 'ConfigureMake' + _easyconfig_files_cache = {} _easyconfigs_cache = {} @@ -739,9 +741,6 @@ def get_easyblock_class(easyblock, name=None): Get class for a particular easyblock (or use default) """ - def_class = get_easyconfig_parameter_default('easyblock') - def_mod_path = get_module_path(def_class, generic=True) - try: if easyblock: # something was specified, lets parse it @@ -788,8 +787,15 @@ def get_easyblock_class(easyblock, name=None): _log.debug("error regexp: %s" % error_re.pattern) if error_re.match(str(err)): # no easyblock could be found, so fall back to default class. + def_class = DEFAULT_EASYBLOCK + def_mod_path = get_module_path(def_class, generic=True) + _log.warning("Failed to import easyblock for %s, falling back to default class %s: error: %s" % \ (class_name, (def_mod_path, def_class), err)) + + depr_msg = "Fallback to default easyblock %s (from %s)" % (def_class, def_mod_path) + depr_msg += "; use \"easyblock = '%s'\" in easyconfig file?" % def_class + _log.deprecated(depr_msg, '2.0') cls = get_class_for(def_mod_path, def_class) else: _log.error("Failed to import easyblock for %s because of module issue: %s" % (class_name, err)) @@ -798,6 +804,9 @@ def get_easyblock_class(easyblock, name=None): _log.info("Successfully obtained class '%s' for easyblock '%s' (software name '%s')" % tup) return cls + except EasyBuildError, err: + # simply reraise rather than wrapping it into another error + raise err except Exception, err: _log.error("Failed to obtain class for %s easyblock (not available?): %s" % (easyblock, err)) From 346505c283721b08fed163cd7b8d669b535da160 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 12 Dec 2014 09:56:14 +0100 Subject: [PATCH 181/298] add gzip sources to test sandbox --- .../sandbox/sources/g/gzip/gzip-1.4.tar.gz | Bin 0 -> 907411 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/framework/sandbox/sources/g/gzip/gzip-1.4.tar.gz diff --git a/test/framework/sandbox/sources/g/gzip/gzip-1.4.tar.gz b/test/framework/sandbox/sources/g/gzip/gzip-1.4.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..92d6ae4e08281020a1bb19e2de4d888bfaf36433 GIT binary patch literal 907411 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

I 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

j3Rk}HTG=!_-y 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 !ZYF* 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%0Dp=HP@F_MLzZ`yl@WVER-C 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±cMR?B!Mn5jY8DgcO(kVhWGYWXX}W? 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

    {KAof+kAqYbqgn=#9OR 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

    ;v~DPiuFJ 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

    qef&`x|nzoPmk2Y(WqGsC0*30ZiD`3?Q4l=Luw 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%0D^zsR0{+@YZ~s;3a9hDsrRQ9 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%0Dllt+UY``rXcC2nUSyiw)Cr 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 DWm%^p literal 0 HcmV?d00001 From a273f6f79ac04fca28656df834f40f62c83e6b9e Mon Sep 17 00:00:00 2001 From: Ward Poelmans Date: Fri, 12 Dec 2014 11:15:19 +0100 Subject: [PATCH 182/298] Use logger from simple_options --- easybuild/scripts/clean_gists.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/scripts/clean_gists.py b/easybuild/scripts/clean_gists.py index 5a7933d038..14e26a6a14 100755 --- a/easybuild/scripts/clean_gists.py +++ b/easybuild/scripts/clean_gists.py @@ -42,7 +42,6 @@ def main(): """the main function""" fancylogger.logToScreen(enable=True, stdout=True) fancylogger.setLogLevelInfo() - log = fancylogger.getLogger() options = { 'github-user': ('Your github username to use', None, 'store', None, 'g'), @@ -52,6 +51,7 @@ def main(): } go = simple_option(options) + log = go.log if not (go.options.all or go.options.closed_pr or go.options.orphans): log.error("Please tell me what to do?") From 19d401559bb87522941b387b0cf254c7b4e65f7e Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 12 Dec 2014 15:49:43 +0100 Subject: [PATCH 183/298] include URL to documentation on deprecated functionality in log.deprecated message --- easybuild/tools/build_log.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/easybuild/tools/build_log.py b/easybuild/tools/build_log.py index 3ceb28bae3..b447090e0e 100644 --- a/easybuild/tools/build_log.py +++ b/easybuild/tools/build_log.py @@ -49,6 +49,8 @@ # allow some experimental experimental code EXPERIMENTAL = False +DEPRECATED_DOC_URL = 'http://easybuild.readthedocs.org/en/latest/Deprecated-functionality.html' + class EasyBuildError(Exception): """ @@ -96,6 +98,7 @@ def experimental(self, msg, *args, **kwargs): def deprecated(self, msg, max_ver): """Print deprecation warning or raise an EasyBuildError, depending on max version allowed.""" + msg += "; see %s for more information" % DEPRECATED_DOC_URL fancylogger.FancyLogger.deprecated(self, msg, str(CURRENT_VERSION), max_ver, exception=EasyBuildError) def error(self, msg, *args, **kwargs): From 8db5b4b3b280f29b0b310d4b24c1eaad137d9606 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 12 Dec 2014 16:49:12 +0100 Subject: [PATCH 184/298] drop top-level log deprecation message for not-yet-mandatory 'license' easyconfig parameter --- easybuild/framework/easyconfig/easyconfig.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/easybuild/framework/easyconfig/easyconfig.py b/easybuild/framework/easyconfig/easyconfig.py index 8d871a6d7d..ce1c9e1a84 100644 --- a/easybuild/framework/easyconfig/easyconfig.py +++ b/easybuild/framework/easyconfig/easyconfig.py @@ -66,9 +66,7 @@ _log = fancylogger.getLogger('easyconfig.easyconfig', fname=False) - # add license here to make it really MANDATORY (remove comment in default) -_log.deprecated('Mandatory license not enforced', '2.0') MANDATORY_PARAMS = ['name', 'version', 'homepage', 'description', 'toolchain'] # set of configure/build/install options that can be provided as lists for an iterated build @@ -185,6 +183,7 @@ def __init__(self, path, extra_options=None, build_specs=None, validate=True, hi self._config.update(self.extra_options) self.path = path + self.mandatory = MANDATORY_PARAMS[:] # extend mandatory keys From 3abefb733ea09b3ce86786648951c2bc3cfddbc8 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 12 Dec 2014 21:14:21 +0100 Subject: [PATCH 185/298] more refactoring to avoid triggered log.deprecated unnecessarily --- easybuild/framework/easyconfig/easyconfig.py | 3 +- .../easyconfig/format/pyheaderconfigobj.py | 6 +- easybuild/tools/config.py | 107 ++++++------------ easybuild/tools/filetools.py | 3 + easybuild/tools/options.py | 51 ++++----- test/framework/config.py | 12 +- 6 files changed, 75 insertions(+), 107 deletions(-) diff --git a/easybuild/framework/easyconfig/easyconfig.py b/easybuild/framework/easyconfig/easyconfig.py index abf54b1c63..12a7fde741 100644 --- a/easybuild/framework/easyconfig/easyconfig.py +++ b/easybuild/framework/easyconfig/easyconfig.py @@ -346,10 +346,9 @@ def validate_license(self): """Validate the license""" lic = self._config['software_license'][0] if lic is None: - self.log.deprecated('Mandatory license not enforced', '2.0') # when mandatory, remove this possibility if 'software_license' in self.mandatory: - self.log.error('License is mandatory') + self.log.error("License is mandatory, but 'software_license' is undefined") elif not isinstance(lic, License): self.log.error('License %s has to be a License subclass instance, found classname %s.' % (lic, lic.__class__.__name__)) diff --git a/easybuild/framework/easyconfig/format/pyheaderconfigobj.py b/easybuild/framework/easyconfig/format/pyheaderconfigobj.py index 995931f1e1..64695589b7 100644 --- a/easybuild/framework/easyconfig/format/pyheaderconfigobj.py +++ b/easybuild/framework/easyconfig/format/pyheaderconfigobj.py @@ -75,7 +75,6 @@ def build_easyconfig_constants_dict(): def build_easyconfig_variables_dict(): """Make a dictionary with all variables that can be used""" - _log.deprecated("Magic 'global' easyconfigs variables like shared_lib_ext should no longer be used", '2.0') vars_dict = { "shared_lib_ext": get_shared_lib_ext(), } @@ -178,6 +177,11 @@ def parse_pyheader(self, pyheader): self.log.debug("pyheader initial local_vars %s" % local_vars) self.log.debug("pyheader text being exec'ed: %s" % pyheader) + # check for use of deprecated magic easyconfigs variables + for magic_var in build_easyconfig_variables_dict(): + if re.search(magic_var, pyheader, re.M): + _log.deprecated("Magic 'global' easyconfigs variable %s should no longer be used" % magic_var, '2.0') + try: exec(pyheader, global_vars, local_vars) except SyntaxError, err: diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py index 54d9b025ae..88cbdc63df 100644 --- a/easybuild/tools/config.py +++ b/easybuild/tools/config.py @@ -55,10 +55,9 @@ SUPPORT_OLDSTYLE = True DEFAULT_OLDSTYLE_CONFIG_FILE = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'easybuild_config.py') - DEFAULT_LOGFILE_FORMAT = ("easybuild", "easybuild-%(name)s-%(version)s-%(date)s.%(time)s.log") - - +DEFAULT_MNS = 'EasyBuildMNS' +DEFAULT_MODULES_TOOL = 'EnvironmentModulesC' DEFAULT_PATH_SUBDIRS = { 'buildpath': 'build', 'installpath': '', @@ -67,6 +66,18 @@ 'subdir_modules': 'modules', 'subdir_software': 'software', } +DEFAULT_PREFIX = os.path.join(os.path.expanduser('~'), ".local", "easybuild") +DEFAULT_REPOSITORY = 'FileRepository' +DEFAULT_TMP_LOGDIR = tempfile.gettempdir() + +# utility function for defining DEFAULT_ constants below +def mk_full_default_path(name, prefix=DEFAULT_PREFIX): + """Create full path, avoid '/' at the end.""" + args = [prefix] + path = DEFAULT_PATH_SUBDIRS[name] + if path: + args.append(path) + return os.path.join(*args) # build options that have a perfectly matching command line option, listed by default value BUILD_OPTIONS_CMDLINE = { @@ -160,13 +171,13 @@ # note: keys are new style option names OLDSTYLE_ENVIRONMENT_VARIABLES = { - 'buildpath': 'EASYBUILDBUILDPATH', - 'config': 'EASYBUILDCONFIG', - 'installpath': 'EASYBUILDINSTALLPATH', - 'logfile_format': 'EASYBUILDLOGFORMAT', - 'tmp_logdir': 'EASYBUILDLOGDIR', - 'sourcepath': 'EASYBUILDSOURCEPATH', - 'testoutput': 'EASYBUILDTESTOUTPUT', + 'build_path': 'EASYBUILDBUILDPATH', + 'config_file': 'EASYBUILDCONFIG', + 'install_path': 'EASYBUILDINSTALLPATH', + 'log_format': 'EASYBUILDLOGFORMAT', + 'log_dir': 'EASYBUILDLOGDIR', + 'source_path': 'EASYBUILDSOURCEPATH', + 'test_output_path': 'EASYBUILDTESTOUTPUT', } @@ -251,12 +262,14 @@ def get_user_easybuild_dir(): xdg_config_home = os.environ.get("XDG_CONFIG_HOME", os.path.join(os.path.expanduser('~'), ".config")) newpath = os.path.join(xdg_config_home, "easybuild") - if os.path.isdir(newpath): - return newpath - else: + # only issue deprecation warning/error is new path doesn't exist, but deprecated path does + if not os.path.isdir(newpath) and os.path.isdir(oldpath): _log.deprecated("The user easybuild dir has moved from %s to %s." % (oldpath, newpath), "2.0") return oldpath + # if neither exist, new path wins + return newpath + def get_default_oldstyle_configfile(): """Get the default location of the oldstyle config file to be set as default in the options""" @@ -264,7 +277,7 @@ def get_default_oldstyle_configfile(): # - check environment variable EASYBUILDCONFIG # - next, check for an EasyBuild config in $HOME/.easybuild/config.py # - last, use default config file easybuild_config.py in main.py directory - config_env_var = OLDSTYLE_ENVIRONMENT_VARIABLES['config'] + config_env_var = OLDSTYLE_ENVIRONMENT_VARIABLES['config_file'] home_config_file = os.path.join(get_user_easybuild_dir(), "config.py") if os.getenv(config_env_var): _log.debug("Environment variable %s, so using that as config file." % config_env_var) @@ -284,49 +297,6 @@ def get_default_oldstyle_configfile(): return config_file -def get_default_oldstyle_configfile_defaults(prefix=None): - """ - Return a dict with the defaults from the shipped legacy easybuild_config.py and/or environment variables - prefix: string, when provided, it used as prefix for the other defaults (where applicable) - """ - if prefix is None: - prefix = os.path.join(os.path.expanduser('~'), ".local", "easybuild") - - def mk_full_path(name): - """Create full path, avoid '/' at the end.""" - args = [prefix] - path = DEFAULT_PATH_SUBDIRS[name] - if path: - args.append(path) - return os.path.join(*args) - - # keys are the options dest - defaults = { - 'config': get_default_oldstyle_configfile(), - 'prefix': prefix, - 'buildpath': mk_full_path('buildpath'), - 'installpath': mk_full_path('installpath'), - 'sourcepath': mk_full_path('sourcepath'), - 'repository': 'FileRepository', - 'repositorypath': {'FileRepository': [mk_full_path('repositorypath')]}, - 'logfile_format': DEFAULT_LOGFILE_FORMAT[:], # make a copy - 'tmp_logdir': tempfile.gettempdir(), - 'moduleclasses': [x[0] for x in DEFAULT_MODULECLASSES], - 'subdir_modules': DEFAULT_PATH_SUBDIRS['subdir_modules'], - 'subdir_software': DEFAULT_PATH_SUBDIRS['subdir_software'], - 'modules_tool': 'EnvironmentModulesC', - 'module_naming_scheme': 'EasyBuildMNS', - } - - # sanity check - if not defaults['repository'] in defaults['repositorypath']: - _log.error('Failed to get repository path default for default %s' % (defaults['repository'])) - - _log.deprecated("get_default_oldstyle_configfile_defaults", "2.0") - - return defaults - - def get_default_configfiles(): """Return a list of default configfiles for tools.options/generaloption""" return [os.path.join(get_user_easybuild_dir(), "config.cfg")] @@ -477,10 +447,9 @@ def install_path(typ=None): suffix = variables[key] else: # TODO remove default setting. it should have been set through options - _log.deprecated('%s not set in config, returning default' % key, "2.0") - defaults = get_default_oldstyle_configfile_defaults() try: - suffix = defaults[key] + suffix = DEFAULT_PATH_SUBDIRS[key] + _log.deprecated('%s not set in config, returning default: %s' % (key, suffix), "2.0") except: _log.error('install_path trying to get unknown suffix %s' % key) @@ -524,10 +493,9 @@ def log_file_format(return_directory=False): if 'logfile_format' in variables: res = variables['logfile_format'][idx] else: + res = DEFAULT_LOGFILE_FORMAT[:][idx] # purposely take a copy # TODO remove default setting. it should have been set through options - _log.deprecated('logfile_format not set in config, returning default', "2.0") - defaults = get_default_oldstyle_configfile_defaults() - res = defaults['logfile_format'][idx] + _log.deprecated('logfile_format not set in config, returning default: %s' % res, '2.0') return res @@ -555,9 +523,8 @@ def get_build_log_path(): return variables['tmp_logdir'] else: # TODO remove default setting. it should have been set through options - _log.deprecated('tmp_logdir not set in config, returning default', "2.0") - defaults = get_default_oldstyle_configfile_defaults() - return defaults['tmp_logdir'] + _log.deprecated('tmp_logdir not set in config, returning default: %s' % DEFAULT_TMP_LOGDIR, "2.0") + return DEFAULT_TMP_LOGDIR def get_log_filename(name, version, add_salt=False): @@ -608,10 +575,10 @@ def module_classes(): if 'moduleclasses' in variables: return variables['moduleclasses'] else: + res = [x[0] for x in DEFAULT_MODULECLASSES] # TODO remove default setting. it should have been set through options - _log.deprecated('moduleclasses not set in config, returning default', "2.0") - defaults = get_default_oldstyle_configfile_defaults() - return defaults['moduleclasses'] + _log.deprecated('moduleclasses not set in config, returning default: %s' % res, "2.0") + return res def read_environment(env_vars, strict=False): @@ -667,7 +634,7 @@ def oldstyle_read_environment(env_vars=None, strict=False): env_var = env_vars[key] if env_var in os.environ: result[key] = os.environ[env_var] - _log.deprecated("Use of oldstyle environment variable %s for %s: %s" % (env_var, key, result[key]), "2.0") + _log.deprecated("Use of oldstyle environment variable %s for %s: %s" % (env_var, key, result[key]), '2.0') elif strict: _log.error("Can't determine value for %s. Environment variable %s is missing" % (key, env_var)) else: diff --git a/easybuild/tools/filetools.py b/easybuild/tools/filetools.py index 385422e8ad..1eb26b6aa8 100644 --- a/easybuild/tools/filetools.py +++ b/easybuild/tools/filetools.py @@ -1044,17 +1044,20 @@ def decode_class_name(name): def run_cmd(cmd, log_ok=True, log_all=False, simple=False, inp=None, regexp=True, log_output=False, path=None): """Legacy wrapper/placeholder for run.run_cmd""" + _log.deprecated("run_cmd was moved from tools.filetools to tools.run", '2.0') return run.run_cmd(cmd, log_ok=log_ok, log_all=log_all, simple=simple, inp=inp, regexp=regexp, log_output=log_output, path=path) def run_cmd_qa(cmd, qa, no_qa=None, log_ok=True, log_all=False, simple=False, regexp=True, std_qa=None, path=None): """Legacy wrapper/placeholder for run.run_cmd_qa""" + _log.deprecated("run_cmd_qa was moved from tools.filetools to tools.run", '2.0') return run.run_cmd_qa(cmd, qa, no_qa=no_qa, log_ok=log_ok, log_all=log_all, simple=simple, regexp=regexp, std_qa=std_qa, path=path) def parse_log_for_error(txt, regExp=None, stdout=True, msg=None): """Legacy wrapper/placeholder for run.parse_log_for_error""" + _log.deprecated("parse_log_for_error was moved from tools.filetools to tools.run", '2.0') return run.parse_log_for_error(txt, regExp=regExp, stdout=stdout, msg=msg) diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index 954bbc5a2c..f6e0004b70 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -50,9 +50,10 @@ from easybuild.framework.easyconfig.tools import get_paths_for from easybuild.framework.extension import Extension from easybuild.tools import build_log, config, run # @UnusedImport make sure config is always initialized! +from easybuild.tools.config import DEFAULT_LOGFILE_FORMAT, DEFAULT_MNS, DEFAULT_MODULES_TOOL, DEFAULT_MODULECLASSES +from easybuild.tools.config import DEFAULT_PATH_SUBDIRS, DEFAULT_PREFIX, DEFAULT_REPOSITORY, DEFAULT_TMP_LOGDIR from easybuild.tools.config import get_default_configfiles, get_pretend_installpath -from easybuild.tools.config import get_default_oldstyle_configfile_defaults, DEFAULT_MODULECLASSES -from easybuild.tools.convert import ListOfStrings +from easybuild.tools.config import get_default_oldstyle_configfile, mk_full_default_path from easybuild.tools.github import HAVE_GITHUB_API, HAVE_KEYRING, fetch_github_token from easybuild.tools.modules import avail_modules_tools from easybuild.tools.module_naming_scheme import GENERAL_CLASS @@ -206,8 +207,6 @@ def config_options(self): # config options descr = ("Configuration options", "Configure EasyBuild behavior.") - oldstyle_defaults = get_default_oldstyle_configfile_defaults() - opts = OrderedDict({ 'avail-module-naming-schemes': ("Show all supported module naming schemes", None, 'store_true', False,), @@ -215,50 +214,49 @@ def config_options(self): None, "store_true", False,), 'avail-repositories': ("Show all repository types (incl. non-usable)", None, "store_true", False,), - 'buildpath': ("Temporary build path", None, 'store', oldstyle_defaults['buildpath']), + 'buildpath': ("Temporary build path", None, 'store', mk_full_default_path('buildpath')), 'ignore-dirs': ("Directory names to ignore when searching for files/dirs", 'strlist', 'store', ['.git', '.svn']), - 'installpath': ("Install path for software and modules", None, 'store', oldstyle_defaults['installpath']), + 'installpath': ("Install path for software and modules", None, 'store', mk_full_default_path('installpath')), 'config': ("Path to EasyBuild config file (DEPRECATED, use --configfiles instead!)", - None, 'store', oldstyle_defaults['config'], 'C'), + None, 'store', get_default_oldstyle_configfile(), 'C'), + # purposely take a copy for the default logfile format 'logfile-format': ("Directory name and format of the log file", - 'strtuple', 'store', oldstyle_defaults['logfile_format'], {'metavar': 'DIR,FORMAT'}), + 'strtuple', 'store', DEFAULT_LOGFILE_FORMAT[:], {'metavar': 'DIR,FORMAT'}), 'module-naming-scheme': ("Module naming scheme", - 'choice', 'store', oldstyle_defaults['module_naming_scheme'], - sorted(avail_module_naming_schemes().keys())), + 'choice', 'store', DEFAULT_MNS, sorted(avail_module_naming_schemes().keys())), 'moduleclasses': (("Extend supported module classes " "(For more info on the default classes, use --show-default-moduleclasses)"), - None, 'extend', oldstyle_defaults['moduleclasses']), + None, 'extend', [x[0] for x in DEFAULT_MODULECLASSES]), 'modules-footer': ("Path to file containing footer to be added to all generated module files", None, 'store_or_None', None, {'metavar': "PATH"}), 'modules-tool': ("Modules tool to use", - 'choice', 'store', oldstyle_defaults['modules_tool'], - sorted(avail_modules_tools().keys())), + 'choice', 'store', DEFAULT_MODULES_TOOL, sorted(avail_modules_tools().keys())), 'prefix': (("Change prefix for buildpath, installpath, sourcepath and repositorypath " - "(used prefix for defaults %s)" % oldstyle_defaults['prefix']), + "(used prefix for defaults %s)" % DEFAULT_PREFIX), None, 'store', None), 'recursive-module-unload': ("Enable generating of modules that unload recursively.", None, 'store_true', False), 'repository': ("Repository type, using repositorypath", - 'choice', 'store', oldstyle_defaults['repository'], sorted(avail_repositories().keys())), + 'choice', 'store', DEFAULT_REPOSITORY, sorted(avail_repositories().keys())), 'repositorypath': (("Repository path, used by repository " "(is passed as list of arguments to create the repository instance). " "For more info, use --avail-repositories."), 'strlist', 'store', - oldstyle_defaults['repositorypath'][oldstyle_defaults['repository']]), + mk_full_default_path('repositorypath')), 'show-default-moduleclasses': ("Show default module classes with description", None, 'store_true', False), 'sourcepath': ("Path(s) to where sources should be downloaded (string, colon-separated)", - None, 'store', oldstyle_defaults['sourcepath']), - 'subdir-modules': ("Installpath subdir for modules", None, 'store', oldstyle_defaults['subdir_modules']), - 'subdir-software': ("Installpath subdir for software", None, 'store', oldstyle_defaults['subdir_software']), + None, 'store', mk_full_default_path('sourcepath')), + 'subdir-modules': ("Installpath subdir for modules", None, 'store', DEFAULT_PATH_SUBDIRS['subdir_modules']), + 'subdir-software': ("Installpath subdir for software", None, 'store', DEFAULT_PATH_SUBDIRS['subdir_software']), 'suffix-modules-path': ("Suffix for module files install path", None, 'store', GENERAL_CLASS), # this one is sort of an exception, it's something jobscripts can set, # has no real meaning for regular eb usage 'testoutput': ("Path to where a job should place the output (to be set within jobscript)", None, 'store', None), 'tmp-logdir': ("Log directory where temporary log files are stored", - None, 'store', oldstyle_defaults['tmp_logdir']), + None, 'store', DEFAULT_TMP_LOGDIR), 'tmpdir': ('Directory to use for temporary storage', None, 'store', None), }) @@ -413,18 +411,15 @@ def postprocess(self): def _postprocess_config(self): """Postprocessing of configuration options""" if self.options.prefix is not None: - changed_defaults = get_default_oldstyle_configfile_defaults(self.options.prefix) # prefix applies to all paths, and repository has to be reinitialised to take new repositorypath into account # in the legacy-style configuration, repository is initialised in configuration file itself for dest in ['installpath', 'buildpath', 'sourcepath', 'repository', 'repositorypath']: if not self.options._action_taken.get(dest, False): - new_def = changed_defaults[dest] - if dest == 'repositorypath': - setattr(self.options, dest, new_def[changed_defaults['repository']]) + if dest == 'repository': + setattr(self.options, dest, DEFAULT_REPOSITORY) else: - setattr(self.options, dest, new_def) - # LEGACY this line is here for oldstyle reasons - self.log.deprecated('Fake action taken to distinguish from default', '2.0') + setattr(self.options, dest, mk_full_default_path(dest, prefix=self.options.prefix)) + # LEGACY this line is here for oldstyle config reasons self.options._action_taken[dest] = True if self.options.pretend: @@ -619,7 +614,7 @@ def avail_toolchains(self): def avail_repositories(self): """Show list of known repository types.""" - repopath_defaults = get_default_oldstyle_configfile_defaults()['repositorypath'] + repopath_defaults = mk_full_default_path('repositorypath') all_repos = avail_repositories(check_useable=False) usable_repos = avail_repositories(check_useable=True).keys() diff --git a/test/framework/config.py b/test/framework/config.py index 63c55efaf1..b9b811c0e4 100644 --- a/test/framework/config.py +++ b/test/framework/config.py @@ -80,7 +80,7 @@ def configure(self, args=None): options = init_config(args=args) return options.config - def xtest_default_config(self): + def test_default_config(self): """Test default configuration.""" self.purge_environment() @@ -355,7 +355,7 @@ def test_legacy_config_file(self): self.assertEqual(log_file_format(), logtmpl) self.assertEqual(get_build_log_path(), tmplogdir) - def xtest_generaloption_config(self): + def test_generaloption_config(self): """Test new-style configuration (based on generaloption).""" self.purge_environment() @@ -414,7 +414,7 @@ def xtest_generaloption_config(self): del os.environ['EASYBUILD_PREFIX'] del os.environ['EASYBUILD_SUBDIR_SOFTWARE'] - def xtest_generaloption_config_file(self): + def test_generaloption_config_file(self): """Test use of new-style configuration file.""" self.purge_environment() @@ -491,7 +491,7 @@ def xtest_generaloption_config_file(self): del os.environ['EASYBUILD_CONFIGFILES'] sys.path[:] = orig_sys_path - def xtest_set_tmpdir(self): + def test_set_tmpdir(self): """Test set_tmpdir config function.""" self.purge_environment() @@ -517,7 +517,7 @@ def xtest_set_tmpdir(self): modify_env(os.environ, self.orig_environ) tempfile.tempdir = None - def xtest_configuration_variables(self): + def test_configuration_variables(self): """Test usage of ConfigurationVariables.""" # delete instance of ConfigurationVariables ConfigurationVariables.__metaclass__._instances.pop(ConfigurationVariables, None) @@ -529,7 +529,7 @@ def xtest_configuration_variables(self): self.assertTrue(cv1 is cv2) self.assertTrue(cv1 is cv3) - def xtest_build_options(self): + def test_build_options(self): """Test usage of BuildOptions.""" # delete instance of BuildOptions BuildOptions.__metaclass__._instances.pop(BuildOptions, None) From 64abf8d90dde9f7026d93a8882289717f26f184d Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Sat, 13 Dec 2014 14:14:12 +0100 Subject: [PATCH 186/298] restore test that checks for easybuild_config.py legacy config file --- test/framework/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/framework/config.py b/test/framework/config.py index b9b811c0e4..fb4cdd854b 100644 --- a/test/framework/config.py +++ b/test/framework/config.py @@ -246,7 +246,7 @@ def test_legacy_config_file(self): self.purge_environment() cfg_fn = self.configure(args=[]) - #self.assertTrue(cfg_fn.endswith('easybuild/easybuild_config.py')) + self.assertTrue(cfg_fn.endswith('easybuild/easybuild_config.py')) configtxt = """ build_path = '%(buildpath)s' From 11e9f37ff33a7263a3dff84525603ac6e5aa3078 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Sat, 13 Dec 2014 14:14:52 +0100 Subject: [PATCH 187/298] improve deprecation message w.r.t. extra_options return type --- easybuild/framework/easyblock.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index aefe1b607f..d26253f392 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -97,8 +97,9 @@ def extra_options(extra=None): extra = {} if not isinstance(extra, dict): - _log.deprecated("Obtained value of type '%s' for extra, should be 'dict'" % type(extra), '2.0') - _log.debug("Converting extra_options value '%s' of type '%s' to a dict" % (extra, type(extra))) + typ = type(extra) + _log.deprecated("Obtained 'extra' value of type '%s' in extra_options, should be 'dict'" % typ, '2.0') + _log.debug("Converting extra_options value '%s' of type '%s' to a dict" % (extra, typ)) extra = dict(extra) # to avoid breaking backward compatibility, we still need to return a list of tuples in EasyBuild v1.x From 9bb1a272b12d0375576dbc355077b40e722dc399 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Sat, 13 Dec 2014 14:15:08 +0100 Subject: [PATCH 188/298] fix run_cmd imports --- easybuild/framework/easyconfig/tools.py | 3 ++- easybuild/framework/extension.py | 2 +- easybuild/scripts/mk_tmpl_easyblock_for.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/easybuild/framework/easyconfig/tools.py b/easybuild/framework/easyconfig/tools.py index a3c0599c07..45f8659f06 100644 --- a/easybuild/framework/easyconfig/tools.py +++ b/easybuild/framework/easyconfig/tools.py @@ -70,10 +70,11 @@ from easybuild.framework.easyconfig.easyconfig import process_easyconfig from easybuild.tools.build_log import EasyBuildError from easybuild.tools.config import build_option -from easybuild.tools.filetools import find_easyconfigs, run_cmd, search_file, write_file +from easybuild.tools.filetools import find_easyconfigs, search_file, write_file from easybuild.tools.github import fetch_easyconfigs_from_pr from easybuild.tools.modules import modules_tool from easybuild.tools.ordereddict import OrderedDict +from easybuild.tools.run import run_cmd from easybuild.tools.utilities import quote_str diff --git a/easybuild/framework/extension.py b/easybuild/framework/extension.py index af5b17672b..a50781e9b3 100644 --- a/easybuild/framework/extension.py +++ b/easybuild/framework/extension.py @@ -37,7 +37,7 @@ import os from easybuild.tools.config import build_path -from easybuild.tools.filetools import run_cmd +from easybuild.tools.run import run_cmd class Extension(object): diff --git a/easybuild/scripts/mk_tmpl_easyblock_for.py b/easybuild/scripts/mk_tmpl_easyblock_for.py index f2fcffbca6..78c7716507 100755 --- a/easybuild/scripts/mk_tmpl_easyblock_for.py +++ b/easybuild/scripts/mk_tmpl_easyblock_for.py @@ -121,7 +121,7 @@ import easybuild.tools.toolchain as toolchain %(parent_import)s from easybuild.framework.easyconfig import CUSTOM, MANDATORY -from easybuild.tools.filetools import run_cmd +from easybuild.tools.run import run_cmd class %(class_name)s(%(parent)s): From 1b81421f0116db61f86a3794973221f226ae5012 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Sat, 13 Dec 2014 15:55:05 +0100 Subject: [PATCH 189/298] don't trigger deprecation warning just for having parameters defined in extra_options --- easybuild/framework/easyconfig/easyconfig.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/easybuild/framework/easyconfig/easyconfig.py b/easybuild/framework/easyconfig/easyconfig.py index 52aa4d2c24..c716d722a5 100644 --- a/easybuild/framework/easyconfig/easyconfig.py +++ b/easybuild/framework/easyconfig/easyconfig.py @@ -174,14 +174,6 @@ def __init__(self, path, extra_options=None, build_specs=None, validate=True, hi tup = (type(self.extra_options), self.extra_options) self.log.error("extra_options parameter passed is of incorrect type: %s ('%s')" % tup) - # map deprecated params to new names if they occur in extra_options - for key, val in self.extra_options.items(): - if key in DEPRECATED_OPTIONS: - new_key, depr_ver = DEPRECATED_OPTIONS[key] - self.log.deprecated("Found deprecated key '%s', should use '%s' instead." % (key, new_key), depr_ver) - self.extra_options[new_key] = self.extra_options[key] - self.log.debug("Set '%s' with value of deprecated '%s': %s" % (new_key, key, self.extra_options[key])) - del self.extra_options[key] self._config.update(self.extra_options) self.path = path From 5f33cc1edd9ba6041e453462c06667b940ae2fcc Mon Sep 17 00:00:00 2001 From: Ward Poelmans Date: Sat, 13 Dec 2014 16:02:22 +0100 Subject: [PATCH 190/298] Fix bug: fallback to dpkg/... should work --- easybuild/tools/systemtools.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/easybuild/tools/systemtools.py b/easybuild/tools/systemtools.py index 8564f4b8bf..a50723cf0f 100644 --- a/easybuild/tools/systemtools.py +++ b/easybuild/tools/systemtools.py @@ -396,16 +396,16 @@ def check_os_dependency(dep): cmd = "rpm -q %s" % dep found = run_cmd(cmd, simple=True, log_all=False, log_ok=False) - if found is None and which('dpkg'): + if not found and which('dpkg'): cmd = "dpkg -s %s" % dep found = run_cmd(cmd, simple=True, log_all=False, log_ok=False) - if found is None: + if not found: # fallback for when os-dependency is a binary/library found = which(dep) # try locate if it's available - if found is None and which('locate'): + :x cmd = 'locate --regexp "/%s$"' % dep found = run_cmd(cmd, simple=True, log_all=False, log_ok=False) From d6e90907e7f58ffacd26fa60b00bb92a30a00312 Mon Sep 17 00:00:00 2001 From: Ward Poelmans Date: Sat, 13 Dec 2014 16:07:28 +0100 Subject: [PATCH 191/298] Fix typo --- easybuild/tools/systemtools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/tools/systemtools.py b/easybuild/tools/systemtools.py index a50723cf0f..6c11ff6a4b 100644 --- a/easybuild/tools/systemtools.py +++ b/easybuild/tools/systemtools.py @@ -405,7 +405,7 @@ def check_os_dependency(dep): found = which(dep) # try locate if it's available - :x + if not found and which('locate'): cmd = 'locate --regexp "/%s$"' % dep found = run_cmd(cmd, simple=True, log_all=False, log_ok=False) From 4afe0a9ea101f2af32acf78466b96b67bc0a84ff Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Sat, 13 Dec 2014 20:36:48 +0100 Subject: [PATCH 192/298] update with vsc-base for HybridListDict --- vsc/README.md | 2 +- vsc/utils/wrapper.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/vsc/README.md b/vsc/README.md index b5efeb45be..0338b53837 100644 --- a/vsc/README.md +++ b/vsc/README.md @@ -1,3 +1,3 @@ Code from https://github.com/hpcugent/vsc-base -based on 2146be5301da34043adf4646169e5dfec88cd2f5 (vsc-base v1.9.9) +based on e114e817ba3003582a805fc70f6203f9cf6062fa (vsc-base v1.9.10) diff --git a/vsc/utils/wrapper.py b/vsc/utils/wrapper.py index 537c1f252b..b449966c22 100644 --- a/vsc/utils/wrapper.py +++ b/vsc/utils/wrapper.py @@ -40,3 +40,45 @@ def proxy(self, *args): if name.startswith("__"): if name not in ignore and name not in dct: setattr(cls, name, property(make_proxy(name))) + + +class HybridListDict(Wrapper): + """ + Hybrid list/dict object: is a list of 2-element tuples, but also acts like a dict. + + Supported dict-like methods include: update(adict), items(), keys(), values() + """ + __wraps__ = list + + def __getitem__(self, index_key): + """Get value by specified index/key.""" + if isinstance(index_key, int): + res = self._obj[index_key] + else: + res = dict(self._obj)[index_key] + return res + + def __setitem__(self, index_key, value): + """Add value at specified index/key.""" + if isinstance(index_key, int): + self._obj[index_key] = value + else: + self._obj = [(k, v) for (k, v) in self._obj if k != index_key] + self._obj.append((index_key, value)) + + def update(self, extra): + """Update with keys/values in supplied dictionary.""" + self._obj = [(k, v) for (k, v) in self._obj if k not in extra.keys()] + self._obj.extend(extra.items()) + + def items(self): + """Get list of key/value tuples.""" + return self._obj + + def keys(self): + """Get list of keys.""" + return [x[0] for x in self.items()] + + def values(self): + """Get list of values.""" + return [x[1] for x in self.items()] From 3a941c8c24a2996be96900a0c1535b0a88f29825 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Sat, 13 Dec 2014 20:37:21 +0100 Subject: [PATCH 193/298] use HybridListDict as return type for extra_options --- easybuild/framework/easyblock.py | 5 +++-- easybuild/framework/easyconfig/easyconfig.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index d26253f392..10393da77b 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -47,6 +47,7 @@ from distutils.version import LooseVersion from vsc.utils import fancylogger from vsc.utils.missing import get_class_for +from vsc.utils.wrapper import HybridListDict import easybuild.tools.environment as env from easybuild.tools import config, filetools @@ -104,8 +105,8 @@ def extra_options(extra=None): # to avoid breaking backward compatibility, we still need to return a list of tuples in EasyBuild v1.x # starting with EasyBuild v2.0, this will be changed to return the actual dict - res = extra.items() - + # as a temporary workaround, return a value which is a hybrid between a list and a dict + res = HybridListDict(extra.items()) return res # diff --git a/easybuild/framework/easyconfig/easyconfig.py b/easybuild/framework/easyconfig/easyconfig.py index c716d722a5..e67d8d5653 100644 --- a/easybuild/framework/easyconfig/easyconfig.py +++ b/easybuild/framework/easyconfig/easyconfig.py @@ -42,6 +42,7 @@ from vsc.utils import fancylogger from vsc.utils.missing import any, get_class_for, nub from vsc.utils.patterns import Singleton +from vsc.utils.wrapper import HybridListDict import easybuild.tools.environment as env from easybuild.tools.build_log import EasyBuildError @@ -163,9 +164,9 @@ def __init__(self, path, extra_options=None, build_specs=None, validate=True, hi self.extra_options = extra_options if not isinstance(self.extra_options, dict): - if isinstance(self.extra_options, (list, tuple,)): + if isinstance(self.extra_options, (list, tuple, HybridListDict)): typ = type(self.extra_options) - if extra_options: + if not isinstance(self.extra_options, HybridListDict): self.log.deprecated("extra_options return value should be of type 'dict', found '%s'" % typ, '2.0') tup = (self.extra_options, type(self.extra_options)) self.log.debug("Converting extra_options value '%s' of type '%s' to a dict" % tup) From e13ebbc993659e7fcb68f1ec09afaa327b9a49aa Mon Sep 17 00:00:00 2001 From: Ward Poelmans Date: Sat, 13 Dec 2014 20:52:28 +0100 Subject: [PATCH 194/298] osdeps: this should work fail if rpm/dpkg say no If rpm or dpkg is found and they say No, don't try which or locate. --- easybuild/tools/systemtools.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/easybuild/tools/systemtools.py b/easybuild/tools/systemtools.py index 6c11ff6a4b..339db48648 100644 --- a/easybuild/tools/systemtools.py +++ b/easybuild/tools/systemtools.py @@ -392,6 +392,7 @@ def check_os_dependency(dep): # - fallback on which # - should be extended to files later? found = None + cmd = None if which('rpm'): cmd = "rpm -q %s" % dep found = run_cmd(cmd, simple=True, log_all=False, log_ok=False) @@ -400,14 +401,14 @@ def check_os_dependency(dep): cmd = "dpkg -s %s" % dep found = run_cmd(cmd, simple=True, log_all=False, log_ok=False) - if not found: + if cmd is None: # fallback for when os-dependency is a binary/library found = which(dep) - # try locate if it's available - if not found and which('locate'): - cmd = 'locate --regexp "/%s$"' % dep - found = run_cmd(cmd, simple=True, log_all=False, log_ok=False) + # try locate if it's available + if not found and which('locate'): + cmd = 'locate --regexp "/%s$"' % dep + found = run_cmd(cmd, simple=True, log_all=False, log_ok=False) return found From 7a300bc610d70d76defa9676e80c552886a1f02d Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Sat, 13 Dec 2014 21:20:30 +0100 Subject: [PATCH 195/298] remove faulty comment --- easybuild/tools/config.py | 1 - 1 file changed, 1 deletion(-) diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py index 88cbdc63df..b1f6f3427b 100644 --- a/easybuild/tools/config.py +++ b/easybuild/tools/config.py @@ -169,7 +169,6 @@ def mk_full_default_path(name, prefix=DEFAULT_PREFIX): ] -# note: keys are new style option names OLDSTYLE_ENVIRONMENT_VARIABLES = { 'build_path': 'EASYBUILDBUILDPATH', 'config_file': 'EASYBUILDCONFIG', From 3c8a56de761b17d2b492f3ede0e770291899c8c6 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Sun, 14 Dec 2014 10:28:29 +0100 Subject: [PATCH 196/298] fix broken unit tests w.r.t. HybridListDict trickery --- easybuild/framework/easyconfig/default.py | 3 +++ easybuild/scripts/mk_tmpl_easyblock_for.py | 9 ++++----- test/framework/easyblock.py | 18 +++++++++++------- test/framework/options.py | 6 +++--- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/easybuild/framework/easyconfig/default.py b/easybuild/framework/easyconfig/default.py index d9934e092e..8be6e4a4e9 100644 --- a/easybuild/framework/easyconfig/default.py +++ b/easybuild/framework/easyconfig/default.py @@ -34,6 +34,7 @@ @author: Toon Willems (Ghent University) """ from vsc.utils import fancylogger +from vsc.utils.wrapper import HybridListDict from easybuild.tools.ordereddict import OrderedDict @@ -194,6 +195,8 @@ def convert_to_help(opts, has_default=False): mapping = OrderedDict() if isinstance(opts, dict): opts = opts.items() + elif isinstance(opts, HybridListDict): + opts = list(opts) if not has_default: defs = [(k, [def_val, descr, ALL_CATEGORIES[cat]]) for k, (def_val, descr, cat) in DEFAULT_CONFIG.items()] opts = defs + opts diff --git a/easybuild/scripts/mk_tmpl_easyblock_for.py b/easybuild/scripts/mk_tmpl_easyblock_for.py index 78c7716507..0d57f13f35 100755 --- a/easybuild/scripts/mk_tmpl_easyblock_for.py +++ b/easybuild/scripts/mk_tmpl_easyblock_for.py @@ -136,11 +136,10 @@ def __init__(self, *args, **kwargs): @staticmethod def extra_options(): \"\"\"Custom easyconfig parameters for %(name)s.\"\"\" - - extra_vars = [ - ('mandatory_extra_param', ['default value', "short description", MANDATORY]), - ('optional_extra_param', ['default value', "short description", CUSTOM]), - ] + extra_vars = { + 'mandatory_extra_param': ['default value', "short description", MANDATORY], + 'optional_extra_param': ['default value', "short description", CUSTOM], + } return %(parent)s.extra_options(extra_vars) def configure_step(self): diff --git a/test/framework/easyblock.py b/test/framework/easyblock.py index 91a7441d18..d583397e12 100644 --- a/test/framework/easyblock.py +++ b/test/framework/easyblock.py @@ -79,19 +79,23 @@ def test_easyblock(self): def check_extra_options_format(extra_options): """Make sure extra_options value is of correct format.""" - # EasyBuild v1.x - self.assertTrue(isinstance(extra_options, list)) + # EasyBuild v1.x: list of (, ) tuples + self.assertTrue(isinstance(list(extra_options), list)) # conversion to a list works for extra_option in extra_options: self.assertTrue(isinstance(extra_option, tuple)) self.assertEqual(len(extra_option), 2) self.assertTrue(isinstance(extra_option[0], basestring)) self.assertTrue(isinstance(extra_option[1], list)) self.assertEqual(len(extra_option[1]), 3) - # EasyBuild v2.0 (breaks backward compatibility compared to v1.x) - #self.assertTrue(isinstance(extra_options, dict)) - #for key in extra_options: - # self.assertTrue(isinstance(extra_options[key], list)) - # self.assertTrue(len(extra_options[key]), 3) + # EasyBuild v2.0: dict with keys and values + # (breaks backward compatibility compared to v1.x) + self.assertTrue(isinstance(dict(extra_options), dict)) # conversion to a dict works + extra_options.items() + extra_options.keys() + extra_options.values() + for key in extra_options.keys(): + self.assertTrue(isinstance(extra_options[key], list)) + self.assertTrue(len(extra_options[key]), 3) name = "pi" version = "3.14" diff --git a/test/framework/options.py b/test/framework/options.py index 17f3688640..77694b83dd 100644 --- a/test/framework/options.py +++ b/test/framework/options.py @@ -349,9 +349,9 @@ def run_test(custom=None, extra_params=[]): write_file(self.logfile, '') args = [ - avail_arg, - '--unittest-file=%s' % self.logfile, - ] + avail_arg, + '--unittest-file=%s' % self.logfile, + ] if custom is not None: args.extend(['-e', custom]) From d9f51cbf7e9f163b1ca37d0979716a74b1c66ab3 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Sun, 14 Dec 2014 10:35:06 +0100 Subject: [PATCH 197/298] don't set old-style $EASYBUILDTESTOUTPUT, set $EASYBUILD_TESTOUTPUT instead --- easybuild/tools/parallelbuild.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/easybuild/tools/parallelbuild.py b/easybuild/tools/parallelbuild.py index 8af42cffeb..e6f8ee45ea 100644 --- a/easybuild/tools/parallelbuild.py +++ b/easybuild/tools/parallelbuild.py @@ -182,8 +182,9 @@ def create_job(build_command, easyconfig, output_dir=None, conn=None, ppn=None): ec_tuple = (easyconfig['ec']['name'], det_full_ec_version(easyconfig['ec'])) name = '-'.join(ec_tuple) - var = config.OLDSTYLE_ENVIRONMENT_VARIABLES['test_output_path'] - easybuild_vars[var] = os.path.join(os.path.abspath(output_dir), name) + oldstyle_testoutput_env_var = config.OLDSTYLE_ENVIRONMENT_VARIABLES['test_output_path'] + if not (oldstyle_testoutput_env_var in easybuild_vars or 'EASYBUILD_TESTOUTPUT' in easybuild_vars): + easybuild_vars['EASYBUILD_TESTOUTPUT'] = os.path.join(os.path.abspath(output_dir), name) # just use latest build stats repo = init_repository(get_repository(), get_repositorypath()) From e70808d3f52f7e8c176e278ff09e8ce3e6fe28e7 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Sun, 14 Dec 2014 20:22:30 +0100 Subject: [PATCH 198/298] drop log.deprecated message for own any/all functions --- easybuild/tools/utilities.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/easybuild/tools/utilities.py b/easybuild/tools/utilities.py index 246619e3fd..05948c9c6d 100644 --- a/easybuild/tools/utilities.py +++ b/easybuild/tools/utilities.py @@ -47,13 +47,11 @@ def any(ls): """Reimplementation of 'any' function, which is not available in Python 2.4 yet.""" - _log.deprecated("own definition of any", "2.0") return _any(ls) def all(ls): """Reimplementation of 'all' function, which is not available in Python 2.4 yet.""" - _log.deprecated("own definition of all", "2.0") return _all(ls) From ac509adb7f7ab10ce9af60055c088f56509f56a9 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Mon, 15 Dec 2014 08:19:48 +0100 Subject: [PATCH 199/298] fix creating and using of regtest output dir --- easybuild/tools/config.py | 1 + easybuild/tools/parallelbuild.py | 6 +++--- easybuild/tools/testing.py | 14 ++++++-------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py index b1f6f3427b..502950fd88 100644 --- a/easybuild/tools/config.py +++ b/easybuild/tools/config.py @@ -98,6 +98,7 @@ def mk_full_default_path(name, prefix=DEFAULT_PREFIX): 'stop', 'suffix_modules_path', 'test_report_env_filter', + 'testoutput', 'umask', ], False: [ diff --git a/easybuild/tools/parallelbuild.py b/easybuild/tools/parallelbuild.py index e6f8ee45ea..c3ca5b782e 100644 --- a/easybuild/tools/parallelbuild.py +++ b/easybuild/tools/parallelbuild.py @@ -182,9 +182,9 @@ def create_job(build_command, easyconfig, output_dir=None, conn=None, ppn=None): ec_tuple = (easyconfig['ec']['name'], det_full_ec_version(easyconfig['ec'])) name = '-'.join(ec_tuple) - oldstyle_testoutput_env_var = config.OLDSTYLE_ENVIRONMENT_VARIABLES['test_output_path'] - if not (oldstyle_testoutput_env_var in easybuild_vars or 'EASYBUILD_TESTOUTPUT' in easybuild_vars): - easybuild_vars['EASYBUILD_TESTOUTPUT'] = os.path.join(os.path.abspath(output_dir), name) + regtest_output_dir_var = 'EASYBUILD_REGTEST_OUTPUT_DIR' + if not regtest_output_dir_var in easybuild_vars: + easybuild_vars[regtest_output_dir_var] = os.path.join(os.path.abspath(output_dir), name) # just use latest build stats repo = init_repository(get_repository(), get_repositorypath()) diff --git a/easybuild/tools/testing.py b/easybuild/tools/testing.py index 5c67e972e2..695577fd05 100644 --- a/easybuild/tools/testing.py +++ b/easybuild/tools/testing.py @@ -76,19 +76,17 @@ def regtest(easyconfig_paths, build_specs=None): _log.info("aggregated xml files inside %s, output written to: %s" % (aggregate_regtest, output_file)) sys.exit(0) - # create base directory, which is used to place - # all log files and the test output as xml - basename = "easybuild-test-%s" % datetime.now().strftime("%Y%m%d%H%M%S") - var = config.OLDSTYLE_ENVIRONMENT_VARIABLES['test_output_path'] - + # create base directory, which is used to place all log files and the test output as xml regtest_output_dir = build_option('regtest_output_dir') + testoutput = build_option('testoutput') if regtest_output_dir is not None: output_dir = regtest_output_dir - elif var in os.environ: - output_dir = os.path.abspath(os.environ[var]) + elif testoutput is not None: + output_dir = os.path.abspath(testoutput) else: # default: current dir + easybuild-test-[timestamp] - output_dir = os.path.join(cur_dir, basename) + dirname = "easybuild-test-%s" % datetime.now().strftime("%Y%m%d%H%M%S") + output_dir = os.path.join(cur_dir, dirname) mkdir(output_dir, parents=True) From 0dff4ff015b4400c498399797090d05d12456cc5 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Mon, 15 Dec 2014 08:43:29 +0100 Subject: [PATCH 200/298] fix comments --- easybuild/tools/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py index 502950fd88..d66e269a3c 100644 --- a/easybuild/tools/config.py +++ b/easybuild/tools/config.py @@ -70,7 +70,7 @@ DEFAULT_REPOSITORY = 'FileRepository' DEFAULT_TMP_LOGDIR = tempfile.gettempdir() -# utility function for defining DEFAULT_ constants below +# utility function for obtaining default paths def mk_full_default_path(name, prefix=DEFAULT_PREFIX): """Create full path, avoid '/' at the end.""" args = [prefix] @@ -262,7 +262,7 @@ def get_user_easybuild_dir(): xdg_config_home = os.environ.get("XDG_CONFIG_HOME", os.path.join(os.path.expanduser('~'), ".config")) newpath = os.path.join(xdg_config_home, "easybuild") - # only issue deprecation warning/error is new path doesn't exist, but deprecated path does + # only issue deprecation warning/error if new path doesn't exist, but deprecated path does if not os.path.isdir(newpath) and os.path.isdir(oldpath): _log.deprecated("The user easybuild dir has moved from %s to %s." % (oldpath, newpath), "2.0") return oldpath From 84a21ed7c64903580d20d0404365ef9ec4bbfa6b Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Mon, 15 Dec 2014 09:49:43 +0100 Subject: [PATCH 201/298] drop kernel_name entry from get_system_info return value due to deprecation --- easybuild/tools/systemtools.py | 1 - 1 file changed, 1 deletion(-) diff --git a/easybuild/tools/systemtools.py b/easybuild/tools/systemtools.py index 7cd2babc04..d351679fd7 100644 --- a/easybuild/tools/systemtools.py +++ b/easybuild/tools/systemtools.py @@ -464,7 +464,6 @@ def get_system_info(): 'gcc_version': get_tool_version('gcc', version_option='-v'), 'hostname': gethostname(), 'glibc_version': get_glibc_version(), - 'kernel_name': get_kernel_name(), 'os_name': get_os_name(), 'os_type': get_os_type(), 'os_version': get_os_version(), From c61ff516c3fb4a02af79499d348777f4da1021e2 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Mon, 15 Dec 2014 15:31:32 +0100 Subject: [PATCH 202/298] fix finding correct easyblock to install extensions with, without duplicating code (i.e. reuse get_easyblock_class) --- easybuild/framework/easyblock.py | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 10393da77b..967220ce64 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -52,7 +52,7 @@ import easybuild.tools.environment as env from easybuild.tools import config, filetools from easybuild.framework.easyconfig import EASYCONFIGS_PKG_SUBDIR -from easybuild.framework.easyconfig.easyconfig import EasyConfig, ActiveMNS, ITERATE_OPTIONS +from easybuild.framework.easyconfig.easyconfig import DEFAULT_EASYBLOCK, ITERATE_OPTIONS, EasyConfig, ActiveMNS from easybuild.framework.easyconfig.easyconfig import fetch_parameter_from_easyconfig_file from easybuild.framework.easyconfig.easyconfig import get_easyblock_class, get_module_path, resolve_template from easybuild.framework.easyconfig.tools import get_paths_for @@ -1406,23 +1406,17 @@ def extensions_step(self, fetch=False): inst = None # try instantiating extension-specific class - class_name = encode_class_name(ext['name']) # use the same encoding as get_class - mod_path = get_module_path(class_name) - if not os.path.exists("%s.py" % mod_path): - self.log.deprecated("Determine module path based on software name", "2.0") - mod_path = get_module_path(ext['name'], decode=False) - try: - cls = get_class_for(mod_path, class_name) - inst = cls(self, ext) + cls = get_easyblock_class(None, name=ext['name']) + self.log.debug("Obtained class %s for extension %s" % (cls, ext['name'])) + if cls.__name__ != DEFAULT_EASYBLOCK: + inst = cls(self, ext) except (ImportError, NameError), err: - self.log.debug("Failed to use class %s from %s for extension %s: %s" % (class_name, - mod_path, - ext['name'], - err)) + self.log.debug("Failed to use extension-specific class for extension %s: %s" % (ext['name'], err)) # LEGACY: try and use default module path for getting extension class instance if inst is None and legacy: + self.log.deprecated("Using specified module path for default class", '2.0') try: msg = "Failed to use derived module path for %s, " % class_name msg += "considering specified module path as (legacy) fallback." @@ -1450,9 +1444,7 @@ def extensions_step(self, fetch=False): err)) # fallback attempt: use default class - if not inst is None: - self.log.debug("Installing extension %s with class %s (from %s)" % (ext['name'], class_name, mod_path)) - else: + if inst is None: try: cls = get_class_for(default_class_modpath, default_class) self.log.debug("Obtained class %s for installing extension %s" % (cls, ext['name'])) @@ -1463,6 +1455,8 @@ def extensions_step(self, fetch=False): msg = "Also failed to use default class %s from %s for extension %s: %s, giving up" % \ (default_class, default_class_modpath, ext['name'], err) self.log.error(msg) + else: + self.log.debug("Installing extension %s with class %s (from %s)" % (ext['name'], class_name, mod_path)) # real work inst.prerun() From a02928151f1fda5417d2cc4437bbfe037d598220 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Mon, 15 Dec 2014 15:58:18 +0100 Subject: [PATCH 203/298] pass default extension class as default to get_easyblock_class for extensions --- easybuild/framework/easyblock.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 967220ce64..ae8d23b6fe 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -1407,9 +1407,9 @@ def extensions_step(self, fetch=False): # try instantiating extension-specific class try: - cls = get_easyblock_class(None, name=ext['name']) + cls = get_easyblock_class(default_class, name=ext['name']) self.log.debug("Obtained class %s for extension %s" % (cls, ext['name'])) - if cls.__name__ != DEFAULT_EASYBLOCK: + if cls.__name__ != default_class: inst = cls(self, ext) except (ImportError, NameError), err: self.log.debug("Failed to use extension-specific class for extension %s: %s" % (ext['name'], err)) From f8d39a4a0cabe09faf7f572ecb12dcb6e3c5001b Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Mon, 15 Dec 2014 17:00:58 +0100 Subject: [PATCH 204/298] enhance get_easyblock_class so it can be reused for extensions, by making fallback to default optional --- easybuild/framework/easyblock.py | 6 +-- easybuild/framework/easyconfig/easyconfig.py | 36 ++++++++------- test/framework/easyconfig.py | 47 ++++++++++---------- 3 files changed, 48 insertions(+), 41 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index ae8d23b6fe..bc26543edd 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -1403,13 +1403,13 @@ def extensions_step(self, fetch=False): # always go back to original work dir to avoid running stuff from a dir that no longer exists os.chdir(self.orig_workdir) - inst = None + cls, inst = None, None # try instantiating extension-specific class try: - cls = get_easyblock_class(default_class, name=ext['name']) + cls = get_easyblock_class(None, name=ext['name'], default_fallback=False) self.log.debug("Obtained class %s for extension %s" % (cls, ext['name'])) - if cls.__name__ != default_class: + if cls is not None: inst = cls(self, ext) except (ImportError, NameError), err: self.log.debug("Failed to use extension-specific class for extension %s: %s" % (ext['name'], err)) diff --git a/easybuild/framework/easyconfig/easyconfig.py b/easybuild/framework/easyconfig/easyconfig.py index e67d8d5653..7d2c36eb9d 100644 --- a/easybuild/framework/easyconfig/easyconfig.py +++ b/easybuild/framework/easyconfig/easyconfig.py @@ -728,11 +728,11 @@ def fetch_parameter_from_easyconfig_file(path, param): return None -def get_easyblock_class(easyblock, name=None): +def get_easyblock_class(easyblock, name=None, default_fallback=True): """ Get class for a particular easyblock (or use default) """ - + cls = None try: if easyblock: # something was specified, lets parse it @@ -792,22 +792,28 @@ def get_easyblock_class(easyblock, name=None): error_re = re.compile(r"No module named %s" % modulepath.replace("easybuild.easyblocks.", '')) _log.debug("error regexp: %s" % error_re.pattern) if error_re.match(str(err)): - # no easyblock could be found, so fall back to default class. - def_class = DEFAULT_EASYBLOCK - def_mod_path = get_module_path(def_class, generic=True) - - _log.warning("Failed to import easyblock for %s, falling back to default class %s: error: %s" % \ - (class_name, (def_mod_path, def_class), err)) - - depr_msg = "Fallback to default easyblock %s (from %s)" % (def_class, def_mod_path) - depr_msg += "; use \"easyblock = '%s'\" in easyconfig file?" % def_class - _log.deprecated(depr_msg, '2.0') - cls = get_class_for(def_mod_path, def_class) + if default_fallback: + # no easyblock could be found, so fall back to default class. + def_class = DEFAULT_EASYBLOCK + def_mod_path = get_module_path(def_class, generic=True) + + _log.warning("Failed to import easyblock for %s, falling back to default class %s: error: %s" % \ + (class_name, (def_mod_path, def_class), err)) + + depr_msg = "Fallback to default easyblock %s (from %s)" % (def_class, def_mod_path) + depr_msg += "; use \"easyblock = '%s'\" in easyconfig file?" % def_class + _log.deprecated(depr_msg, '2.0') + cls = get_class_for(def_mod_path, def_class) else: _log.error("Failed to import easyblock for %s because of module issue: %s" % (class_name, err)) - tup = (cls.__name__, easyblock, name) - _log.info("Successfully obtained class '%s' for easyblock '%s' (software name '%s')" % tup) + if cls is not None: + tup = (cls.__name__, easyblock, name) + _log.info("Successfully obtained class '%s' for easyblock '%s' (software name '%s')" % tup) + else: + tup = (easyblock, name, default_fallback) + _log.debug("No class found for easyblock '%s' (software name '%s', default fallback: %s" % tup) + return cls except EasyBuildError, err: diff --git a/test/framework/easyconfig.py b/test/framework/easyconfig.py index de25d9e82c..d46eac9d26 100644 --- a/test/framework/easyconfig.py +++ b/test/framework/easyconfig.py @@ -88,14 +88,14 @@ def tearDown(self): if os.path.exists(self.eb_file): os.remove(self.eb_file) - def test_empty(self): + def xtest_empty(self): """ empty files should not parse! """ self.contents = "# empty string" self.prep() self.assertRaises(EasyBuildError, EasyConfig, self.eb_file) self.assertErrorRegex(EasyBuildError, "expected a valid path", EasyConfig, "") - def test_mandatory(self): + def xtest_mandatory(self): """ make sure all checking of mandatory variables works """ self.contents = '\n'.join([ 'name = "pi"', @@ -119,7 +119,7 @@ def test_mandatory(self): self.assertEqual(eb['toolchain'], {"name":"dummy", "version": "dummy"}) self.assertEqual(eb['description'], "test easyconfig") - def test_validation(self): + def xtest_validation(self): """ test other validations beside mandatory variables """ self.contents = '\n'.join([ 'name = "pi"', @@ -151,7 +151,7 @@ def test_validation(self): self.prep() self.assertErrorRegex(EasyBuildError, "SyntaxError", EasyConfig, self.eb_file) - def test_shared_lib_ext(self): + def xtest_shared_lib_ext(self): """ inside easyconfigs shared_lib_ext should be set """ self.contents = '\n'.join([ 'name = "pi"', @@ -165,7 +165,7 @@ def test_shared_lib_ext(self): eb = EasyConfig(self.eb_file) self.assertEqual(eb['sanity_check_paths']['files'][0], "lib/lib.%s" % get_shared_lib_ext()) - def test_dependency(self): + def xtest_dependency(self): """ test all possible ways of specifying dependencies """ self.contents = '\n'.join([ 'name = "pi"', @@ -211,7 +211,7 @@ def test_dependency(self): self.assertErrorRegex(EasyBuildError, "without name", eb._parse_dependency, ()) self.assertErrorRegex(EasyBuildError, "without version", eb._parse_dependency, {'name': 'test'}) - def test_extra_options(self): + def xtest_extra_options(self): """ extra_options should allow other variables to be stored """ self.contents = '\n'.join([ 'name = "pi"', @@ -264,7 +264,7 @@ def test_extra_options(self): self.assertEqual(eb['mandatory_key'], 'value') - def test_exts_list(self): + def xtest_exts_list(self): """Test handling of list of extensions.""" os.environ['EASYBUILD_SOURCEPATH'] = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs') init_config() @@ -295,7 +295,7 @@ def test_exts_list(self): eb = EasyBlock(ec) exts_sources = eb.fetch_extension_sources() - def test_suggestions(self): + def xtest_suggestions(self): """ If a typo is present, suggestions should be provided (if possible) """ self.contents = '\n'.join([ 'name = "pi"', @@ -314,7 +314,7 @@ def test_suggestions(self): self.assertErrorRegex(EasyBuildError, "source_URLs -> source_urls", EasyConfig, self.eb_file) self.assertErrorRegex(EasyBuildError, "sourceURLs -> source_urls", EasyConfig, self.eb_file) - def test_tweaking(self): + def xtest_tweaking(self): """test tweaking ability of easyconfigs""" fd, tweaked_fn = tempfile.mkstemp(prefix='easybuild-tweaked-', suffix='.eb') @@ -386,7 +386,7 @@ def test_tweaking(self): # cleanup os.remove(tweaked_fn) - def test_installversion(self): + def xtest_installversion(self): """Test generation of install version.""" ver = "3.14" @@ -416,7 +416,7 @@ def test_installversion(self): installver = det_full_ec_version(cfg) self.assertEqual(installver, correct_installver) - def test_legacy_installversion(self): + def xtest_legacy_installversion(self): """Test generation of install version (legacy).""" ver = "3.14" @@ -434,7 +434,7 @@ def test_legacy_installversion(self): installver = det_installversion(ver, dummy, tcver, verpref, versuff) self.assertEqual(installver, correct_installver) - def test_obtain_easyconfig(self): + def xtest_obtain_easyconfig(self): """test obtaining an easyconfig file given certain specifications""" tcname = 'GCC' @@ -670,7 +670,7 @@ def trim_path(path): # cleanup shutil.rmtree(self.ec_dir) - def test_templating(self): + def xtest_templating(self): """ test easyconfig templating """ inp = { 'name': 'PI', @@ -707,7 +707,7 @@ def test_templating(self): eb['description'] = "test easyconfig % %% %s% %%% %(name)s %%(name)s %%%(name)s %%%%(name)s" self.assertEqual(eb['description'], "test easyconfig % %% %s% %%% PI %(name)s %PI %%(name)s") - def test_templating_doc(self): + def xtest_templating_doc(self): """test templating documentation""" doc = easyconfig.templates.template_documentation() # expected length: 1 per constant and 1 extra per constantgroup @@ -720,7 +720,7 @@ def test_templating_doc(self): ] self.assertEqual(len(doc.split('\n')), sum([len(temps)] + [len(x) for x in temps])) - def test_constant_doc(self): + def xtest_constant_doc(self): """test constant documentation""" doc = easyconfig.constants.constant_documentation() # expected length: 1 per constant and 1 extra per constantgroup @@ -729,7 +729,7 @@ def test_constant_doc(self): ] self.assertEqual(len(doc.split('\n')), sum([len(temps)] + [len(x) for x in temps])) - def test_build_options(self): + def xtest_build_options(self): """Test configure/build/install options, both strings and lists.""" orig_contents = '\n'.join([ 'name = "pi"', @@ -798,7 +798,7 @@ def test_build_options(self): self.prep() eb = EasyConfig(self.eb_file) - def test_buildininstalldir(self): + def xtest_buildininstalldir(self): """Test specifying build in install dir.""" self.contents = '\n'.join([ 'name = "pi"', @@ -818,7 +818,7 @@ def test_buildininstalldir(self): self.assertEqual(eb.builddir, eb.installdir) self.assertTrue(os.path.isdir(eb.builddir)) - def test_format_equivalence_basic(self): + def xtest_format_equivalence_basic(self): """Test whether easyconfigs in different formats are equivalent.""" # hard enable experimental orig_experimental = easybuild.tools.build_log.EXPERIMENTAL @@ -850,7 +850,7 @@ def test_format_equivalence_basic(self): # restore easybuild.tools.build_log.EXPERIMENTAL = orig_experimental - def test_fetch_parameter_from_easyconfig_file(self): + def xtest_fetch_parameter_from_easyconfig_file(self): """Test fetch_easyblock_from_easyconfig_file function.""" test_ecs_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'easyconfigs') toy_ec_file = os.path.join(test_ecs_dir, 'toy-0.0.eb') @@ -880,9 +880,10 @@ def test_get_easyblock_class(self): self.assertEqual(get_easyblock_class(easyblock), easyblock_class) self.assertEqual(get_easyblock_class(None, name='gzip'), ConfigureMake) + self.assertEqual(get_easyblock_class(None, name='gzip', default_fallback=False), None) self.assertEqual(get_easyblock_class(None, name='toy'), EB_toy) - def test_easyconfig_paths(self): + def xtest_easyconfig_paths(self): """Test create_paths function.""" cand_paths = create_paths("/some/path", "Foo", "1.2.3") expected_paths = [ @@ -893,7 +894,7 @@ def test_easyconfig_paths(self): ] self.assertEqual(cand_paths, expected_paths) - def test_deprecated_options(self): + def xtest_deprecated_options(self): """Test whether deprecated options are handled correctly.""" deprecated_options = [ ('makeopts', 'buildopts', 'CC=foo'), @@ -914,7 +915,7 @@ def test_deprecated_options(self): ec = EasyConfig(self.eb_file) self.assertEqual(ec[depr_opt], ec[new_opt]) - def test_toolchain_inspection(self): + def xtest_toolchain_inspection(self): """Test whether available toolchain inspection functionality is working.""" build_options = { 'robot_path': os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs'), @@ -935,7 +936,7 @@ def test_toolchain_inspection(self): self.assertEqual(det_toolchain_compilers(ec), None) self.assertEqual(det_toolchain_mpi(ec), None) - def test_filter_deps(self): + def xtest_filter_deps(self): """Test filtered dependencies.""" test_ecs_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'easyconfigs') ec_file = os.path.join(test_ecs_dir, 'goolf-1.4.10.eb') From 45a4623dcc693f6f2560c0d4a663e49899a587e5 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Mon, 15 Dec 2014 17:27:05 +0100 Subject: [PATCH 205/298] restore class_name --- easybuild/framework/easyblock.py | 1 + 1 file changed, 1 insertion(+) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index bc26543edd..caf395dea4 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -1404,6 +1404,7 @@ def extensions_step(self, fetch=False): os.chdir(self.orig_workdir) cls, inst = None, None + class_name = encode_class_name(ext['name']) # try instantiating extension-specific class try: From 3e40297205f810ab495cbd39fbe36d1aaf9fdb56 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Mon, 15 Dec 2014 17:44:00 +0100 Subject: [PATCH 206/298] restore mod_path --- easybuild/framework/easyblock.py | 1 + 1 file changed, 1 insertion(+) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index caf395dea4..4b8da3fc65 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -1405,6 +1405,7 @@ def extensions_step(self, fetch=False): cls, inst = None, None class_name = encode_class_name(ext['name']) + mod_path = get_module_path(class_name) # try instantiating extension-specific class try: From a247b8a27a6e7d7ca9b713c4f1738650b4eee7e2 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Mon, 15 Dec 2014 20:40:11 +0100 Subject: [PATCH 207/298] fix setting repositorypath when --prefix is set + unit test to check on it --- easybuild/tools/options.py | 4 +++- test/framework/config.py | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index f6e0004b70..a410d411f5 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -243,7 +243,7 @@ def config_options(self): "(is passed as list of arguments to create the repository instance). " "For more info, use --avail-repositories."), 'strlist', 'store', - mk_full_default_path('repositorypath')), + [mk_full_default_path('repositorypath')]), 'show-default-moduleclasses': ("Show default module classes with description", None, 'store_true', False), 'sourcepath': ("Path(s) to where sources should be downloaded (string, colon-separated)", @@ -417,6 +417,8 @@ def _postprocess_config(self): if not self.options._action_taken.get(dest, False): if dest == 'repository': setattr(self.options, dest, DEFAULT_REPOSITORY) + elif dest == 'repositorypath': + setattr(self.options, dest, [mk_full_default_path(dest, prefix=self.options.prefix)]) else: setattr(self.options, dest, mk_full_default_path(dest, prefix=self.options.prefix)) # LEGACY this line is here for oldstyle config reasons diff --git a/test/framework/config.py b/test/framework/config.py index fb4cdd854b..cf55b25ea1 100644 --- a/test/framework/config.py +++ b/test/framework/config.py @@ -367,6 +367,8 @@ def test_generaloption_config(self): options = init_config(args=[]) self.assertEqual(build_path(), buildpath_env_var) self.assertEqual(install_path(), os.path.join(prefix, 'software')) + self.assertEqual(get_repositorypath(), [os.path.join(prefix, 'ebfiles_repo')]) + del os.environ['EASYBUILD_PREFIX'] del os.environ['EASYBUILD_BUILDPATH'] From 9af552732386d2d199e3b613b6f91faddb8854d5 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Mon, 15 Dec 2014 20:58:27 +0100 Subject: [PATCH 208/298] don't fail on easyblock import attempt for extensions --- easybuild/framework/easyblock.py | 4 +++- easybuild/framework/easyconfig/easyconfig.py | 7 +++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 4b8da3fc65..bf6f681673 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -1409,7 +1409,9 @@ def extensions_step(self, fetch=False): # try instantiating extension-specific class try: - cls = get_easyblock_class(None, name=ext['name'], default_fallback=False) + # don't fail when importing class fails, in case we run into an existing easyblock + # with a similar name (e.g., Perl Extension 'GO' vs 'Go' for which 'EB_Go' is available) + cls = get_easyblock_class(None, name=ext['name'], default_fallback=False, error_on_failed_import=True) self.log.debug("Obtained class %s for extension %s" % (cls, ext['name'])) if cls is not None: inst = cls(self, ext) diff --git a/easybuild/framework/easyconfig/easyconfig.py b/easybuild/framework/easyconfig/easyconfig.py index 7d2c36eb9d..52b12ccf38 100644 --- a/easybuild/framework/easyconfig/easyconfig.py +++ b/easybuild/framework/easyconfig/easyconfig.py @@ -728,7 +728,7 @@ def fetch_parameter_from_easyconfig_file(path, param): return None -def get_easyblock_class(easyblock, name=None, default_fallback=True): +def get_easyblock_class(easyblock, name=None, default_fallback=True, error_on_failed_import=True): """ Get class for a particular easyblock (or use default) """ @@ -805,7 +805,10 @@ def get_easyblock_class(easyblock, name=None, default_fallback=True): _log.deprecated(depr_msg, '2.0') cls = get_class_for(def_mod_path, def_class) else: - _log.error("Failed to import easyblock for %s because of module issue: %s" % (class_name, err)) + if error_on_failed_import: + _log.error("Failed to import easyblock for %s because of module issue: %s" % (class_name, err)) + else: + _log.debug("Failed to import easyblock for %s, but ignoring it: %s" % (class_name, err)) if cls is not None: tup = (cls.__name__, easyblock, name) From 3a85e8d07837d22faaa246162bd66822147143ed Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 16 Dec 2014 09:22:53 +0100 Subject: [PATCH 209/298] fix error_on_failed_import on False for get_easyblock_class call for extensions --- easybuild/framework/easyblock.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index bf6f681673..a8457e82b0 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -1409,9 +1409,9 @@ def extensions_step(self, fetch=False): # try instantiating extension-specific class try: - # don't fail when importing class fails, in case we run into an existing easyblock + # no error when importing class fails, in case we run into an existing easyblock # with a similar name (e.g., Perl Extension 'GO' vs 'Go' for which 'EB_Go' is available) - cls = get_easyblock_class(None, name=ext['name'], default_fallback=False, error_on_failed_import=True) + cls = get_easyblock_class(None, name=ext['name'], default_fallback=False, error_on_failed_import=False) self.log.debug("Obtained class %s for extension %s" % (cls, ext['name'])) if cls is not None: inst = cls(self, ext) From 5366ea35c3c9625039e875c25870edf0564f73a5 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 16 Dec 2014 09:50:16 +0100 Subject: [PATCH 210/298] Revert "update with vsc-base for HybridListDict" This reverts commit 4afe0a9ea101f2af32acf78466b96b67bc0a84ff. --- vsc/README.md | 2 +- vsc/utils/wrapper.py | 42 ------------------------------------------ 2 files changed, 1 insertion(+), 43 deletions(-) diff --git a/vsc/README.md b/vsc/README.md index 0338b53837..b5efeb45be 100644 --- a/vsc/README.md +++ b/vsc/README.md @@ -1,3 +1,3 @@ Code from https://github.com/hpcugent/vsc-base -based on e114e817ba3003582a805fc70f6203f9cf6062fa (vsc-base v1.9.10) +based on 2146be5301da34043adf4646169e5dfec88cd2f5 (vsc-base v1.9.9) diff --git a/vsc/utils/wrapper.py b/vsc/utils/wrapper.py index b449966c22..537c1f252b 100644 --- a/vsc/utils/wrapper.py +++ b/vsc/utils/wrapper.py @@ -40,45 +40,3 @@ def proxy(self, *args): if name.startswith("__"): if name not in ignore and name not in dct: setattr(cls, name, property(make_proxy(name))) - - -class HybridListDict(Wrapper): - """ - Hybrid list/dict object: is a list of 2-element tuples, but also acts like a dict. - - Supported dict-like methods include: update(adict), items(), keys(), values() - """ - __wraps__ = list - - def __getitem__(self, index_key): - """Get value by specified index/key.""" - if isinstance(index_key, int): - res = self._obj[index_key] - else: - res = dict(self._obj)[index_key] - return res - - def __setitem__(self, index_key, value): - """Add value at specified index/key.""" - if isinstance(index_key, int): - self._obj[index_key] = value - else: - self._obj = [(k, v) for (k, v) in self._obj if k != index_key] - self._obj.append((index_key, value)) - - def update(self, extra): - """Update with keys/values in supplied dictionary.""" - self._obj = [(k, v) for (k, v) in self._obj if k not in extra.keys()] - self._obj.extend(extra.items()) - - def items(self): - """Get list of key/value tuples.""" - return self._obj - - def keys(self): - """Get list of keys.""" - return [x[0] for x in self.items()] - - def values(self): - """Get list of values.""" - return [x[1] for x in self.items()] From 194b59390c2ccc9d113111d39378a4ff1e4379c4 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 16 Dec 2014 09:55:29 +0100 Subject: [PATCH 211/298] implement ExtraOptionsDeprecatedReturnValue in tools.deprecated.eb_2_0 and use it --- easybuild/framework/easyblock.py | 4 +- easybuild/framework/easyconfig/default.py | 4 +- easybuild/framework/easyconfig/easyconfig.py | 6 +- easybuild/tools/deprecated/__init__.py | 38 ++++++++++ easybuild/tools/deprecated/eb_2_0.py | 74 ++++++++++++++++++++ 5 files changed, 119 insertions(+), 7 deletions(-) create mode 100644 easybuild/tools/deprecated/__init__.py create mode 100644 easybuild/tools/deprecated/eb_2_0.py diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index a8457e82b0..4713be9aa3 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -47,7 +47,6 @@ from distutils.version import LooseVersion from vsc.utils import fancylogger from vsc.utils.missing import get_class_for -from vsc.utils.wrapper import HybridListDict import easybuild.tools.environment as env from easybuild.tools import config, filetools @@ -61,6 +60,7 @@ from easybuild.tools.build_log import EasyBuildError, print_error, print_msg from easybuild.tools.config import build_option, build_path, get_log_filename, get_repository, get_repositorypath from easybuild.tools.config import install_path, log_path, read_only_installdir, source_paths +from easybuild.tools.deprecated.eb_2_0 import ExtraOptionsDeprecatedReturnValue from easybuild.tools.environment import restore_env from easybuild.tools.filetools import DEFAULT_CHECKSUM from easybuild.tools.filetools import adjust_permissions, apply_patch, convert_name, download_file, encode_class_name @@ -106,7 +106,7 @@ def extra_options(extra=None): # to avoid breaking backward compatibility, we still need to return a list of tuples in EasyBuild v1.x # starting with EasyBuild v2.0, this will be changed to return the actual dict # as a temporary workaround, return a value which is a hybrid between a list and a dict - res = HybridListDict(extra.items()) + res = ExtraOptionsDeprecatedReturnValue(extra.items()) return res # diff --git a/easybuild/framework/easyconfig/default.py b/easybuild/framework/easyconfig/default.py index 8be6e4a4e9..acc99c33eb 100644 --- a/easybuild/framework/easyconfig/default.py +++ b/easybuild/framework/easyconfig/default.py @@ -34,8 +34,8 @@ @author: Toon Willems (Ghent University) """ from vsc.utils import fancylogger -from vsc.utils.wrapper import HybridListDict +from easybuild.tools.deprecated.eb_2_0 import ExtraOptionsDeprecatedReturnValue from easybuild.tools.ordereddict import OrderedDict _log = fancylogger.getLogger('easyconfig.default', fname=False) @@ -195,7 +195,7 @@ def convert_to_help(opts, has_default=False): mapping = OrderedDict() if isinstance(opts, dict): opts = opts.items() - elif isinstance(opts, HybridListDict): + elif isinstance(opts, ExtraOptionsDeprecatedReturnValue): opts = list(opts) if not has_default: defs = [(k, [def_val, descr, ALL_CATEGORIES[cat]]) for k, (def_val, descr, cat) in DEFAULT_CONFIG.items()] diff --git a/easybuild/framework/easyconfig/easyconfig.py b/easybuild/framework/easyconfig/easyconfig.py index 52b12ccf38..103b792c1d 100644 --- a/easybuild/framework/easyconfig/easyconfig.py +++ b/easybuild/framework/easyconfig/easyconfig.py @@ -42,11 +42,11 @@ from vsc.utils import fancylogger from vsc.utils.missing import any, get_class_for, nub from vsc.utils.patterns import Singleton -from vsc.utils.wrapper import HybridListDict import easybuild.tools.environment as env from easybuild.tools.build_log import EasyBuildError from easybuild.tools.config import build_option, get_module_naming_scheme +from easybuild.tools.deprecated.eb_2_0 import ExtraOptionsDeprecatedReturnValue from easybuild.tools.filetools import decode_class_name, encode_class_name, read_file from easybuild.tools.module_naming_scheme import DEVEL_MODULE_SUFFIX from easybuild.tools.module_naming_scheme.utilities import avail_module_naming_schemes, det_full_ec_version @@ -164,9 +164,9 @@ def __init__(self, path, extra_options=None, build_specs=None, validate=True, hi self.extra_options = extra_options if not isinstance(self.extra_options, dict): - if isinstance(self.extra_options, (list, tuple, HybridListDict)): + if isinstance(self.extra_options, (list, tuple, ExtraOptionsDeprecatedReturnValue)): typ = type(self.extra_options) - if not isinstance(self.extra_options, HybridListDict): + if not isinstance(self.extra_options, ExtraOptionsDeprecatedReturnValue): self.log.deprecated("extra_options return value should be of type 'dict', found '%s'" % typ, '2.0') tup = (self.extra_options, type(self.extra_options)) self.log.debug("Converting extra_options value '%s' of type '%s' to a dict" % tup) diff --git a/easybuild/tools/deprecated/__init__.py b/easybuild/tools/deprecated/__init__.py new file mode 100644 index 0000000000..8c26fd6795 --- /dev/null +++ b/easybuild/tools/deprecated/__init__.py @@ -0,0 +1,38 @@ +## +# Copyright 2009-2014 Ghent University +# +# This file is part of EasyBuild, +# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en), +# with support of Ghent University (http://ugent.be/hpc), +# the Flemish Supercomputer Centre (VSC) (https://vscentrum.be/nl/en), +# the Hercules foundation (http://www.herculesstichting.be/in_English) +# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en). +# +# http://github.com/hpcugent/easybuild +# +# EasyBuild is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation v2. +# +# EasyBuild is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with EasyBuild. If not, see . +## +""" +This declares the namespace for the tools.deprecated submodule of EasyBuild, +which provides deprecated functionality. + +@author: Stijn De Weirdt (Ghent University) +@author: Dries Verdegem (Ghent University) +@author: Kenneth Hoste (Ghent University) +@author: Pieter De Baets (Ghent University) +@author: Jens Timmerman (Ghent University) +""" +from pkgutil import extend_path + +# we're not the only ones in this namespace +__path__ = extend_path(__path__, __name__) #@ReservedAssignment diff --git a/easybuild/tools/deprecated/eb_2_0.py b/easybuild/tools/deprecated/eb_2_0.py new file mode 100644 index 0000000000..55f43b4af4 --- /dev/null +++ b/easybuild/tools/deprecated/eb_2_0.py @@ -0,0 +1,74 @@ +# # +# Copyright 2014-2014 Ghent University +# +# This file is part of EasyBuild, +# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en), +# with support of Ghent University (http://ugent.be/hpc), +# the Flemish Supercomputer Centre (VSC) (https://vscentrum.be/nl/en), +# the Hercules foundation (http://www.herculesstichting.be/in_English) +# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en). +# +# http://github.com/hpcugent/easybuild +# +# EasyBuild is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation v2. +# +# EasyBuild is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with EasyBuild. If not, see . +# # +""" +Deprecated functionality for EasyBuild v1.x + +@author: Kenneth Hoste (Ghent University) +""" +from vsc.utils.wrapper import Wrapper + + +class ExtraOptionsDeprecatedReturnValue(Wrapper): + """ + Hybrid list/dict object: is a list (of 2-element tuples), but also acts like a dict. + + Supported dict-like methods include: update(adict), items(), keys(), values() + + Consistency of values being 2-element tuples is *not* checked! + """ + __wraps__ = list + + def __getitem__(self, index_key): + """Get value by specified index/key.""" + if isinstance(index_key, int): + res = self._obj[index_key] + else: + res = dict(self._obj)[index_key] + return res + + def __setitem__(self, index_key, value): + """Add value at specified index/key.""" + if isinstance(index_key, int): + self._obj[index_key] = value + else: + self._obj = [(k, v) for (k, v) in self._obj if k != index_key] + self._obj.append((index_key, value)) + + def update(self, extra): + """Update with keys/values in supplied dictionary.""" + self._obj = [(k, v) for (k, v) in self._obj if k not in extra.keys()] + self._obj.extend(extra.items()) + + def items(self): + """Get list of key/value tuples.""" + return self._obj + + def keys(self): + """Get list of keys.""" + return [x[0] for x in self.items()] + + def values(self): + """Get list of values.""" + return [x[1] for x in self.items()] From 77a68a103a356a8ff2693b158236f038e917adb3 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 16 Dec 2014 10:00:37 +0100 Subject: [PATCH 212/298] add tools.deprecated package to setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 361191c59d..783cce2062 100644 --- a/setup.py +++ b/setup.py @@ -71,7 +71,7 @@ def find_rel_test(): easybuild_packages = [ "easybuild", "easybuild.framework", "easybuild.framework.easyconfig", "easybuild.framework.easyconfig.format", "easybuild.toolchains", "easybuild.toolchains.compiler", "easybuild.toolchains.mpi", - "easybuild.toolchains.fft", "easybuild.toolchains.linalg", "easybuild.tools", + "easybuild.toolchains.fft", "easybuild.toolchains.linalg", "easybuild.tools", "easybuild.tools.deprecated", "easybuild.tools.toolchain", "easybuild.tools.module_naming_scheme", "easybuild.tools.repository", "test.framework", "test", "vsc", "vsc.utils", From 10c31dec9d2a70384c1f2b9e43633cdca63eb0e8 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 16 Dec 2014 10:20:50 +0100 Subject: [PATCH 213/298] reinstate easyconfig unit tests --- test/framework/easyconfig.py | 46 ++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/test/framework/easyconfig.py b/test/framework/easyconfig.py index d46eac9d26..278d457db5 100644 --- a/test/framework/easyconfig.py +++ b/test/framework/easyconfig.py @@ -88,14 +88,14 @@ def tearDown(self): if os.path.exists(self.eb_file): os.remove(self.eb_file) - def xtest_empty(self): + def test_empty(self): """ empty files should not parse! """ self.contents = "# empty string" self.prep() self.assertRaises(EasyBuildError, EasyConfig, self.eb_file) self.assertErrorRegex(EasyBuildError, "expected a valid path", EasyConfig, "") - def xtest_mandatory(self): + def test_mandatory(self): """ make sure all checking of mandatory variables works """ self.contents = '\n'.join([ 'name = "pi"', @@ -119,7 +119,7 @@ def xtest_mandatory(self): self.assertEqual(eb['toolchain'], {"name":"dummy", "version": "dummy"}) self.assertEqual(eb['description'], "test easyconfig") - def xtest_validation(self): + def test_validation(self): """ test other validations beside mandatory variables """ self.contents = '\n'.join([ 'name = "pi"', @@ -151,7 +151,7 @@ def xtest_validation(self): self.prep() self.assertErrorRegex(EasyBuildError, "SyntaxError", EasyConfig, self.eb_file) - def xtest_shared_lib_ext(self): + def test_shared_lib_ext(self): """ inside easyconfigs shared_lib_ext should be set """ self.contents = '\n'.join([ 'name = "pi"', @@ -165,7 +165,7 @@ def xtest_shared_lib_ext(self): eb = EasyConfig(self.eb_file) self.assertEqual(eb['sanity_check_paths']['files'][0], "lib/lib.%s" % get_shared_lib_ext()) - def xtest_dependency(self): + def test_dependency(self): """ test all possible ways of specifying dependencies """ self.contents = '\n'.join([ 'name = "pi"', @@ -211,7 +211,7 @@ def xtest_dependency(self): self.assertErrorRegex(EasyBuildError, "without name", eb._parse_dependency, ()) self.assertErrorRegex(EasyBuildError, "without version", eb._parse_dependency, {'name': 'test'}) - def xtest_extra_options(self): + def test_extra_options(self): """ extra_options should allow other variables to be stored """ self.contents = '\n'.join([ 'name = "pi"', @@ -264,7 +264,7 @@ def xtest_extra_options(self): self.assertEqual(eb['mandatory_key'], 'value') - def xtest_exts_list(self): + def test_exts_list(self): """Test handling of list of extensions.""" os.environ['EASYBUILD_SOURCEPATH'] = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs') init_config() @@ -295,7 +295,7 @@ def xtest_exts_list(self): eb = EasyBlock(ec) exts_sources = eb.fetch_extension_sources() - def xtest_suggestions(self): + def test_suggestions(self): """ If a typo is present, suggestions should be provided (if possible) """ self.contents = '\n'.join([ 'name = "pi"', @@ -314,7 +314,7 @@ def xtest_suggestions(self): self.assertErrorRegex(EasyBuildError, "source_URLs -> source_urls", EasyConfig, self.eb_file) self.assertErrorRegex(EasyBuildError, "sourceURLs -> source_urls", EasyConfig, self.eb_file) - def xtest_tweaking(self): + def test_tweaking(self): """test tweaking ability of easyconfigs""" fd, tweaked_fn = tempfile.mkstemp(prefix='easybuild-tweaked-', suffix='.eb') @@ -386,7 +386,7 @@ def xtest_tweaking(self): # cleanup os.remove(tweaked_fn) - def xtest_installversion(self): + def test_installversion(self): """Test generation of install version.""" ver = "3.14" @@ -416,7 +416,7 @@ def xtest_installversion(self): installver = det_full_ec_version(cfg) self.assertEqual(installver, correct_installver) - def xtest_legacy_installversion(self): + def test_legacy_installversion(self): """Test generation of install version (legacy).""" ver = "3.14" @@ -434,7 +434,7 @@ def xtest_legacy_installversion(self): installver = det_installversion(ver, dummy, tcver, verpref, versuff) self.assertEqual(installver, correct_installver) - def xtest_obtain_easyconfig(self): + def test_obtain_easyconfig(self): """test obtaining an easyconfig file given certain specifications""" tcname = 'GCC' @@ -670,7 +670,7 @@ def trim_path(path): # cleanup shutil.rmtree(self.ec_dir) - def xtest_templating(self): + def test_templating(self): """ test easyconfig templating """ inp = { 'name': 'PI', @@ -707,7 +707,7 @@ def xtest_templating(self): eb['description'] = "test easyconfig % %% %s% %%% %(name)s %%(name)s %%%(name)s %%%%(name)s" self.assertEqual(eb['description'], "test easyconfig % %% %s% %%% PI %(name)s %PI %%(name)s") - def xtest_templating_doc(self): + def test_templating_doc(self): """test templating documentation""" doc = easyconfig.templates.template_documentation() # expected length: 1 per constant and 1 extra per constantgroup @@ -720,7 +720,7 @@ def xtest_templating_doc(self): ] self.assertEqual(len(doc.split('\n')), sum([len(temps)] + [len(x) for x in temps])) - def xtest_constant_doc(self): + def test_constant_doc(self): """test constant documentation""" doc = easyconfig.constants.constant_documentation() # expected length: 1 per constant and 1 extra per constantgroup @@ -729,7 +729,7 @@ def xtest_constant_doc(self): ] self.assertEqual(len(doc.split('\n')), sum([len(temps)] + [len(x) for x in temps])) - def xtest_build_options(self): + def test_build_options(self): """Test configure/build/install options, both strings and lists.""" orig_contents = '\n'.join([ 'name = "pi"', @@ -798,7 +798,7 @@ def xtest_build_options(self): self.prep() eb = EasyConfig(self.eb_file) - def xtest_buildininstalldir(self): + def test_buildininstalldir(self): """Test specifying build in install dir.""" self.contents = '\n'.join([ 'name = "pi"', @@ -818,7 +818,7 @@ def xtest_buildininstalldir(self): self.assertEqual(eb.builddir, eb.installdir) self.assertTrue(os.path.isdir(eb.builddir)) - def xtest_format_equivalence_basic(self): + def test_format_equivalence_basic(self): """Test whether easyconfigs in different formats are equivalent.""" # hard enable experimental orig_experimental = easybuild.tools.build_log.EXPERIMENTAL @@ -850,7 +850,7 @@ def xtest_format_equivalence_basic(self): # restore easybuild.tools.build_log.EXPERIMENTAL = orig_experimental - def xtest_fetch_parameter_from_easyconfig_file(self): + def test_fetch_parameter_from_easyconfig_file(self): """Test fetch_easyblock_from_easyconfig_file function.""" test_ecs_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'easyconfigs') toy_ec_file = os.path.join(test_ecs_dir, 'toy-0.0.eb') @@ -883,7 +883,7 @@ def test_get_easyblock_class(self): self.assertEqual(get_easyblock_class(None, name='gzip', default_fallback=False), None) self.assertEqual(get_easyblock_class(None, name='toy'), EB_toy) - def xtest_easyconfig_paths(self): + def test_easyconfig_paths(self): """Test create_paths function.""" cand_paths = create_paths("/some/path", "Foo", "1.2.3") expected_paths = [ @@ -894,7 +894,7 @@ def xtest_easyconfig_paths(self): ] self.assertEqual(cand_paths, expected_paths) - def xtest_deprecated_options(self): + def test_deprecated_options(self): """Test whether deprecated options are handled correctly.""" deprecated_options = [ ('makeopts', 'buildopts', 'CC=foo'), @@ -915,7 +915,7 @@ def xtest_deprecated_options(self): ec = EasyConfig(self.eb_file) self.assertEqual(ec[depr_opt], ec[new_opt]) - def xtest_toolchain_inspection(self): + def test_toolchain_inspection(self): """Test whether available toolchain inspection functionality is working.""" build_options = { 'robot_path': os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs'), @@ -936,7 +936,7 @@ def xtest_toolchain_inspection(self): self.assertEqual(det_toolchain_compilers(ec), None) self.assertEqual(det_toolchain_mpi(ec), None) - def xtest_filter_deps(self): + def test_filter_deps(self): """Test filtered dependencies.""" test_ecs_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'easyconfigs') ec_file = os.path.join(test_ecs_dir, 'goolf-1.4.10.eb') From b2e8c8ea6936ff0d78856c992c9279c7597444d1 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 16 Dec 2014 10:34:53 +0100 Subject: [PATCH 214/298] specify output dir for job output via --testoutput --- easybuild/tools/parallelbuild.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/easybuild/tools/parallelbuild.py b/easybuild/tools/parallelbuild.py index c3ca5b782e..ac1d5e2f59 100644 --- a/easybuild/tools/parallelbuild.py +++ b/easybuild/tools/parallelbuild.py @@ -135,7 +135,7 @@ def submit_jobs(ordered_ecs, cmd_line_opts, testing=False): quoted_opts = subprocess.list2cmdline(opts) - command = "unset TMPDIR && cd %s && eb %%(spec)s %s" % (curdir, quoted_opts) + command = "unset TMPDIR && cd %s && eb %%(spec)s %s --testoutput=%%(output_dir)s" % (curdir, quoted_opts) _log.info("Command template for jobs: %s" % command) job_info_lines = [] if testing: @@ -153,7 +153,7 @@ def create_job(build_command, easyconfig, output_dir=None, conn=None, ppn=None): Creates a job, to build a *single* easyconfig @param build_command: format string for command, full path to an easyconfig file will be substituted in it @param easyconfig: easyconfig as processed by process_easyconfig - @param output_dir: optional output path; $EASYBUILDTESTOUTPUT will be set inside the job with this variable + @param output_dir: optional output path; --regtest-output-dir will be used inside the job with this variable @param conn: open connection to PBS server @param ppn: ppn setting to use (# 'processors' (cores) per node to use) returns the job @@ -161,9 +161,6 @@ def create_job(build_command, easyconfig, output_dir=None, conn=None, ppn=None): if output_dir is None: output_dir = 'easybuild-build' - # create command based on build_command template - command = build_command % {'spec': easyconfig['spec']} - # capture PYTHONPATH, MODULEPATH and all variables starting with EASYBUILD easybuild_vars = {} for name in os.environ: @@ -182,9 +179,11 @@ def create_job(build_command, easyconfig, output_dir=None, conn=None, ppn=None): ec_tuple = (easyconfig['ec']['name'], det_full_ec_version(easyconfig['ec'])) name = '-'.join(ec_tuple) - regtest_output_dir_var = 'EASYBUILD_REGTEST_OUTPUT_DIR' - if not regtest_output_dir_var in easybuild_vars: - easybuild_vars[regtest_output_dir_var] = os.path.join(os.path.abspath(output_dir), name) + # create command based on build_command template + command = build_command % { + 'spec': easyconfig['spec'], + 'output_dir': os.path.join(os.path.abspath(output_dir), name), + } # just use latest build stats repo = init_repository(get_repository(), get_repositorypath()) From bac1573091ed2b247537ab0d9282b3733247a6ec Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 16 Dec 2014 10:55:36 +0100 Subject: [PATCH 215/298] extend get_easyblock_class tests --- test/framework/easyconfig.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/framework/easyconfig.py b/test/framework/easyconfig.py index 278d457db5..2e6de7ee1a 100644 --- a/test/framework/easyconfig.py +++ b/test/framework/easyconfig.py @@ -882,6 +882,8 @@ def test_get_easyblock_class(self): self.assertEqual(get_easyblock_class(None, name='gzip'), ConfigureMake) self.assertEqual(get_easyblock_class(None, name='gzip', default_fallback=False), None) self.assertEqual(get_easyblock_class(None, name='toy'), EB_toy) + self.assertErrorRegex(EasyBuildError, "Failed to import EB_TOY", get_easyblock_class, None, name='TOY') + self.assertEqual(get_easyblock_class(None, name='TOY', error_on_failed_import=False), None) def test_easyconfig_paths(self): """Test create_paths function.""" From 20ef28e8d9f1bcf3ca755841bf490cbc31e16925 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 16 Dec 2014 11:28:34 +0100 Subject: [PATCH 216/298] also specify --testoutput for regtest jobs --- easybuild/tools/testing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/tools/testing.py b/easybuild/tools/testing.py index 695577fd05..8d283e0799 100644 --- a/easybuild/tools/testing.py +++ b/easybuild/tools/testing.py @@ -119,7 +119,7 @@ def regtest(easyconfig_paths, build_specs=None): else: resolved = resolve_dependencies(easyconfigs, build_specs=build_specs) - cmd = "eb %(spec)s --regtest --sequential -ld" + cmd = "eb %(spec)s --regtest --sequential -ld --testoutput=%%(output_dir)s" command = "unset TMPDIR && cd %s && %s; " % (cur_dir, cmd) # retry twice in case of failure, to avoid fluke errors command += "if [ $? -ne 0 ]; then %(cmd)s --force && %(cmd)s --force; fi" % {'cmd': cmd} From a9cf70115a12888733ecc285f9af4372e4236d95 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 16 Dec 2014 11:36:02 +0100 Subject: [PATCH 217/298] fix typo --- easybuild/tools/testing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/tools/testing.py b/easybuild/tools/testing.py index 8d283e0799..8bebbf3612 100644 --- a/easybuild/tools/testing.py +++ b/easybuild/tools/testing.py @@ -119,7 +119,7 @@ def regtest(easyconfig_paths, build_specs=None): else: resolved = resolve_dependencies(easyconfigs, build_specs=build_specs) - cmd = "eb %(spec)s --regtest --sequential -ld --testoutput=%%(output_dir)s" + cmd = "eb %(spec)s --regtest --sequential -ld --testoutput=%(output_dir)s" command = "unset TMPDIR && cd %s && %s; " % (cur_dir, cmd) # retry twice in case of failure, to avoid fluke errors command += "if [ $? -ne 0 ]; then %(cmd)s --force && %(cmd)s --force; fi" % {'cmd': cmd} From 1511923d07616d9d3010787b434a84067c376868 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 17 Dec 2014 14:34:46 +0100 Subject: [PATCH 218/298] only issue deprecation warning for non-dict and non-ExtraOptionsDeprecatedReturnValue values in extra_options --- easybuild/framework/easyblock.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 4713be9aa3..84127c3dbc 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -99,7 +99,8 @@ def extra_options(extra=None): if not isinstance(extra, dict): typ = type(extra) - _log.deprecated("Obtained 'extra' value of type '%s' in extra_options, should be 'dict'" % typ, '2.0') + if not isinstance(extra, ExtraOptionsDeprecatedReturnValue): + _log.deprecated("Obtained 'extra' value of type '%s' in extra_options, should be 'dict'" % typ, '2.0') _log.debug("Converting extra_options value '%s' of type '%s' to a dict" % (extra, typ)) extra = dict(extra) From d61cc262018c463d417edb7de66c7af6eb633bb9 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 17 Dec 2014 14:37:37 +0100 Subject: [PATCH 219/298] verbose warning on log.deprecated --- easybuild/tools/build_log.py | 1 + 1 file changed, 1 insertion(+) diff --git a/easybuild/tools/build_log.py b/easybuild/tools/build_log.py index b447090e0e..52f35bd0d6 100644 --- a/easybuild/tools/build_log.py +++ b/easybuild/tools/build_log.py @@ -99,6 +99,7 @@ def experimental(self, msg, *args, **kwargs): def deprecated(self, msg, max_ver): """Print deprecation warning or raise an EasyBuildError, depending on max version allowed.""" msg += "; see %s for more information" % DEPRECATED_DOC_URL + print_warning("Deprecated functionality, will no longer work in v%s: %s" % (max_ver, msg)) fancylogger.FancyLogger.deprecated(self, msg, str(CURRENT_VERSION), max_ver, exception=EasyBuildError) def error(self, msg, *args, **kwargs): From 86249343c2b2364c547a8ca8acbb6e9f7df53b49 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 17 Dec 2014 14:55:53 +0100 Subject: [PATCH 220/298] Revert "verbose warning on log.deprecated" This reverts commit d61cc262018c463d417edb7de66c7af6eb633bb9. --- easybuild/tools/build_log.py | 1 - 1 file changed, 1 deletion(-) diff --git a/easybuild/tools/build_log.py b/easybuild/tools/build_log.py index 52f35bd0d6..b447090e0e 100644 --- a/easybuild/tools/build_log.py +++ b/easybuild/tools/build_log.py @@ -99,7 +99,6 @@ def experimental(self, msg, *args, **kwargs): def deprecated(self, msg, max_ver): """Print deprecation warning or raise an EasyBuildError, depending on max version allowed.""" msg += "; see %s for more information" % DEPRECATED_DOC_URL - print_warning("Deprecated functionality, will no longer work in v%s: %s" % (max_ver, msg)) fancylogger.FancyLogger.deprecated(self, msg, str(CURRENT_VERSION), max_ver, exception=EasyBuildError) def error(self, msg, *args, **kwargs): From 3cf79953b0028e85e616629afb412f2105c4049a Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 18 Dec 2014 08:42:47 +0100 Subject: [PATCH 221/298] bump version to 1.16.0 and update release notes --- RELEASE_NOTES | 48 ++++++++++++++++++++++++++++++++++++++ easybuild/tools/version.py | 2 +- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/RELEASE_NOTES b/RELEASE_NOTES index fe1835fc19..604a153609 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -1,6 +1,54 @@ This file contains a description of the major changes to the easybuild-framework EasyBuild package. For more detailed information, please see the git log. +These release notes can also be consulted at http://easybuild.readthedocs.org/en/latest/Release_notes.html. + +v1.16.0 (December 18th 2014) +---------------------------- + +feature + bugfix release +- deprecate automagic fallback to ConfigureMake easyblock (#1113) + - easyconfigs should specify easyblock = 'ConfigureMake' instead of relying on the fallback mechanism + - note: automagic fallback to ConfigureMake easyblock will be dropped in EasyBuild v2.0 + - see also http://easybuild.readthedocs.org/en/latest/Deprecated-functionality.html#configuremake-fallback +- stop triggering deprecated functionality, to enable use of --deprecated=2.0 (#1107, #1115) + - see http://easybuild.readthedocs.org/en/latest/Deprecated-functionality.html#configuremake-fallback for more information +- various other enhancements, including: + - add script to clean up gists created via --upload-test-report (#958) + - also use -xHost when using Intel compilers on AMD systems (as opposed to -msse3) (#960) + - add Python version check in eb command (#1046) + - take versionprefix into account in HierarchicalMNS module naming scheme (#1058) + - clean up and refactor main.py, move functionality to other modules (#1059, #1064, #1075, #1087) + - add check in download_file function for HTTP return code + show download progress report (#1066, #1090) + - include info log message with name and location of used easyblock (#1069) + - add toolchains definitions for gpsmpi, gpsolf, impich, intel-para, ipsmpi toolchains (#1072, #1073) + - support for Parastation MPI based toolchains + - enforce that hiddendependencies is a subset of dependencies (#1078) + - this is done to avoid that site-specific policies w.r.t. hidden modules slip into contributed easyconfigs + - enable use of --show_hidden for avail subcommand with recent Lmod versions (#1081) + - add --robot-paths configure option (#1080, #1093, #1095, #1114) + - support use of %(DEFAULT_ROBOT_PATHS)s template in EasyBuild configuration files (#1100) + - see also http://easybuild.readthedocs.org/en/latest/Using_the_EasyBuild_command_line.html#controlling-the-robot-search-path + - use -xHost rather than -xHOST, to match Intel documentation (#1084) + - update and cleanup README file (#1085) + - deprecate self.moduleGenerator in favor of self.module_generator in EasyBlock (#1088) + - also support MPICH MPI family in mpi_cmd_for function (#1098) + - update documentation references to point to http://easybuild.readthedocs.org (#1102) + - check for OS dependencies with both rpm and dpkg (if available) (#1111) +- various bug fixes, including: + - fix picking required software version specified by --software-version and clean up tweak.py (#1062, #1063) + - escape $ characters in module load message specified via modloadmsg easyconfig parameter) (#1068) + - take available hidden modules into account in dependency resolution (#1065) + - fix hard crash when using patch files with an empty list of sources (#1070) + - fix Intel MKL BLACS library being used for MPICH/MPICH2-based toolchains (#1072) + - fix regular expression in fetch_parameter_from_easyconfig_file function (#1096) + - don’t hardcode queue names when submitting a job (#1106) + - fix affiliation/mail address for Fotis in headers (#1105) + - filter out /dev/null entries in patch file in det_patched_files function (#1108) + - fix gmpolf toolchain definition, to have gmpich as MPI components (instead of gmpich2) (#1101) + - ‘MPICH’ refers to MPICH v3.x, while MPICH2 refers to MPICH(2) v2.x (MPICH v1.x is ancient/obsolete) + - note: this requires to reinstall the gmpolf module, using the updated easyconfig from easybuild-easyconfigs#1217 + v1.15.2 (October 7th 2014) -------------------------- diff --git a/easybuild/tools/version.py b/easybuild/tools/version.py index f527eaecee..4ed5f0086d 100644 --- a/easybuild/tools/version.py +++ b/easybuild/tools/version.py @@ -37,7 +37,7 @@ # note: release candidates should be versioned as a pre-release, e.g. "1.1rc1" # 1.1-rc1 would indicate a post-release, i.e., and update of 1.1, so beware! -VERSION = LooseVersion("1.16.0dev") +VERSION = LooseVersion("1.16.0") UNKNOWN = "UNKNOWN" def get_git_revision(): From b7c5c3fe2147a8c617ee572dddc2cb72f8f7479f Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 18 Dec 2014 09:23:35 +0100 Subject: [PATCH 222/298] bump version to 1.16.1dev --- easybuild/tools/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/tools/version.py b/easybuild/tools/version.py index 4ed5f0086d..e47bcb41ec 100644 --- a/easybuild/tools/version.py +++ b/easybuild/tools/version.py @@ -37,7 +37,7 @@ # note: release candidates should be versioned as a pre-release, e.g. "1.1rc1" # 1.1-rc1 would indicate a post-release, i.e., and update of 1.1, so beware! -VERSION = LooseVersion("1.16.0") +VERSION = LooseVersion("1.16.1dev") UNKNOWN = "UNKNOWN" def get_git_revision(): From fe70342c3c934a92d302523fb31b2ce23f2a24f7 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 18 Dec 2014 09:24:13 +0100 Subject: [PATCH 223/298] bump version to 2.0.0dev --- easybuild/tools/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/tools/version.py b/easybuild/tools/version.py index e47bcb41ec..1bd1419af5 100644 --- a/easybuild/tools/version.py +++ b/easybuild/tools/version.py @@ -37,7 +37,7 @@ # note: release candidates should be versioned as a pre-release, e.g. "1.1rc1" # 1.1-rc1 would indicate a post-release, i.e., and update of 1.1, so beware! -VERSION = LooseVersion("1.16.1dev") +VERSION = LooseVersion("2.0.0dev") UNKNOWN = "UNKNOWN" def get_git_revision(): From 82e4e7a5654f3767936de8a0a24c29bea83145d2 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 18 Dec 2014 10:37:31 +0100 Subject: [PATCH 224/298] only trip deprecation if extensions filter still relies on 'name'/'version' keys --- easybuild/framework/easyblock.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 84127c3dbc..2d77b4eccb 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -1067,14 +1067,18 @@ def skip_extensions(self): 'src': ext.get('source'), } - deprecated_msg = "Providing 'name' and 'version' keys for extensions, should use 'ext_name', 'ext_version'" - self.log.deprecated(deprecated_msg, '2.0') - tmpldict.update({ - 'name': modname, - 'version': ext.get('version'), - }) - - cmd = cmdtmpl % tmpldict + try: + cmd = cmdtmpl % tmpldict + except KeyError, err: + self.log.warning("Failed to complete filter command template '%s' with %s: %s" % (cmdtmpl, tmpldict, err)) + deprecated_msg = "Providing 'name' and 'version' keys for extensions, should use 'ext_name', 'ext_version'" + self.log.deprecated(deprecated_msg, '2.0') + tmpldict.update({ + 'name': modname, + 'version': ext.get('version'), + }) + cmd = cmdtmpl % tmpldict + if cmdinputtmpl: stdin = cmdinputtmpl % tmpldict (cmdstdouterr, ec) = run_cmd(cmd, log_all=False, log_ok=False, simple=False, inp=stdin, regexp=False) From b6721086f2dc93185ce4878dc3e1e467d89edc2e Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 18 Dec 2014 10:40:39 +0100 Subject: [PATCH 225/298] add #1119 to release notes --- RELEASE_NOTES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE_NOTES b/RELEASE_NOTES index 604a153609..3f94327924 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -11,7 +11,7 @@ feature + bugfix release - easyconfigs should specify easyblock = 'ConfigureMake' instead of relying on the fallback mechanism - note: automagic fallback to ConfigureMake easyblock will be dropped in EasyBuild v2.0 - see also http://easybuild.readthedocs.org/en/latest/Deprecated-functionality.html#configuremake-fallback -- stop triggering deprecated functionality, to enable use of --deprecated=2.0 (#1107, #1115) +- stop triggering deprecated functionality, to enable use of --deprecated=2.0 (#1107, #1115, #1119) - see http://easybuild.readthedocs.org/en/latest/Deprecated-functionality.html#configuremake-fallback for more information - various other enhancements, including: - add script to clean up gists created via --upload-test-report (#958) From 8064076bc8340eb958051ae13791cb46eee40cc8 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 18 Dec 2014 10:53:06 +0100 Subject: [PATCH 226/298] fix remark + long lines --- easybuild/framework/easyblock.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 2d77b4eccb..ba37110e6b 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -1070,13 +1070,14 @@ def skip_extensions(self): try: cmd = cmdtmpl % tmpldict except KeyError, err: - self.log.warning("Failed to complete filter command template '%s' with %s: %s" % (cmdtmpl, tmpldict, err)) - deprecated_msg = "Providing 'name' and 'version' keys for extensions, should use 'ext_name', 'ext_version'" + self.log.warning("Failed to complete filter cmd templ '%s' using %s: %s" % (cmdtmpl, tmpldict, err)) + deprecated_msg = "Providing 'name'/'version' keys for extensions, should use 'ext_name', 'ext_version'" self.log.deprecated(deprecated_msg, '2.0') tmpldict.update({ 'name': modname, 'version': ext.get('version'), }) + self.log.debug("Retrying to complete filter cmd templ with added name/version keys: %s" % tmpldict) cmd = cmdtmpl % tmpldict if cmdinputtmpl: From 5a5fef138f9994f844e450ac2c4e200164b2bf6a Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 18 Dec 2014 11:16:46 +0100 Subject: [PATCH 227/298] sync with vsc-base 2.0.0 --- vsc/README.md | 2 +- vsc/utils/generaloption.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vsc/README.md b/vsc/README.md index b5efeb45be..165e0109fb 100644 --- a/vsc/README.md +++ b/vsc/README.md @@ -1,3 +1,3 @@ Code from https://github.com/hpcugent/vsc-base -based on 2146be5301da34043adf4646169e5dfec88cd2f5 (vsc-base v1.9.9) +based on eb47bee435e5e24666b398d8dd41f82a40214b7a (vsc-base v2.0.0) diff --git a/vsc/utils/generaloption.py b/vsc/utils/generaloption.py index e4548dc668..af6d6397c0 100644 --- a/vsc/utils/generaloption.py +++ b/vsc/utils/generaloption.py @@ -1,6 +1,6 @@ # # -# Copyright 2011-2013 Ghent University +# Copyright 2011-2014 Ghent University # # This file is part of vsc-base, # originally created by the HPC team of Ghent University (http://ugent.be/hpc/en), @@ -708,7 +708,7 @@ def __init__(self, **kwargs): go_args = kwargs.pop('go_args', None) self.no_system_exit = kwargs.pop('go_nosystemexit', None) # unit test option self.use_configfiles = kwargs.pop('go_useconfigfiles', self.CONFIGFILES_USE) # use or ignore config files - self.configfiles = kwargs.pop('go_configfiles', self.CONFIGFILES_INIT) # configfiles to parse + self.configfiles = kwargs.pop('go_configfiles', self.CONFIGFILES_INIT[:]) # configfiles to parse configfiles_initenv = kwargs.pop('go_configfiles_initenv', None) # initial environment for configfiles to parse prefixloggername = kwargs.pop('go_prefixloggername', False) # name of logger is same as envvar prefix mainbeforedefault = kwargs.pop('go_mainbeforedefault', False) # Set the main options before the default ones From 876f21e69103fa7b08415bef4ede0b57afec1b32 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 18 Dec 2014 11:17:16 +0100 Subject: [PATCH 228/298] clean up log file after running test --- test/framework/utilities.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/framework/utilities.py b/test/framework/utilities.py index e34cb37ae5..e9c84d1e9e 100644 --- a/test/framework/utilities.py +++ b/test/framework/utilities.py @@ -131,9 +131,13 @@ def tearDown(self): # restore original Python search path sys.path = self.orig_sys_path - for path in [self.test_buildpath, self.test_installpath, self.test_prefix]: + # cleanup + for path in [self.logfile, self.test_buildpath, self.test_installpath, self.test_prefix]: try: - shutil.rmtree(path) + if os.path.isdir(path): + shutil.rmtree(path) + else: + os.remove(path) except OSError, err: pass From 10db9e0f30955e4fb43728878f1c3f833e1b5899 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 18 Dec 2014 11:34:40 +0100 Subject: [PATCH 229/298] fix version to v1.16.0 --- easybuild/tools/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/tools/version.py b/easybuild/tools/version.py index 1bd1419af5..4ed5f0086d 100644 --- a/easybuild/tools/version.py +++ b/easybuild/tools/version.py @@ -37,7 +37,7 @@ # note: release candidates should be versioned as a pre-release, e.g. "1.1rc1" # 1.1-rc1 would indicate a post-release, i.e., and update of 1.1, so beware! -VERSION = LooseVersion("2.0.0dev") +VERSION = LooseVersion("1.16.0") UNKNOWN = "UNKNOWN" def get_git_revision(): From 1f54e6ffb1db003bc93a67325dda14ea964cd439 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 18 Dec 2014 12:13:21 +0100 Subject: [PATCH 230/298] bump version to 1.6.1dev --- easybuild/tools/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/tools/version.py b/easybuild/tools/version.py index 4ed5f0086d..e47bcb41ec 100644 --- a/easybuild/tools/version.py +++ b/easybuild/tools/version.py @@ -37,7 +37,7 @@ # note: release candidates should be versioned as a pre-release, e.g. "1.1rc1" # 1.1-rc1 would indicate a post-release, i.e., and update of 1.1, so beware! -VERSION = LooseVersion("1.16.0") +VERSION = LooseVersion("1.16.1dev") UNKNOWN = "UNKNOWN" def get_git_revision(): From 353565771b5025d7464458302c11cd071f7bd6cd Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 18 Dec 2014 12:13:43 +0100 Subject: [PATCH 231/298] bump version to 2.0.0dev --- easybuild/tools/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/tools/version.py b/easybuild/tools/version.py index e47bcb41ec..1bd1419af5 100644 --- a/easybuild/tools/version.py +++ b/easybuild/tools/version.py @@ -37,7 +37,7 @@ # note: release candidates should be versioned as a pre-release, e.g. "1.1rc1" # 1.1-rc1 would indicate a post-release, i.e., and update of 1.1, so beware! -VERSION = LooseVersion("1.16.1dev") +VERSION = LooseVersion("2.0.0dev") UNKNOWN = "UNKNOWN" def get_git_revision(): From 49b80ff5c22ebec24c2846d7da24c9db2fca6048 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 19 Dec 2014 09:22:58 +0100 Subject: [PATCH 232/298] don't include ConfigureMake-specific parameters in 'eb -a' output, since fallback is deprecated --- easybuild/tools/options.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index a410d411f5..c0ab4c3577 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -508,10 +508,14 @@ def avail_easyconfig_params(self): """ Print the available easyconfig parameters, for the given easyblock. """ - app = get_easyblock_class(self.options.easyblock) - extra = app.extra_options() + extra = [] + app = get_easyblock_class(self.options.easyblock, default_fallback=False) + if app is None: + extra = [] + else: + extra = app.extra_options() mapping = convert_to_help(extra, has_default=False) - if len(extra) > 0: + if extra: ebb_msg = " (* indicates specific for the %s EasyBlock)" % app.__name__ extra_names = [x[0] for x in extra] else: From 4469dbe9dcf47ba2f665c81941b6bc76fb45d79a Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 19 Dec 2014 09:35:52 +0100 Subject: [PATCH 233/298] enhance unit tests for 'eb -a' --- test/framework/options.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/framework/options.py b/test/framework/options.py index 77694b83dd..9565b1d97c 100644 --- a/test/framework/options.py +++ b/test/framework/options.py @@ -382,6 +382,7 @@ def run_test(custom=None, extra_params=[]): if os.path.exists(dummylogfn): os.remove(dummylogfn) + run_test() run_test(custom='EB_foo', extra_params=['foo_extra1', 'foo_extra2']) run_test(custom='bar', extra_params=['bar_extra1', 'bar_extra2']) run_test(custom='EB_foofoo', extra_params=['foofoo_extra1', 'foofoo_extra2']) From 63734d4431550e813a630f3060f7e77fe77f1d1b Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 19 Dec 2014 09:44:10 +0100 Subject: [PATCH 234/298] style cleanup in avail_easyconfig_params --- easybuild/tools/options.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index c0ab4c3577..59a3503116 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -510,9 +510,7 @@ def avail_easyconfig_params(self): """ extra = [] app = get_easyblock_class(self.options.easyblock, default_fallback=False) - if app is None: - extra = [] - else: + if app is not None: extra = app.extra_options() mapping = convert_to_help(extra, has_default=False) if extra: From 763dce11ba002c8789c6e33e6438790678191a16 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 19 Dec 2014 10:34:48 +0100 Subject: [PATCH 235/298] correctly check software_license value type --- easybuild/framework/easyconfig/easyconfig.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/easybuild/framework/easyconfig/easyconfig.py b/easybuild/framework/easyconfig/easyconfig.py index 103b792c1d..9a9936265b 100644 --- a/easybuild/framework/easyconfig/easyconfig.py +++ b/easybuild/framework/easyconfig/easyconfig.py @@ -99,13 +99,13 @@ def new_ec_method(self, key, *args, **kwargs): # make sure that value for software_license has correct type, convert if needed if key == 'software_license': # key 'license' will already be mapped to 'software_license' above - lic = self._config['software_license'] - if not isinstance(lic, License): + lic = self._config['software_license'][0] + if lic is not None and not isinstance(lic, License): self.log.deprecated('Type for software_license must to be instance of License (sub)class', '2.0') lic_type = type(lic) class LicenseLegacy(License, lic_type): - """A special License class to deal with legacy license paramters""" + """A special License class to deal with legacy license parameters""" DESCRICPTION = ("Internal-only, legacy closed license class to deprecate license parameter." " (DO NOT USE).") HIDDEN = False From d9d867b450269e716b014e13e0c4bb5336226f1e Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 19 Dec 2014 11:23:05 +0100 Subject: [PATCH 236/298] stop triggering deprecated behavior in tests, unless intended --- test/framework/config.py | 18 ++- test/framework/easyblock.py | 15 +- test/framework/easyconfig.py | 145 ++++++++++++------ .../easyconfigs/CUDA-5.5.22-GCC-4.8.2.eb | 1 + .../easyconfigs/FFTW-3.3.3-gompi-1.4.10.eb | 2 + test/framework/easyconfigs/GCC-4.6.3.eb | 2 + test/framework/easyconfigs/GCC-4.6.4.eb | 2 + test/framework/easyconfigs/GCC-4.7.2.eb | 2 + test/framework/easyconfigs/GCC-4.8.2.eb | 2 + test/framework/easyconfigs/GCC-4.8.3.eb | 3 + ...penBLAS-0.2.6-gompi-1.4.10-LAPACK-3.4.2.eb | 4 +- .../easyconfigs/OpenMPI-1.6.4-GCC-4.6.4.eb | 4 +- .../easyconfigs/OpenMPI-1.6.4-GCC-4.7.2.eb | 4 +- ...ompi-1.4.10-OpenBLAS-0.2.6-LAPACK-3.4.2.eb | 2 + .../easyconfigs/gzip-1.4-GCC-4.6.3.eb | 1 + test/framework/easyconfigs/gzip-1.4.eb | 1 + .../easyconfigs/gzip-1.5-goolf-1.4.10.eb | 1 + .../easyconfigs/gzip-1.5-ictce-4.1.13.eb | 1 + .../easyconfigs/hwloc-1.6.2-GCC-4.6.4.eb | 2 + .../easyconfigs/hwloc-1.6.2-GCC-4.7.2.eb | 2 + .../easyconfigs/icc-2013.5.192-GCC-4.8.3.eb | 3 + .../framework/easyconfigs/ifort-2013.3.163.eb | 2 + .../easyconfigs/ifort-2013.5.192-GCC-4.8.3.eb | 3 + .../imkl-11.1.2.144-iimpi-5.5.3-GCC-4.8.3.eb | 3 + ...4.1.3.049-iccifort-2013.5.192-GCC-4.8.3.eb | 3 + test/framework/easyconfigs/impi-4.1.3.049.eb | 2 + test/framework/easyconfigs/v1.0/GCC-4.6.3.eb | 2 + .../easyconfigs/v1.0/gzip-1.4-GCC-4.6.3.eb | 1 + test/framework/easyconfigs/v1.0/gzip-1.4.eb | 1 + .../easyconfigs/v1.0/gzip-1.5-goolf-1.4.10.eb | 1 + .../easyconfigs/v1.0/gzip-1.5-ictce-4.1.13.eb | 1 + test/framework/easyconfigs/v2.0/GCC.eb | 2 + .../easyconfigs/v2.0/doesnotexist.eb | 1 + test/framework/easyconfigs/v2.0/gzip.eb | 1 + test/framework/easyconfigs/v2.0/libpng.eb | 1 + test/framework/filetools.py | 11 -- test/framework/modules.py | 2 + test/framework/options.py | 79 ++++++---- .../easybuild/easyblocks/generic/bar.py | 9 +- .../easyblocks/generic/dummyextension.py | 34 ++++ .../sandbox/easybuild/easyblocks/toy.py | 7 +- test/framework/systemtools.py | 18 ++- test/framework/utilities.py | 5 + 43 files changed, 293 insertions(+), 113 deletions(-) create mode 100644 test/framework/sandbox/easybuild/easyblocks/generic/dummyextension.py diff --git a/test/framework/config.py b/test/framework/config.py index cf55b25ea1..5e433d1259 100644 --- a/test/framework/config.py +++ b/test/framework/config.py @@ -110,6 +110,10 @@ def test_default_config(self): def test_generaloption_overrides_legacy(self): """Test whether generaloption overrides legacy configuration.""" + # lower 'current' version to avoid tripping over deprecation errors + os.environ['EASYBUILD_DEPRECATED'] = '1.0' + init_config() + self.purge_environment() # if both legacy and generaloption style configuration is mixed, generaloption wins legacy_prefix = os.path.join(self.tmpdir, 'legacy') @@ -140,6 +144,10 @@ def test_generaloption_overrides_legacy(self): def test_legacy_env_vars(self): """Test legacy environment variables.""" + # lower 'current' version to avoid tripping over deprecation errors + os.environ['EASYBUILD_DEPRECATED'] = '1.0' + init_config() + self.purge_environment() # build path @@ -243,6 +251,10 @@ def test_legacy_env_vars(self): def test_legacy_config_file(self): """Test finding/using legacy configuration files.""" + # lower 'current' version to avoid tripping over deprecation errors + os.environ['EASYBUILD_DEPRECATED'] = '1.0' + init_config() + self.purge_environment() cfg_fn = self.configure(args=[]) @@ -381,7 +393,7 @@ def test_generaloption_config(self): write_file(config_file, '') args = [ - '--config', config_file, # force empty oldstyle config file + '--configfiles', config_file, # force empty config file '--prefix', prefix, '--installpath', install, '--repositorypath', repopath, @@ -394,14 +406,14 @@ def test_generaloption_config(self): self.assertEqual(install_path(typ='mod'), os.path.join(install, 'modules')) self.assertEqual(options.installpath, install) - self.assertEqual(options.config, config_file) + self.assertTrue(config_file in options.configfiles) # check mixed command line/env var configuration prefix = os.path.join(self.tmpdir, 'test3') install = os.path.join(self.tmpdir, 'test4', 'install') subdir_software = 'eb-soft' args = [ - '--config', config_file, # force empty oldstyle config file + '--configfiles', config_file, # force empty config file '--installpath', install, ] diff --git a/test/framework/easyblock.py b/test/framework/easyblock.py index d583397e12..976c781c9a 100644 --- a/test/framework/easyblock.py +++ b/test/framework/easyblock.py @@ -100,6 +100,7 @@ def check_extra_options_format(extra_options): name = "pi" version = "3.14" self.contents = '\n'.join([ + 'easyblock = "ConfigureMake"', 'name = "%s"' % name, 'version = "%s"' % version, 'homepage = "http://example.com"', @@ -143,7 +144,7 @@ def check_extra_options_format(extra_options): class TestExtension(ExtensionEasyBlock): @staticmethod def extra_options(): - return ExtensionEasyBlock.extra_options([('extra_param', [None, "help", CUSTOM])]) + return ExtensionEasyBlock.extra_options({'extra_param': [None, "help", CUSTOM]}) texeb = TestExtension(eb, {'name': 'bar'}) self.assertEqual(texeb.cfg['name'], 'bar') extra_options = texeb.extra_options() @@ -158,6 +159,7 @@ def extra_options(): def test_fake_module_load(self): """Testcase for fake module load""" self.contents = '\n'.join([ + 'easyblock = "ConfigureMake"', 'name = "pi"', 'version = "3.14"', 'homepage = "http://example.com"', @@ -177,6 +179,7 @@ def test_fake_module_load(self): def test_make_module_req(self): """Testcase for make_module_req""" self.contents = '\n'.join([ + 'easyblock = "ConfigureMake"', 'name = "pi"', 'version = "3.14"', 'homepage = "http://example.com"', @@ -212,6 +215,7 @@ def test_make_module_req(self): def test_extensions_step(self): """Test the extensions_step""" self.contents = '\n'.join([ + 'easyblock = "ConfigureMake"', 'name = "pi"', 'version = "3.14"', 'homepage = "http://example.com"', @@ -228,7 +232,7 @@ def test_extensions_step(self): self.assertErrorRegex(EasyBuildError, "No default extension class set", eb.extensions_step, fetch=True) # test if everything works fine if set - self.contents += "\nexts_defaultclass = ['easybuild.framework.extension', 'Extension']" + self.contents += "\nexts_defaultclass = 'DummyExtension'" self.writeEC() eb = EasyBlock(EasyConfig(self.eb_file)) eb.builddir = config.build_path() @@ -246,14 +250,15 @@ def test_extensions_step(self): def test_skip_extensions_step(self): """Test the skip_extensions_step""" self.contents = '\n'.join([ + 'easyblock = "ConfigureMake"', 'name = "pi"', 'version = "3.14"', 'homepage = "http://example.com"', 'description = "test easyconfig"', 'toolchain = {"name": "dummy", "version": "dummy"}', 'exts_list = ["ext1", "ext2"]', - 'exts_filter = ("if [ %(name)s == \'ext2\' ]; then exit 0; else exit 1; fi", "")', - 'exts_defaultclass = ["easybuild.framework.extension", "Extension"]', + 'exts_filter = ("if [ %(ext_name)s == \'ext2\' ]; then exit 0; else exit 1; fi", "")', + 'exts_defaultclass = "DummyExtension"', ]) # check if skip skips correct extensions self.writeEC() @@ -282,6 +287,7 @@ def test_make_module_step(self): modextravars = {'PI': '3.1415', 'FOO': 'bar'} modextrapaths = {'PATH': 'pibin', 'CPATH': 'pi/include'} self.contents = '\n'.join([ + 'easyblock = "ConfigureMake"', 'name = "%s"' % name, 'version = "%s"' % version, 'homepage = "http://example.com"', @@ -333,6 +339,7 @@ def test_make_module_step(self): def test_gen_dirs(self): """Test methods that generate/set build/install directory names.""" self.contents = '\n'.join([ + 'easyblock = "ConfigureMake"', "name = 'pi'", "version = '3.14'", "homepage = 'http://example.com'", diff --git a/test/framework/easyconfig.py b/test/framework/easyconfig.py index 2e6de7ee1a..3a97e400b4 100644 --- a/test/framework/easyconfig.py +++ b/test/framework/easyconfig.py @@ -98,6 +98,7 @@ def test_empty(self): def test_mandatory(self): """ make sure all checking of mandatory variables works """ self.contents = '\n'.join([ + 'easyblock = "ConfigureMake"', 'name = "pi"', 'version = "3.14"', ]) @@ -122,6 +123,7 @@ def test_mandatory(self): def test_validation(self): """ test other validations beside mandatory variables """ self.contents = '\n'.join([ + 'easyblock = "ConfigureMake"', 'name = "pi"', 'version = "3.14"', 'homepage = "http://example.com"', @@ -151,9 +153,13 @@ def test_validation(self): self.prep() self.assertErrorRegex(EasyBuildError, "SyntaxError", EasyConfig, self.eb_file) - def test_shared_lib_ext(self): + def test_deprecated_shared_lib_ext(self): """ inside easyconfigs shared_lib_ext should be set """ + os.environ['EASYBUILD_DEPRECATED'] = '1.0' + init_config() + self.contents = '\n'.join([ + 'easyblock = "ConfigureMake"', 'name = "pi"', 'version = "3.14"', 'homepage = "http://example.com"', @@ -165,9 +171,25 @@ def test_shared_lib_ext(self): eb = EasyConfig(self.eb_file) self.assertEqual(eb['sanity_check_paths']['files'][0], "lib/lib.%s" % get_shared_lib_ext()) + def test_SHLIB_EXT(self): + """ inside easyconfigs shared_lib_ext should be set """ + self.contents = '\n'.join([ + 'easyblock = "ConfigureMake"', + 'name = "pi"', + 'version = "3.14"', + 'homepage = "http://example.com"', + 'description = "test easyconfig"', + 'toolchain = {"name":"dummy", "version": "dummy"}', + 'sanity_check_paths = { "files": ["lib/lib.%s" % SHLIB_EXT] }', + ]) + self.prep() + eb = EasyConfig(self.eb_file) + self.assertEqual(eb['sanity_check_paths']['files'][0], "lib/lib.%s" % get_shared_lib_ext()) + def test_dependency(self): """ test all possible ways of specifying dependencies """ self.contents = '\n'.join([ + 'easyblock = "ConfigureMake"', 'name = "pi"', 'version = "3.14"', 'homepage = "http://example.com"', @@ -214,6 +236,7 @@ def test_dependency(self): def test_extra_options(self): """ extra_options should allow other variables to be stored """ self.contents = '\n'.join([ + 'easyblock = "ConfigureMake"', 'name = "pi"', 'version = "3.14"', 'homepage = "http://example.com"', @@ -247,13 +270,8 @@ def test_extra_options(self): # test if extra toolchain options are being passed self.assertEqual(eb.toolchain.options['static'], True) - # test legacy behavior of passing a list of tuples rather than a dict - eb = EasyConfig(self.eb_file, extra_options=extra_vars.items()) - self.assertEqual(eb['custom_key'], 'test') - - extra_vars.update({'mandatory_key': ['default', 'another mandatory key', easyconfig.MANDATORY]}) - # test extra mandatory vars + extra_vars.update({'mandatory_key': ['default', 'another mandatory key', easyconfig.MANDATORY]}) self.assertErrorRegex(EasyBuildError, r"mandatory variables? \S* not provided", EasyConfig, self.eb_file, extra_vars) @@ -264,11 +282,18 @@ def test_extra_options(self): self.assertEqual(eb['mandatory_key'], 'value') + # test legacy behavior of passing a list of tuples rather than a dict + os.environ['EASYBUILD_DEPRECATED'] = '1.0' + init_config() + eb = EasyConfig(self.eb_file, extra_options=extra_vars.items()) + self.assertEqual(eb['custom_key'], 'test') + def test_exts_list(self): """Test handling of list of extensions.""" os.environ['EASYBUILD_SOURCEPATH'] = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs') init_config() self.contents = '\n'.join([ + 'easyblock = "ConfigureMake"', 'name = "pi"', 'version = "3.14"', 'homepage = "http://example.com"', @@ -284,8 +309,8 @@ def test_exts_list(self): ' "source_urls": [("http://example.com", "suffix")],' ' "patches": ["toy-0.0.eb"],', # dummy patch to avoid downloading fail ' "checksums": [', - ' "504c7036558938f997c1c269a01d7458",', # checksum for source (gzip-1.4.eb) - ' "ddd5161154f5db67701525123129ff09",', # checksum for patch (toy-0.0.eb) + ' "787393bfc465c85607a5b24486e861c5",', # MD5 checksum for source (gzip-1.4.eb) + ' "ddd5161154f5db67701525123129ff09",', # MD5 checksum for patch (toy-0.0.eb) ' ],', ' }),', ']', @@ -298,6 +323,7 @@ def test_exts_list(self): def test_suggestions(self): """ If a typo is present, suggestions should be provided (if possible) """ self.contents = '\n'.join([ + 'easyblock = "ConfigureMake"', 'name = "pi"', 'version = "3.14"', 'homepage = "http://example.com"', @@ -321,6 +347,7 @@ def test_tweaking(self): os.close(fd) patches = ["t1.patch", ("t2.patch", 1), ("t3.patch", "test"), ("t4.h", "include")] self.contents = '\n'.join([ + 'easyblock = "ConfigureMake"', 'name = "pi"', 'homepage = "http://www.example.com"', 'description = "dummy description"', @@ -418,6 +445,8 @@ def test_installversion(self): def test_legacy_installversion(self): """Test generation of install version (legacy).""" + os.environ['EASYBUILD_DEPRECATED'] = '1.0' + init_config() ver = "3.14" verpref = "myprefix|" @@ -447,41 +476,51 @@ def test_obtain_easyconfig(self): "pi-3.15-GCC-4.3.2.eb", "pi-3.15-GCC-4.4.5.eb", "foo-1.2.3-GCC-4.3.2.eb"] - eb_files = [(fns[0], "\n".join(['name = "pi"', - 'version = "3.12"', - 'homepage = "http://example.com"', - 'description = "test easyconfig"', - 'toolchain = {"name": "dummy", "version": "dummy"}', - 'patches = %s' % patches - ])), - (fns[1], "\n".join(['name = "pi"', - 'version = "3.13"', - 'homepage = "http://example.com"', - 'description = "test easyconfig"', - 'toolchain = {"name": "%s", "version": "%s"}' % (tcname, tcver), - 'patches = %s' % patches - ])), - (fns[2], "\n".join(['name = "pi"', - 'version = "3.15"', - 'homepage = "http://example.com"', - 'description = "test easyconfig"', - 'toolchain = {"name": "%s", "version": "%s"}' % (tcname, tcver), - 'patches = %s' % patches - ])), - (fns[3], "\n".join(['name = "pi"', - 'version = "3.15"', - 'homepage = "http://example.com"', - 'description = "test easyconfig"', - 'toolchain = {"name": "%s", "version": "4.5.1"}' % tcname, - 'patches = %s' % patches - ])), - (fns[4], "\n".join(['name = "foo"', - 'version = "1.2.3"', - 'homepage = "http://example.com"', - 'description = "test easyconfig"', - 'toolchain = {"name": "%s", "version": "%s"}' % (tcname, tcver), - 'foo_extra1 = "bar"', - ])) + eb_files = [(fns[0], "\n".join([ + 'easyblock = "ConfigureMake"', + 'name = "pi"', + 'version = "3.12"', + 'homepage = "http://example.com"', + 'description = "test easyconfig"', + 'toolchain = {"name": "dummy", "version": "dummy"}', + 'patches = %s' % patches + ])), + (fns[1], "\n".join([ + 'easyblock = "ConfigureMake"', + 'name = "pi"', + 'version = "3.13"', + 'homepage = "http://example.com"', + 'description = "test easyconfig"', + 'toolchain = {"name": "%s", "version": "%s"}' % (tcname, tcver), + 'patches = %s' % patches + ])), + (fns[2], "\n".join([ + 'easyblock = "ConfigureMake"', + 'name = "pi"', + 'version = "3.15"', + 'homepage = "http://example.com"', + 'description = "test easyconfig"', + 'toolchain = {"name": "%s", "version": "%s"}' % (tcname, tcver), + 'patches = %s' % patches + ])), + (fns[3], "\n".join([ + 'easyblock = "ConfigureMake"', + 'name = "pi"', + 'version = "3.15"', + 'homepage = "http://example.com"', + 'description = "test easyconfig"', + 'toolchain = {"name": "%s", "version": "4.5.1"}' % tcname, + 'patches = %s' % patches + ])), + (fns[4], "\n".join([ + 'easyblock = "ConfigureMake"', + 'name = "foo"', + 'version = "1.2.3"', + 'homepage = "http://example.com"', + 'description = "test easyconfig"', + 'toolchain = {"name": "%s", "version": "%s"}' % (tcname, tcver), + 'foo_extra1 = "bar"', + ])) ] @@ -496,7 +535,10 @@ def test_obtain_easyconfig(self): self.assertErrorRegex(EasyBuildError, error_regexp, obtain_ec_for, specs, [self.ec_dir], None) # should find matching easyconfig file - specs = {'name': 'foo', 'version': '1.2.3'} + specs = { + 'name': 'foo', + 'version': '1.2.3' + } res = obtain_ec_for(specs, [self.ec_dir], None) self.assertEqual(res[0], False) self.assertEqual(res[1], os.path.join(self.ec_dir, fns[-1])) @@ -680,6 +722,7 @@ def test_templating(self): } # don't use any escaping insanity here, since it is templated itself self.contents = '\n'.join([ + 'easyblock = "ConfigureMake"', 'name = "%(name)s"', 'version = "%(version)s"', 'homepage = "http://example.com/%%(nameletter)s/%%(nameletterlower)s"', @@ -732,6 +775,7 @@ def test_constant_doc(self): def test_build_options(self): """Test configure/build/install options, both strings and lists.""" orig_contents = '\n'.join([ + 'easyblock = "ConfigureMake"', 'name = "pi"', 'version = "3.14"', 'homepage = "http://example.com"', @@ -801,6 +845,7 @@ def test_build_options(self): def test_buildininstalldir(self): """Test specifying build in install dir.""" self.contents = '\n'.join([ + 'easyblock = "ConfigureMake"', 'name = "pi"', 'version = "3.14"', 'homepage = "http://example.com"', @@ -879,11 +924,14 @@ def test_get_easyblock_class(self): ]: self.assertEqual(get_easyblock_class(easyblock), easyblock_class) - self.assertEqual(get_easyblock_class(None, name='gzip'), ConfigureMake) self.assertEqual(get_easyblock_class(None, name='gzip', default_fallback=False), None) self.assertEqual(get_easyblock_class(None, name='toy'), EB_toy) self.assertErrorRegex(EasyBuildError, "Failed to import EB_TOY", get_easyblock_class, None, name='TOY') self.assertEqual(get_easyblock_class(None, name='TOY', error_on_failed_import=False), None) + # deprecated functionality: ConfigureMake fallback still enabled + os.environ['EASYBUILD_DEPRECATED'] = '1.0' + init_config() + self.assertEqual(get_easyblock_class(None, name='gzip'), ConfigureMake) def test_easyconfig_paths(self): """Test create_paths function.""" @@ -898,11 +946,16 @@ def test_easyconfig_paths(self): def test_deprecated_options(self): """Test whether deprecated options are handled correctly.""" + # lower 'current' version to avoid tripping over deprecation errors + os.environ['EASYBUILD_DEPRECATED'] = '1.0' + init_config() + deprecated_options = [ ('makeopts', 'buildopts', 'CC=foo'), ('premakeopts', 'prebuildopts', ['PATH=%(builddir)s/foo:$PATH', 'PATH=%(builddir)s/bar:$PATH']), ] clean_contents = [ + 'easyblock = "ConfigureMake"', 'name = "pi"', 'version = "3.14"', 'homepage = "http://example.com"', diff --git a/test/framework/easyconfigs/CUDA-5.5.22-GCC-4.8.2.eb b/test/framework/easyconfigs/CUDA-5.5.22-GCC-4.8.2.eb index f4bf718e88..a5503aabe9 100644 --- a/test/framework/easyconfigs/CUDA-5.5.22-GCC-4.8.2.eb +++ b/test/framework/easyconfigs/CUDA-5.5.22-GCC-4.8.2.eb @@ -9,6 +9,7 @@ # This work implements a part of the HPCBIOS project and is a component of the policy: # http://hpcbios.readthedocs.org/en/latest/HPCBIOS_2012-99.html ## +easyblock = 'EB_CUDA' name = 'CUDA' version = '5.5.22' diff --git a/test/framework/easyconfigs/FFTW-3.3.3-gompi-1.4.10.eb b/test/framework/easyconfigs/FFTW-3.3.3-gompi-1.4.10.eb index 06b0c2e2e1..bead8318f4 100644 --- a/test/framework/easyconfigs/FFTW-3.3.3-gompi-1.4.10.eb +++ b/test/framework/easyconfigs/FFTW-3.3.3-gompi-1.4.10.eb @@ -1,3 +1,5 @@ +easyblock = 'ConfigureMake' + name = 'FFTW' version = '3.3.3' diff --git a/test/framework/easyconfigs/GCC-4.6.3.eb b/test/framework/easyconfigs/GCC-4.6.3.eb index 3b4c4c53c9..48cef12da0 100644 --- a/test/framework/easyconfigs/GCC-4.6.3.eb +++ b/test/framework/easyconfigs/GCC-4.6.3.eb @@ -1,3 +1,5 @@ +easyblock = 'EB_GCC' + name="GCC" version='4.6.3' diff --git a/test/framework/easyconfigs/GCC-4.6.4.eb b/test/framework/easyconfigs/GCC-4.6.4.eb index bf4adc61a6..2094773f61 100644 --- a/test/framework/easyconfigs/GCC-4.6.4.eb +++ b/test/framework/easyconfigs/GCC-4.6.4.eb @@ -1,3 +1,5 @@ +easyblock = 'EB_GCC' + name = "GCC" version = '4.6.4' diff --git a/test/framework/easyconfigs/GCC-4.7.2.eb b/test/framework/easyconfigs/GCC-4.7.2.eb index 7b4dfcf410..accf6afca8 100644 --- a/test/framework/easyconfigs/GCC-4.7.2.eb +++ b/test/framework/easyconfigs/GCC-4.7.2.eb @@ -1,3 +1,5 @@ +easyblock = 'EB_GCC' + name = "GCC" version = '4.7.2' diff --git a/test/framework/easyconfigs/GCC-4.8.2.eb b/test/framework/easyconfigs/GCC-4.8.2.eb index cef0802601..40b715006c 100644 --- a/test/framework/easyconfigs/GCC-4.8.2.eb +++ b/test/framework/easyconfigs/GCC-4.8.2.eb @@ -1,3 +1,5 @@ +easyblock = 'EB_GCC' + name = "GCC" version = '4.8.2' diff --git a/test/framework/easyconfigs/GCC-4.8.3.eb b/test/framework/easyconfigs/GCC-4.8.3.eb index c62aa710d1..14e91d37d2 100644 --- a/test/framework/easyconfigs/GCC-4.8.3.eb +++ b/test/framework/easyconfigs/GCC-4.8.3.eb @@ -1,3 +1,6 @@ +# should be EB_GCC, but OK for testing purposes +easyblock = 'EB_toy' + name = "GCC" version = '4.8.3' diff --git a/test/framework/easyconfigs/OpenBLAS-0.2.6-gompi-1.4.10-LAPACK-3.4.2.eb b/test/framework/easyconfigs/OpenBLAS-0.2.6-gompi-1.4.10-LAPACK-3.4.2.eb index 8f1b31ff70..bd9785f7cc 100644 --- a/test/framework/easyconfigs/OpenBLAS-0.2.6-gompi-1.4.10-LAPACK-3.4.2.eb +++ b/test/framework/easyconfigs/OpenBLAS-0.2.6-gompi-1.4.10-LAPACK-3.4.2.eb @@ -1,3 +1,5 @@ +easyblock = 'ConfigureMake' + name = 'OpenBLAS' version = '0.2.6' @@ -44,7 +46,7 @@ installopts = threading + " PREFIX=%(installdir)s" sanity_check_paths = { 'files': ['include/cblas.h', 'include/f77blas.h', 'include/lapacke_config.h', 'include/lapacke.h', 'include/lapacke_mangling.h', 'include/lapacke_utils.h', 'include/openblas_config.h', - 'lib/libopenblas.a', 'lib/libopenblas.%s' % shared_lib_ext], + 'lib/libopenblas.a', 'lib/libopenblas.%s' % SHLIB_EXT], 'dirs': [], } diff --git a/test/framework/easyconfigs/OpenMPI-1.6.4-GCC-4.6.4.eb b/test/framework/easyconfigs/OpenMPI-1.6.4-GCC-4.6.4.eb index 053791e834..bd0832e690 100644 --- a/test/framework/easyconfigs/OpenMPI-1.6.4-GCC-4.6.4.eb +++ b/test/framework/easyconfigs/OpenMPI-1.6.4-GCC-4.6.4.eb @@ -1,3 +1,5 @@ +easyblock = 'ConfigureMake' + name = 'OpenMPI' version = '1.6.4' @@ -25,7 +27,7 @@ else: sanity_check_paths = { 'files': ["bin/%s" % binfile for binfile in ["ompi_info", "opal_wrapper", "orterun"]] + - ["lib/lib%s.%s" % (libfile, shared_lib_ext) for libfile in ["mpi_cxx", "mpi_f77", "mpi_f90", + ["lib/lib%s.%s" % (libfile, SHLIB_EXT) for libfile in ["mpi_cxx", "mpi_f77", "mpi_f90", "mpi", "ompitrace", "open-pal", "open-rte", "vt", "vt-hyb", "vt-mpi", "vt-mpi-unify"]] + diff --git a/test/framework/easyconfigs/OpenMPI-1.6.4-GCC-4.7.2.eb b/test/framework/easyconfigs/OpenMPI-1.6.4-GCC-4.7.2.eb index fa3425f1ab..1505eba3ad 100644 --- a/test/framework/easyconfigs/OpenMPI-1.6.4-GCC-4.7.2.eb +++ b/test/framework/easyconfigs/OpenMPI-1.6.4-GCC-4.7.2.eb @@ -1,3 +1,5 @@ +easyblock = 'ConfigureMake' + name = 'OpenMPI' version = '1.6.4' @@ -25,7 +27,7 @@ else: sanity_check_paths = { 'files': ["bin/%s" % binfile for binfile in ["ompi_info", "opal_wrapper", "orterun"]] + - ["lib/lib%s.%s" % (libfile, shared_lib_ext) for libfile in ["mpi_cxx", "mpi_f77", "mpi_f90", + ["lib/lib%s.%s" % (libfile, SHLIB_EXT) for libfile in ["mpi_cxx", "mpi_f77", "mpi_f90", "mpi", "ompitrace", "open-pal", "open-rte", "vt", "vt-hyb", "vt-mpi", "vt-mpi-unify"]] + diff --git a/test/framework/easyconfigs/ScaLAPACK-2.0.2-gompi-1.4.10-OpenBLAS-0.2.6-LAPACK-3.4.2.eb b/test/framework/easyconfigs/ScaLAPACK-2.0.2-gompi-1.4.10-OpenBLAS-0.2.6-LAPACK-3.4.2.eb index 8f7ad295d1..be8a1f73d2 100644 --- a/test/framework/easyconfigs/ScaLAPACK-2.0.2-gompi-1.4.10-OpenBLAS-0.2.6-LAPACK-3.4.2.eb +++ b/test/framework/easyconfigs/ScaLAPACK-2.0.2-gompi-1.4.10-OpenBLAS-0.2.6-LAPACK-3.4.2.eb @@ -1,3 +1,5 @@ +easyblock = 'EB_ScaLAPACK' + name = 'ScaLAPACK' version = '2.0.2' diff --git a/test/framework/easyconfigs/gzip-1.4-GCC-4.6.3.eb b/test/framework/easyconfigs/gzip-1.4-GCC-4.6.3.eb index f47c7482a5..c5f783e816 100644 --- a/test/framework/easyconfigs/gzip-1.4-GCC-4.6.3.eb +++ b/test/framework/easyconfigs/gzip-1.4-GCC-4.6.3.eb @@ -9,6 +9,7 @@ # This work implements a part of the HPCBIOS project and is a component of the policy: # http://hpcbios.readthedocs.org/en/latest/HPCBIOS_06-19.html ## +easyblock = 'ConfigureMake' name = 'gzip' version = '1.4' diff --git a/test/framework/easyconfigs/gzip-1.4.eb b/test/framework/easyconfigs/gzip-1.4.eb index ab769c6b69..c5a94274b3 100644 --- a/test/framework/easyconfigs/gzip-1.4.eb +++ b/test/framework/easyconfigs/gzip-1.4.eb @@ -9,6 +9,7 @@ # This work implements a part of the HPCBIOS project and is a component of the policy: # http://hpcbios.readthedocs.org/en/latest/HPCBIOS_06-19.html ## +easyblock = 'ConfigureMake' name = 'gzip' version = '1.4' diff --git a/test/framework/easyconfigs/gzip-1.5-goolf-1.4.10.eb b/test/framework/easyconfigs/gzip-1.5-goolf-1.4.10.eb index 08ce4ddc61..d1636586c9 100644 --- a/test/framework/easyconfigs/gzip-1.5-goolf-1.4.10.eb +++ b/test/framework/easyconfigs/gzip-1.5-goolf-1.4.10.eb @@ -9,6 +9,7 @@ # This work implements a part of the HPCBIOS project and is a component of the policy: # http://hpcbios.readthedocs.org/en/latest/HPCBIOS_06-19.html ## +easyblock = 'ConfigureMake' name = 'gzip' version = '1.5' diff --git a/test/framework/easyconfigs/gzip-1.5-ictce-4.1.13.eb b/test/framework/easyconfigs/gzip-1.5-ictce-4.1.13.eb index 2552d296e9..9fb11ce6ca 100644 --- a/test/framework/easyconfigs/gzip-1.5-ictce-4.1.13.eb +++ b/test/framework/easyconfigs/gzip-1.5-ictce-4.1.13.eb @@ -9,6 +9,7 @@ # This work implements a part of the HPCBIOS project and is a component of the policy: # http://hpcbios.readthedocs.org/en/latest/HPCBIOS_06-19.html ## +easyblock = 'ConfigureMake' name = 'gzip' version = '1.5' diff --git a/test/framework/easyconfigs/hwloc-1.6.2-GCC-4.6.4.eb b/test/framework/easyconfigs/hwloc-1.6.2-GCC-4.6.4.eb index 116ee4cb8c..3c07bbe614 100644 --- a/test/framework/easyconfigs/hwloc-1.6.2-GCC-4.6.4.eb +++ b/test/framework/easyconfigs/hwloc-1.6.2-GCC-4.6.4.eb @@ -1,3 +1,5 @@ +easyblock = 'ConfigureMake' + name = 'hwloc' version = '1.6.2' diff --git a/test/framework/easyconfigs/hwloc-1.6.2-GCC-4.7.2.eb b/test/framework/easyconfigs/hwloc-1.6.2-GCC-4.7.2.eb index 00a3ce7444..19d34c3d1a 100644 --- a/test/framework/easyconfigs/hwloc-1.6.2-GCC-4.7.2.eb +++ b/test/framework/easyconfigs/hwloc-1.6.2-GCC-4.7.2.eb @@ -1,3 +1,5 @@ +easyblock = 'ConfigureMake' + name = 'hwloc' version = '1.6.2' diff --git a/test/framework/easyconfigs/icc-2013.5.192-GCC-4.8.3.eb b/test/framework/easyconfigs/icc-2013.5.192-GCC-4.8.3.eb index 23b5d632b0..a084c374d0 100644 --- a/test/framework/easyconfigs/icc-2013.5.192-GCC-4.8.3.eb +++ b/test/framework/easyconfigs/icc-2013.5.192-GCC-4.8.3.eb @@ -1,3 +1,6 @@ +# should be EB_icc, but OK for testing purposes +easyblock = 'EB_toy' + name = 'icc' version = '2013.5.192' diff --git a/test/framework/easyconfigs/ifort-2013.3.163.eb b/test/framework/easyconfigs/ifort-2013.3.163.eb index 4efd890d23..b764b92c91 100644 --- a/test/framework/easyconfigs/ifort-2013.3.163.eb +++ b/test/framework/easyconfigs/ifort-2013.3.163.eb @@ -1,3 +1,5 @@ +easyblock = 'EB_ifort' + name = 'ifort' version = '2013.3.163' diff --git a/test/framework/easyconfigs/ifort-2013.5.192-GCC-4.8.3.eb b/test/framework/easyconfigs/ifort-2013.5.192-GCC-4.8.3.eb index cba97b45a2..8a74093865 100644 --- a/test/framework/easyconfigs/ifort-2013.5.192-GCC-4.8.3.eb +++ b/test/framework/easyconfigs/ifort-2013.5.192-GCC-4.8.3.eb @@ -1,3 +1,6 @@ +# should be EB_ifort, but OK for testing purposes +easyblock = 'EB_toy' + name = 'ifort' version = '2013.5.192' diff --git a/test/framework/easyconfigs/imkl-11.1.2.144-iimpi-5.5.3-GCC-4.8.3.eb b/test/framework/easyconfigs/imkl-11.1.2.144-iimpi-5.5.3-GCC-4.8.3.eb index 2114374474..a23cf2c3c9 100644 --- a/test/framework/easyconfigs/imkl-11.1.2.144-iimpi-5.5.3-GCC-4.8.3.eb +++ b/test/framework/easyconfigs/imkl-11.1.2.144-iimpi-5.5.3-GCC-4.8.3.eb @@ -1,3 +1,6 @@ +# should be EB_imkl, but OK for testing purposes +easyblock = 'EB_toy' + name = 'imkl' version = '11.1.2.144' diff --git a/test/framework/easyconfigs/impi-4.1.3.049-iccifort-2013.5.192-GCC-4.8.3.eb b/test/framework/easyconfigs/impi-4.1.3.049-iccifort-2013.5.192-GCC-4.8.3.eb index ddba0fe1d6..e325ca2e75 100644 --- a/test/framework/easyconfigs/impi-4.1.3.049-iccifort-2013.5.192-GCC-4.8.3.eb +++ b/test/framework/easyconfigs/impi-4.1.3.049-iccifort-2013.5.192-GCC-4.8.3.eb @@ -1,3 +1,6 @@ +# should be EB_impi, but OK for testing purposes +easyblock = 'EB_toy' + name = 'impi' version = '4.1.3.049' diff --git a/test/framework/easyconfigs/impi-4.1.3.049.eb b/test/framework/easyconfigs/impi-4.1.3.049.eb index e55725a62a..956f85c717 100644 --- a/test/framework/easyconfigs/impi-4.1.3.049.eb +++ b/test/framework/easyconfigs/impi-4.1.3.049.eb @@ -1,3 +1,5 @@ +easyblock = 'EB_impi' + name = 'impi' version = '4.1.3.049' diff --git a/test/framework/easyconfigs/v1.0/GCC-4.6.3.eb b/test/framework/easyconfigs/v1.0/GCC-4.6.3.eb index 3b4c4c53c9..48cef12da0 100644 --- a/test/framework/easyconfigs/v1.0/GCC-4.6.3.eb +++ b/test/framework/easyconfigs/v1.0/GCC-4.6.3.eb @@ -1,3 +1,5 @@ +easyblock = 'EB_GCC' + name="GCC" version='4.6.3' diff --git a/test/framework/easyconfigs/v1.0/gzip-1.4-GCC-4.6.3.eb b/test/framework/easyconfigs/v1.0/gzip-1.4-GCC-4.6.3.eb index 9f1c615c51..0f8d2efc28 100644 --- a/test/framework/easyconfigs/v1.0/gzip-1.4-GCC-4.6.3.eb +++ b/test/framework/easyconfigs/v1.0/gzip-1.4-GCC-4.6.3.eb @@ -9,6 +9,7 @@ # This work implements a part of the HPCBIOS project and is a component of the policy: # http://hpcbios.readthedocs.org/en/latest/HPCBIOS_06-19.html ## +easyblock = 'ConfigureMake' name = 'gzip' version = '1.4' diff --git a/test/framework/easyconfigs/v1.0/gzip-1.4.eb b/test/framework/easyconfigs/v1.0/gzip-1.4.eb index ab769c6b69..c5a94274b3 100644 --- a/test/framework/easyconfigs/v1.0/gzip-1.4.eb +++ b/test/framework/easyconfigs/v1.0/gzip-1.4.eb @@ -9,6 +9,7 @@ # This work implements a part of the HPCBIOS project and is a component of the policy: # http://hpcbios.readthedocs.org/en/latest/HPCBIOS_06-19.html ## +easyblock = 'ConfigureMake' name = 'gzip' version = '1.4' diff --git a/test/framework/easyconfigs/v1.0/gzip-1.5-goolf-1.4.10.eb b/test/framework/easyconfigs/v1.0/gzip-1.5-goolf-1.4.10.eb index 08ce4ddc61..d1636586c9 100644 --- a/test/framework/easyconfigs/v1.0/gzip-1.5-goolf-1.4.10.eb +++ b/test/framework/easyconfigs/v1.0/gzip-1.5-goolf-1.4.10.eb @@ -9,6 +9,7 @@ # This work implements a part of the HPCBIOS project and is a component of the policy: # http://hpcbios.readthedocs.org/en/latest/HPCBIOS_06-19.html ## +easyblock = 'ConfigureMake' name = 'gzip' version = '1.5' diff --git a/test/framework/easyconfigs/v1.0/gzip-1.5-ictce-4.1.13.eb b/test/framework/easyconfigs/v1.0/gzip-1.5-ictce-4.1.13.eb index 2552d296e9..9fb11ce6ca 100644 --- a/test/framework/easyconfigs/v1.0/gzip-1.5-ictce-4.1.13.eb +++ b/test/framework/easyconfigs/v1.0/gzip-1.5-ictce-4.1.13.eb @@ -9,6 +9,7 @@ # This work implements a part of the HPCBIOS project and is a component of the policy: # http://hpcbios.readthedocs.org/en/latest/HPCBIOS_06-19.html ## +easyblock = 'ConfigureMake' name = 'gzip' version = '1.5' diff --git a/test/framework/easyconfigs/v2.0/GCC.eb b/test/framework/easyconfigs/v2.0/GCC.eb index 6a1baf1bef..fde1551ad2 100644 --- a/test/framework/easyconfigs/v2.0/GCC.eb +++ b/test/framework/easyconfigs/v2.0/GCC.eb @@ -5,6 +5,8 @@ docstring test @author: Stijn De Weirdt (UGent) @maintainer: Kenneth Hoste (UGent) """ +easyblock = 'EB_GCC' + name = "GCC" homepage = 'http://gcc.gnu.org/' diff --git a/test/framework/easyconfigs/v2.0/doesnotexist.eb b/test/framework/easyconfigs/v2.0/doesnotexist.eb index 7067a83a07..7a85355ce6 100644 --- a/test/framework/easyconfigs/v2.0/doesnotexist.eb +++ b/test/framework/easyconfigs/v2.0/doesnotexist.eb @@ -5,6 +5,7 @@ docstring test @author: Stijn De Weirdt (UGent) @maintainer: Kenneth Hoste (UGent) """ +easyblock = 'ConfigureMake' name = 'doesnotexist' diff --git a/test/framework/easyconfigs/v2.0/gzip.eb b/test/framework/easyconfigs/v2.0/gzip.eb index de795268c7..602528e2af 100644 --- a/test/framework/easyconfigs/v2.0/gzip.eb +++ b/test/framework/easyconfigs/v2.0/gzip.eb @@ -41,4 +41,5 @@ versions = 1.4, 1.5 toolchains = dummy == dummy, goolf, GCC == 4.6.3, goolf == 1.4.10, ictce == 4.1.13 [DEFAULT] +easyblock = ConfigureMake moduleclass = base diff --git a/test/framework/easyconfigs/v2.0/libpng.eb b/test/framework/easyconfigs/v2.0/libpng.eb index 1cc9175bca..ecc5bd59f6 100644 --- a/test/framework/easyconfigs/v2.0/libpng.eb +++ b/test/framework/easyconfigs/v2.0/libpng.eb @@ -28,6 +28,7 @@ versions = 1.5.10, 1.5.11, 1.5.13, 1.5.14, 1.6.2, 1.6.3, 1.6.6 toolchains = goolf == 1.4.10, ictce == 4.1.13, goalf == 1.1.0-no-OFED [DEFAULT] +easyblock = ConfigureMake moduleclass = lib [DEPENDENCIES] diff --git a/test/framework/filetools.py b/test/framework/filetools.py index 5a23905947..cff024f9de 100644 --- a/test/framework/filetools.py +++ b/test/framework/filetools.py @@ -51,17 +51,6 @@ class FileToolsTest(EnhancedTestCase): ('0_foo+0x0x#-$__', 'EB_0_underscore_foo_plus_0x0x_hash__minus__dollar__underscore__underscore_'), ] - def setUp(self): - """Set up testcase.""" - super(FileToolsTest, self).setUp() - self.legacySetUp() - - def legacySetUp(self): - self.log.deprecated("legacySetUp", "2.0") - cfg_path = os.path.join('easybuild', 'easybuild_config.py') - cfg_full_path = find_full_path(cfg_path) - self.assertTrue(cfg_full_path) - def test_extract_cmd(self): """Test various extract commands.""" tests = [ diff --git a/test/framework/modules.py b/test/framework/modules.py index 21f0a5407c..059a80604a 100644 --- a/test/framework/modules.py +++ b/test/framework/modules.py @@ -123,6 +123,8 @@ def test_exists(self): self.assertEqual(self.testmods.exist(mod_names), [True, False, False, False, True, True, True]) # test deprecated functionality + os.environ['EASYBUILD_DEPRECATED'] = '1.0' + init_config() self.assertTrue(self.testmods.exists('OpenMPI/1.6.4-GCC-4.6.4')) self.assertFalse(self.testmods.exists('foo/1.2.3')) # exists should not return True for incomplete module names diff --git a/test/framework/options.py b/test/framework/options.py index 9565b1d97c..375c0a89d3 100644 --- a/test/framework/options.py +++ b/test/framework/options.py @@ -33,9 +33,10 @@ import shutil import sys import tempfile -from test.framework.utilities import EnhancedTestCase +from test.framework.utilities import EnhancedTestCase, init_config from unittest import TestLoader from unittest import main as unittestmain +from urllib2 import URLError import easybuild.tools.build_log from easybuild.framework.easyconfig import BUILD, CUSTOM, DEPENDENCIES, EXTENSIONS, FILEMANAGEMENT, LICENSE @@ -103,12 +104,11 @@ def test_no_args(self): def test_debug(self): """Test enabling debug logging.""" - for debug_arg in ['-d', '--debug']: args = [ - '--software-name=somethingrandom', - debug_arg, - ] + 'nosuchfile.eb', + debug_arg, + ] outtxt = self.eb_main(args) for log_msg_type in ['DEBUG', 'INFO', 'ERROR']: @@ -123,7 +123,7 @@ def test_info(self): for info_arg in ['--info']: args = [ - '--software-name=somethingrandom', + 'nosuchfile.eb', info_arg, ] outtxt = self.eb_main(args) @@ -144,7 +144,7 @@ def test_quiet(self): for quiet_arg in ['--quiet']: args = [ - '--software-name=somethingrandom', + 'nosuchfile.eb', quiet_arg, ] outtxt = self.eb_main(args) @@ -749,8 +749,8 @@ def test_from_pr(self): tmpdir = tempfile.mkdtemp() args = [ - # PR for ictce/6.2.5, see https://github.com/hpcugent/easybuild-easyconfigs/pull/726/files - '--from-pr=726', + # PR for intel/2014b, see https://github.com/hpcugent/easybuild-easyconfigs/pull/948/files + '--from-pr=948', '--dry-run', # an argument must be specified to --robot, since easybuild-easyconfigs may not be installed '--robot=%s' % os.path.join(os.path.dirname(__file__), 'easyconfigs'), @@ -758,26 +758,29 @@ def test_from_pr(self): '--github-user=easybuild_test', # a GitHub token should be available for this user '--tmpdir=%s' % tmpdir, ] - outtxt = self.eb_main(args, logfile=dummylogfn, verbose=True) - - modules = [ - 'icc/2013_sp1.2.144', - 'ifort/2013_sp1.2.144', - 'impi/4.1.3.049', - 'imkl/11.1.2.144', - 'ictce/6.2.5', - 'gzip/1.6-ictce-6.2.5', - ] - for module in modules: - ec_fn = "%s.eb" % '-'.join(module.split('/')) - regex = re.compile(r"^ \* \[.\] .*/%s \(module: %s\)$" % (ec_fn, module), re.M) - self.assertTrue(regex.search(outtxt), "Found pattern %s in %s" % (regex.pattern, outtxt)) - - pr_tmpdir = os.path.join(tmpdir, 'easybuild-\S{6}', 'files_pr726') - regex = re.compile("Prepended list of robot search paths with %s:" % pr_tmpdir, re.M) - self.assertTrue(regex.search(outtxt), "Found pattern %s in %s" % (regex.pattern, outtxt)) + try: + outtxt = self.eb_main(args, logfile=dummylogfn, raise_error=True) + + modules = [ + 'HPL/2.1-intel-2014b', + 'GCC/4.8.3', + 'icc/2013.5.192-GCC-4.8.3', + 'ifort/2013.5.192-GCC-4.8.3', + 'imkl/11.1.2.144-2013.5.192-GCC-4.8.3', + 'impi/4.1.3.049-GCC-4.8.3', + 'intel/2014b', + ] + for module in modules: + ec_fn = "%s.eb" % '-'.join(module.split('/')) + regex = re.compile(r"^ \* \[.\] .*/%s \(module: %s\)$" % (ec_fn, module), re.M) + self.assertTrue(regex.search(outtxt), "Found pattern %s in %s" % (regex.pattern, outtxt)) - shutil.rmtree(tmpdir) + pr_tmpdir = os.path.join(tmpdir, 'easybuild-\S{6}', 'files_pr948') + regex = re.compile("Prepended list of robot search paths with %s:" % pr_tmpdir, re.M) + self.assertTrue(regex.search(outtxt), "Found pattern %s in %s" % (regex.pattern, outtxt)) + except URLError, err: + print "Ignoring URLError '%s' in test_from_pr" % err + shutil.rmtree(tmpdir) def test_no_such_software(self): """Test using no arguments.""" @@ -896,6 +899,7 @@ def test_tmpdir(self): def test_ignore_osdeps(self): """Test ignoring of listed OS dependencies.""" txt = '\n'.join([ + 'easyblock = "ConfigureMake"', 'name = "pi"', 'version = "3.14"', 'homepage = "http://example.com"', @@ -976,6 +980,10 @@ def test_experimental(self): def test_deprecated(self): """Test the deprecated option""" + if 'EASYBUILD_DEPRECATED' in os.environ: + os.environ['EASYBUILD_DEPRECATED'] = str(VERSION) + init_config() + orig_value = easybuild.tools.build_log.CURRENT_VERSION # make sure it's off by default @@ -1003,7 +1011,7 @@ def test_deprecated(self): except easybuild.tools.build_log.EasyBuildError, err2: self.assertTrue('DEPRECATED' in str(err2)) - # force higher version by prefixing it with 1, which should result in deprecation + # force higher version by prefixing it with 1, which should result in deprecation errors topt = EasyBuildOptions( go_args=['--deprecated=1%s' % orig_value], ) @@ -1070,10 +1078,15 @@ def test_allow_modules_tool_mismatch(self): def test_try(self): """Test whether --try options are taken into account.""" - ecs_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'easyconfigs') - ec_file = os.path.join(ecs_path, 'toy-0.0.eb') + ecs_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs') + tweaked_toy_ec = os.path.join(self.test_buildpath, 'toy-0.0-tweaked.eb') + shutil.copy2(os.path.join(ecs_path, 'toy-0.0.eb'), tweaked_toy_ec) + f = open(tweaked_toy_ec, 'a') + f.write("easyblock = 'ConfigureMake'") + f.close() + args = [ - ec_file, + tweaked_toy_ec, '--sourcepath=%s' % self.test_sourcepath, '--buildpath=%s' % self.test_buildpath, '--installpath=%s' % self.test_installpath, @@ -1190,7 +1203,7 @@ def test_cleanup_builddir(self): args = [ toy_ec, '--force', - '--try-amend=premakeopts=nosuchcommand &&', + '--try-amend=prebuildopts=nosuchcommand &&', ] self.eb_main(args, do_build=True) self.assertTrue(os.path.exists(toy_buildpath), "Build dir %s is retained after failed build" % toy_buildpath) diff --git a/test/framework/sandbox/easybuild/easyblocks/generic/bar.py b/test/framework/sandbox/easybuild/easyblocks/generic/bar.py index 482ee15cc1..2dcc4c5c01 100644 --- a/test/framework/sandbox/easybuild/easyblocks/generic/bar.py +++ b/test/framework/sandbox/easybuild/easyblocks/generic/bar.py @@ -38,9 +38,8 @@ class bar(EasyBlock): @staticmethod def extra_options(): """Custom easyconfig parameters for bar.""" - - extra_vars = [ - ('bar_extra1', [None, "first bar-specific easyconfig parameter (mandatory)", MANDATORY]), - ('bar_extra2', ['BAR', "second bar-specific easyconfig parameter", CUSTOM]), - ] + extra_vars = { + 'bar_extra1': [None, "first bar-specific easyconfig parameter (mandatory)", MANDATORY], + 'bar_extra2': ['BAR', "second bar-specific easyconfig parameter", CUSTOM], + } return EasyBlock.extra_options(extra_vars) diff --git a/test/framework/sandbox/easybuild/easyblocks/generic/dummyextension.py b/test/framework/sandbox/easybuild/easyblocks/generic/dummyextension.py new file mode 100644 index 0000000000..8024520f32 --- /dev/null +++ b/test/framework/sandbox/easybuild/easyblocks/generic/dummyextension.py @@ -0,0 +1,34 @@ +## +# Copyright 2009-2014 Ghent University +# +# This file is part of EasyBuild, +# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en), +# with support of Ghent University (http://ugent.be/hpc), +# the Flemish Supercomputer Centre (VSC) (https://vscentrum.be/nl/en), +# the Hercules foundation (http://www.herculesstichting.be/in_English) +# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en). +# +# http://github.com/hpcugent/easybuild +# +# EasyBuild is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation v2. +# +# EasyBuild is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with EasyBuild. If not, see . +## +""" +EasyBuild support for building and installing dummy extensions, implemented as an easyblock + +@author: Kenneth Hoste (Ghent University) +""" + +from easybuild.framework.extensioneasyblock import ExtensionEasyBlock + +class DummyExtension(ExtensionEasyBlock): + """Support for building/installing dummy extensions.""" diff --git a/test/framework/sandbox/easybuild/easyblocks/toy.py b/test/framework/sandbox/easybuild/easyblocks/toy.py index 897d248dfe..b03b6d9959 100644 --- a/test/framework/sandbox/easybuild/easyblocks/toy.py +++ b/test/framework/sandbox/easybuild/easyblocks/toy.py @@ -33,8 +33,9 @@ import shutil from easybuild.framework.easyblock import EasyBlock -from easybuild.tools.filetools import mkdir, run_cmd +from easybuild.tools.filetools import mkdir from easybuild.tools.modules import get_software_root, get_software_version +from easybuild.tools.run import run_cmd class EB_toy(EasyBlock): """Support for building/installing toy.""" @@ -61,9 +62,9 @@ def build_step(self, name=None): """Build toy.""" if name is None: name = self.name - run_cmd('%(premakeopts)s gcc %(name)s.c -o %(name)s' % { + run_cmd('%(prebuildopts)s gcc %(name)s.c -o %(name)s' % { 'name': name, - 'premakeopts': self.cfg['premakeopts'], + 'prebuildopts': self.cfg['prebuildopts'], }) def install_step(self, name=None): diff --git a/test/framework/systemtools.py b/test/framework/systemtools.py index 8f9e8a4cf2..20b6b0068d 100644 --- a/test/framework/systemtools.py +++ b/test/framework/systemtools.py @@ -27,7 +27,8 @@ @author: Kenneth hoste (Ghent University) """ -from test.framework.utilities import EnhancedTestCase +import os +from test.framework.utilities import EnhancedTestCase, init_config from unittest import TestLoader, main from easybuild.tools.systemtools import AMD, ARM, DARWIN, INTEL, LINUX, UNKNOWN @@ -40,11 +41,18 @@ class SystemToolsTest(EnhancedTestCase): """ very basis FileRepository test, we don't want git / svn dependency """ - def test_core_count(self): + def test_avail_core_count(self): """Test getting core count.""" - for core_count in [get_avail_core_count(), get_core_count()]: - self.assertTrue(isinstance(core_count, int), "core_count has type int: %s, %s" % (core_count, type(core_count))) - self.assertTrue(core_count > 0, "core_count %d > 0" % core_count) + core_count = get_avail_core_count() + self.assertTrue(isinstance(core_count, int), "core_count has type int: %s, %s" % (core_count, type(core_count))) + self.assertTrue(core_count > 0, "core_count %d > 0" % core_count) + + # also test deprecated get_core_count + os.environ['EASYBUILD_DEPRECATED'] = '1.0' + init_config() + core_count = get_core_count() + self.assertTrue(isinstance(core_count, int), "core_count has type int: %s, %s" % (core_count, type(core_count))) + self.assertTrue(core_count > 0, "core_count %d > 0" % core_count) def test_cpu_model(self): """Test getting CPU model.""" diff --git a/test/framework/utilities.py b/test/framework/utilities.py index e9c84d1e9e..628e0c9181 100644 --- a/test/framework/utilities.py +++ b/test/framework/utilities.py @@ -105,6 +105,11 @@ def setUp(self): os.environ['EASYBUILD_BUILDPATH'] = self.test_buildpath self.test_installpath = tempfile.mkdtemp() os.environ['EASYBUILD_INSTALLPATH'] = self.test_installpath + + # make sure no deprecated behaviour is being triggered (unless intended by the test) + # trip *all* log.deprecated statements by setting deprecation version ridiculously high + os.environ['EASYBUILD_DEPRECATED'] = '10000000' + init_config() # add test easyblocks to Python search path and (re)import and reload easybuild modules From 43f1a3ebb8d67fe59b5423896e78bbf38dfc1563 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 19 Dec 2014 11:24:19 +0100 Subject: [PATCH 237/298] fix generate_software_list.py script w.r.t. deprecated fallback to ConfigureMake --- easybuild/scripts/generate_software_list.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/easybuild/scripts/generate_software_list.py b/easybuild/scripts/generate_software_list.py index 0183ff2c5e..94018d05d6 100644 --- a/easybuild/scripts/generate_software_list.py +++ b/easybuild/scripts/generate_software_list.py @@ -126,12 +126,13 @@ log.info("found valid easyconfig %s" % ec) if not ec.name in names: log.info("found new software package %s" % ec) + ec.easyblock = None # check if an easyblock exists - module = get_easyblock_class(None, name=ec.name).__module__.split('.')[-1] - if module != "configuremake": - ec.easyblock = module - else: - ec.easyblock = None + ebclass = get_easyblock_class(None, name=ec.name, default_fallback=False) + if ebclass is not None: + module = ebclass.__module__.split('.')[-1] + if module != "configuremake": + ec.easyblock = module configs.append(ec) names.append(ec.name) except Exception, err: From d009b63a2c2803f25642a48f950dae4a135de679 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 19 Dec 2014 12:05:14 +0100 Subject: [PATCH 238/298] use EB_toy easyblock in test easyconfigs --- test/framework/easyconfigs/CUDA-5.5.22-GCC-4.8.2.eb | 3 ++- test/framework/easyconfigs/GCC-4.6.3.eb | 3 ++- test/framework/easyconfigs/GCC-4.6.4.eb | 3 ++- test/framework/easyconfigs/GCC-4.7.2.eb | 3 ++- test/framework/easyconfigs/GCC-4.8.2.eb | 3 ++- ...ScaLAPACK-2.0.2-gompi-1.4.10-OpenBLAS-0.2.6-LAPACK-3.4.2.eb | 3 ++- test/framework/easyconfigs/ifort-2013.3.163.eb | 3 ++- test/framework/easyconfigs/impi-4.1.3.049.eb | 3 ++- test/framework/easyconfigs/v1.0/GCC-4.6.3.eb | 3 ++- test/framework/easyconfigs/v2.0/GCC.eb | 3 ++- 10 files changed, 20 insertions(+), 10 deletions(-) diff --git a/test/framework/easyconfigs/CUDA-5.5.22-GCC-4.8.2.eb b/test/framework/easyconfigs/CUDA-5.5.22-GCC-4.8.2.eb index a5503aabe9..00bf4df2f9 100644 --- a/test/framework/easyconfigs/CUDA-5.5.22-GCC-4.8.2.eb +++ b/test/framework/easyconfigs/CUDA-5.5.22-GCC-4.8.2.eb @@ -9,7 +9,8 @@ # This work implements a part of the HPCBIOS project and is a component of the policy: # http://hpcbios.readthedocs.org/en/latest/HPCBIOS_2012-99.html ## -easyblock = 'EB_CUDA' +# should be EB_CUDA, but OK for testing purposes +easyblock = 'EB_toy' name = 'CUDA' version = '5.5.22' diff --git a/test/framework/easyconfigs/GCC-4.6.3.eb b/test/framework/easyconfigs/GCC-4.6.3.eb index 48cef12da0..8f9e3c6a1f 100644 --- a/test/framework/easyconfigs/GCC-4.6.3.eb +++ b/test/framework/easyconfigs/GCC-4.6.3.eb @@ -1,4 +1,5 @@ -easyblock = 'EB_GCC' +# should be EB_GCC, but OK for testing purposes +easyblock = 'EB_toy' name="GCC" version='4.6.3' diff --git a/test/framework/easyconfigs/GCC-4.6.4.eb b/test/framework/easyconfigs/GCC-4.6.4.eb index 2094773f61..baf448818b 100644 --- a/test/framework/easyconfigs/GCC-4.6.4.eb +++ b/test/framework/easyconfigs/GCC-4.6.4.eb @@ -1,4 +1,5 @@ -easyblock = 'EB_GCC' +# should be EB_GCC, but OK for testing purposes +easyblock = 'EB_toy' name = "GCC" version = '4.6.4' diff --git a/test/framework/easyconfigs/GCC-4.7.2.eb b/test/framework/easyconfigs/GCC-4.7.2.eb index accf6afca8..d4b386baae 100644 --- a/test/framework/easyconfigs/GCC-4.7.2.eb +++ b/test/framework/easyconfigs/GCC-4.7.2.eb @@ -1,4 +1,5 @@ -easyblock = 'EB_GCC' +# should be EB_GCC, but OK for testing purposes +easyblock = 'EB_toy' name = "GCC" version = '4.7.2' diff --git a/test/framework/easyconfigs/GCC-4.8.2.eb b/test/framework/easyconfigs/GCC-4.8.2.eb index 40b715006c..a7723b5eb9 100644 --- a/test/framework/easyconfigs/GCC-4.8.2.eb +++ b/test/framework/easyconfigs/GCC-4.8.2.eb @@ -1,4 +1,5 @@ -easyblock = 'EB_GCC' +# should be EB_GCC, but OK for testing purposes +easyblock = 'EB_toy' name = "GCC" version = '4.8.2' diff --git a/test/framework/easyconfigs/ScaLAPACK-2.0.2-gompi-1.4.10-OpenBLAS-0.2.6-LAPACK-3.4.2.eb b/test/framework/easyconfigs/ScaLAPACK-2.0.2-gompi-1.4.10-OpenBLAS-0.2.6-LAPACK-3.4.2.eb index be8a1f73d2..14f049732b 100644 --- a/test/framework/easyconfigs/ScaLAPACK-2.0.2-gompi-1.4.10-OpenBLAS-0.2.6-LAPACK-3.4.2.eb +++ b/test/framework/easyconfigs/ScaLAPACK-2.0.2-gompi-1.4.10-OpenBLAS-0.2.6-LAPACK-3.4.2.eb @@ -1,4 +1,5 @@ -easyblock = 'EB_ScaLAPACK' +# should be EB_ScaLAPACK, but OK for testing purposes +easyblock = 'EB_toy' name = 'ScaLAPACK' version = '2.0.2' diff --git a/test/framework/easyconfigs/ifort-2013.3.163.eb b/test/framework/easyconfigs/ifort-2013.3.163.eb index b764b92c91..09c286af0b 100644 --- a/test/framework/easyconfigs/ifort-2013.3.163.eb +++ b/test/framework/easyconfigs/ifort-2013.3.163.eb @@ -1,4 +1,5 @@ -easyblock = 'EB_ifort' +# should be EB_ifort, but OK for testing purposes +easyblock = 'EB_toy' name = 'ifort' version = '2013.3.163' diff --git a/test/framework/easyconfigs/impi-4.1.3.049.eb b/test/framework/easyconfigs/impi-4.1.3.049.eb index 956f85c717..4267dce6b6 100644 --- a/test/framework/easyconfigs/impi-4.1.3.049.eb +++ b/test/framework/easyconfigs/impi-4.1.3.049.eb @@ -1,4 +1,5 @@ -easyblock = 'EB_impi' +# should be EB_impi, but OK for testing purposes +easyblock = 'EB_toy' name = 'impi' version = '4.1.3.049' diff --git a/test/framework/easyconfigs/v1.0/GCC-4.6.3.eb b/test/framework/easyconfigs/v1.0/GCC-4.6.3.eb index 48cef12da0..8f9e3c6a1f 100644 --- a/test/framework/easyconfigs/v1.0/GCC-4.6.3.eb +++ b/test/framework/easyconfigs/v1.0/GCC-4.6.3.eb @@ -1,4 +1,5 @@ -easyblock = 'EB_GCC' +# should be EB_GCC, but OK for testing purposes +easyblock = 'EB_toy' name="GCC" version='4.6.3' diff --git a/test/framework/easyconfigs/v2.0/GCC.eb b/test/framework/easyconfigs/v2.0/GCC.eb index fde1551ad2..b753004a6e 100644 --- a/test/framework/easyconfigs/v2.0/GCC.eb +++ b/test/framework/easyconfigs/v2.0/GCC.eb @@ -5,7 +5,8 @@ docstring test @author: Stijn De Weirdt (UGent) @maintainer: Kenneth Hoste (UGent) """ -easyblock = 'EB_GCC' +# should be EB_GCC, but OK for testing purposes +easyblock = 'EB_toy' name = "GCC" From e5fcb76c8eab8dc45c87b08647b0c86dbf4910bc Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 19 Dec 2014 12:29:44 +0100 Subject: [PATCH 239/298] fix test_from_pr using #1238, fix remark --- test/framework/easyconfig.py | 2 +- test/framework/options.py | 15 +++++---------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/test/framework/easyconfig.py b/test/framework/easyconfig.py index 3a97e400b4..a11a245938 100644 --- a/test/framework/easyconfig.py +++ b/test/framework/easyconfig.py @@ -171,7 +171,7 @@ def test_deprecated_shared_lib_ext(self): eb = EasyConfig(self.eb_file) self.assertEqual(eb['sanity_check_paths']['files'][0], "lib/lib.%s" % get_shared_lib_ext()) - def test_SHLIB_EXT(self): + def test_shlib_ext(self): """ inside easyconfigs shared_lib_ext should be set """ self.contents = '\n'.join([ 'easyblock = "ConfigureMake"', diff --git a/test/framework/options.py b/test/framework/options.py index 375c0a89d3..3d317e859d 100644 --- a/test/framework/options.py +++ b/test/framework/options.py @@ -749,8 +749,8 @@ def test_from_pr(self): tmpdir = tempfile.mkdtemp() args = [ - # PR for intel/2014b, see https://github.com/hpcugent/easybuild-easyconfigs/pull/948/files - '--from-pr=948', + # PR for intel/2014b, see https://github.com/hpcugent/easybuild-easyconfigs/pull/1238/files + '--from-pr=1238', '--dry-run', # an argument must be specified to --robot, since easybuild-easyconfigs may not be installed '--robot=%s' % os.path.join(os.path.dirname(__file__), 'easyconfigs'), @@ -762,20 +762,15 @@ def test_from_pr(self): outtxt = self.eb_main(args, logfile=dummylogfn, raise_error=True) modules = [ - 'HPL/2.1-intel-2014b', - 'GCC/4.8.3', - 'icc/2013.5.192-GCC-4.8.3', - 'ifort/2013.5.192-GCC-4.8.3', - 'imkl/11.1.2.144-2013.5.192-GCC-4.8.3', - 'impi/4.1.3.049-GCC-4.8.3', - 'intel/2014b', + 'HPL/2.1-intel-2015a', + 'intel/2015a', ] for module in modules: ec_fn = "%s.eb" % '-'.join(module.split('/')) regex = re.compile(r"^ \* \[.\] .*/%s \(module: %s\)$" % (ec_fn, module), re.M) self.assertTrue(regex.search(outtxt), "Found pattern %s in %s" % (regex.pattern, outtxt)) - pr_tmpdir = os.path.join(tmpdir, 'easybuild-\S{6}', 'files_pr948') + pr_tmpdir = os.path.join(tmpdir, 'easybuild-\S{6}', 'files_pr1238') regex = re.compile("Prepended list of robot search paths with %s:" % pr_tmpdir, re.M) self.assertTrue(regex.search(outtxt), "Found pattern %s in %s" % (regex.pattern, outtxt)) except URLError, err: From 75b09ca13789eda903345fbd179890197351189f Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 19 Dec 2014 13:32:01 +0100 Subject: [PATCH 240/298] really fix test_from_pr using #1239 --- test/framework/easyconfigs/GCC-4.9.2.eb | 36 ++++++++++ test/framework/options.py | 87 +++++++++++++------------ 2 files changed, 83 insertions(+), 40 deletions(-) create mode 100644 test/framework/easyconfigs/GCC-4.9.2.eb diff --git a/test/framework/easyconfigs/GCC-4.9.2.eb b/test/framework/easyconfigs/GCC-4.9.2.eb new file mode 100644 index 0000000000..ec651b931d --- /dev/null +++ b/test/framework/easyconfigs/GCC-4.9.2.eb @@ -0,0 +1,36 @@ +# should be EB_GCC, but OK for testing purposes +easyblock = 'EB_toy' + +name = "GCC" +version = '4.9.2' + +homepage = 'http://gcc.gnu.org/' +description = """The GNU Compiler Collection includes front ends for C, C++, Objective-C, Fortran, Java, and Ada, + as well as libraries for these languages (libstdc++, libgcj,...).""" + +toolchain = {'name': 'dummy', 'version': 'dummy'} + +source_urls = [ + 'http://ftpmirror.gnu.org/%(namelower)s/%(namelower)s-%(version)s', # GCC auto-resolving HTTP mirror + 'http://ftpmirror.gnu.org/gmp', # idem for GMP + 'http://ftpmirror.gnu.org/mpfr', # idem for MPFR + 'http://www.multiprecision.org/mpc/download', # MPC official +] + +mpfr_version = '3.1.2' + +sources = [ + SOURCELOWER_TAR_BZ2, + 'gmp-6.0.0a.tar.bz2', + 'mpfr-%s.tar.gz' % mpfr_version, + 'mpc-1.0.2.tar.gz', +] + +patches = [('mpfr-%s-allpatches-20140630.patch' % mpfr_version, '../mpfr-%s' % mpfr_version)] + +languages = ['c', 'c++', 'fortran', 'lto'] + +# building GCC sometimes fails if make parallelism is too high, so let's limit it +maxparallel = 4 + +moduleclass = 'compiler' diff --git a/test/framework/options.py b/test/framework/options.py index 3d317e859d..f3c1739e11 100644 --- a/test/framework/options.py +++ b/test/framework/options.py @@ -54,7 +54,7 @@ class CommandLineOptionsTest(EnhancedTestCase): logfile = None - def test_help_short(self, txt=None): + def xtest_help_short(self, txt=None): """Test short help message.""" if txt is None: @@ -76,7 +76,7 @@ def test_help_short(self, txt=None): self.assertEqual(re.search("Software search and build options", outtxt), None, "Not all option groups included in short help (1)") self.assertEqual(re.search("Regression test options", outtxt), None, "Not all option groups included in short help (2)") - def test_help_long(self): + def xtest_help_long(self): """Test long help message.""" topt = EasyBuildOptions( @@ -93,7 +93,7 @@ def test_help_long(self): self.assertTrue(re.search("Software search and build options", outtxt), "Not all option groups included in short help (1)") self.assertTrue(re.search("Regression test options", outtxt), "Not all option groups included in short help (2)") - def test_no_args(self): + def xtest_no_args(self): """Test using no arguments.""" outtxt = self.eb_main([]) @@ -102,7 +102,7 @@ def test_no_args(self): error_msg += " or use software build options to make EasyBuild search for easyconfigs" self.assertTrue(re.search(error_msg, outtxt), "Error message when eb is run without arguments") - def test_debug(self): + def xtest_debug(self): """Test enabling debug logging.""" for debug_arg in ['-d', '--debug']: args = [ @@ -118,7 +118,7 @@ def test_debug(self): modify_env(os.environ, self.orig_environ) tempfile.tempdir = None - def test_info(self): + def xtest_info(self): """Test enabling info logging.""" for info_arg in ['--info']: @@ -139,7 +139,7 @@ def test_info(self): modify_env(os.environ, self.orig_environ) tempfile.tempdir = None - def test_quiet(self): + def xtest_quiet(self): """Test enabling quiet logging (errors only).""" for quiet_arg in ['--quiet']: @@ -160,7 +160,7 @@ def test_quiet(self): modify_env(os.environ, self.orig_environ) tempfile.tempdir = None - def test_force(self): + def xtest_force(self): """Test forcing installation even if the module is already available.""" # use GCC-4.6.3.eb easyconfig file that comes with the tests @@ -193,7 +193,7 @@ def test_force(self): self.assertTrue(not re.search(already_msg, outtxt), "Already installed message not there with --force") - def test_skip(self): + def xtest_skip(self): """Test skipping installation of module (--skip, -k).""" # use toy-0.0.eb easyconfig file that comes with the tests @@ -255,7 +255,7 @@ def test_skip(self): modify_env(os.environ, self.orig_environ) modules_tool() - def test_job(self): + def xtest_job(self): """Test submitting build as a job.""" # use gzip-1.4.eb easyconfig file that comes with the tests @@ -291,7 +291,7 @@ def check_args(job_args, passed_args=None): # 'zzz' prefix in the test name is intentional to make this test run last, # since it fiddles with the logging infrastructure which may break things - def test_zzz_logtostdout(self): + def xtest_zzz_logtostdout(self): """Testing redirecting log to stdout.""" fd, dummylogfn = tempfile.mkstemp(prefix='easybuild-dummy', suffix='.log') @@ -331,7 +331,7 @@ def test_zzz_logtostdout(self): os.remove(dummylogfn) fancylogger.logToFile(self.logfile) - def test_avail_easyconfig_params(self): + def xtest_avail_easyconfig_params(self): """Test listing available easyconfig parameters.""" def run_test(custom=None, extra_params=[]): @@ -389,7 +389,7 @@ def run_test(custom=None, extra_params=[]): # double underscore to make sure it runs first, which is required to detect certain types of bugs, # e.g. running with non-initialized EasyBuild config (truly mimicing 'eb --list-toolchains') - def test__list_toolchains(self): + def xtest__list_toolchains(self): """Test listing known compiler toolchains.""" fd, dummylogfn = tempfile.mkstemp(prefix='easybuild-dummy', suffix='.log') @@ -421,7 +421,7 @@ def test__list_toolchains(self): if os.path.exists(dummylogfn): os.remove(dummylogfn) - def test_avail_lists(self): + def xtest_avail_lists(self): """Test listing available values of certain types.""" fd, dummylogfn = tempfile.mkstemp(prefix='easybuild-dummy', suffix='.log') @@ -454,7 +454,7 @@ def test_avail_lists(self): if os.path.exists(dummylogfn): os.remove(dummylogfn) - def test_avail_cfgfile_constants(self): + def xtest_avail_cfgfile_constants(self): """Test --avail-cfgfile-constants.""" fd, dummylogfn = tempfile.mkstemp(prefix='easybuild-dummy', suffix='.log') os.close(fd) @@ -487,7 +487,7 @@ def test_avail_cfgfile_constants(self): os.remove(dummylogfn) sys.path[:] = orig_sys_path - def test_list_easyblocks(self): + def xtest_list_easyblocks(self): """Test listing easyblock hierarchy.""" fd, dummylogfn = tempfile.mkstemp(prefix='easybuild-dummy', suffix='.log') @@ -549,7 +549,7 @@ def test_list_easyblocks(self): if os.path.exists(dummylogfn): os.remove(dummylogfn) - def test_search(self): + def xtest_search(self): """Test searching for easyconfigs.""" fd, dummylogfn = tempfile.mkstemp(prefix='easybuild-dummy', suffix='.log') @@ -590,7 +590,7 @@ def test_search(self): if os.path.exists(dummylogfn): os.remove(dummylogfn) - def test_dry_run(self): + def xtest_dry_run(self): """Test dry run (long format).""" fd, dummylogfn = tempfile.mkstemp(prefix='easybuild-dummy', suffix='.log') os.close(fd) @@ -613,7 +613,7 @@ def test_dry_run(self): regex = re.compile(r" \* \[%s\] \S+%s \(module: %s\)" % (mark, ec, mod), re.M) self.assertTrue(regex.search(outtxt), "Found match for pattern %s in '%s'" % (regex.pattern, outtxt)) - def test_dry_run_short(self): + def xtest_dry_run_short(self): """Test dry run (short format).""" fd, dummylogfn = tempfile.mkstemp(prefix='easybuild-dummy', suffix='.log') os.close(fd) @@ -658,7 +658,7 @@ def test_dry_run_short(self): shutil.rmtree(tmpdir) sys.path[:] = orig_sys_path - def test_try_robot_force(self): + def xtest_try_robot_force(self): """ Test correct behavior for combination of --try-toolchain --robot --force. Only the listed easyconfigs should be forced, resolved dependencies should not (even if tweaked). @@ -702,7 +702,7 @@ def test_try_robot_force(self): regex = re.compile("^ \* \[%s\] \S+%s \(module: %s\)$" % (mark, ec, mod), re.M) self.assertTrue(regex.search(outtxt), "Found match for pattern %s in '%s'" % (regex.pattern, outtxt)) - def test_dry_run_hierarchical(self): + def xtest_dry_run_hierarchical(self): """Test dry run using a hierarchical module naming scheme.""" fd, dummylogfn = tempfile.mkstemp(prefix='easybuild-dummy', suffix='.log') os.close(fd) @@ -749,8 +749,8 @@ def test_from_pr(self): tmpdir = tempfile.mkdtemp() args = [ - # PR for intel/2014b, see https://github.com/hpcugent/easybuild-easyconfigs/pull/1238/files - '--from-pr=1238', + # PR for intel/2014b, see https://github.com/hpcugent/easybuild-easyconfigs/pull/1239/files + '--from-pr=1239', '--dry-run', # an argument must be specified to --robot, since easybuild-easyconfigs may not be installed '--robot=%s' % os.path.join(os.path.dirname(__file__), 'easyconfigs'), @@ -760,24 +760,31 @@ def test_from_pr(self): ] try: outtxt = self.eb_main(args, logfile=dummylogfn, raise_error=True) - modules = [ - 'HPL/2.1-intel-2015a', - 'intel/2015a', + 'FFTW/3.3.4-gompi-2015a', + 'foss/2015a', + 'GCC/4.9.2', + 'gompi/2015a', + 'HPL/2.1-foss-2015a', + 'hwloc/1.10.0-GCC-4.9.2', + 'numactl/2.0.10-GCC-4.9.2', + 'OpenBLAS/0.2.13-GCC-4.9.2-LAPACK-3.5.0', + 'OpenMPI/1.8.3-GCC-4.9.2', + 'ScaLAPACK/2.0.2-gompi-2015a-OpenBLAS-0.2.13-LAPACK-3.5.0', ] for module in modules: ec_fn = "%s.eb" % '-'.join(module.split('/')) regex = re.compile(r"^ \* \[.\] .*/%s \(module: %s\)$" % (ec_fn, module), re.M) self.assertTrue(regex.search(outtxt), "Found pattern %s in %s" % (regex.pattern, outtxt)) - pr_tmpdir = os.path.join(tmpdir, 'easybuild-\S{6}', 'files_pr1238') + pr_tmpdir = os.path.join(tmpdir, 'easybuild-\S{6}', 'files_pr1239') regex = re.compile("Prepended list of robot search paths with %s:" % pr_tmpdir, re.M) self.assertTrue(regex.search(outtxt), "Found pattern %s in %s" % (regex.pattern, outtxt)) except URLError, err: print "Ignoring URLError '%s' in test_from_pr" % err shutil.rmtree(tmpdir) - def test_no_such_software(self): + def xtest_no_such_software(self): """Test using no arguments.""" args = [ @@ -794,7 +801,7 @@ def test_no_such_software(self): msg = "Error message when eb can't find software with specified name (outtxt: %s)" % outtxt self.assertTrue(re.search(error_msg1, outtxt) or re.search(error_msg2, outtxt), msg) - def test_footer(self): + def xtest_footer(self): """Test specifying a module footer.""" # create file containing modules footer @@ -832,7 +839,7 @@ def test_footer(self): # cleanup os.remove(modules_footer) - def test_recursive_module_unload(self): + def xtest_recursive_module_unload(self): """Test generating recursively unloading modules.""" # use toy-0.0.eb easyconfig file that comes with the tests @@ -855,7 +862,7 @@ def test_recursive_module_unload(self): is_loaded_regex = re.compile(r"if { !\[is-loaded gompi/1.3.12\] }", re.M) self.assertFalse(is_loaded_regex.search(toy_module_txt), "Recursive unloading is used: %s" % toy_module_txt) - def test_tmpdir(self): + def xtest_tmpdir(self): """Test setting temporary directory to use by EasyBuild.""" # use temporary paths for build/install paths, make sure sources can be found @@ -891,7 +898,7 @@ def test_tmpdir(self): os.close(fd) shutil.rmtree(tmpdir) - def test_ignore_osdeps(self): + def xtest_ignore_osdeps(self): """Test ignoring of listed OS dependencies.""" txt = '\n'.join([ 'easyblock = "ConfigureMake"', @@ -941,7 +948,7 @@ def test_ignore_osdeps(self): regex = re.compile("stop provided 'notavalidstop' is not valid", re.M) self.assertTrue(regex.search(outtxt), "Validations are performed with --ignore-osdeps, outtxt: %s" % outtxt) - def test_experimental(self): + def xtest_experimental(self): """Test the experimental option""" orig_value = easybuild.tools.build_log.EXPERIMENTAL # make sure it's off by default @@ -973,7 +980,7 @@ def test_experimental(self): # set it back easybuild.tools.build_log.EXPERIMENTAL = orig_value - def test_deprecated(self): + def xtest_deprecated(self): """Test the deprecated option""" if 'EASYBUILD_DEPRECATED' in os.environ: os.environ['EASYBUILD_DEPRECATED'] = str(VERSION) @@ -1020,7 +1027,7 @@ def test_deprecated(self): # set it back easybuild.tools.build_log.CURRENT_VERSION = orig_value - def test_allow_modules_tool_mismatch(self): + def xtest_allow_modules_tool_mismatch(self): """Test allowing mismatch of modules tool with 'module' function.""" # make sure MockModulesTool is available from test.framework.modulestool import MockModulesTool @@ -1071,7 +1078,7 @@ def test_allow_modules_tool_mismatch(self): else: del os.environ['module'] - def test_try(self): + def xtest_try(self): """Test whether --try options are taken into account.""" ecs_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs') tweaked_toy_ec = os.path.join(self.test_buildpath, 'toy-0.0-tweaked.eb') @@ -1116,7 +1123,7 @@ def test_try(self): allargs = args + ['--software-version=1.2.3', '--toolchain=gompi,1.4.10'] self.assertErrorRegex(EasyBuildError, "version .* not available", self.eb_main, allargs, raise_error=True) - def test_recursive_try(self): + def xtest_recursive_try(self): """Test whether recursive --try-X works.""" ecs_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs') tweaked_toy_ec = os.path.join(self.test_buildpath, 'toy-0.0-tweaked.eb') @@ -1175,7 +1182,7 @@ def test_recursive_try(self): mod_regex = re.compile("\(module: %s\)$" % mod, re.M) self.assertFalse(mod_regex.search(outtxt), "Pattern %s found in %s" % (mod_regex.pattern, outtxt)) - def test_cleanup_builddir(self): + def xtest_cleanup_builddir(self): """Test cleaning up of build dir and --disable-cleanup-builddir.""" toy_ec = os.path.join(os.path.dirname(__file__), 'easyconfigs', 'toy-0.0.eb') toy_buildpath = os.path.join(self.test_buildpath, 'toy', '0.0', 'dummy-dummy') @@ -1203,7 +1210,7 @@ def test_cleanup_builddir(self): self.eb_main(args, do_build=True) self.assertTrue(os.path.exists(toy_buildpath), "Build dir %s is retained after failed build" % toy_buildpath) - def test_filter_deps(self): + def xtest_filter_deps(self): """Test use of --filter-deps.""" test_dir = os.path.dirname(os.path.abspath(__file__)) ec_file = os.path.join(test_dir, 'easyconfigs', 'goolf-1.4.10.eb') @@ -1230,7 +1237,7 @@ def test_filter_deps(self): self.assertFalse(re.search('module: ScaLAPACK/2.0.2-gompi', outtxt)) self.assertFalse(re.search('module: zlib', outtxt)) - def test_test_report_env_filter(self): + def xtest_test_report_env_filter(self): """Test use of --test-report-env-filter.""" def toy(extra_args=None): @@ -1279,7 +1286,7 @@ def toy(extra_args=None): tup = (filter_arg_regex.pattern, test_report_txt) self.assertTrue(filter_arg_regex.search(test_report_txt), "%s in %s" % tup) - def test_robot(self): + def xtest_robot(self): """Test --robot and --robot-paths command line options.""" test_ecs_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs') eb_file = os.path.join(test_ecs_path, 'gzip-1.4-GCC-4.6.3.eb') # includes 'toy/.0.0-deps' as a dependency From a4d509a75c32a88db18818ae645d5e71d7ad5b77 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 19 Dec 2014 13:32:42 +0100 Subject: [PATCH 241/298] reenable disabled tests --- test/framework/options.py | 68 +++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/test/framework/options.py b/test/framework/options.py index f3c1739e11..728156d5e4 100644 --- a/test/framework/options.py +++ b/test/framework/options.py @@ -54,7 +54,7 @@ class CommandLineOptionsTest(EnhancedTestCase): logfile = None - def xtest_help_short(self, txt=None): + def test_help_short(self, txt=None): """Test short help message.""" if txt is None: @@ -76,7 +76,7 @@ def xtest_help_short(self, txt=None): self.assertEqual(re.search("Software search and build options", outtxt), None, "Not all option groups included in short help (1)") self.assertEqual(re.search("Regression test options", outtxt), None, "Not all option groups included in short help (2)") - def xtest_help_long(self): + def test_help_long(self): """Test long help message.""" topt = EasyBuildOptions( @@ -93,7 +93,7 @@ def xtest_help_long(self): self.assertTrue(re.search("Software search and build options", outtxt), "Not all option groups included in short help (1)") self.assertTrue(re.search("Regression test options", outtxt), "Not all option groups included in short help (2)") - def xtest_no_args(self): + def test_no_args(self): """Test using no arguments.""" outtxt = self.eb_main([]) @@ -102,7 +102,7 @@ def xtest_no_args(self): error_msg += " or use software build options to make EasyBuild search for easyconfigs" self.assertTrue(re.search(error_msg, outtxt), "Error message when eb is run without arguments") - def xtest_debug(self): + def test_debug(self): """Test enabling debug logging.""" for debug_arg in ['-d', '--debug']: args = [ @@ -118,7 +118,7 @@ def xtest_debug(self): modify_env(os.environ, self.orig_environ) tempfile.tempdir = None - def xtest_info(self): + def test_info(self): """Test enabling info logging.""" for info_arg in ['--info']: @@ -139,7 +139,7 @@ def xtest_info(self): modify_env(os.environ, self.orig_environ) tempfile.tempdir = None - def xtest_quiet(self): + def test_quiet(self): """Test enabling quiet logging (errors only).""" for quiet_arg in ['--quiet']: @@ -160,7 +160,7 @@ def xtest_quiet(self): modify_env(os.environ, self.orig_environ) tempfile.tempdir = None - def xtest_force(self): + def test_force(self): """Test forcing installation even if the module is already available.""" # use GCC-4.6.3.eb easyconfig file that comes with the tests @@ -193,7 +193,7 @@ def xtest_force(self): self.assertTrue(not re.search(already_msg, outtxt), "Already installed message not there with --force") - def xtest_skip(self): + def test_skip(self): """Test skipping installation of module (--skip, -k).""" # use toy-0.0.eb easyconfig file that comes with the tests @@ -255,7 +255,7 @@ def xtest_skip(self): modify_env(os.environ, self.orig_environ) modules_tool() - def xtest_job(self): + def test_job(self): """Test submitting build as a job.""" # use gzip-1.4.eb easyconfig file that comes with the tests @@ -291,7 +291,7 @@ def check_args(job_args, passed_args=None): # 'zzz' prefix in the test name is intentional to make this test run last, # since it fiddles with the logging infrastructure which may break things - def xtest_zzz_logtostdout(self): + def test_zzz_logtostdout(self): """Testing redirecting log to stdout.""" fd, dummylogfn = tempfile.mkstemp(prefix='easybuild-dummy', suffix='.log') @@ -331,7 +331,7 @@ def xtest_zzz_logtostdout(self): os.remove(dummylogfn) fancylogger.logToFile(self.logfile) - def xtest_avail_easyconfig_params(self): + def test_avail_easyconfig_params(self): """Test listing available easyconfig parameters.""" def run_test(custom=None, extra_params=[]): @@ -389,7 +389,7 @@ def run_test(custom=None, extra_params=[]): # double underscore to make sure it runs first, which is required to detect certain types of bugs, # e.g. running with non-initialized EasyBuild config (truly mimicing 'eb --list-toolchains') - def xtest__list_toolchains(self): + def test__list_toolchains(self): """Test listing known compiler toolchains.""" fd, dummylogfn = tempfile.mkstemp(prefix='easybuild-dummy', suffix='.log') @@ -421,7 +421,7 @@ def xtest__list_toolchains(self): if os.path.exists(dummylogfn): os.remove(dummylogfn) - def xtest_avail_lists(self): + def test_avail_lists(self): """Test listing available values of certain types.""" fd, dummylogfn = tempfile.mkstemp(prefix='easybuild-dummy', suffix='.log') @@ -454,7 +454,7 @@ def xtest_avail_lists(self): if os.path.exists(dummylogfn): os.remove(dummylogfn) - def xtest_avail_cfgfile_constants(self): + def test_avail_cfgfile_constants(self): """Test --avail-cfgfile-constants.""" fd, dummylogfn = tempfile.mkstemp(prefix='easybuild-dummy', suffix='.log') os.close(fd) @@ -487,7 +487,7 @@ def xtest_avail_cfgfile_constants(self): os.remove(dummylogfn) sys.path[:] = orig_sys_path - def xtest_list_easyblocks(self): + def test_list_easyblocks(self): """Test listing easyblock hierarchy.""" fd, dummylogfn = tempfile.mkstemp(prefix='easybuild-dummy', suffix='.log') @@ -549,7 +549,7 @@ def xtest_list_easyblocks(self): if os.path.exists(dummylogfn): os.remove(dummylogfn) - def xtest_search(self): + def test_search(self): """Test searching for easyconfigs.""" fd, dummylogfn = tempfile.mkstemp(prefix='easybuild-dummy', suffix='.log') @@ -590,7 +590,7 @@ def xtest_search(self): if os.path.exists(dummylogfn): os.remove(dummylogfn) - def xtest_dry_run(self): + def test_dry_run(self): """Test dry run (long format).""" fd, dummylogfn = tempfile.mkstemp(prefix='easybuild-dummy', suffix='.log') os.close(fd) @@ -613,7 +613,7 @@ def xtest_dry_run(self): regex = re.compile(r" \* \[%s\] \S+%s \(module: %s\)" % (mark, ec, mod), re.M) self.assertTrue(regex.search(outtxt), "Found match for pattern %s in '%s'" % (regex.pattern, outtxt)) - def xtest_dry_run_short(self): + def test_dry_run_short(self): """Test dry run (short format).""" fd, dummylogfn = tempfile.mkstemp(prefix='easybuild-dummy', suffix='.log') os.close(fd) @@ -658,7 +658,7 @@ def xtest_dry_run_short(self): shutil.rmtree(tmpdir) sys.path[:] = orig_sys_path - def xtest_try_robot_force(self): + def test_try_robot_force(self): """ Test correct behavior for combination of --try-toolchain --robot --force. Only the listed easyconfigs should be forced, resolved dependencies should not (even if tweaked). @@ -702,7 +702,7 @@ def xtest_try_robot_force(self): regex = re.compile("^ \* \[%s\] \S+%s \(module: %s\)$" % (mark, ec, mod), re.M) self.assertTrue(regex.search(outtxt), "Found match for pattern %s in '%s'" % (regex.pattern, outtxt)) - def xtest_dry_run_hierarchical(self): + def test_dry_run_hierarchical(self): """Test dry run using a hierarchical module naming scheme.""" fd, dummylogfn = tempfile.mkstemp(prefix='easybuild-dummy', suffix='.log') os.close(fd) @@ -784,7 +784,7 @@ def test_from_pr(self): print "Ignoring URLError '%s' in test_from_pr" % err shutil.rmtree(tmpdir) - def xtest_no_such_software(self): + def test_no_such_software(self): """Test using no arguments.""" args = [ @@ -801,7 +801,7 @@ def xtest_no_such_software(self): msg = "Error message when eb can't find software with specified name (outtxt: %s)" % outtxt self.assertTrue(re.search(error_msg1, outtxt) or re.search(error_msg2, outtxt), msg) - def xtest_footer(self): + def test_footer(self): """Test specifying a module footer.""" # create file containing modules footer @@ -839,7 +839,7 @@ def xtest_footer(self): # cleanup os.remove(modules_footer) - def xtest_recursive_module_unload(self): + def test_recursive_module_unload(self): """Test generating recursively unloading modules.""" # use toy-0.0.eb easyconfig file that comes with the tests @@ -862,7 +862,7 @@ def xtest_recursive_module_unload(self): is_loaded_regex = re.compile(r"if { !\[is-loaded gompi/1.3.12\] }", re.M) self.assertFalse(is_loaded_regex.search(toy_module_txt), "Recursive unloading is used: %s" % toy_module_txt) - def xtest_tmpdir(self): + def test_tmpdir(self): """Test setting temporary directory to use by EasyBuild.""" # use temporary paths for build/install paths, make sure sources can be found @@ -898,7 +898,7 @@ def xtest_tmpdir(self): os.close(fd) shutil.rmtree(tmpdir) - def xtest_ignore_osdeps(self): + def test_ignore_osdeps(self): """Test ignoring of listed OS dependencies.""" txt = '\n'.join([ 'easyblock = "ConfigureMake"', @@ -948,7 +948,7 @@ def xtest_ignore_osdeps(self): regex = re.compile("stop provided 'notavalidstop' is not valid", re.M) self.assertTrue(regex.search(outtxt), "Validations are performed with --ignore-osdeps, outtxt: %s" % outtxt) - def xtest_experimental(self): + def test_experimental(self): """Test the experimental option""" orig_value = easybuild.tools.build_log.EXPERIMENTAL # make sure it's off by default @@ -980,7 +980,7 @@ def xtest_experimental(self): # set it back easybuild.tools.build_log.EXPERIMENTAL = orig_value - def xtest_deprecated(self): + def test_deprecated(self): """Test the deprecated option""" if 'EASYBUILD_DEPRECATED' in os.environ: os.environ['EASYBUILD_DEPRECATED'] = str(VERSION) @@ -1027,7 +1027,7 @@ def xtest_deprecated(self): # set it back easybuild.tools.build_log.CURRENT_VERSION = orig_value - def xtest_allow_modules_tool_mismatch(self): + def test_allow_modules_tool_mismatch(self): """Test allowing mismatch of modules tool with 'module' function.""" # make sure MockModulesTool is available from test.framework.modulestool import MockModulesTool @@ -1078,7 +1078,7 @@ def xtest_allow_modules_tool_mismatch(self): else: del os.environ['module'] - def xtest_try(self): + def test_try(self): """Test whether --try options are taken into account.""" ecs_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs') tweaked_toy_ec = os.path.join(self.test_buildpath, 'toy-0.0-tweaked.eb') @@ -1123,7 +1123,7 @@ def xtest_try(self): allargs = args + ['--software-version=1.2.3', '--toolchain=gompi,1.4.10'] self.assertErrorRegex(EasyBuildError, "version .* not available", self.eb_main, allargs, raise_error=True) - def xtest_recursive_try(self): + def test_recursive_try(self): """Test whether recursive --try-X works.""" ecs_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs') tweaked_toy_ec = os.path.join(self.test_buildpath, 'toy-0.0-tweaked.eb') @@ -1182,7 +1182,7 @@ def xtest_recursive_try(self): mod_regex = re.compile("\(module: %s\)$" % mod, re.M) self.assertFalse(mod_regex.search(outtxt), "Pattern %s found in %s" % (mod_regex.pattern, outtxt)) - def xtest_cleanup_builddir(self): + def test_cleanup_builddir(self): """Test cleaning up of build dir and --disable-cleanup-builddir.""" toy_ec = os.path.join(os.path.dirname(__file__), 'easyconfigs', 'toy-0.0.eb') toy_buildpath = os.path.join(self.test_buildpath, 'toy', '0.0', 'dummy-dummy') @@ -1210,7 +1210,7 @@ def xtest_cleanup_builddir(self): self.eb_main(args, do_build=True) self.assertTrue(os.path.exists(toy_buildpath), "Build dir %s is retained after failed build" % toy_buildpath) - def xtest_filter_deps(self): + def test_filter_deps(self): """Test use of --filter-deps.""" test_dir = os.path.dirname(os.path.abspath(__file__)) ec_file = os.path.join(test_dir, 'easyconfigs', 'goolf-1.4.10.eb') @@ -1237,7 +1237,7 @@ def xtest_filter_deps(self): self.assertFalse(re.search('module: ScaLAPACK/2.0.2-gompi', outtxt)) self.assertFalse(re.search('module: zlib', outtxt)) - def xtest_test_report_env_filter(self): + def test_test_report_env_filter(self): """Test use of --test-report-env-filter.""" def toy(extra_args=None): @@ -1286,7 +1286,7 @@ def toy(extra_args=None): tup = (filter_arg_regex.pattern, test_report_txt) self.assertTrue(filter_arg_regex.search(test_report_txt), "%s in %s" % tup) - def xtest_robot(self): + def test_robot(self): """Test --robot and --robot-paths command line options.""" test_ecs_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs') eb_file = os.path.join(test_ecs_path, 'gzip-1.4-GCC-4.6.3.eb') # includes 'toy/.0.0-deps' as a dependency From 80033f62d275960c264897476f7947f147d6c350 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 19 Dec 2014 13:42:40 +0100 Subject: [PATCH 242/298] bump to v1.16.1 + update release notes --- RELEASE_NOTES | 11 +++++++++++ easybuild/tools/version.py | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/RELEASE_NOTES b/RELEASE_NOTES index 3f94327924..13ef17e9cc 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -3,6 +3,17 @@ For more detailed information, please see the git log. These release notes can also be consulted at http://easybuild.readthedocs.org/en/latest/Release_notes.html. +v1.16.1 (December 19th 2014) +---------------------------- + +bugfix release +- fix functionality that is broken with --deprecated=2.0 or with $EASYBUILD_DEPRECATED=2.0 + - don't include easyconfig parameters for ConfigureMake in eb -a, since fallback is deprecated (#1123) + - correctly check software_license value type (#1124) + - fix generate_software_list.py script w.r.t. deprecated fallback to ConfigureMake (#1127) +- other bug fixes + - fix logging issues in tests, sync with vsc-base v2.0.0 (#1120) + v1.16.0 (December 18th 2014) ---------------------------- diff --git a/easybuild/tools/version.py b/easybuild/tools/version.py index e47bcb41ec..224892d87b 100644 --- a/easybuild/tools/version.py +++ b/easybuild/tools/version.py @@ -37,7 +37,7 @@ # note: release candidates should be versioned as a pre-release, e.g. "1.1rc1" # 1.1-rc1 would indicate a post-release, i.e., and update of 1.1, so beware! -VERSION = LooseVersion("1.16.1dev") +VERSION = LooseVersion("1.16.1") UNKNOWN = "UNKNOWN" def get_git_revision(): From 08b83e587a6f08ac0621ac242f009a2c5d4cfea5 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 19 Dec 2014 13:50:48 +0100 Subject: [PATCH 243/298] fix tests in tweak.py --- test/framework/tweak.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/framework/tweak.py b/test/framework/tweak.py index 061a0bcf8b..0395bd7d11 100644 --- a/test/framework/tweak.py +++ b/test/framework/tweak.py @@ -52,7 +52,7 @@ def test_find_matching_easyconfigs(self): self.assertTrue(len(ecs) == 1 and ecs[0].endswith('/%s-%s.eb' % (name, installver))) ecs = find_matching_easyconfigs('GCC', '*', [test_easyconfigs_path]) - gccvers = ['4.6.3', '4.6.4', '4.7.2', '4.8.2', '4.8.3'] + gccvers = ['4.6.3', '4.6.4', '4.7.2', '4.8.2', '4.8.3', '4.9.2'] self.assertEqual(len(ecs), len(gccvers)) ecs_basename = [os.path.basename(ec) for ec in ecs] for gccver in gccvers: @@ -96,7 +96,7 @@ def test_obtain_ec_for(self): } (generated, ec_file) = obtain_ec_for(specs, [test_easyconfigs_path]) self.assertFalse(generated) - self.assertEqual(os.path.basename(ec_file), 'GCC-4.8.3.eb') + self.assertEqual(os.path.basename(ec_file), 'GCC-4.9.2.eb') # generate non-existing easyconfig specs = { From f1d8dad632f7b3d1fc46896e81bd9754aaa4434e Mon Sep 17 00:00:00 2001 From: Jens Timmerman Date: Mon, 22 Dec 2014 18:13:33 +0100 Subject: [PATCH 244/298] added tesetcases for fetching patch files, + fix them! * patches with an explicit level 0 had the level ignored * patches with a boolean as second argument had that boolean interpreted as an int --- easybuild/framework/easyblock.py | 17 ++++++---- test/framework/easyblock.py | 25 +++++++++++++++ test/framework/easyconfigs/toy-0.0-patches.eb | 32 +++++++++++++++++++ .../sandbox/sources/toy/toy-0.0_level0.patch | 0 .../sources/toy/toy-0.0_level0_2.patch | 0 .../sandbox/sources/toy/toy-0.0_level4.patch | 0 6 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 test/framework/easyconfigs/toy-0.0-patches.eb create mode 100644 test/framework/sandbox/sources/toy/toy-0.0_level0.patch create mode 100644 test/framework/sandbox/sources/toy/toy-0.0_level0_2.patch create mode 100644 test/framework/sandbox/sources/toy/toy-0.0_level4.patch diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index ba37110e6b..19a21c090e 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -304,10 +304,12 @@ def fetch_patches(self, list_of_patches, extension=False, checksums=None): level = None if isinstance(patch_entry, (list, tuple)): if not len(patch_entry) == 2: - self.log.error("Unknown patch specification '%s', only two-element lists/tuples are supported!" % patch_entry) + self.log.error("Unknown patch specification '%s', only two-element lists/tuples are supported!", + str(patch_entry)) pf = patch_entry[0] - if isinstance(patch_entry[1], int): + if type(patch_entry[1]) == int: # int and only int is allowed here, we are parsing a config file, not + # trying to write generic code level = patch_entry[1] elif isinstance(patch_entry[1], basestring): # non-patch files are assumed to be files to copy @@ -315,7 +317,10 @@ def fetch_patches(self, list_of_patches, extension=False, checksums=None): copy_file = True suff = patch_entry[1] else: - self.log.error("Wrong patch specification '%s', only int and string are supported as second element!" % patch_entry) + self.log.error( + "Wrong patch specification '%s', only int and string are supported as second element!", + str(patch_entry), + ) else: pf = patch_entry @@ -332,7 +337,7 @@ def fetch_patches(self, list_of_patches, extension=False, checksums=None): patchspec['copy'] = suff else: patchspec['sourcepath'] = suff - if level: + if level is not None: patchspec['level'] = level if extension: @@ -1215,7 +1220,7 @@ def fetch_step(self, skip_checksums=False): # fetch extensions if len(self.cfg['exts_list']) > 0: self.exts = self.fetch_extension_sources() - + # fetch patches if self.cfg['patches']: if isinstance(self.cfg['checksums'], (list, tuple)): @@ -1364,7 +1369,7 @@ def extensions_step(self, fetch=False): if fetch: self.exts = self.fetch_extension_sources() - + self.exts_all = self.exts[:] # retain a copy of all extensions, regardless of filtering/skipping if self.skip: diff --git a/test/framework/easyblock.py b/test/framework/easyblock.py index 976c781c9a..0a4353c96f 100644 --- a/test/framework/easyblock.py +++ b/test/framework/easyblock.py @@ -407,6 +407,31 @@ def test_get_easyblock_instance(self): logtxt = read_file(eb.logfile) self.assertTrue(eb_log_msg_re.search(logtxt), "Pattern '%s' found in: %s" % (eb_log_msg_re.pattern, logtxt)) + def test_patchlevel(self): + """Test the parsing of the fetch_patches function.""" + # adjust PYTHONPATH such that test easyblocks are found + testdir = os.path.abspath(os.path.dirname(__file__)) + ec = process_easyconfig(os.path.join(testdir, 'easyconfigs', 'toy-0.0-patches.eb'))[0] + eb = get_easyblock_instance(ec) + + patches = [ + ('toy-0.0_level0_2.patch',0), # should also be level 0 (not None) + ('toy-0.0_level4.patch',4), # should be level4 + ] + sandbox_sources = os.path.join(testdir, 'sandbox', 'sources') + init_config(args=["--sourcepath=%s" % sandbox_sources]) + #check if patch levels are parsed correctly + eb.fetch_patches(patches) + + self.assertEquals(eb.patches[0]['level'], 0) + self.assertEquals(eb.patches[1]['level'], 4) + + patches = [ + ('toy-0.0_level4.patch', False), #should throw an error, only int's an strings allowed here + ] + self.assertRaises(EasyBuildError, eb.fetch_patches, patches) + + def test_obtain_file(self): """Test obtain_file method.""" toy_tarball = 'toy-0.0.tar.gz' diff --git a/test/framework/easyconfigs/toy-0.0-patches.eb b/test/framework/easyconfigs/toy-0.0-patches.eb new file mode 100644 index 0000000000..89994f6681 --- /dev/null +++ b/test/framework/easyconfigs/toy-0.0-patches.eb @@ -0,0 +1,32 @@ +name = 'toy' +version = '0.0' + +homepage = 'http://hpcugent.github.com/easybuild' +description = "Toy C program." + +toolchain = {'name': 'dummy', 'version': 'dummy'} + +sources = [SOURCE_TAR_GZ] +checksums = [[ + 'be662daa971a640e40be5c804d9d7d10', # default (MD5) + ('adler32', '0x998410035'), + ('crc32', '0x1553842328'), + ('md5', 'be662daa971a640e40be5c804d9d7d10'), + ('sha1', 'f618096c52244539d0e89867405f573fdb0b55b0'), + ('size', 273), +]] +patches = [ + 'toy-0.0_level0.patch', # should be level 0 + ('toy-0.0_level0_2.patch',0), # should also be level 0 (not None) + ('toy-0.0_level4.patch',4), # should be level4 + ('toy-0.0_level4.patch', False), #should throw an error, only int's an strings allowed here +] + +sanity_check_paths = { + 'files': [('bin/yot', 'bin/toy')], + 'dirs': ['bin'], +} + +postinstallcmds = ["echo TOY > %(installdir)s/README"] + +moduleclass = 'tools' diff --git a/test/framework/sandbox/sources/toy/toy-0.0_level0.patch b/test/framework/sandbox/sources/toy/toy-0.0_level0.patch new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/framework/sandbox/sources/toy/toy-0.0_level0_2.patch b/test/framework/sandbox/sources/toy/toy-0.0_level0_2.patch new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/framework/sandbox/sources/toy/toy-0.0_level4.patch b/test/framework/sandbox/sources/toy/toy-0.0_level4.patch new file mode 100644 index 0000000000..e69de29bb2 From ba175ddee0cc733bd09d6136284ad02a714ef09e Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 9 Jan 2015 09:38:02 +0100 Subject: [PATCH 245/298] add support for 'eb -a rst', clean up implementation of existing 'eb -a' which is equivalent to 'eb -a txt' --- easybuild/tools/options.py | 151 ++++++++++++++++++++++++++++++------- 1 file changed, 124 insertions(+), 27 deletions(-) diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index 59a3503116..456286162b 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -33,6 +33,7 @@ @author: Toon Willems (Ghent University) @author: Ward Poelmans (Ghent University) """ +import copy import os import re import sys @@ -42,7 +43,7 @@ from easybuild.framework.easyblock import EasyBlock from easybuild.framework.easyconfig import EASYCONFIGS_PKG_SUBDIR from easybuild.framework.easyconfig.constants import constant_documentation -from easybuild.framework.easyconfig.default import convert_to_help +from easybuild.framework.easyconfig.default import ALL_CATEGORIES, DEFAULT_CONFIG, HIDDEN, sorted_categories from easybuild.framework.easyconfig.easyconfig import get_easyblock_class from easybuild.framework.easyconfig.format.pyheaderconfigobj import build_easyconfig_constants_dict from easybuild.framework.easyconfig.licenses import license_documentation @@ -54,6 +55,7 @@ from easybuild.tools.config import DEFAULT_PATH_SUBDIRS, DEFAULT_PREFIX, DEFAULT_REPOSITORY, DEFAULT_TMP_LOGDIR from easybuild.tools.config import get_default_configfiles, get_pretend_installpath from easybuild.tools.config import get_default_oldstyle_configfile, mk_full_default_path +from easybuild.tools.deprecated.eb_2_0 import ExtraOptionsDeprecatedReturnValue from easybuild.tools.github import HAVE_GITHUB_API, HAVE_KEYRING, fetch_github_token from easybuild.tools.modules import avail_modules_tools from easybuild.tools.module_naming_scheme import GENERAL_CLASS @@ -61,12 +63,17 @@ from easybuild.tools.ordereddict import OrderedDict from easybuild.tools.toolchain.utilities import search_toolchain from easybuild.tools.repository.repository import avail_repositories +from easybuild.tools.utilities import quote_str from easybuild.tools.version import this_is_easybuild from vsc.utils import fancylogger from vsc.utils.generaloption import GeneralOption from vsc.utils.missing import any +FORMAT_RST = 'rst' +FORMAT_TXT = 'txt' + + class EasyBuildOptions(GeneralOption): """Easybuild generaloption class""" VERSION = this_is_easybuild() @@ -276,7 +283,7 @@ def informative_options(self): None, 'store_true', False), 'avail-easyconfig-params': (("Show all easyconfig parameters (include " "easyblock-specific ones by using -e)"), - None, "store_true", False, 'a'), + 'choice', 'store_or_None', FORMAT_TXT, [FORMAT_RST, FORMAT_TXT], 'a'), 'avail-easyconfig-templates': (("Show all template names and template constants " "that can be used in easyconfigs"), None, 'store_true', False), @@ -504,36 +511,126 @@ def avail_cfgfile_constants(self): lines.append("* %s: %s [value: %s]" % (cst_name, cst_help, cst_value)) return '\n'.join(lines) + def avail_easyconfig_params_txt(self, title, grouped_params): + """ + Compose overview of available easyconfig parameters, in plain text format. + """ + # main title + lines = [ + '%s:' % title, + '', + ] + + for grpname in grouped_params: + # group section title + lines.append(grpname.upper()) + lines.append('-' * len(lines[-1])) + + # determine width of 'name' column, to left-align descriptions + nw = max(map(len, grouped_params[grpname].keys())) + + # line by parameter + for name, (descr, dflt) in sorted(grouped_params[grpname].items()): + lines.append("{0:<{nw}} {1:} [default: {2:}]".format(name, descr, str(quote_str(dflt)), nw=nw)) + lines.append('') + + return '\n'.join(lines) + + def avail_easyconfig_params_rst(self, title, grouped_params): + """ + Compose overview of available easyconfig parameters, in RST format. + """ + def det_col_width(entries, title): + """Determine column width based on column title and list of entries.""" + return max(map(len, entries + [title])) + + # main title + lines = [ + title, + '=' * len(title), + '', + ] + + for grpname in grouped_params: + # group section title + lines.append("%s parameters" % grpname) + lines.extend(['-' * len(lines[-1]), '']) + + name_title = "**Parameter name**" + descr_title = "**Description**" + dflt_title = "**Default value**" + + # figure out column widths + nw = det_col_width(grouped_params[grpname].keys(), name_title) + 4 # +4 for raw format ("``foo``") + dw = det_col_width([x[0] for x in grouped_params[grpname].values()], descr_title) + dfw = det_col_width([str(quote_str(x[1])) for x in grouped_params[grpname].values()], dflt_title) + + # 3 columns (name, description, default value), left-aligned, {c} is fill char + line_tmpl = "{0:{c}<%s} {1:{c}<%s} {2:{c}<%s}" % (nw, dw, dfw) + table_line = line_tmpl.format('', '', '', c='=', nw=nw, dw=dw, dfw=dfw) + + # table header + lines.append(table_line) + lines.append(line_tmpl.format(name_title, descr_title, dflt_title, c=' ')) + lines.append(line_tmpl.format('', '', '', c='-')) + + # table rows by parameter + for name, (descr, dflt) in sorted(grouped_params[grpname].items()): + rawname = '``%s``' % name + lines.append(line_tmpl.format(rawname, descr, str(quote_str(dflt)), c=' ')) + lines.append(table_line) + lines.append('') + + return '\n'.join(lines) + def avail_easyconfig_params(self): """ - Print the available easyconfig parameters, for the given easyblock. + Compose overview of available easyconfig parameters, in specified format. """ - extra = [] + params = copy.deepcopy(DEFAULT_CONFIG) + + # include list of extra parameters (if any) + extra_params = {} app = get_easyblock_class(self.options.easyblock, default_fallback=False) if app is not None: - extra = app.extra_options() - mapping = convert_to_help(extra, has_default=False) - if extra: - ebb_msg = " (* indicates specific for the %s EasyBlock)" % app.__name__ - extra_names = [x[0] for x in extra] - else: - ebb_msg = '' - extra_names = [] - txt = ["Available easyconfig parameters%s" % ebb_msg] - params = [(k, v) for (k, v) in mapping.items() if k.upper() not in ['HIDDEN']] - for key, values in params: - txt.append("%s" % key.upper()) - txt.append('-' * len(key)) - for name, value in values: - tabs = "\t" * (3 - (len(name) + 1) / 8) - if name in extra_names: - starred = '(*)' - else: - starred = '' - txt.append("%s%s:%s%s" % (name, starred, tabs, value)) - txt.append('') - - return "\n".join(txt) + extra_params = app.extra_options() + if isinstance(extra_params, ExtraOptionsDeprecatedReturnValue): + extra_params = dict(extra_params) + params.update(extra_params) + + # compose title + title = "Available easyconfig parameters" + if extra_params: + title += " (* indicates specific to the %s easyblock)" % app.__name__ + + # group parameters by category + grouped_params = OrderedDict() + for category in sorted_categories(): + # exclude hidden parameters + if category[1].upper() in [HIDDEN]: + continue + + grpname = category[1] + grouped_params[grpname] = {} + for name, (dflt, descr, cat) in params.items(): + # FIXME bug in default.py? + if isinstance(cat, basestring): + cat = ALL_CATEGORIES[cat] + if cat == category: + if name in extra_params: + # mark easyblock-specific parameters + name = '%s*' % name + grouped_params[grpname].update({name: (descr, dflt)}) + + if not grouped_params[grpname]: + del grouped_params[grpname] + + # compose output, according to specified format (txt, rst, ...) + avail_easyconfig_params_functions = { + FORMAT_RST: self.avail_easyconfig_params_rst, + FORMAT_TXT: self.avail_easyconfig_params_txt, + } + return avail_easyconfig_params_functions[self.options.avail_easyconfig_params](title, grouped_params) def avail_classes_tree(self, classes, classNames, detailed, depth=0): """Print list of classes as a tree.""" From 1a85480b19fa281f18596e526d0066b2df430674 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 9 Jan 2015 09:38:27 +0100 Subject: [PATCH 246/298] remove convert_to_help function which is no longer used --- easybuild/framework/easyconfig/default.py | 26 ----------------------- 1 file changed, 26 deletions(-) diff --git a/easybuild/framework/easyconfig/default.py b/easybuild/framework/easyconfig/default.py index acc99c33eb..e74b5bb8cd 100644 --- a/easybuild/framework/easyconfig/default.py +++ b/easybuild/framework/easyconfig/default.py @@ -35,8 +35,6 @@ """ from vsc.utils import fancylogger -from easybuild.tools.deprecated.eb_2_0 import ExtraOptionsDeprecatedReturnValue -from easybuild.tools.ordereddict import OrderedDict _log = fancylogger.getLogger('easyconfig.default', fname=False) @@ -187,30 +185,6 @@ def sorted_categories(): return categories -def convert_to_help(opts, has_default=False): - """ - Converts the given list to a mapping of category -> [(name, help)] (OrderedDict) - @param: has_default, if False, add the DEFAULT_CONFIG list - """ - mapping = OrderedDict() - if isinstance(opts, dict): - opts = opts.items() - elif isinstance(opts, ExtraOptionsDeprecatedReturnValue): - opts = list(opts) - if not has_default: - defs = [(k, [def_val, descr, ALL_CATEGORIES[cat]]) for k, (def_val, descr, cat) in DEFAULT_CONFIG.items()] - opts = defs + opts - - # sort opts - opts.sort() - - for cat in sorted_categories(): - mapping[cat[1]] = [(opt[0], "%s (default: %s)" % (opt[1][1], opt[1][0])) - for opt in opts if opt[1][2] == cat] - - return mapping - - def get_easyconfig_parameter_default(param): """Get default value for given easyconfig parameter.""" if param not in DEFAULT_CONFIG: From 7d952dea7eaa36499050dc8a9164e40a79f5d728 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 9 Jan 2015 10:40:59 +0100 Subject: [PATCH 247/298] enhance test_avail_easyconfig_params test to check both txt/rst output formats --- test/framework/options.py | 41 +++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/test/framework/options.py b/test/framework/options.py index 728156d5e4..e3131507f6 100644 --- a/test/framework/options.py +++ b/test/framework/options.py @@ -334,24 +334,27 @@ def test_zzz_logtostdout(self): def test_avail_easyconfig_params(self): """Test listing available easyconfig parameters.""" - def run_test(custom=None, extra_params=[]): + def run_test(custom=None, extra_params=[], fmt=None): """Inner function to run actual test in current setting.""" fd, dummylogfn = tempfile.mkstemp(prefix='easybuild-dummy', suffix='.log') os.close(fd) - for avail_arg in [ - '-a', - '--avail-easyconfig-params', - ]: + avail_args = [ + '-a', + '--avail-easyconfig-params', + ] + for avail_arg in avail_args: # clear log write_file(self.logfile, '') args = [ - avail_arg, '--unittest-file=%s' % self.logfile, + avail_arg, ] + if fmt is not None: + args.append(fmt) if custom is not None: args.extend(['-e', custom]) @@ -364,17 +367,20 @@ def run_test(custom=None, extra_params=[]): par_types.append(CUSTOM) for param_type in [x[1] for x in par_types]: - self.assertTrue(re.search("%s\n%s" % (param_type.upper(), '-' * len(param_type)), outtxt), - "Parameter type %s is featured in output of eb %s (args: %s): %s" % - (param_type, avail_arg, args, outtxt)) + # regex for parameter group title, matches both txt and rst formats + regex = re.compile("%s.*\n%s" % (param_type, '-' * len(param_type)), re.I) + tup = (param_type, avail_arg, args, outtxt) + msg = "Parameter type %s is featured in output of eb %s (args: %s): %s" % tup + self.assertTrue(regex.search(outtxt), msg) # check a couple of easyconfig parameters for param in ["name", "version", "toolchain", "versionsuffix", "buildopts", "sources", "start_dir", "dependencies", "group", "exts_list", "moduleclass", "buildstats"] + extra_params: - self.assertTrue(re.search("%s(?:\(\*\))?:\s*\w.*" % param, outtxt), - "Parameter %s is listed with help in output of eb %s (args: %s): %s" % - (param, avail_arg, args, outtxt) - ) + # regex for parameter name (with optional '*') & description, matches both txt and rst formats + regex = re.compile("^[`]*%s(?:\*)?[`]*\s+\w+" % param, re.M) + tup = (param, avail_arg, args, regex.pattern, outtxt) + msg = "Parameter %s is listed with help in output of eb %s (args: %s, regex: %s): %s" % tup + self.assertTrue(regex.search(outtxt), msg) modify_env(os.environ, self.orig_environ) tempfile.tempdir = None @@ -382,10 +388,11 @@ def run_test(custom=None, extra_params=[]): if os.path.exists(dummylogfn): os.remove(dummylogfn) - run_test() - run_test(custom='EB_foo', extra_params=['foo_extra1', 'foo_extra2']) - run_test(custom='bar', extra_params=['bar_extra1', 'bar_extra2']) - run_test(custom='EB_foofoo', extra_params=['foofoo_extra1', 'foofoo_extra2']) + for fmt in [None, 'txt', 'rst']: + run_test(fmt=fmt) + run_test(custom='EB_foo', extra_params=['foo_extra1', 'foo_extra2'], fmt=fmt) + run_test(custom='bar', extra_params=['bar_extra1', 'bar_extra2'], fmt=fmt) + run_test(custom='EB_foofoo', extra_params=['foofoo_extra1', 'foofoo_extra2'], fmt=fmt) # double underscore to make sure it runs first, which is required to detect certain types of bugs, # e.g. running with non-initialized EasyBuild config (truly mimicing 'eb --list-toolchains') From 3f0764168716fe3414f65f7bea49627eb5e7a22f Mon Sep 17 00:00:00 2001 From: Ward Poelmans Date: Fri, 9 Jan 2015 11:38:27 +0100 Subject: [PATCH 248/298] Choose a easyconfig from a PR eb --from-pr XX file.eb now works. It will only install the file.eb easyconfig but using all deps in the PR. --- easybuild/framework/easyconfig/tools.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/easybuild/framework/easyconfig/tools.py b/easybuild/framework/easyconfig/tools.py index 45f8659f06..555df096a0 100644 --- a/easybuild/framework/easyconfig/tools.py +++ b/easybuild/framework/easyconfig/tools.py @@ -34,6 +34,7 @@ @author: Jens Timmerman (Ghent University) @author: Toon Willems (Ghent University) @author: Fotis Georgatos (Uni.Lu, NTUA) +@author: Ward Poelmans (Ghent University) """ import os @@ -248,12 +249,15 @@ def det_easyconfig_paths(orig_paths, from_pr=None, easyconfigs_pkg_paths=None): if easyconfigs_pkg_paths is None: easyconfigs_pkg_paths = [] + if from_pr is not None: + pr_files = fetch_easyconfigs_from_pr(from_pr) + ec_files = orig_paths[:] - if not ec_files and from_pr: - pr_files = fetch_easyconfigs_from_pr(from_pr) + if not ec_files and pr_files: ec_files = [path for path in pr_files if path.endswith('.eb')] - + elif ec_files and pr_files: + ec_files = [path for path in pr_files if os.path.basename(path) in orig_paths] elif ec_files and easyconfigs_pkg_paths: # look for easyconfigs with relative paths in easybuild-easyconfigs package, # unless they were found at the given relative paths @@ -282,7 +286,7 @@ def det_easyconfig_paths(orig_paths, from_pr=None, easyconfigs_pkg_paths=None): break # ignore subdirs specified to be ignored by replacing items in dirnames list used by os.walk - dirnames[:] = [d for d in dirnames if not d in build_option('ignore_dirs')] + dirnames[:] = [d for d in dirnames if d not in build_option('ignore_dirs')] # stop os.walk insanity as soon as we have all we need (outer loop) if not ecs_to_find: From 5804b75aec34b7420e000c10f532a6ccaa1db114 Mon Sep 17 00:00:00 2001 From: Ward Poelmans Date: Fri, 9 Jan 2015 12:06:08 +0100 Subject: [PATCH 249/298] Fix bug --- easybuild/framework/easyconfig/tools.py | 1 + 1 file changed, 1 insertion(+) diff --git a/easybuild/framework/easyconfig/tools.py b/easybuild/framework/easyconfig/tools.py index 555df096a0..b4fd674208 100644 --- a/easybuild/framework/easyconfig/tools.py +++ b/easybuild/framework/easyconfig/tools.py @@ -249,6 +249,7 @@ def det_easyconfig_paths(orig_paths, from_pr=None, easyconfigs_pkg_paths=None): if easyconfigs_pkg_paths is None: easyconfigs_pkg_paths = [] + pr_files = None if from_pr is not None: pr_files = fetch_easyconfigs_from_pr(from_pr) From c66155a005ac80ecb9d7c847596c5b9e9af02567 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 9 Jan 2015 13:42:23 +0100 Subject: [PATCH 250/298] fix from_pr test by including dummy easyblocks for HPL and ScaLAPACK --- test/framework/options.py | 17 +++++++++- .../sandbox/easybuild/easyblocks/hpl.py | 33 +++++++++++++++++++ .../sandbox/easybuild/easyblocks/scalapack.py | 33 +++++++++++++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 test/framework/sandbox/easybuild/easyblocks/hpl.py create mode 100644 test/framework/sandbox/easybuild/easyblocks/scalapack.py diff --git a/test/framework/options.py b/test/framework/options.py index 728156d5e4..580835d0a8 100644 --- a/test/framework/options.py +++ b/test/framework/options.py @@ -747,9 +747,22 @@ def test_from_pr(self): fd, dummylogfn = tempfile.mkstemp(prefix='easybuild-dummy', suffix='.log') os.close(fd) + orig_sys_path = sys.path[:] + + # adjust PYTHONPATH such that test easyblocks are found + # this is required since the HPL and ScaLAPACK easyconfigs included in the tested PR have no 'easyblock' spec + import easybuild + eb_blocks_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'sandbox')) + if not eb_blocks_path in sys.path: + sys.path.append(eb_blocks_path) + easybuild = reload(easybuild) + + import easybuild.easyblocks + reload(easybuild.easyblocks) + tmpdir = tempfile.mkdtemp() args = [ - # PR for intel/2014b, see https://github.com/hpcugent/easybuild-easyconfigs/pull/1239/files + # PR for foss/2015a, see https://github.com/hpcugent/easybuild-easyconfigs/pull/1239/files '--from-pr=1239', '--dry-run', # an argument must be specified to --robot, since easybuild-easyconfigs may not be installed @@ -784,6 +797,8 @@ def test_from_pr(self): print "Ignoring URLError '%s' in test_from_pr" % err shutil.rmtree(tmpdir) + sys.path = orig_sys_path + def test_no_such_software(self): """Test using no arguments.""" diff --git a/test/framework/sandbox/easybuild/easyblocks/hpl.py b/test/framework/sandbox/easybuild/easyblocks/hpl.py new file mode 100644 index 0000000000..1ef029f6c1 --- /dev/null +++ b/test/framework/sandbox/easybuild/easyblocks/hpl.py @@ -0,0 +1,33 @@ +## +# Copyright 2009-2014 Ghent University +# +# This file is part of EasyBuild, +# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en), +# with support of Ghent University (http://ugent.be/hpc), +# the Flemish Supercomputer Centre (VSC) (https://vscentrum.be/nl/en), +# the Hercules foundation (http://www.herculesstichting.be/in_English) +# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en). +# +# http://github.com/hpcugent/easybuild +# +# EasyBuild is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation v2. +# +# EasyBuild is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with EasyBuild. If not, see . +## +""" +Dummy easyblock for HPL + +@author: Kenneth Hoste (Ghent University) +""" +from easybuild.framework.easyblock import EasyBlock + +class EB_HPL(EasyBlock): + pass diff --git a/test/framework/sandbox/easybuild/easyblocks/scalapack.py b/test/framework/sandbox/easybuild/easyblocks/scalapack.py new file mode 100644 index 0000000000..4534a5cd10 --- /dev/null +++ b/test/framework/sandbox/easybuild/easyblocks/scalapack.py @@ -0,0 +1,33 @@ +## +# Copyright 2009-2014 Ghent University +# +# This file is part of EasyBuild, +# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en), +# with support of Ghent University (http://ugent.be/hpc), +# the Flemish Supercomputer Centre (VSC) (https://vscentrum.be/nl/en), +# the Hercules foundation (http://www.herculesstichting.be/in_English) +# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en). +# +# http://github.com/hpcugent/easybuild +# +# EasyBuild is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation v2. +# +# EasyBuild is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with EasyBuild. If not, see . +## +""" +Dummy easyblock for ScaLAPACK + +@author: Kenneth Hoste (Ghent University) +""" +from easybuild.framework.easyblock import EasyBlock + +class EB_ScaLAPACK(EasyBlock): + pass From 8dc734426f357e6a8f3eab858fb6e7793ef1a5b3 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 9 Jan 2015 14:43:01 +0100 Subject: [PATCH 251/298] change semantics w.r.t. synergy between --from-pr and specified easyconfigs --- easybuild/framework/easyconfig/tools.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/easybuild/framework/easyconfig/tools.py b/easybuild/framework/easyconfig/tools.py index b4fd674208..da3af771ac 100644 --- a/easybuild/framework/easyconfig/tools.py +++ b/easybuild/framework/easyconfig/tools.py @@ -249,17 +249,23 @@ def det_easyconfig_paths(orig_paths, from_pr=None, easyconfigs_pkg_paths=None): if easyconfigs_pkg_paths is None: easyconfigs_pkg_paths = [] - pr_files = None + # list of specified easyconfig files + ec_files = orig_paths[:] + if from_pr is not None: pr_files = fetch_easyconfigs_from_pr(from_pr) - ec_files = orig_paths[:] + if ec_files: + # replace paths for specified easyconfigs that are touched in PR + for i, ec_file in enumerate(ec_files): + for pr_file in pr_files: + if ec_file == os.path.basename(pr_file): + ec_files[i] = pr_file + else: + # if no easyconfigs are specified, use all the ones touched in the PR + ec_files = [path for path in pr_files if path.endswith('.eb')] - if not ec_files and pr_files: - ec_files = [path for path in pr_files if path.endswith('.eb')] - elif ec_files and pr_files: - ec_files = [path for path in pr_files if os.path.basename(path) in orig_paths] - elif ec_files and easyconfigs_pkg_paths: + if ec_files and easyconfigs_pkg_paths: # look for easyconfigs with relative paths in easybuild-easyconfigs package, # unless they were found at the given relative paths From 763be8835251e3ed09df21943cb4261b4e269e55 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 9 Jan 2015 16:01:37 +0100 Subject: [PATCH 252/298] add unit test for enhanced --from-pr functionality --- test/framework/options.py | 94 ++++++++++++++++++++++++++++----------- 1 file changed, 68 insertions(+), 26 deletions(-) diff --git a/test/framework/options.py b/test/framework/options.py index 580835d0a8..6cb7d80c03 100644 --- a/test/framework/options.py +++ b/test/framework/options.py @@ -747,19 +747,6 @@ def test_from_pr(self): fd, dummylogfn = tempfile.mkstemp(prefix='easybuild-dummy', suffix='.log') os.close(fd) - orig_sys_path = sys.path[:] - - # adjust PYTHONPATH such that test easyblocks are found - # this is required since the HPL and ScaLAPACK easyconfigs included in the tested PR have no 'easyblock' spec - import easybuild - eb_blocks_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'sandbox')) - if not eb_blocks_path in sys.path: - sys.path.append(eb_blocks_path) - easybuild = reload(easybuild) - - import easybuild.easyblocks - reload(easybuild.easyblocks) - tmpdir = tempfile.mkdtemp() args = [ # PR for foss/2015a, see https://github.com/hpcugent/easybuild-easyconfigs/pull/1239/files @@ -774,22 +761,27 @@ def test_from_pr(self): try: outtxt = self.eb_main(args, logfile=dummylogfn, raise_error=True) modules = [ - 'FFTW/3.3.4-gompi-2015a', - 'foss/2015a', - 'GCC/4.9.2', - 'gompi/2015a', - 'HPL/2.1-foss-2015a', - 'hwloc/1.10.0-GCC-4.9.2', - 'numactl/2.0.10-GCC-4.9.2', - 'OpenBLAS/0.2.13-GCC-4.9.2-LAPACK-3.5.0', - 'OpenMPI/1.8.3-GCC-4.9.2', - 'ScaLAPACK/2.0.2-gompi-2015a-OpenBLAS-0.2.13-LAPACK-3.5.0', + (tmpdir, 'FFTW/3.3.4-gompi-2015a'), + (tmpdir, 'foss/2015a'), + ('.*', 'GCC/4.9.2'), # not included in PR + (tmpdir, 'gompi/2015a'), + (tmpdir, 'HPL/2.1-foss-2015a'), + (tmpdir, 'hwloc/1.10.0-GCC-4.9.2'), + (tmpdir, 'numactl/2.0.10-GCC-4.9.2'), + (tmpdir, 'OpenBLAS/0.2.13-GCC-4.9.2-LAPACK-3.5.0'), + (tmpdir, 'OpenMPI/1.8.3-GCC-4.9.2'), + (tmpdir, 'OpenMPI/1.8.4-GCC-4.9.2'), + (tmpdir, 'ScaLAPACK/2.0.2-gompi-2015a-OpenBLAS-0.2.13-LAPACK-3.5.0'), ] - for module in modules: + for path_prefix, module in modules: ec_fn = "%s.eb" % '-'.join(module.split('/')) - regex = re.compile(r"^ \* \[.\] .*/%s \(module: %s\)$" % (ec_fn, module), re.M) + regex = re.compile(r"^ \* \[.\] %s.*%s \(module: %s\)$" % (path_prefix, ec_fn, module), re.M) self.assertTrue(regex.search(outtxt), "Found pattern %s in %s" % (regex.pattern, outtxt)) + # make sure that *only* these modules are listed, no others + regex = re.compile(r"^ \* \[.\] .*/(?P.*) \(module: (?P.*)\)$", re.M) + self.assertTrue(sorted(regex.findall(outtxt)), sorted(modules)) + pr_tmpdir = os.path.join(tmpdir, 'easybuild-\S{6}', 'files_pr1239') regex = re.compile("Prepended list of robot search paths with %s:" % pr_tmpdir, re.M) self.assertTrue(regex.search(outtxt), "Found pattern %s in %s" % (regex.pattern, outtxt)) @@ -797,7 +789,57 @@ def test_from_pr(self): print "Ignoring URLError '%s' in test_from_pr" % err shutil.rmtree(tmpdir) - sys.path = orig_sys_path + def test_from_pr_listed_ecs(self): + """Test --from-pr in combination with specifying easyconfigs on the command line.""" + fd, dummylogfn = tempfile.mkstemp(prefix='easybuild-dummy', suffix='.log') + os.close(fd) + + # copy test easyconfigs to easybuild/easyconfigs subdirectory of temp directory + test_ecs_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs') + ecstmpdir = tempfile.mkdtemp(prefix='easybuild-easyconfigs-pkg-install-path') + mkdir(os.path.join(ecstmpdir, 'easybuild'), parents=True) + shutil.copytree(test_ecs_path, os.path.join(ecstmpdir, 'easybuild', 'easyconfigs')) + + # inject path to test easyconfigs into head of Python search path + sys.path.insert(0, ecstmpdir) + + tmpdir = tempfile.mkdtemp() + args = [ + 'toy-0.0.eb', + 'gompi-2015a.eb', # also pulls in GCC, OpenMPI (which pulls in hwloc and numactl) + 'GCC-4.6.3.eb', + # PR for foss/2015a, see https://github.com/hpcugent/easybuild-easyconfigs/pull/1239/files + '--from-pr=1239', + '--dry-run', + # an argument must be specified to --robot, since easybuild-easyconfigs may not be installed + '--robot=%s' % os.path.join(os.path.dirname(__file__), 'easyconfigs'), + '--unittest-file=%s' % self.logfile, + '--github-user=easybuild_test', # a GitHub token should be available for this user + '--tmpdir=%s' % tmpdir, + ] + try: + outtxt = self.eb_main(args, logfile=dummylogfn, raise_error=True) + modules = [ + (ecstmpdir, 'toy/0.0'), + ('.*', 'GCC/4.9.2'), # not included in PR + (tmpdir, 'hwloc/1.10.0-GCC-4.9.2'), + (tmpdir, 'numactl/2.0.10-GCC-4.9.2'), + (tmpdir, 'OpenMPI/1.8.4-GCC-4.9.2'), + (tmpdir, 'gompi/2015a'), + ('.*', 'GCC/4.6.3'), + ] + for path_prefix, module in modules: + ec_fn = "%s.eb" % '-'.join(module.split('/')) + regex = re.compile(r"^ \* \[.\] %s.*%s \(module: %s\)$" % (path_prefix, ec_fn, module), re.M) + self.assertTrue(regex.search(outtxt), "Found pattern %s in %s" % (regex.pattern, outtxt)) + + # make sure that *only* these modules are listed, no others + regex = re.compile(r"^ \* \[.\] .*/(?P.*) \(module: (?P.*)\)$", re.M) + self.assertTrue(sorted(regex.findall(outtxt)), sorted(modules)) + + except URLError, err: + print "Ignoring URLError '%s' in test_from_pr" % err + shutil.rmtree(tmpdir) def test_no_such_software(self): """Test using no arguments.""" From 38583f44e1dfe6096adb41714305e9bc198c91e8 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 9 Jan 2015 18:30:06 +0100 Subject: [PATCH 253/298] flesh out functions related to 'eb -a' support into new modules easybuild.tools.docs --- easybuild/tools/docs.py | 168 +++++++++++++++++++++++++++++++++++++ easybuild/tools/options.py | 133 +---------------------------- 2 files changed, 170 insertions(+), 131 deletions(-) create mode 100644 easybuild/tools/docs.py diff --git a/easybuild/tools/docs.py b/easybuild/tools/docs.py new file mode 100644 index 0000000000..ff79f2e2df --- /dev/null +++ b/easybuild/tools/docs.py @@ -0,0 +1,168 @@ +# # +# Copyright 2009-2014 Ghent University +# +# This file is part of EasyBuild, +# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en), +# with support of Ghent University (http://ugent.be/hpc), +# the Flemish Supercomputer Centre (VSC) (https://vscentrum.be/nl/en), +# the Hercules foundation (http://www.herculesstichting.be/in_English) +# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en). +# +# http://github.com/hpcugent/easybuild +# +# EasyBuild is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation v2. +# +# EasyBuild is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with EasyBuild. If not, see . +# # +""" +Documentation-related functionality + +@author: Stijn De Weirdt (Ghent University) +@author: Dries Verdegem (Ghent University) +@author: Kenneth Hoste (Ghent University) +@author: Pieter De Baets (Ghent University) +@author: Jens Timmerman (Ghent University) +@author: Toon Willems (Ghent University) +@author: Ward Poelmans (Ghent University) +""" +import copy + +from easybuild.framework.easyconfig.default import ALL_CATEGORIES, DEFAULT_CONFIG, HIDDEN, sorted_categories +from easybuild.framework.easyconfig.easyconfig import get_easyblock_class +from easybuild.tools.deprecated.eb_2_0 import ExtraOptionsDeprecatedReturnValue +from easybuild.tools.ordereddict import OrderedDict +from easybuild.tools.utilities import quote_str + + +FORMAT_RST = 'rst' +FORMAT_TXT = 'txt' + + +def avail_easyconfig_params_rst(title, grouped_params): + """ + Compose overview of available easyconfig parameters, in RST format. + """ + def det_col_width(entries, title): + """Determine column width based on column title and list of entries.""" + return max(map(len, entries + [title])) + + # main title + lines = [ + title, + '=' * len(title), + '', + ] + + for grpname in grouped_params: + # group section title + lines.append("%s parameters" % grpname) + lines.extend(['-' * len(lines[-1]), '']) + + name_title = "**Parameter name**" + descr_title = "**Description**" + dflt_title = "**Default value**" + + # figure out column widths + nw = det_col_width(grouped_params[grpname].keys(), name_title) + 4 # +4 for raw format ("``foo``") + dw = det_col_width([x[0] for x in grouped_params[grpname].values()], descr_title) + dfw = det_col_width([str(quote_str(x[1])) for x in grouped_params[grpname].values()], dflt_title) + + # 3 columns (name, description, default value), left-aligned, {c} is fill char + line_tmpl = "{0:{c}<%s} {1:{c}<%s} {2:{c}<%s}" % (nw, dw, dfw) + table_line = line_tmpl.format('', '', '', c='=', nw=nw, dw=dw, dfw=dfw) + + # table header + lines.append(table_line) + lines.append(line_tmpl.format(name_title, descr_title, dflt_title, c=' ')) + lines.append(line_tmpl.format('', '', '', c='-')) + + # table rows by parameter + for name, (descr, dflt) in sorted(grouped_params[grpname].items()): + rawname = '``%s``' % name + lines.append(line_tmpl.format(rawname, descr, str(quote_str(dflt)), c=' ')) + lines.append(table_line) + lines.append('') + + return '\n'.join(lines) + +def avail_easyconfig_params_txt(title, grouped_params): + """ + Compose overview of available easyconfig parameters, in plain text format. + """ + # main title + lines = [ + '%s:' % title, + '', + ] + + for grpname in grouped_params: + # group section title + lines.append(grpname.upper()) + lines.append('-' * len(lines[-1])) + + # determine width of 'name' column, to left-align descriptions + nw = max(map(len, grouped_params[grpname].keys())) + + # line by parameter + for name, (descr, dflt) in sorted(grouped_params[grpname].items()): + lines.append("{0:<{nw}} {1:} [default: {2:}]".format(name, descr, str(quote_str(dflt)), nw=nw)) + lines.append('') + + return '\n'.join(lines) + +def avail_easyconfig_params(easyblock, output_format): + """ + Compose overview of available easyconfig parameters, in specified format. + """ + params = copy.deepcopy(DEFAULT_CONFIG) + + # include list of extra parameters (if any) + extra_params = {} + app = get_easyblock_class(easyblock, default_fallback=False) + if app is not None: + extra_params = app.extra_options() + if isinstance(extra_params, ExtraOptionsDeprecatedReturnValue): + extra_params = dict(extra_params) + params.update(extra_params) + + # compose title + title = "Available easyconfig parameters" + if extra_params: + title += " (* indicates specific to the %s easyblock)" % app.__name__ + + # group parameters by category + grouped_params = OrderedDict() + for category in sorted_categories(): + # exclude hidden parameters + if category[1].upper() in [HIDDEN]: + continue + + grpname = category[1] + grouped_params[grpname] = {} + for name, (dflt, descr, cat) in params.items(): + # FIXME bug in default.py? + if isinstance(cat, basestring): + cat = ALL_CATEGORIES[cat] + if cat == category: + if name in extra_params: + # mark easyblock-specific parameters + name = '%s*' % name + grouped_params[grpname].update({name: (descr, dflt)}) + + if not grouped_params[grpname]: + del grouped_params[grpname] + + # compose output, according to specified format (txt, rst, ...) + avail_easyconfig_params_functions = { + FORMAT_RST: avail_easyconfig_params_rst, + FORMAT_TXT: avail_easyconfig_params_txt, + } + return avail_easyconfig_params_functions[output_format](title, grouped_params) diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index 456286162b..1d4472b353 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -33,7 +33,6 @@ @author: Toon Willems (Ghent University) @author: Ward Poelmans (Ghent University) """ -import copy import os import re import sys @@ -43,8 +42,6 @@ from easybuild.framework.easyblock import EasyBlock from easybuild.framework.easyconfig import EASYCONFIGS_PKG_SUBDIR from easybuild.framework.easyconfig.constants import constant_documentation -from easybuild.framework.easyconfig.default import ALL_CATEGORIES, DEFAULT_CONFIG, HIDDEN, sorted_categories -from easybuild.framework.easyconfig.easyconfig import get_easyblock_class from easybuild.framework.easyconfig.format.pyheaderconfigobj import build_easyconfig_constants_dict from easybuild.framework.easyconfig.licenses import license_documentation from easybuild.framework.easyconfig.templates import template_documentation @@ -55,7 +52,7 @@ from easybuild.tools.config import DEFAULT_PATH_SUBDIRS, DEFAULT_PREFIX, DEFAULT_REPOSITORY, DEFAULT_TMP_LOGDIR from easybuild.tools.config import get_default_configfiles, get_pretend_installpath from easybuild.tools.config import get_default_oldstyle_configfile, mk_full_default_path -from easybuild.tools.deprecated.eb_2_0 import ExtraOptionsDeprecatedReturnValue +from easybuild.tools.docs import FORMAT_RST, FORMAT_TXT, avail_easyconfig_params from easybuild.tools.github import HAVE_GITHUB_API, HAVE_KEYRING, fetch_github_token from easybuild.tools.modules import avail_modules_tools from easybuild.tools.module_naming_scheme import GENERAL_CLASS @@ -63,17 +60,12 @@ from easybuild.tools.ordereddict import OrderedDict from easybuild.tools.toolchain.utilities import search_toolchain from easybuild.tools.repository.repository import avail_repositories -from easybuild.tools.utilities import quote_str from easybuild.tools.version import this_is_easybuild from vsc.utils import fancylogger from vsc.utils.generaloption import GeneralOption from vsc.utils.missing import any -FORMAT_RST = 'rst' -FORMAT_TXT = 'txt' - - class EasyBuildOptions(GeneralOption): """Easybuild generaloption class""" VERSION = this_is_easybuild() @@ -450,7 +442,7 @@ def _postprocess_list_avail(self): # dump possible easyconfig params if self.options.avail_easyconfig_params: - msg += self.avail_easyconfig_params() + msg += avail_easyconfig_params(self.options.easyblock, self.options.avail_easyconfig_params) # dump easyconfig template options if self.options.avail_easyconfig_templates: @@ -511,127 +503,6 @@ def avail_cfgfile_constants(self): lines.append("* %s: %s [value: %s]" % (cst_name, cst_help, cst_value)) return '\n'.join(lines) - def avail_easyconfig_params_txt(self, title, grouped_params): - """ - Compose overview of available easyconfig parameters, in plain text format. - """ - # main title - lines = [ - '%s:' % title, - '', - ] - - for grpname in grouped_params: - # group section title - lines.append(grpname.upper()) - lines.append('-' * len(lines[-1])) - - # determine width of 'name' column, to left-align descriptions - nw = max(map(len, grouped_params[grpname].keys())) - - # line by parameter - for name, (descr, dflt) in sorted(grouped_params[grpname].items()): - lines.append("{0:<{nw}} {1:} [default: {2:}]".format(name, descr, str(quote_str(dflt)), nw=nw)) - lines.append('') - - return '\n'.join(lines) - - def avail_easyconfig_params_rst(self, title, grouped_params): - """ - Compose overview of available easyconfig parameters, in RST format. - """ - def det_col_width(entries, title): - """Determine column width based on column title and list of entries.""" - return max(map(len, entries + [title])) - - # main title - lines = [ - title, - '=' * len(title), - '', - ] - - for grpname in grouped_params: - # group section title - lines.append("%s parameters" % grpname) - lines.extend(['-' * len(lines[-1]), '']) - - name_title = "**Parameter name**" - descr_title = "**Description**" - dflt_title = "**Default value**" - - # figure out column widths - nw = det_col_width(grouped_params[grpname].keys(), name_title) + 4 # +4 for raw format ("``foo``") - dw = det_col_width([x[0] for x in grouped_params[grpname].values()], descr_title) - dfw = det_col_width([str(quote_str(x[1])) for x in grouped_params[grpname].values()], dflt_title) - - # 3 columns (name, description, default value), left-aligned, {c} is fill char - line_tmpl = "{0:{c}<%s} {1:{c}<%s} {2:{c}<%s}" % (nw, dw, dfw) - table_line = line_tmpl.format('', '', '', c='=', nw=nw, dw=dw, dfw=dfw) - - # table header - lines.append(table_line) - lines.append(line_tmpl.format(name_title, descr_title, dflt_title, c=' ')) - lines.append(line_tmpl.format('', '', '', c='-')) - - # table rows by parameter - for name, (descr, dflt) in sorted(grouped_params[grpname].items()): - rawname = '``%s``' % name - lines.append(line_tmpl.format(rawname, descr, str(quote_str(dflt)), c=' ')) - lines.append(table_line) - lines.append('') - - return '\n'.join(lines) - - def avail_easyconfig_params(self): - """ - Compose overview of available easyconfig parameters, in specified format. - """ - params = copy.deepcopy(DEFAULT_CONFIG) - - # include list of extra parameters (if any) - extra_params = {} - app = get_easyblock_class(self.options.easyblock, default_fallback=False) - if app is not None: - extra_params = app.extra_options() - if isinstance(extra_params, ExtraOptionsDeprecatedReturnValue): - extra_params = dict(extra_params) - params.update(extra_params) - - # compose title - title = "Available easyconfig parameters" - if extra_params: - title += " (* indicates specific to the %s easyblock)" % app.__name__ - - # group parameters by category - grouped_params = OrderedDict() - for category in sorted_categories(): - # exclude hidden parameters - if category[1].upper() in [HIDDEN]: - continue - - grpname = category[1] - grouped_params[grpname] = {} - for name, (dflt, descr, cat) in params.items(): - # FIXME bug in default.py? - if isinstance(cat, basestring): - cat = ALL_CATEGORIES[cat] - if cat == category: - if name in extra_params: - # mark easyblock-specific parameters - name = '%s*' % name - grouped_params[grpname].update({name: (descr, dflt)}) - - if not grouped_params[grpname]: - del grouped_params[grpname] - - # compose output, according to specified format (txt, rst, ...) - avail_easyconfig_params_functions = { - FORMAT_RST: self.avail_easyconfig_params_rst, - FORMAT_TXT: self.avail_easyconfig_params_txt, - } - return avail_easyconfig_params_functions[self.options.avail_easyconfig_params](title, grouped_params) - def avail_classes_tree(self, classes, classNames, detailed, depth=0): """Print list of classes as a tree.""" txt = [] From 10aae861ade7e1fd0814d1a4e1421c3f3e9e3bf8 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 9 Jan 2015 21:09:02 +0100 Subject: [PATCH 254/298] fix wrongly defined constants for easyconfig parameter categories in framework/easyconfig/default.py --- easybuild/framework/easyconfig/default.py | 44 ++++++++++---------- easybuild/framework/easyconfig/easyconfig.py | 7 +--- easybuild/tools/docs.py | 5 +-- 3 files changed, 25 insertions(+), 31 deletions(-) diff --git a/easybuild/framework/easyconfig/default.py b/easybuild/framework/easyconfig/default.py index e74b5bb8cd..1c251a7629 100644 --- a/easybuild/framework/easyconfig/default.py +++ b/easybuild/framework/easyconfig/default.py @@ -40,30 +40,30 @@ # we use a tuple here so we can sort them based on the numbers -HIDDEN = "HIDDEN" -MANDATORY = "MANDATORY" -CUSTOM = "CUSTOM" -TOOLCHAIN = "TOOLCHAIN" -BUILD = "BUILD" -FILEMANAGEMENT = "FILEMANAGEMENT" -DEPENDENCIES = "DEPENDENCIES" -LICENSE = "LICENSE" -EXTENSIONS = "EXTENSIONS" -MODULES = "MODULES" -OTHER = "OTHER" +HIDDEN = (-1, 'hidden') +MANDATORY = (0, 'mandatory') +CUSTOM = (1, 'easyblock-specific') +TOOLCHAIN = (2, 'toolchain') +BUILD = (3, 'build') +FILEMANAGEMENT = (4, 'file-management') +DEPENDENCIES = (5, 'dependencies') +LICENSE = (6, 'license') +EXTENSIONS = (7, 'extensions') +MODULES = (8, 'modules') +OTHER = (9, 'other') ALL_CATEGORIES = { - HIDDEN: (-1, 'hidden'), - MANDATORY: (0, 'mandatory'), - CUSTOM: (1, 'easyblock-specific'), - TOOLCHAIN: (2, 'toolchain'), - BUILD: (3, 'build'), - FILEMANAGEMENT: (4, 'file-management'), - DEPENDENCIES: (5, 'dependencies'), - LICENSE: (6, 'license'), - EXTENSIONS: (7, 'extensions'), - MODULES: (8, 'modules'), - OTHER: (9, 'other'), + 'HIDDEN': HIDDEN, + 'MANDATORY': MANDATORY, + 'CUSTOM': CUSTOM, + 'TOOLCHAIN': TOOLCHAIN, + 'BUILD': BUILD, + 'FILEMANAGEMENT': FILEMANAGEMENT, + 'DEPENDENCIES': DEPENDENCIES, + 'LICENSE': LICENSE, + 'EXTENSIONS': EXTENSIONS, + 'MODULES': MODULES, + 'OTHER': OTHER, } # List of tuples. Each tuple has the following format (key, [default, help text, category]) diff --git a/easybuild/framework/easyconfig/easyconfig.py b/easybuild/framework/easyconfig/easyconfig.py index 9a9936265b..94bc9c9db0 100644 --- a/easybuild/framework/easyconfig/easyconfig.py +++ b/easybuild/framework/easyconfig/easyconfig.py @@ -57,7 +57,7 @@ from easybuild.tools.toolchain.utilities import get_toolchain from easybuild.tools.utilities import remove_unwanted_chars from easybuild.framework.easyconfig import MANDATORY -from easybuild.framework.easyconfig.default import DEFAULT_CONFIG, ALL_CATEGORIES, get_easyconfig_parameter_default +from easybuild.framework.easyconfig.default import DEFAULT_CONFIG, get_easyconfig_parameter_default from easybuild.framework.easyconfig.format.convert import Dependency from easybuild.framework.easyconfig.format.one import retrieve_blocks_in_spec from easybuild.framework.easyconfig.licenses import EASYCONFIG_LICENSES_DICT, License @@ -150,10 +150,7 @@ def __init__(self, path, extra_options=None, build_specs=None, validate=True, hi if self.valid_module_classes is not None: self.log.info("Obtained list of valid module classes: %s" % self.valid_module_classes) - # replace the category name with the category - self._config = {} - for k, [def_val, descr, cat] in copy.deepcopy(DEFAULT_CONFIG).items(): - self._config[k] = [def_val, descr, ALL_CATEGORIES[cat]] + self._config = copy.deepcopy(DEFAULT_CONFIG) if extra_options is None: name = fetch_parameter_from_easyconfig_file(path, 'name') diff --git a/easybuild/tools/docs.py b/easybuild/tools/docs.py index ff79f2e2df..4e64029e7a 100644 --- a/easybuild/tools/docs.py +++ b/easybuild/tools/docs.py @@ -35,7 +35,7 @@ """ import copy -from easybuild.framework.easyconfig.default import ALL_CATEGORIES, DEFAULT_CONFIG, HIDDEN, sorted_categories +from easybuild.framework.easyconfig.default import DEFAULT_CONFIG, HIDDEN, sorted_categories from easybuild.framework.easyconfig.easyconfig import get_easyblock_class from easybuild.tools.deprecated.eb_2_0 import ExtraOptionsDeprecatedReturnValue from easybuild.tools.ordereddict import OrderedDict @@ -148,9 +148,6 @@ def avail_easyconfig_params(easyblock, output_format): grpname = category[1] grouped_params[grpname] = {} for name, (dflt, descr, cat) in params.items(): - # FIXME bug in default.py? - if isinstance(cat, basestring): - cat = ALL_CATEGORIES[cat] if cat == category: if name in extra_params: # mark easyblock-specific parameters From 0f67d576ddf19bf3cab8987559bf799e5267c405 Mon Sep 17 00:00:00 2001 From: Jens Timmerman Date: Mon, 12 Jan 2015 13:09:23 +0100 Subject: [PATCH 255/298] fixed remarks --- test/framework/easyblock.py | 22 ++++--------- test/framework/easyconfigs/toy-0.0-patches.eb | 32 ------------------- 2 files changed, 7 insertions(+), 47 deletions(-) delete mode 100644 test/framework/easyconfigs/toy-0.0-patches.eb diff --git a/test/framework/easyblock.py b/test/framework/easyblock.py index 0a4353c96f..d9402bf865 100644 --- a/test/framework/easyblock.py +++ b/test/framework/easyblock.py @@ -28,7 +28,6 @@ @author: Jens Timmerman (Ghent University) @author: Kenneth Hoste (Ghent University) """ -import copy import os import re import shutil @@ -44,7 +43,6 @@ from easybuild.framework.extensioneasyblock import ExtensionEasyBlock from easybuild.tools import config from easybuild.tools.build_log import EasyBuildError -from easybuild.tools.environment import modify_env from easybuild.tools.filetools import mkdir, read_file, write_file from easybuild.tools.modules import modules_tool @@ -99,7 +97,7 @@ def check_extra_options_format(extra_options): name = "pi" version = "3.14" - self.contents = '\n'.join([ + self.contents = '\n'.join([ 'easyblock = "ConfigureMake"', 'name = "%s"' % name, 'version = "%s"' % version, @@ -263,7 +261,6 @@ def test_skip_extensions_step(self): # check if skip skips correct extensions self.writeEC() eb = EasyBlock(EasyConfig(self.eb_file)) - #self.assertTrue('ext1' in eb.exts.keys() and 'ext2' in eb.exts.keys()) eb.builddir = config.build_path() eb.installdir = config.install_path() eb.skip = True @@ -307,7 +304,6 @@ def test_make_module_step(self): self.writeEC() ec = EasyConfig(self.eb_file) eb = EasyBlock(ec) - #eb.builddir = self.test_buildpath eb.installdir = os.path.join(config.install_path(), 'pi', '3.14') eb.check_readiness_step() @@ -389,7 +385,7 @@ def test_get_easyblock_instance(self): testdir = os.path.abspath(os.path.dirname(__file__)) import easybuild eb_blocks_path = os.path.join(testdir, 'sandbox') - if not eb_blocks_path in sys.path: + if eb_blocks_path not in sys.path: sys.path.append(eb_blocks_path) easybuild = reload(easybuild) @@ -411,27 +407,24 @@ def test_patchlevel(self): """Test the parsing of the fetch_patches function.""" # adjust PYTHONPATH such that test easyblocks are found testdir = os.path.abspath(os.path.dirname(__file__)) - ec = process_easyconfig(os.path.join(testdir, 'easyconfigs', 'toy-0.0-patches.eb'))[0] + ec = process_easyconfig(os.path.join(testdir, 'easyconfigs', 'toy-0.0.eb'))[0] eb = get_easyblock_instance(ec) patches = [ - ('toy-0.0_level0_2.patch',0), # should also be level 0 (not None) - ('toy-0.0_level4.patch',4), # should be level4 + ('toy-0.0_level0_2.patch', 0), # should also be level 0 (not None) + ('toy-0.0_level4.patch', 4), # should be level4 ] - sandbox_sources = os.path.join(testdir, 'sandbox', 'sources') - init_config(args=["--sourcepath=%s" % sandbox_sources]) - #check if patch levels are parsed correctly + # check if patch levels are parsed correctly eb.fetch_patches(patches) self.assertEquals(eb.patches[0]['level'], 0) self.assertEquals(eb.patches[1]['level'], 4) patches = [ - ('toy-0.0_level4.patch', False), #should throw an error, only int's an strings allowed here + ('toy-0.0_level4.patch', False), # should throw an error, only int's an strings allowed here ] self.assertRaises(EasyBuildError, eb.fetch_patches, patches) - def test_obtain_file(self): """Test obtain_file method.""" toy_tarball = 'toy-0.0.tar.gz' @@ -533,7 +526,6 @@ def test_exclude_path_to_top_of_module_tree(self): os.environ['EASYBUILD_MODULE_NAMING_SCHEME'] = 'HierarchicalMNS' init_config(build_options=build_options) self.setup_hierarchical_modules() - modtool = modules_tool() modfile_prefix = os.path.join(self.test_installpath, 'modules', 'all') mkdir(os.path.join(modfile_prefix, 'Compiler', 'GCC', '4.8.3'), parents=True) diff --git a/test/framework/easyconfigs/toy-0.0-patches.eb b/test/framework/easyconfigs/toy-0.0-patches.eb deleted file mode 100644 index 89994f6681..0000000000 --- a/test/framework/easyconfigs/toy-0.0-patches.eb +++ /dev/null @@ -1,32 +0,0 @@ -name = 'toy' -version = '0.0' - -homepage = 'http://hpcugent.github.com/easybuild' -description = "Toy C program." - -toolchain = {'name': 'dummy', 'version': 'dummy'} - -sources = [SOURCE_TAR_GZ] -checksums = [[ - 'be662daa971a640e40be5c804d9d7d10', # default (MD5) - ('adler32', '0x998410035'), - ('crc32', '0x1553842328'), - ('md5', 'be662daa971a640e40be5c804d9d7d10'), - ('sha1', 'f618096c52244539d0e89867405f573fdb0b55b0'), - ('size', 273), -]] -patches = [ - 'toy-0.0_level0.patch', # should be level 0 - ('toy-0.0_level0_2.patch',0), # should also be level 0 (not None) - ('toy-0.0_level4.patch',4), # should be level4 - ('toy-0.0_level4.patch', False), #should throw an error, only int's an strings allowed here -] - -sanity_check_paths = { - 'files': [('bin/yot', 'bin/toy')], - 'dirs': ['bin'], -} - -postinstallcmds = ["echo TOY > %(installdir)s/README"] - -moduleclass = 'tools' From 0b2c7c24862dadb6286d59c0fba45577585156b8 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 13 Jan 2015 09:13:19 +0100 Subject: [PATCH 256/298] escape use of '%' in string with cmdline options with --job --- easybuild/tools/parallelbuild.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/easybuild/tools/parallelbuild.py b/easybuild/tools/parallelbuild.py index ac1d5e2f59..f71de69a5f 100644 --- a/easybuild/tools/parallelbuild.py +++ b/easybuild/tools/parallelbuild.py @@ -133,9 +133,10 @@ def submit_jobs(ordered_ecs, cmd_line_opts, testing=False): # generate_cmd_line returns the options in form --longopt=value opts = [x for x in cmd_line_opts if not x.split('=')[0] in ['--%s' % y for y in ignore_opts]] - quoted_opts = subprocess.list2cmdline(opts) + # compose string with command line options, properly quoted and with '%' characters escaped + opts_str = subprocess.list2cmdline(opts).replace('%', '%%') - command = "unset TMPDIR && cd %s && eb %%(spec)s %s --testoutput=%%(output_dir)s" % (curdir, quoted_opts) + command = "unset TMPDIR && cd %s && eb %%(spec)s %s --testoutput=%%(output_dir)s" % (curdir, opts_str) _log.info("Command template for jobs: %s" % command) job_info_lines = [] if testing: From bf1746703d9a0b3c394e94118e4fa88cbc7678ec Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 16 Jan 2015 22:22:21 +0100 Subject: [PATCH 257/298] cleaner solution for defining category constants in default.py --- easybuild/framework/easyconfig/default.py | 38 +++++++++-------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/easybuild/framework/easyconfig/default.py b/easybuild/framework/easyconfig/default.py index 1c251a7629..0d33bd8388 100644 --- a/easybuild/framework/easyconfig/default.py +++ b/easybuild/framework/easyconfig/default.py @@ -40,31 +40,23 @@ # we use a tuple here so we can sort them based on the numbers -HIDDEN = (-1, 'hidden') -MANDATORY = (0, 'mandatory') -CUSTOM = (1, 'easyblock-specific') -TOOLCHAIN = (2, 'toolchain') -BUILD = (3, 'build') -FILEMANAGEMENT = (4, 'file-management') -DEPENDENCIES = (5, 'dependencies') -LICENSE = (6, 'license') -EXTENSIONS = (7, 'extensions') -MODULES = (8, 'modules') -OTHER = (9, 'other') - ALL_CATEGORIES = { - 'HIDDEN': HIDDEN, - 'MANDATORY': MANDATORY, - 'CUSTOM': CUSTOM, - 'TOOLCHAIN': TOOLCHAIN, - 'BUILD': BUILD, - 'FILEMANAGEMENT': FILEMANAGEMENT, - 'DEPENDENCIES': DEPENDENCIES, - 'LICENSE': LICENSE, - 'EXTENSIONS': EXTENSIONS, - 'MODULES': MODULES, - 'OTHER': OTHER, + 'HIDDEN': (-1, 'hidden'), + 'MANDATORY': (0, 'mandatory'), + 'CUSTOM': (1, 'easyblock-specific'), + 'TOOLCHAIN': (2, 'toolchain'), + 'BUILD': (3, 'build'), + 'FILEMANAGEMENT': (4, 'file-management'), + 'DEPENDENCIES': (5, 'dependencies'), + 'LICENSE': (6, 'license'), + 'EXTENSIONS': (7, 'extensions'), + 'MODULES': (8, 'modules'), + 'OTHER': (9, 'other'), } +# define constants so they can be used below +# avoid that pylint complains about unknown variables in this file +# pylint: disable=E0602 +globals().update(ALL_CATEGORIES) # List of tuples. Each tuple has the following format (key, [default, help text, category]) DEFAULT_CONFIG = { From 0a4c4c0356251bc8c668702b511decaa906eb2ec Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 16 Jan 2015 23:18:08 +0100 Subject: [PATCH 258/298] cleanup in fetch_patches, enhance test for fetch_patches, remove useless toy patch files in tests --- easybuild/framework/easyblock.py | 50 +++++++++---------- test/framework/easyblock.py | 35 +++++++++---- .../sandbox/sources/toy/toy-0.0_level0.patch | 0 .../sources/toy/toy-0.0_level0_2.patch | 0 .../sandbox/sources/toy/toy-0.0_level4.patch | 0 5 files changed, 51 insertions(+), 34 deletions(-) delete mode 100644 test/framework/sandbox/sources/toy/toy-0.0_level0.patch delete mode 100644 test/framework/sandbox/sources/toy/toy-0.0_level0_2.patch delete mode 100644 test/framework/sandbox/sources/toy/toy-0.0_level4.patch diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 19a21c090e..c56029875d 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -289,48 +289,48 @@ def fetch_sources(self, list_of_sources, checksums=None): self.log.info("Added sources: %s" % self.src) - def fetch_patches(self, list_of_patches, extension=False, checksums=None): + def fetch_patches(self, patch_specs=None, extension=False, checksums=None): """ Add a list of patches. All patches will be checked if a file exists (or can be located) """ + if patch_specs is None: + patch_specs = self.cfg['patches'] patches = [] - for index, patch_entry in enumerate(list_of_patches): + for index, patch_spec in enumerate(patch_specs): # check if the patches can be located copy_file = False suff = None level = None - if isinstance(patch_entry, (list, tuple)): - if not len(patch_entry) == 2: + if isinstance(patch_spec, (list, tuple)): + if not len(patch_spec) == 2: self.log.error("Unknown patch specification '%s', only two-element lists/tuples are supported!", - str(patch_entry)) - pf = patch_entry[0] - - if type(patch_entry[1]) == int: # int and only int is allowed here, we are parsing a config file, not - # trying to write generic code - level = patch_entry[1] - elif isinstance(patch_entry[1], basestring): + str(patch_spec)) + patch_file = patch_spec[0] + + # this *must* be of typ int, nothing else + # no 'isinstance(..., int)', since that would make True/False also acceptable + if type(patch_spec[1]) == int: + level = patch_spec[1] + elif isinstance(patch_spec[1], basestring): # non-patch files are assumed to be files to copy - if not patch_entry[0].endswith('.patch'): + if not patch_spec[0].endswith('.patch'): copy_file = True - suff = patch_entry[1] + suff = patch_spec[1] else: - self.log.error( - "Wrong patch specification '%s', only int and string are supported as second element!", - str(patch_entry), - ) + self.log.error("Wrong patch spec '%s', only int/string are supported as 2nd element" % str(patch_spec)) else: - pf = patch_entry + patch_file = patch_spec - path = self.obtain_file(pf, extension=extension) + path = self.obtain_file(patch_file, extension=extension) if path: - self.log.debug('File %s found for patch %s' % (path, patch_entry)) + self.log.debug('File %s found for patch %s' % (path, patch_spec)) patchspec = { - 'name': pf, + 'name': patch_file, 'path': path, - 'checksum': self.get_checksum_for(checksums, filename=pf, index=index), + 'checksum': self.get_checksum_for(checksums, filename=patch_file, index=index), } if suff: if copy_file: @@ -345,7 +345,7 @@ def fetch_patches(self, list_of_patches, extension=False, checksums=None): else: self.patches.append(patchspec) else: - self.log.error('No file found for patch %s' % patch_entry) + self.log.error('No file found for patch %s' % patch_spec) if extension: self.log.info("Fetched extension patches: %s" % patches) @@ -412,7 +412,7 @@ def fetch_extension_sources(self): else: self.log.error('Checksum for ext source %s failed' % fn) - ext_patches = self.fetch_patches(ext_options.get('patches', []), extension=True) + ext_patches = self.fetch_patches(patch_specs=ext_options.get('patches', []), extension=True) if ext_patches: self.log.debug('Found patches for extension %s: %s' % (ext_name, ext_patches)) ext_src.update({'patches': ext_patches}) @@ -1228,7 +1228,7 @@ def fetch_step(self, skip_checksums=False): patches_checksums = self.cfg['checksums'][len(self.cfg['sources']):] else: patches_checksums = self.cfg['checksums'] - self.fetch_patches(self.cfg['patches'], checksums=patches_checksums) + self.fetch_patches(checksums=patches_checksums) else: self.log.info('no patches provided') diff --git a/test/framework/easyblock.py b/test/framework/easyblock.py index d9402bf865..78e423853b 100644 --- a/test/framework/easyblock.py +++ b/test/framework/easyblock.py @@ -403,27 +403,44 @@ def test_get_easyblock_instance(self): logtxt = read_file(eb.logfile) self.assertTrue(eb_log_msg_re.search(logtxt), "Pattern '%s' found in: %s" % (eb_log_msg_re.pattern, logtxt)) - def test_patchlevel(self): - """Test the parsing of the fetch_patches function.""" + def test_fetch_patches(self): + """Test fetch_patches method.""" # adjust PYTHONPATH such that test easyblocks are found testdir = os.path.abspath(os.path.dirname(__file__)) ec = process_easyconfig(os.path.join(testdir, 'easyconfigs', 'toy-0.0.eb'))[0] eb = get_easyblock_instance(ec) + eb.fetch_patches() + self.assertEqual(len(eb.patches), 1) + self.assertEqual(eb.patches[0]['name'], 'toy-0.0_typo.patch') + self.assertFalse('level' in eb.patches[0]) + + # reset + eb.patches = [] + patches = [ - ('toy-0.0_level0_2.patch', 0), # should also be level 0 (not None) - ('toy-0.0_level4.patch', 4), # should be level4 + ('toy-0.0_typo.patch', 0), # should also be level 0 (not None or something else) + ('toy-0.0_typo.patch', 4), # should be level 4 + ('toy-0.0_typo.patch', 'foobar'), # sourcepath should be set to 'foobar' + ('toy-0.0.tar.gz', 'some/path'), # copy mode (not a .patch file) ] # check if patch levels are parsed correctly - eb.fetch_patches(patches) - - self.assertEquals(eb.patches[0]['level'], 0) - self.assertEquals(eb.patches[1]['level'], 4) + eb.fetch_patches(patch_specs=patches) + + self.assertEqual(len(eb.patches), 4) + self.assertEqual(eb.patches[0]['name'], 'toy-0.0_typo.patch') + self.assertEqual(eb.patches[0]['level'], 0) + self.assertEqual(eb.patches[1]['name'], 'toy-0.0_typo.patch') + self.assertEqual(eb.patches[1]['level'], 4) + self.assertEqual(eb.patches[2]['name'], 'toy-0.0_typo.patch') + self.assertEqual(eb.patches[2]['sourcepath'], 'foobar') + self.assertEqual(eb.patches[3]['name'], 'toy-0.0.tar.gz'), + self.assertEqual(eb.patches[3]['copy'], 'some/path') patches = [ ('toy-0.0_level4.patch', False), # should throw an error, only int's an strings allowed here ] - self.assertRaises(EasyBuildError, eb.fetch_patches, patches) + self.assertRaises(EasyBuildError, eb.fetch_patches, patch_specs=patches) def test_obtain_file(self): """Test obtain_file method.""" diff --git a/test/framework/sandbox/sources/toy/toy-0.0_level0.patch b/test/framework/sandbox/sources/toy/toy-0.0_level0.patch deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/test/framework/sandbox/sources/toy/toy-0.0_level0_2.patch b/test/framework/sandbox/sources/toy/toy-0.0_level0_2.patch deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/test/framework/sandbox/sources/toy/toy-0.0_level4.patch b/test/framework/sandbox/sources/toy/toy-0.0_level4.patch deleted file mode 100644 index e69de29bb2..0000000000 From b04a035a313593212c07690130c2e1511b5465aa Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 21 Jan 2015 13:51:37 +0100 Subject: [PATCH 259/298] drop support for deprecated functionality --- easybuild/easybuild_config.py | 96 ------- easybuild/framework/easyblock.py | 70 +----- easybuild/framework/easyconfig/easyconfig.py | 96 +++---- .../framework/easyconfig/format/format.py | 2 +- .../easyconfig/format/pyheaderconfigobj.py | 2 +- easybuild/framework/extensioneasyblock.py | 3 +- easybuild/main.py | 1 - easybuild/toolchains/fft/intelfftw.py | 2 +- easybuild/tools/build_log.py | 12 +- easybuild/tools/config.py | 237 ++---------------- easybuild/tools/deprecated/eb_2_0.py | 74 ------ easybuild/tools/docs.py | 3 - easybuild/tools/filetools.py | 25 +- easybuild/tools/modules.py | 72 +++--- easybuild/tools/options.py | 21 +- easybuild/tools/systemtools.py | 32 +-- easybuild/tools/toolchain/toolchain.py | 1 - easybuild/tools/utilities.py | 21 +- 18 files changed, 127 insertions(+), 643 deletions(-) delete mode 100644 easybuild/easybuild_config.py delete mode 100644 easybuild/tools/deprecated/eb_2_0.py diff --git a/easybuild/easybuild_config.py b/easybuild/easybuild_config.py deleted file mode 100644 index 5befd7bf58..0000000000 --- a/easybuild/easybuild_config.py +++ /dev/null @@ -1,96 +0,0 @@ -# # -# Copyright 2009-2014 Ghent University -# -# This file is part of EasyBuild, -# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en), -# with support of Ghent University (http://ugent.be/hpc), -# the Flemish Supercomputer Centre (VSC) (https://vscentrum.be/nl/en), -# the Hercules foundation (http://www.herculesstichting.be/in_English) -# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en). -# -# http://github.com/hpcugent/easybuild -# -# EasyBuild is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation v2. -# -# EasyBuild is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with EasyBuild. If not, see . -# # -""" -EasyBuild configuration file. - This is now frozen. - All new configuration should be done through the options parser. - This is deprecated and will be removed in 2.0 - -@author: Stijn De Weirdt (Ghent University) -@author: Dries Verdegem (Ghent University) -@author: Kenneth Hoste (Ghent University) -@author: Pieter De Baets (Ghent University) -@author: Jens Timmerman (Ghent University) -@author: Toon Willems (Ghent University) -@author: Fotis Georgatos (Uni.Lu, NTUA) -""" - -# -# Developers, please do not add any new defaults or variables -# Use the config options -# - -import os -import tempfile - -import easybuild.tools.config as config - -# this should result in a MODULEPATH=($HOME/.local/easybuild|$EASYBUILDPREFIX)//all -if os.getenv('EASYBUILDPREFIX'): - prefix = os.getenv('EASYBUILDPREFIX') -else: - prefix = os.path.join(os.getenv('HOME'), ".local", "easybuild") - -# build/install/source paths configuration for EasyBuild -# build_path possibly overridden by EASYBUILDBUILDPATH -# install_path possibly overridden by EASYBUILDINSTALLPATH -build_path = os.path.join(prefix, 'build') -install_path = prefix -source_path = os.path.join(prefix, 'sources') - -# repository for eb files -# currently, EasyBuild supports the following repository types: - -# * `FileRepository`: a plain flat file repository. In this case, the `repositoryPath` contains the directory where the files are stored, -# * `GitRepository`: a _non-empty_ **bare** git repository (created with `git init --bare` or `git clone --bare`). -# Here, the `repositoryPath` contains the git repository location, which can be a directory or an URL. -# * `SvnRepository`: an SVN repository. In this case, the `repositoryPath` contains the subversion repository location, again, this can be a directory or an URL. - -# you have to set the `repository` variable inside the config like so: -# `repository = FileRepository(repositoryPath)` - -# optionally a subdir argument can be specified: -# `repository = FileRepository(repositoryPath, subdir)` -repository_path = os.path.join(prefix, 'ebfiles_repo') -repository = FileRepository(repository_path) # @UndefinedVariable (this file gets exec'ed, so ignore this) - -# log format: (dir, filename template) -# supported in template: name, version, data, time -log_format = ("easybuild", "easybuild-%(name)s-%(version)s-%(date)s.%(time)s.log") - -# set the path where log files will be stored -log_dir = tempfile.gettempdir() - -# define set of supported module classes -module_classes = ['base', 'bio', 'chem', 'compiler', 'lib', 'phys', 'tools', - 'cae', 'data', 'debugger', 'devel', 'ide', 'math', 'mpi', 'numlib', 'perf', 'system', 'vis'] - -# general cleanliness -del os, tempfile, config, prefix - -# -# Developers, please do not add any new defaults or variables -# Use the config options -# diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index c56029875d..c29563a99f 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -51,7 +51,7 @@ import easybuild.tools.environment as env from easybuild.tools import config, filetools from easybuild.framework.easyconfig import EASYCONFIGS_PKG_SUBDIR -from easybuild.framework.easyconfig.easyconfig import DEFAULT_EASYBLOCK, ITERATE_OPTIONS, EasyConfig, ActiveMNS +from easybuild.framework.easyconfig.easyconfig import ITERATE_OPTIONS, EasyConfig, ActiveMNS from easybuild.framework.easyconfig.easyconfig import fetch_parameter_from_easyconfig_file from easybuild.framework.easyconfig.easyconfig import get_easyblock_class, get_module_path, resolve_template from easybuild.framework.easyconfig.tools import get_paths_for @@ -60,7 +60,6 @@ from easybuild.tools.build_log import EasyBuildError, print_error, print_msg from easybuild.tools.config import build_option, build_path, get_log_filename, get_repository, get_repositorypath from easybuild.tools.config import install_path, log_path, read_only_installdir, source_paths -from easybuild.tools.deprecated.eb_2_0 import ExtraOptionsDeprecatedReturnValue from easybuild.tools.environment import restore_env from easybuild.tools.filetools import DEFAULT_CHECKSUM from easybuild.tools.filetools import adjust_permissions, apply_patch, convert_name, download_file, encode_class_name @@ -98,17 +97,9 @@ def extra_options(extra=None): extra = {} if not isinstance(extra, dict): - typ = type(extra) - if not isinstance(extra, ExtraOptionsDeprecatedReturnValue): - _log.deprecated("Obtained 'extra' value of type '%s' in extra_options, should be 'dict'" % typ, '2.0') - _log.debug("Converting extra_options value '%s' of type '%s' to a dict" % (extra, typ)) - extra = dict(extra) - - # to avoid breaking backward compatibility, we still need to return a list of tuples in EasyBuild v1.x - # starting with EasyBuild v2.0, this will be changed to return the actual dict - # as a temporary workaround, return a value which is a hybrid between a list and a dict - res = ExtraOptionsDeprecatedReturnValue(extra.items()) - return res + _log.nosupport("Obtained 'extra' value of type '%s' in extra_options, should be 'dict'" % type(extra), '2.0') + + return extra # # INIT @@ -627,8 +618,7 @@ def moduleGenerator(self): """ Module generator (DEPRECATED, use self.module_generator instead). """ - self.log.deprecated("self.moduleGenerator is replaced by self.module_generator", "2.0") - return self.module_generator + self.log.nosupport("self.moduleGenerator is replaced by self.module_generator", '2.0') # # DIRECTORY UTILITY FUNCTIONS @@ -770,14 +760,14 @@ def make_devel_module(self, create_in_builddir=False): # these should be all the dependencies and we should load them for key in os.environ: # legacy support - if key.startswith(DEVEL_ENV_VAR_NAME_PREFIX) or key.startswith("SOFTDEVEL"): - if key.startswith("SOFTDEVEL"): - self.log.deprecated("Environment variable SOFTDEVEL* being relied on", "2.0") + if key.startswith(DEVEL_ENV_VAR_NAME_PREFIX): if not key.endswith(convert_name(self.name, upper=True)): path = os.environ[key] if os.path.isfile(path): mod_name = path.rsplit(os.path.sep, 1)[-1] load_txt += mod_gen.load_module(mod_name) + elif key.startswith('SOFTDEVEL'): + self.log.nosupport("Environment variable SOFTDEVEL* being relied on", '2.0') if create_in_builddir: output_dir = self.builddir @@ -1075,15 +1065,8 @@ def skip_extensions(self): try: cmd = cmdtmpl % tmpldict except KeyError, err: - self.log.warning("Failed to complete filter cmd templ '%s' using %s: %s" % (cmdtmpl, tmpldict, err)) - deprecated_msg = "Providing 'name'/'version' keys for extensions, should use 'ext_name', 'ext_version'" - self.log.deprecated(deprecated_msg, '2.0') - tmpldict.update({ - 'name': modname, - 'version': ext.get('version'), - }) - self.log.debug("Retrying to complete filter cmd templ with added name/version keys: %s" % tmpldict) - cmd = cmdtmpl % tmpldict + msg = "Use of 'name'/'version' keys for extensions filter, should use 'ext_name', 'ext_version' instead" + self.log.nosupport(msg, '2.0') if cmdinputtmpl: stdin = cmdinputtmpl % tmpldict @@ -1386,18 +1369,8 @@ def extensions_step(self, fetch=False): self.log.error("ERROR: No default extension class set for %s" % self.name) # obtain name and module path for default extention class - legacy = False if hasattr(exts_defaultclass, '__iter__'): - # LEGACY: module path is explicitely specified - self.log.deprecated("Using specified module path for default class", "2.0") - default_class_modpath = exts_defaultclass[0] - default_class = exts_defaultclass[1] - derived_mod_path = get_module_path(default_class, generic=True) - if not default_class_modpath == derived_mod_path: - msg = "Specified module path for default class %s " % default_class_modpath - msg += "doesn't match derived path %s" % derived_mod_path - self.log.warning(msg) - legacy = True + self.log.nosupport("Using specified module path for default class", '2.0') elif isinstance(exts_defaultclass, basestring): # proper way: derive module path from specified class name @@ -1429,22 +1402,6 @@ def extensions_step(self, fetch=False): except (ImportError, NameError), err: self.log.debug("Failed to use extension-specific class for extension %s: %s" % (ext['name'], err)) - # LEGACY: try and use default module path for getting extension class instance - if inst is None and legacy: - self.log.deprecated("Using specified module path for default class", '2.0') - try: - msg = "Failed to use derived module path for %s, " % class_name - msg += "considering specified module path as (legacy) fallback." - self.log.debug(msg) - mod_path = default_class_modpath - cls = get_class_for(mod_path, class_name) - inst = cls(self, ext) - except (ImportError, NameError), err: - self.log.debug("Failed to use class %s from %s for extension %s: %s" % (class_name, - mod_path, - ext['name'], - err)) - # alternative attempt: use class specified in class map (if any) if inst is None and ext['name'] in exts_classmap: @@ -1454,9 +1411,8 @@ def extensions_step(self, fetch=False): cls = get_class_for(mod_path, class_name) inst = cls(self, ext) except (ImportError, NameError), err: - self.log.error("Failed to load specified class %s for extension %s: %s" % (class_name, - ext['name'], - err)) + tup = (class_name, ext['name'], err) + self.log.error("Failed to load specified class %s for extension %s: %s" % tup) # fallback attempt: use default class if inst is None: diff --git a/easybuild/framework/easyconfig/easyconfig.py b/easybuild/framework/easyconfig/easyconfig.py index 94bc9c9db0..1a50efb87c 100644 --- a/easybuild/framework/easyconfig/easyconfig.py +++ b/easybuild/framework/easyconfig/easyconfig.py @@ -40,13 +40,12 @@ import os import re from vsc.utils import fancylogger -from vsc.utils.missing import any, get_class_for, nub +from vsc.utils.missing import get_class_for, nub from vsc.utils.patterns import Singleton import easybuild.tools.environment as env from easybuild.tools.build_log import EasyBuildError from easybuild.tools.config import build_option, get_module_naming_scheme -from easybuild.tools.deprecated.eb_2_0 import ExtraOptionsDeprecatedReturnValue from easybuild.tools.filetools import decode_class_name, encode_class_name, read_file from easybuild.tools.module_naming_scheme import DEVEL_MODULE_SUFFIX from easybuild.tools.module_naming_scheme.utilities import avail_module_naming_schemes, det_full_ec_version @@ -73,50 +72,31 @@ # set of configure/build/install options that can be provided as lists for an iterated build ITERATE_OPTIONS = ['preconfigopts', 'configopts', 'prebuildopts', 'buildopts', 'preinstallopts', 'installopts'] -# map of deprecated easyconfig parameters, and their replacements -DEPRECATED_OPTIONS = { - 'license': ('software_license', '2.0'), - 'makeopts': ('buildopts', '2.0'), - 'premakeopts': ('prebuildopts', '2.0'), +# replaced easyconfig parameters, and their replacements +REPLACED_PARAMETERS = { + 'license': 'software_license', + 'makeopts': 'buildopts', + 'premakeopts': 'prebuildopts', } -DEFAULT_EASYBLOCK = 'ConfigureMake' - _easyconfig_files_cache = {} _easyconfigs_cache = {} -def handle_deprecated_easyconfig_parameter(ec_method): - """Decorator to handle deprecated easyconfig parameters.""" +def handle_replaced_easyconfig_parameter(ec_method): + """Decorator to handle replaced easyconfig parameters.""" def new_ec_method(self, key, *args, **kwargs): - """Map deprecated easyconfig parameters to the new correct parameter.""" - # map name of deprecated easyconfig parameter to new name - if key in DEPRECATED_OPTIONS: - depr_key = key - key, ver = DEPRECATED_OPTIONS[depr_key] - _log.deprecated("Easyconfig parameter '%s' is deprecated, use '%s' instead." % (depr_key, key), ver) + """Map replaced easyconfig parameters to the new correct parameter.""" + # map name of replaced easyconfig parameter to new name + if key in REPLACED_PARAMETERS: + _log.nosupport("Easyconfig parameter '%s' is replaced by '%s'" % (key, REPLACED_PARAMETERS[key]), '2.0') # make sure that value for software_license has correct type, convert if needed if key == 'software_license': # key 'license' will already be mapped to 'software_license' above lic = self._config['software_license'][0] if lic is not None and not isinstance(lic, License): - self.log.deprecated('Type for software_license must to be instance of License (sub)class', '2.0') - lic_type = type(lic) - - class LicenseLegacy(License, lic_type): - """A special License class to deal with legacy license parameters""" - DESCRICPTION = ("Internal-only, legacy closed license class to deprecate license parameter." - " (DO NOT USE).") - HIDDEN = False - - def __init__(self, *args): - if len(args) > 0: - lic_type.__init__(self, args[0]) - License.__init__(self) - lic = LicenseLegacy(lic) - EASYCONFIG_LICENSES_DICT[lic.name] = lic - self._config['software_license'] = lic + self.log.nosupport('Type for software_license must to be instance of License (sub)class', '2.0') return ec_method(self, key, *args, **kwargs) @@ -161,16 +141,8 @@ def __init__(self, path, extra_options=None, build_specs=None, validate=True, hi self.extra_options = extra_options if not isinstance(self.extra_options, dict): - if isinstance(self.extra_options, (list, tuple, ExtraOptionsDeprecatedReturnValue)): - typ = type(self.extra_options) - if not isinstance(self.extra_options, ExtraOptionsDeprecatedReturnValue): - self.log.deprecated("extra_options return value should be of type 'dict', found '%s'" % typ, '2.0') - tup = (self.extra_options, type(self.extra_options)) - self.log.debug("Converting extra_options value '%s' of type '%s' to a dict" % tup) - self.extra_options = dict(self.extra_options) - else: - tup = (type(self.extra_options), self.extra_options) - self.log.error("extra_options parameter passed is of incorrect type: %s ('%s')" % tup) + tup = (type(self.extra_options), self.extra_options) + self.log.nosupport("extra_options return value should be of type 'dict', found '%s': %s" % tup, '2.0') self._config.update(self.extra_options) @@ -279,7 +251,7 @@ def parse(self): for key in ['toolchain'] + local_vars.keys(): # validations are skipped, just set in the config # do not store variables we don't need - if key in self._config.keys() + DEPRECATED_OPTIONS.keys(): + if key in self._config.keys(): if key in ['builddependencies', 'dependencies']: self[key] = [self._parse_dependency(dep) for dep in local_vars[key]] elif key in ['hiddendependencies']: @@ -288,6 +260,8 @@ def parse(self): self[key] = local_vars[key] tup = (key, self[key], type(self[key])) self.log.info("setting config option %s: value %s (type: %s)" % tup) + elif key in REPLACED_PARAMETERS: + _log.nosupport("Easyconfig parameter '%s' is replaced by '%s'" % (key, REPLACED_PARAMETERS[key]), '2.0') else: self.log.debug("Ignoring unknown config option %s (value: %s)" % (key, local_vars[key])) @@ -654,7 +628,7 @@ def _generate_template_values(self, ignore=None, skip_lower=True): if v is None: del self.template_values[k] - @handle_deprecated_easyconfig_parameter + @handle_replaced_easyconfig_parameter def __getitem__(self, key): """ will return the value without the help text @@ -667,7 +641,7 @@ def __getitem__(self, key): else: return value - @handle_deprecated_easyconfig_parameter + @handle_replaced_easyconfig_parameter def __setitem__(self, key, value): """ sets the value of key in config. @@ -702,14 +676,7 @@ def asdict(self): def det_installversion(version, toolchain_name, toolchain_version, prefix, suffix): """Deprecated 'det_installversion' function, to determine exact install version, based on supplied parameters.""" old_fn = 'framework.easyconfig.easyconfig.det_installversion' - _log.deprecated('Use module_generator.det_full_ec_version instead of %s' % old_fn, '2.0') - cfg = { - 'version': version, - 'toolchain': {'name': toolchain_name, 'version': toolchain_version}, - 'versionprefix': prefix, - 'versionsuffix': suffix, - } - return det_full_ec_version(cfg) + _log.nosupport('Use det_full_ec_version from easybuild.tools.module_generator instead of %s' % old_fn, '2.0') def fetch_parameter_from_easyconfig_file(path, param): @@ -774,8 +741,7 @@ def get_easyblock_class(easyblock, name=None, default_fallback=True, error_on_fa modulepath_bis = get_module_path(name, decode=False) _log.debug("Module path determined based on software name: %s" % modulepath_bis) if modulepath_bis != modulepath: - _log.deprecated("Determine module path based on software name", "2.0") - modulepath = modulepath_bis + _log.nosupport("Determine module path based on software name", '2.0') # try and find easyblock try: @@ -783,24 +749,18 @@ def get_easyblock_class(easyblock, name=None, default_fallback=True, error_on_fa cls = get_class_for(modulepath, class_name) _log.info("Successfully obtained %s class instance from %s" % (class_name, modulepath)) except ImportError, err: - # when an ImportError occurs, make sure that it's caused by not finding the easyblock module, # and not because of a broken import statement in the easyblock module error_re = re.compile(r"No module named %s" % modulepath.replace("easybuild.easyblocks.", '')) _log.debug("error regexp: %s" % error_re.pattern) if error_re.match(str(err)): if default_fallback: - # no easyblock could be found, so fall back to default class. - def_class = DEFAULT_EASYBLOCK - def_mod_path = get_module_path(def_class, generic=True) - - _log.warning("Failed to import easyblock for %s, falling back to default class %s: error: %s" % \ - (class_name, (def_mod_path, def_class), err)) - - depr_msg = "Fallback to default easyblock %s (from %s)" % (def_class, def_mod_path) - depr_msg += "; use \"easyblock = '%s'\" in easyconfig file?" % def_class - _log.deprecated(depr_msg, '2.0') - cls = get_class_for(def_mod_path, def_class) + # no easyblock could be found, so fall back to ConfigureMake (NO LONGER SUPPORTED) + legacy_fallback_easyblock = 'ConfigureMake' + def_mod_path = get_module_path(legacy_fallback_easyblock, generic=True) + depr_msg = "Fallback to default easyblock %s (from %s)" % (legacy_fallback_easyblock, def_mod_path) + depr_msg += "; use \"easyblock = '%s'\" in easyconfig file?" % legacy_fallback_easyblock + _log.nosupport(depr_msg, '2.0') else: if error_on_failed_import: _log.error("Failed to import easyblock for %s because of module issue: %s" % (class_name, err)) diff --git a/easybuild/framework/easyconfig/format/format.py b/easybuild/framework/easyconfig/format/format.py index 4003f5682d..7fb06cc2f9 100644 --- a/easybuild/framework/easyconfig/format/format.py +++ b/easybuild/framework/easyconfig/format/format.py @@ -32,7 +32,7 @@ import copy import re from vsc.utils import fancylogger -from vsc.utils.missing import get_subclasses, any +from vsc.utils.missing import get_subclasses from easybuild.framework.easyconfig.format.version import EasyVersion, OrderedVersionOperators from easybuild.framework.easyconfig.format.version import ToolchainVersionOperator, VersionOperator diff --git a/easybuild/framework/easyconfig/format/pyheaderconfigobj.py b/easybuild/framework/easyconfig/format/pyheaderconfigobj.py index 64695589b7..7728792947 100644 --- a/easybuild/framework/easyconfig/format/pyheaderconfigobj.py +++ b/easybuild/framework/easyconfig/format/pyheaderconfigobj.py @@ -180,7 +180,7 @@ def parse_pyheader(self, pyheader): # check for use of deprecated magic easyconfigs variables for magic_var in build_easyconfig_variables_dict(): if re.search(magic_var, pyheader, re.M): - _log.deprecated("Magic 'global' easyconfigs variable %s should no longer be used" % magic_var, '2.0') + _log.nosupport("Magic 'global' easyconfigs variable %s should no longer be used" % magic_var, '2.0') try: exec(pyheader, global_vars, local_vars) diff --git a/easybuild/framework/extensioneasyblock.py b/easybuild/framework/extensioneasyblock.py index 1308561fb9..a7a5ba6488 100644 --- a/easybuild/framework/extensioneasyblock.py +++ b/easybuild/framework/extensioneasyblock.py @@ -58,8 +58,7 @@ def extra_options(extra_vars=None): extra_vars = {} if not isinstance(extra_vars, dict): - _log.deprecated("Obtained value of type '%s' for extra_vars, should be 'dict'" % type(extra_vars), '2.0') - extra_vars = dict(extra_vars) + _log.nosupport("Obtained value of type '%s' for extra_vars, should be 'dict'" % type(extra_vars), '2.0') extra_vars.update({ 'options': [{}, "Dictionary with extension options.", CUSTOM], diff --git a/easybuild/main.py b/easybuild/main.py index a97ce592f1..f1ab88af41 100644 --- a/easybuild/main.py +++ b/easybuild/main.py @@ -39,7 +39,6 @@ import os import sys import traceback -from vsc.utils.missing import any # IMPORTANT this has to be the first easybuild import as it customises the logging # expect missing log output when this not the case! diff --git a/easybuild/toolchains/fft/intelfftw.py b/easybuild/toolchains/fft/intelfftw.py index 12f35035b9..a6eb1e1eaa 100644 --- a/easybuild/toolchains/fft/intelfftw.py +++ b/easybuild/toolchains/fft/intelfftw.py @@ -33,7 +33,7 @@ from easybuild.toolchains.fft.fftw import Fftw from easybuild.tools.modules import get_software_root, get_software_version -from easybuild.tools.utilities import all, any + class IntelFFTW(Fftw): """FFTW wrapper functionality of Intel MKL""" diff --git a/easybuild/tools/build_log.py b/easybuild/tools/build_log.py index b447090e0e..27ee40708e 100644 --- a/easybuild/tools/build_log.py +++ b/easybuild/tools/build_log.py @@ -101,6 +101,10 @@ def deprecated(self, msg, max_ver): msg += "; see %s for more information" % DEPRECATED_DOC_URL fancylogger.FancyLogger.deprecated(self, msg, str(CURRENT_VERSION), max_ver, exception=EasyBuildError) + def nosupport(self, msg, ver): + """Print error message for no longer supported behaviour, and raise an EasyBuildError.""" + self.error("NO LONGER SUPPORTED: %s; see %s for more information" % (msg, DEPRECATED_DOC_URL)) + def error(self, msg, *args, **kwargs): """Print error message and raise an EasyBuildError.""" newMsg = "EasyBuild crashed with an error %s: %s" % (self.caller_info(), msg) @@ -165,13 +169,9 @@ def stop_logging(logfile, logtostdout=False): def get_log(name=None): """ - Generate logger object + (NO LONGER SUPPORTED!) Generate logger object """ - # fname is always get_log, useless - log = fancylogger.getLogger(name, fname=False) - log.info("Logger started for %s." % name) - log.deprecated("get_log", "2.0") - return log + log.nosupport("Use of get_log function", '2.0') def print_msg(msg, log=None, silent=False, prefix=True): diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py index d66e269a3c..be244d5daf 100644 --- a/easybuild/tools/config.py +++ b/easybuild/tools/config.py @@ -51,9 +51,6 @@ _log = fancylogger.getLogger('config', fname=False) -# class constant to prepare migration to generaloption as only way of configuration (maybe for v2.X) -SUPPORT_OLDSTYLE = True -DEFAULT_OLDSTYLE_CONFIG_FILE = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'easybuild_config.py') DEFAULT_LOGFILE_FORMAT = ("easybuild", "easybuild-%(name)s-%(version)s-%(date)s.%(time)s.log") DEFAULT_MNS = 'EasyBuildMNS' @@ -68,7 +65,7 @@ } DEFAULT_PREFIX = os.path.join(os.path.expanduser('~'), ".local", "easybuild") DEFAULT_REPOSITORY = 'FileRepository' -DEFAULT_TMP_LOGDIR = tempfile.gettempdir() + # utility function for obtaining default paths def mk_full_default_path(name, prefix=DEFAULT_PREFIX): @@ -170,49 +167,14 @@ def mk_full_default_path(name, prefix=DEFAULT_PREFIX): ] -OLDSTYLE_ENVIRONMENT_VARIABLES = { - 'build_path': 'EASYBUILDBUILDPATH', - 'config_file': 'EASYBUILDCONFIG', - 'install_path': 'EASYBUILDINSTALLPATH', - 'log_format': 'EASYBUILDLOGFORMAT', - 'log_dir': 'EASYBUILDLOGDIR', - 'source_path': 'EASYBUILDSOURCEPATH', - 'test_output_path': 'EASYBUILDTESTOUTPUT', -} - - -OLDSTYLE_NEWSTYLE_MAP = { - 'build_path': 'buildpath', - 'install_path': 'installpath', - 'log_dir': 'tmp_logdir', - 'config_file': 'config', - 'source_path': 'sourcepath', - 'log_format': 'logfile_format', - 'test_output_path': 'testoutput', - 'module_classes': 'moduleclasses', - 'repository_path': 'repositorypath', - 'modules_install_suffix': 'subdir_modules', - 'software_install_suffix': 'subdir_software', -} - - -def map_to_newstyle(adict): - """Map a dictionary with oldstyle keys to the new style.""" - res = {} - for key, val in adict.items(): - if key in OLDSTYLE_NEWSTYLE_MAP: - key = OLDSTYLE_NEWSTYLE_MAP[key] - res[key] = val - return res - - class ConfigurationVariables(FrozenDictKnownKeys): """This is a dict that supports legacy config names transparently.""" # singleton metaclass: only one instance is created __metaclass__ = Singleton - REQUIRED = [ + # list of know/required keys + KNOWN_KEYS = [ 'config', 'prefix', 'buildpath', @@ -229,14 +191,12 @@ class ConfigurationVariables(FrozenDictKnownKeys): 'module_naming_scheme', ] - KNOWN_KEYS = nub(OLDSTYLE_NEWSTYLE_MAP.values() + REQUIRED) - def get_items_check_required(self, no_missing=True): """ - For all REQUIRED, check if exists and return all key,value pairs. + For all known/required keys, check if exists and return all key/value pairs. no_missing: boolean, when True, will throw error message for missing values """ - missing = [x for x in self.REQUIRED if not x in self] + missing = [x for x in self.KNOWN_KEYS if not x in self] if len(missing) > 0: msg = 'Cannot determine value for configuration variables %s. Please specify it.' % missing if no_missing: @@ -256,50 +216,10 @@ class BuildOptions(FrozenDictKnownKeys): KNOWN_KEYS = [k for kss in [BUILD_OPTIONS_CMDLINE, BUILD_OPTIONS_OTHER] for ks in kss.values() for k in ks] -def get_user_easybuild_dir(): - """Return the per-user easybuild dir (e.g. to store config files)""" - oldpath = os.path.join(os.path.expanduser('~'), ".easybuild") - xdg_config_home = os.environ.get("XDG_CONFIG_HOME", os.path.join(os.path.expanduser('~'), ".config")) - newpath = os.path.join(xdg_config_home, "easybuild") - - # only issue deprecation warning/error if new path doesn't exist, but deprecated path does - if not os.path.isdir(newpath) and os.path.isdir(oldpath): - _log.deprecated("The user easybuild dir has moved from %s to %s." % (oldpath, newpath), "2.0") - return oldpath - - # if neither exist, new path wins - return newpath - - -def get_default_oldstyle_configfile(): - """Get the default location of the oldstyle config file to be set as default in the options""" - # TODO these _log.debug here can't be controlled/set with the generaloption - # - check environment variable EASYBUILDCONFIG - # - next, check for an EasyBuild config in $HOME/.easybuild/config.py - # - last, use default config file easybuild_config.py in main.py directory - config_env_var = OLDSTYLE_ENVIRONMENT_VARIABLES['config_file'] - home_config_file = os.path.join(get_user_easybuild_dir(), "config.py") - if os.getenv(config_env_var): - _log.debug("Environment variable %s, so using that as config file." % config_env_var) - config_file = os.getenv(config_env_var) - elif os.path.exists(home_config_file): - config_file = home_config_file - _log.debug("Found EasyBuild configuration file at %s." % config_file) - else: - # this should be easybuild.tools.config, the default config file is - # part of framework in easybuild (ie in tool/..) - if os.path.exists(DEFAULT_OLDSTYLE_CONFIG_FILE): - config_file = DEFAULT_OLDSTYLE_CONFIG_FILE - _log.debug("Falling back to default config: %s" % config_file) - else: - config_file = None - - return config_file - - def get_default_configfiles(): """Return a list of default configfiles for tools.options/generaloption""" - return [os.path.join(get_user_easybuild_dir(), "config.cfg")] + xdg_config_home = os.environ.get("XDG_CONFIG_HOME", os.path.join(os.path.expanduser('~'), ".config")) + return [os.path.join(xdg_config_home, 'easybuild', 'config.cfg')] def get_pretend_installpath(): @@ -312,37 +232,7 @@ def init(options, config_options_dict): Gather all variables and check if they're valid Variables are read in this order of preference: generaloption > legacy environment > legacy config file """ - tmpdict = {} - if SUPPORT_OLDSTYLE: - if not os.path.samefile(options.config, DEFAULT_OLDSTYLE_CONFIG_FILE): - # only trip if an oldstyle config other than the default is used (via $EASYBUILDCONFIG or --config) - # we still need the oldstyle default config file to ensure legacy behavior, for now - _log.deprecated('use of oldstyle configuration file %s' % options.config, '2.0') - tmpdict.update(oldstyle_init(options.config)) - - # add the DEFAULT_MODULECLASSES as default (behavior is now that this extends the default list) - tmpdict['moduleclasses'] = nub(list(tmpdict.get('moduleclasses', [])) + - [x[0] for x in DEFAULT_MODULECLASSES]) - - # make sure we have new-style keys - tmpdict = map_to_newstyle(tmpdict) - - # all defaults are now set in generaloption - # distinguish between default generaloption values and values actually passed by generaloption - for dest in config_options_dict.keys(): - if not options._action_taken.get(dest, False): - if dest == 'installpath' and options.pretend: - # the installpath has been set by pretend option in postprocess - continue - # remove the default options if they are set in variables - # this way, all defaults are set - if dest in tmpdict: - _log.debug("Oldstyle support: no action for dest %s." % dest) - del config_options_dict[dest] - - # update the variables with the generaloption values - _log.debug("Updating config variables with generaloption dict %s" % config_options_dict) - tmpdict.update(config_options_dict) + tmpdict = copy.deepcopy(config_options_dict) # make sure source path is a list sourcepath = tmpdict['sourcepath'] @@ -422,11 +312,8 @@ def source_paths(): def source_path(): - """ - Return the source path (deprecated) - """ - _log.deprecated("Use of source_path() is deprecated, use source_paths() instead.", '2.0') - return source_paths() + """NO LONGER SUPPORTED: use source_paths instead""" + _log.nosupport("Use of source_path(), use source_paths() instead.", '2.0') def install_path(typ=None): @@ -435,24 +322,13 @@ def install_path(typ=None): - subdir 'software' for actual installation (default) - subdir 'modules' for environment modules (typ='mod') """ - variables = ConfigurationVariables() - if typ is None: typ = 'software' - if typ == 'mod': + elif typ == 'mod': typ = 'modules' - key = "subdir_%s" % typ - if key in variables: - suffix = variables[key] - else: - # TODO remove default setting. it should have been set through options - try: - suffix = DEFAULT_PATH_SUBDIRS[key] - _log.deprecated('%s not set in config, returning default: %s' % (key, suffix), "2.0") - except: - _log.error('install_path trying to get unknown suffix %s' % key) - + variables = ConfigurationVariables() + suffix = variables['subdir_%s' % typ] return os.path.join(variables['installpath'], suffix) @@ -488,15 +364,7 @@ def get_module_naming_scheme(): def log_file_format(return_directory=False): """Return the format for the logfile or the directory""" idx = int(not return_directory) - - variables = ConfigurationVariables() - if 'logfile_format' in variables: - res = variables['logfile_format'][idx] - else: - res = DEFAULT_LOGFILE_FORMAT[:][idx] # purposely take a copy - # TODO remove default setting. it should have been set through options - _log.deprecated('logfile_format not set in config, returning default: %s' % res, '2.0') - return res + return ConfigurationVariables()['logfile_format'][idx] def log_format(): @@ -519,12 +387,11 @@ def get_build_log_path(): return temporary log directory """ variables = ConfigurationVariables() - if 'tmp_logdir' in variables: - return variables['tmp_logdir'] + if variables['tmp_logdir'] is not None: + res = variables['tmp_logdir'] else: - # TODO remove default setting. it should have been set through options - _log.deprecated('tmp_logdir not set in config, returning default: %s' % DEFAULT_TMP_LOGDIR, "2.0") - return DEFAULT_TMP_LOGDIR + res = tempfile.gettempdir() + return res def get_log_filename(name, version, add_salt=False): @@ -571,76 +438,12 @@ def module_classes(): """ Return list of module classes specified in config file. """ - variables = ConfigurationVariables() - if 'moduleclasses' in variables: - return variables['moduleclasses'] - else: - res = [x[0] for x in DEFAULT_MODULECLASSES] - # TODO remove default setting. it should have been set through options - _log.deprecated('moduleclasses not set in config, returning default: %s' % res, "2.0") - return res + return ConfigurationVariables()['moduleclasses'] def read_environment(env_vars, strict=False): """Depreacted location for read_environment, use easybuild.tools.environment""" - _log.deprecated("Deprecated location for read_environment, use easybuild.tools.environment", '2.0') - return _read_environment(env_vars, strict) - - -def oldstyle_init(filename, **kwargs): - """ - Gather all variables and check if they're valid - Variables are read in this order of preference: CLI option > environment > config file - """ - res = {} - - _log.debug('variables before oldstyle_init %s' % res) - res.update(oldstyle_read_configuration(filename)) # config file - _log.debug('variables after oldstyle_init read_configuration (%s) %s' % (filename, res)) - res.update(oldstyle_read_environment()) # environment - _log.debug('variables after oldstyle_init read_environment %s' % res) - if kwargs: - res.update(kwargs) # CLI options - _log.debug('variables after oldstyle_init kwargs (passed %s) %s' % (kwargs, res)) - - return res - - -def oldstyle_read_configuration(filename): - """ - Read variables from the config file - """ - # import avail_repositories here to avoid cyclic dependencies - # this block of code is going to be removed in EB v2.0 - from easybuild.tools.repository.repository import avail_repositories - file_variables = avail_repositories(check_useable=False) - try: - execfile(filename, {}, file_variables) - except (IOError, SyntaxError), err: - _log.exception("Failed to read config file %s %s" % (filename, err)) - - return file_variables - - -def oldstyle_read_environment(env_vars=None, strict=False): - """ - Read variables from the environment - - strict=True enforces that all possible environment variables are found - """ - if env_vars is None: - env_vars = OLDSTYLE_ENVIRONMENT_VARIABLES - result = {} - for key in env_vars.keys(): - env_var = env_vars[key] - if env_var in os.environ: - result[key] = os.environ[env_var] - _log.deprecated("Use of oldstyle environment variable %s for %s: %s" % (env_var, key, result[key]), '2.0') - elif strict: - _log.error("Can't determine value for %s. Environment variable %s is missing" % (key, env_var)) - else: - _log.debug("Old style env var %s not defined." % env_var) - - return result + _log.nosupport("Deprecated location for read_environment, use easybuild.tools.environment", '2.0') def set_tmpdir(tmpdir=None): diff --git a/easybuild/tools/deprecated/eb_2_0.py b/easybuild/tools/deprecated/eb_2_0.py deleted file mode 100644 index 55f43b4af4..0000000000 --- a/easybuild/tools/deprecated/eb_2_0.py +++ /dev/null @@ -1,74 +0,0 @@ -# # -# Copyright 2014-2014 Ghent University -# -# This file is part of EasyBuild, -# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en), -# with support of Ghent University (http://ugent.be/hpc), -# the Flemish Supercomputer Centre (VSC) (https://vscentrum.be/nl/en), -# the Hercules foundation (http://www.herculesstichting.be/in_English) -# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en). -# -# http://github.com/hpcugent/easybuild -# -# EasyBuild is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation v2. -# -# EasyBuild is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with EasyBuild. If not, see . -# # -""" -Deprecated functionality for EasyBuild v1.x - -@author: Kenneth Hoste (Ghent University) -""" -from vsc.utils.wrapper import Wrapper - - -class ExtraOptionsDeprecatedReturnValue(Wrapper): - """ - Hybrid list/dict object: is a list (of 2-element tuples), but also acts like a dict. - - Supported dict-like methods include: update(adict), items(), keys(), values() - - Consistency of values being 2-element tuples is *not* checked! - """ - __wraps__ = list - - def __getitem__(self, index_key): - """Get value by specified index/key.""" - if isinstance(index_key, int): - res = self._obj[index_key] - else: - res = dict(self._obj)[index_key] - return res - - def __setitem__(self, index_key, value): - """Add value at specified index/key.""" - if isinstance(index_key, int): - self._obj[index_key] = value - else: - self._obj = [(k, v) for (k, v) in self._obj if k != index_key] - self._obj.append((index_key, value)) - - def update(self, extra): - """Update with keys/values in supplied dictionary.""" - self._obj = [(k, v) for (k, v) in self._obj if k not in extra.keys()] - self._obj.extend(extra.items()) - - def items(self): - """Get list of key/value tuples.""" - return self._obj - - def keys(self): - """Get list of keys.""" - return [x[0] for x in self.items()] - - def values(self): - """Get list of values.""" - return [x[1] for x in self.items()] diff --git a/easybuild/tools/docs.py b/easybuild/tools/docs.py index 4e64029e7a..c8033be791 100644 --- a/easybuild/tools/docs.py +++ b/easybuild/tools/docs.py @@ -37,7 +37,6 @@ from easybuild.framework.easyconfig.default import DEFAULT_CONFIG, HIDDEN, sorted_categories from easybuild.framework.easyconfig.easyconfig import get_easyblock_class -from easybuild.tools.deprecated.eb_2_0 import ExtraOptionsDeprecatedReturnValue from easybuild.tools.ordereddict import OrderedDict from easybuild.tools.utilities import quote_str @@ -129,8 +128,6 @@ def avail_easyconfig_params(easyblock, output_format): app = get_easyblock_class(easyblock, default_fallback=False) if app is not None: extra_params = app.extra_options() - if isinstance(extra_params, ExtraOptionsDeprecatedReturnValue): - extra_params = dict(extra_params) params.update(extra_params) # compose title diff --git a/easybuild/tools/filetools.py b/easybuild/tools/filetools.py index 1eb26b6aa8..b9faa6e976 100644 --- a/easybuild/tools/filetools.py +++ b/easybuild/tools/filetools.py @@ -42,7 +42,6 @@ import urllib import zlib from vsc.utils import fancylogger -from vsc.utils.missing import all, any import easybuild.tools.environment as env from easybuild.tools.build_log import print_msg # import build_log must stay, to activate use of EasyBuildLog @@ -712,11 +711,8 @@ def apply_patch(patch_file, dest, fn=None, copy=False, level=None): def modify_env(old, new): - """ - Compares 2 os.environ dumps. Adapts final environment. - """ - _log.deprecated("moved modify_env to tools.environment", "2.0") - return env.modify_env(old, new) + """NO LONGER SUPPORTED: use modify_env from easybuild.tools.environment instead""" + _log.nosupport("moved modify_env to tools.environment", "2.0") def convert_name(name, upper=False): @@ -1043,22 +1039,17 @@ def decode_class_name(name): def run_cmd(cmd, log_ok=True, log_all=False, simple=False, inp=None, regexp=True, log_output=False, path=None): - """Legacy wrapper/placeholder for run.run_cmd""" - _log.deprecated("run_cmd was moved from tools.filetools to tools.run", '2.0') - return run.run_cmd(cmd, log_ok=log_ok, log_all=log_all, simple=simple, - inp=inp, regexp=regexp, log_output=log_output, path=path) + """NO LONGER SUPPORTED: use run_cmd from easybuild.tools.run instead""" + _log.nosupport("run_cmd was moved from tools.filetools to tools.run", '2.0') def run_cmd_qa(cmd, qa, no_qa=None, log_ok=True, log_all=False, simple=False, regexp=True, std_qa=None, path=None): - """Legacy wrapper/placeholder for run.run_cmd_qa""" - _log.deprecated("run_cmd_qa was moved from tools.filetools to tools.run", '2.0') - return run.run_cmd_qa(cmd, qa, no_qa=no_qa, log_ok=log_ok, log_all=log_all, - simple=simple, regexp=regexp, std_qa=std_qa, path=path) + """NO LONGER SUPPORTED: use run_cmd_qa from easybuild.tools.run instead""" + _log.nosupport("run_cmd_qa was moved from tools.filetools to tools.run", '2.0') def parse_log_for_error(txt, regExp=None, stdout=True, msg=None): - """Legacy wrapper/placeholder for run.parse_log_for_error""" - _log.deprecated("parse_log_for_error was moved from tools.filetools to tools.run", '2.0') - return run.parse_log_for_error(txt, regExp=regExp, stdout=stdout, msg=msg) + """NO LONGER SUPPORTED: use parse_log_for_error from easybuild.tools.run instead""" + _log.nosupport("parse_log_for_error was moved from tools.filetools to tools.run", '2.0') def det_size(path): diff --git a/easybuild/tools/modules.py b/easybuild/tools/modules.py index ce8353b0e2..5c8b057fd7 100644 --- a/easybuild/tools/modules.py +++ b/easybuild/tools/modules.py @@ -42,7 +42,7 @@ from distutils.version import StrictVersion from subprocess import PIPE from vsc.utils import fancylogger -from vsc.utils.missing import get_subclasses, any +from vsc.utils.missing import get_subclasses from vsc.utils.patterns import Singleton from easybuild.tools.build_log import EasyBuildError @@ -180,9 +180,8 @@ def buildstats(self): @property def modules(self): - """Property providing access to deprecated 'modules' class variable.""" - self.log.deprecated("'modules' class variable is deprecated, just use load([])", '2.0') - return self._modules + """(NO LONGER SUPPORTED!) Property providing access to 'modules' class variable""" + self.log.nosupport("'modules' class variable is not supported anymore, just use load([])", '2.0') def set_and_check_version(self): """Get the module version, and check any requirements""" @@ -370,9 +369,8 @@ def exist(self, mod_names): return mods_exist def exists(self, mod_name): - """Check if a module with the specified name exists.""" - self.log.deprecated("exists() is deprecated, use exist([]) instead", '2.0') - return self.exist([mod_name])[0] + """NO LONGER SUPPORTED: use exist method instead""" + self.log.nosupport("exists() is not supported anymore, use exist([]) instead", '2.0') def load(self, modules, mod_paths=None, purge=False, orig_env=None): """ @@ -408,8 +406,7 @@ def unload(self, modules=None): Unload all requested modules. """ if modules is None: - self.log.deprecated("Unloading modules listed in _modules class variable", '2.0') - modules = self._modules[:] + self.log.nosupport("Unloading modules listed in _modules class variable", '2.0') for mod in modules: self.run_module('unload', mod) @@ -470,15 +467,12 @@ def run_module(self, *args, **kwargs): args.insert(*self.TERSE_OPTION) module_path_key = None - original_module_path = None if 'mod_paths' in kwargs: module_path_key = 'mod_paths' elif 'modulePath' in kwargs: module_path_key = 'modulePath' if module_path_key is not None: - original_module_path = os.environ['MODULEPATH'] - os.environ['MODULEPATH'] = kwargs[module_path_key] - self.log.deprecated("Use of '%s' named argument in 'run_module'" % module_path_key, '2.0') + self.log.nosupport("Use of '%s' named argument in 'run_module'" % module_path_key, '2.0') self.log.debug('Current MODULEPATH: %s' % os.environ.get('MODULEPATH', '')) @@ -504,9 +498,6 @@ def run_module(self, *args, **kwargs): # stderr will contain text (just like the normal module command) (stdout, stderr) = proc.communicate() self.log.debug("Output of module command '%s': stdout: %s; stderr: %s" % (full_cmd, stdout, stderr)) - if original_module_path is not None: - os.environ['MODULEPATH'] = original_module_path - self.log.deprecated("Restoring $MODULEPATH back to what it was before running module command/.", '2.0') if kwargs.get('return_output', False): return stdout + stderr @@ -884,24 +875,22 @@ def get_software_root(name, with_env_var=False): """ Return the software root set for a particular software name. """ - environment_key = get_software_root_env_var_name(name) - newname = convert_name(name, upper=True) - legacy_key = "SOFTROOT%s" % newname + env_var = get_software_root_env_var_name(name) + legacy_key = "SOFTROOT%s" % convert_name(name, upper=True) - # keep on supporting legacy installations - if environment_key in os.environ: - env_var = environment_key - else: - env_var = legacy_key - if legacy_key in os.environ: - _log.deprecated("Legacy env var %s is being relied on!" % legacy_key, "2.0") + root = None + if env_var in os.environ: + root = os.getenv(env_var) - root = os.getenv(env_var) + elif legacy_key in os.environ: + _log.nosupport("Legacy env var %s is being relied on!" % legacy_key, "2.0") if with_env_var: - return (root, env_var) + res = (root, env_var) else: - return root + res = root + + return res def get_software_libdir(name, only_one=True, fs=None): @@ -947,18 +936,16 @@ def get_software_version(name): """ Return the software version set for a particular software name. """ - environment_key = get_software_version_env_var_name(name) - newname = convert_name(name, upper=True) - legacy_key = "SOFTVERSION%s" % newname + env_var = get_software_version_env_var_name(name) + legacy_key = "SOFTVERSION%s" % convert_name(name, upper=True) - # keep on supporting legacy installations - if environment_key in os.environ: - return os.getenv(environment_key) - else: - if legacy_key in os.environ: - _log.deprecated("Legacy env var %s is being relied on!" % legacy_key, "2.0") - return os.getenv(legacy_key) + version = None + if env_var in os.environ: + version = os.getenv(env_var) + elif legacy_key in os.environ: + _log.nosupport("Legacy env var %s is being relied on!" % legacy_key, "2.0") + return version def curr_module_paths(): """ @@ -998,10 +985,7 @@ def modules_tool(mod_paths=None, testing=False): return None -# provide Modules class for backward compatibility (e.g., in easyblocks) class Modules(EnvironmentModulesC): - """Deprecated interface to modules tool.""" - + """NO LONGER SUPPORTED: interface to modules tool, use modules_tool from easybuild.tools.modules instead""" def __init__(self, *args, **kwargs): - _log.deprecated("modules.Modules class is now an abstract interface, use modules.modules_tool instead", "2.0") - super(Modules, self).__init__(*args, **kwargs) + _log.nosupport("modules.Modules class is now an abstract interface, use modules.modules_tool instead", '2.0') diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index 1d4472b353..14baac2909 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -36,6 +36,7 @@ import os import re import sys +import tempfile from distutils.version import LooseVersion from vsc.utils.missing import nub @@ -49,9 +50,9 @@ from easybuild.framework.extension import Extension from easybuild.tools import build_log, config, run # @UnusedImport make sure config is always initialized! from easybuild.tools.config import DEFAULT_LOGFILE_FORMAT, DEFAULT_MNS, DEFAULT_MODULES_TOOL, DEFAULT_MODULECLASSES -from easybuild.tools.config import DEFAULT_PATH_SUBDIRS, DEFAULT_PREFIX, DEFAULT_REPOSITORY, DEFAULT_TMP_LOGDIR +from easybuild.tools.config import DEFAULT_PATH_SUBDIRS, DEFAULT_PREFIX, DEFAULT_REPOSITORY from easybuild.tools.config import get_default_configfiles, get_pretend_installpath -from easybuild.tools.config import get_default_oldstyle_configfile, mk_full_default_path +from easybuild.tools.config import mk_full_default_path from easybuild.tools.docs import FORMAT_RST, FORMAT_TXT, avail_easyconfig_params from easybuild.tools.github import HAVE_GITHUB_API, HAVE_KEYRING, fetch_github_token from easybuild.tools.modules import avail_modules_tools @@ -63,7 +64,10 @@ from easybuild.tools.version import this_is_easybuild from vsc.utils import fancylogger from vsc.utils.generaloption import GeneralOption -from vsc.utils.missing import any + + +XDG_CONFIG_HOME = os.environ.get('XDG_CONFIG_HOME', os.path.join(os.path.expanduser('~'), ".config")) +DEFAULT_CONFIGFILE = os.path.join(XDG_CONFIG_HOME, 'easybuild', 'config.cfg') class EasyBuildOptions(GeneralOption): @@ -71,7 +75,8 @@ class EasyBuildOptions(GeneralOption): VERSION = this_is_easybuild() DEFAULT_LOGLEVEL = 'INFO' - DEFAULT_CONFIGFILES = get_default_configfiles() + + DEFAULT_CONFIGFILES = [DEFAULT_CONFIGFILE] ALLOPTSMANDATORY = False # allow more than one argument @@ -217,8 +222,6 @@ def config_options(self): 'ignore-dirs': ("Directory names to ignore when searching for files/dirs", 'strlist', 'store', ['.git', '.svn']), 'installpath': ("Install path for software and modules", None, 'store', mk_full_default_path('installpath')), - 'config': ("Path to EasyBuild config file (DEPRECATED, use --configfiles instead!)", - None, 'store', get_default_oldstyle_configfile(), 'C'), # purposely take a copy for the default logfile format 'logfile-format': ("Directory name and format of the log file", 'strtuple', 'store', DEFAULT_LOGFILE_FORMAT[:], {'metavar': 'DIR,FORMAT'}), @@ -252,10 +255,8 @@ def config_options(self): 'suffix-modules-path': ("Suffix for module files install path", None, 'store', GENERAL_CLASS), # this one is sort of an exception, it's something jobscripts can set, # has no real meaning for regular eb usage - 'testoutput': ("Path to where a job should place the output (to be set within jobscript)", - None, 'store', None), - 'tmp-logdir': ("Log directory where temporary log files are stored", - None, 'store', DEFAULT_TMP_LOGDIR), + 'testoutput': ("Path to where a job should place the output (to be set within jobscript)", None, 'store', None), + 'tmp-logdir': ("Log directory where temporary log files are stored", None, 'store', None), 'tmpdir': ('Directory to use for temporary storage', None, 'store', None), }) diff --git a/easybuild/tools/systemtools.py b/easybuild/tools/systemtools.py index 6a5f875555..d76185054b 100644 --- a/easybuild/tools/systemtools.py +++ b/easybuild/tools/systemtools.py @@ -128,12 +128,8 @@ def count_bits(n): def get_core_count(): - """ - Try to detect the number of virtual or physical CPUs on this system - (DEPRECATED, use get_avail_core_count instead) - """ - _log.deprecated("get_core_count() is deprecated, use get_avail_core_count() instead", '2.0') - return get_avail_core_count() + """NO LONGER SUPPORTED: use get_avail_core_count() instead""" + _log.nosupport("get_core_count() is nosupport, use get_avail_core_count() instead", '2.0') def get_cpu_vendor(): @@ -259,16 +255,8 @@ def get_cpu_speed(): def get_kernel_name(): - """Try to determine kernel name - - e.g., 'Linux', 'Darwin', ... - """ - _log.deprecated("get_kernel_name() (replaced by get_os_type())", "2.0") - try: - kernel_name = os.uname()[0] - return kernel_name - except OSError, err: - raise SystemToolsException("Failed to determine kernel name: %s" % err) + """NO LONGER SUPPORTED: use get_os_type() instead""" + _log.nosupport("get_kernel_name() (replaced by get_os_type())", "2.0") def get_os_type(): @@ -326,15 +314,9 @@ def get_os_name(): Determine system name, e.g., 'redhat' (generic), 'centos', 'debian', 'fedora', 'suse', 'ubuntu', 'red hat enterprise linux server', 'SL' (Scientific Linux), 'opensuse', ... """ - try: - # platform.linux_distribution is more useful, but only available since Python 2.6 - # this allows to differentiate between Fedora, CentOS, RHEL and Scientific Linux (Rocks is just CentOS) - os_name = platform.linux_distribution()[0].strip().lower() - except AttributeError: - # platform.dist can be used as a fallback - # CentOS, RHEL, Rocks and Scientific Linux may all appear as 'redhat' (especially if Python version is pre v2.6) - os_name = platform.dist()[0].strip().lower() - _log.deprecated("platform.dist as fallback for platform.linux_distribution", "2.0") + # platform.linux_distribution is more useful, but only available since Python 2.6 + # this allows to differentiate between Fedora, CentOS, RHEL and Scientific Linux (Rocks is just CentOS) + os_name = platform.linux_distribution()[0].strip().lower() os_name_map = { 'red hat enterprise linux server': 'RHEL', diff --git a/easybuild/tools/toolchain/toolchain.py b/easybuild/tools/toolchain/toolchain.py index 75c5a29c85..bb24ab4d19 100644 --- a/easybuild/tools/toolchain/toolchain.py +++ b/easybuild/tools/toolchain/toolchain.py @@ -34,7 +34,6 @@ import os import re from vsc.utils import fancylogger -from vsc.utils.missing import all, any from easybuild.tools.config import build_option, install_path from easybuild.tools.environment import setvar diff --git a/easybuild/tools/utilities.py b/easybuild/tools/utilities.py index 05948c9c6d..7c8a959319 100644 --- a/easybuild/tools/utilities.py +++ b/easybuild/tools/utilities.py @@ -32,8 +32,6 @@ import string import sys from vsc.utils import fancylogger -from vsc.utils.missing import any as _any -from vsc.utils.missing import all as _all import easybuild.tools.environment as env _log = fancylogger.getLogger('tools.utilities') @@ -45,24 +43,9 @@ UNWANTED_CHARS = ASCII_CHARS.translate(ASCII_CHARS, string.digits + string.ascii_letters + "_") -def any(ls): - """Reimplementation of 'any' function, which is not available in Python 2.4 yet.""" - return _any(ls) - - -def all(ls): - """Reimplementation of 'all' function, which is not available in Python 2.4 yet.""" - return _all(ls) - - def read_environment(env_vars, strict=False): - """ - Read variables from the environment - @param: env_vars: a dict with key a name, value a environment variable name - @param: strict, boolean, if True enforces that all specified environment variables are found - """ - _log.deprecated("moved read_environment to tools.environment", "2.0") - return env.read_environment(env_vars, strict) + """NO LONGER SUPPORTED: use read_environment from easybuild.tools.environment instead""" + _log.nosupport("read_environment has been moved to easybuild.tools.environment", '2.0') def flatten(lst): From 1943577472b3078c7b98fa217c0d0b006fe6b379 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 21 Jan 2015 13:52:00 +0100 Subject: [PATCH 260/298] bump required Python version to 2.6 --- eb | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/eb b/eb index c67fe08284..51eeb7152a 100755 --- a/eb +++ b/eb @@ -39,9 +39,9 @@ # @author: Pieter De Baets (Ghent University) # @author: Jens Timmerman (Ghent University) -# Python 2.4 or more recent 2.x required +# Python 2.6 or more recent 2.x required REQ_MAJ_PYVER=2 -REQ_MIN_PYVER=4 +REQ_MIN_PYVER=6 REQ_PYVER=${REQ_MAJ_PYVER}.${REQ_MIN_PYVER} # make sure Python version being used is compatible @@ -60,15 +60,6 @@ then exit 2 fi -# support for Python versions older than v2.6 is deprecated -OK_MIN_PYVER=6 -if [ $pyver_min -lt $OK_MIN_PYVER ] -then - OK_PYVER=${REQ_MAJ_PYVER}.${OK_MIN_PYVER} - echo -n "WARNING: Running EasyBuild with a Python version prior to v${OK_PYVER} is deprecated, " 1>&2 - echo "found Python v$pyver which will no longer be supported in EasyBuild v2.0." 1>&2 -fi - main_script_base_path="easybuild/main.py" python_search_path_cmd="python -c \"import sys; print ' '.join(sys.path)\"" From f8d379b93e38950862c48e6ca314664c24e7cb06 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 21 Jan 2015 13:52:20 +0100 Subject: [PATCH 261/298] remove unit tests for deprecated functionality --- test/framework/config.py | 270 +--------------------------------- test/framework/easyblock.py | 18 +-- test/framework/easyconfig.py | 74 ---------- test/framework/modules.py | 8 - test/framework/systemtools.py | 7 - test/framework/toy_build.py | 11 -- 6 files changed, 12 insertions(+), 376 deletions(-) diff --git a/test/framework/config.py b/test/framework/config.py index 5e433d1259..21ee31185a 100644 --- a/test/framework/config.py +++ b/test/framework/config.py @@ -28,7 +28,6 @@ @author: Kenneth Hoste (Ghent University) @author: Stijn De Weirdt (Ghent University) """ -import copy import os import shutil import sys @@ -39,8 +38,8 @@ from vsc.utils.fancylogger import setLogLevelDebug, logToScreen import easybuild.tools.options as eboptions -from easybuild.tools.config import build_path, source_paths, install_path, get_repository, get_repositorypath -from easybuild.tools.config import log_file_format, set_tmpdir, BuildOptions, ConfigurationVariables +from easybuild.tools.config import build_path, source_paths, install_path, get_repositorypath +from easybuild.tools.config import set_tmpdir, BuildOptions, ConfigurationVariables from easybuild.tools.config import get_build_log_path, DEFAULT_PATH_SUBDIRS, init_build_options, build_option from easybuild.tools.environment import modify_env from easybuild.tools.filetools import mkdir, write_file @@ -106,266 +105,8 @@ def test_default_config(self): self.assertEqual(config_options['repositorypath'], [os.path.join(eb_homedir, 'ebfiles_repo')]) self.assertEqual(config_options['logfile_format'][0], 'easybuild') self.assertEqual(config_options['logfile_format'][1], "easybuild-%(name)s-%(version)s-%(date)s.%(time)s.log") - self.assertEqual(config_options['tmp_logdir'], tempfile.gettempdir()) - - def test_generaloption_overrides_legacy(self): - """Test whether generaloption overrides legacy configuration.""" - # lower 'current' version to avoid tripping over deprecation errors - os.environ['EASYBUILD_DEPRECATED'] = '1.0' - init_config() - - self.purge_environment() - # if both legacy and generaloption style configuration is mixed, generaloption wins - legacy_prefix = os.path.join(self.tmpdir, 'legacy') - go_prefix = os.path.join(self.tmpdir, 'generaloption') - - # legacy env vars - os.environ['EASYBUILDPREFIX'] = legacy_prefix - os.environ['EASYBUILDBUILDPATH'] = os.path.join(legacy_prefix, 'build') - # generaloption env vars - os.environ['EASYBUILD_INSTALLPATH'] = go_prefix - init_config() - self.assertEqual(build_path(), os.path.join(legacy_prefix, 'build')) - self.assertEqual(install_path(), os.path.join(go_prefix, 'software')) - repo = init_repository(get_repository(), get_repositorypath()) - self.assertEqual(repo.repo, os.path.join(legacy_prefix, 'ebfiles_repo')) - del os.environ['EASYBUILDPREFIX'] - - # legacy env vars - os.environ['EASYBUILDBUILDPATH'] = os.path.join(legacy_prefix, 'buildagain') - # generaloption env vars - os.environ['EASYBUILD_PREFIX'] = go_prefix - init_config() - self.assertEqual(build_path(), os.path.join(go_prefix, 'build')) - self.assertEqual(install_path(), os.path.join(go_prefix, 'software')) - repo = init_repository(get_repository(), get_repositorypath()) - self.assertEqual(repo.repo, os.path.join(go_prefix, 'ebfiles_repo')) - del os.environ['EASYBUILDBUILDPATH'] - - def test_legacy_env_vars(self): - """Test legacy environment variables.""" - # lower 'current' version to avoid tripping over deprecation errors - os.environ['EASYBUILD_DEPRECATED'] = '1.0' - init_config() - - self.purge_environment() - - # build path - test_buildpath = os.path.join(self.tmpdir, 'build', 'path') - os.environ['EASYBUILDBUILDPATH'] = test_buildpath - self.configure(args=[]) - self.assertEqual(build_path(), test_buildpath) - del os.environ['EASYBUILDBUILDPATH'] - - # source path(s) - test_sourcepaths = [ - os.path.join(self.tmpdir, 'source', 'path'), - ':'.join([ - os.path.join(self.tmpdir, 'source', 'path1'), - os.path.join(self.tmpdir, 'source', 'path2'), - ]), - ':'.join([ - os.path.join(self.tmpdir, 'source', 'path1'), - os.path.join(self.tmpdir, 'source', 'path2'), - os.path.join(self.tmpdir, 'source', 'path3'), - ]), - ] - for test_sourcepath in test_sourcepaths: - init_config() - os.environ['EASYBUILDSOURCEPATH'] = test_sourcepath - self.configure(args=[]) - self.assertEqual(build_path(), os.path.join(os.path.expanduser('~'), '.local', 'easybuild', - DEFAULT_PATH_SUBDIRS['buildpath'])) - self.assertEqual(source_paths(), test_sourcepath.split(':')) - del os.environ['EASYBUILDSOURCEPATH'] - - test_sourcepath = os.path.join(self.tmpdir, 'source', 'path') - - # install path - init_config() - test_installpath = os.path.join(self.tmpdir, 'install', 'path') - os.environ['EASYBUILDINSTALLPATH'] = test_installpath - self.configure(args=[]) - self.assertEqual(source_paths()[0], os.path.join(os.path.expanduser('~'), '.local', 'easybuild', - DEFAULT_PATH_SUBDIRS['sourcepath'])) - self.assertEqual(install_path(), os.path.join(test_installpath, DEFAULT_PATH_SUBDIRS['subdir_software'])) - self.assertEqual(install_path(typ='mod'), os.path.join(test_installpath, - DEFAULT_PATH_SUBDIRS['subdir_modules'])) - del os.environ['EASYBUILDINSTALLPATH'] - - # prefix: should change build/install/source/repo paths - init_config() - test_prefixpath = os.path.join(self.tmpdir, 'prefix', 'path') - os.environ['EASYBUILDPREFIX'] = test_prefixpath - self.configure(args=[]) - self.assertEqual(build_path(), os.path.join(test_prefixpath, DEFAULT_PATH_SUBDIRS['buildpath'])) - self.assertEqual(source_paths()[0], os.path.join(test_prefixpath, DEFAULT_PATH_SUBDIRS['sourcepath'])) - self.assertEqual(install_path(), os.path.join(test_prefixpath, DEFAULT_PATH_SUBDIRS['subdir_software'])) - self.assertEqual(install_path(typ='mod'), os.path.join(test_prefixpath, - DEFAULT_PATH_SUBDIRS['subdir_modules'])) - repo = init_repository(get_repository(), get_repositorypath()) - self.assertTrue(isinstance(repo, FileRepository)) - self.assertEqual(repo.repo, os.path.join(test_prefixpath, DEFAULT_PATH_SUBDIRS['repositorypath'])) - # purposely not unsetting $EASYBUILDPREFIX yet here - - # build/source/install path overrides prefix - init_config() - os.environ['EASYBUILDBUILDPATH'] = test_buildpath - self.configure(args=[]) - self.assertEqual(build_path(), test_buildpath) - self.assertEqual(source_paths()[0], os.path.join(test_prefixpath, DEFAULT_PATH_SUBDIRS['sourcepath'])) - self.assertEqual(install_path(), os.path.join(test_prefixpath, DEFAULT_PATH_SUBDIRS['subdir_software'])) - self.assertEqual(install_path(typ='mod'), os.path.join(test_prefixpath, - DEFAULT_PATH_SUBDIRS['subdir_modules'])) - repo = init_repository(get_repository(), get_repositorypath()) - self.assertTrue(isinstance(repo, FileRepository)) - self.assertEqual(repo.repo, os.path.join(test_prefixpath, DEFAULT_PATH_SUBDIRS['repositorypath'])) - del os.environ['EASYBUILDBUILDPATH'] - - init_config() - os.environ['EASYBUILDSOURCEPATH'] = test_sourcepath - self.configure(args=[]) - self.assertEqual(build_path(), os.path.join(test_prefixpath, DEFAULT_PATH_SUBDIRS['buildpath'])) - self.assertEqual(source_paths()[0], test_sourcepath) - self.assertEqual(install_path(), os.path.join(test_prefixpath, DEFAULT_PATH_SUBDIRS['subdir_software'])) - self.assertEqual(install_path(typ='mod'), os.path.join(test_prefixpath, - DEFAULT_PATH_SUBDIRS['subdir_modules'])) - repo = init_repository(get_repository(), get_repositorypath()) - self.assertTrue(isinstance(repo, FileRepository)) - self.assertEqual(repo.repo, os.path.join(test_prefixpath, DEFAULT_PATH_SUBDIRS['repositorypath'])) - del os.environ['EASYBUILDSOURCEPATH'] - - init_config() - os.environ['EASYBUILDINSTALLPATH'] = test_installpath - self.configure(args=[]) - self.assertEqual(build_path(), os.path.join(test_prefixpath, DEFAULT_PATH_SUBDIRS['buildpath'])) - self.assertEqual(source_paths()[0], os.path.join(test_prefixpath, DEFAULT_PATH_SUBDIRS['sourcepath'])) - self.assertEqual(install_path(), os.path.join(test_installpath, DEFAULT_PATH_SUBDIRS['subdir_software'])) - self.assertEqual(install_path(typ='mod'), os.path.join(test_installpath, - DEFAULT_PATH_SUBDIRS['subdir_modules'])) - repo = init_repository(get_repository(), get_repositorypath()) - self.assertTrue(isinstance(repo, FileRepository)) - self.assertEqual(repo.repo, os.path.join(test_prefixpath, DEFAULT_PATH_SUBDIRS['repositorypath'])) - del os.environ['EASYBUILDPREFIX'] - del os.environ['EASYBUILDINSTALLPATH'] - - def test_legacy_config_file(self): - """Test finding/using legacy configuration files.""" - # lower 'current' version to avoid tripping over deprecation errors - os.environ['EASYBUILD_DEPRECATED'] = '1.0' - init_config() - - self.purge_environment() - - cfg_fn = self.configure(args=[]) - self.assertTrue(cfg_fn.endswith('easybuild/easybuild_config.py')) - - configtxt = """ -build_path = '%(buildpath)s' -source_path = '%(sourcepath)s' -install_path = '%(installpath)s' -repository_path = '%(repopath)s' -repository = FileRepository(repository_path) -log_format = ('%(logdir)s', '%(logtmpl)s') -log_dir = '%(tmplogdir)s' -software_install_suffix = '%(softsuffix)s' -modules_install_suffix = '%(modsuffix)s' -""" - - buildpath = os.path.join(self.tmpdir, 'my', 'test', 'build', 'path') - sourcepath = os.path.join(self.tmpdir, 'my', 'test', 'source', 'path') - installpath = os.path.join(self.tmpdir, 'my', 'test', 'install', 'path') - repopath = os.path.join(self.tmpdir, 'my', 'test', 'repo', 'path') - logdir = 'somedir' - logtmpl = 'test-eb-%(name)s%(version)s_date-%(date)s__time-%(time)s.log' - tmplogdir = os.path.join(self.tmpdir, 'my', 'test', 'tmplogdir') - softsuffix = 'myfavoritesoftware' - modsuffix = 'modulesgohere' - - configdict = { - 'buildpath': buildpath, - 'sourcepath': sourcepath, - 'installpath': installpath, - 'repopath': repopath, - 'logdir': logdir, - 'logtmpl': logtmpl, - 'tmplogdir': tmplogdir, - 'softsuffix': softsuffix, - 'modsuffix': modsuffix - } - - # create user config file on default location - myconfigfile = os.path.join(self.tmpdir, '.easybuild', 'config.py') - if not os.path.exists(os.path.dirname(myconfigfile)): - os.makedirs(os.path.dirname(myconfigfile)) - write_file(myconfigfile, configtxt % configdict) - - # redefine home so we can test user config file on default location - home = os.environ.get('HOME', None) - os.environ['HOME'] = self.tmpdir - init_config() - cfg_fn = self.configure(args=[]) - if home is not None: - os.environ['HOME'] = home - - # check finding and use of config file - self.assertEqual(cfg_fn, myconfigfile) - self.assertEqual(build_path(), buildpath) - self.assertEqual(source_paths()[0], sourcepath) - self.assertEqual(install_path(), os.path.join(installpath, softsuffix)) - self.assertEqual(install_path(typ='mod'), os.path.join(installpath, modsuffix)) - repo = init_repository(get_repository(), get_repositorypath()) - self.assertTrue(isinstance(repo, FileRepository)) - self.assertEqual(repo.repo, repopath) - self.assertEqual(log_file_format(return_directory=True), logdir) - self.assertEqual(log_file_format(), logtmpl) - self.assertEqual(get_build_log_path(), tmplogdir) - - # redefine config file entries for proper testing below - buildpath = os.path.join(self.tmpdir, 'my', 'custom', 'test', 'build', 'path') - sourcepath = os.path.join(self.tmpdir, 'my', 'custom', 'test', 'source', 'path') - installpath = os.path.join(self.tmpdir, 'my', 'custom', 'test', 'install', 'path') - repopath = os.path.join(self.tmpdir, 'my', 'custom', 'test', 'repo', 'path') - logdir = 'somedir_custom' - logtmpl = 'test-custom-eb-%(name)_%(date)s%(time)s__%(version)s.log' - tmplogdir = os.path.join(self.tmpdir, 'my', 'custom', 'test', 'tmplogdir') - softsuffix = 'myfavoritesoftware_custom' - modsuffix = 'modulesgohere_custom' - - configdict = { - 'buildpath': buildpath, - 'sourcepath': sourcepath, - 'installpath': installpath, - 'repopath': repopath, - 'logdir': logdir, - 'logtmpl': logtmpl, - 'tmplogdir': tmplogdir, - 'softsuffix': softsuffix, - 'modsuffix': modsuffix } - - # create custom config file, and point to it - mycustomconfigfile = os.path.join(self.tmpdir, 'mycustomconfig.py') - if not os.path.exists(os.path.dirname(mycustomconfigfile)): - os.makedirs(os.path.dirname(mycustomconfigfile)) - write_file(mycustomconfigfile, configtxt % configdict) - os.environ['EASYBUILDCONFIG'] = mycustomconfigfile - - # reconfigure - init_config() - cfg_fn = self.configure(args=[]) - - # verify configuration - self.assertEqual(cfg_fn, mycustomconfigfile) - self.assertEqual(build_path(), buildpath) - self.assertEqual(source_paths()[0], sourcepath) - self.assertEqual(install_path(), os.path.join(installpath, softsuffix)) - self.assertEqual(install_path(typ='mod'), os.path.join(installpath, modsuffix)) - repo = init_repository(get_repository(), get_repositorypath()) - self.assertTrue(isinstance(repo, FileRepository)) - self.assertEqual(repo.repo, repopath) - self.assertEqual(log_file_format(return_directory=True), logdir) - self.assertEqual(log_file_format(), logtmpl) - self.assertEqual(get_build_log_path(), tmplogdir) + self.assertEqual(config_options['tmpdir'], None) + self.assertEqual(config_options['tmp_logdir'], None) def test_generaloption_config(self): """Test new-style configuration (based on generaloption).""" @@ -525,6 +266,9 @@ def test_set_tmpdir(self): fd, tempfile_tmpfile = tempfile.mkstemp() self.assertTrue(tempfile_tmpfile.startswith(os.path.join(parent, 'easybuild-'))) + # tmp_logdir follows tmpdir + self.assertEqual(get_build_log_path(), mytmpdir) + # cleanup os.close(fd) shutil.rmtree(mytmpdir) diff --git a/test/framework/easyblock.py b/test/framework/easyblock.py index 78e423853b..c2dcb01655 100644 --- a/test/framework/easyblock.py +++ b/test/framework/easyblock.py @@ -77,17 +77,9 @@ def test_easyblock(self): def check_extra_options_format(extra_options): """Make sure extra_options value is of correct format.""" - # EasyBuild v1.x: list of (, ) tuples - self.assertTrue(isinstance(list(extra_options), list)) # conversion to a list works - for extra_option in extra_options: - self.assertTrue(isinstance(extra_option, tuple)) - self.assertEqual(len(extra_option), 2) - self.assertTrue(isinstance(extra_option[0], basestring)) - self.assertTrue(isinstance(extra_option[1], list)) - self.assertEqual(len(extra_option[1]), 3) # EasyBuild v2.0: dict with keys and values # (breaks backward compatibility compared to v1.x) - self.assertTrue(isinstance(dict(extra_options), dict)) # conversion to a dict works + self.assertTrue(isinstance(extra_options, dict)) # conversion to a dict works extra_options.items() extra_options.keys() extra_options.values() @@ -129,7 +121,7 @@ def check_extra_options_format(extra_options): self.assertEqual(exeb1.cfg['name'], 'foo') extra_options = exeb1.extra_options() check_extra_options_format(extra_options) - self.assertTrue('options' in [key for (key, _) in extra_options]) + self.assertTrue('options' in extra_options) # test extensioneasyblock, as easyblock exeb2 = ExtensionEasyBlock(ec) @@ -137,7 +129,7 @@ def check_extra_options_format(extra_options): self.assertEqual(exeb2.cfg['version'], '3.14') extra_options = exeb2.extra_options() check_extra_options_format(extra_options) - self.assertTrue('options' in [key for (key, _) in extra_options]) + self.assertTrue('options' in extra_options) class TestExtension(ExtensionEasyBlock): @staticmethod @@ -147,8 +139,8 @@ def extra_options(): self.assertEqual(texeb.cfg['name'], 'bar') extra_options = texeb.extra_options() check_extra_options_format(extra_options) - self.assertTrue('options' in [key for (key, _) in extra_options]) - self.assertEqual([val for (key, val) in extra_options if key == 'extra_param'][0], [None, "help", CUSTOM]) + self.assertTrue('options' in extra_options) + self.assertEqual(extra_options['extra_param'], [None, "help", CUSTOM]) # cleanup eb.close_log() diff --git a/test/framework/easyconfig.py b/test/framework/easyconfig.py index a11a245938..e67b43983d 100644 --- a/test/framework/easyconfig.py +++ b/test/framework/easyconfig.py @@ -153,24 +153,6 @@ def test_validation(self): self.prep() self.assertErrorRegex(EasyBuildError, "SyntaxError", EasyConfig, self.eb_file) - def test_deprecated_shared_lib_ext(self): - """ inside easyconfigs shared_lib_ext should be set """ - os.environ['EASYBUILD_DEPRECATED'] = '1.0' - init_config() - - self.contents = '\n'.join([ - 'easyblock = "ConfigureMake"', - 'name = "pi"', - 'version = "3.14"', - 'homepage = "http://example.com"', - 'description = "test easyconfig"', - 'toolchain = {"name":"dummy", "version": "dummy"}', - 'sanity_check_paths = { "files": ["lib/lib.%s" % shared_lib_ext] }', - ]) - self.prep() - eb = EasyConfig(self.eb_file) - self.assertEqual(eb['sanity_check_paths']['files'][0], "lib/lib.%s" % get_shared_lib_ext()) - def test_shlib_ext(self): """ inside easyconfigs shared_lib_ext should be set """ self.contents = '\n'.join([ @@ -282,12 +264,6 @@ def test_extra_options(self): self.assertEqual(eb['mandatory_key'], 'value') - # test legacy behavior of passing a list of tuples rather than a dict - os.environ['EASYBUILD_DEPRECATED'] = '1.0' - init_config() - eb = EasyConfig(self.eb_file, extra_options=extra_vars.items()) - self.assertEqual(eb['custom_key'], 'test') - def test_exts_list(self): """Test handling of list of extensions.""" os.environ['EASYBUILD_SOURCEPATH'] = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs') @@ -443,26 +419,6 @@ def test_installversion(self): installver = det_full_ec_version(cfg) self.assertEqual(installver, correct_installver) - def test_legacy_installversion(self): - """Test generation of install version (legacy).""" - os.environ['EASYBUILD_DEPRECATED'] = '1.0' - init_config() - - ver = "3.14" - verpref = "myprefix|" - versuff = "|mysuffix" - tcname = "GCC" - tcver = "4.6.3" - dummy = "dummy" - - correct_installver = "%s%s-%s-%s%s" % (verpref, ver, tcname, tcver, versuff) - installver = det_installversion(ver, tcname, tcver, verpref, versuff) - self.assertEqual(installver, correct_installver) - - correct_installver = "%s%s%s" % (verpref, ver, versuff) - installver = det_installversion(ver, dummy, tcver, verpref, versuff) - self.assertEqual(installver, correct_installver) - def test_obtain_easyconfig(self): """test obtaining an easyconfig file given certain specifications""" @@ -928,10 +884,6 @@ def test_get_easyblock_class(self): self.assertEqual(get_easyblock_class(None, name='toy'), EB_toy) self.assertErrorRegex(EasyBuildError, "Failed to import EB_TOY", get_easyblock_class, None, name='TOY') self.assertEqual(get_easyblock_class(None, name='TOY', error_on_failed_import=False), None) - # deprecated functionality: ConfigureMake fallback still enabled - os.environ['EASYBUILD_DEPRECATED'] = '1.0' - init_config() - self.assertEqual(get_easyblock_class(None, name='gzip'), ConfigureMake) def test_easyconfig_paths(self): """Test create_paths function.""" @@ -944,32 +896,6 @@ def test_easyconfig_paths(self): ] self.assertEqual(cand_paths, expected_paths) - def test_deprecated_options(self): - """Test whether deprecated options are handled correctly.""" - # lower 'current' version to avoid tripping over deprecation errors - os.environ['EASYBUILD_DEPRECATED'] = '1.0' - init_config() - - deprecated_options = [ - ('makeopts', 'buildopts', 'CC=foo'), - ('premakeopts', 'prebuildopts', ['PATH=%(builddir)s/foo:$PATH', 'PATH=%(builddir)s/bar:$PATH']), - ] - clean_contents = [ - 'easyblock = "ConfigureMake"', - 'name = "pi"', - 'version = "3.14"', - 'homepage = "http://example.com"', - 'description = "test easyconfig"', - 'toolchain = {"name": "dummy", "version": "dummy"}', - 'buildininstalldir = True', - ] - # alternative option is ready to use - for depr_opt, new_opt, val in deprecated_options: - self.contents = '\n'.join(clean_contents + ['%s = %s' % (depr_opt, quote_str(val))]) - self.prep() - ec = EasyConfig(self.eb_file) - self.assertEqual(ec[depr_opt], ec[new_opt]) - def test_toolchain_inspection(self): """Test whether available toolchain inspection functionality is working.""" build_options = { diff --git a/test/framework/modules.py b/test/framework/modules.py index 059a80604a..00cce786d7 100644 --- a/test/framework/modules.py +++ b/test/framework/modules.py @@ -122,14 +122,6 @@ def test_exists(self): 'Compiler/GCC/4.7.2/OpenMPI/1.6.4', 'toy/.0.0-deps'] self.assertEqual(self.testmods.exist(mod_names), [True, False, False, False, True, True, True]) - # test deprecated functionality - os.environ['EASYBUILD_DEPRECATED'] = '1.0' - init_config() - self.assertTrue(self.testmods.exists('OpenMPI/1.6.4-GCC-4.6.4')) - self.assertFalse(self.testmods.exists('foo/1.2.3')) - # exists should not return True for incomplete module names - self.assertFalse(self.testmods.exists('GCC')) - def test_load(self): """ test if we load one module it is in the loaded_modules """ self.init_testmods() diff --git a/test/framework/systemtools.py b/test/framework/systemtools.py index 20b6b0068d..8447913dc7 100644 --- a/test/framework/systemtools.py +++ b/test/framework/systemtools.py @@ -47,13 +47,6 @@ def test_avail_core_count(self): self.assertTrue(isinstance(core_count, int), "core_count has type int: %s, %s" % (core_count, type(core_count))) self.assertTrue(core_count > 0, "core_count %d > 0" % core_count) - # also test deprecated get_core_count - os.environ['EASYBUILD_DEPRECATED'] = '1.0' - init_config() - core_count = get_core_count() - self.assertTrue(isinstance(core_count, int), "core_count has type int: %s, %s" % (core_count, type(core_count))) - self.assertTrue(core_count > 0, "core_count %d > 0" % core_count) - def test_cpu_model(self): """Test getting CPU model.""" cpu_model = get_cpu_model() diff --git a/test/framework/toy_build.py b/test/framework/toy_build.py index 1b8371b379..95eb21b03b 100644 --- a/test/framework/toy_build.py +++ b/test/framework/toy_build.py @@ -57,17 +57,6 @@ def setUp(self): fd, self.dummylogfn = tempfile.mkstemp(prefix='easybuild-dummy', suffix='.log') os.close(fd) - # adjust PYTHONPATH such that test easyblocks are found - import easybuild - eb_blocks_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'sandbox')) - if not eb_blocks_path in sys.path: - sys.path.append(eb_blocks_path) - easybuild = reload(easybuild) - - import easybuild.easyblocks - reload(easybuild.easyblocks) - reload(easybuild.tools.module_naming_scheme) - # clear log write_file(self.logfile, '') From 94f15a4f96200d938307c929518c4aabb11ea4de Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 21 Jan 2015 14:24:50 +0100 Subject: [PATCH 262/298] style fixes --- easybuild/framework/easyblock.py | 8 ++++---- easybuild/framework/easyconfig/easyconfig.py | 21 ++++++-------------- easybuild/tools/config.py | 14 ++++--------- easybuild/tools/filetools.py | 8 ++++---- easybuild/tools/modules.py | 2 +- easybuild/tools/options.py | 4 +--- easybuild/tools/systemtools.py | 4 ++-- 7 files changed, 22 insertions(+), 39 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index c29563a99f..3269660935 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -1065,7 +1065,8 @@ def skip_extensions(self): try: cmd = cmdtmpl % tmpldict except KeyError, err: - msg = "Use of 'name'/'version' keys for extensions filter, should use 'ext_name', 'ext_version' instead" + msg = "KeyError occured on completing extension filter template: %s; " + msg += "'name'/'version' keys are no longer supported, should use 'ext_name'/'ext_version' instead" self.log.nosupport(msg, '2.0') if cmdinputtmpl: @@ -1370,7 +1371,7 @@ def extensions_step(self, fetch=False): # obtain name and module path for default extention class if hasattr(exts_defaultclass, '__iter__'): - self.log.nosupport("Using specified module path for default class", '2.0') + self.log.nosupport("Module path for default class is explicitly defined", '2.0') elif isinstance(exts_defaultclass, basestring): # proper way: derive module path from specified class name @@ -1411,8 +1412,7 @@ def extensions_step(self, fetch=False): cls = get_class_for(mod_path, class_name) inst = cls(self, ext) except (ImportError, NameError), err: - tup = (class_name, ext['name'], err) - self.log.error("Failed to load specified class %s for extension %s: %s" % tup) + self.log.error("Failed to load specified class %s for extension %s: %s" % (class_name, ext['name'], err)) # fallback attempt: use default class if inst is None: diff --git a/easybuild/framework/easyconfig/easyconfig.py b/easybuild/framework/easyconfig/easyconfig.py index 1a50efb87c..b366821fbf 100644 --- a/easybuild/framework/easyconfig/easyconfig.py +++ b/easybuild/framework/easyconfig/easyconfig.py @@ -83,21 +83,12 @@ _easyconfigs_cache = {} -def handle_replaced_easyconfig_parameter(ec_method): - """Decorator to handle replaced easyconfig parameters.""" +def check_replaced_easyconfig_parameter(ec_method): + """Decorator to check for replaced easyconfig parameters.""" def new_ec_method(self, key, *args, **kwargs): - """Map replaced easyconfig parameters to the new correct parameter.""" - # map name of replaced easyconfig parameter to new name + """Check whether any replace easyconfig parameters are still used""" if key in REPLACED_PARAMETERS: _log.nosupport("Easyconfig parameter '%s' is replaced by '%s'" % (key, REPLACED_PARAMETERS[key]), '2.0') - - # make sure that value for software_license has correct type, convert if needed - if key == 'software_license': - # key 'license' will already be mapped to 'software_license' above - lic = self._config['software_license'][0] - if lic is not None and not isinstance(lic, License): - self.log.nosupport('Type for software_license must to be instance of License (sub)class', '2.0') - return ec_method(self, key, *args, **kwargs) return new_ec_method @@ -628,7 +619,7 @@ def _generate_template_values(self, ignore=None, skip_lower=True): if v is None: del self.template_values[k] - @handle_replaced_easyconfig_parameter + @check_replaced_easyconfig_parameter def __getitem__(self, key): """ will return the value without the help text @@ -641,7 +632,7 @@ def __getitem__(self, key): else: return value - @handle_replaced_easyconfig_parameter + @check_replaced_easyconfig_parameter def __setitem__(self, key, value): """ sets the value of key in config. @@ -741,7 +732,7 @@ def get_easyblock_class(easyblock, name=None, default_fallback=True, error_on_fa modulepath_bis = get_module_path(name, decode=False) _log.debug("Module path determined based on software name: %s" % modulepath_bis) if modulepath_bis != modulepath: - _log.nosupport("Determine module path based on software name", '2.0') + _log.nosupport("Determining module path based on software name", '2.0') # try and find easyblock try: diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py index be244d5daf..9fd02948fd 100644 --- a/easybuild/tools/config.py +++ b/easybuild/tools/config.py @@ -216,12 +216,6 @@ class BuildOptions(FrozenDictKnownKeys): KNOWN_KEYS = [k for kss in [BUILD_OPTIONS_CMDLINE, BUILD_OPTIONS_OTHER] for ks in kss.values() for k in ks] -def get_default_configfiles(): - """Return a list of default configfiles for tools.options/generaloption""" - xdg_config_home = os.environ.get("XDG_CONFIG_HOME", os.path.join(os.path.expanduser('~'), ".config")) - return [os.path.join(xdg_config_home, 'easybuild', 'config.cfg')] - - def get_pretend_installpath(): """Get the installpath when --pretend option is used""" return os.path.join(os.path.expanduser('~'), 'easybuildinstall') @@ -313,7 +307,7 @@ def source_paths(): def source_path(): """NO LONGER SUPPORTED: use source_paths instead""" - _log.nosupport("Use of source_path(), use source_paths() instead.", '2.0') + _log.nosupport("source_path() is replaced by source_paths()", '2.0') def install_path(typ=None): @@ -384,7 +378,7 @@ def log_path(): def get_build_log_path(): """ - return temporary log directory + Return (temporary) directory for build log """ variables = ConfigurationVariables() if variables['tmp_logdir'] is not None: @@ -442,8 +436,8 @@ def module_classes(): def read_environment(env_vars, strict=False): - """Depreacted location for read_environment, use easybuild.tools.environment""" - _log.nosupport("Deprecated location for read_environment, use easybuild.tools.environment", '2.0') + """NO LONGER SUPPORTED: use read_environment from easybuild.tools.environment instead""" + _log.nosupport("read_environment has moved to easybuild.tools.environment", '2.0') def set_tmpdir(tmpdir=None): diff --git a/easybuild/tools/filetools.py b/easybuild/tools/filetools.py index b9faa6e976..d524c0ca61 100644 --- a/easybuild/tools/filetools.py +++ b/easybuild/tools/filetools.py @@ -712,7 +712,7 @@ def apply_patch(patch_file, dest, fn=None, copy=False, level=None): def modify_env(old, new): """NO LONGER SUPPORTED: use modify_env from easybuild.tools.environment instead""" - _log.nosupport("moved modify_env to tools.environment", "2.0") + _log.nosupport("moved modify_env to easybuild.tools.environment", "2.0") def convert_name(name, upper=False): @@ -1040,16 +1040,16 @@ def decode_class_name(name): def run_cmd(cmd, log_ok=True, log_all=False, simple=False, inp=None, regexp=True, log_output=False, path=None): """NO LONGER SUPPORTED: use run_cmd from easybuild.tools.run instead""" - _log.nosupport("run_cmd was moved from tools.filetools to tools.run", '2.0') + _log.nosupport("run_cmd was moved from easybuild.tools.filetools to easybuild.tools.run", '2.0') def run_cmd_qa(cmd, qa, no_qa=None, log_ok=True, log_all=False, simple=False, regexp=True, std_qa=None, path=None): """NO LONGER SUPPORTED: use run_cmd_qa from easybuild.tools.run instead""" - _log.nosupport("run_cmd_qa was moved from tools.filetools to tools.run", '2.0') + _log.nosupport("run_cmd_qa was moved from easybuild.tools.filetools to easybuild.tools.run", '2.0') def parse_log_for_error(txt, regExp=None, stdout=True, msg=None): """NO LONGER SUPPORTED: use parse_log_for_error from easybuild.tools.run instead""" - _log.nosupport("parse_log_for_error was moved from tools.filetools to tools.run", '2.0') + _log.nosupport("parse_log_for_error was moved from easybuild.tools.filetools to easybuild.tools.run", '2.0') def det_size(path): diff --git a/easybuild/tools/modules.py b/easybuild/tools/modules.py index 5c8b057fd7..36f412a9bc 100644 --- a/easybuild/tools/modules.py +++ b/easybuild/tools/modules.py @@ -181,7 +181,7 @@ def buildstats(self): @property def modules(self): """(NO LONGER SUPPORTED!) Property providing access to 'modules' class variable""" - self.log.nosupport("'modules' class variable is not supported anymore, just use load([])", '2.0') + self.log.nosupport("'modules' class variable is not supported anymore, use load([]) instead", '2.0') def set_and_check_version(self): """Get the module version, and check any requirements""" diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index 14baac2909..6d3c4393af 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -36,7 +36,6 @@ import os import re import sys -import tempfile from distutils.version import LooseVersion from vsc.utils.missing import nub @@ -51,7 +50,7 @@ from easybuild.tools import build_log, config, run # @UnusedImport make sure config is always initialized! from easybuild.tools.config import DEFAULT_LOGFILE_FORMAT, DEFAULT_MNS, DEFAULT_MODULES_TOOL, DEFAULT_MODULECLASSES from easybuild.tools.config import DEFAULT_PATH_SUBDIRS, DEFAULT_PREFIX, DEFAULT_REPOSITORY -from easybuild.tools.config import get_default_configfiles, get_pretend_installpath +from easybuild.tools.config import get_pretend_installpath from easybuild.tools.config import mk_full_default_path from easybuild.tools.docs import FORMAT_RST, FORMAT_TXT, avail_easyconfig_params from easybuild.tools.github import HAVE_GITHUB_API, HAVE_KEYRING, fetch_github_token @@ -75,7 +74,6 @@ class EasyBuildOptions(GeneralOption): VERSION = this_is_easybuild() DEFAULT_LOGLEVEL = 'INFO' - DEFAULT_CONFIGFILES = [DEFAULT_CONFIGFILE] ALLOPTSMANDATORY = False # allow more than one argument diff --git a/easybuild/tools/systemtools.py b/easybuild/tools/systemtools.py index d76185054b..2d2fbc696c 100644 --- a/easybuild/tools/systemtools.py +++ b/easybuild/tools/systemtools.py @@ -129,7 +129,7 @@ def count_bits(n): def get_core_count(): """NO LONGER SUPPORTED: use get_avail_core_count() instead""" - _log.nosupport("get_core_count() is nosupport, use get_avail_core_count() instead", '2.0') + _log.nosupport("get_core_count() is replaced by get_avail_core_count()", '2.0') def get_cpu_vendor(): @@ -256,7 +256,7 @@ def get_cpu_speed(): def get_kernel_name(): """NO LONGER SUPPORTED: use get_os_type() instead""" - _log.nosupport("get_kernel_name() (replaced by get_os_type())", "2.0") + _log.nosupport("get_kernel_name() is replaced by get_os_type()", '2.0') def get_os_type(): From 0eb24a32c7906c4fc9040e90af2228bb8a23248b Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 21 Jan 2015 15:46:07 +0100 Subject: [PATCH 263/298] remove easybuild_config.py from setup.py --- setup.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 783cce2062..1a961f4a36 100644 --- a/setup.py +++ b/setup.py @@ -91,9 +91,7 @@ def find_rel_test(): package_dir = {'test.framework': "test/framework"}, package_data = {"test.framework": find_rel_test()}, scripts = ["eb", "optcomplete.bash", "minimal_bash_completion.bash"], - data_files = [ - ('easybuild', ["easybuild/easybuild_config.py"]), - ], + data_files = [], long_description = read('README.rst'), classifiers = [ "Development Status :: 5 - Production/Stable", From 413b51b2b00a6c5c77d1f4d25ed61c2ec2226f5f Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 23 Jan 2015 16:36:13 +0100 Subject: [PATCH 264/298] change tweaking of easyconfig parameters: drop adding comments, fix issue of disappearing newline, no adding of useless newline at the end --- easybuild/framework/easyconfig/tweak.py | 28 ++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/easybuild/framework/easyconfig/tweak.py b/easybuild/framework/easyconfig/tweak.py index ee4176bf52..6302ddb499 100644 --- a/easybuild/framework/easyconfig/tweak.py +++ b/easybuild/framework/easyconfig/tweak.py @@ -166,7 +166,7 @@ def __repr__(self): for (key, val) in tweaks.items(): if isinstance(val, list): - regexp = re.compile(r"^\s*%s\s*=\s*(.*)$" % key, re.M) + regexp = re.compile(r"^(?P\s*%s)\s*=\s*(?P.*)$" % key, re.M) res = regexp.search(ectxt) if res: fval = [x for x in val if x != ''] # filter out empty strings @@ -175,48 +175,48 @@ def __repr__(self): # - input starting with comma (empty head list element) => append # - no empty head/tail list element => overwrite if val[0] == '': - newval = "%s + %s" % (res.group(1), fval) + newval = "%s + %s" % (res.group('val'), fval) _log.debug("Appending %s to %s" % (fval, key)) elif val[-1] == '': - newval = "%s + %s" % (fval, res.group(1)) + newval = "%s + %s" % (fval, res.group('val')) _log.debug("Prepending %s to %s" % (fval, key)) else: newval = "%s" % fval _log.debug("Overwriting %s with %s" % (key, fval)) - ectxt = regexp.sub("%s = %s # tweaked by EasyBuild (was: %s)" % (key, newval, res.group(1)), ectxt) + ectxt = regexp.sub("%s = %s" % (res.group('key'), newval), ectxt) _log.info("Tweaked %s list to '%s'" % (key, newval)) else: - additions.append("%s = %s # added by EasyBuild" % (key, val)) + additions.append("%s = %s" % (key, val)) tweaks.pop(key) # add parameters or replace existing ones for (key, val) in tweaks.items(): - regexp = re.compile(r"^\s*%s\s*=\s*(.*)$" % key, re.M) + regexp = re.compile(r"^(?P\s*%s)\s*=\s*(?P.*)$" % key, re.M) _log.debug("Regexp pattern for replacing '%s': %s" % (key, regexp.pattern)) res = regexp.search(ectxt) if res: # only tweak if the value is different diff = True try: - _log.debug("eval(%s): %s" % (res.group(1), eval(res.group(1)))) - diff = not eval(res.group(1)) == val + _log.debug("eval(%s): %s" % (res.group('val'), eval(res.group('val')))) + diff = not eval(res.group('val')) == val except (NameError, SyntaxError): # if eval fails, just fall back to string comparison - _log.debug("eval failed for \"%s\", falling back to string comparison against \"%s\"..." % (res.group(1), val)) - diff = not res.group(1) == val + tup = (res.group('val'), val) + _log.debug("eval failed for \"%s\", falling back to string comparison against \"%s\"..." % tup) + diff = not res.group('val') == val if diff: - ectxt = regexp.sub("%s = %s # tweaked by EasyBuild (was: %s)" % (key, quote_str(val), res.group(1)), ectxt) + ectxt = regexp.sub("%s = %s" % (res.group('key'), quote_str(val)), ectxt) _log.info("Tweaked '%s' to '%s'" % (key, quote_str(val))) else: additions.append("%s = %s" % (key, quote_str(val))) if additions: - _log.info("Adding additional parameters to tweaked easyconfig file: %s") - ectxt += "\n\n# added by EasyBuild as dictated by command line options\n" - ectxt += '\n'.join(additions) + '\n' + _log.info("Adding additional parameters to tweaked easyconfig file: %s" % additions) + ectxt = '\n'.join([ectxt] + additions) _log.debug("Contents of tweaked easyconfig file:\n%s" % ectxt) From 66b0f872b2c822154d6baeda9772a3159b313c1b Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 23 Jan 2015 16:36:43 +0100 Subject: [PATCH 265/298] enhance --try unit tests to include tweaked of list-typed values --- test/framework/options.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/framework/options.py b/test/framework/options.py index 1c4f847494..66f75a88a1 100644 --- a/test/framework/options.py +++ b/test/framework/options.py @@ -1168,6 +1168,14 @@ def test_try(self): (['--try-toolchain-name=gompi', '--try-toolchain-version=1.4.10'], 'toy/0.0-gompi-1.4.10'), (['--try-software-version=1.2.3', '--try-toolchain=gompi,1.4.10'], 'toy/1.2.3-gompi-1.4.10'), (['--try-amend=versionsuffix=-test'], 'toy/0.0-test'), + # tweak existing list-typed value (patches) + (['--try-amend=versionsuffix=-test2', '--try-amend=patches=1.patch,2.patch'], 'toy/0.0-test2'), + # append to existing list-typed value (patches) + (['--try-amend=versionsuffix=-test3', '--try-amend=patches=,extra.patch'], 'toy/0.0-test3'), + # prepend to existing list-typed value (patches) + (['--try-amend=versionsuffix=-test4', '--try-amend=patches=extra.patch,'], 'toy/0.0-test4'), + # define extra list-typed parameter + (['--try-amend=versionsuffix=-test5', '--try-amend=exts_list=1,2,3'], 'toy/0.0-test5'), # only --try causes other build specs to be included too (['--try-software=foo,1.2.3', '--toolchain=gompi,1.4.10'], 'foo/1.2.3-gompi-1.4.10'), (['--software=foo,1.2.3', '--try-toolchain=gompi,1.4.10'], 'foo/1.2.3-gompi-1.4.10'), From 09af38ed0bd7c3b2c8c56a1ce96e42fe45fb8af8 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 23 Jan 2015 17:32:23 +0100 Subject: [PATCH 266/298] fix remark --- easybuild/framework/easyconfig/tweak.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/easybuild/framework/easyconfig/tweak.py b/easybuild/framework/easyconfig/tweak.py index 6302ddb499..0047ae8a6d 100644 --- a/easybuild/framework/easyconfig/tweak.py +++ b/easybuild/framework/easyconfig/tweak.py @@ -201,12 +201,12 @@ def __repr__(self): diff = True try: _log.debug("eval(%s): %s" % (res.group('val'), eval(res.group('val')))) - diff = not eval(res.group('val')) == val + diff = eval(res.group('val')) != val except (NameError, SyntaxError): # if eval fails, just fall back to string comparison tup = (res.group('val'), val) _log.debug("eval failed for \"%s\", falling back to string comparison against \"%s\"..." % tup) - diff = not res.group('val') == val + diff = res.group('val') != val if diff: ectxt = regexp.sub("%s = %s" % (res.group('key'), quote_str(val)), ectxt) @@ -524,7 +524,7 @@ def unique(l): for (key, val) in specs.items(): if key in selected_ec._config: # values must be equal to have a full match - if not selected_ec[key] == val: + if selected_ec[key] != val: match = False else: # if we encounter a key that is not set in the selected easyconfig, we don't have a full match From d4345eecdfc38c21974f3f6f153fcbfa08cbe404 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 23 Jan 2015 17:32:35 +0100 Subject: [PATCH 267/298] fix broken test --- test/framework/easyconfig.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/framework/easyconfig.py b/test/framework/easyconfig.py index a11a245938..1c3f5c0cf9 100644 --- a/test/framework/easyconfig.py +++ b/test/framework/easyconfig.py @@ -585,7 +585,7 @@ def test_obtain_easyconfig(self): ec = EasyConfig(res[1]) self.assertEqual(ec['version'], specs['version']) txt = read_file(res[1]) - self.assertTrue(re.search("version = [\"']%s[\"'] .*was: [\"']3.13[\"']" % ver, txt)) + self.assertTrue(re.search("^version = [\"']%s[\"']$" % ver, txt, re.M)) os.remove(res[1]) # should pick correct toolchain version as well, i.e. now newer than what's specified, if a choice needs to be made @@ -599,8 +599,8 @@ def test_obtain_easyconfig(self): self.assertEqual(ec['version'], specs['version']) self.assertEqual(ec['toolchain']['version'], specs['toolchain_version']) txt = read_file(res[1]) - pattern = "toolchain = .*version.*[\"']%s[\"'].*was: .*version.*[\"']%s[\"']" % (specs['toolchain_version'], tcver) - self.assertTrue(re.search(pattern, txt)) + pattern = "^toolchain = .*version.*[\"']%s[\"'].*}$" % specs['toolchain_version'] + self.assertTrue(re.search(pattern, txt, re.M)) os.remove(res[1]) From a778631bb7d34c9b9b01eba56f6ee5b0dafe75fc Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 23 Jan 2015 18:27:34 +0100 Subject: [PATCH 268/298] stop using log.raiseException in toolchain.py, use log.error instead --- easybuild/tools/toolchain/toolchain.py | 28 ++++++++++++-------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/easybuild/tools/toolchain/toolchain.py b/easybuild/tools/toolchain/toolchain.py index 75c5a29c85..f9d241873f 100644 --- a/easybuild/tools/toolchain/toolchain.py +++ b/easybuild/tools/toolchain/toolchain.py @@ -82,13 +82,13 @@ def __init__(self, name=None, version=None, mns=None): if name is None: name = self.NAME if name is None: - self.log.raiseException("init: no name provided") + self.log.error("Toolchain init: no name provided") self.name = name if version is None: version = self.VERSION if version is None: - self.log.raiseException("init: no version provided") + self.log.error("Toolchain init: no version provided") self.version = version self.vars = None @@ -129,7 +129,7 @@ def get_variable(self, name, typ=str): elif typ == list: return self.variables[name].flatten() else: - self.log.raiseException("get_variables: Don't know how to create value of type %s." % typ) + self.log.error("get_variable: Don't know how to create value of type %s." % typ) def set_variables(self): """Do nothing? Everything should have been set by others @@ -188,7 +188,7 @@ def _get_software_root(self, name): """Try to get the software root for name""" root = get_software_root(name) if root is None: - self.log.raiseException("get_software_root software root for %s was not found in environment" % (name)) + self.log.error("get_software_root software root for %s was not found in environment" % name) else: self.log.debug("get_software_root software root %s for %s was found in environment" % (root, name)) return root @@ -197,11 +197,9 @@ def _get_software_version(self, name): """Try to get the software root for name""" version = get_software_version(name) if version is None: - self.log.raiseException("get_software_version software version for %s was not found in environment" % - (name)) + self.log.error("get_software_version software version for %s was not found in environment" % name) else: - self.log.debug("get_software_version software version %s for %s was found in environment" % - (version, name)) + self.log.debug("get_software_version software version %s for %s was found in environment" % (version, name)) return version @@ -249,8 +247,8 @@ def set_options(self, options): self.options[opt] = options[opt] else: # used to be warning, but this is a severe error imho - self.log.raiseException("set_options: undefined toolchain option %s specified (possible names %s)" % - (opt, ",".join(self.options.keys()))) + known_opts = ','.join(self.options.keys()) + self.log.error("Undefined toolchain option %s specified (known options: %s)" % (opt, known_opts)) def get_dependency_version(self, dependency): """ Generate a version string for a dependency on a module using this toolchain """ @@ -281,8 +279,8 @@ def get_dependency_version(self, dependency): self.log.debug("get_dependency_version: version not in dependency return %s" % version) return else: - self.log.raiseException('get_dependency_version: No toolchain version for dependency '\ - 'name %s (suffix %s) found' % (dependency['name'], toolchain_suffix)) + tup = (dependency['name'], toolchain_suffix) + self.log.error("No toolchain version for dependency name %s (suffix %s) found" % tup) def add_dependencies(self, dependencies): """ Verify if the given dependencies exist and add them """ @@ -331,10 +329,10 @@ def prepare(self, onlymod=None): (If string: comma separated list of variables that will be ignored). """ if self.modules_tool is None: - self.log.raiseException("No modules tool defined.") + self.log.error("No modules tool defined in Toolchain instance.") if not self._toolchain_exists(): - self.log.raiseException("No module found for toolchain name '%s' (%s)" % (self.name, self.version)) + self.log.error("No module found for toolchain: %s" % self.mod_short_name) if self.name == DUMMY_TOOLCHAIN_NAME: if self.version == DUMMY_TOOLCHAIN_VERSION: @@ -429,7 +427,7 @@ def _setenv_variables(self, donotset=None): donotsetlist = [] if isinstance(donotset, str): # TODO : more legacy code that should be using proper type - self.log.raiseException("_setenv_variables: using commas-separated list. should be deprecated.") + self.log.error("_setenv_variables: using commas-separated list. should be deprecated.") donotsetlist = donotset.split(',') elif isinstance(donotset, list): donotsetlist = donotset From 661edaa8af22ecbb01eb03bf27ab3ef3f28ba35f Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 23 Jan 2015 18:28:01 +0100 Subject: [PATCH 269/298] remove dead code in toolchain.py --- easybuild/tools/toolchain/toolchain.py | 34 -------------------------- 1 file changed, 34 deletions(-) diff --git a/easybuild/tools/toolchain/toolchain.py b/easybuild/tools/toolchain/toolchain.py index f9d241873f..cb5a9fdd54 100644 --- a/easybuild/tools/toolchain/toolchain.py +++ b/easybuild/tools/toolchain/toolchain.py @@ -457,37 +457,3 @@ def comp_family(self): def mpi_family(self): """ Return type of MPI library used in this toolchain (abstract method).""" raise NotImplementedError - - # legacy functions TODO remove AFTER migration - # should search'n'replaced - def get_type(self, name, type_map): - """Determine type of toolchain based on toolchain dependencies.""" - self.log.raiseException("get_type: legacy code. should not be needed anymore.") - - def _set_variables(self, dontset=None): - """ Sets the environment variables """ - self.log.raiseException("_set_variables: legacy code. use _setenv_variables.") - - def _addDependencyVariables(self, names=None): - """ Add LDFLAGS and CPPFLAGS to the self.vars based on the dependencies - names should be a list of strings containing the name of the dependency""" - self.log.raiseException("_addDependencyVaraibles: legacy code. use _add_dependency_variables.") - - def _setVariables(self, dontset=None): - """ Sets the environment variables """ - self.log.raiseException("_setVariables: legacy code. use _set_variables.") - - def _toolkitExists(self, name=None, version=None): - """ - Verify if there exists a toolkit by this name and version - """ - self.log.raiseException("_toolkitExists: legacy code. replace use _toolchain_exists.") - - def get_openmp_flag(self): - """Get compiler flag for OpenMP support.""" - self.log.raiseException("get_openmp_flag: legacy code. use options.get_flag('openmp').") - - @property - def opts(self): - """Get value for specified option.""" - self.log.raiseException("opts[x]: legacy code. use options[x].") From 91d479bde30a48e4978b5f4d7c291b44f0e3b0c3 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 23 Jan 2015 18:44:27 +0100 Subject: [PATCH 270/298] add test on preparing for a toolchain for which no module is available --- test/framework/toolchain.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/framework/toolchain.py b/test/framework/toolchain.py index 6d76ac1100..921626628e 100644 --- a/test/framework/toolchain.py +++ b/test/framework/toolchain.py @@ -496,6 +496,11 @@ def test_toolchain_verification(self): tc.set_options(opts) tc.prepare() + def test_nosuchtoolchain(self): + """Test preparing for a toolchain for which no module is available.""" + tc = self.get_toolchain('intel', version='1970.01') + self.assertErrorRegex(EasyBuildError, "No module found for toolchain", tc.prepare) + def tearDown(self): """Cleanup.""" # purge any loaded modules before restoring $MODULEPATH From 6c72c3ab905144c5bb1b8e9690c374e03e1d69aa Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Sat, 24 Jan 2015 00:35:04 +0100 Subject: [PATCH 271/298] fix hardcoding of /tmp in mpi_cmd_for --- easybuild/tools/toolchain/mpi.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/easybuild/tools/toolchain/mpi.py b/easybuild/tools/toolchain/mpi.py index b2a4cdc84a..053b85f02c 100644 --- a/easybuild/tools/toolchain/mpi.py +++ b/easybuild/tools/toolchain/mpi.py @@ -28,8 +28,8 @@ @author: Stijn De Weirdt (Ghent University) @author: Kenneth Hoste (Ghent University) """ - import os +import tempfile import easybuild.tools.environment as env import easybuild.tools.toolchain as toolchain @@ -185,8 +185,10 @@ def mpi_cmd_for(self, cmd, nr_ranks): # Intel MPI mpirun needs more work if mpi_family == toolchain.INTELMPI: # @UndefinedVariable + tmpdir = tempfile.mkdtemp(prefix='eb-mpi_cmd_for-') + # set temporary dir for mdp - env.setvar('I_MPI_MPD_TMPDIR', "/tmp") + env.setvar('I_MPI_MPD_TMPDIR', tmpdir) # set PBS_ENVIRONMENT, so that --file option for mpdboot isn't stripped away env.setvar('PBS_ENVIRONMENT', "PBS_BATCH_MPI") @@ -196,7 +198,7 @@ def mpi_cmd_for(self, cmd, nr_ranks): env.setvar('I_MPI_PROCESS_MANAGER', 'mpd') # create mpdboot file - fn = "/tmp/mpdboot" + fn = os.path.join(tmpdir, 'mpdboot') try: if os.path.exists(fn): os.remove(fn) @@ -204,10 +206,10 @@ def mpi_cmd_for(self, cmd, nr_ranks): except OSError, err: self.log.error("Failed to create file %s: %s" % (fn, err)) - params.update({'mpdbf':"--file=%s" % fn}) + params.update({'mpdbf': "--file=%s" % fn}) # create nodes file - fn = "/tmp/nodes" + fn = os.path.join(tmpdir, 'nodes') try: if os.path.exists(fn): os.remove(fn) @@ -215,7 +217,7 @@ def mpi_cmd_for(self, cmd, nr_ranks): except OSError, err: self.log.error("Failed to create file %s: %s" % (fn, err)) - params.update({'nodesfile':"-machinefile %s" % fn}) + params.update({'nodesfile': "-machinefile %s" % fn}) if mpi_family in mpi_cmds.keys(): return mpi_cmds[mpi_family] % params From 26111522bb89c0a4ecf2af33166ac3f7fa4a8549 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Sat, 24 Jan 2015 00:48:03 +0100 Subject: [PATCH 272/298] minor style fixes --- easybuild/tools/toolchain/mpi.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/easybuild/tools/toolchain/mpi.py b/easybuild/tools/toolchain/mpi.py index 053b85f02c..7a9694de8c 100644 --- a/easybuild/tools/toolchain/mpi.py +++ b/easybuild/tools/toolchain/mpi.py @@ -167,7 +167,10 @@ def mpi_cmd_for(self, cmd, nr_ranks): """Construct an MPI command for the given command and number of ranks.""" # parameter values for mpirun command - params = {'nr_ranks':nr_ranks, 'cmd':cmd} + params = { + 'nr_ranks': nr_ranks, + 'cmd': cmd, + } # different known mpirun commands mpirun_n_cmd = "mpirun -n %(nr_ranks)d %(cmd)s" From e7e75f83ce3525454c27238274bb836c06a90568 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Sat, 24 Jan 2015 00:48:33 +0100 Subject: [PATCH 273/298] add unit test for mpi_cmd_for with Intel MPI-based toolchain --- test/framework/toolchain.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/test/framework/toolchain.py b/test/framework/toolchain.py index 921626628e..dc60b2c041 100644 --- a/test/framework/toolchain.py +++ b/test/framework/toolchain.py @@ -38,6 +38,7 @@ import easybuild.tools.modules as modules from easybuild.framework.easyconfig.easyconfig import EasyConfig, ActiveMNS from easybuild.tools.build_log import EasyBuildError +from easybuild.tools.filetools import write_file from easybuild.tools.toolchain.utilities import search_toolchain from test.framework.utilities import find_full_path @@ -408,8 +409,8 @@ def test_goolfc(self): # check CUDA runtime lib self.assertTrue("-lrt -lcudart" in tc.get_variable('LIBS')) - def test_ictce_toolchain(self): - """Test for ictce toolchain.""" + def setup_sandbox_for_intel_fftw(self): + """Set up sandbox for Intel FFTW""" # hack to make Intel FFTW lib check pass # rewrite $root in imkl module so we can put required lib*.a files in place tmpdir = tempfile.mkdtemp() @@ -426,7 +427,13 @@ def test_ictce_toolchain(self): for subdir in ['mkl/lib/intel64', 'compiler/lib/intel64']: os.makedirs(os.path.join(tmpdir, subdir)) for fftlib in fftw_libs: - open(os.path.join(tmpdir, subdir, 'lib%s.a' % fftlib), 'w').write('foo') + write_file(os.path.join(tmpdir, subdir, 'lib%s.a' % fftlib), 'foo') + + return tmpdir, imkl_module_path, imkl_module_txt + + def test_ictce_toolchain(self): + """Test for ictce toolchain.""" + tmpdir, imkl_module_path, imkl_module_txt = self.setup_sandbox_for_intel_fftw() tc = self.get_toolchain("ictce", version="4.1.13") tc.prepare() @@ -501,6 +508,20 @@ def test_nosuchtoolchain(self): tc = self.get_toolchain('intel', version='1970.01') self.assertErrorRegex(EasyBuildError, "No module found for toolchain", tc.prepare) + def test_mpi_cmd_for(self): + """Test mpi_cmd_for function.""" + tmpdir, imkl_module_path, imkl_module_txt = self.setup_sandbox_for_intel_fftw() + + tc = self.get_toolchain('ictce', version='4.1.13') + tc.prepare() + + mpi_cmd_for_re = re.compile("^mpirun --file=.*/mpdboot -machinefile .*/nodes -np 4 test$") + self.assertTrue(mpi_cmd_for_re.match(tc.mpi_cmd_for('test', 4))) + + # cleanup + shutil.rmtree(tmpdir) + open(imkl_module_path, 'w').write(imkl_module_txt) + def tearDown(self): """Cleanup.""" # purge any loaded modules before restoring $MODULEPATH From c7fc821b11f6aecb370075f59470d6305faf61a4 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 27 Jan 2015 10:28:01 +0100 Subject: [PATCH 274/298] reinstate DEPRECATED_PARAMETERS, add unit tests for checking deprecated/replaced easyconfig parameters --- easybuild/framework/easyconfig/easyconfig.py | 19 ++++-- test/framework/easyconfig.py | 65 ++++++++++++++++++-- test/framework/utilities.py | 2 + 3 files changed, 78 insertions(+), 8 deletions(-) diff --git a/easybuild/framework/easyconfig/easyconfig.py b/easybuild/framework/easyconfig/easyconfig.py index b366821fbf..a3db2cb3ae 100644 --- a/easybuild/framework/easyconfig/easyconfig.py +++ b/easybuild/framework/easyconfig/easyconfig.py @@ -72,6 +72,11 @@ # set of configure/build/install options that can be provided as lists for an iterated build ITERATE_OPTIONS = ['preconfigopts', 'configopts', 'prebuildopts', 'buildopts', 'preinstallopts', 'installopts'] +# deprecated easyconfig parameters, and their replacements +DEPRECATED_PARAMETERS = { + # : (, ), +} + # replaced easyconfig parameters, and their replacements REPLACED_PARAMETERS = { 'license': 'software_license', @@ -83,10 +88,15 @@ _easyconfigs_cache = {} -def check_replaced_easyconfig_parameter(ec_method): - """Decorator to check for replaced easyconfig parameters.""" +def handle_deprecated_or_replaced_easyconfig_parameters(ec_method): + """Decorator to handle deprecated/replaced easyconfig parameters.""" def new_ec_method(self, key, *args, **kwargs): """Check whether any replace easyconfig parameters are still used""" + # map deprecated parameters to their replacements, issue deprecation warning(/error) + if key in DEPRECATED_PARAMETERS: + depr_key = key + key, ver = DEPRECATED_PARAMETERS[depr_key] + _log.deprecated("Easyconfig parameter '%s' is deprecated, use '%s' instead." % (depr_key, key), ver) if key in REPLACED_PARAMETERS: _log.nosupport("Easyconfig parameter '%s' is replaced by '%s'" % (key, REPLACED_PARAMETERS[key]), '2.0') return ec_method(self, key, *args, **kwargs) @@ -619,7 +629,7 @@ def _generate_template_values(self, ignore=None, skip_lower=True): if v is None: del self.template_values[k] - @check_replaced_easyconfig_parameter + @handle_deprecated_or_replaced_easyconfig_parameters def __getitem__(self, key): """ will return the value without the help text @@ -632,7 +642,7 @@ def __getitem__(self, key): else: return value - @check_replaced_easyconfig_parameter + @handle_deprecated_or_replaced_easyconfig_parameters def __setitem__(self, key, value): """ sets the value of key in config. @@ -640,6 +650,7 @@ def __setitem__(self, key, value): """ self._config[key][0] = value + @handle_deprecated_or_replaced_easyconfig_parameters def get(self, key, default=None): """ Gets the value of a key in the config, with 'default' as fallback. diff --git a/test/framework/easyconfig.py b/test/framework/easyconfig.py index e67b43983d..7306f81f22 100644 --- a/test/framework/easyconfig.py +++ b/test/framework/easyconfig.py @@ -29,11 +29,12 @@ @author: Kenneth Hoste (Ghent University) @author: Stijn De Weirdt (Ghent University) """ - +import copy import os import re import shutil import tempfile +from distutils.version import LooseVersion from test.framework.utilities import EnhancedTestCase, init_config from unittest import TestLoader, main from vsc.utils.fancylogger import setLogLevelDebug, logToScreen @@ -69,8 +70,6 @@ def setUp(self): if os.path.exists(self.eb_file): os.remove(self.eb_file) - self.orig_current_version = easybuild.tools.build_log.CURRENT_VERSION - def prep(self): """Prepare for test.""" # (re)cleanup last test file @@ -83,7 +82,6 @@ def prep(self): def tearDown(self): """ make sure to remove the temporary file """ - easybuild.tools.build_log.CURRENT_VERSION = self.orig_current_version super(EasyConfigTest, self).tearDown() if os.path.exists(self.eb_file): os.remove(self.eb_file) @@ -943,6 +941,65 @@ def test_filter_deps(self): opts = init_config(args=['--filter-deps=zlib,ncurses']) self.assertEqual(opts.filter_deps, ['zlib', 'ncurses']) + def test_replaced_easyconfig_parameters(self): + """Test handling of replaced easyconfig parameters.""" + test_ecs_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'easyconfigs') + ec = EasyConfig(os.path.join(test_ecs_dir, 'toy-0.0.eb')) + replaced_parameters = { + 'license': 'software_license', + 'makeopts': 'buildopts', + 'premakeopts': 'prebuildopts', + } + for key in replaced_parameters: + error_regex = "NO LONGER SUPPORTED.*'%s'" % replaced_parameters[key] + self.assertErrorRegex(EasyBuildError, error_regex, ec.get, key) + self.assertErrorRegex(EasyBuildError, error_regex, lambda k: ec[k], key) + def foo(key): + ec[key] = 'foo' + self.assertErrorRegex(EasyBuildError, error_regex, foo, key) + + def test_deprecated_easyconfig_parameters(self): + """Test handling of replaced easyconfig parameters.""" + os.environ.pop('EASYBUILD_DEPRECATED') + easybuild.tools.build_log.CURRENT_VERSION = self.orig_current_version + init_config() + + test_ecs_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'easyconfigs') + ec = EasyConfig(os.path.join(test_ecs_dir, 'toy-0.0.eb')) + + orig_deprecated_parameters = copy.deepcopy(easyconfig.easyconfig.DEPRECATED_PARAMETERS) + easyconfig.easyconfig.DEPRECATED_PARAMETERS.update({ + 'foobar': ('barfoo', '0.0'), # deprecated since forever + 'foobarbarfoo': ('barfoofoobar', '1000000000'), # won't be actually deprecated for a while + }) + + # copy classes before reloading, so we can restore them (other isinstance checks fail) + orig_EasyConfig = copy.deepcopy(easyconfig.easyconfig.EasyConfig) + orig_ActiveMNS = copy.deepcopy(easyconfig.easyconfig.ActiveMNS) + reload(easyconfig.easyconfig) + + for key, (newkey, depr_ver) in easyconfig.easyconfig.DEPRECATED_PARAMETERS.items(): + if LooseVersion(depr_ver) <= easybuild.tools.build_log.CURRENT_VERSION: + # deprecation error + error_regex = "DEPRECATED.*since v%s.*'%s' is deprecated.*use '%s' instead" % (depr_ver, key, newkey) + self.assertErrorRegex(EasyBuildError, error_regex, ec.get, key) + self.assertErrorRegex(EasyBuildError, error_regex, lambda k: ec[k], key) + def foo(key): + ec[key] = 'foo' + self.assertErrorRegex(EasyBuildError, error_regex, foo, key) + else: + # only deprecation warning, but key is replaced when getting/setting + ec[key] = 'test123' + self.assertEqual(ec[newkey], 'test123') + self.assertEqual(ec[key], 'test123') + ec[newkey] = '123test' + self.assertEqual(ec[newkey], '123test') + self.assertEqual(ec[key], '123test') + + easyconfig.easyconfig.DEPRECATED_PARAMETERS = orig_deprecated_parameters + reload(easyconfig.easyconfig) + easyconfig.easyconfig.EasyConfig = orig_EasyConfig + easyconfig.easyconfig.ActiveMNS = orig_ActiveMNS def suite(): """ returns all the testcases in this module """ diff --git a/test/framework/utilities.py b/test/framework/utilities.py index 628e0c9181..f2b26c85e4 100644 --- a/test/framework/utilities.py +++ b/test/framework/utilities.py @@ -38,6 +38,7 @@ from vsc.utils import fancylogger from vsc.utils.patterns import Singleton +import easybuild.tools.build_log as eb_build_log import easybuild.tools.options as eboptions import easybuild.tools.toolchain.utilities as tc_utils import easybuild.tools.module_naming_scheme.toolchain as mns_toolchain @@ -108,6 +109,7 @@ def setUp(self): # make sure no deprecated behaviour is being triggered (unless intended by the test) # trip *all* log.deprecated statements by setting deprecation version ridiculously high + self.orig_current_version = eb_build_log.CURRENT_VERSION os.environ['EASYBUILD_DEPRECATED'] = '10000000' init_config() From af1c03731d04c25f17e0e894e033b2ec323f2910 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 27 Jan 2015 10:48:54 +0100 Subject: [PATCH 275/298] fix remaining remarks --- easybuild/tools/build_log.py | 2 +- easybuild/tools/config.py | 12 +++++------- test/framework/easyconfig.py | 10 +++++----- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/easybuild/tools/build_log.py b/easybuild/tools/build_log.py index 27ee40708e..1472e00e5e 100644 --- a/easybuild/tools/build_log.py +++ b/easybuild/tools/build_log.py @@ -103,7 +103,7 @@ def deprecated(self, msg, max_ver): def nosupport(self, msg, ver): """Print error message for no longer supported behaviour, and raise an EasyBuildError.""" - self.error("NO LONGER SUPPORTED: %s; see %s for more information" % (msg, DEPRECATED_DOC_URL)) + self.error("NO LONGER SUPPORTED since v%s: %s; see %s for more information" % (ver, msg, DEPRECATED_DOC_URL)) def error(self, msg, *args, **kwargs): """Print error message and raise an EasyBuildError.""" diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py index 9fd02948fd..cc3b83ea8d 100644 --- a/easybuild/tools/config.py +++ b/easybuild/tools/config.py @@ -173,8 +173,8 @@ class ConfigurationVariables(FrozenDictKnownKeys): # singleton metaclass: only one instance is created __metaclass__ = Singleton - # list of know/required keys - KNOWN_KEYS = [ + # list of known/required keys + REQUIRED = [ 'config', 'prefix', 'buildpath', @@ -190,8 +190,9 @@ class ConfigurationVariables(FrozenDictKnownKeys): 'modules_tool', 'module_naming_scheme', ] + KNOWN_KEYS = REQUIRED # KNOWN_KEYS must be defined for FrozenDictKnownKeys functionality - def get_items_check_required(self, no_missing=True): + def get_items_check_required(self): """ For all known/required keys, check if exists and return all key/value pairs. no_missing: boolean, when True, will throw error message for missing values @@ -199,10 +200,7 @@ def get_items_check_required(self, no_missing=True): missing = [x for x in self.KNOWN_KEYS if not x in self] if len(missing) > 0: msg = 'Cannot determine value for configuration variables %s. Please specify it.' % missing - if no_missing: - self.log.error(msg) - else: - self.log.debug(msg) + self.log.error(msg) return self.items() diff --git a/test/framework/easyconfig.py b/test/framework/easyconfig.py index 7306f81f22..6643269fc7 100644 --- a/test/framework/easyconfig.py +++ b/test/framework/easyconfig.py @@ -946,12 +946,12 @@ def test_replaced_easyconfig_parameters(self): test_ecs_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'easyconfigs') ec = EasyConfig(os.path.join(test_ecs_dir, 'toy-0.0.eb')) replaced_parameters = { - 'license': 'software_license', - 'makeopts': 'buildopts', - 'premakeopts': 'prebuildopts', + 'license': ('software_license', '2.0'), + 'makeopts': ('buildopts', '2.0'), + 'premakeopts': ('prebuildopts', '2.0'), } - for key in replaced_parameters: - error_regex = "NO LONGER SUPPORTED.*'%s'" % replaced_parameters[key] + for key, (newkey, ver) in replaced_parameters.items(): + error_regex = "NO LONGER SUPPORTED since v%s.*'%s' is replaced by '%s'" % (ver, key, newkey) self.assertErrorRegex(EasyBuildError, error_regex, ec.get, key) self.assertErrorRegex(EasyBuildError, error_regex, lambda k: ec[k], key) def foo(key): From e99f414d0415934207ec90ea10c36b78846d0371 Mon Sep 17 00:00:00 2001 From: Martin Marcher Date: Tue, 27 Jan 2015 16:25:39 +0100 Subject: [PATCH 276/298] Add proxy support for downloading This add support for downloading with proxies as often found in corporate settings. Pythons urllib2 will do "the right thing" when common environment variables such as `http_proxy` or `https_proxy` are set. Most noteably this also removes the `reporthook` nested function and removes a lot of code from the `download_file` function. --- easybuild/tools/filetools.py | 86 +++++++++--------------------------- 1 file changed, 22 insertions(+), 64 deletions(-) diff --git a/easybuild/tools/filetools.py b/easybuild/tools/filetools.py index 1eb26b6aa8..f671469fba 100644 --- a/easybuild/tools/filetools.py +++ b/easybuild/tools/filetools.py @@ -40,6 +40,7 @@ import stat import time import urllib +import urllib2 import zlib from vsc.utils import fancylogger from vsc.utils.missing import all, any @@ -260,75 +261,30 @@ def download_file(filename, url, path): basedir = os.path.dirname(path) mkdir(basedir, parents=True) - # internal function to report on download progress - def report(blocks_read, blocksize, filesize): - """ - Report hook for urlretrieve, which logs the download progress every 10 seconds with log level info. - @param blocks_read: number of blocks already read - @param blocksize: size of one block, in bytes - @param filesize: total size of the download (in number of blocks blocks) - """ - if download_file.last_time + 10 < time.time(): - newblocks = blocks_read - download_file.last_block - download_file.last_block = blocks_read - tot_time = time.time() - download_file.last_time - - if filesize <= 0: - # content length isn't always set - report_msg = "downloaded in %ss" % tot_time - else: - percent = blocks_read * blocksize * 100 // filesize - report_msg = "of %d kb downloaded in %ss [%d %%]" % (filesize / 1024.0, tot_time, percent) - - downloaded_kbs = (blocks_read * blocksize) / 1024.0 - kbps = (blocksize * newblocks) / 1024 // tot_time - _log.info("Download report: %d kb %s (%d kbps)", downloaded_kbs, report_msg, kbps) - - download_file.last_time = time.time() - # try downloading, three times max. downloaded = False attempt_cnt = 0 while not downloaded and attempt_cnt < 3: - # get HTTP response code first before downloading file - response_code = None - try: - urlfile = urllib.urlopen(url) - if hasattr(urlfile, 'getcode'): # no getcode() in Py2.4 yet - response_code = urlfile.getcode() - urlfile.close() - except IOError, err: - _log.warning("Failed to get HTTP response code for %s, retrying: %s", url, err) - - if response_code is not None: - _log.debug('HTTP response code for given url: %d', response_code) - # check for a 4xx response code which indicates a non-existing URL - if response_code // 100 == 4: - _log.warning('url %s was not found (HTTP response %d), not trying again', url, response_code) - return None - - # use this functions's scope for variables we share with inner function used as report hook for urlretrieve - download_file.last_time = time.time() - download_file.last_block = 0 - - httpmsg = None - try: - (_, httpmsg) = urllib.urlretrieve(url, path, reporthook=report) - _log.info("Downloaded file %s from url %s to %s", filename, url, path) - - if httpmsg.type == "text/html" and not filename.endswith('.html'): - _log.warning("HTML file downloaded to %s, so assuming invalid download, retrying.", path) - remove_file(path) - else: - # successful download + with open(path, "wb+") as dest_fd: + try: + src_fd = urllib2.urlopen(url) + _log.debug('HTTP response code for given url: %d', src_fd.getcode()) + dest_fd.write(src_fd.read()) + _log.info("Downloaded file %s from url %s to %s", filename, url, path) downloaded = True - except IOError, err: - _log.warning("Error when downloading from %s to %s (%s), removing it and retrying", url, path, err) - remove_file(path) - - if not downloaded: - attempt_cnt += 1 - _log.warning("Downloading failed at attempt %s, retrying...", attempt_cnt) + src_fd.close() + except (urllib2.HTTPError, ) as err: + if err.code == 404: + attempt_cnt += 1 + _log.warning("Downloading failed at attempt %s, retrying...", attempt_cnt) + continue + raise + except (IOError, ) as err: + if attempt_cnt <= 3: + _log.warning("Failed to get HTTP response code for %s, retrying: %s", url, err) + attempt_cnt += 1 + continue + raise if downloaded: _log.info("Successful download of file %s from url %s to path %s", filename, url, path) @@ -1078,3 +1034,5 @@ def det_size(path): _log.warn("Could not determine install size: %s" % err) return installsize + +# vim: set ts=4 sts=4 fenc=utf-8 expandtab list: From 03d2b6eaa3bd0cad25e4b11c6abe35878674236f Mon Sep 17 00:00:00 2001 From: Martin Marcher Date: Tue, 27 Jan 2015 17:43:43 +0100 Subject: [PATCH 277/298] Fix condition where a local file with protocol may come in --- easybuild/tools/filetools.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/easybuild/tools/filetools.py b/easybuild/tools/filetools.py index f671469fba..f654e474d4 100644 --- a/easybuild/tools/filetools.py +++ b/easybuild/tools/filetools.py @@ -273,12 +273,16 @@ def download_file(filename, url, path): _log.info("Downloaded file %s from url %s to %s", filename, url, path) downloaded = True src_fd.close() + except (ValueError, ) as err: + attempt_cnt += 1 + shutil.copy(url, path) + downloaded = True except (urllib2.HTTPError, ) as err: - if err.code == 404: - attempt_cnt += 1 - _log.warning("Downloading failed at attempt %s, retrying...", attempt_cnt) - continue - raise + if err.code == 404: + attempt_cnt += 1 + _log.warning("Downloading failed at attempt %s, retrying...", attempt_cnt) + continue + raise except (IOError, ) as err: if attempt_cnt <= 3: _log.warning("Failed to get HTTP response code for %s, retrying: %s", url, err) From 7af85e5bb31626323841eb16f309330b318656cf Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 3 Feb 2015 16:24:02 +0100 Subject: [PATCH 278/298] define __contains__ in EasyConfig class --- easybuild/framework/easyconfig/easyconfig.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/easybuild/framework/easyconfig/easyconfig.py b/easybuild/framework/easyconfig/easyconfig.py index a3db2cb3ae..c03c2032f9 100644 --- a/easybuild/framework/easyconfig/easyconfig.py +++ b/easybuild/framework/easyconfig/easyconfig.py @@ -629,11 +629,14 @@ def _generate_template_values(self, ignore=None, skip_lower=True): if v is None: del self.template_values[k] + @handle_deprecated_or_replaced_easyconfig_parameters + def __contains__(self, key): + """Check whether easyconfig parameter is defined""" + return key in self._config + @handle_deprecated_or_replaced_easyconfig_parameters def __getitem__(self, key): - """ - will return the value without the help text - """ + """Return value of specified easyconfig parameter (without help text, etc.)""" value = self._config[key][0] if self.enable_templating: if self.template_values is None or len(self.template_values) == 0: @@ -644,10 +647,7 @@ def __getitem__(self, key): @handle_deprecated_or_replaced_easyconfig_parameters def __setitem__(self, key, value): - """ - sets the value of key in config. - help text is untouched - """ + """Set value of specified easyconfig parameter (help text & co is left untouched)""" self._config[key][0] = value @handle_deprecated_or_replaced_easyconfig_parameters From 6d627694037cbcabbb153112a17efb04376b2231 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 3 Feb 2015 16:52:12 +0100 Subject: [PATCH 279/298] emit decent error message when unknown key is used in __getitem__/__setitem__ of EasyConfig class --- easybuild/framework/easyconfig/easyconfig.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/easybuild/framework/easyconfig/easyconfig.py b/easybuild/framework/easyconfig/easyconfig.py index c03c2032f9..33325326a1 100644 --- a/easybuild/framework/easyconfig/easyconfig.py +++ b/easybuild/framework/easyconfig/easyconfig.py @@ -637,18 +637,26 @@ def __contains__(self, key): @handle_deprecated_or_replaced_easyconfig_parameters def __getitem__(self, key): """Return value of specified easyconfig parameter (without help text, etc.)""" - value = self._config[key][0] + value = None + if key in self._config: + value = self._config[key][0] + else: + self.log.error("Use of unknown easyconfig parameter '%s' when getting parameter value" % key) + if self.enable_templating: if self.template_values is None or len(self.template_values) == 0: self.generate_template_values() - return resolve_template(value, self.template_values) - else: - return value + value = resolve_template(value, self.template_values) + + return value @handle_deprecated_or_replaced_easyconfig_parameters def __setitem__(self, key, value): """Set value of specified easyconfig parameter (help text & co is left untouched)""" - self._config[key][0] = value + if key in self._config: + self._config[key][0] = value + else: + self.log.error("Use of unknown easyconfig parameter '%s' when setting parameter value" % key) @handle_deprecated_or_replaced_easyconfig_parameters def get(self, key, default=None): From 6092b24fa856b102dbc8b8e96b523bf55abc3b61 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 3 Feb 2015 16:57:36 +0100 Subject: [PATCH 280/298] add unit test for behaviour concerning use of unknown easyconfig parameters --- test/framework/easyconfig.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/test/framework/easyconfig.py b/test/framework/easyconfig.py index 0baa757515..5f662ccbf2 100644 --- a/test/framework/easyconfig.py +++ b/test/framework/easyconfig.py @@ -227,7 +227,7 @@ def test_extra_options(self): ]) self.prep() eb = EasyConfig(self.eb_file) - self.assertRaises(KeyError, lambda: eb['custom_key']) + self.assertErrorRegex(EasyBuildError, "unknown easyconfig parameter", lambda: eb['custom_key']) extra_vars = {'custom_key': ['default', "This is a default key", easyconfig.CUSTOM]} @@ -1001,6 +1001,27 @@ def foo(key): easyconfig.easyconfig.EasyConfig = orig_EasyConfig easyconfig.easyconfig.ActiveMNS = orig_ActiveMNS + def test_unknown_easyconfig_parameter(self): + """Check behaviour when unknown easyconfig parameters are used.""" + self.contents = '\n'.join([ + 'easyblock = "ConfigureMake"', + 'name = "pi"', + 'version = "3.14"', + 'homepage = "http://example.com"', + 'description = "test easyconfig"', + 'toolchain = {"name": "dummy", "version": "dummy"}', + ]) + self.prep() + ec = EasyConfig(self.eb_file) + self.assertFalse('therenosucheasyconfigparameterlikethis' in ec) + error_regex = "unknown easyconfig parameter" + self.assertErrorRegex(EasyBuildError, error_regex, lambda k: ec[k], 'therenosucheasyconfigparameterlikethis') + def set_ec_key(key): + """Dummy function to set easyconfig parameter in 'ec' EasyConfig instance""" + ec[key] = 'foobar' + self.assertErrorRegex(EasyBuildError, error_regex, set_ec_key, 'therenosucheasyconfigparameterlikethis') + + def suite(): """ returns all the testcases in this module """ return TestLoader().loadTestsFromTestCase(EasyConfigTest) From a477a7ec89e42c2d9ac7439a0ee46e4faebc0f87 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 3 Feb 2015 21:07:40 +0100 Subject: [PATCH 281/298] fix remarks --- easybuild/framework/easyconfig/easyconfig.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/easybuild/framework/easyconfig/easyconfig.py b/easybuild/framework/easyconfig/easyconfig.py index 33325326a1..9db79d881d 100644 --- a/easybuild/framework/easyconfig/easyconfig.py +++ b/easybuild/framework/easyconfig/easyconfig.py @@ -241,7 +241,7 @@ def parse(self): # provide suggestions for typos possible_typos = [(key, difflib.get_close_matches(key.lower(), self._config.keys(), 1, 0.85)) - for key in local_vars if key not in self._config] + for key in local_vars if key not in self] typos = [(key, guesses[0]) for (key, guesses) in possible_typos if len(guesses) == 1] if typos: @@ -656,15 +656,16 @@ def __setitem__(self, key, value): if key in self._config: self._config[key][0] = value else: - self.log.error("Use of unknown easyconfig parameter '%s' when setting parameter value" % key) + tup = (key, value) + self.log.error("Use of unknown easyconfig parameter '%s' when setting parameter value to '%s'" % tup) @handle_deprecated_or_replaced_easyconfig_parameters def get(self, key, default=None): """ Gets the value of a key in the config, with 'default' as fallback. """ - if key in self._config: - return self.__getitem__(key) + if key in self: + return self[key] else: return default From 2d4116320c9f7fd2943d04efc1c1262dc423d507 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 3 Feb 2015 21:07:47 +0100 Subject: [PATCH 282/298] fix broken test --- test/framework/module_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/framework/module_generator.py b/test/framework/module_generator.py index 8e606c3ed1..67db3f17e0 100644 --- a/test/framework/module_generator.py +++ b/test/framework/module_generator.py @@ -259,7 +259,7 @@ def test_mns(): init_config(build_options=build_options) err_pattern = 'nosucheasyconfigparameteravailable' - self.assertErrorRegex(KeyError, err_pattern, EasyConfig, os.path.join(ecs_dir, 'gzip-1.5-goolf-1.4.10.eb')) + self.assertErrorRegex(EasyBuildError, err_pattern, EasyConfig, os.path.join(ecs_dir, 'gzip-1.5-goolf-1.4.10.eb')) # test simple custom module naming scheme os.environ['EASYBUILD_MODULE_NAMING_SCHEME'] = 'TestModuleNamingScheme' From 328c091300e4dd4265ca5c68bdbc35432bba6fe1 Mon Sep 17 00:00:00 2001 From: Martin Marcher Date: Tue, 27 Jan 2015 16:25:39 +0100 Subject: [PATCH 283/298] Add proxy support for downloading This add support for downloading with proxies as often found in corporate settings. Pythons urllib2 will do "the right thing" when common environment variables such as `http_proxy` or `https_proxy` are set. Most noteably this also removes the `reporthook` nested function and removes a lot of code from the `download_file` function. --- easybuild/tools/filetools.py | 86 +++++++++--------------------------- 1 file changed, 22 insertions(+), 64 deletions(-) diff --git a/easybuild/tools/filetools.py b/easybuild/tools/filetools.py index d524c0ca61..d83ff659b5 100644 --- a/easybuild/tools/filetools.py +++ b/easybuild/tools/filetools.py @@ -40,6 +40,7 @@ import stat import time import urllib +import urllib2 import zlib from vsc.utils import fancylogger @@ -259,75 +260,30 @@ def download_file(filename, url, path): basedir = os.path.dirname(path) mkdir(basedir, parents=True) - # internal function to report on download progress - def report(blocks_read, blocksize, filesize): - """ - Report hook for urlretrieve, which logs the download progress every 10 seconds with log level info. - @param blocks_read: number of blocks already read - @param blocksize: size of one block, in bytes - @param filesize: total size of the download (in number of blocks blocks) - """ - if download_file.last_time + 10 < time.time(): - newblocks = blocks_read - download_file.last_block - download_file.last_block = blocks_read - tot_time = time.time() - download_file.last_time - - if filesize <= 0: - # content length isn't always set - report_msg = "downloaded in %ss" % tot_time - else: - percent = blocks_read * blocksize * 100 // filesize - report_msg = "of %d kb downloaded in %ss [%d %%]" % (filesize / 1024.0, tot_time, percent) - - downloaded_kbs = (blocks_read * blocksize) / 1024.0 - kbps = (blocksize * newblocks) / 1024 // tot_time - _log.info("Download report: %d kb %s (%d kbps)", downloaded_kbs, report_msg, kbps) - - download_file.last_time = time.time() - # try downloading, three times max. downloaded = False attempt_cnt = 0 while not downloaded and attempt_cnt < 3: - # get HTTP response code first before downloading file - response_code = None - try: - urlfile = urllib.urlopen(url) - if hasattr(urlfile, 'getcode'): # no getcode() in Py2.4 yet - response_code = urlfile.getcode() - urlfile.close() - except IOError, err: - _log.warning("Failed to get HTTP response code for %s, retrying: %s", url, err) - - if response_code is not None: - _log.debug('HTTP response code for given url: %d', response_code) - # check for a 4xx response code which indicates a non-existing URL - if response_code // 100 == 4: - _log.warning('url %s was not found (HTTP response %d), not trying again', url, response_code) - return None - - # use this functions's scope for variables we share with inner function used as report hook for urlretrieve - download_file.last_time = time.time() - download_file.last_block = 0 - - httpmsg = None - try: - (_, httpmsg) = urllib.urlretrieve(url, path, reporthook=report) - _log.info("Downloaded file %s from url %s to %s", filename, url, path) - - if httpmsg.type == "text/html" and not filename.endswith('.html'): - _log.warning("HTML file downloaded to %s, so assuming invalid download, retrying.", path) - remove_file(path) - else: - # successful download + with open(path, "wb+") as dest_fd: + try: + src_fd = urllib2.urlopen(url) + _log.debug('HTTP response code for given url: %d', src_fd.getcode()) + dest_fd.write(src_fd.read()) + _log.info("Downloaded file %s from url %s to %s", filename, url, path) downloaded = True - except IOError, err: - _log.warning("Error when downloading from %s to %s (%s), removing it and retrying", url, path, err) - remove_file(path) - - if not downloaded: - attempt_cnt += 1 - _log.warning("Downloading failed at attempt %s, retrying...", attempt_cnt) + src_fd.close() + except (urllib2.HTTPError, ) as err: + if err.code == 404: + attempt_cnt += 1 + _log.warning("Downloading failed at attempt %s, retrying...", attempt_cnt) + continue + raise + except (IOError, ) as err: + if attempt_cnt <= 3: + _log.warning("Failed to get HTTP response code for %s, retrying: %s", url, err) + attempt_cnt += 1 + continue + raise if downloaded: _log.info("Successful download of file %s from url %s to path %s", filename, url, path) @@ -1069,3 +1025,5 @@ def det_size(path): _log.warn("Could not determine install size: %s" % err) return installsize + +# vim: set ts=4 sts=4 fenc=utf-8 expandtab list: From 43d1ee47fa5ba9e6f1b88711a653790db7d4e224 Mon Sep 17 00:00:00 2001 From: Martin Marcher Date: Tue, 27 Jan 2015 17:43:43 +0100 Subject: [PATCH 284/298] Fix condition where a local file with protocol may come in --- easybuild/tools/filetools.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/easybuild/tools/filetools.py b/easybuild/tools/filetools.py index d83ff659b5..2180e7c7e0 100644 --- a/easybuild/tools/filetools.py +++ b/easybuild/tools/filetools.py @@ -272,12 +272,16 @@ def download_file(filename, url, path): _log.info("Downloaded file %s from url %s to %s", filename, url, path) downloaded = True src_fd.close() + except (ValueError, ) as err: + attempt_cnt += 1 + shutil.copy(url, path) + downloaded = True except (urllib2.HTTPError, ) as err: - if err.code == 404: - attempt_cnt += 1 - _log.warning("Downloading failed at attempt %s, retrying...", attempt_cnt) - continue - raise + if err.code == 404: + attempt_cnt += 1 + _log.warning("Downloading failed at attempt %s, retrying...", attempt_cnt) + continue + raise except (IOError, ) as err: if attempt_cnt <= 3: _log.warning("Failed to get HTTP response code for %s, retrying: %s", url, err) From 91132fc5ee9fd1ee14beba94e65dcca1dd5539da Mon Sep 17 00:00:00 2001 From: Martin Marcher Date: Thu, 5 Feb 2015 12:03:49 +0100 Subject: [PATCH 285/298] Fix code style --- easybuild/tools/filetools.py | 46 ++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/easybuild/tools/filetools.py b/easybuild/tools/filetools.py index 2180e7c7e0..1ee07da23a 100644 --- a/easybuild/tools/filetools.py +++ b/easybuild/tools/filetools.py @@ -39,8 +39,7 @@ import shutil import stat import time -import urllib -import urllib2 +import urllib2 # does the right thing for http proxy setups import zlib from vsc.utils import fancylogger @@ -264,29 +263,28 @@ def download_file(filename, url, path): downloaded = False attempt_cnt = 0 while not downloaded and attempt_cnt < 3: - with open(path, "wb+") as dest_fd: - try: - src_fd = urllib2.urlopen(url) - _log.debug('HTTP response code for given url: %d', src_fd.getcode()) - dest_fd.write(src_fd.read()) - _log.info("Downloaded file %s from url %s to %s", filename, url, path) - downloaded = True - src_fd.close() - except (ValueError, ) as err: + try: + src_fd = urllib2.urlopen(url) + _log.debug('HTTP response code for given url: %d', src_fd.getcode()) + write_file(path, src_fd.read()) + _log.info("Downloaded file %s from url %s to %s", filename, url, path) + downloaded = True + src_fd.close() + except (ValueError, ) as err: + attempt_cnt += 1 + shutil.copy(url, path) + downloaded = True + except (urllib2.HTTPError, ) as err: + if 400 <= err.code <= 499: attempt_cnt += 1 - shutil.copy(url, path) - downloaded = True - except (urllib2.HTTPError, ) as err: - if err.code == 404: - attempt_cnt += 1 - _log.warning("Downloading failed at attempt %s, retrying...", attempt_cnt) - continue + _log.warning("Downloading failed at attempt %s, retrying...", attempt_cnt) + else: raise - except (IOError, ) as err: - if attempt_cnt <= 3: - _log.warning("Failed to get HTTP response code for %s, retrying: %s", url, err) - attempt_cnt += 1 - continue + except (IOError, ) as err: + if attempt_cnt <= 3: + _log.warning("Failed to get HTTP response code for %s, retrying: %s", url, err) + attempt_cnt += 1 + else: raise if downloaded: @@ -1029,5 +1027,3 @@ def det_size(path): _log.warn("Could not determine install size: %s" % err) return installsize - -# vim: set ts=4 sts=4 fenc=utf-8 expandtab list: From e5705b92f908eb40378bcf7ff23eeaa5a61d28fc Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 5 Feb 2015 20:59:46 +0100 Subject: [PATCH 286/298] correctly determine variable name for EBEXTLIST --- easybuild/framework/easyblock.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 3269660935..92cce5e43d 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -881,7 +881,8 @@ def make_module_extra_extensions(self): # set environment variable that specifies list of extensions if self.exts_all: exts_list = ','.join(['%s-%s' % (ext['name'], ext.get('version', '')) for ext in self.exts_all]) - txt += self.module_generator.set_environment('EBEXTSLIST%s' % self.name.upper(), exts_list) + env_var_name = convert_name(self.name, upper=True) + txt += self.module_generator.set_environment('EBEXTSLIST%s' % env_var_name, exts_list) return txt From 5465be09090f359b2cc01d7dc54221eb74ecb6a6 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 6 Feb 2015 09:12:10 +0100 Subject: [PATCH 287/298] enhance unit test for download_file function to make sure it takes proxies into account --- test/framework/filetools.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/test/framework/filetools.py b/test/framework/filetools.py index cff024f9de..6c94e81a5d 100644 --- a/test/framework/filetools.py +++ b/test/framework/filetools.py @@ -33,7 +33,8 @@ import shutil import stat import tempfile -from test.framework.utilities import EnhancedTestCase, find_full_path +import urllib2 +from test.framework.utilities import EnhancedTestCase from unittest import TestLoader, main import easybuild.tools.filetools as ft @@ -181,11 +182,25 @@ def test_download_file(self): test_dir = os.path.abspath(os.path.dirname(__file__)) source_url = 'file://%s/sandbox/sources/toy/%s' % (test_dir, fn) res = ft.download_file(fn, source_url, target_location) - self.assertEqual(res, target_location) + self.assertEqual(res, target_location, "'download' of local file works") # non-existing files result in None return value self.assertEqual(ft.download_file(fn, 'file://%s/nosuchfile' % test_dir, target_location), None) + # install broken proxy handler for opening local files + # this should make urllib2.urlopen use this broken proxy for downloading from a file:// URL + proxy_handler = urllib2.ProxyHandler({'file': 'file://%s/nosuchfile' % test_dir}) + urllib2.install_opener(urllib2.build_opener(proxy_handler)) + + # downloading over a broken proxy results in None return value (failed download) + # this tests whether proxies are taken into account by download_file + self.assertEqual(ft.download_file(fn, source_url, target_location), None, "download over broken proxy fails") + + # restore a working file handler, and retest download of local file + urllib2.install_opener(urllib2.build_opener(urllib2.FileHandler())) + res = ft.download_file(fn, source_url, target_location) + self.assertEqual(res, target_location, "'download' of local file works after removing broken proxy") + def test_mkdir(self): """Test mkdir function.""" tmpdir = tempfile.mkdtemp() From e9284f2c6cc7844773166857f0520cbb42ec94ab Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 6 Feb 2015 15:09:36 +0100 Subject: [PATCH 288/298] do not ignore exit code of failing postinstall commands --- easybuild/framework/easyblock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 3269660935..db832d4ca2 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -1460,7 +1460,7 @@ def post_install_step(self): for cmd in self.cfg['postinstallcmds']: if not isinstance(cmd, basestring): self.log.error("Invalid element in 'postinstallcmds', not a string: %s" % cmd) - run_cmd(cmd, simple=True, log_ok=False, log_all=False) + run_cmd(cmd, simple=True, log_ok=True, log_all=True) if self.group is not None: # remove permissions for others, and set group ID From 61a24fe54ba2ebe0c9507bd64e6287ef2722f36b Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 6 Feb 2015 18:36:36 +0100 Subject: [PATCH 289/298] specify default timeout for initiating download, provide configure option for specifying different timeout --- easybuild/tools/config.py | 1 + easybuild/tools/filetools.py | 9 ++++++++- easybuild/tools/options.py | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py index cc3b83ea8d..60af87d556 100644 --- a/easybuild/tools/config.py +++ b/easybuild/tools/config.py @@ -80,6 +80,7 @@ def mk_full_default_path(name, prefix=DEFAULT_PREFIX): BUILD_OPTIONS_CMDLINE = { None: [ 'aggregate_regtest', + 'download_timeout', 'dump_test_report', 'easyblock', 'filter_deps', diff --git a/easybuild/tools/filetools.py b/easybuild/tools/filetools.py index 1ee07da23a..2505a762e4 100644 --- a/easybuild/tools/filetools.py +++ b/easybuild/tools/filetools.py @@ -255,6 +255,13 @@ def download_file(filename, url, path): _log.debug("Trying to download %s from %s to %s", filename, url, path) + timeout = build_option('download_timeout') + if timeout is None: + # default to 10sec timeout if none was specified + # default system timeout (used is nothing is specified) may be infinite (?) + timeout = 10 + _log.debug("Using timeout of %s seconds for initiating download" % timeout) + # make sure directory exists basedir = os.path.dirname(path) mkdir(basedir, parents=True) @@ -264,7 +271,7 @@ def download_file(filename, url, path): attempt_cnt = 0 while not downloaded and attempt_cnt < 3: try: - src_fd = urllib2.urlopen(url) + src_fd = urllib2.urlopen(url, timeout=timeout) _log.debug('HTTP response code for given url: %d', src_fd.getcode()) write_file(path, src_fd.read()) _log.info("Downloaded file %s from url %s to %s", filename, url, path) diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index 6d3c4393af..80fd4db664 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -179,6 +179,7 @@ def override_options(self): 'cleanup-builddir': ("Cleanup build dir after successful installation.", None, 'store_true', True), 'deprecated': ("Run pretending to be (future) version, to test removal of deprecated code.", None, 'store', None), + 'download_timeout': ("Timeout for initiating downloads (in seconds)", None, 'store', None), 'easyblock': ("easyblock to use for processing the spec file or dumping the options", None, 'store', None, 'e', {'metavar': 'CLASS'}), 'experimental': ("Allow experimental code (with behaviour that can be changed or removed at any given time).", From de609e07b0df11a42b2db7229c60c2d3b657e2bc Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 6 Feb 2015 20:18:26 +0100 Subject: [PATCH 290/298] rework download_file --- easybuild/tools/filetools.py | 43 +++++++++++++++++------------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/easybuild/tools/filetools.py b/easybuild/tools/filetools.py index 2505a762e4..f46d327698 100644 --- a/easybuild/tools/filetools.py +++ b/easybuild/tools/filetools.py @@ -39,7 +39,7 @@ import shutil import stat import time -import urllib2 # does the right thing for http proxy setups +import urllib2 # does the right thing for http proxy setups, urllib does not! import zlib from vsc.utils import fancylogger @@ -268,38 +268,35 @@ def download_file(filename, url, path): # try downloading, three times max. downloaded = False + max_attempts = 3 attempt_cnt = 0 - while not downloaded and attempt_cnt < 3: + while not downloaded and attempt_cnt < max_attempts: try: - src_fd = urllib2.urlopen(url, timeout=timeout) - _log.debug('HTTP response code for given url: %d', src_fd.getcode()) - write_file(path, src_fd.read()) - _log.info("Downloaded file %s from url %s to %s", filename, url, path) + url_fd = urllib2.urlopen(url, timeout=timeout) + _log.debug('response code for given url: %s' % url_fd.getcode()) + write_file(path, url_fd.read()) + _log.info("Downloaded file %s from url %s to %s" % (filename, url, path)) downloaded = True - src_fd.close() - except (ValueError, ) as err: - attempt_cnt += 1 - shutil.copy(url, path) - downloaded = True - except (urllib2.HTTPError, ) as err: + url_fd.close() + except urllib2.HTTPError as err: if 400 <= err.code <= 499: - attempt_cnt += 1 - _log.warning("Downloading failed at attempt %s, retrying...", attempt_cnt) + _log.warning("URL %s was not found (HTTP response code %s), not trying again" % (url, err.code)) + break else: - raise - except (IOError, ) as err: - if attempt_cnt <= 3: - _log.warning("Failed to get HTTP response code for %s, retrying: %s", url, err) + _log.warning("HTTPError occured while trying to download %s to %s: %s" % (url, path, err)) attempt_cnt += 1 - else: - raise + except IOError as err: + _log.warning("IOError occurred while trying to download %s to %s: %s" % (url, path, err)) + attempt_cnt += 1 + + if not downloaded and attempt_cnt < max_attempts: + _log.info("Attempt %d of downloading %s to %s failed, trying again..." % (attempt_cnt, url, path)) if downloaded: - _log.info("Successful download of file %s from url %s to path %s", filename, url, path) + _log.info("Successful download of file %s from url %s to path %s" % (filename, url, path)) return path else: - # failed to download after multiple attempts - _log.warning("Too many failed download attempts, giving up") + _log.warning("Download of %s to %s failed, done trying" % (url, path)) return None From c186ebbc9c2d3352e218fea2afc613559b6d9a97 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 6 Feb 2015 20:18:57 +0100 Subject: [PATCH 291/298] move comment --- easybuild/tools/filetools.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/easybuild/tools/filetools.py b/easybuild/tools/filetools.py index f46d327698..12e2fd93ec 100644 --- a/easybuild/tools/filetools.py +++ b/easybuild/tools/filetools.py @@ -39,7 +39,7 @@ import shutil import stat import time -import urllib2 # does the right thing for http proxy setups, urllib does not! +import urllib2 import zlib from vsc.utils import fancylogger @@ -272,6 +272,7 @@ def download_file(filename, url, path): attempt_cnt = 0 while not downloaded and attempt_cnt < max_attempts: try: + # urllib2 does the right thing for http proxy setups, urllib does not! url_fd = urllib2.urlopen(url, timeout=timeout) _log.debug('response code for given url: %s' % url_fd.getcode()) write_file(path, url_fd.read()) From df2cdcdbe432391e5c00b27d6ebe35b32fda2dad Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 6 Feb 2015 21:21:05 +0100 Subject: [PATCH 292/298] catch all exceptions that may occur in download_file for proper error reporting, fix broken unit tests --- easybuild/tools/filetools.py | 2 ++ test/framework/easyblock.py | 11 ++++------- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/easybuild/tools/filetools.py b/easybuild/tools/filetools.py index 12e2fd93ec..ec2d7122a2 100644 --- a/easybuild/tools/filetools.py +++ b/easybuild/tools/filetools.py @@ -289,6 +289,8 @@ def download_file(filename, url, path): except IOError as err: _log.warning("IOError occurred while trying to download %s to %s: %s" % (url, path, err)) attempt_cnt += 1 + except Exception, err: + _log.error("Unexpected error occurred when trying to download %s to %s: %s" % (url, path, err)) if not downloaded and attempt_cnt < max_attempts: _log.info("Attempt %d of downloading %s to %s failed, trying again..." % (attempt_cnt, url, path)) diff --git a/test/framework/easyblock.py b/test/framework/easyblock.py index c2dcb01655..2657389906 100644 --- a/test/framework/easyblock.py +++ b/test/framework/easyblock.py @@ -451,7 +451,7 @@ def test_obtain_file(self): # 'downloading' a file to (first) sourcepath works init_config(args=["--sourcepath=%s:/no/such/dir:%s" % (tmpdir, testdir)]) shutil.copy2(toy_tarball_path, tmpdir_subdir) - res = eb.obtain_file(toy_tarball, urls=[os.path.join('file://', tmpdir_subdir)]) + res = eb.obtain_file(toy_tarball, urls=['file://%s' % tmpdir_subdir]) self.assertEqual(res, os.path.join(tmpdir, 't', 'toy', toy_tarball)) # finding a file in sourcepath works @@ -460,16 +460,13 @@ def test_obtain_file(self): self.assertEqual(res, toy_tarball_path) # sourcepath has preference over downloading - res = eb.obtain_file(toy_tarball, urls=[os.path.join('file://', tmpdir_subdir)]) + res = eb.obtain_file(toy_tarball, urls=['file://%s' % tmpdir_subdir]) self.assertEqual(res, toy_tarball_path) # obtain_file yields error for non-existing files fn = 'thisisclearlyanonexistingfile' - try: - eb.obtain_file(fn, urls=[os.path.join('file://', tmpdir_subdir)]) - except EasyBuildError, err: - fail_regex = re.compile("Couldn't find file %s anywhere, and downloading it didn't work either" % fn) - self.assertTrue(fail_regex.search(str(err))) + error_regex = "Couldn't find file %s anywhere, and downloading it didn't work either" % fn + self.assertErrorRegex(EasyBuildError, error_regex, eb.obtain_file, fn, urls=['file://%s' % tmpdir_subdir]) # file specifications via URL also work, are downloaded to (first) sourcepath init_config(args=["--sourcepath=%s:/no/such/dir:%s" % (tmpdir, sandbox_sources)]) From a76af3bb986d94ed5e4acd6123bcd70cd47609fb Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 6 Feb 2015 21:25:15 +0100 Subject: [PATCH 293/298] fix rare case in which used easyconfig and copied easyconfig are the same --- easybuild/framework/easyblock.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 3269660935..37f14a2cdf 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -1934,7 +1934,9 @@ def build_and_install_one(module, orig_environ): try: newspec = os.path.join(new_log_dir, "%s-%s.eb" % (app.name, det_full_ec_version(app.cfg))) - shutil.copy(spec, newspec) + # only copy if the files are not the same actual file already + if not os.path.samefile(spec, newspec): + shutil.copy(spec, newspec) _log.debug("Copied easyconfig file %s to %s" % (spec, newspec)) except (IOError, OSError), err: print_error("Failed to move easyconfig %s to log dir %s: %s" % (spec, new_log_dir, err)) From 85b622e4d4d2c27153bd26f05360d6aad63c33ec Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 6 Feb 2015 21:46:24 +0100 Subject: [PATCH 294/298] better debug logging in download_file --- easybuild/tools/filetools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/tools/filetools.py b/easybuild/tools/filetools.py index ec2d7122a2..8b81d71d1c 100644 --- a/easybuild/tools/filetools.py +++ b/easybuild/tools/filetools.py @@ -274,7 +274,7 @@ def download_file(filename, url, path): try: # urllib2 does the right thing for http proxy setups, urllib does not! url_fd = urllib2.urlopen(url, timeout=timeout) - _log.debug('response code for given url: %s' % url_fd.getcode()) + _log.debug('response code for given url %s: %s' % (url, url_fd.getcode())) write_file(path, url_fd.read()) _log.info("Downloaded file %s from url %s to %s" % (filename, url, path)) downloaded = True From 2ba93e74375cdb6b368ce9aac3cf6b1808f48e0a Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 6 Feb 2015 21:58:58 +0100 Subject: [PATCH 295/298] fix error message on copying easyconfig to install dir --- easybuild/framework/easyblock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 65270998b5..8da192401c 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -1940,7 +1940,7 @@ def build_and_install_one(module, orig_environ): shutil.copy(spec, newspec) _log.debug("Copied easyconfig file %s to %s" % (spec, newspec)) except (IOError, OSError), err: - print_error("Failed to move easyconfig %s to log dir %s: %s" % (spec, new_log_dir, err)) + print_error("Failed to copy easyconfig %s to %s: %s" % (spec, newspec, err)) # build failed else: From 7247010347ca25e8bfda6df0722597a63ce41a28 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 6 Feb 2015 22:27:59 +0100 Subject: [PATCH 296/298] make sure newspec exists before using os.path.samefile on it --- easybuild/framework/easyblock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 8da192401c..28387f5cfb 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -1936,7 +1936,7 @@ def build_and_install_one(module, orig_environ): try: newspec = os.path.join(new_log_dir, "%s-%s.eb" % (app.name, det_full_ec_version(app.cfg))) # only copy if the files are not the same actual file already - if not os.path.samefile(spec, newspec): + if not (os.path.exists(newspec) or os.path.samefile(spec, newspec)): shutil.copy(spec, newspec) _log.debug("Copied easyconfig file %s to %s" % (spec, newspec)) except (IOError, OSError), err: From d331f1b6f424160f18f3d008db247e5044c1e9f6 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 6 Feb 2015 22:34:41 +0100 Subject: [PATCH 297/298] always issue debug log message for run_cmd exit code and output --- easybuild/tools/run.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/easybuild/tools/run.py b/easybuild/tools/run.py index 9b42b159fe..ba13928464 100644 --- a/easybuild/tools/run.py +++ b/easybuild/tools/run.py @@ -376,18 +376,17 @@ def parse_cmd_output(cmd, stdouterr, ec, simple, log_all, log_ok, regexp): if not regexp: use_regexp = False + _log.debug('cmd "%s" exited with exitcode %s and output:\n%s' % (cmd, ec, stdouterr)) + if ec and (log_all or log_ok): # We don't want to error if the user doesn't care if check_ec: _log.error('cmd "%s" exited with exitcode %s and output:\n%s' % (cmd, ec, stdouterr)) else: _log.warn('cmd "%s" exited with exitcode %s and output:\n%s' % (cmd, ec, stdouterr)) - - if not ec: + elif not ec: if log_all: _log.info('cmd "%s" exited with exitcode %s and output:\n%s' % (cmd, ec, stdouterr)) - else: - _log.debug('cmd "%s" exited with exitcode %s and output:\n%s' % (cmd, ec, stdouterr)) # parse the stdout/stderr for errors when strictness dictates this or when regexp is passed in if use_regexp or regexp: From 30dc4853eb60338ffa5f71e31c53872a171afd27 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 6 Feb 2015 22:39:04 +0100 Subject: [PATCH 298/298] fix exists/samefile logic in check whether or not to copy easyconfig file --- easybuild/framework/easyblock.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 28387f5cfb..2d72e5ef11 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -1935,10 +1935,12 @@ def build_and_install_one(module, orig_environ): try: newspec = os.path.join(new_log_dir, "%s-%s.eb" % (app.name, det_full_ec_version(app.cfg))) - # only copy if the files are not the same actual file already - if not (os.path.exists(newspec) or os.path.samefile(spec, newspec)): + # only copy if the files are not the same file already (yes, it happens) + if os.path.exists(newspec) and os.path.samefile(spec, newspec): + _log.debug("Not copying easyconfig file %s to %s since files are identical" % (spec, newspec)) + else: shutil.copy(spec, newspec) - _log.debug("Copied easyconfig file %s to %s" % (spec, newspec)) + _log.debug("Copied easyconfig file %s to %s" % (spec, newspec)) except (IOError, OSError), err: print_error("Failed to copy easyconfig %s to %s: %s" % (spec, newspec, err))

    LF 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 ePx9Z)o_KINc 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