Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make builddependencies iterable. #2741

Merged
merged 33 commits into from
Mar 15, 2019
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
08adff3
Add iterate_builddependencies easyconfig option.
bartoldeman Jan 30, 2019
e723d4b
Make docstring raw to make Hound-ci happy.
bartoldeman Jan 30, 2019
fc0b990
Fix dump of exts_list by special casing iterate_builddependencies.
bartoldeman Jan 31, 2019
26259c7
Modify toy-0.0-iter dep to have both easyconfig and module available.
bartoldeman Jan 31, 2019
66ce0c7
Restore initial environment when resetting the environment.
bartoldeman Jan 31, 2019
8c91557
In reset environment for iterate reset to the env first encountered h…
bartoldeman Jan 31, 2019
8dd69f3
Avoid temporary modification of builddeps for _finalize_dependencies()
bartoldeman Jan 31, 2019
5c3beb2
Merge iterate_builddependencies into DEPENDENCY_PARAMETERS.
bartoldeman Jan 31, 2019
a22dfb2
Fix typo in parced.
bartoldeman Jan 31, 2019
f976245
Fix check for iterate_* keys in validate_iterate_opts_lists
bartoldeman Jan 31, 2019
f074e51
Keep iterate_builddependencies completely separate from builddependen…
bartoldeman Jan 31, 2019
f5def9e
Use flatten function instead of list comprehension to flatten.
bartoldeman Jan 31, 2019
3bf5873
Properly unload modules for iterate instead of messing with the envir…
bartoldeman Jan 31, 2019
641421b
Make Hound happy about comment.
bartoldeman Jan 31, 2019
d9f06e3
Merge remote-tracking branch 'github_hpcugent/develop' into iterable-…
bartoldeman Feb 11, 2019
ed6f73f
Iterate builddependencies instead of iterate_builddependencies.
bartoldeman Feb 14, 2019
8d6f3bb
Hound-ci changes.
bartoldeman Feb 14, 2019
b51ad11
yaml easyconfigs builddeps already a list of dicts, fixing that.
bartoldeman Feb 15, 2019
fcdc90b
add & use EasyConfig.get_ref method + code cleanup & minor style fixes
boegel Feb 20, 2019
96b0df0
Merge pull request #14 from boegel/iterable-builddep
bartoldeman Feb 20, 2019
4ff5722
Restore environment when iterating instead of use modules.
bartoldeman Mar 1, 2019
aa29163
Introduce "iterating" easyconfig field.
bartoldeman Mar 14, 2019
72c48cd
Correct boolean check for iterating.
bartoldeman Mar 15, 2019
e8b393b
Remove duplicates in flattened lists of builddependencies.
bartoldeman Mar 15, 2019
b0579ad
Fix typo in parameters spotted by @boegel.
bartoldeman Mar 15, 2019
248dc25
Add comment about reset_environ.
bartoldeman Mar 15, 2019
bddaa0d
rename 'EasyBlock.reset_changes' to 'EasyBlock.reset_env'
boegel Mar 15, 2019
7c34a92
use 'nub' to filter out duplicates from flattened list of build depen…
boegel Mar 15, 2019
8e4299b
Merge pull request #15 from boegel/iterable-builddep
bartoldeman Mar 15, 2019
96c253b
can't use nub to filter out duplicates in flattened list of build dep…
boegel Mar 15, 2019
e9bcc8f
Merge pull request #16 from boegel/iterable-builddep
bartoldeman Mar 15, 2019
4945275
fix filtering of duplicates in builddependencies method (@boegel shou…
boegel Mar 15, 2019
33c46df
Merge pull request #17 from boegel/iterable-builddep
bartoldeman Mar 15, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 34 additions & 18 deletions easybuild/framework/easyblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ def __init__(self, ec):

# keep track of initial environment we start in, so we can restore it if needed
self.initial_environ = copy.deepcopy(os.environ)
self.reset_environ = None
self.tweaked_env_vars = {}

# should we keep quiet?
Expand Down Expand Up @@ -863,10 +864,22 @@ def make_builddir(self):
# otherwise we wipe the already partially populated installation directory,
# see https://github.com/easybuilders/easybuild-framework/issues/2556
if not (self.build_in_installdir and self.iter_idx > 0):
# make sure we no longer sit in the build directory before cleaning it.
change_dir(self.orig_workdir)
self.make_dir(self.builddir, self.cfg['cleanupoldbuild'])

trace_msg("build dir: %s" % self.builddir)

def reset_changes(self):
boegel marked this conversation as resolved.
Show resolved Hide resolved
"""
Reset environment
"""
env.reset_changes()
if self.reset_environ is None:
self.reset_environ = copy.deepcopy(os.environ)
bartoldeman marked this conversation as resolved.
Show resolved Hide resolved
else:
restore_env(self.reset_environ)

def gen_installdir(self):
"""
Generate the name of the installation directory.
Expand Down Expand Up @@ -1478,7 +1491,11 @@ def handle_iterate_opts(self):
# this will only be done during first iteration, since after that the options won't be lists anymore
for opt in ITERATE_OPTIONS:
# keep track of list, supply first element as first option to handle
if isinstance(self.cfg[opt], (list, tuple)):
if isinstance(self.cfg[opt], (list, tuple)) and self.cfg[opt] != [[]]:
if opt.startswith('iterate_') and self.iter_idx == 0:
# save original common value (e.g. builddependencies)
commonopt = opt[len('iterate_'):]
self.iter_opts[commonopt] = self.cfg[commonopt]
self.iter_opts[opt] = self.cfg[opt] # copy
self.log.debug("Found list for %s: %s", opt, self.iter_opts[opt])

Expand All @@ -1487,11 +1504,15 @@ def handle_iterate_opts(self):

# pop first element from all *_list options as next value to use
for opt in self.iter_opts:
if len(self.iter_opts[opt]) > self.iter_idx:
self.cfg[opt] = self.iter_opts[opt][self.iter_idx]
else:
self.cfg[opt] = '' # empty list => empty option as next value
self.log.debug("Next value for %s: %s" % (opt, str(self.cfg[opt])))
if opt in ITERATE_OPTIONS:
if opt.startswith('iterate_'):
commonopt = opt[len('iterate_'):]
self.cfg[commonopt] = self.iter_opts[opt][self.iter_idx] + self.iter_opts[commonopt]
elif len(self.iter_opts[opt]) > self.iter_idx:
self.cfg[opt] = self.iter_opts[opt][self.iter_idx]
else:
self.cfg[opt] = '' # empty list => empty option as next value
self.log.debug("Next value for %s: %s" % (opt, str(self.cfg[opt])))

# re-enable templating before self.cfg values are used
self.cfg.enable_templating = True
Expand Down Expand Up @@ -1822,13 +1843,15 @@ def prepare_step(self, start_dir=True):

# list of paths to include in RPATH filter;
# only include builddir if we're not building in installation directory
self.rpath_filter_dirs = []
self.rpath_filter_dirs.append(tempfile.gettempdir())
if not self.build_in_installdir:
self.rpath_filter_dirs.append(self.builddir)

# always include '<installdir>/lib', '<installdir>/lib64', $ORIGIN, $ORIGIN/../lib and $ORIGIN/../lib64
# $ORIGIN will be resolved by the loader to be the full path to the executable or shared object
# see also https://linux.die.net/man/8/ld-linux;
self.rpath_include_dirs = []
self.rpath_include_dirs.append(os.path.join(self.installdir, 'lib'))
self.rpath_include_dirs.append(os.path.join(self.installdir, 'lib64'))
self.rpath_include_dirs.append('$ORIGIN')
Expand Down Expand Up @@ -2660,7 +2683,7 @@ def get_step(tag, descr, substeps, skippable, initial=True):
ready_substeps = [
(False, lambda x: x.check_readiness_step),
(True, lambda x: x.make_builddir),
(True, lambda x: env.reset_changes),
(True, lambda x: x.reset_changes),
(True, lambda x: x.handle_iterate_opts),
]

Expand All @@ -2678,14 +2701,6 @@ def source_step_spec(initial):
"""Return source step specified."""
return get_step(SOURCE_STEP, "unpacking", source_substeps, True, initial=initial)

def prepare_step_spec(initial):
"""Return prepare step specification."""
if initial:
substeps = [lambda x: x.prepare_step]
else:
substeps = [lambda x: x.guess_start_dir]
return (PREPARE_STEP, 'preparing', substeps, False)

install_substeps = [
(False, lambda x: x.stage_install_step),
(False, lambda x: x.make_installdir),
Expand All @@ -2696,10 +2711,11 @@ def install_step_spec(initial):
"""Return install step specification."""
return get_step(INSTALL_STEP, "installing", install_substeps, True, initial=initial)

# format for step specifications: (stop_name: (description, list of functions, skippable))
# format for step specifications: (step_name, description, list of functions, skippable)

# core steps that are part of the iterated loop
patch_step_spec = (PATCH_STEP, 'patching', [lambda x: x.patch_step], True)
prepare_step_spec = (PREPARE_STEP, 'preparing', [lambda x: x.prepare_step], False)
configure_step_spec = (CONFIGURE_STEP, 'configuring', [lambda x: x.configure_step], True)
build_step_spec = (BUILD_STEP, 'building', [lambda x: x.build_step], True)
test_step_spec = (TEST_STEP, 'testing', [lambda x: x.test_step], True)
Expand All @@ -2710,7 +2726,7 @@ def install_step_spec(initial):
ready_step_spec(True),
source_step_spec(True),
patch_step_spec,
prepare_step_spec(True),
prepare_step_spec,
configure_step_spec,
build_step_spec,
test_step_spec,
Expand All @@ -2723,7 +2739,7 @@ def install_step_spec(initial):
ready_step_spec(False),
source_step_spec(False),
patch_step_spec,
prepare_step_spec(False),
prepare_step_spec,
configure_step_spec,
build_step_spec,
test_step_spec,
Expand Down
1 change: 1 addition & 0 deletions easybuild/framework/easyconfig/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@
# DEPENDENCIES easyconfig parameters
'allow_system_deps': [[], "Allow listed system dependencies (format: (<name>, <version>))", DEPENDENCIES],
'builddependencies': [[], "List of build dependencies", DEPENDENCIES],
'iterate_builddependencies': [[[]], "Iterated list of list of build dependencies", DEPENDENCIES],
'dependencies': [[], "List of dependencies", DEPENDENCIES],
'hiddendependencies': [[], "List of dependencies available as hidden modules", DEPENDENCIES],
'osdependencies': [[], "OS dependencies that should be present on the system", DEPENDENCIES],
Expand Down
18 changes: 13 additions & 5 deletions easybuild/framework/easyconfig/easyconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@
MANDATORY_PARAMS = ['name', 'version', 'homepage', 'description', 'toolchain']

# set of configure/build/install options that can be provided as lists for an iterated build
ITERATE_OPTIONS = ['preconfigopts', 'configopts', 'prebuildopts', 'buildopts', 'preinstallopts', 'installopts']
ITERATE_OPTIONS = ['iterate_builddependencies',
'preconfigopts', 'configopts', 'prebuildopts', 'buildopts', 'preinstallopts', 'installopts']

# name of easyconfigs archive subdirectory
EASYCONFIGS_ARCHIVE_DIR = '__archive__'
Expand Down Expand Up @@ -174,7 +175,7 @@ def det_subtoolchain_version(current_tc, subtoolchain_name, optional_toolchains,

@toolchain_hierarchy_cache
def get_toolchain_hierarchy(parent_toolchain, incl_capabilities=False):
"""
r"""
Determine list of subtoolchains for specified parent toolchain.
Result starts with the most minimal subtoolchains first, ends with specified toolchain.

Expand Down Expand Up @@ -543,6 +544,8 @@ def parse(self):
# it's important that templating is still disabled at this stage!
self.log.info("Parsing dependency specifications...")
self['builddependencies'] = [self._parse_dependency(dep, build_only=True) for dep in self['builddependencies']]
self['iterate_builddependencies'] = [[self._parse_dependency(dep, build_only=True) for dep in x]
for x in self['iterate_builddependencies']]
self['dependencies'] = [self._parse_dependency(dep) for dep in self['dependencies']]
self['hiddendependencies'] = [self._parse_dependency(dep, hidden=True) for dep in self['hiddendependencies']]

Expand Down Expand Up @@ -665,7 +668,7 @@ def validate_iterate_opts_lists(self):
raise EasyBuildError("%s not available in self.cfg (anymore)?!", opt)

# keep track of list, supply first element as first option to handle
if isinstance(self[opt], (list, tuple)):
if isinstance(self[opt], (list, tuple)) or opt.endswith('_iteropt'):
opt_counts.append((opt, len(self[opt])))

# make sure that options that specify lists have the same length
Expand Down Expand Up @@ -1096,10 +1099,15 @@ def _finalize_dependencies(self):
for key in DEPENDENCY_PARAMETERS:
# loop over a *copy* of dependency dicts (with resolved templates);
# to update the original dep dict, we need to index with idx into self._config[key][0]...
for idx, dep in enumerate(self[key]):
val = self[key]
orig_val = self._config[key][0]
if key == 'iterate_builddependencies':
val = [dep for deps in val for dep in deps]
orig_val = [dep for deps in orig_val for dep in deps]
for idx, dep in enumerate(val):

# reference to original dep dict, this is the one we should be updating
orig_dep = self._config[key][0][idx]
orig_dep = orig_val[idx]

if filter_deps and orig_dep['name'] in filter_deps:
self.log.debug("Skipping filtered dependency %s when finalising dependencies", orig_dep['name'])
Expand Down
2 changes: 1 addition & 1 deletion easybuild/framework/easyconfig/format/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
FORMAT_VERSION_REGEXP = re.compile(r'^#\s+%s\s*(?P<major>\d+)\.(?P<minor>\d+)\s*$' % FORMAT_VERSION_KEYWORD, re.M)
FORMAT_DEFAULT_VERSION = EasyVersion('1.0')

DEPENDENCY_PARAMETERS = ['builddependencies', 'dependencies', 'hiddendependencies']
DEPENDENCY_PARAMETERS = ['iterate_builddependencies', 'builddependencies', 'dependencies', 'hiddendependencies']

# values for these keys will not be templated in dump()
EXCLUDED_KEYS_REPLACE_TEMPLATES = ['description', 'easyblock', 'exts_list', 'homepage', 'name', 'toolchain',
Expand Down
14 changes: 12 additions & 2 deletions easybuild/framework/easyconfig/format/one.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
# dependency parameters always need to be reformatted, to correctly deal with dumping parsed dependencies
REFORMAT_FORCED_PARAMS = ['sanity_check_paths'] + DEPENDENCY_PARAMETERS
REFORMAT_SKIPPED_PARAMS = ['toolchain', 'toolchainopts']
REFORMAT_LIST_OF_LISTS_OF_TUPLES = ['iterate_builddependencies']
REFORMAT_THRESHOLD_LENGTH = 100 # only reformat lines that would be longer than this amount of characters
REFORMAT_ORDERED_ITEM_KEYS = {
'sanity_check_paths': ['files', 'dirs'],
Expand Down Expand Up @@ -140,6 +141,7 @@ def _reformat_line(self, param_name, param_val, outer=False, addlen=0):
# note: this does not take into account the parameter name + '=', only the value
line_too_long = len(param_strval) + addlen > REFORMAT_THRESHOLD_LENGTH
forced = param_name in REFORMAT_FORCED_PARAMS
list_of_lists_of_tuples = param_name in REFORMAT_LIST_OF_LISTS_OF_TUPLES

if param_name in REFORMAT_SKIPPED_PARAMS:
self.log.info("Skipping reformatting value for parameter '%s'", param_name)
Expand Down Expand Up @@ -170,9 +172,15 @@ def _reformat_line(self, param_name, param_val, outer=False, addlen=0):
for item in param_val:
comment = self._get_item_comments(param_name, item).get(str(item), '')
addlen = addlen + len(INDENT_4SPACES) + len(comment)
if isinstance(item, list) and list_of_lists_of_tuples:
itemstr = '[' + (",\n " + INDENT_4SPACES).join([
self._reformat_line(param_name, subitem, outer=True, addlen=addlen)
for subitem in item]) + ']'
else:
itemstr = self._reformat_line(param_name, item, addlen=addlen)
res += item_tmpl % {
'comment': comment,
'item': self._reformat_line(param_name, item, addlen=addlen)
'item': itemstr
}

# end with closing character: ], ), }
Expand Down Expand Up @@ -234,7 +242,9 @@ def _find_defined_params(self, ecfg, keyset, default_values, templ_const, templ_

if val != default_values[key]:
# dependency easyconfig parameters were parsed, so these need special care to 'unparse' them
if key in DEPENDENCY_PARAMETERS:
if key == 'iterate_builddependencies':
valstr = [[dump_dependency(d, ecfg['toolchain']) for d in dep] for dep in val]
elif key in DEPENDENCY_PARAMETERS:
valstr = [dump_dependency(d, ecfg['toolchain']) for d in val]
elif key == 'toolchain':
valstr = "{'name': '%(name)s', 'version': '%(version)s'}" % ecfg[key]
Expand Down
9 changes: 7 additions & 2 deletions easybuild/framework/easyconfig/tweak.py
Original file line number Diff line number Diff line change
Expand Up @@ -832,9 +832,14 @@ def map_easyconfig_to_target_tc_hierarchy(ec_spec, toolchain_mapping, targetdir=
for key in DEPENDENCY_PARAMETERS:
# loop over a *copy* of dependency dicts (with resolved templates);
# to update the original dep dict, we need to index with idx into self._config[key][0]...
for idx, dep in enumerate(parsed_ec['ec'][key]):
val = parced_ec['ec'][key]
boegel marked this conversation as resolved.
Show resolved Hide resolved
orig_val = parced_ec['ec']._config[key][0]
boegel marked this conversation as resolved.
Show resolved Hide resolved
if key == 'iterate_builddependencies':
val = [dep for deps in val for dep in deps]
orig_val = [dep for deps in orig_val for dep in deps]
for idx, dep in enumerate(val):
# reference to original dep dict, this is the one we should be updating
orig_dep = parsed_ec['ec']._config[key][0][idx]
orig_dep = orig_val[idx]
# skip dependencies that are marked as external modules
if dep['external_module']:
continue
Expand Down
15 changes: 11 additions & 4 deletions test/framework/easyconfigs/test_ecs/t/toy/toy-0.0-iter.eb
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,26 @@ versionsuffix = '-iter'
homepage = 'https://easybuilders.github.io/easybuild'
description = "Toy C program, 100% toy."

toolchain = {'name': 'dummy', 'version': 'dummy'}
toolchain = {'name': 'dummy', 'version': ''}

sources = [SOURCE_TAR_GZ]
patches = ['toy-0.0_fix-silly-typo-in-printf-statement.patch']

iterate_builddependencies = [
[('GCC', '6.4.0-2.28'),
('OpenMPI', '2.1.2', '', ('GCC', '6.4.0-2.28'))],
[('GCC', '6.4.0-2.28')],
[('GCC', '7.3.0-2.30')],
]

buildopts = [
'',
"-O2; mv %(name)s toy_O2",
"-O1; mv %(name)s toy_O1",
"-O2; mv %(name)s toy_O2_$EBVERSIONGCC",
"-O1; mv %(name)s toy_O1_$EBVERSIONGCC",
]

sanity_check_paths = {
'files': [('bin/yot', 'bin/toy'), 'bin/toy_O1', 'bin/toy_O2'],
'files': [('bin/yot', 'bin/toy'), 'bin/toy_O1_7.3.0-2.30', 'bin/toy_O2_6.4.0-2.28'],
'dirs': ['bin'],
}

Expand Down
2 changes: 1 addition & 1 deletion test/framework/toy_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -1796,7 +1796,7 @@ def test_toy_iter(self):
topdir = os.path.abspath(os.path.dirname(__file__))
toy_ec = os.path.join(topdir, 'easyconfigs', 'test_ecs', 't', 'toy', 'toy-0.0-iter.eb')

expected_buildopts = ['', '-O2; mv %(name)s toy_O2', '-O1; mv %(name)s toy_O1']
expected_buildopts = ['', '-O2; mv %(name)s toy_O2_$EBVERSIONGCC', '-O1; mv %(name)s toy_O1_$EBVERSIONGCC']

for extra_args in [None, ['--minimal-toolchains']]:
# sanity check will make sure all entries in buildopts list were taken into account
Expand Down