From 22e28e58b3e3d689ebf698477d7531b0064c1f30 Mon Sep 17 00:00:00 2001 From: Gianluca Santarossa Date: Wed, 11 Feb 2015 15:10:02 +0100 Subject: [PATCH 01/51] Added first version of experimental support for package_step (based on code by Marc Litherland) Update easyblock.py Bug fixes round #1 bugfixing round #2 Bugfixes round #3 Bugfixes round #4 Bugfixes #5. Rewritten map() as list comprehension. Still not available as an optional parameter --- easybuild/framework/easyblock.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index f0cf43bb0b..dcaee49fca 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -77,7 +77,6 @@ from easybuild.tools.utilities import remove_unwanted_chars from easybuild.tools.version import this_is_easybuild, VERBOSE_VERSION, VERSION - _log = fancylogger.getLogger('easyblock') @@ -1444,8 +1443,19 @@ def extensions_step(self, fetch=False): self.clean_up_fake_module(fake_mod_data) def package_step(self): - """Package software (e.g. into an RPM).""" - pass + """Prepare package software (e.g. into an RPM) with fpm.""" + rpmname = "HPCBIOS.20150211-%s-%s" % (self.name, self.version) + os.chdir(os.environ['TMPDIR']) + + if self.toolchain.name == "dummy": + dependencies = [] + else: + dependencies = [ "=".join([ self.toolchain.name, self.toolchain.version ]) ] + dependencies.extend([ "=".join([ dep['name'], dep['version'] ]) for dep in self.cfg.dependencies() ]) + depstring = '--dependency ' + ' --dependency '.join(dependencies) + + cmd = "fpm --workdir $TMPDIR -t rpm --name %s -s dir %s -C %s" % (rpmname, depstring, self.installdir) + (out, _) = run_cmd(cmd, log_all=True, simple=False) def post_install_step(self): """ @@ -1787,11 +1797,11 @@ def prepare_step_spec(initial): # part 3: post-iteration part steps_part3 = [ ('extensions', 'taking care of extensions', [lambda x: x.extensions_step()], False), - ('package', 'packaging', [lambda x: x.package_step()], True), ('postproc', 'postprocessing', [lambda x: x.post_install_step()], True), ('sanitycheck', 'sanity checking', [lambda x: x.sanity_check_step()], False), ('cleanup', 'cleaning up', [lambda x: x.cleanup_step()], False), ('module', 'creating module', [lambda x: x.make_module_step()], False), + # ('package', 'packaging', [lambda x: x.package_step()], True), ] # full list of steps, included iterated steps @@ -1803,6 +1813,14 @@ def prepare_step_spec(initial): lambda x: x.test_cases_step(), ], False)) + ## CHANGE TRUE TO build_option, and + ## ADD build-pkg to all the configuration dicts + ## # if build_option('build-pkg'): + if True: + steps.append(('package', 'packaging', [lambda x: x.package_step()], True)) + else: + self.log.debug('Skipping package step') + return steps def run_all_steps(self, run_test_cases): From 46ad3997d5650ed1a12975236f71ec6aad3ae862 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Sat, 21 Feb 2015 23:02:11 -0500 Subject: [PATCH 02/51] adding a new fpm packaging function --- easybuild/framework/easyblock.py | 15 +++------ easybuild/framework/package.py | 58 ++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 11 deletions(-) create mode 100644 easybuild/framework/package.py diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index dcaee49fca..37e6ef1a6d 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -56,6 +56,7 @@ from easybuild.framework.easyconfig.parser import fetch_parameters_from_easyconfig from easybuild.framework.easyconfig.tools import get_paths_for from easybuild.framework.easyconfig.templates import TEMPLATE_NAMES_EASYBLOCK_RUN_STEP +from easybuild.framework.package import package_fpm from easybuild.tools.build_details import get_build_stats 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 @@ -1444,18 +1445,10 @@ def extensions_step(self, fetch=False): def package_step(self): """Prepare package software (e.g. into an RPM) with fpm.""" - rpmname = "HPCBIOS.20150211-%s-%s" % (self.name, self.version) - os.chdir(os.environ['TMPDIR']) - - if self.toolchain.name == "dummy": - dependencies = [] - else: - dependencies = [ "=".join([ self.toolchain.name, self.toolchain.version ]) ] - dependencies.extend([ "=".join([ dep['name'], dep['version'] ]) for dep in self.cfg.dependencies() ]) - depstring = '--dependency ' + ' --dependency '.join(dependencies) - cmd = "fpm --workdir $TMPDIR -t rpm --name %s -s dir %s -C %s" % (rpmname, depstring, self.installdir) - (out, _) = run_cmd(cmd, log_all=True, simple=False) + path_to_module_file = os.path.join(install_path('mod'), build_option('suffix_modules_path'), self.full_mod_name) + + package_fpm(self, path_to_module_file) def post_install_step(self): """ diff --git a/easybuild/framework/package.py b/easybuild/framework/package.py new file mode 100644 index 0000000000..446a88ba48 --- /dev/null +++ b/easybuild/framework/package.py @@ -0,0 +1,58 @@ +# # +# 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 . +# # + +""" +A place for packaging functions + +""" + +import os + +def package_fpm(easyblock,modfile_path ): + rpmname = "HPCBIOS.20150211-%s-%s" % (easyblock.name, easyblock.version) + os.chdir("/tmp") + + if easyblock.toolchain.name == "dummy": + dependencies = [] + else: + dependencies = [ "=".join([ easyblock.toolchain.name, easyblock.toolchain.version ]) ] + dependencies.extend([ "=".join([ dep['name'], dep['version'] ]) for dep in easyblock.cfg.dependencies() ]) + depstring = '--depends ' + ' --depends '.join(dependencies) + cmdlist=[ + 'fpm', + '--workdir', workdir, + '--name', pkgname, + + ] + cmdlist.extend(' --depends '.join(dependencies)) + [ + '-t', flavour, # target + '-s', 'dir', # source + '-C', easyblock.installdir, + ] + cmdlist.extend(deplist) + + cmd = "fpm --workdir /tmp --name %s %s -s dir -t rpm -C %s ." % (rpmname, depstring, easyblock.installdir) + (out, _) = run_cmd(cmd, log_all=True, simple=False) From e86ca6a3e46668383944133df21f0ffe68575913 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 23 Feb 2015 14:55:31 -0500 Subject: [PATCH 03/51] trying to teplate out the name and make a command list --- easybuild/framework/package.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/easybuild/framework/package.py b/easybuild/framework/package.py index 446a88ba48..708d23490e 100644 --- a/easybuild/framework/package.py +++ b/easybuild/framework/package.py @@ -29,10 +29,19 @@ """ import os +from easybuild.tools.run import run_cmd -def package_fpm(easyblock,modfile_path ): +def package_fpm(easyblock, modfile_path ): rpmname = "HPCBIOS.20150211-%s-%s" % (easyblock.name, easyblock.version) - os.chdir("/tmp") + workdir = "/tmp" + os.chdir(workdir) + + pkgtemplate = "HPCBIOS.20150211-%(name)s-%(version)s" + + pkgname=pkgtemplate % { + 'name' : easyblock.name, + 'version' : easyblock.version, + } if easyblock.toolchain.name == "dummy": dependencies = [] @@ -47,12 +56,10 @@ def package_fpm(easyblock,modfile_path ): ] cmdlist.extend(' --depends '.join(dependencies)) - [ - '-t', flavour, # target + cmdlist.extend([ + '-t', 'rpm', # target '-s', 'dir', # source '-C', easyblock.installdir, - ] - cmdlist.extend(deplist) + ]) - cmd = "fpm --workdir /tmp --name %s %s -s dir -t rpm -C %s ." % (rpmname, depstring, easyblock.installdir) - (out, _) = run_cmd(cmd, log_all=True, simple=False) + (out, _) = run_cmd(cmdlist, log_all=True, simple=False) From 8f62f859b94757c76d7096d9ff68cc66860f60d2 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Tue, 3 Mar 2015 09:46:26 -0500 Subject: [PATCH 04/51] adding some updates --- easybuild/framework/package.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/easybuild/framework/package.py b/easybuild/framework/package.py index 708d23490e..658914d1c5 100644 --- a/easybuild/framework/package.py +++ b/easybuild/framework/package.py @@ -53,13 +53,13 @@ def package_fpm(easyblock, modfile_path ): 'fpm', '--workdir', workdir, '--name', pkgname, - - ] - cmdlist.extend(' --depends '.join(dependencies)) - cmdlist.extend([ '-t', 'rpm', # target '-s', 'dir', # source '-C', easyblock.installdir, + ] + cmdlist.extend([ depstring ]) + cmdlist.extend([ + easyblock.installdir, ]) (out, _) = run_cmd(cmdlist, log_all=True, simple=False) From 2a850a7d7000639fe4dabaadec0e2cc37d958fe1 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Tue, 17 Mar 2015 10:02:28 -0400 Subject: [PATCH 05/51] update tmp directory generator --- easybuild/framework/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/framework/package.py b/easybuild/framework/package.py index 658914d1c5..ca9ff8fe76 100644 --- a/easybuild/framework/package.py +++ b/easybuild/framework/package.py @@ -33,7 +33,7 @@ def package_fpm(easyblock, modfile_path ): rpmname = "HPCBIOS.20150211-%s-%s" % (easyblock.name, easyblock.version) - workdir = "/tmp" + workdir = tempfile.mkdtemp() os.chdir(workdir) pkgtemplate = "HPCBIOS.20150211-%(name)s-%(version)s" From c2dd5fcbeb3a75dd2dc740a3d4629f70e550aaff Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Tue, 17 Mar 2015 10:11:38 -0400 Subject: [PATCH 06/51] cleanup per PR comments from @boegel --- easybuild/framework/easyblock.py | 3 +-- .../{framework/package.py => tools/packaging.py} | 13 ++++++------- 2 files changed, 7 insertions(+), 9 deletions(-) rename easybuild/{framework/package.py => tools/packaging.py} (88%) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 37e6ef1a6d..b792fc471a 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -56,7 +56,7 @@ from easybuild.framework.easyconfig.parser import fetch_parameters_from_easyconfig from easybuild.framework.easyconfig.tools import get_paths_for from easybuild.framework.easyconfig.templates import TEMPLATE_NAMES_EASYBLOCK_RUN_STEP -from easybuild.framework.package import package_fpm +from easybuild.tools.packaging import package_fpm from easybuild.tools.build_details import get_build_stats 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 @@ -1794,7 +1794,6 @@ def prepare_step_spec(initial): ('sanitycheck', 'sanity checking', [lambda x: x.sanity_check_step()], False), ('cleanup', 'cleaning up', [lambda x: x.cleanup_step()], False), ('module', 'creating module', [lambda x: x.make_module_step()], False), - # ('package', 'packaging', [lambda x: x.package_step()], True), ] # full list of steps, included iterated steps diff --git a/easybuild/framework/package.py b/easybuild/tools/packaging.py similarity index 88% rename from easybuild/framework/package.py rename to easybuild/tools/packaging.py index ca9ff8fe76..da5811c0ab 100644 --- a/easybuild/framework/package.py +++ b/easybuild/tools/packaging.py @@ -34,7 +34,10 @@ def package_fpm(easyblock, modfile_path ): rpmname = "HPCBIOS.20150211-%s-%s" % (easyblock.name, easyblock.version) workdir = tempfile.mkdtemp() - os.chdir(workdir) + try: + os.chdir(workdir) + except OSError, err: + _log.error("Failed to chdir into workdir: %s : %s" % (workdir, err)) pkgtemplate = "HPCBIOS.20150211-%(name)s-%(version)s" @@ -42,11 +45,7 @@ def package_fpm(easyblock, modfile_path ): 'name' : easyblock.name, 'version' : easyblock.version, } - - if easyblock.toolchain.name == "dummy": - dependencies = [] - else: - dependencies = [ "=".join([ easyblock.toolchain.name, easyblock.toolchain.version ]) ] + dependencies = [] dependencies.extend([ "=".join([ dep['name'], dep['version'] ]) for dep in easyblock.cfg.dependencies() ]) depstring = '--depends ' + ' --depends '.join(dependencies) cmdlist=[ @@ -62,4 +61,4 @@ def package_fpm(easyblock, modfile_path ): easyblock.installdir, ]) - (out, _) = run_cmd(cmdlist, log_all=True, simple=False) + (out, _) = run_cmd(cmdlist, log_all=True, simple=True) From 390ddc73976443afccf99d587b2c1f2f6ec9eaf6 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Tue, 17 Mar 2015 10:17:56 -0400 Subject: [PATCH 07/51] adding authors --- easybuild/tools/packaging.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/easybuild/tools/packaging.py b/easybuild/tools/packaging.py index da5811c0ab..07484b5c87 100644 --- a/easybuild/tools/packaging.py +++ b/easybuild/tools/packaging.py @@ -26,6 +26,9 @@ """ A place for packaging functions +@author: Marc Litherland +@author: Gianluca Santarossa +@author: Robert Schmidt (Ottawa Hospital Research Institute) """ import os From cdeef5191cbccdac5433486a3ceb50cf41ecdd99 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 18 Mar 2015 21:00:21 -0400 Subject: [PATCH 08/51] adding the package_with option --- easybuild/framework/easyblock.py | 3 ++- easybuild/tools/config.py | 1 + easybuild/tools/options.py | 1 + easybuild/tools/packaging.py | 1 + 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index b792fc471a..4d73e73d4d 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -1808,7 +1808,8 @@ def prepare_step_spec(initial): ## CHANGE TRUE TO build_option, and ## ADD build-pkg to all the configuration dicts ## # if build_option('build-pkg'): - if True: + packaging_tool = build_option('package_with') + if packaging_tool is not None: steps.append(('package', 'packaging', [lambda x: x.package_step()], True)) else: self.log.debug('Skipping package step') diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py index 054b109d26..fcb4e41322 100644 --- a/easybuild/tools/config.py +++ b/easybuild/tools/config.py @@ -98,6 +98,7 @@ def mk_full_default_path(name, prefix=DEFAULT_PREFIX): 'test_report_env_filter', 'testoutput', 'umask', + 'package_with', ], False: [ 'allow_modules_tool_mismatch', diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index 5dc144e522..76b51be40c 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -268,6 +268,7 @@ def config_options(self): 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), + 'package-with': ("Define the packaging system to be used", None, 'store', None), }) self.log.debug("config_options: descr %s opts %s" % (descr, opts)) diff --git a/easybuild/tools/packaging.py b/easybuild/tools/packaging.py index 07484b5c87..c97872ac9c 100644 --- a/easybuild/tools/packaging.py +++ b/easybuild/tools/packaging.py @@ -32,6 +32,7 @@ """ import os +import tempfile from easybuild.tools.run import run_cmd def package_fpm(easyblock, modfile_path ): From ddc3855844aea61a42629e7c1b56b2a138da9c23 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 19 Mar 2015 07:56:32 -0400 Subject: [PATCH 09/51] second try to get build_option working --- easybuild/framework/easyblock.py | 4 ++-- easybuild/tools/config.py | 2 +- easybuild/tools/options.py | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 4d73e73d4d..1bf5a6f728 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -1808,11 +1808,11 @@ def prepare_step_spec(initial): ## CHANGE TRUE TO build_option, and ## ADD build-pkg to all the configuration dicts ## # if build_option('build-pkg'): - packaging_tool = build_option('package_with') + packaging_tool = build_option('package_tool') if packaging_tool is not None: steps.append(('package', 'packaging', [lambda x: x.package_step()], True)) else: - self.log.debug('Skipping package step') + _log.debug('Skipping package step') return steps diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py index fcb4e41322..3848bbab1c 100644 --- a/easybuild/tools/config.py +++ b/easybuild/tools/config.py @@ -91,6 +91,7 @@ def mk_full_default_path(name, prefix=DEFAULT_PREFIX): 'modules_footer', 'only_blocks', 'optarch', + 'package_tool', 'regtest_output_dir', 'skip', 'stop', @@ -98,7 +99,6 @@ def mk_full_default_path(name, prefix=DEFAULT_PREFIX): 'test_report_env_filter', 'testoutput', 'umask', - 'package_with', ], False: [ 'allow_modules_tool_mismatch', diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index 76b51be40c..de5a51cfd5 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -243,6 +243,8 @@ def config_options(self): None, 'store_or_None', None, {'metavar': "PATH"}), 'modules-tool': ("Modules tool to use", 'choice', 'store', DEFAULT_MODULES_TOOL, sorted(avail_modules_tools().keys())), + 'package-tool': ("Packaging tool to use", + None, 'store_or_None', None), 'prefix': (("Change prefix for buildpath, installpath, sourcepath and repositorypath " "(used prefix for defaults %s)" % DEFAULT_PREFIX), None, 'store', None), @@ -268,7 +270,6 @@ def config_options(self): 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), - 'package-with': ("Define the packaging system to be used", None, 'store', None), }) self.log.debug("config_options: descr %s opts %s" % (descr, opts)) From a6abba7587de82bc9ac90e9b98cace03ebcf78d0 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 19 Mar 2015 21:15:00 -0400 Subject: [PATCH 10/51] moving the build_option to the packaging_step --- easybuild/framework/easyblock.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 1bf5a6f728..02068e17f0 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -1448,7 +1448,12 @@ def package_step(self): path_to_module_file = os.path.join(install_path('mod'), build_option('suffix_modules_path'), self.full_mod_name) - package_fpm(self, path_to_module_file) + packaging_tool = build_option('package_tool') + if packaging_tool is not None: + package_fpm(self, path_to_module_file) + else: + _log.debug('Skipping package step') + def post_install_step(self): """ @@ -1794,6 +1799,7 @@ def prepare_step_spec(initial): ('sanitycheck', 'sanity checking', [lambda x: x.sanity_check_step()], False), ('cleanup', 'cleaning up', [lambda x: x.cleanup_step()], False), ('module', 'creating module', [lambda x: x.make_module_step()], False), + ('package', 'packaging', [lambda x: x.package_step()], True), ] # full list of steps, included iterated steps @@ -1805,15 +1811,6 @@ def prepare_step_spec(initial): lambda x: x.test_cases_step(), ], False)) - ## CHANGE TRUE TO build_option, and - ## ADD build-pkg to all the configuration dicts - ## # if build_option('build-pkg'): - packaging_tool = build_option('package_tool') - if packaging_tool is not None: - steps.append(('package', 'packaging', [lambda x: x.package_step()], True)) - else: - _log.debug('Skipping package step') - return steps def run_all_steps(self, run_test_cases): From 2dc218a5086deb00f185b5fbd6b0568655d46a73 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Fri, 20 Mar 2015 10:15:10 -0400 Subject: [PATCH 11/51] working, but puts the RPM into installdir (under package dir) --- easybuild/framework/easyblock.py | 6 ++++-- easybuild/tools/packaging.py | 20 +++++++++++++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 02068e17f0..ba1bfe7faa 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -1450,10 +1450,12 @@ def package_step(self): packaging_tool = build_option('package_tool') if packaging_tool is not None: - package_fpm(self, path_to_module_file) + package_dir = package_fpm(self, path_to_module_file) else: _log.debug('Skipping package step') + shutil.copytree(package_dir, os.path.join(self.installdir, "package")) + def post_install_step(self): """ @@ -1930,7 +1932,7 @@ def build_and_install_one(ecdict, orig_environ): except EasyBuildError, err: _log.warn("Unable to commit easyconfig to repository: %s", err) - success = True + success = True succ = "successfully" summary = "COMPLETED" diff --git a/easybuild/tools/packaging.py b/easybuild/tools/packaging.py index c97872ac9c..082cb868b1 100644 --- a/easybuild/tools/packaging.py +++ b/easybuild/tools/packaging.py @@ -27,17 +27,25 @@ A place for packaging functions @author: Marc Litherland -@author: Gianluca Santarossa +@author: Gianluca Santarossa (Novartis) @author: Robert Schmidt (Ottawa Hospital Research Institute) +@author: Fotis Georgatos (Uni.Lu, NTUA) +@author: Kenneth Hoste (Ghent University) """ import os import tempfile +from vsc.utils import fancylogger + from easybuild.tools.run import run_cmd +_log = fancylogger.getLogger('tools.packaging') + def package_fpm(easyblock, modfile_path ): - rpmname = "HPCBIOS.20150211-%s-%s" % (easyblock.name, easyblock.version) + workdir = tempfile.mkdtemp() + _log.info("Will be writing RPM to %s" % workdir) + try: os.chdir(workdir) except OSError, err: @@ -64,5 +72,11 @@ def package_fpm(easyblock, modfile_path ): cmdlist.extend([ easyblock.installdir, ]) + cmdstr = " ".join(cmdlist) + _log.debug("The flattened cmdlist looks like" + cmdstr) + out = run_cmd(cmdstr, log_all=True, simple=True) + + _log.info("wrote rpm to %s" % (workdir) ) + + return workdir - (out, _) = run_cmd(cmdlist, log_all=True, simple=True) From d5e532996664620b0a7c41b237da8c588bab7da8 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Fri, 20 Mar 2015 16:35:37 -0400 Subject: [PATCH 12/51] some new options (packagepath and package_template) and the code to get it working. Is able to build RPMs, but still somewhat broken on teh dependency side --- easybuild/framework/easyblock.py | 13 +++++++++---- easybuild/tools/config.py | 16 +++++++++++++++- easybuild/tools/options.py | 8 ++++++-- easybuild/tools/packaging.py | 14 +++++++++----- 4 files changed, 39 insertions(+), 12 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index ba1bfe7faa..c7d6c072e8 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -60,7 +60,7 @@ from easybuild.tools.build_details import get_build_stats 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.config import install_path, log_path, read_only_installdir, source_paths, package_path 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 @@ -1447,15 +1447,20 @@ def package_step(self): """Prepare package software (e.g. into an RPM) with fpm.""" path_to_module_file = os.path.join(install_path('mod'), build_option('suffix_modules_path'), self.full_mod_name) + packagedir_dest = os.path.abspath(package_path()) packaging_tool = build_option('package_tool') if packaging_tool is not None: - package_dir = package_fpm(self, path_to_module_file) + packagedir_src = package_fpm(self, path_to_module_file) + + if not os.path.exists(packagedir_dest): + mkdir(packagedir_dest) + + for file in glob.glob(os.path.join(packagedir_src, "*.rpm")): + shutil.copy(file, packagedir_dest) else: _log.debug('Skipping package step') - shutil.copytree(package_dir, os.path.join(self.installdir, "package")) - def post_install_step(self): """ diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py index 3848bbab1c..87be90f575 100644 --- a/easybuild/tools/config.py +++ b/easybuild/tools/config.py @@ -58,6 +58,7 @@ DEFAULT_PATH_SUBDIRS = { 'buildpath': 'build', 'installpath': '', + 'packagepath': 'packages', 'repositorypath': 'ebfiles_repo', 'sourcepath': 'sources', 'subdir_modules': 'modules', @@ -65,7 +66,7 @@ } DEFAULT_PREFIX = os.path.join(os.path.expanduser('~'), ".local", "easybuild") DEFAULT_REPOSITORY = 'FileRepository' - +DEFAULT_PACKAGE_TEMPLATE = "easybuild-%(name)s-%(version)s" # utility function for obtaining default paths def mk_full_default_path(name, prefix=DEFAULT_PREFIX): @@ -182,6 +183,8 @@ class ConfigurationVariables(FrozenDictKnownKeys): 'prefix', 'buildpath', 'installpath', + 'packagepath', + 'package_template', 'sourcepath', 'repository', 'repositorypath', @@ -340,6 +343,17 @@ def get_repositorypath(): """ return ConfigurationVariables()['repositorypath'] +def package_path(): + """ + Return the path where built packages are copied to + """ + return ConfigurationVariables()['packagepath'] + +def package_template(): + """ + Returns the package template + """ + return ConfigurationVariables()['package_template'] def get_modules_tool(): """ diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index de5a51cfd5..5457fe8675 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -50,7 +50,7 @@ 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 +from easybuild.tools.config import DEFAULT_PATH_SUBDIRS, DEFAULT_PREFIX, DEFAULT_REPOSITORY, DEFAULT_PACKAGE_TEMPLATE 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 @@ -245,6 +245,10 @@ def config_options(self): 'choice', 'store', DEFAULT_MODULES_TOOL, sorted(avail_modules_tools().keys())), 'package-tool': ("Packaging tool to use", None, 'store_or_None', None), + 'package-template': ("A template string to name the package", + None, 'store', DEFAULT_PACKAGE_TEMPLATE), + 'packagepath': ("The destination path for the packages built by package-tool", + None, 'store', mk_full_default_path('packagepath')), 'prefix': (("Change prefix for buildpath, installpath, sourcepath and repositorypath " "(used prefix for defaults %s)" % DEFAULT_PREFIX), None, 'store', None), @@ -425,7 +429,7 @@ def _postprocess_config(self): if self.options.prefix is not None: # prefix applies to all paths, and repository has to be reinitialised to take new repositorypath in account # in the legacy-style configuration, repository is initialised in configuration file itself - for dest in ['installpath', 'buildpath', 'sourcepath', 'repository', 'repositorypath']: + for dest in ['installpath', 'buildpath', 'sourcepath', 'repository', 'repositorypath', 'packagepath']: if not self.options._action_taken.get(dest, False): if dest == 'repository': setattr(self.options, dest, DEFAULT_REPOSITORY) diff --git a/easybuild/tools/packaging.py b/easybuild/tools/packaging.py index 082cb868b1..f120672dea 100644 --- a/easybuild/tools/packaging.py +++ b/easybuild/tools/packaging.py @@ -38,6 +38,7 @@ from vsc.utils import fancylogger from easybuild.tools.run import run_cmd +from easybuild.tools.config import install_path, package_template _log = fancylogger.getLogger('tools.packaging') @@ -51,26 +52,29 @@ def package_fpm(easyblock, modfile_path ): except OSError, err: _log.error("Failed to chdir into workdir: %s : %s" % (workdir, err)) - pkgtemplate = "HPCBIOS.20150211-%(name)s-%(version)s" + pkgtemplate = package_template() + #"HPCBIOS.20150211-%(name)s-%(version)s" pkgname=pkgtemplate % { 'name' : easyblock.name, 'version' : easyblock.version, } - dependencies = [] - dependencies.extend([ "=".join([ dep['name'], dep['version'] ]) for dep in easyblock.cfg.dependencies() ]) - depstring = '--depends ' + ' --depends '.join(dependencies) + + depstring = "" + for dep in easyblock.cfg.dependencies(): + depstring += " --depends %s=%s" % ( dep['name'], dep['version']) + cmdlist=[ 'fpm', '--workdir', workdir, '--name', pkgname, '-t', 'rpm', # target '-s', 'dir', # source - '-C', easyblock.installdir, ] cmdlist.extend([ depstring ]) cmdlist.extend([ easyblock.installdir, + modfile_path ]) cmdstr = " ".join(cmdlist) _log.debug("The flattened cmdlist looks like" + cmdstr) From 1310f4387be6a9119107feac32783ec1d0a89f55 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Fri, 20 Mar 2015 20:46:43 -0400 Subject: [PATCH 13/51] adding provides to create the dependencies and reordering to put package after extensions --- easybuild/framework/easyblock.py | 2 +- easybuild/tools/packaging.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index c7d6c072e8..5593993112 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -1802,11 +1802,11 @@ def prepare_step_spec(initial): # part 3: post-iteration part steps_part3 = [ ('extensions', 'taking care of extensions', [lambda x: x.extensions_step()], False), + ('package', 'packaging', [lambda x: x.package_step()], True), ('postproc', 'postprocessing', [lambda x: x.post_install_step()], True), ('sanitycheck', 'sanity checking', [lambda x: x.sanity_check_step()], False), ('cleanup', 'cleaning up', [lambda x: x.cleanup_step()], False), ('module', 'creating module', [lambda x: x.make_module_step()], False), - ('package', 'packaging', [lambda x: x.package_step()], True), ] # full list of steps, included iterated steps diff --git a/easybuild/tools/packaging.py b/easybuild/tools/packaging.py index f120672dea..7868bacdc5 100644 --- a/easybuild/tools/packaging.py +++ b/easybuild/tools/packaging.py @@ -68,6 +68,7 @@ def package_fpm(easyblock, modfile_path ): 'fpm', '--workdir', workdir, '--name', pkgname, + '--provides', pkgname, '-t', 'rpm', # target '-s', 'dir', # source ] From a0d84ae8ae8c53e326350be1b4e28502414fbc7e Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Sat, 21 Mar 2015 21:00:28 -0400 Subject: [PATCH 14/51] adding a package prefix rather than template by option (and it is prefixed to the package name). some debugging on dependency issues --- easybuild/framework/easyblock.py | 4 ++-- easybuild/tools/config.py | 8 ++++---- easybuild/tools/options.py | 6 +++--- easybuild/tools/packaging.py | 16 ++++++++++------ 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 5593993112..890c98ac3f 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -1802,11 +1802,11 @@ def prepare_step_spec(initial): # part 3: post-iteration part steps_part3 = [ ('extensions', 'taking care of extensions', [lambda x: x.extensions_step()], False), - ('package', 'packaging', [lambda x: x.package_step()], True), ('postproc', 'postprocessing', [lambda x: x.post_install_step()], True), ('sanitycheck', 'sanity checking', [lambda x: x.sanity_check_step()], False), ('cleanup', 'cleaning up', [lambda x: x.cleanup_step()], False), ('module', 'creating module', [lambda x: x.make_module_step()], False), + ('package', 'packaging', [lambda x: x.package_step()], True), ] # full list of steps, included iterated steps @@ -1937,7 +1937,7 @@ def build_and_install_one(ecdict, orig_environ): except EasyBuildError, err: _log.warn("Unable to commit easyconfig to repository: %s", err) - success = True + success = True succ = "successfully" summary = "COMPLETED" diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py index 87be90f575..c1dad20186 100644 --- a/easybuild/tools/config.py +++ b/easybuild/tools/config.py @@ -66,7 +66,7 @@ } DEFAULT_PREFIX = os.path.join(os.path.expanduser('~'), ".local", "easybuild") DEFAULT_REPOSITORY = 'FileRepository' -DEFAULT_PACKAGE_TEMPLATE = "easybuild-%(name)s-%(version)s" +DEFAULT_PACKAGE_PREFIX = "easybuild" # utility function for obtaining default paths def mk_full_default_path(name, prefix=DEFAULT_PREFIX): @@ -184,7 +184,7 @@ class ConfigurationVariables(FrozenDictKnownKeys): 'buildpath', 'installpath', 'packagepath', - 'package_template', + 'package_prefix', 'sourcepath', 'repository', 'repositorypath', @@ -349,11 +349,11 @@ def package_path(): """ return ConfigurationVariables()['packagepath'] -def package_template(): +def package_prefix(): """ Returns the package template """ - return ConfigurationVariables()['package_template'] + return ConfigurationVariables()['package_prefix'] def get_modules_tool(): """ diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index 5457fe8675..b2ab6db75e 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -50,7 +50,7 @@ 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_PACKAGE_TEMPLATE +from easybuild.tools.config import DEFAULT_PATH_SUBDIRS, DEFAULT_PREFIX, DEFAULT_REPOSITORY, DEFAULT_PACKAGE_PREFIX 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 @@ -245,8 +245,8 @@ def config_options(self): 'choice', 'store', DEFAULT_MODULES_TOOL, sorted(avail_modules_tools().keys())), 'package-tool': ("Packaging tool to use", None, 'store_or_None', None), - 'package-template': ("A template string to name the package", - None, 'store', DEFAULT_PACKAGE_TEMPLATE), + 'package-prefix': ("A template string to name the package", + None, 'store', DEFAULT_PACKAGE_PREFIX), 'packagepath': ("The destination path for the packages built by package-tool", None, 'store', mk_full_default_path('packagepath')), 'prefix': (("Change prefix for buildpath, installpath, sourcepath and repositorypath " diff --git a/easybuild/tools/packaging.py b/easybuild/tools/packaging.py index 7868bacdc5..7ac0a0d229 100644 --- a/easybuild/tools/packaging.py +++ b/easybuild/tools/packaging.py @@ -35,10 +35,12 @@ import os import tempfile +import pprint + from vsc.utils import fancylogger from easybuild.tools.run import run_cmd -from easybuild.tools.config import install_path, package_template +from easybuild.tools.config import install_path, package_prefix _log = fancylogger.getLogger('tools.packaging') @@ -52,25 +54,27 @@ def package_fpm(easyblock, modfile_path ): except OSError, err: _log.error("Failed to chdir into workdir: %s : %s" % (workdir, err)) - pkgtemplate = package_template() + pkgprefix = package_prefix() + pkgtemplate = "%(prefix)s-%(name)s" #"HPCBIOS.20150211-%(name)s-%(version)s" pkgname=pkgtemplate % { + 'prefix' : pkgprefix, 'name' : easyblock.name, - 'version' : easyblock.version, } - + _log.debug("The dependencies to be added to the package are: " + pprint.pformat(easyblock.cfg.dependencies())) depstring = "" for dep in easyblock.cfg.dependencies(): - depstring += " --depends %s=%s" % ( dep['name'], dep['version']) + depstring += " --depends '%s-%s = %s-1'" % ( pkgprefix , dep['name'], dep['version']) cmdlist=[ 'fpm', '--workdir', workdir, '--name', pkgname, - '--provides', pkgname, + '--provides', "%s-%s" %(pkgprefix,easyblock.name), '-t', 'rpm', # target '-s', 'dir', # source + '--version', easyblock.version, ] cmdlist.extend([ depstring ]) cmdlist.extend([ From 6b41ec881e357cd20623d68d156813ef58e9021e Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Tue, 24 Mar 2015 21:21:25 -0400 Subject: [PATCH 15/51] moving the log message outside of the filter block for easier debugging --- 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 7aac218a73..191ee1c743 100644 --- a/easybuild/framework/easyconfig/easyconfig.py +++ b/easybuild/framework/easyconfig/easyconfig.py @@ -411,9 +411,9 @@ def dependencies(self): # if filter-deps option is provided we "clean" the list of dependencies for # each processed easyconfig to remove the unwanted dependencies + self.log.debug("Dependencies BEFORE filtering: %s" % deps) filter_deps = build_option('filter_deps') if filter_deps: - self.log.debug("Dependencies BEFORE filtering: %s" % deps) filtered_deps = [] for dep in deps: if dep['name'] not in filter_deps: From c98d7310b5b0a709586c7560b563d40b52fe11ff Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 25 Mar 2015 21:41:24 -0400 Subject: [PATCH 16/51] grabbing a full toolchain version --- easybuild/tools/packaging.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/easybuild/tools/packaging.py b/easybuild/tools/packaging.py index 7ac0a0d229..19100c8474 100644 --- a/easybuild/tools/packaging.py +++ b/easybuild/tools/packaging.py @@ -41,6 +41,8 @@ from easybuild.tools.run import run_cmd from easybuild.tools.config import install_path, package_prefix +from easybuild.tools.module_naming_scheme.utilities import det_full_ec_version +from easybuild.tools.toolchain import DUMMY_TOOLCHAIN_NAME _log = fancylogger.getLogger('tools.packaging') @@ -56,16 +58,31 @@ def package_fpm(easyblock, modfile_path ): pkgprefix = package_prefix() pkgtemplate = "%(prefix)s-%(name)s" + full_ec_version = det_full_ec_version(easyblock.cfg) #"HPCBIOS.20150211-%(name)s-%(version)s" pkgname=pkgtemplate % { 'prefix' : pkgprefix, 'name' : easyblock.name, } - _log.debug("The dependencies to be added to the package are: " + pprint.pformat(easyblock.cfg.dependencies())) + + # a lot of this logic should probably be put elsewhere, but make_module_dep is the only place I've seen that uses it + + deps = [] + if easyblock.toolchain.name != DUMMY_TOOLCHAIN_NAME: + short_mod_name = easyblock.toolchain.det_short_module_name() + _log.debug("The toolchain short module name is: %s" % short_mod_name) + toolchain_dict = easyblock.toolchain.as_dict() + toolchain_dict["short_mod_name"] = short_mod_name + deps.extend([toolchain_dict]) + + deps.extend(easyblock.cfg.dependencies()) + + _log.debug("The dependencies to be added to the package are: " + pprint.pformat([easyblock.toolchain.as_dict()]+easyblock.cfg.dependencies())) depstring = "" - for dep in easyblock.cfg.dependencies(): - depstring += " --depends '%s-%s = %s-1'" % ( pkgprefix , dep['name'], dep['version']) + for dep in deps: + short_mod_name = dep['short_mod_name'].partition('/') + depstring += " --depends '%s-%s = %s-1'" % ( pkgprefix , dep['name'], short_mod_name[2]) cmdlist=[ 'fpm', @@ -74,7 +91,7 @@ def package_fpm(easyblock, modfile_path ): '--provides', "%s-%s" %(pkgprefix,easyblock.name), '-t', 'rpm', # target '-s', 'dir', # source - '--version', easyblock.version, + '--version', full_ec_version, ] cmdlist.extend([ depstring ]) cmdlist.extend([ From 62ce4bf39236a8b121623d3756f1245e2b42cf11 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 26 Mar 2015 11:11:20 -0400 Subject: [PATCH 17/51] using short mod was bad --- easybuild/tools/packaging.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/easybuild/tools/packaging.py b/easybuild/tools/packaging.py index 19100c8474..05235eb5b9 100644 --- a/easybuild/tools/packaging.py +++ b/easybuild/tools/packaging.py @@ -70,10 +70,7 @@ def package_fpm(easyblock, modfile_path ): deps = [] if easyblock.toolchain.name != DUMMY_TOOLCHAIN_NAME: - short_mod_name = easyblock.toolchain.det_short_module_name() - _log.debug("The toolchain short module name is: %s" % short_mod_name) toolchain_dict = easyblock.toolchain.as_dict() - toolchain_dict["short_mod_name"] = short_mod_name deps.extend([toolchain_dict]) deps.extend(easyblock.cfg.dependencies()) @@ -81,8 +78,9 @@ def package_fpm(easyblock, modfile_path ): _log.debug("The dependencies to be added to the package are: " + pprint.pformat([easyblock.toolchain.as_dict()]+easyblock.cfg.dependencies())) depstring = "" for dep in deps: - short_mod_name = dep['short_mod_name'].partition('/') - depstring += " --depends '%s-%s = %s-1'" % ( pkgprefix , dep['name'], short_mod_name[2]) + full_dep_version = det_full_ec_version(dep) + #by default will only build iteration 1 packages, do we need to enhance this? + depstring += " --depends '%s-%s = %s-1'" % ( pkgprefix , dep['name'], full_dep_version) cmdlist=[ 'fpm', From ccf2f41d23fb0a2e3ca87e3f8599aa3572ae0521 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Fri, 27 Mar 2015 11:58:14 -0400 Subject: [PATCH 18/51] adding some comments --- easybuild/tools/packaging.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/easybuild/tools/packaging.py b/easybuild/tools/packaging.py index 05235eb5b9..420de20e65 100644 --- a/easybuild/tools/packaging.py +++ b/easybuild/tools/packaging.py @@ -47,6 +47,9 @@ _log = fancylogger.getLogger('tools.packaging') def package_fpm(easyblock, modfile_path ): + ''' + This function will build a package using fpm and return the directory where the packages are + ''' workdir = tempfile.mkdtemp() _log.info("Will be writing RPM to %s" % workdir) From 99073fb343558717ffa5dc4ddd5758ad3569430d Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Sat, 28 Mar 2015 22:38:35 -0400 Subject: [PATCH 19/51] adding support to specify deb or rpm as a package_type and handle a template rather than a prefix --- easybuild/framework/easyblock.py | 8 +++++--- easybuild/tools/config.py | 9 +++++---- easybuild/tools/options.py | 8 +++++--- easybuild/tools/packaging.py | 33 ++++++++++++++++++++------------ 4 files changed, 36 insertions(+), 22 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 890c98ac3f..57566dc667 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -1450,13 +1450,15 @@ def package_step(self): packagedir_dest = os.path.abspath(package_path()) packaging_tool = build_option('package_tool') - if packaging_tool is not None: - packagedir_src = package_fpm(self, path_to_module_file) + if packaging_tool == "fpm": + packaging_type = build_option('package_type') if build_option('package_type') else "rpm" + + packagedir_src = package_fpm(self, path_to_module_file, package_type=packaging_type) if not os.path.exists(packagedir_dest): mkdir(packagedir_dest) - for file in glob.glob(os.path.join(packagedir_src, "*.rpm")): + for file in glob.glob(os.path.join(packagedir_src, "*.%s" % packaging_type)): shutil.copy(file, packagedir_dest) else: _log.debug('Skipping package step') diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py index c1dad20186..89b104f2cc 100644 --- a/easybuild/tools/config.py +++ b/easybuild/tools/config.py @@ -66,7 +66,7 @@ } DEFAULT_PREFIX = os.path.join(os.path.expanduser('~'), ".local", "easybuild") DEFAULT_REPOSITORY = 'FileRepository' -DEFAULT_PACKAGE_PREFIX = "easybuild" +DEFAULT_PACKAGE_TEMPLATE = "eb-%(toolchain)s-%(name)s" # utility function for obtaining default paths def mk_full_default_path(name, prefix=DEFAULT_PREFIX): @@ -93,6 +93,7 @@ def mk_full_default_path(name, prefix=DEFAULT_PREFIX): 'only_blocks', 'optarch', 'package_tool', + 'package_type', 'regtest_output_dir', 'skip', 'stop', @@ -184,7 +185,7 @@ class ConfigurationVariables(FrozenDictKnownKeys): 'buildpath', 'installpath', 'packagepath', - 'package_prefix', + 'package_template', 'sourcepath', 'repository', 'repositorypath', @@ -349,11 +350,11 @@ def package_path(): """ return ConfigurationVariables()['packagepath'] -def package_prefix(): +def package_template(): """ Returns the package template """ - return ConfigurationVariables()['package_prefix'] + return ConfigurationVariables()['package_template'] def get_modules_tool(): """ diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index b2ab6db75e..0f1cf09d45 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -50,7 +50,7 @@ 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_PACKAGE_PREFIX +from easybuild.tools.config import DEFAULT_PATH_SUBDIRS, DEFAULT_PREFIX, DEFAULT_REPOSITORY, DEFAULT_PACKAGE_TEMPLATE 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 @@ -245,8 +245,10 @@ def config_options(self): 'choice', 'store', DEFAULT_MODULES_TOOL, sorted(avail_modules_tools().keys())), 'package-tool': ("Packaging tool to use", None, 'store_or_None', None), - 'package-prefix': ("A template string to name the package", - None, 'store', DEFAULT_PACKAGE_PREFIX), + 'package-type': ("Packaging type to output to", + None, 'store_or_None', None), + 'package-template': ("A template string to name the package", + None, 'store', DEFAULT_PACKAGE_TEMPLATE), 'packagepath': ("The destination path for the packages built by package-tool", None, 'store', mk_full_default_path('packagepath')), 'prefix': (("Change prefix for buildpath, installpath, sourcepath and repositorypath " diff --git a/easybuild/tools/packaging.py b/easybuild/tools/packaging.py index 420de20e65..0ad3aed8d2 100644 --- a/easybuild/tools/packaging.py +++ b/easybuild/tools/packaging.py @@ -40,13 +40,13 @@ from vsc.utils import fancylogger from easybuild.tools.run import run_cmd -from easybuild.tools.config import install_path, package_prefix +from easybuild.tools.config import install_path, package_template from easybuild.tools.module_naming_scheme.utilities import det_full_ec_version from easybuild.tools.toolchain import DUMMY_TOOLCHAIN_NAME _log = fancylogger.getLogger('tools.packaging') -def package_fpm(easyblock, modfile_path ): +def package_fpm(easyblock, modfile_path, package_type="rpm" ): ''' This function will build a package using fpm and return the directory where the packages are ''' @@ -59,18 +59,21 @@ def package_fpm(easyblock, modfile_path ): except OSError, err: _log.error("Failed to chdir into workdir: %s : %s" % (workdir, err)) - pkgprefix = package_prefix() - pkgtemplate = "%(prefix)s-%(name)s" + # default package_template is "eb-%(toolchain)s-%(name)s" + pkgtemplate = package_template() full_ec_version = det_full_ec_version(easyblock.cfg) - #"HPCBIOS.20150211-%(name)s-%(version)s" + _log.debug("I got a package template that looks like: %s " % pkgtemplate ) - pkgname=pkgtemplate % { - 'prefix' : pkgprefix, + if easyblock.toolchain.name == DUMMY_TOOLCHAIN_NAME: + toolchain_name = easyblock.version + else: + toolchain_name = "%s-%s" % (easyblock.toolchain.name, easyblock.toolchain.version) + + pkgname = pkgtemplate % { + 'toolchain' : toolchain_name, 'name' : easyblock.name, } - # a lot of this logic should probably be put elsewhere, but make_module_dep is the only place I've seen that uses it - deps = [] if easyblock.toolchain.name != DUMMY_TOOLCHAIN_NAME: toolchain_dict = easyblock.toolchain.as_dict() @@ -83,14 +86,20 @@ def package_fpm(easyblock, modfile_path ): for dep in deps: full_dep_version = det_full_ec_version(dep) #by default will only build iteration 1 packages, do we need to enhance this? - depstring += " --depends '%s-%s = %s-1'" % ( pkgprefix , dep['name'], full_dep_version) + _log.debug("The dep added looks like %s " % dep) + dep_pkgname = pkgtemplate % { + 'name': dep['name'], + 'toolchain': "%s-%s" % (dep['toolchain']['name'], dep['toolchain']['version']), + + } + depstring += " --depends '%s = %s-1'" % ( dep_pkgname, full_dep_version) cmdlist=[ 'fpm', '--workdir', workdir, '--name', pkgname, - '--provides', "%s-%s" %(pkgprefix,easyblock.name), - '-t', 'rpm', # target + '--provides', pkgname, + '-t', package_type, # target '-s', 'dir', # source '--version', full_ec_version, ] From 1511e213d9187fa4f7c366f11d5e099e2dfcf378 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 2 Apr 2015 20:34:54 -0400 Subject: [PATCH 20/51] Adding a better default --- easybuild/tools/config.py | 2 +- easybuild/tools/packaging.py | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py index 89b104f2cc..4eeb06a66d 100644 --- a/easybuild/tools/config.py +++ b/easybuild/tools/config.py @@ -66,7 +66,7 @@ } DEFAULT_PREFIX = os.path.join(os.path.expanduser('~'), ".local", "easybuild") DEFAULT_REPOSITORY = 'FileRepository' -DEFAULT_PACKAGE_TEMPLATE = "eb-%(toolchain)s-%(name)s" +DEFAULT_PACKAGE_TEMPLATE = "eb-%(name)s-%(version)s-%(toolchain)s" # utility function for obtaining default paths def mk_full_default_path(name, prefix=DEFAULT_PREFIX): diff --git a/easybuild/tools/packaging.py b/easybuild/tools/packaging.py index 0ad3aed8d2..cc729e709c 100644 --- a/easybuild/tools/packaging.py +++ b/easybuild/tools/packaging.py @@ -64,13 +64,11 @@ def package_fpm(easyblock, modfile_path, package_type="rpm" ): full_ec_version = det_full_ec_version(easyblock.cfg) _log.debug("I got a package template that looks like: %s " % pkgtemplate ) - if easyblock.toolchain.name == DUMMY_TOOLCHAIN_NAME: - toolchain_name = easyblock.version - else: - toolchain_name = "%s-%s" % (easyblock.toolchain.name, easyblock.toolchain.version) + toolchain_name = "%s-%s" % (easyblock.toolchain.name, easyblock.toolchain.version) pkgname = pkgtemplate % { 'toolchain' : toolchain_name, + 'version': '-'.join([x for x in [easyblock.cfg.get('versionprefix', ''), easyblock.cfg['version'], easyblock.cfg['versionsuffix']] if x]), 'name' : easyblock.name, } @@ -89,10 +87,10 @@ def package_fpm(easyblock, modfile_path, package_type="rpm" ): _log.debug("The dep added looks like %s " % dep) dep_pkgname = pkgtemplate % { 'name': dep['name'], + 'version': '-'.join([x for x in [dep.get('versionprefix',''), dep['version'], dep['versionsuffix']] if x]), 'toolchain': "%s-%s" % (dep['toolchain']['name'], dep['toolchain']['version']), - } - depstring += " --depends '%s = %s-1'" % ( dep_pkgname, full_dep_version) + depstring += " --depends '%s'" % ( dep_pkgname) cmdlist=[ 'fpm', @@ -101,7 +99,7 @@ def package_fpm(easyblock, modfile_path, package_type="rpm" ): '--provides', pkgname, '-t', package_type, # target '-s', 'dir', # source - '--version', full_ec_version, + '--version', "eb", ] cmdlist.extend([ depstring ]) cmdlist.extend([ From de704ff18a7861658fcea8d2141eaf4f33e3319f Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 4 Jun 2015 20:57:17 -0400 Subject: [PATCH 21/51] Adding lstrip to stop double -- in versions of packages and deps --- easybuild/tools/packaging.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/easybuild/tools/packaging.py b/easybuild/tools/packaging.py index cc729e709c..77e9f0959e 100644 --- a/easybuild/tools/packaging.py +++ b/easybuild/tools/packaging.py @@ -68,7 +68,7 @@ def package_fpm(easyblock, modfile_path, package_type="rpm" ): pkgname = pkgtemplate % { 'toolchain' : toolchain_name, - 'version': '-'.join([x for x in [easyblock.cfg.get('versionprefix', ''), easyblock.cfg['version'], easyblock.cfg['versionsuffix']] if x]), + 'version': '-'.join([x for x in [easyblock.cfg.get('versionprefix', ''), easyblock.cfg['version'], easyblock.cfg['versionsuffix'].lstrip('-')] if x]), 'name' : easyblock.name, } @@ -87,7 +87,7 @@ def package_fpm(easyblock, modfile_path, package_type="rpm" ): _log.debug("The dep added looks like %s " % dep) dep_pkgname = pkgtemplate % { 'name': dep['name'], - 'version': '-'.join([x for x in [dep.get('versionprefix',''), dep['version'], dep['versionsuffix']] if x]), + 'version': '-'.join([x for x in [dep.get('versionprefix',''), dep['version'], dep['versionsuffix'].lstrip('-')] if x]), 'toolchain': "%s-%s" % (dep['toolchain']['name'], dep['toolchain']['version']), } depstring += " --depends '%s'" % ( dep_pkgname) From 7671f2be3524262d26c2cebed92fb1e08b436406 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 11 Jun 2015 07:50:42 -0400 Subject: [PATCH 22/51] readding a couple of options that got dropped in a develop merge --- easybuild/tools/config.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py index fe9d7c786e..1b1db96dfa 100644 --- a/easybuild/tools/config.py +++ b/easybuild/tools/config.py @@ -191,6 +191,8 @@ class ConfigurationVariables(FrozenDictKnownKeys): 'buildpath', 'config', 'installpath', + 'installpath_modules', + 'installpath_software', 'logfile_format', 'moduleclasses', 'module_naming_scheme', From e4d5675befb3383a7c481f486a9eab2ff665c60b Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 11 Jun 2015 08:03:37 -0400 Subject: [PATCH 23/51] removing deprecated log.errors --- easybuild/tools/packaging.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/easybuild/tools/packaging.py b/easybuild/tools/packaging.py index 77e9f0959e..efe9d507b6 100644 --- a/easybuild/tools/packaging.py +++ b/easybuild/tools/packaging.py @@ -43,6 +43,7 @@ from easybuild.tools.config import install_path, package_template from easybuild.tools.module_naming_scheme.utilities import det_full_ec_version from easybuild.tools.toolchain import DUMMY_TOOLCHAIN_NAME +from easybuild.tools.build_log import EasyBuildError _log = fancylogger.getLogger('tools.packaging') @@ -57,7 +58,7 @@ def package_fpm(easyblock, modfile_path, package_type="rpm" ): try: os.chdir(workdir) except OSError, err: - _log.error("Failed to chdir into workdir: %s : %s" % (workdir, err)) + raise EasybBuildError("Failed to chdir into workdir: %s : %s", workdir, err) # default package_template is "eb-%(toolchain)s-%(name)s" pkgtemplate = package_template() From b815d6643f7a7b5da9c61f55c54d34ac37127a46 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 11 Jun 2015 15:40:00 -0400 Subject: [PATCH 24/51] adding some extra checks for experimental, package existance and options --- easybuild/framework/easyblock.py | 11 +++++++++-- easybuild/tools/options.py | 11 ++++++++++- easybuild/tools/packaging.py | 16 ++++++++++++++++ 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index eb92b13af7..b0fad7c021 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -1499,6 +1499,8 @@ def package_step(self): packagedir_dest = os.path.abspath(package_path()) packaging_tool = build_option('package_tool') + opt_force = build_option('force') + if packaging_tool == "fpm": packaging_type = build_option('package_type') if build_option('package_type') else "rpm" @@ -1507,8 +1509,13 @@ def package_step(self): if not os.path.exists(packagedir_dest): mkdir(packagedir_dest) - for file in glob.glob(os.path.join(packagedir_src, "*.%s" % packaging_type)): - shutil.copy(file, packagedir_dest) + for src_file in glob.glob(os.path.join(packagedir_src, "*.%s" % packaging_type)): + src_filename = os.path.basename(src_file) + dest_file = os.path.join(packagedir_dest, src_filename) + if os.path.exists(dest_file) and not opt_force: + raise EasyBuildError("Unable to copy file, dest already exists. Look in src for packages dest: %s src: %s ", dest_file, src_file) + else: + shutil.copy(src_file, packagedir_dest) else: _log.debug('Skipping package step') diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index ef8757e7a1..6e1e70bd1d 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -48,7 +48,7 @@ from easybuild.framework.easyconfig.templates import template_documentation 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 import build_log, config, run, packaging # @UnusedImport make sure config is always initialized! from easybuild.tools.build_log import EasyBuildError, raise_easybuilderror from easybuild.tools.config import DEFAULT_LOGFILE_FORMAT, DEFAULT_MNS, DEFAULT_MODULE_SYNTAX, DEFAULT_MODULES_TOOL from easybuild.tools.config import DEFAULT_MODULECLASSES, DEFAULT_PACKAGE_TEMPLATE, DEFAULT_PATH_SUBDIRS @@ -459,6 +459,15 @@ def postprocess(self): self._postprocess_config() + #Check experimental option dependencies (for now packaging) + #print "Got config_options: %s" % packaging.config_options + package_options = [ getattr(self.options, x) for x in packaging.config_options if getattr(self.options, x) ] + if any( package_options ): + packaging.option_postprocess() + else: + self.log.debug("Didn't find any packaging options") + + def _postprocess_external_modules_metadata(self): """Parse file(s) specifying metadata for external modules.""" # leave external_modules_metadata untouched if no files are provided diff --git a/easybuild/tools/packaging.py b/easybuild/tools/packaging.py index efe9d507b6..811b1711d7 100644 --- a/easybuild/tools/packaging.py +++ b/easybuild/tools/packaging.py @@ -44,8 +44,10 @@ from easybuild.tools.module_naming_scheme.utilities import det_full_ec_version from easybuild.tools.toolchain import DUMMY_TOOLCHAIN_NAME from easybuild.tools.build_log import EasyBuildError +from easybuild.tools.filetools import which _log = fancylogger.getLogger('tools.packaging') +config_options = [ 'package_tool', 'package_type' ] def package_fpm(easyblock, modfile_path, package_type="rpm" ): ''' @@ -115,3 +117,17 @@ def package_fpm(easyblock, modfile_path, package_type="rpm" ): return workdir + +def option_postprocess(): + ''' + Called from easybuild.tools.options.postprocess to check that experimental is triggered and fpm is available + ''' + + _log.experimental("Using the packaging module, This is experimental") + fpm_path = which('fpm') + rpmbuild_path = which('rpmbuild') + if fpm_path and rpmbuild_path: + _log.info("fpm found at: %s" % fpm_path) + else: + raise EasyBuildError("Need both fpm and rpmbuild. Found fpm: %s rpmbuild: %s", fpm_path, rpmbuild_path) + From 099d73098b86f3c81292b5c6ae60c195690d63d2 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Tue, 16 Jun 2015 20:50:08 -0400 Subject: [PATCH 25/51] just some docs --- easybuild/tools/packaging.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/easybuild/tools/packaging.py b/easybuild/tools/packaging.py index 811b1711d7..68ac779b31 100644 --- a/easybuild/tools/packaging.py +++ b/easybuild/tools/packaging.py @@ -47,6 +47,8 @@ from easybuild.tools.filetools import which _log = fancylogger.getLogger('tools.packaging') +# This is an abbreviated list of the package options, eventually it might make sense to set them +# all in the "plugin" rather than in tools.options config_options = [ 'package_tool', 'package_type' ] def package_fpm(easyblock, modfile_path, package_type="rpm" ): From 0a4d82c3b1e3ad04fd22cdd01acc49362a68a71e Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 17 Jun 2015 21:16:10 +0200 Subject: [PATCH 26/51] don't skip package_step, add --package option, group package-related options together --- easybuild/framework/easyblock.py | 15 +++++++++------ easybuild/tools/config.py | 9 ++------- easybuild/tools/options.py | 20 ++++++++++++++------ easybuild/tools/packaging.py | 6 +++--- 4 files changed, 28 insertions(+), 22 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index b0fad7c021..b576b8fd24 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -1501,14 +1501,17 @@ def package_step(self): packaging_tool = build_option('package_tool') opt_force = build_option('force') - if packaging_tool == "fpm": + if not build_option('package'): + _log.info("Skipping package step (not enabled)") + + elif packaging_tool == "fpm": packaging_type = build_option('package_type') if build_option('package_type') else "rpm" - + packagedir_src = package_fpm(self, path_to_module_file, package_type=packaging_type) - + if not os.path.exists(packagedir_dest): mkdir(packagedir_dest) - + for src_file in glob.glob(os.path.join(packagedir_src, "*.%s" % packaging_type)): src_filename = os.path.basename(src_file) dest_file = os.path.join(packagedir_dest, src_filename) @@ -1517,7 +1520,7 @@ def package_step(self): else: shutil.copy(src_file, packagedir_dest) else: - _log.debug('Skipping package step') + raise EasyBuildError("Unknown packaging tool specified: %s", packaging_tool) def post_install_step(self): @@ -1894,7 +1897,7 @@ def prepare_step_spec(initial): (SANITYCHECK_STEP, 'sanity checking', [lambda x: x.sanity_check_step()], False), (CLEANUP_STEP, 'cleaning up', [lambda x: x.cleanup_step()], False), (MODULE_STEP, 'creating module', [lambda x: x.make_module_step()], False), - (PACKAGE_STEP, 'packaging', [lambda x: x.package_step()], True), + (PACKAGE_STEP, 'packaging', [lambda x: x.package_step()], False), ] # full list of steps, included iterated steps diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py index 1b1db96dfa..34589cc955 100644 --- a/easybuild/tools/config.py +++ b/easybuild/tools/config.py @@ -96,6 +96,7 @@ def mk_full_default_path(name, prefix=DEFAULT_PREFIX): 'modules_footer', 'only_blocks', 'optarch', + 'package_template', 'package_tool', 'package_type', 'regtest_output_dir', @@ -113,6 +114,7 @@ def mk_full_default_path(name, prefix=DEFAULT_PREFIX): 'force', 'hidden', 'module_only', + 'package', 'robot', 'sequential', 'set_gid_bit', @@ -199,7 +201,6 @@ class ConfigurationVariables(FrozenDictKnownKeys): 'module_syntax', 'modules_tool', 'packagepath', - 'package_template', 'prefix', 'repository', 'repositorypath', @@ -373,12 +374,6 @@ def package_path(): """ return ConfigurationVariables()['packagepath'] -def package_template(): - """ - Returns the package template - """ - return ConfigurationVariables()['package_template'] - def get_modules_tool(): """ Return modules tool (EnvironmentModulesC, Lmod, ...) diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index 6e1e70bd1d..d9afdb4273 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -261,12 +261,6 @@ def config_options(self): None, 'store_or_None', None, {'metavar': "PATH"}), 'modules-tool': ("Modules tool to use", 'choice', 'store', DEFAULT_MODULES_TOOL, sorted(avail_modules_tools().keys())), - 'package-tool': ("Packaging tool to use", - None, 'store_or_None', None), - 'package-type': ("Packaging type to output to", - None, 'store_or_None', None), - 'package-template': ("A template string to name the package", - None, 'store', DEFAULT_PACKAGE_TEMPLATE), 'packagepath': ("The destination path for the packages built by package-tool", None, 'store', mk_full_default_path('packagepath')), 'prefix': (("Change prefix for buildpath, installpath, sourcepath and repositorypath " @@ -355,6 +349,20 @@ def regtest_options(self): self.log.debug("regtest_options: descr %s opts %s" % (descr, opts)) self.add_group_parser(opts, descr) + def package_options(self): + # package-related options + descr = ("Package options", "Control packaging performed by EasyBuild.") + + opts = OrderedDict({ + 'package': ("Enabling packaging", None, 'store_true', False), + 'package-template': ("A template string to name the package", None, 'store', DEFAULT_PACKAGE_TEMPLATE), + 'package-tool': ("Packaging tool to use", None, 'store_or_None', None), + 'package-type': ("Packaging type to output to", None, 'store_or_None', None), + }) + + self.log.debug("package_options: descr %s opts %s" % (descr, opts)) + self.add_group_parser(opts, descr) + def easyconfig_options(self): # easyconfig options (to be passed to easyconfig instance) descr = ("Options for Easyconfigs", "Options to be passed to all Easyconfig.") diff --git a/easybuild/tools/packaging.py b/easybuild/tools/packaging.py index 811b1711d7..0b434f742e 100644 --- a/easybuild/tools/packaging.py +++ b/easybuild/tools/packaging.py @@ -40,7 +40,7 @@ from vsc.utils import fancylogger from easybuild.tools.run import run_cmd -from easybuild.tools.config import install_path, package_template +from easybuild.tools.config import build_option from easybuild.tools.module_naming_scheme.utilities import det_full_ec_version from easybuild.tools.toolchain import DUMMY_TOOLCHAIN_NAME from easybuild.tools.build_log import EasyBuildError @@ -60,10 +60,10 @@ def package_fpm(easyblock, modfile_path, package_type="rpm" ): try: os.chdir(workdir) except OSError, err: - raise EasybBuildError("Failed to chdir into workdir: %s : %s", workdir, err) + raise EasyBuildError("Failed to chdir into workdir: %s : %s", workdir, err) # default package_template is "eb-%(toolchain)s-%(name)s" - pkgtemplate = package_template() + pkgtemplate = build_option('package_template') full_ec_version = det_full_ec_version(easyblock.cfg) _log.debug("I got a package template that looks like: %s " % pkgtemplate ) From 5e9d0f44c7ee45e366714c4c7ce3a1eab8f641ba Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 17 Jun 2015 22:02:51 -0400 Subject: [PATCH 27/51] creating a packaging_naming_scheme structure --- easybuild/framework/easyblock.py | 2 +- easybuild/tools/config.py | 1 - easybuild/tools/options.py | 7 ++- easybuild/tools/package/__init__.py | 8 ++++ easybuild/tools/package/activepns.py | 46 +++++++++++++++++++ .../tools/package/packaging_naming_scheme.py | 24 ++++++++++ .../packaging_naming_scheme/__init__.py | 0 .../packaging_naming_scheme/easybuild_pns.py | 42 +++++++++++++++++ .../package/packaging_naming_scheme/pns.py | 25 ++++++++++ .../{packaging.py => package/utilities.py} | 29 ++++-------- 10 files changed, 158 insertions(+), 26 deletions(-) create mode 100644 easybuild/tools/package/__init__.py create mode 100644 easybuild/tools/package/activepns.py create mode 100644 easybuild/tools/package/packaging_naming_scheme.py create mode 100644 easybuild/tools/package/packaging_naming_scheme/__init__.py create mode 100644 easybuild/tools/package/packaging_naming_scheme/easybuild_pns.py create mode 100644 easybuild/tools/package/packaging_naming_scheme/pns.py rename easybuild/tools/{packaging.py => package/utilities.py} (78%) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index b0fad7c021..25af96a60a 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -56,7 +56,7 @@ from easybuild.framework.easyconfig.parser import fetch_parameters_from_easyconfig from easybuild.framework.easyconfig.tools import get_paths_for from easybuild.framework.easyconfig.templates import TEMPLATE_NAMES_EASYBLOCK_RUN_STEP -from easybuild.tools.packaging import package_fpm +from easybuild.tools.package.utilities import package_fpm from easybuild.tools.build_details import get_build_stats 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 diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py index 1b1db96dfa..2790039857 100644 --- a/easybuild/tools/config.py +++ b/easybuild/tools/config.py @@ -67,7 +67,6 @@ } DEFAULT_PREFIX = os.path.join(os.path.expanduser('~'), ".local", "easybuild") DEFAULT_REPOSITORY = 'FileRepository' -DEFAULT_PACKAGE_TEMPLATE = "eb-%(name)s-%(version)s-%(toolchain)s" DEFAULT_STRICT = run.WARN # utility function for obtaining default paths diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index 6e1e70bd1d..f575f7f4b2 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -48,10 +48,10 @@ from easybuild.framework.easyconfig.templates import template_documentation from easybuild.framework.easyconfig.tools import get_paths_for from easybuild.framework.extension import Extension -from easybuild.tools import build_log, config, run, packaging # @UnusedImport make sure config is always initialized! +from easybuild.tools import build_log, config, run # @UnusedImport make sure config is always initialized! from easybuild.tools.build_log import EasyBuildError, raise_easybuilderror from easybuild.tools.config import DEFAULT_LOGFILE_FORMAT, DEFAULT_MNS, DEFAULT_MODULE_SYNTAX, DEFAULT_MODULES_TOOL -from easybuild.tools.config import DEFAULT_MODULECLASSES, DEFAULT_PACKAGE_TEMPLATE, DEFAULT_PATH_SUBDIRS +from easybuild.tools.config import DEFAULT_MODULECLASSES, DEFAULT_PATH_SUBDIRS from easybuild.tools.config import DEFAULT_PREFIX, DEFAULT_REPOSITORY from easybuild.tools.config import DEFAULT_STRICT, get_pretend_installpath, mk_full_default_path from easybuild.tools.configobj import ConfigObj, ConfigObjError @@ -63,6 +63,7 @@ from easybuild.tools.module_naming_scheme.utilities import avail_module_naming_schemes from easybuild.tools.modules import Lmod from easybuild.tools.ordereddict import OrderedDict +import easybuild.tools.package.utilities as packaging from easybuild.tools.toolchain.utilities import search_toolchain from easybuild.tools.repository.repository import avail_repositories from easybuild.tools.version import this_is_easybuild @@ -265,8 +266,6 @@ def config_options(self): None, 'store_or_None', None), 'package-type': ("Packaging type to output to", None, 'store_or_None', None), - 'package-template': ("A template string to name the package", - None, 'store', DEFAULT_PACKAGE_TEMPLATE), 'packagepath': ("The destination path for the packages built by package-tool", None, 'store', mk_full_default_path('packagepath')), 'prefix': (("Change prefix for buildpath, installpath, sourcepath and repositorypath " diff --git a/easybuild/tools/package/__init__.py b/easybuild/tools/package/__init__.py new file mode 100644 index 0000000000..de858db457 --- /dev/null +++ b/easybuild/tools/package/__init__.py @@ -0,0 +1,8 @@ +""" +The packaging module, will contain code for packaging and naming-schemes that can be +overriden to cover site customizations + +""" + + + diff --git a/easybuild/tools/package/activepns.py b/easybuild/tools/package/activepns.py new file mode 100644 index 0000000000..d9184eec70 --- /dev/null +++ b/easybuild/tools/package/activepns.py @@ -0,0 +1,46 @@ + + +from vsc.utils import fancylogger +from vsc.utils.patterns import Singleton +from easybuild.tools.config import build_option +from easybuild.tools.utilities import import_available_modules +from easybuild.tools.build_log import EasyBuildError, print_error, print_msg + +def avail_package_naming_scheme(): + ''' + Returns the list of valed naming schemes that are in the easybuild.package.package_naming_scheme namespace + ''' + pns = import_available_modules('easybuild.tools.package.packaging_naming_scheme') + + return pns + +class ActivePNS(object): + """ + The wrapper class for Package Naming Schmese, follows the model of Module Naming Schemes, mostly + """ + + __metaclass__ = Singleton + + def __init__(self, *args, **kwargs): + """Initialize logger and find available PNSes to load""" + self.log = fancylogger.getLogger(self.__class__.__name__, fname=False) + + avail_pns = avail_package_naming_scheme() + sel_pns = build_option("package-naming-scheme") + if sel_pns in avail_pns: + self.pns = avail_pns[sel_pns]() + else: + raise EasyBuildError("Selected package naming scheme %s could not be found in %s", + sel_pns, avail_pns.keys()) + + def name(self): + name = self.pns.name() + return name + + def version(self): + version = self.pns.version() + return version + + def release(self): + release = self.pns.release() + return release diff --git a/easybuild/tools/package/packaging_naming_scheme.py b/easybuild/tools/package/packaging_naming_scheme.py new file mode 100644 index 0000000000..bfe620d2ea --- /dev/null +++ b/easybuild/tools/package/packaging_naming_scheme.py @@ -0,0 +1,24 @@ + +from vsc.utils import fancylogger + +options = [ "package-naming-name-template", "package-naming-version-template", "package-naming-toolchain-template" ] + +class PackagingNamingScheme(object): + """Abstract class for package naming scheme""" + + + def __init__(self, *args, **kwargs): + """initialize logger.""" + self.log = fancylogger.getLogger(self.__class__.__name__, fname=False) + + def name: + """Return name of the package, by default would include name, version, toolchain""" + + + def name_version: + + def version_version: + + def release: + + diff --git a/easybuild/tools/package/packaging_naming_scheme/__init__.py b/easybuild/tools/package/packaging_naming_scheme/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/easybuild/tools/package/packaging_naming_scheme/easybuild_pns.py b/easybuild/tools/package/packaging_naming_scheme/easybuild_pns.py new file mode 100644 index 0000000000..e0ab409e74 --- /dev/null +++ b/easybuild/tools/package/packaging_naming_scheme/easybuild_pns.py @@ -0,0 +1,42 @@ + + + +""" +Default implementation of the EasyBuild packaging naming scheme + +@author: Rob Schmidt (Ottawa Hospital Research Institute) +@author: Kenneth Hoste (Ghent University) +""" + +from easybuild.tools.package.packaging_naming_scheme.pns import PackagingNamingScheme + + +class EasyBuildPNS(PackagingNamingScheme): + """Class implmenting the default EasyBuild packaging naming scheme.""" + + REQUIRED_KEYS = ['name', 'version', 'versionsuffix', 'toolchain'] + + def name(self, ec): + name_template = "eb-%(name)s-%(version)s-%(toolchain)s" + pkg_name = name_template % { + 'toolchain' : self.toolchain(ec), + 'version': '-'.join([x for x in [ec.get('versionprefix', ''), ec['version'], ec['versionsuffix'].lstrip('-')] if x]), + 'name' : eb.name, + } + + def _toolchain(self, eb): + toolchain_template = "%(toolchain_name)s-%(toolchain_version)s" + pkg_toolchain = toolchain_template % { + 'toolchain_name': eb.toolchain.name, + 'toolchain_version': eb.toolchain.version, + } + + + def version(self, eb): + return eb.cfg['version'] + + + + def release(self): + return 1 + diff --git a/easybuild/tools/package/packaging_naming_scheme/pns.py b/easybuild/tools/package/packaging_naming_scheme/pns.py new file mode 100644 index 0000000000..f4427465ca --- /dev/null +++ b/easybuild/tools/package/packaging_naming_scheme/pns.py @@ -0,0 +1,25 @@ + +from vsc.utils import fancylogger + +options = [ "package-naming-name-template", "package-naming-version-template", "package-naming-toolchain-template" ] + +class PackagingNamingScheme(object): + """Abstract class for package naming scheme""" + + + def __init__(self, *args, **kwargs): + """initialize logger.""" + self.log = fancylogger.getLogger(self.__class__.__name__, fname=False) + + def name(self): + """Return name of the package, by default would include name, version, toolchain""" + + + def version(self): + """The version in the version part of the package""" + + def release(self): + """Just the release""" + return 1 + + diff --git a/easybuild/tools/packaging.py b/easybuild/tools/package/utilities.py similarity index 78% rename from easybuild/tools/packaging.py rename to easybuild/tools/package/utilities.py index 68ac779b31..d346f7932d 100644 --- a/easybuild/tools/packaging.py +++ b/easybuild/tools/package/utilities.py @@ -45,6 +45,7 @@ from easybuild.tools.toolchain import DUMMY_TOOLCHAIN_NAME from easybuild.tools.build_log import EasyBuildError from easybuild.tools.filetools import which +from easybuild.tools.package.activepns import ActivePNS _log = fancylogger.getLogger('tools.packaging') # This is an abbreviated list of the package options, eventually it might make sense to set them @@ -64,19 +65,12 @@ def package_fpm(easyblock, modfile_path, package_type="rpm" ): except OSError, err: raise EasybBuildError("Failed to chdir into workdir: %s : %s", workdir, err) - # default package_template is "eb-%(toolchain)s-%(name)s" - pkgtemplate = package_template() - full_ec_version = det_full_ec_version(easyblock.cfg) - _log.debug("I got a package template that looks like: %s " % pkgtemplate ) + package_naming_scheme = ActivePNS() - toolchain_name = "%s-%s" % (easyblock.toolchain.name, easyblock.toolchain.version) - - pkgname = pkgtemplate % { - 'toolchain' : toolchain_name, - 'version': '-'.join([x for x in [easyblock.cfg.get('versionprefix', ''), easyblock.cfg['version'], easyblock.cfg['versionsuffix'].lstrip('-')] if x]), - 'name' : easyblock.name, - } - + pkgname = package_naming_scheme.name(easyblock.cfg) + pkgver = package_naming_scheme.version(easyblock.cfg) + pkgrel = package_naming_scheme.release(easyblock.cfg) + deps = [] if easyblock.toolchain.name != DUMMY_TOOLCHAIN_NAME: toolchain_dict = easyblock.toolchain.as_dict() @@ -87,14 +81,8 @@ def package_fpm(easyblock, modfile_path, package_type="rpm" ): _log.debug("The dependencies to be added to the package are: " + pprint.pformat([easyblock.toolchain.as_dict()]+easyblock.cfg.dependencies())) depstring = "" for dep in deps: - full_dep_version = det_full_ec_version(dep) - #by default will only build iteration 1 packages, do we need to enhance this? _log.debug("The dep added looks like %s " % dep) - dep_pkgname = pkgtemplate % { - 'name': dep['name'], - 'version': '-'.join([x for x in [dep.get('versionprefix',''), dep['version'], dep['versionsuffix'].lstrip('-')] if x]), - 'toolchain': "%s-%s" % (dep['toolchain']['name'], dep['toolchain']['version']), - } + dep_pkgname = package_naming_scheme.name(dep) depstring += " --depends '%s'" % ( dep_pkgname) cmdlist=[ @@ -104,7 +92,7 @@ def package_fpm(easyblock, modfile_path, package_type="rpm" ): '--provides', pkgname, '-t', package_type, # target '-s', 'dir', # source - '--version', "eb", + '--version', pkgver, ] cmdlist.extend([ depstring ]) cmdlist.extend([ @@ -133,3 +121,4 @@ def option_postprocess(): else: raise EasyBuildError("Need both fpm and rpmbuild. Found fpm: %s rpmbuild: %s", fpm_path, rpmbuild_path) + From 44797ea87cbfabebf7864ce9647a0dcfb5a6f117 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 17 Jun 2015 22:03:23 -0400 Subject: [PATCH 28/51] moving this file out of the default file name of the sub module --- .../tools/package/packaging_naming_scheme.py | 24 ------------------- 1 file changed, 24 deletions(-) delete mode 100644 easybuild/tools/package/packaging_naming_scheme.py diff --git a/easybuild/tools/package/packaging_naming_scheme.py b/easybuild/tools/package/packaging_naming_scheme.py deleted file mode 100644 index bfe620d2ea..0000000000 --- a/easybuild/tools/package/packaging_naming_scheme.py +++ /dev/null @@ -1,24 +0,0 @@ - -from vsc.utils import fancylogger - -options = [ "package-naming-name-template", "package-naming-version-template", "package-naming-toolchain-template" ] - -class PackagingNamingScheme(object): - """Abstract class for package naming scheme""" - - - def __init__(self, *args, **kwargs): - """initialize logger.""" - self.log = fancylogger.getLogger(self.__class__.__name__, fname=False) - - def name: - """Return name of the package, by default would include name, version, toolchain""" - - - def name_version: - - def version_version: - - def release: - - From 2a3127dd7bd99d9b1ff65ff30ae2a7f0da7255d1 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 17 Jun 2015 22:46:51 -0400 Subject: [PATCH 29/51] broken, but closer. Pick up options and config (keyerror causing problems) --- easybuild/tools/config.py | 8 +++++++- easybuild/tools/options.py | 4 +++- easybuild/tools/package/activepns.py | 14 +++++++++----- easybuild/tools/package/utilities.py | 2 ++ 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py index d48ff5c7b0..31cac9add6 100644 --- a/easybuild/tools/config.py +++ b/easybuild/tools/config.py @@ -95,7 +95,6 @@ def mk_full_default_path(name, prefix=DEFAULT_PREFIX): 'modules_footer', 'only_blocks', 'optarch', - 'package_template', 'package_tool', 'package_type', 'regtest_output_dir', @@ -200,6 +199,7 @@ class ConfigurationVariables(FrozenDictKnownKeys): 'module_syntax', 'modules_tool', 'packagepath', + 'package_naming_scheme', 'prefix', 'repository', 'repositorypath', @@ -367,6 +367,12 @@ def get_repositorypath(): """ return ConfigurationVariables()['repositorypath'] +def get_package_naming_scheme(): + """ + Return the package naming scheme + """ + return ConfigurationVariables()['package_naming_scheme'] + def package_path(): """ Return the path where built packages are copied to diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index d894560292..47a355eccc 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -64,6 +64,8 @@ from easybuild.tools.modules import Lmod from easybuild.tools.ordereddict import OrderedDict import easybuild.tools.package.utilities as packaging +from easybuild.tools.package.utilities import DEFAULT_PNS +from easybuild.tools.package.activepns import avail_package_naming_scheme from easybuild.tools.toolchain.utilities import search_toolchain from easybuild.tools.repository.repository import avail_repositories from easybuild.tools.version import this_is_easybuild @@ -356,9 +358,9 @@ def package_options(self): opts = OrderedDict({ 'package': ("Enabling packaging", None, 'store_true', False), - 'package-template': ("A template string to name the package", None, 'store', DEFAULT_PACKAGE_TEMPLATE), 'package-tool': ("Packaging tool to use", None, 'store_or_None', None), 'package-type': ("Packaging type to output to", None, 'store_or_None', None), + 'package-naming-scheme': ("Packaging naming scheme choice", 'choice', 'store', DEFAULT_PNS, sorted(avail_package_naming_scheme().keys())) }) self.log.debug("package_options: descr %s opts %s" % (descr, opts)) diff --git a/easybuild/tools/package/activepns.py b/easybuild/tools/package/activepns.py index d9184eec70..5c4983854a 100644 --- a/easybuild/tools/package/activepns.py +++ b/easybuild/tools/package/activepns.py @@ -1,18 +1,22 @@ from vsc.utils import fancylogger +from vsc.utils.missing import get_subclasses from vsc.utils.patterns import Singleton -from easybuild.tools.config import build_option -from easybuild.tools.utilities import import_available_modules +from easybuild.tools.config import get_package_naming_scheme from easybuild.tools.build_log import EasyBuildError, print_error, print_msg +from easybuild.tools.package.packaging_naming_scheme.pns import PackagingNamingScheme +from easybuild.tools.utilities import import_available_modules def avail_package_naming_scheme(): ''' Returns the list of valed naming schemes that are in the easybuild.package.package_naming_scheme namespace ''' - pns = import_available_modules('easybuild.tools.package.packaging_naming_scheme') + import_available_modules('easybuild.tools.package.packaging_naming_scheme') + + class_dict = dict([(x.__name__, x) for x in get_subclasses(PackagingNamingScheme)]) - return pns + return class_dict class ActivePNS(object): """ @@ -26,7 +30,7 @@ def __init__(self, *args, **kwargs): self.log = fancylogger.getLogger(self.__class__.__name__, fname=False) avail_pns = avail_package_naming_scheme() - sel_pns = build_option("package-naming-scheme") + sel_pns = get_package_naming_scheme() if sel_pns in avail_pns: self.pns = avail_pns[sel_pns]() else: diff --git a/easybuild/tools/package/utilities.py b/easybuild/tools/package/utilities.py index 8d94826b52..c4c53c2419 100644 --- a/easybuild/tools/package/utilities.py +++ b/easybuild/tools/package/utilities.py @@ -47,6 +47,8 @@ from easybuild.tools.filetools import which from easybuild.tools.package.activepns import ActivePNS +DEFAULT_PNS = 'EasyBuildPNS' + _log = fancylogger.getLogger('tools.packaging') # This is an abbreviated list of the package options, eventually it might make sense to set them # all in the "plugin" rather than in tools.options From 786719f6ef327dd93287c1e5acd6692a722760e0 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Fri, 19 Jun 2015 09:59:07 -0400 Subject: [PATCH 30/51] working to build a basic package again, with the default package naming scheme --- easybuild/tools/options.py | 3 ++- easybuild/tools/package/activepns.py | 10 +++++----- .../packaging_naming_scheme/easybuild_pns.py | 18 ++++++++++-------- .../package/packaging_naming_scheme/pns.py | 6 +++--- easybuild/tools/package/utilities.py | 3 ++- 5 files changed, 22 insertions(+), 18 deletions(-) diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index 47a355eccc..117bf054e3 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -266,6 +266,8 @@ def config_options(self): 'choice', 'store', DEFAULT_MODULES_TOOL, sorted(avail_modules_tools().keys())), 'packagepath': ("The destination path for the packages built by package-tool", None, 'store', mk_full_default_path('packagepath')), + 'package-naming-scheme': ("Packaging naming scheme choice", + 'choice', 'store', DEFAULT_PNS, sorted(avail_package_naming_scheme().keys())), 'prefix': (("Change prefix for buildpath, installpath, sourcepath and repositorypath " "(used prefix for defaults %s)" % DEFAULT_PREFIX), None, 'store', None), @@ -360,7 +362,6 @@ def package_options(self): 'package': ("Enabling packaging", None, 'store_true', False), 'package-tool': ("Packaging tool to use", None, 'store_or_None', None), 'package-type': ("Packaging type to output to", None, 'store_or_None', None), - 'package-naming-scheme': ("Packaging naming scheme choice", 'choice', 'store', DEFAULT_PNS, sorted(avail_package_naming_scheme().keys())) }) self.log.debug("package_options: descr %s opts %s" % (descr, opts)) diff --git a/easybuild/tools/package/activepns.py b/easybuild/tools/package/activepns.py index 5c4983854a..7267bc014d 100644 --- a/easybuild/tools/package/activepns.py +++ b/easybuild/tools/package/activepns.py @@ -37,14 +37,14 @@ def __init__(self, *args, **kwargs): raise EasyBuildError("Selected package naming scheme %s could not be found in %s", sel_pns, avail_pns.keys()) - def name(self): - name = self.pns.name() + def name(self, ec): + name = self.pns.name(ec) return name - def version(self): - version = self.pns.version() + def version(self, ec): + version = self.pns.version(ec) return version - def release(self): + def release(self, ec): release = self.pns.release() return release diff --git a/easybuild/tools/package/packaging_naming_scheme/easybuild_pns.py b/easybuild/tools/package/packaging_naming_scheme/easybuild_pns.py index e0ab409e74..b6dd702528 100644 --- a/easybuild/tools/package/packaging_naming_scheme/easybuild_pns.py +++ b/easybuild/tools/package/packaging_naming_scheme/easybuild_pns.py @@ -19,21 +19,23 @@ class EasyBuildPNS(PackagingNamingScheme): def name(self, ec): name_template = "eb-%(name)s-%(version)s-%(toolchain)s" pkg_name = name_template % { - 'toolchain' : self.toolchain(ec), + 'toolchain' : self._toolchain(ec), 'version': '-'.join([x for x in [ec.get('versionprefix', ''), ec['version'], ec['versionsuffix'].lstrip('-')] if x]), - 'name' : eb.name, - } + 'name' : ec.name, + } + return pkg_name - def _toolchain(self, eb): + def _toolchain(self, ec): toolchain_template = "%(toolchain_name)s-%(toolchain_version)s" pkg_toolchain = toolchain_template % { - 'toolchain_name': eb.toolchain.name, - 'toolchain_version': eb.toolchain.version, + 'toolchain_name': ec.toolchain.name, + 'toolchain_version': ec.toolchain.version, } + return pkg_toolchain - def version(self, eb): - return eb.cfg['version'] + def version(self, ec): + return ec['version'] diff --git a/easybuild/tools/package/packaging_naming_scheme/pns.py b/easybuild/tools/package/packaging_naming_scheme/pns.py index f4427465ca..9d60949b6f 100644 --- a/easybuild/tools/package/packaging_naming_scheme/pns.py +++ b/easybuild/tools/package/packaging_naming_scheme/pns.py @@ -11,14 +11,14 @@ def __init__(self, *args, **kwargs): """initialize logger.""" self.log = fancylogger.getLogger(self.__class__.__name__, fname=False) - def name(self): + def name(self,ec): """Return name of the package, by default would include name, version, toolchain""" - def version(self): + def version(self,ec): """The version in the version part of the package""" - def release(self): + def release(self,ec): """Just the release""" return 1 diff --git a/easybuild/tools/package/utilities.py b/easybuild/tools/package/utilities.py index c4c53c2419..1df0382a53 100644 --- a/easybuild/tools/package/utilities.py +++ b/easybuild/tools/package/utilities.py @@ -72,7 +72,8 @@ def package_fpm(easyblock, modfile_path, package_type="rpm" ): pkgname = package_naming_scheme.name(easyblock.cfg) pkgver = package_naming_scheme.version(easyblock.cfg) pkgrel = package_naming_scheme.release(easyblock.cfg) - + + _log.debug("Got the pns values for (name, version, release): (%s, %s, %s)" % (pkgname, pkgver, pkgrel)) deps = [] if easyblock.toolchain.name != DUMMY_TOOLCHAIN_NAME: toolchain_dict = easyblock.toolchain.as_dict() From 598cb157f5c8f4c8f3255e3f1870a5bccc36a93b Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 22 Jun 2015 22:38:38 -0400 Subject: [PATCH 31/51] fix error message, add release option, bug fixes --- easybuild/framework/easyblock.py | 2 +- easybuild/tools/config.py | 1 + easybuild/tools/options.py | 1 + .../packaging_naming_scheme/easybuild_pns.py | 16 ++++------------ .../tools/package/packaging_naming_scheme/pns.py | 8 +++++--- easybuild/tools/package/utilities.py | 1 + 6 files changed, 13 insertions(+), 16 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 6962c929e8..3c88e9121f 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -1514,7 +1514,7 @@ def package_step(self): src_filename = os.path.basename(src_file) dest_file = os.path.join(packagedir_dest, src_filename) if os.path.exists(dest_file) and not opt_force: - raise EasyBuildError("Unable to copy file, dest already exists. Look in src for packages dest: %s src: %s ", dest_file, src_file) + raise EasyBuildError("Unable to copy package: %s as it already exists in %s. Your package should still be in %s", src_filename, dest_file, packagedir_src) else: shutil.copy(src_file, packagedir_dest) else: diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py index 31cac9add6..62a5ce0fcd 100644 --- a/easybuild/tools/config.py +++ b/easybuild/tools/config.py @@ -97,6 +97,7 @@ def mk_full_default_path(name, prefix=DEFAULT_PREFIX): 'optarch', 'package_tool', 'package_type', + 'package_release', 'regtest_output_dir', 'skip', 'stop', diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index 117bf054e3..79eb82ac64 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -362,6 +362,7 @@ def package_options(self): 'package': ("Enabling packaging", None, 'store_true', False), 'package-tool': ("Packaging tool to use", None, 'store_or_None', None), 'package-type': ("Packaging type to output to", None, 'store_or_None', None), + 'package-release': ("Package release iteration number", None, 'store', "1"), }) self.log.debug("package_options: descr %s opts %s" % (descr, opts)) diff --git a/easybuild/tools/package/packaging_naming_scheme/easybuild_pns.py b/easybuild/tools/package/packaging_naming_scheme/easybuild_pns.py index b6dd702528..855c831a51 100644 --- a/easybuild/tools/package/packaging_naming_scheme/easybuild_pns.py +++ b/easybuild/tools/package/packaging_naming_scheme/easybuild_pns.py @@ -17,28 +17,20 @@ class EasyBuildPNS(PackagingNamingScheme): REQUIRED_KEYS = ['name', 'version', 'versionsuffix', 'toolchain'] def name(self, ec): + self.log.debug("easyconfig dict for name looks like %s " % ec ) name_template = "eb-%(name)s-%(version)s-%(toolchain)s" pkg_name = name_template % { 'toolchain' : self._toolchain(ec), 'version': '-'.join([x for x in [ec.get('versionprefix', ''), ec['version'], ec['versionsuffix'].lstrip('-')] if x]), - 'name' : ec.name, + 'name' : ec['name'], } return pkg_name def _toolchain(self, ec): toolchain_template = "%(toolchain_name)s-%(toolchain_version)s" pkg_toolchain = toolchain_template % { - 'toolchain_name': ec.toolchain.name, - 'toolchain_version': ec.toolchain.version, + 'toolchain_name': ec['toolchain']['name'], + 'toolchain_version': ec['toolchain']['version'], } return pkg_toolchain - - def version(self, ec): - return ec['version'] - - - - def release(self): - return 1 - diff --git a/easybuild/tools/package/packaging_naming_scheme/pns.py b/easybuild/tools/package/packaging_naming_scheme/pns.py index 9d60949b6f..3062682ab1 100644 --- a/easybuild/tools/package/packaging_naming_scheme/pns.py +++ b/easybuild/tools/package/packaging_naming_scheme/pns.py @@ -1,5 +1,6 @@ from vsc.utils import fancylogger +from easybuild.tools.config import build_option options = [ "package-naming-name-template", "package-naming-version-template", "package-naming-toolchain-template" ] @@ -13,13 +14,14 @@ def __init__(self, *args, **kwargs): def name(self,ec): """Return name of the package, by default would include name, version, toolchain""" - + raise NotImplementedError def version(self,ec): """The version in the version part of the package""" + return ec['version'] - def release(self,ec): + def release(self,ec=None): """Just the release""" - return 1 + return build_option('package_release') diff --git a/easybuild/tools/package/utilities.py b/easybuild/tools/package/utilities.py index 1df0382a53..ccaef6bd38 100644 --- a/easybuild/tools/package/utilities.py +++ b/easybuild/tools/package/utilities.py @@ -96,6 +96,7 @@ def package_fpm(easyblock, modfile_path, package_type="rpm" ): '-t', package_type, # target '-s', 'dir', # source '--version', pkgver, + '--iteration', pkgrel, ] cmdlist.extend([ depstring ]) cmdlist.extend([ From 0fac3673945e2444a1abb88f2104691a57489603 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 22 Jun 2015 22:50:49 -0400 Subject: [PATCH 32/51] add eb version to default name --- .../tools/package/packaging_naming_scheme/easybuild_pns.py | 3 ++- easybuild/tools/package/packaging_naming_scheme/pns.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/easybuild/tools/package/packaging_naming_scheme/easybuild_pns.py b/easybuild/tools/package/packaging_naming_scheme/easybuild_pns.py index 855c831a51..1dae429657 100644 --- a/easybuild/tools/package/packaging_naming_scheme/easybuild_pns.py +++ b/easybuild/tools/package/packaging_naming_scheme/easybuild_pns.py @@ -18,11 +18,12 @@ class EasyBuildPNS(PackagingNamingScheme): def name(self, ec): self.log.debug("easyconfig dict for name looks like %s " % ec ) - name_template = "eb-%(name)s-%(version)s-%(toolchain)s" + name_template = "eb%(eb_ver)s-%(name)s-%(version)s-%(toolchain)s" pkg_name = name_template % { 'toolchain' : self._toolchain(ec), 'version': '-'.join([x for x in [ec.get('versionprefix', ''), ec['version'], ec['versionsuffix'].lstrip('-')] if x]), 'name' : ec['name'], + 'eb_ver': self.eb_ver, } return pkg_name diff --git a/easybuild/tools/package/packaging_naming_scheme/pns.py b/easybuild/tools/package/packaging_naming_scheme/pns.py index 3062682ab1..408a1b6904 100644 --- a/easybuild/tools/package/packaging_naming_scheme/pns.py +++ b/easybuild/tools/package/packaging_naming_scheme/pns.py @@ -1,7 +1,7 @@ from vsc.utils import fancylogger from easybuild.tools.config import build_option - +from easybuild.tools.version import VERSION as EASYBUILD_VERSION options = [ "package-naming-name-template", "package-naming-version-template", "package-naming-toolchain-template" ] class PackagingNamingScheme(object): @@ -11,6 +11,7 @@ class PackagingNamingScheme(object): def __init__(self, *args, **kwargs): """initialize logger.""" self.log = fancylogger.getLogger(self.__class__.__name__, fname=False) + self.eb_ver = EASYBUILD_VERSION def name(self,ec): """Return name of the package, by default would include name, version, toolchain""" From 3504714c3364c11df2d0635689634e1a08a02f5a Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 26 Jun 2015 10:23:41 +0200 Subject: [PATCH 33/51] add --read-only-installdir and --group-writeable-installdir configuration options --- easybuild/framework/easyblock.py | 11 ++++++++--- easybuild/tools/config.py | 12 ++---------- easybuild/tools/options.py | 4 ++++ 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index b2828a6d41..cd82d9d28f 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -59,7 +59,7 @@ from easybuild.tools.build_details import get_build_stats 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.config import install_path, log_path, source_paths 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 @@ -1519,13 +1519,18 @@ def post_install_step(self): raise EasyBuildError("Unable to change group permissions of file(s): %s", err) self.log.info("Successfully made software only available for group %s (gid %s)" % self.group) - if read_only_installdir(): + if build_option('read_only_installdir'): # remove write permissions for everyone perms = stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH adjust_permissions(self.installdir, perms, add=False, recursive=True, relative=True, ignore_errors=True) self.log.info("Successfully removed write permissions recursively for *EVERYONE* on install dir.") + elif build_option('group_writable_installdir'): + # enable write permissions for group + perms = stat.S_IWGRP + adjust_permissions(self.installdir, perms, add=True, recursive=True, relative=True, ignore_errors=True) + self.log.info("Successfully enabled write permissions recursively for group on install dir.") else: - # remove write permissions for group and other to protect installation + # remove write permissions for group and other perms = stat.S_IWGRP | stat.S_IWOTH adjust_permissions(self.installdir, perms, add=False, recursive=True, relative=True, ignore_errors=True) self.log.info("Successfully removed write permissions recursively for group/other on install dir.") diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py index 976bc3e7e5..2a6c34e7d0 100644 --- a/easybuild/tools/config.py +++ b/easybuild/tools/config.py @@ -108,8 +108,10 @@ def mk_full_default_path(name, prefix=DEFAULT_PREFIX): 'debug', 'experimental', 'force', + 'group_writable_installdir', 'hidden', 'module_only', + 'read_only_installdir', 'robot', 'sequential', 'set_gid_bit', @@ -448,16 +450,6 @@ def get_log_filename(name, version, add_salt=False): return filepath -def read_only_installdir(): - """ - Return whether installation dir should be fully read-only after installation. - """ - # FIXME (see issue #123): add a config option to set this, should be True by default (?) - # this also needs to be checked when --force is used; - # install dir will have to (temporarily) be made writeable again for owner in that case - return False - - def module_classes(): """ Return list of module classes specified in config file. diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index ebb1a3ef1e..71585b00ae 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -199,6 +199,8 @@ def override_options(self): 'experimental': ("Allow experimental code (with behaviour that can be changed/removed at any given time).", None, 'store_true', False), 'group': ("Group to be used for software installations (only verified, not set)", None, 'store', None), + 'group-writable-installdir': ("Enable group write permissions on installation directory after installation", + None, 'store_true', False), 'hidden': ("Install 'hidden' module file(s) by prefixing their name with '.'", None, 'store_true', False), 'ignore-osdeps': ("Ignore any listed OS dependencies", None, 'store_true', False), 'filter-deps': ("Comma separated list of dependencies that you DON'T want to install with EasyBuild, " @@ -212,6 +214,8 @@ def override_options(self): None, 'store', None), 'pretend': (("Does the build/installation in a test directory located in $HOME/easybuildinstall"), None, 'store_true', False, 'p'), + 'read-only-installdir': ("Set read-only permissions on installation directory after installation", + None, 'store_true', False), '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'), From d5abf406b4d229225b04d9abb2880b56de19611e Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 7 Jul 2015 23:31:35 +0200 Subject: [PATCH 34/51] cleanup of support for packaging + kickstart unit tests related to packaging support --- easybuild/tools/options.py | 20 +-- easybuild/tools/package/activepns.py | 50 ------- .../packaging_naming_scheme/easybuild_pns.py | 34 ++++- .../package/packaging_naming_scheme/pns.py | 57 ++++++-- easybuild/tools/package/utilities.py | 133 +++++++++++------- test/framework/package.py | 79 +++++++++++ test/framework/suite.py | 4 +- 7 files changed, 244 insertions(+), 133 deletions(-) delete mode 100644 easybuild/tools/package/activepns.py create mode 100644 test/framework/package.py diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index 79eb82ac64..cd7ad8da81 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -48,11 +48,10 @@ from easybuild.framework.easyconfig.templates import template_documentation 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 import build_log, config, run # build_log should always stay there, to ensure EasyBuildLog from easybuild.tools.build_log import EasyBuildError, raise_easybuilderror from easybuild.tools.config import DEFAULT_LOGFILE_FORMAT, DEFAULT_MNS, DEFAULT_MODULE_SYNTAX, DEFAULT_MODULES_TOOL -from easybuild.tools.config import DEFAULT_MODULECLASSES, DEFAULT_PATH_SUBDIRS -from easybuild.tools.config import DEFAULT_PREFIX, DEFAULT_REPOSITORY +from easybuild.tools.config import DEFAULT_MODULECLASSES, DEFAULT_PATH_SUBDIRS, DEFAULT_PREFIX, DEFAULT_REPOSITORY from easybuild.tools.config import DEFAULT_STRICT, get_pretend_installpath, mk_full_default_path from easybuild.tools.configobj import ConfigObj, ConfigObjError from easybuild.tools.docs import FORMAT_RST, FORMAT_TXT, avail_easyconfig_params @@ -63,9 +62,7 @@ from easybuild.tools.module_naming_scheme.utilities import avail_module_naming_schemes from easybuild.tools.modules import Lmod from easybuild.tools.ordereddict import OrderedDict -import easybuild.tools.package.utilities as packaging -from easybuild.tools.package.utilities import DEFAULT_PNS -from easybuild.tools.package.activepns import avail_package_naming_scheme +from easybuild.tools.package.utilities import DEFAULT_PNS, avail_package_naming_schemes, check_pkg_support from easybuild.tools.toolchain.utilities import search_toolchain from easybuild.tools.repository.repository import avail_repositories from easybuild.tools.version import this_is_easybuild @@ -267,7 +264,7 @@ def config_options(self): 'packagepath': ("The destination path for the packages built by package-tool", None, 'store', mk_full_default_path('packagepath')), 'package-naming-scheme': ("Packaging naming scheme choice", - 'choice', 'store', DEFAULT_PNS, sorted(avail_package_naming_scheme().keys())), + 'choice', 'store', DEFAULT_PNS, sorted(avail_package_naming_schemes().keys())), 'prefix': (("Change prefix for buildpath, installpath, sourcepath and repositorypath " "(used prefix for defaults %s)" % DEFAULT_PREFIX), None, 'store', None), @@ -472,15 +469,12 @@ def postprocess(self): self._postprocess_config() - #Check experimental option dependencies (for now packaging) - #print "Got config_options: %s" % packaging.config_options - package_options = [ getattr(self.options, x) for x in packaging.config_options if getattr(self.options, x) ] - if any( package_options ): - packaging.option_postprocess() + # check whether packaging is supported when it's being used + if any([self.options.package_tool, self.options.package_type]): + check_pkg_support() else: self.log.debug("Didn't find any packaging options") - def _postprocess_external_modules_metadata(self): """Parse file(s) specifying metadata for external modules.""" # leave external_modules_metadata untouched if no files are provided diff --git a/easybuild/tools/package/activepns.py b/easybuild/tools/package/activepns.py deleted file mode 100644 index 7267bc014d..0000000000 --- a/easybuild/tools/package/activepns.py +++ /dev/null @@ -1,50 +0,0 @@ - - -from vsc.utils import fancylogger -from vsc.utils.missing import get_subclasses -from vsc.utils.patterns import Singleton -from easybuild.tools.config import get_package_naming_scheme -from easybuild.tools.build_log import EasyBuildError, print_error, print_msg -from easybuild.tools.package.packaging_naming_scheme.pns import PackagingNamingScheme -from easybuild.tools.utilities import import_available_modules - -def avail_package_naming_scheme(): - ''' - Returns the list of valed naming schemes that are in the easybuild.package.package_naming_scheme namespace - ''' - import_available_modules('easybuild.tools.package.packaging_naming_scheme') - - class_dict = dict([(x.__name__, x) for x in get_subclasses(PackagingNamingScheme)]) - - return class_dict - -class ActivePNS(object): - """ - The wrapper class for Package Naming Schmese, follows the model of Module Naming Schemes, mostly - """ - - __metaclass__ = Singleton - - def __init__(self, *args, **kwargs): - """Initialize logger and find available PNSes to load""" - self.log = fancylogger.getLogger(self.__class__.__name__, fname=False) - - avail_pns = avail_package_naming_scheme() - sel_pns = get_package_naming_scheme() - if sel_pns in avail_pns: - self.pns = avail_pns[sel_pns]() - else: - raise EasyBuildError("Selected package naming scheme %s could not be found in %s", - sel_pns, avail_pns.keys()) - - def name(self, ec): - name = self.pns.name(ec) - return name - - def version(self, ec): - version = self.pns.version(ec) - return version - - def release(self, ec): - release = self.pns.release() - return release diff --git a/easybuild/tools/package/packaging_naming_scheme/easybuild_pns.py b/easybuild/tools/package/packaging_naming_scheme/easybuild_pns.py index 1dae429657..bc95cace8a 100644 --- a/easybuild/tools/package/packaging_naming_scheme/easybuild_pns.py +++ b/easybuild/tools/package/packaging_naming_scheme/easybuild_pns.py @@ -1,10 +1,31 @@ - - - +## +# Copyright 2015-2015 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 . +## """ -Default implementation of the EasyBuild packaging naming scheme +Implementation of the EasyBuild packaging naming scheme -@author: Rob Schmidt (Ottawa Hospital Research Institute) +@author: Robert Schmidt (Ottawa Hospital Research Institute) @author: Kenneth Hoste (Ghent University) """ @@ -17,6 +38,7 @@ class EasyBuildPNS(PackagingNamingScheme): REQUIRED_KEYS = ['name', 'version', 'versionsuffix', 'toolchain'] def name(self, ec): + """Determine package name""" self.log.debug("easyconfig dict for name looks like %s " % ec ) name_template = "eb%(eb_ver)s-%(name)s-%(version)s-%(toolchain)s" pkg_name = name_template % { @@ -28,10 +50,10 @@ def name(self, ec): return pkg_name def _toolchain(self, ec): + """Determine toolchain""" toolchain_template = "%(toolchain_name)s-%(toolchain_version)s" pkg_toolchain = toolchain_template % { 'toolchain_name': ec['toolchain']['name'], 'toolchain_version': ec['toolchain']['version'], } return pkg_toolchain - diff --git a/easybuild/tools/package/packaging_naming_scheme/pns.py b/easybuild/tools/package/packaging_naming_scheme/pns.py index 408a1b6904..b1569d0f66 100644 --- a/easybuild/tools/package/packaging_naming_scheme/pns.py +++ b/easybuild/tools/package/packaging_naming_scheme/pns.py @@ -1,28 +1,55 @@ - +## +# Copyright 2015-2015 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 . +## + +""" +General package naming scheme. + +@author: Robert Schmidt (Ottawa Hospital Research Institute) +@author: Kenneth Hoste (Ghent University) +""" from vsc.utils import fancylogger from easybuild.tools.config import build_option from easybuild.tools.version import VERSION as EASYBUILD_VERSION -options = [ "package-naming-name-template", "package-naming-version-template", "package-naming-toolchain-template" ] -class PackagingNamingScheme(object): - """Abstract class for package naming scheme""" +class PackagingNamingScheme(object): + """Abstract class for package naming schemes""" - def __init__(self, *args, **kwargs): + def __init__(self): """initialize logger.""" self.log = fancylogger.getLogger(self.__class__.__name__, fname=False) self.eb_ver = EASYBUILD_VERSION - def name(self,ec): - """Return name of the package, by default would include name, version, toolchain""" + def name(self, ec): + """Determine package name""" raise NotImplementedError - - def version(self,ec): - """The version in the version part of the package""" - return ec['version'] - - def release(self,ec=None): - """Just the release""" - return build_option('package_release') + def version(self, ec): + """Determine package version""" + return ec['version'] + def release(self, ec=None): + """Determine package release""" + return build_option('package_release') diff --git a/easybuild/tools/package/utilities.py b/easybuild/tools/package/utilities.py index ccaef6bd38..14e215e2ca 100644 --- a/easybuild/tools/package/utilities.py +++ b/easybuild/tools/package/utilities.py @@ -1,5 +1,5 @@ -# # -# Copyright 2009-2014 Ghent University +## +# Copyright 2015-2015 Ghent University # # This file is part of EasyBuild, # originally created by the HPC team of Ghent University (http://ugent.be/hpc/en), @@ -21,10 +21,10 @@ # # You should have received a copy of the GNU General Public License # along with EasyBuild. If not, see . -# # +## """ -A place for packaging functions +Various utilities related to packaging support. @author: Marc Litherland @author: Gianluca Santarossa (Novartis) @@ -32,97 +32,134 @@ @author: Fotis Georgatos (Uni.Lu, NTUA) @author: Kenneth Hoste (Ghent University) """ - import os import tempfile import pprint from vsc.utils import fancylogger +from vsc.utils.missing import get_subclasses +from vsc.utils.patterns import Singleton -from easybuild.tools.run import run_cmd -from easybuild.tools.config import build_option -from easybuild.tools.module_naming_scheme.utilities import det_full_ec_version -from easybuild.tools.toolchain import DUMMY_TOOLCHAIN_NAME +from easybuild.tools.config import get_package_naming_scheme from easybuild.tools.build_log import EasyBuildError from easybuild.tools.filetools import which -from easybuild.tools.package.activepns import ActivePNS +from easybuild.tools.package.packaging_naming_scheme.pns import PackagingNamingScheme +from easybuild.tools.run import run_cmd +from easybuild.tools.toolchain import DUMMY_TOOLCHAIN_NAME +from easybuild.tools.utilities import import_available_modules + DEFAULT_PNS = 'EasyBuildPNS' -_log = fancylogger.getLogger('tools.packaging') -# This is an abbreviated list of the package options, eventually it might make sense to set them -# all in the "plugin" rather than in tools.options -config_options = [ 'package_tool', 'package_type' ] +_log = fancylogger.getLogger('tools.package') + -def package_fpm(easyblock, modfile_path, package_type="rpm" ): - ''' +def avail_package_naming_schemes(): + """ + Returns the list of valed naming schemes that are in the easybuild.package.package_naming_scheme namespace + """ + import_available_modules('easybuild.tools.package.packaging_naming_scheme') + class_dict = dict([(x.__name__, x) for x in get_subclasses(PackagingNamingScheme)]) + return class_dict + + +def package_fpm(easyblock, modfile_path, package_type='rpm'): + """ This function will build a package using fpm and return the directory where the packages are - ''' - - workdir = tempfile.mkdtemp() - _log.info("Will be writing RPM to %s" % workdir) + """ + workdir = tempfile.mkdtemp(prefix='eb-pkgs') + _log.info("Will be creating packages in %s", workdir) try: os.chdir(workdir) except OSError, err: - raise EasyBuildError("Failed to chdir into workdir: %s : %s", workdir, err) + raise EasyBuildError("Failed to chdir into workdir %s: %s", workdir, err) package_naming_scheme = ActivePNS() pkgname = package_naming_scheme.name(easyblock.cfg) - pkgver = package_naming_scheme.version(easyblock.cfg) - pkgrel = package_naming_scheme.release(easyblock.cfg) + pkgver = package_naming_scheme.version(easyblock.cfg) + pkgrel = package_naming_scheme.release(easyblock.cfg) - _log.debug("Got the pns values for (name, version, release): (%s, %s, %s)" % (pkgname, pkgver, pkgrel)) + _log.debug("Got the PNS values for (name, version, release): (%s, %s, %s)", pkgname, pkgver, pkgrel) deps = [] if easyblock.toolchain.name != DUMMY_TOOLCHAIN_NAME: toolchain_dict = easyblock.toolchain.as_dict() deps.extend([toolchain_dict]) deps.extend(easyblock.cfg.dependencies()) - - _log.debug("The dependencies to be added to the package are: " + pprint.pformat([easyblock.toolchain.as_dict()]+easyblock.cfg.dependencies())) - depstring = "" + + _log.debug("The dependencies to be added to the package are: %s", + pprint.pformat([easyblock.toolchain.as_dict()] + easyblock.cfg.dependencies())) + depstring = '' for dep in deps: - _log.debug("The dep added looks like %s " % dep) + _log.debug("The dep added looks like %s ", dep) dep_pkgname = package_naming_scheme.name(dep) - depstring += " --depends '%s'" % ( dep_pkgname) + depstring += " --depends '%s'" % dep_pkgname - cmdlist=[ + cmdlist = [ 'fpm', '--workdir', workdir, '--name', pkgname, '--provides', pkgname, - '-t', package_type, # target - '-s', 'dir', # source + '-t', package_type, # target + '-s', 'dir', # source '--version', pkgver, '--iteration', pkgrel, - ] - cmdlist.extend([ depstring ]) - cmdlist.extend([ + depstring, easyblock.installdir, - modfile_path - ]) - cmdstr = " ".join(cmdlist) - _log.debug("The flattened cmdlist looks like" + cmdstr) - out = run_cmd(cmdstr, log_all=True, simple=True) - - _log.info("wrote rpm to %s" % (workdir) ) + modfile_path, + ] + cmd = ' '.join(cmdlist) + _log.debug("The flattened cmdlist looks like: %s", cmd) + run_cmd(cmd, log_all=True, simple=True) + + _log.info("Created %s package in %s", package_type, workdir) return workdir -def option_postprocess(): - ''' - Called from easybuild.tools.options.postprocess to check that experimental is triggered and fpm is available - ''' +def check_pkg_support(): + """Check whether packaging is supported, i.e. whether the required dependencies are available.""" - _log.experimental("Using the packaging module, This is experimental") + _log.experimental("Support for packaging installed software.") fpm_path = which('fpm') rpmbuild_path = which('rpmbuild') if fpm_path and rpmbuild_path: - _log.info("fpm found at: %s" % fpm_path) + _log.info("fpm found at: %s", fpm_path) else: raise EasyBuildError("Need both fpm and rpmbuild. Found fpm: %s rpmbuild: %s", fpm_path, rpmbuild_path) +class ActivePNS(object): + """ + The wrapper class for Package Naming Schemes. + """ + __metaclass__ = Singleton + + def __init__(self): + """Initialize logger and find available PNSes to load""" + self.log = fancylogger.getLogger(self.__class__.__name__, fname=False) + + avail_pns = avail_package_naming_schemes() + sel_pns = get_package_naming_scheme() + if sel_pns in avail_pns: + self.pns = avail_pns[sel_pns]() + else: + raise EasyBuildError("Selected package naming scheme %s could not be found in %s", + sel_pns, avail_pns.keys()) + + def name(self, ec): + """Determine package name""" + name = self.pns.name(ec) + return name + + def version(self, ec): + """Determine package version""" + version = self.pns.version(ec) + return version + + def release(self, ec): + """Determine package release""" + release = self.pns.release() + return release diff --git a/test/framework/package.py b/test/framework/package.py new file mode 100644 index 0000000000..8b28dc0eef --- /dev/null +++ b/test/framework/package.py @@ -0,0 +1,79 @@ +# # +# Copyright 2015-2015 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 packaging support. + +@author: Kenneth Hoste (Ghent University) +""" +import os +import stat + +from test.framework.utilities import EnhancedTestCase +from unittest import TestLoader +from unittest import main as unittestmain + +import easybuild.tools.build_log +from easybuild.tools.build_log import EasyBuildError +from easybuild.tools.filetools import adjust_permissions, write_file +from easybuild.tools.package.utilities import avail_package_naming_schemes, check_pkg_support + + +class PackageTest(EnhancedTestCase): + """Tests for packaging support.""" + + def test_avail_package_naming_schemes(self): + """Test avail_package_naming_schemes()""" + self.assertEqual(sorted(avail_package_naming_schemes().keys()), ['EasyBuildPNS']) + + def test_check_pkg_support(self): + """Test check_pkg_support().""" + # hard enable experimental + orig_experimental = easybuild.tools.build_log.EXPERIMENTAL + easybuild.tools.build_log.EXPERIMENTAL = True + + # clear $PATH to make sure fpm/rpmbuild can not be found + os.environ['PATH'] = '' + + self.assertErrorRegex(EasyBuildError, "Need both fpm and rpmbuild", check_pkg_support) + + for binary in ['fpm', 'rpmbuild']: + binpath = os.path.join(self.test_prefix, binary) + write_file(binpath, '#!/bin/bash') + adjust_permissions(binpath, stat.S_IXUSR, add=True) + os.environ['PATH'] = self.test_prefix + + check_pkg_support() + + # restore + easybuild.tools.build_log.EXPERIMENTAL = orig_experimental + + +def suite(): + """ returns all the testcases in this module """ + return TestLoader().loadTestsFromTestCase(PackageTest) + + +if __name__ == '__main__': + unittestmain() diff --git a/test/framework/suite.py b/test/framework/suite.py index 2b6001221f..636966391a 100644 --- a/test/framework/suite.py +++ b/test/framework/suite.py @@ -72,6 +72,7 @@ import test.framework.modulestool as mt import test.framework.options as o import test.framework.parallelbuild as p +import test.framework.package as pkg import test.framework.repository as r import test.framework.robot as robot import test.framework.run as run @@ -100,7 +101,8 @@ # 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 = [gen, bl, 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, p] +tests = [gen, bl, 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, + p, pkg] SUITE = unittest.TestSuite([x.suite() for x in tests]) From f6ff0ac0f69759b35c96d14f5487e465e89d59c6 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 7 Jul 2015 23:39:34 +0200 Subject: [PATCH 35/51] keep imports in alphabetical order --- 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 3c88e9121f..9cfad19244 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -56,7 +56,6 @@ from easybuild.framework.easyconfig.parser import fetch_parameters_from_easyconfig from easybuild.framework.easyconfig.tools import get_paths_for from easybuild.framework.easyconfig.templates import TEMPLATE_NAMES_EASYBLOCK_RUN_STEP -from easybuild.tools.package.utilities import package_fpm from easybuild.tools.build_details import get_build_stats 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 @@ -72,6 +71,7 @@ from easybuild.tools.module_naming_scheme.utilities import avail_module_naming_schemes, det_full_ec_version from easybuild.tools.modules import ROOT_ENV_VAR_NAME_PREFIX, VERSION_ENV_VAR_NAME_PREFIX, DEVEL_ENV_VAR_NAME_PREFIX from easybuild.tools.modules import get_software_root, modules_tool +from easybuild.tools.package.utilities import package_fpm from easybuild.tools.repository.repository import init_repository from easybuild.tools.toolchain import DUMMY_TOOLCHAIN_NAME from easybuild.tools.systemtools import det_parallelism, use_group From 3ccbb5a3c7d804cf446a1c849d513f5c113eb202 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 10 Jul 2015 07:54:04 +0200 Subject: [PATCH 36/51] style cleanup in package_step --- easybuild/framework/easyblock.py | 46 ++++++++++++++-------------- easybuild/tools/options.py | 4 +-- easybuild/tools/package/utilities.py | 12 +++++--- 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 9cfad19244..4a32e812e0 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -68,10 +68,10 @@ from easybuild.tools.run import run_cmd from easybuild.tools.jenkins import write_to_xml from easybuild.tools.module_generator import ModuleGeneratorLua, ModuleGeneratorTcl, module_generator -from easybuild.tools.module_naming_scheme.utilities import avail_module_naming_schemes, det_full_ec_version +from easybuild.tools.module_naming_scheme.utilities import det_full_ec_version from easybuild.tools.modules import ROOT_ENV_VAR_NAME_PREFIX, VERSION_ENV_VAR_NAME_PREFIX, DEVEL_ENV_VAR_NAME_PREFIX from easybuild.tools.modules import get_software_root, modules_tool -from easybuild.tools.package.utilities import package_fpm +from easybuild.tools.package.utilities import PKG_TOOL_FPM, package_fpm from easybuild.tools.repository.repository import init_repository from easybuild.tools.toolchain import DUMMY_TOOLCHAIN_NAME from easybuild.tools.systemtools import det_parallelism, use_group @@ -1491,35 +1491,35 @@ def extensions_step(self, fetch=False): self.clean_up_fake_module(fake_mod_data) def package_step(self): - """Prepare package software (e.g. into an RPM) with fpm.""" + """Package installed software (e.g., into an RPM), if requested, using selected package tool.""" - path_to_module_file = os.path.join(install_path('mod'), build_option('suffix_modules_path'), self.full_mod_name) - packagedir_dest = os.path.abspath(package_path()) + if build_option('package'): - packaging_tool = build_option('package_tool') - opt_force = build_option('force') + path_to_module_file = self.module_generator.filename + pkgdir_dest = os.path.abspath(package_path()) - if not build_option('package'): - _log.info("Skipping package step (not enabled)") + pkgtool = build_option('package_tool') + opt_force = build_option('force') - elif packaging_tool == "fpm": - packaging_type = build_option('package_type') if build_option('package_type') else "rpm" + if pkgtool == PKG_TOOL_FPM: + pkgtype = build_option('package_type') + self.log.info("Generating %s package using %s in %s", pkgtype, pkgtool, pkgdir_dest) + pkgdir_src = package_fpm(self, path_to_module_file, pkgtype) - packagedir_src = package_fpm(self, path_to_module_file, package_type=packaging_type) + mkdir(pkgdir_dest) - if not os.path.exists(packagedir_dest): - mkdir(packagedir_dest) + for src_file in glob.glob(os.path.join(pkgdir_src, "*.%s" % pkgtype)): + dest_file = os.path.join(pkgdir_dest, os.path.basename(src_file)) + if os.path.exists(dest_file) and not opt_force: + raise EasyBuildError("Unable to copy package %s to %s (already exists).", src_file, dest_file) + else: + self.log.info("Copied package %s to %s", src_file, pkgdir_dest) + shutil.copy(src_file, pkgdir_dest) + else: + raise EasyBuildError("Unknown packaging tool specified: %s", pkgtool) - for src_file in glob.glob(os.path.join(packagedir_src, "*.%s" % packaging_type)): - src_filename = os.path.basename(src_file) - dest_file = os.path.join(packagedir_dest, src_filename) - if os.path.exists(dest_file) and not opt_force: - raise EasyBuildError("Unable to copy package: %s as it already exists in %s. Your package should still be in %s", src_filename, dest_file, packagedir_src) - else: - shutil.copy(src_file, packagedir_dest) else: - raise EasyBuildError("Unknown packaging tool specified: %s", packaging_tool) - + self.log.info("Skipping package step (not enabled)") def post_install_step(self): """ diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index 80de25f11a..10a96595dd 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -369,8 +369,8 @@ def package_options(self): opts = OrderedDict({ 'package': ("Enabling packaging", None, 'store_true', False), 'package-tool': ("Packaging tool to use", None, 'store_or_None', None), - 'package-type': ("Packaging type to output to", None, 'store_or_None', None), - 'package-release': ("Package release iteration number", None, 'store', "1"), + 'package-type': ("Type of package to generate", None, 'store_or_None', 'rpm'), + 'package-release': ("Package release iteration number", None, 'store', '1'), }) self.log.debug("package_options: descr %s opts %s" % (descr, opts)) diff --git a/easybuild/tools/package/utilities.py b/easybuild/tools/package/utilities.py index 14e215e2ca..11d73a07f6 100644 --- a/easybuild/tools/package/utilities.py +++ b/easybuild/tools/package/utilities.py @@ -50,6 +50,8 @@ DEFAULT_PNS = 'EasyBuildPNS' +PKG_TOOL_FPM = 'fpm' + _log = fancylogger.getLogger('tools.package') @@ -63,7 +65,7 @@ def avail_package_naming_schemes(): return class_dict -def package_fpm(easyblock, modfile_path, package_type='rpm'): +def package_fpm(easyblock, modfile_path, pkgtype): """ This function will build a package using fpm and return the directory where the packages are """ @@ -98,11 +100,11 @@ def package_fpm(easyblock, modfile_path, package_type='rpm'): depstring += " --depends '%s'" % dep_pkgname cmdlist = [ - 'fpm', + PKG_TOOL_FPM, '--workdir', workdir, '--name', pkgname, '--provides', pkgname, - '-t', package_type, # target + '-t', pkgtype, # target '-s', 'dir', # source '--version', pkgver, '--iteration', pkgrel, @@ -114,7 +116,7 @@ def package_fpm(easyblock, modfile_path, package_type='rpm'): _log.debug("The flattened cmdlist looks like: %s", cmd) run_cmd(cmd, log_all=True, simple=True) - _log.info("Created %s package in %s", package_type, workdir) + _log.info("Created %s package in %s", pkgtype, workdir) return workdir @@ -123,7 +125,7 @@ def check_pkg_support(): """Check whether packaging is supported, i.e. whether the required dependencies are available.""" _log.experimental("Support for packaging installed software.") - fpm_path = which('fpm') + fpm_path = which(PKG_TOOL_FPM) rpmbuild_path = which('rpmbuild') if fpm_path and rpmbuild_path: _log.info("fpm found at: %s", fpm_path) From 71e8f8d5d546a890526d591a1d2ecf53e040e92e Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 10 Jul 2015 08:08:07 +0200 Subject: [PATCH 37/51] add unit test for ActivePNS --- easybuild/tools/config.py | 8 ++++++-- test/framework/package.py | 17 +++++++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py index 8280413cdc..85ae8def48 100644 --- a/easybuild/tools/config.py +++ b/easybuild/tools/config.py @@ -103,8 +103,6 @@ def mk_full_default_path(name, prefix=DEFAULT_PREFIX): 'only_blocks', 'optarch', 'package_tool', - 'package_type', - 'package_release', 'regtest_output_dir', 'skip', 'stop', @@ -135,6 +133,12 @@ def mk_full_default_path(name, prefix=DEFAULT_PREFIX): DEFAULT_STRICT: [ 'strict', ], + '1': [ + 'package_release', + ], + 'rpm': [ + 'package_type', + ], } # build option that do not have a perfectly matching command line option BUILD_OPTIONS_OTHER = { diff --git a/test/framework/package.py b/test/framework/package.py index 8b28dc0eef..9a95efc4ae 100644 --- a/test/framework/package.py +++ b/test/framework/package.py @@ -30,14 +30,15 @@ import os import stat -from test.framework.utilities import EnhancedTestCase +from test.framework.utilities import EnhancedTestCase, init_config from unittest import TestLoader from unittest import main as unittestmain import easybuild.tools.build_log +from easybuild.framework.easyconfig.easyconfig import EasyConfig from easybuild.tools.build_log import EasyBuildError from easybuild.tools.filetools import adjust_permissions, write_file -from easybuild.tools.package.utilities import avail_package_naming_schemes, check_pkg_support +from easybuild.tools.package.utilities import ActivePNS, avail_package_naming_schemes, check_pkg_support class PackageTest(EnhancedTestCase): @@ -69,6 +70,18 @@ def test_check_pkg_support(self): # restore easybuild.tools.build_log.EXPERIMENTAL = orig_experimental + def test_active_pns(self): + """Test use of ActivePNS.""" + test_easyconfigs = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs') + ec = EasyConfig(os.path.join(test_easyconfigs, 'OpenMPI-1.6.4-GCC-4.6.4.eb'), validate=False) + + pns = ActivePNS() + + # default: EasyBuild package naming scheme, pkg release 1 + self.assertEqual(pns.name(ec), 'eb2.2.0dev-OpenMPI-1.6.4-GCC-4.6.4') + self.assertEqual(pns.version(ec), '1.6.4') + self.assertEqual(pns.release(ec), '1') + def suite(): """ returns all the testcases in this module """ From a187736d6ecd9039cf666054f21b88c2fd9f370e Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 10 Jul 2015 08:25:31 +0200 Subject: [PATCH 38/51] enhance check_pkg_support function --- easybuild/tools/config.py | 7 ++++++- easybuild/tools/options.py | 11 ++++++----- easybuild/tools/package/utilities.py | 24 ++++++++++++++++++------ test/framework/package.py | 3 ++- 4 files changed, 32 insertions(+), 13 deletions(-) diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py index 85ae8def48..1dd1e16b5f 100644 --- a/easybuild/tools/config.py +++ b/easybuild/tools/config.py @@ -102,7 +102,6 @@ def mk_full_default_path(name, prefix=DEFAULT_PREFIX): 'modules_footer', 'only_blocks', 'optarch', - 'package_tool', 'regtest_output_dir', 'skip', 'stop', @@ -136,6 +135,9 @@ def mk_full_default_path(name, prefix=DEFAULT_PREFIX): '1': [ 'package_release', ], + 'fpm': [ + 'package_tool', + ], 'rpm': [ 'package_type', ], @@ -380,18 +382,21 @@ def get_repositorypath(): """ return ConfigurationVariables()['repositorypath'] + def get_package_naming_scheme(): """ Return the package naming scheme """ return ConfigurationVariables()['package_naming_scheme'] + def package_path(): """ Return the path where built packages are copied to """ return ConfigurationVariables()['packagepath'] + def get_modules_tool(): """ Return modules tool (EnvironmentModulesC, Lmod, ...) diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index 10a96595dd..f0cacb58c4 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -66,7 +66,8 @@ from easybuild.tools.module_naming_scheme.utilities import avail_module_naming_schemes from easybuild.tools.modules import Lmod from easybuild.tools.ordereddict import OrderedDict -from easybuild.tools.package.utilities import DEFAULT_PNS, avail_package_naming_schemes, check_pkg_support +from easybuild.tools.package.utilities import DEFAULT_PNS, PKG_TOOL_FPM, PKG_TYPE_RPM +from easybuild.tools.package.utilities import avail_package_naming_schemes, check_pkg_support from easybuild.tools.toolchain.utilities import search_toolchain from easybuild.tools.repository.repository import avail_repositories from easybuild.tools.version import this_is_easybuild @@ -368,8 +369,8 @@ def package_options(self): opts = OrderedDict({ 'package': ("Enabling packaging", None, 'store_true', False), - 'package-tool': ("Packaging tool to use", None, 'store_or_None', None), - 'package-type': ("Type of package to generate", None, 'store_or_None', 'rpm'), + 'package-tool': ("Packaging tool to use", None, 'store_or_None', PKG_TOOL_FPM), + 'package-type': ("Type of package to generate", None, 'store_or_None', PKG_TYPE_RPM), 'package-release': ("Package release iteration number", None, 'store', '1'), }) @@ -503,10 +504,10 @@ def postprocess(self): self._postprocess_config() # check whether packaging is supported when it's being used - if any([self.options.package_tool, self.options.package_type]): + if self.options.package: check_pkg_support() else: - self.log.debug("Didn't find any packaging options") + self.log.debug("Packaging not enabled, so not check for packaging support.") def _postprocess_external_modules_metadata(self): """Parse file(s) specifying metadata for external modules.""" diff --git a/easybuild/tools/package/utilities.py b/easybuild/tools/package/utilities.py index 11d73a07f6..f800abf389 100644 --- a/easybuild/tools/package/utilities.py +++ b/easybuild/tools/package/utilities.py @@ -40,7 +40,7 @@ from vsc.utils.missing import get_subclasses from vsc.utils.patterns import Singleton -from easybuild.tools.config import get_package_naming_scheme +from easybuild.tools.config import build_option, get_package_naming_scheme from easybuild.tools.build_log import EasyBuildError from easybuild.tools.filetools import which from easybuild.tools.package.packaging_naming_scheme.pns import PackagingNamingScheme @@ -51,6 +51,7 @@ DEFAULT_PNS = 'EasyBuildPNS' PKG_TOOL_FPM = 'fpm' +PKG_TYPE_RPM = 'rpm' _log = fancylogger.getLogger('tools.package') @@ -124,13 +125,24 @@ def package_fpm(easyblock, modfile_path, pkgtype): def check_pkg_support(): """Check whether packaging is supported, i.e. whether the required dependencies are available.""" + # packaging support is considered experimental for now (requires using --experimental) _log.experimental("Support for packaging installed software.") - fpm_path = which(PKG_TOOL_FPM) - rpmbuild_path = which('rpmbuild') - if fpm_path and rpmbuild_path: - _log.info("fpm found at: %s", fpm_path) + + pkgtool = build_option('package_tool') + pkgtool_path = which(pkgtool) + if pkgtool_path: + _log.info("Selected packaging tool '%s' found at %s", pkgtool, pkgtool_path) + + # rpmbuild is required for generating RPMs with FPM + if pkgtool == PKG_TOOL_FPM and build_option('package_type') == PKG_TYPE_RPM: + rpmbuild_path = which('rpmbuild') + if rpmbuild_path: + _log.info("Required tool 'rpmbuild' found at %s", rpmbuild_path) + else: + raise EasyBuildError("rpmbuild is required when generating RPM packages with FPM, but was not found") + else: - raise EasyBuildError("Need both fpm and rpmbuild. Found fpm: %s rpmbuild: %s", fpm_path, rpmbuild_path) + raise EasyBuildError("Selected packaging tool '%s' not found", pkgtool) class ActivePNS(object): diff --git a/test/framework/package.py b/test/framework/package.py index 9a95efc4ae..d51412ca58 100644 --- a/test/framework/package.py +++ b/test/framework/package.py @@ -57,7 +57,7 @@ def test_check_pkg_support(self): # clear $PATH to make sure fpm/rpmbuild can not be found os.environ['PATH'] = '' - self.assertErrorRegex(EasyBuildError, "Need both fpm and rpmbuild", check_pkg_support) + self.assertErrorRegex(EasyBuildError, "Selected packaging tool 'fpm' not found", check_pkg_support) for binary in ['fpm', 'rpmbuild']: binpath = os.path.join(self.test_prefix, binary) @@ -65,6 +65,7 @@ def test_check_pkg_support(self): adjust_permissions(binpath, stat.S_IXUSR, add=True) os.environ['PATH'] = self.test_prefix + # no errors => support check passes check_pkg_support() # restore From 37ad376bc51984afe744217c1df3fc7c6d5d1903 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 10 Jul 2015 08:30:24 +0200 Subject: [PATCH 39/51] fix __init__.py's in tools/package, rename to PackageNamingScheme (was PackagingNamingScheme) --- easybuild/tools/package/__init__.py | 38 +++++++++++++++++-- .../package/package_naming_scheme/__init__.py | 37 ++++++++++++++++++ .../easybuild_pns.py | 4 +- .../pns.py | 2 +- .../packaging_naming_scheme/__init__.py | 0 easybuild/tools/package/utilities.py | 6 +-- 6 files changed, 77 insertions(+), 10 deletions(-) create mode 100644 easybuild/tools/package/package_naming_scheme/__init__.py rename easybuild/tools/package/{packaging_naming_scheme => package_naming_scheme}/easybuild_pns.py (94%) rename easybuild/tools/package/{packaging_naming_scheme => package_naming_scheme}/pns.py (98%) delete mode 100644 easybuild/tools/package/packaging_naming_scheme/__init__.py diff --git a/easybuild/tools/package/__init__.py b/easybuild/tools/package/__init__.py index de858db457..51a62d3f44 100644 --- a/easybuild/tools/package/__init__.py +++ b/easybuild/tools/package/__init__.py @@ -1,8 +1,38 @@ +## +# Copyright 2009-2015 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 . +## """ -The packaging module, will contain code for packaging and naming-schemes that can be -overriden to cover site customizations +This declares the namespace for the tools.package submodule of EasyBuild, +which contains support for packaging and package naming schemes that can be overriden to cover site customizations. +@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/package/package_naming_scheme/__init__.py b/easybuild/tools/package/package_naming_scheme/__init__.py new file mode 100644 index 0000000000..6af2449881 --- /dev/null +++ b/easybuild/tools/package/package_naming_scheme/__init__.py @@ -0,0 +1,37 @@ +## +# Copyright 2009-2015 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.package.package_naming_scheme submodule of EasyBuild. + +@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/package/packaging_naming_scheme/easybuild_pns.py b/easybuild/tools/package/package_naming_scheme/easybuild_pns.py similarity index 94% rename from easybuild/tools/package/packaging_naming_scheme/easybuild_pns.py rename to easybuild/tools/package/package_naming_scheme/easybuild_pns.py index bc95cace8a..2ed2ff2013 100644 --- a/easybuild/tools/package/packaging_naming_scheme/easybuild_pns.py +++ b/easybuild/tools/package/package_naming_scheme/easybuild_pns.py @@ -29,10 +29,10 @@ @author: Kenneth Hoste (Ghent University) """ -from easybuild.tools.package.packaging_naming_scheme.pns import PackagingNamingScheme +from easybuild.tools.package.package_naming_scheme.pns import PackageNamingScheme -class EasyBuildPNS(PackagingNamingScheme): +class EasyBuildPNS(PackageNamingScheme): """Class implmenting the default EasyBuild packaging naming scheme.""" REQUIRED_KEYS = ['name', 'version', 'versionsuffix', 'toolchain'] diff --git a/easybuild/tools/package/packaging_naming_scheme/pns.py b/easybuild/tools/package/package_naming_scheme/pns.py similarity index 98% rename from easybuild/tools/package/packaging_naming_scheme/pns.py rename to easybuild/tools/package/package_naming_scheme/pns.py index b1569d0f66..e8733c20b6 100644 --- a/easybuild/tools/package/packaging_naming_scheme/pns.py +++ b/easybuild/tools/package/package_naming_scheme/pns.py @@ -34,7 +34,7 @@ from easybuild.tools.version import VERSION as EASYBUILD_VERSION -class PackagingNamingScheme(object): +class PackageNamingScheme(object): """Abstract class for package naming schemes""" def __init__(self): diff --git a/easybuild/tools/package/packaging_naming_scheme/__init__.py b/easybuild/tools/package/packaging_naming_scheme/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/easybuild/tools/package/utilities.py b/easybuild/tools/package/utilities.py index f800abf389..2b282e30d9 100644 --- a/easybuild/tools/package/utilities.py +++ b/easybuild/tools/package/utilities.py @@ -43,7 +43,7 @@ from easybuild.tools.config import build_option, get_package_naming_scheme from easybuild.tools.build_log import EasyBuildError from easybuild.tools.filetools import which -from easybuild.tools.package.packaging_naming_scheme.pns import PackagingNamingScheme +from easybuild.tools.package.package_naming_scheme.pns import PackageNamingScheme from easybuild.tools.run import run_cmd from easybuild.tools.toolchain import DUMMY_TOOLCHAIN_NAME from easybuild.tools.utilities import import_available_modules @@ -61,8 +61,8 @@ def avail_package_naming_schemes(): """ Returns the list of valed naming schemes that are in the easybuild.package.package_naming_scheme namespace """ - import_available_modules('easybuild.tools.package.packaging_naming_scheme') - class_dict = dict([(x.__name__, x) for x in get_subclasses(PackagingNamingScheme)]) + import_available_modules('easybuild.tools.package.package_naming_scheme') + class_dict = dict([(x.__name__, x) for x in get_subclasses(PackageNamingScheme)]) return class_dict From 58021d4b52100e9e1e636720df9a54ec973d8b32 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 10 Jul 2015 13:41:16 +0200 Subject: [PATCH 40/51] clean up package naming scheme modules --- .../package_naming_scheme/easybuild_pns.py | 19 +++++-------------- .../package/package_naming_scheme/pns.py | 12 +++++++----- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/easybuild/tools/package/package_naming_scheme/easybuild_pns.py b/easybuild/tools/package/package_naming_scheme/easybuild_pns.py index 2ed2ff2013..a93a5ef69b 100644 --- a/easybuild/tools/package/package_naming_scheme/easybuild_pns.py +++ b/easybuild/tools/package/package_naming_scheme/easybuild_pns.py @@ -30,30 +30,21 @@ """ from easybuild.tools.package.package_naming_scheme.pns import PackageNamingScheme +from easybuild.tools.version import VERSION as EASYBUILD_VERSION class EasyBuildPNS(PackageNamingScheme): """Class implmenting the default EasyBuild packaging naming scheme.""" - REQUIRED_KEYS = ['name', 'version', 'versionsuffix', 'toolchain'] - def name(self, ec): """Determine package name""" self.log.debug("easyconfig dict for name looks like %s " % ec ) - name_template = "eb%(eb_ver)s-%(name)s-%(version)s-%(toolchain)s" + name_template = "eb%(eb_ver)s-%(name)s-%(version)s-%(toolchain_name)s-%(toolchain_version)s" pkg_name = name_template % { - 'toolchain' : self._toolchain(ec), + 'toolchain_name' : ec['toolchain']['name'], + 'toolchain_version' : ec['toolchain']['version'], 'version': '-'.join([x for x in [ec.get('versionprefix', ''), ec['version'], ec['versionsuffix'].lstrip('-')] if x]), 'name' : ec['name'], - 'eb_ver': self.eb_ver, + 'eb_ver': EASYBUILD_VERSION, } return pkg_name - - def _toolchain(self, ec): - """Determine toolchain""" - toolchain_template = "%(toolchain_name)s-%(toolchain_version)s" - pkg_toolchain = toolchain_template % { - 'toolchain_name': ec['toolchain']['name'], - 'toolchain_version': ec['toolchain']['version'], - } - return pkg_toolchain diff --git a/easybuild/tools/package/package_naming_scheme/pns.py b/easybuild/tools/package/package_naming_scheme/pns.py index e8733c20b6..deb81c3909 100644 --- a/easybuild/tools/package/package_naming_scheme/pns.py +++ b/easybuild/tools/package/package_naming_scheme/pns.py @@ -24,30 +24,32 @@ ## """ -General package naming scheme. +Abstract implementation of a package naming scheme. @author: Robert Schmidt (Ottawa Hospital Research Institute) @author: Kenneth Hoste (Ghent University) """ +from abc import ABCMeta, abstractmethod from vsc.utils import fancylogger + from easybuild.tools.config import build_option -from easybuild.tools.version import VERSION as EASYBUILD_VERSION class PackageNamingScheme(object): """Abstract class for package naming schemes""" + __metaclass__ = ABCMeta def __init__(self): """initialize logger.""" self.log = fancylogger.getLogger(self.__class__.__name__, fname=False) - self.eb_ver = EASYBUILD_VERSION + @abstractmethod def name(self, ec): """Determine package name""" - raise NotImplementedError + pass def version(self, ec): - """Determine package version""" + """Determine package version.""" return ec['version'] def release(self, ec=None): From aab0716b5f2cdc471be6ce9beb673065950d22d9 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 10 Jul 2015 13:52:20 +0200 Subject: [PATCH 41/51] use det_full_ec_version in EasyBuildPNS --- .../package/package_naming_scheme/easybuild_pns.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/easybuild/tools/package/package_naming_scheme/easybuild_pns.py b/easybuild/tools/package/package_naming_scheme/easybuild_pns.py index a93a5ef69b..0f7aa12ba3 100644 --- a/easybuild/tools/package/package_naming_scheme/easybuild_pns.py +++ b/easybuild/tools/package/package_naming_scheme/easybuild_pns.py @@ -28,7 +28,7 @@ @author: Robert Schmidt (Ottawa Hospital Research Institute) @author: Kenneth Hoste (Ghent University) """ - +from easybuild.tools.module_naming_scheme.utilities import det_full_ec_version from easybuild.tools.package.package_naming_scheme.pns import PackageNamingScheme from easybuild.tools.version import VERSION as EASYBUILD_VERSION @@ -38,12 +38,10 @@ class EasyBuildPNS(PackageNamingScheme): def name(self, ec): """Determine package name""" - self.log.debug("easyconfig dict for name looks like %s " % ec ) - name_template = "eb%(eb_ver)s-%(name)s-%(version)s-%(toolchain_name)s-%(toolchain_version)s" + self.log.debug("easyconfig dict for name looks like: %s ", ec) + name_template = "eb%(eb_ver)s-%(name)s-%(fullversion)s" pkg_name = name_template % { - 'toolchain_name' : ec['toolchain']['name'], - 'toolchain_version' : ec['toolchain']['version'], - 'version': '-'.join([x for x in [ec.get('versionprefix', ''), ec['version'], ec['versionsuffix'].lstrip('-')] if x]), + 'fullversion': det_full_ec_version(ec), 'name' : ec['name'], 'eb_ver': EASYBUILD_VERSION, } From 016eac4352a11c7186c375b01e3c966cd69bc94a Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 10 Jul 2015 19:01:07 +0200 Subject: [PATCH 42/51] tweak EasyBuildPNS, add unit test for package_fpm --- easybuild/framework/easyblock.py | 6 +-- .../package_naming_scheme/easybuild_pns.py | 14 +++--- easybuild/tools/package/utilities.py | 6 +-- ...1.3.12.eb => toy-0.0-gompi-1.3.12-test.eb} | 1 + test/framework/package.py | 43 +++++++++++++++++-- test/framework/toy_build.py | 4 +- 6 files changed, 54 insertions(+), 20 deletions(-) rename test/framework/easyconfigs/{toy-0.0-gompi-1.3.12.eb => toy-0.0-gompi-1.3.12-test.eb} (97%) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 4a32e812e0..63b212c5b1 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -1495,16 +1495,14 @@ def package_step(self): if build_option('package'): - path_to_module_file = self.module_generator.filename - pkgdir_dest = os.path.abspath(package_path()) - pkgtool = build_option('package_tool') + pkgdir_dest = os.path.abspath(package_path()) opt_force = build_option('force') if pkgtool == PKG_TOOL_FPM: pkgtype = build_option('package_type') self.log.info("Generating %s package using %s in %s", pkgtype, pkgtool, pkgdir_dest) - pkgdir_src = package_fpm(self, path_to_module_file, pkgtype) + pkgdir_src = package_fpm(self, pkgtype) mkdir(pkgdir_dest) diff --git a/easybuild/tools/package/package_naming_scheme/easybuild_pns.py b/easybuild/tools/package/package_naming_scheme/easybuild_pns.py index 0f7aa12ba3..d1990de6d9 100644 --- a/easybuild/tools/package/package_naming_scheme/easybuild_pns.py +++ b/easybuild/tools/package/package_naming_scheme/easybuild_pns.py @@ -38,11 +38,9 @@ class EasyBuildPNS(PackageNamingScheme): def name(self, ec): """Determine package name""" - self.log.debug("easyconfig dict for name looks like: %s ", ec) - name_template = "eb%(eb_ver)s-%(name)s-%(fullversion)s" - pkg_name = name_template % { - 'fullversion': det_full_ec_version(ec), - 'name' : ec['name'], - 'eb_ver': EASYBUILD_VERSION, - } - return pkg_name + self.log.debug("Easyconfig dict passed to name() looks like: %s ", ec) + return '%s-%s' % (ec['name'], det_full_ec_version(ec)) + + def version(self, ec): + """Determine package version: EasyBuild version used to build & install.""" + return 'eb-%s' % EASYBUILD_VERSION diff --git a/easybuild/tools/package/utilities.py b/easybuild/tools/package/utilities.py index 2b282e30d9..007c713723 100644 --- a/easybuild/tools/package/utilities.py +++ b/easybuild/tools/package/utilities.py @@ -66,11 +66,11 @@ def avail_package_naming_schemes(): return class_dict -def package_fpm(easyblock, modfile_path, pkgtype): +def package_fpm(easyblock, pkgtype): """ This function will build a package using fpm and return the directory where the packages are """ - workdir = tempfile.mkdtemp(prefix='eb-pkgs') + workdir = tempfile.mkdtemp(prefix='eb-pkgs-') _log.info("Will be creating packages in %s", workdir) try: @@ -111,7 +111,7 @@ def package_fpm(easyblock, modfile_path, pkgtype): '--iteration', pkgrel, depstring, easyblock.installdir, - modfile_path, + easyblock.module_generator.filename, ] cmd = ' '.join(cmdlist) _log.debug("The flattened cmdlist looks like: %s", cmd) diff --git a/test/framework/easyconfigs/toy-0.0-gompi-1.3.12.eb b/test/framework/easyconfigs/toy-0.0-gompi-1.3.12-test.eb similarity index 97% rename from test/framework/easyconfigs/toy-0.0-gompi-1.3.12.eb rename to test/framework/easyconfigs/toy-0.0-gompi-1.3.12-test.eb index 632c832da3..b866a7ee1c 100644 --- a/test/framework/easyconfigs/toy-0.0-gompi-1.3.12.eb +++ b/test/framework/easyconfigs/toy-0.0-gompi-1.3.12-test.eb @@ -1,5 +1,6 @@ name = 'toy' version = '0.0' +versionsuffix = '-test' homepage = 'http://hpcugent.github.com/easybuild' description = "Toy C program." diff --git a/test/framework/package.py b/test/framework/package.py index d51412ca58..61bf4272f9 100644 --- a/test/framework/package.py +++ b/test/framework/package.py @@ -38,7 +38,20 @@ from easybuild.framework.easyconfig.easyconfig import EasyConfig from easybuild.tools.build_log import EasyBuildError from easybuild.tools.filetools import adjust_permissions, write_file -from easybuild.tools.package.utilities import ActivePNS, avail_package_naming_schemes, check_pkg_support +from easybuild.tools.package.utilities import ActivePNS, avail_package_naming_schemes, check_pkg_support, package_fpm +from easybuild.tools.version import VERSION as EASYBUILD_VERSION + + +MOCKED_FPM_RPM = """#!/bin/bash +# only parse what we need to spit out the expected package file, ignore the rest +workdir=`echo $@ | sed 's/--workdir \([^ ]*\).*/\\1/g'` +name=`echo $@ | sed 's/.* --name \([^ ]*\).*/\\1/g'` +version=`echo $@ | sed 's/.*--version \([^ ]*\).*/\\1/g'` +iteration=`echo $@ | sed 's/.*--iteration \([^ ]*\).*/\\1/g'` +target=`echo $@ | sed 's/.*-t \([^ ]*\).*/\\1/g'` + +echo "thisisan$target" > ${workdir}/${name}-${version}.${iteration}.${target} +""" class PackageTest(EnhancedTestCase): @@ -79,10 +92,34 @@ def test_active_pns(self): pns = ActivePNS() # default: EasyBuild package naming scheme, pkg release 1 - self.assertEqual(pns.name(ec), 'eb2.2.0dev-OpenMPI-1.6.4-GCC-4.6.4') - self.assertEqual(pns.version(ec), '1.6.4') + self.assertEqual(pns.name(ec), 'OpenMPI-1.6.4-GCC-4.6.4') + self.assertEqual(pns.version(ec), 'eb-%s' % EASYBUILD_VERSION) self.assertEqual(pns.release(ec), '1') + def test_package_fpm(self): + """Test package_fpm function.""" + init_config(build_options={'silent': True}) + + test_easyconfigs = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs') + ec = EasyConfig(os.path.join(test_easyconfigs, 'toy-0.0-gompi-1.3.12-test.eb'), validate=False) + + # put mocked 'fpm' command in place, just for testing purposes + fpm = os.path.join(self.test_prefix, 'fpm') + write_file(fpm, MOCKED_FPM_RPM) + adjust_permissions(fpm, stat.S_IXUSR, add=True) + os.environ['PATH'] = '%s:%s' % (self.test_prefix, os.environ['PATH']) + + # import needs to be done here, since test easyblocks are only included later + from easybuild.easyblocks.toy import EB_toy + eb = EB_toy(ec) + + # build & install first + eb.run_all_steps(False) + pkgdir = package_fpm(eb, 'rpm') + + pkgfile = os.path.join(pkgdir, 'toy-0.0-gompi-1.3.12-test-eb-%s.1.rpm' % EASYBUILD_VERSION) + self.assertTrue(os.path.isfile(pkgfile), "Found %s" % pkgfile) + def suite(): """ returns all the testcases in this module """ diff --git a/test/framework/toy_build.py b/test/framework/toy_build.py index 46797db2d1..25a7c7ad85 100644 --- a/test/framework/toy_build.py +++ b/test/framework/toy_build.py @@ -693,8 +693,8 @@ def test_toy_advanced(self): """Test toy build with extensions and non-dummy toolchain.""" test_dir = os.path.abspath(os.path.dirname(__file__)) os.environ['MODULEPATH'] = os.path.join(test_dir, 'modules') - test_ec = os.path.join(test_dir, 'easyconfigs', 'toy-0.0-gompi-1.3.12.eb') - self.test_toy_build(ec_file=test_ec, versionsuffix='-gompi-1.3.12') + test_ec = os.path.join(test_dir, 'easyconfigs', 'toy-0.0-gompi-1.3.12-test.eb') + self.test_toy_build(ec_file=test_ec, versionsuffix='-gompi-1.3.12-test') def test_toy_hidden(self): """Test installing a hidden module.""" From 8ed418f3d19d7661ae2904b15e7207e74e09df18 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 10 Jul 2015 19:11:22 +0200 Subject: [PATCH 43/51] add package() function, move some of the logic down --- easybuild/framework/easyblock.py | 28 ++++++++++++---------------- easybuild/tools/package/utilities.py | 20 +++++++++++++++++--- test/framework/package.py | 14 ++++++++------ 3 files changed, 37 insertions(+), 25 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 63b212c5b1..bd5f4cdc0b 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -71,7 +71,7 @@ from easybuild.tools.module_naming_scheme.utilities import det_full_ec_version from easybuild.tools.modules import ROOT_ENV_VAR_NAME_PREFIX, VERSION_ENV_VAR_NAME_PREFIX, DEVEL_ENV_VAR_NAME_PREFIX from easybuild.tools.modules import get_software_root, modules_tool -from easybuild.tools.package.utilities import PKG_TOOL_FPM, package_fpm +from easybuild.tools.package.utilities import PKG_TOOL_FPM, package from easybuild.tools.repository.repository import init_repository from easybuild.tools.toolchain import DUMMY_TOOLCHAIN_NAME from easybuild.tools.systemtools import det_parallelism, use_group @@ -1495,26 +1495,22 @@ def package_step(self): if build_option('package'): - pkgtool = build_option('package_tool') + pkgtype = build_option('package_type') pkgdir_dest = os.path.abspath(package_path()) opt_force = build_option('force') - if pkgtool == PKG_TOOL_FPM: - pkgtype = build_option('package_type') - self.log.info("Generating %s package using %s in %s", pkgtype, pkgtool, pkgdir_dest) - pkgdir_src = package_fpm(self, pkgtype) + self.log.info("Generating %s package in %s", pkgtype, pkgdir_dest) + pkgdir_src = package(self) - mkdir(pkgdir_dest) + mkdir(pkgdir_dest) - for src_file in glob.glob(os.path.join(pkgdir_src, "*.%s" % pkgtype)): - dest_file = os.path.join(pkgdir_dest, os.path.basename(src_file)) - if os.path.exists(dest_file) and not opt_force: - raise EasyBuildError("Unable to copy package %s to %s (already exists).", src_file, dest_file) - else: - self.log.info("Copied package %s to %s", src_file, pkgdir_dest) - shutil.copy(src_file, pkgdir_dest) - else: - raise EasyBuildError("Unknown packaging tool specified: %s", pkgtool) + for src_file in glob.glob(os.path.join(pkgdir_src, "*.%s" % pkgtype)): + dest_file = os.path.join(pkgdir_dest, os.path.basename(src_file)) + if os.path.exists(dest_file) and not opt_force: + raise EasyBuildError("Unable to copy package %s to %s (already exists).", src_file, dest_file) + else: + self.log.info("Copied package %s to %s", src_file, pkgdir_dest) + shutil.copy(src_file, pkgdir_dest) else: self.log.info("Skipping package step (not enabled)") diff --git a/easybuild/tools/package/utilities.py b/easybuild/tools/package/utilities.py index 007c713723..748c4f2d03 100644 --- a/easybuild/tools/package/utilities.py +++ b/easybuild/tools/package/utilities.py @@ -66,12 +66,26 @@ def avail_package_naming_schemes(): return class_dict -def package_fpm(easyblock, pkgtype): +def package(easyblock): + """ + Package installed software, according to active packaging configuration settings.""" + pkgtool = build_option('package_tool') + + if pkgtool == PKG_TOOL_FPM: + pkgdir = package_with_fpm(easyblock) + else: + raise EasyBuildError("Unknown packaging tool specified: %s", pkgtool) + + return pkgdir + + +def package_with_fpm(easyblock): """ This function will build a package using fpm and return the directory where the packages are """ workdir = tempfile.mkdtemp(prefix='eb-pkgs-') - _log.info("Will be creating packages in %s", workdir) + pkgtype = build_option('package_type') + _log.info("Will be creating %s package(s) in %s", pkgtype, workdir) try: os.chdir(workdir) @@ -117,7 +131,7 @@ def package_fpm(easyblock, pkgtype): _log.debug("The flattened cmdlist looks like: %s", cmd) run_cmd(cmd, log_all=True, simple=True) - _log.info("Created %s package in %s", pkgtype, workdir) + _log.info("Created %s package(s) in %s", pkgtype, workdir) return workdir diff --git a/test/framework/package.py b/test/framework/package.py index 61bf4272f9..e3892ebbce 100644 --- a/test/framework/package.py +++ b/test/framework/package.py @@ -38,7 +38,7 @@ from easybuild.framework.easyconfig.easyconfig import EasyConfig from easybuild.tools.build_log import EasyBuildError from easybuild.tools.filetools import adjust_permissions, write_file -from easybuild.tools.package.utilities import ActivePNS, avail_package_naming_schemes, check_pkg_support, package_fpm +from easybuild.tools.package.utilities import ActivePNS, avail_package_naming_schemes, check_pkg_support, package from easybuild.tools.version import VERSION as EASYBUILD_VERSION @@ -96,8 +96,8 @@ def test_active_pns(self): self.assertEqual(pns.version(ec), 'eb-%s' % EASYBUILD_VERSION) self.assertEqual(pns.release(ec), '1') - def test_package_fpm(self): - """Test package_fpm function.""" + def test_package(self): + """Test package function.""" init_config(build_options={'silent': True}) test_easyconfigs = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs') @@ -111,11 +111,13 @@ def test_package_fpm(self): # import needs to be done here, since test easyblocks are only included later from easybuild.easyblocks.toy import EB_toy - eb = EB_toy(ec) + easyblock = EB_toy(ec) # build & install first - eb.run_all_steps(False) - pkgdir = package_fpm(eb, 'rpm') + easyblock.run_all_steps(False) + + # package using default packaging configuration (FPM to build RPM packages) + pkgdir = package(easyblock) pkgfile = os.path.join(pkgdir, 'toy-0.0-gompi-1.3.12-test-eb-%s.1.rpm' % EASYBUILD_VERSION) self.assertTrue(os.path.isfile(pkgfile), "Found %s" % pkgfile) From 5c5e78b03d0eab8e3d59ec0f3149d23c586c1070 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 10 Jul 2015 20:47:34 +0200 Subject: [PATCH 44/51] add unit tests for --package* (+ --skip) --- easybuild/main.py | 7 ++++++ easybuild/tools/options.py | 12 +++------- easybuild/tools/package/utilities.py | 7 +++++- test/framework/options.py | 2 ++ test/framework/package.py | 23 +++++++++++++----- test/framework/toy_build.py | 35 ++++++++++++++++++++++++++++ 6 files changed, 70 insertions(+), 16 deletions(-) diff --git a/easybuild/main.py b/easybuild/main.py index c0f90b4b65..a5307a8c97 100755 --- a/easybuild/main.py +++ b/easybuild/main.py @@ -56,6 +56,7 @@ 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, dry_run, resolve_dependencies, search_easyconfigs +from easybuild.tools.package.utilities import check_pkg_support 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 @@ -210,6 +211,12 @@ def main(testing_data=(None, None, None)): config.init(options, config_options_dict) config.init_build_options(build_options=build_options, cmdline_options=options) + # check whether packaging is supported when it's being used + if options.package: + check_pkg_support() + else: + _log.debug("Packaging not enabled, so not check for packaging support.") + # 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 diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index f0cacb58c4..7dbfd59dd2 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -67,7 +67,7 @@ from easybuild.tools.modules import Lmod from easybuild.tools.ordereddict import OrderedDict from easybuild.tools.package.utilities import DEFAULT_PNS, PKG_TOOL_FPM, PKG_TYPE_RPM -from easybuild.tools.package.utilities import avail_package_naming_schemes, check_pkg_support +from easybuild.tools.package.utilities import avail_package_naming_schemes from easybuild.tools.toolchain.utilities import search_toolchain from easybuild.tools.repository.repository import avail_repositories from easybuild.tools.version import this_is_easybuild @@ -369,8 +369,8 @@ def package_options(self): opts = OrderedDict({ 'package': ("Enabling packaging", None, 'store_true', False), - 'package-tool': ("Packaging tool to use", None, 'store_or_None', PKG_TOOL_FPM), - 'package-type': ("Type of package to generate", None, 'store_or_None', PKG_TYPE_RPM), + 'package-tool': ("Packaging tool to use", None, 'store', PKG_TOOL_FPM), + 'package-type': ("Type of package to generate", None, 'store', PKG_TYPE_RPM), 'package-release': ("Package release iteration number", None, 'store', '1'), }) @@ -503,12 +503,6 @@ def postprocess(self): self._postprocess_config() - # check whether packaging is supported when it's being used - if self.options.package: - check_pkg_support() - else: - self.log.debug("Packaging not enabled, so not check for packaging support.") - def _postprocess_external_modules_metadata(self): """Parse file(s) specifying metadata for external modules.""" # leave external_modules_metadata untouched if no files are provided diff --git a/easybuild/tools/package/utilities.py b/easybuild/tools/package/utilities.py index 748c4f2d03..3e70c1c776 100644 --- a/easybuild/tools/package/utilities.py +++ b/easybuild/tools/package/utilities.py @@ -88,6 +88,7 @@ def package_with_fpm(easyblock): _log.info("Will be creating %s package(s) in %s", pkgtype, workdir) try: + origdir = os.getcwd() os.chdir(workdir) except OSError, err: raise EasyBuildError("Failed to chdir into workdir %s: %s", workdir, err) @@ -133,12 +134,16 @@ def package_with_fpm(easyblock): _log.info("Created %s package(s) in %s", pkgtype, workdir) + try: + os.chdir(origdir) + except OSError, err: + raise EasyBuildError("Failed to chdir back to %s: %s", origdir, err) + return workdir def check_pkg_support(): """Check whether packaging is supported, i.e. whether the required dependencies are available.""" - # packaging support is considered experimental for now (requires using --experimental) _log.experimental("Support for packaging installed software.") diff --git a/test/framework/options.py b/test/framework/options.py index 20bf8ca55a..0f6f7ce693 100644 --- a/test/framework/options.py +++ b/test/framework/options.py @@ -1815,6 +1815,8 @@ def test_include_toolchains(self): logtxt = read_file(self.logfile) self.assertTrue(tc_regex.search(logtxt), "Pattern '%s' *not* found in: %s" % (tc_regex.pattern, logtxt)) + def test_package(self): + """Test use of --package.""" def suite(): """ returns all the testcases in this module """ diff --git a/test/framework/package.py b/test/framework/package.py index e3892ebbce..53feaf504f 100644 --- a/test/framework/package.py +++ b/test/framework/package.py @@ -42,7 +42,7 @@ from easybuild.tools.version import VERSION as EASYBUILD_VERSION -MOCKED_FPM_RPM = """#!/bin/bash +MOCKED_FPM = """#!/bin/bash # only parse what we need to spit out the expected package file, ignore the rest workdir=`echo $@ | sed 's/--workdir \([^ ]*\).*/\\1/g'` name=`echo $@ | sed 's/.* --name \([^ ]*\).*/\\1/g'` @@ -54,6 +54,21 @@ """ +def mock_fpm(tmpdir): + """Put mocked version of fpm command in place in specified tmpdir.""" + # put mocked 'fpm' command in place, just for testing purposes + fpm = os.path.join(tmpdir, 'fpm') + write_file(fpm, MOCKED_FPM) + adjust_permissions(fpm, stat.S_IXUSR, add=True) + + # also put mocked rpmbuild in place + rpmbuild = os.path.join(tmpdir, 'rpmbuild') + write_file(rpmbuild, '#!/bin/bash') # only needs to be there, doesn't need to actually do something... + adjust_permissions(rpmbuild, stat.S_IXUSR, add=True) + + os.environ['PATH'] = '%s:%s' % (tmpdir, os.environ['PATH']) + + class PackageTest(EnhancedTestCase): """Tests for packaging support.""" @@ -103,11 +118,7 @@ def test_package(self): test_easyconfigs = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs') ec = EasyConfig(os.path.join(test_easyconfigs, 'toy-0.0-gompi-1.3.12-test.eb'), validate=False) - # put mocked 'fpm' command in place, just for testing purposes - fpm = os.path.join(self.test_prefix, 'fpm') - write_file(fpm, MOCKED_FPM_RPM) - adjust_permissions(fpm, stat.S_IXUSR, add=True) - os.environ['PATH'] = '%s:%s' % (self.test_prefix, os.environ['PATH']) + mock_fpm(self.test_prefix) # import needs to be done here, since test easyblocks are only included later from easybuild.easyblocks.toy import EB_toy diff --git a/test/framework/toy_build.py b/test/framework/toy_build.py index 25a7c7ad85..0f7414e593 100644 --- a/test/framework/toy_build.py +++ b/test/framework/toy_build.py @@ -36,6 +36,7 @@ import sys import tempfile from test.framework.utilities import EnhancedTestCase +from test.framework.package import mock_fpm from unittest import TestLoader from unittest import main as unittestmain from vsc.utils.fancylogger import setLogLevelDebug, logToScreen @@ -46,6 +47,7 @@ from easybuild.tools.config import get_module_syntax from easybuild.tools.filetools import mkdir, read_file, which, write_file from easybuild.tools.modules import modules_tool +from easybuild.tools.version import VERSION as EASYBUILD_VERSION class ToyBuildTest(EnhancedTestCase): @@ -970,6 +972,39 @@ def test_module_only(self): modtxt = read_file(toy_mod + '.lua') self.assertTrue(re.search('load.*ictce/4.1.13', modtxt), "load statement for ictce/4.1.13 found in module") + def test_package(self): + """Test use of --package and accompanying package configuration settings.""" + mock_fpm(self.test_prefix) + pkgpath = os.path.join(self.test_prefix, 'pkgs') + + extra_args = [ + '--experimental', + '--package', + '--package-release=321', + '--package-tool=fpm', + '--package-type=foo', + '--packagepath=%s' % pkgpath, + ] + + self.test_toy_build(extra_args=extra_args) + + toypkg = os.path.join(pkgpath, 'toy-0.0-eb-%s.321.foo' % EASYBUILD_VERSION) + self.assertTrue(os.path.exists(toypkg), "%s is there" % toypkg) + + def test_package_skip(self): + """Test use of --package with --skip.""" + mock_fpm(self.test_prefix) + pkgpath = os.path.join(self.test_prefix, 'packages') # default path + + self.test_toy_build(['--packagepath=%s' % pkgpath]) + self.assertFalse(os.path.exists(pkgpath), "%s is not created without use of --package" % pkgpath) + + self.test_toy_build(extra_args=['--experimental', '--package', '--skip'], verify=False) + + toypkg = os.path.join(pkgpath, 'toy-0.0-eb-%s.1.rpm' % EASYBUILD_VERSION) + self.assertTrue(os.path.exists(toypkg), "%s is there" % toypkg) + + def suite(): """ return all the tests in this file """ return TestLoader().loadTestsFromTestCase(ToyBuildTest) From 1d54b440921dc525cafb9b11fdbf9059da0f8f98 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 10 Jul 2015 23:55:44 +0200 Subject: [PATCH 45/51] move changing of permissions into a separate 'finalize' step, add unit test for --read-only-install and --group-writable-installdir --- easybuild/framework/easyblock.py | 70 +++++++++++++++++++------------- easybuild/main.py | 12 +++++- test/framework/toy_build.py | 27 +++++++++++- 3 files changed, 78 insertions(+), 31 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index cd82d9d28f..31ba2040c8 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -83,6 +83,7 @@ CONFIGURE_STEP = 'configure' EXTENSIONS_STEP = 'extensions' FETCH_STEP = 'fetch' +FINALIZE_STEP = 'finalize' MODULE_STEP = 'module' PACKAGE_STEP = 'package' PATCH_STEP = 'patch' @@ -1497,8 +1498,6 @@ def post_install_step(self): """ Do some postprocessing - run post install commands if any were specified - - set file permissions .... - Installing user must be member of the group that it is changed to """ if self.cfg['postinstallcmds'] is not None: # make sure we have a list of commands @@ -1509,32 +1508,6 @@ def post_install_step(self): raise EasyBuildError("Invalid element in 'postinstallcmds', not a string: %s", cmd) 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 - try: - perms = stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH - adjust_permissions(self.installdir, perms, add=False, recursive=True, group_id=self.group[1], - relative=True, ignore_errors=True) - except EasyBuildError, err: - raise EasyBuildError("Unable to change group permissions of file(s): %s", err) - self.log.info("Successfully made software only available for group %s (gid %s)" % self.group) - - if build_option('read_only_installdir'): - # remove write permissions for everyone - perms = stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH - adjust_permissions(self.installdir, perms, add=False, recursive=True, relative=True, ignore_errors=True) - self.log.info("Successfully removed write permissions recursively for *EVERYONE* on install dir.") - elif build_option('group_writable_installdir'): - # enable write permissions for group - perms = stat.S_IWGRP - adjust_permissions(self.installdir, perms, add=True, recursive=True, relative=True, ignore_errors=True) - self.log.info("Successfully enabled write permissions recursively for group on install dir.") - else: - # remove write permissions for group and other - perms = stat.S_IWGRP | stat.S_IWOTH - adjust_permissions(self.installdir, perms, add=False, recursive=True, relative=True, ignore_errors=True) - self.log.info("Successfully removed write permissions recursively for group/other on install dir.") - def sanity_check_step(self, custom_paths=None, custom_commands=None, extension=False): """ Do a sanity check on the installation @@ -1720,6 +1693,39 @@ def make_module_step(self, fake=False): return modpath + def finalize_step(self): + """ + Finalize installation procedure: adjust permissions as configured, change group ownership (if requested). + Installing user must be member of the group that it is changed to. + """ + if self.group is not None: + # remove permissions for others, and set group ID + try: + perms = stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH + adjust_permissions(self.installdir, perms, add=False, recursive=True, group_id=self.group[1], + relative=True, ignore_errors=True) + except EasyBuildError, err: + raise EasyBuildError("Unable to change group permissions of file(s): %s", err) + self.log.info("Successfully made software only available for group %s (gid %s)" % self.group) + + if build_option('read_only_installdir'): + # remove write permissions for everyone + perms = stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH + adjust_permissions(self.installdir, perms, add=False, recursive=True, relative=True, ignore_errors=True) + self.log.info("Successfully removed write permissions recursively for *EVERYONE* on install dir.") + + elif build_option('group_writable_installdir'): + # enable write permissions for group + perms = stat.S_IWGRP + adjust_permissions(self.installdir, perms, add=True, recursive=True, relative=True, ignore_errors=True) + self.log.info("Successfully enabled write permissions recursively for group on install dir.") + + else: + # remove write permissions for group and other + perms = stat.S_IWGRP | stat.S_IWOTH + adjust_permissions(self.installdir, perms, add=False, recursive=True, relative=True, ignore_errors=True) + self.log.info("Successfully removed write permissions recursively for group/other on install dir.") + def test_cases_step(self): """ Run provided test cases. @@ -1873,6 +1879,7 @@ def prepare_step_spec(initial): (SANITYCHECK_STEP, 'sanity checking', [lambda x: x.sanity_check_step()], False), (CLEANUP_STEP, 'cleaning up', [lambda x: x.cleanup_step()], False), (MODULE_STEP, 'creating module', [lambda x: x.make_module_step()], False), + (FINALIZE_STEP, 'finalizing', [lambda x: x.finalize_step()], False), ] # full list of steps, included iterated steps @@ -1986,6 +1993,9 @@ def build_and_install_one(ecdict, init_env): new_log_dir = os.path.dirname(app.logfile) else: new_log_dir = os.path.join(app.installdir, config.log_path()) + if build_option('read_only_installdir'): + # temporarily re-enable write permissions for copying log/easyconfig to install dir + adjust_permissions(new_log_dir, stat.S_IWUSR, add=True, recursive=False) # collect build stats _log.info("Collecting build stats...") @@ -2027,6 +2037,10 @@ def build_and_install_one(ecdict, init_env): except (IOError, OSError), err: print_error("Failed to copy easyconfig %s to %s: %s" % (spec, newspec, err)) + if build_option('read_only_installdir'): + # take away user write permissions (again) + adjust_permissions(new_log_dir, stat.S_IWUSR, add=False, recursive=False) + # build failed else: success = False diff --git a/easybuild/main.py b/easybuild/main.py index 0c42cd6a2e..7d1a5b627b 100644 --- a/easybuild/main.py +++ b/easybuild/main.py @@ -37,6 +37,7 @@ """ import copy import os +import stat import sys import traceback @@ -52,7 +53,7 @@ from easybuild.framework.easyconfig.tools import get_paths_for, parse_easyconfigs, skip_available 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.filetools import adjust_permissions, cleanup, write_file from easybuild.tools.options import process_software_build_specs from easybuild.tools.robot import det_robot_path, dry_run, resolve_dependencies, search_easyconfigs from easybuild.tools.parallelbuild import submit_jobs @@ -128,7 +129,14 @@ def build_and_install_software(ecs, init_session_state, exit_on_failure=True): 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) + parent_dir = os.path.dirname(test_report_fp) + # parent dir for test report may not be writeable at this time, e.g. when --read-only-installdir is used + if os.stat(parent_dir).st_mode & 0200: + write_file(test_report_fp, test_report_txt) + else: + adjust_permissions(parent_dir, stat.S_IWUSR, add=True, recursive=False) + write_file(test_report_fp, test_report_txt) + adjust_permissions(parent_dir, stat.S_IWUSR, add=False, recursive=False) if not ec_res['success'] and exit_on_failure: if 'traceback' in ec_res: diff --git a/test/framework/toy_build.py b/test/framework/toy_build.py index 46797db2d1..31c454e0a0 100644 --- a/test/framework/toy_build.py +++ b/test/framework/toy_build.py @@ -44,7 +44,7 @@ from easybuild.framework.easyconfig.easyconfig import EasyConfig from easybuild.tools.build_log import EasyBuildError from easybuild.tools.config import get_module_syntax -from easybuild.tools.filetools import mkdir, read_file, which, write_file +from easybuild.tools.filetools import adjust_permissions, mkdir, read_file, which, write_file from easybuild.tools.modules import modules_tool @@ -480,6 +480,31 @@ def test_toy_permissions(self): # restore original umask os.umask(orig_umask) + def test_toy_permissions_installdir(self): + """Test --read-only-installdir and --group-write-installdir.""" + # set umask hard to verify default reliably + orig_umask = os.umask(0022) + + self.test_toy_build() + installdir_perms = os.stat(os.path.join(self.test_installpath, 'software', 'toy', '0.0')).st_mode & 0777 + self.assertEqual(installdir_perms, 0755, "%s has default permissions" % self.test_installpath) + shutil.rmtree(self.test_installpath) + + self.test_toy_build(extra_args=['--read-only-installdir']) + installdir_perms = os.stat(os.path.join(self.test_installpath, 'software', 'toy', '0.0')).st_mode & 0777 + self.assertEqual(installdir_perms, 0555, "%s has read-only permissions" % self.test_installpath) + installdir_perms = os.stat(os.path.join(self.test_installpath, 'software', 'toy')).st_mode & 0777 + self.assertEqual(installdir_perms, 0755, "%s has default permissions" % self.test_installpath) + adjust_permissions(os.path.join(self.test_installpath, 'software', 'toy', '0.0'), stat.S_IWUSR, add=True) + shutil.rmtree(self.test_installpath) + + self.test_toy_build(extra_args=['--group-writable-installdir']) + installdir_perms = os.stat(os.path.join(self.test_installpath, 'software', 'toy', '0.0')).st_mode & 0777 + self.assertEqual(installdir_perms, 0775, "%s has group write permissions" % self.test_installpath) + + # restore original umask + os.umask(orig_umask) + def test_toy_gid_sticky_bits(self): """Test setting gid and sticky bits.""" subdirs = [ From ef6b3bd4048265549f76276fbc43471a262a71af Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 10 Jul 2015 23:59:49 +0200 Subject: [PATCH 46/51] fix typo --- easybuild/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/main.py b/easybuild/main.py index 7d1a5b627b..4b542b4b1b 100644 --- a/easybuild/main.py +++ b/easybuild/main.py @@ -130,7 +130,7 @@ def build_and_install_software(ecs, init_session_state, exit_on_failure=True): if 'log_file' in ec_res: test_report_fp = "%s_test_report.md" % '.'.join(ec_res['log_file'].split('.')[:-1]) parent_dir = os.path.dirname(test_report_fp) - # parent dir for test report may not be writeable at this time, e.g. when --read-only-installdir is used + # parent dir for test report may not be writable at this time, e.g. when --read-only-installdir is used if os.stat(parent_dir).st_mode & 0200: write_file(test_report_fp, test_report_txt) else: From a7ce2c18d7a4dbebaf6135478b83cae0bb271edf Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Sat, 11 Jul 2015 00:15:17 +0200 Subject: [PATCH 47/51] dev -> ~dev --- .../tools/package/package_naming_scheme/easybuild_pns.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/easybuild/tools/package/package_naming_scheme/easybuild_pns.py b/easybuild/tools/package/package_naming_scheme/easybuild_pns.py index d1990de6d9..d6698b551e 100644 --- a/easybuild/tools/package/package_naming_scheme/easybuild_pns.py +++ b/easybuild/tools/package/package_naming_scheme/easybuild_pns.py @@ -43,4 +43,11 @@ def name(self, ec): def version(self, ec): """Determine package version: EasyBuild version used to build & install.""" - return 'eb-%s' % EASYBUILD_VERSION + ebver = str(EASYBUILD_VERSION) + if ebver.endswith('dev'): + # try and make sure that 'dev' EasyBuild version is not considered newer just because it's longer + # (e.g., 2.2.0 vs 2.2.0dev) + # cfr. http://rpm.org/ticket/56, + # https://debian-handbook.info/browse/stable/sect.manipulating-packages-with-dpkg.html (see box in 5.4.3) + ebver.replace('dev', '~dev') + return 'eb-%s' % ebver From 79955a914ca98a12e15465ae813faea3828a2f43 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Sat, 11 Jul 2015 00:23:55 +0200 Subject: [PATCH 48/51] drop empty test --- test/framework/options.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/framework/options.py b/test/framework/options.py index 0f6f7ce693..20bf8ca55a 100644 --- a/test/framework/options.py +++ b/test/framework/options.py @@ -1815,8 +1815,6 @@ def test_include_toolchains(self): logtxt = read_file(self.logfile) self.assertTrue(tc_regex.search(logtxt), "Pattern '%s' *not* found in: %s" % (tc_regex.pattern, logtxt)) - def test_package(self): - """Test use of --package.""" def suite(): """ returns all the testcases in this module """ From 7ee1bad2f7deb29260587fbd1bf209c34b74a300 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Sat, 11 Jul 2015 00:37:30 +0200 Subject: [PATCH 49/51] rename to permissions_step --- easybuild/framework/easyblock.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 31ba2040c8..ef13e0e83b 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -83,10 +83,10 @@ CONFIGURE_STEP = 'configure' EXTENSIONS_STEP = 'extensions' FETCH_STEP = 'fetch' -FINALIZE_STEP = 'finalize' MODULE_STEP = 'module' PACKAGE_STEP = 'package' PATCH_STEP = 'patch' +PERMISSIONS_STEP = 'permissions' POSTPROC_STEP = 'postproc' PREPARE_STEP = 'prepare' READY_STEP = 'ready' @@ -1693,7 +1693,7 @@ def make_module_step(self, fake=False): return modpath - def finalize_step(self): + def permissions_step(self): """ Finalize installation procedure: adjust permissions as configured, change group ownership (if requested). Installing user must be member of the group that it is changed to. @@ -1879,7 +1879,7 @@ def prepare_step_spec(initial): (SANITYCHECK_STEP, 'sanity checking', [lambda x: x.sanity_check_step()], False), (CLEANUP_STEP, 'cleaning up', [lambda x: x.cleanup_step()], False), (MODULE_STEP, 'creating module', [lambda x: x.make_module_step()], False), - (FINALIZE_STEP, 'finalizing', [lambda x: x.finalize_step()], False), + (PERMISSIONS_STEP, 'permissions', [lambda x: x.permissions_step()], False), ] # full list of steps, included iterated steps From e622c2c0f30198136fe5a7b251306871b330ecd7 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Sat, 11 Jul 2015 01:20:39 +0200 Subject: [PATCH 50/51] include tools.package packages in setup.py --- setup.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index edf1ff7655..cb9af5cc78 100644 --- a/setup.py +++ b/setup.py @@ -73,8 +73,9 @@ def find_rel_test(): "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.tools.deprecated", - "easybuild.tools.job", "easybuild.tools.toolchain", "easybuild.tools.module_naming_scheme", "easybuild.tools.repository", - "test.framework", "test", + "easybuild.tools.job", "easybuild.tools.toolchain", "easybuild.tools.module_naming_scheme", + "easybuild.tools.package", "easybuild.tools.package.package_naming_scheme", + "easybuild.tools.repository", "test.framework", "test", ] setup( From 9cf1d2ee9b6637b96aa9e1b80ef1c4d9c2691a73 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Sat, 11 Jul 2015 10:41:54 +0200 Subject: [PATCH 51/51] final style tweaks to packaging support --- easybuild/framework/easyblock.py | 2 +- easybuild/main.py | 2 +- easybuild/tools/config.py | 14 +++++++++++--- easybuild/tools/options.py | 10 +++++----- .../tools/package/package_naming_scheme/pns.py | 3 ++- easybuild/tools/package/utilities.py | 9 ++------- 6 files changed, 22 insertions(+), 18 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index bd5f4cdc0b..8a7d06535c 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -71,7 +71,7 @@ from easybuild.tools.module_naming_scheme.utilities import det_full_ec_version from easybuild.tools.modules import ROOT_ENV_VAR_NAME_PREFIX, VERSION_ENV_VAR_NAME_PREFIX, DEVEL_ENV_VAR_NAME_PREFIX from easybuild.tools.modules import get_software_root, modules_tool -from easybuild.tools.package.utilities import PKG_TOOL_FPM, package +from easybuild.tools.package.utilities import package from easybuild.tools.repository.repository import init_repository from easybuild.tools.toolchain import DUMMY_TOOLCHAIN_NAME from easybuild.tools.systemtools import det_parallelism, use_group diff --git a/easybuild/main.py b/easybuild/main.py index a5307a8c97..67aefd0f4c 100755 --- a/easybuild/main.py +++ b/easybuild/main.py @@ -215,7 +215,7 @@ def main(testing_data=(None, None, None)): if options.package: check_pkg_support() else: - _log.debug("Packaging not enabled, so not check for packaging support.") + _log.debug("Packaging not enabled, so not checking for packaging support.") # update session state eb_config = eb_go.generate_cmd_line(add_default=True) diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py index 1dd1e16b5f..63986efe59 100644 --- a/easybuild/tools/config.py +++ b/easybuild/tools/config.py @@ -52,6 +52,10 @@ _log = fancylogger.getLogger('config', fname=False) +PKG_TOOL_FPM = 'fpm' +PKG_TYPE_RPM = 'rpm' + + DEFAULT_JOB_BACKEND = 'PbsPython' DEFAULT_LOGFILE_FORMAT = ("easybuild", "easybuild-%(name)s-%(version)s-%(date)s.%(time)s.log") DEFAULT_MNS = 'EasyBuildMNS' @@ -66,6 +70,10 @@ 'subdir_modules': 'modules', 'subdir_software': 'software', } +DEFAULT_PKG_RELEASE = '1' +DEFAULT_PKG_TOOL = PKG_TOOL_FPM +DEFAULT_PKG_TYPE = PKG_TYPE_RPM +DEFAULT_PNS = 'EasyBuildPNS' DEFAULT_PREFIX = os.path.join(os.path.expanduser('~'), ".local", "easybuild") DEFAULT_REPOSITORY = 'FileRepository' DEFAULT_STRICT = run.WARN @@ -132,13 +140,13 @@ def mk_full_default_path(name, prefix=DEFAULT_PREFIX): DEFAULT_STRICT: [ 'strict', ], - '1': [ + DEFAULT_PKG_RELEASE: [ 'package_release', ], - 'fpm': [ + DEFAULT_PKG_TOOL: [ 'package_tool', ], - 'rpm': [ + DEFAULT_PKG_TYPE: [ 'package_type', ], } diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index 7dbfd59dd2..c585ae103e 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -52,7 +52,8 @@ from easybuild.tools import build_log, run # build_log should always stay there, to ensure EasyBuildLog from easybuild.tools.build_log import EasyBuildError, raise_easybuilderror from easybuild.tools.config import DEFAULT_JOB_BACKEND, DEFAULT_LOGFILE_FORMAT, DEFAULT_MNS, DEFAULT_MODULE_SYNTAX -from easybuild.tools.config import DEFAULT_MODULES_TOOL, DEFAULT_MODULECLASSES, DEFAULT_PATH_SUBDIRS, DEFAULT_PREFIX +from easybuild.tools.config import DEFAULT_MODULES_TOOL, DEFAULT_MODULECLASSES, DEFAULT_PATH_SUBDIRS +from easybuild.tools.config import DEFAULT_PKG_RELEASE, DEFAULT_PKG_TOOL, DEFAULT_PKG_TYPE, DEFAULT_PNS, DEFAULT_PREFIX from easybuild.tools.config import DEFAULT_REPOSITORY, DEFAULT_STRICT from easybuild.tools.config import get_pretend_installpath, mk_full_default_path, set_tmpdir from easybuild.tools.configobj import ConfigObj, ConfigObjError @@ -66,7 +67,6 @@ from easybuild.tools.module_naming_scheme.utilities import avail_module_naming_schemes from easybuild.tools.modules import Lmod from easybuild.tools.ordereddict import OrderedDict -from easybuild.tools.package.utilities import DEFAULT_PNS, PKG_TOOL_FPM, PKG_TYPE_RPM from easybuild.tools.package.utilities import avail_package_naming_schemes from easybuild.tools.toolchain.utilities import search_toolchain from easybuild.tools.repository.repository import avail_repositories @@ -369,9 +369,9 @@ def package_options(self): opts = OrderedDict({ 'package': ("Enabling packaging", None, 'store_true', False), - 'package-tool': ("Packaging tool to use", None, 'store', PKG_TOOL_FPM), - 'package-type': ("Type of package to generate", None, 'store', PKG_TYPE_RPM), - 'package-release': ("Package release iteration number", None, 'store', '1'), + 'package-tool': ("Packaging tool to use", None, 'store', DEFAULT_PKG_TOOL), + 'package-type': ("Type of package to generate", None, 'store', DEFAULT_PKG_TYPE), + 'package-release': ("Package release iteration number", None, 'store', DEFAULT_PKG_RELEASE), }) self.log.debug("package_options: descr %s opts %s" % (descr, opts)) diff --git a/easybuild/tools/package/package_naming_scheme/pns.py b/easybuild/tools/package/package_naming_scheme/pns.py index deb81c3909..d66bce2262 100644 --- a/easybuild/tools/package/package_naming_scheme/pns.py +++ b/easybuild/tools/package/package_naming_scheme/pns.py @@ -48,9 +48,10 @@ def name(self, ec): """Determine package name""" pass + @abstractmethod def version(self, ec): """Determine package version.""" - return ec['version'] + pass def release(self, ec=None): """Determine package release""" diff --git a/easybuild/tools/package/utilities.py b/easybuild/tools/package/utilities.py index 3e70c1c776..a3d559bcc8 100644 --- a/easybuild/tools/package/utilities.py +++ b/easybuild/tools/package/utilities.py @@ -26,7 +26,7 @@ """ Various utilities related to packaging support. -@author: Marc Litherland +@author: Marc Litherland (Novartis) @author: Gianluca Santarossa (Novartis) @author: Robert Schmidt (Ottawa Hospital Research Institute) @author: Fotis Georgatos (Uni.Lu, NTUA) @@ -40,7 +40,7 @@ from vsc.utils.missing import get_subclasses from vsc.utils.patterns import Singleton -from easybuild.tools.config import build_option, get_package_naming_scheme +from easybuild.tools.config import PKG_TOOL_FPM, PKG_TYPE_RPM, build_option, get_package_naming_scheme from easybuild.tools.build_log import EasyBuildError from easybuild.tools.filetools import which from easybuild.tools.package.package_naming_scheme.pns import PackageNamingScheme @@ -49,11 +49,6 @@ from easybuild.tools.utilities import import_available_modules -DEFAULT_PNS = 'EasyBuildPNS' -PKG_TOOL_FPM = 'fpm' -PKG_TYPE_RPM = 'rpm' - - _log = fancylogger.getLogger('tools.package')