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

take into account custom configuration options specified in easystack file + drop support for easystack files using 'software' top-level key #4057

Merged
merged 57 commits into from
Nov 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
05b3873
Allow reconfiguring. Then, in main, loop over items in the EasyStack …
casparvl Aug 10, 2022
bbd3630
Added support for merging command line arguments and easyconfig speci…
casparvl Sep 15, 2022
88a501d
cleaned up logging, removed unnecessary functions that aren't used in…
casparvl Sep 16, 2022
48ce5b3
Fixing too long lines etc
casparvl Sep 16, 2022
245cee3
Trailing whitespace removed
casparvl Sep 16, 2022
b4c4b8a
More cleanup of too long lines, added additional arg to rest_of_main.…
casparvl Sep 19, 2022
5b908a4
Shortened line
casparvl Sep 19, 2022
3264068
Remove the todo to clean the singleton, this is done now in set_up_co…
casparvl Sep 19, 2022
3c0252e
Move install_latest_eb_release logic to the top of 'rest_of_main', th…
casparvl Oct 12, 2022
8afef99
Get rid of skip_clean_exit. Just always return from rest_of_main wher…
casparvl Oct 12, 2022
81c46fb
Merge branch 'easybuilders:develop' into easystack_options_support
casparvl Oct 12, 2022
ba76b54
Make hound happy
casparvl Oct 12, 2022
e107a4b
Renamed rest_of_main to process_eb_args. Fixed some more hound stuff
casparvl Oct 12, 2022
d129859
Also clear ConfigurationVariables singleton. Furthermore, cleanup the…
casparvl Oct 12, 2022
523e47e
Inline unroll_arguments again, as it used to be. It is only used once…
casparvl Oct 12, 2022
819138d
Clear easyconfigs caches in between loop iterations over items in eas…
casparvl Oct 12, 2022
d3f4ac3
Fix identiation and forgotten bracket
casparvl Oct 12, 2022
a656eaa
Fixed missing import and typo
casparvl Oct 12, 2022
d98aa46
Removed debugging print
casparvl Oct 12, 2022
5096c4c
Seperated out processing of the EasyStack file into a single function…
casparvl Oct 12, 2022
63f9a3d
Warn against using EasyStack combined with normal command line argume…
casparvl Oct 12, 2022
b11f346
add test for test_set_up_configuration
boegel Oct 12, 2022
933f949
rename dict_to_argslist to opts_dict_to_eb_opts + make it more robust…
boegel Oct 12, 2022
7256156
add end-to-end test for easystack file with easyconfig-specific options
boegel Oct 12, 2022
a82ef48
fix indent in main.py + fix syntax error with Python 2.7
boegel Oct 13, 2022
c8d3759
use cleanup rather than clean_exit at the end of main function
boegel Oct 13, 2022
03b7755
also pass down EasyBuildOptions into process_eb_args
boegel Oct 13, 2022
046f581
only pass down EasyBuildOptions into process_eb_args
boegel Oct 13, 2022
863641c
move determining of easyconfigs_pkg_paths into process_eb_args + avoi…
boegel Oct 13, 2022
6cbdd55
Merge branch 'develop' into easystack_options_support
boegel Oct 21, 2022
d5a8071
specify that _log is a global variable in process_eb_args + process_e…
boegel Oct 21, 2022
8174f06
take into account that test suite may be configured to run with 'Tcl'…
boegel Oct 21, 2022
73ab067
drop support for easystack files that use 'software' are top-level ke…
boegel Oct 21, 2022
60c511e
Removed tests that rely on software-key based EasyStack files
casparvl Oct 21, 2022
3d02f31
Removed versions test, which was specific to EasyStacks with software…
casparvl Oct 21, 2022
cd8bb71
Added tests for easystack formats that forget the '-' when listing ea…
casparvl Oct 21, 2022
14c9ba1
Need to decide if the format in test_easystack_basic_dict.yaml is val…
casparvl Oct 21, 2022
7e2e6d6
Changed parsing to no longer build a seperate list of easyconfig name…
casparvl Oct 21, 2022
b4cc78b
proper debug logging of parsed easystack
boegel Oct 21, 2022
544de8c
code style fixes in process_easystack
boegel Oct 21, 2022
63a879d
return full EasyStack instance in parse_easystack
boegel Oct 21, 2022
a5ce7d2
fix test_easystack_basic_dict
boegel Oct 21, 2022
664ed09
fix easystack tests
boegel Oct 21, 2022
761c365
fix iterations in test_easystack_basic
boegel Oct 21, 2022
fe59b10
make sure only 'options' key is used in EasyStackParser.parse_by_easy…
boegel Oct 21, 2022
d207013
fix typo
boegel Oct 21, 2022
b0f856f
Silence the notification about the temporary log when reconfiguring
casparvl Oct 21, 2022
fbd61e7
sort keys in Error
casparvl Oct 21, 2022
40ef1ba
Added tests to check failures when invalid keys are specified
casparvl Oct 21, 2022
ec4f0fd
Added test with EasyConfig file htat is missing easyconfigs top level…
casparvl Oct 21, 2022
e2fbdd2
Added test with EasyConfig file that is missing easyconfigs top level…
casparvl Oct 21, 2022
292498f
add test for processing an easystack file that relies on wiping of ea…
boegel Oct 21, 2022
dfcb2e2
remove unused import of det_full_ec_version in framework/easystack.py…
boegel Oct 21, 2022
0f24784
Merge branch 'develop' into easystack_options_support
boegel Nov 23, 2022
fb36aa2
Merge branch 'develop' into easystack_options_support
boegel Nov 23, 2022
b93dd22
use more YAML-like syntax for options in test easystack file
boegel Nov 23, 2022
f4f486d
sort keys in easystack parser error to avoid failing test due to rand…
boegel Nov 23, 2022
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
223 changes: 48 additions & 175 deletions easybuild/framework/easystack.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@
:author: Denis Kristak (Inuits)
:author: Pavel Grochal (Inuits)
:author: Kenneth Hoste (HPC-UGent)
:author: Caspar van Leeuwen (SURF)
"""
import pprint

from easybuild.base import fancylogger
from easybuild.tools.build_log import EasyBuildError
from easybuild.tools.filetools import read_file
from easybuild.tools.module_naming_scheme.utilities import det_full_ec_version
from easybuild.tools.py2vs3 import string_type
from easybuild.tools.utilities import only_if_module_is_available
try:
Expand Down Expand Up @@ -69,30 +71,13 @@ class EasyStack(object):
def __init__(self):
self.easybuild_version = None
self.robot = False
self.software_list = []
self.easyconfigs = [] # A list of easyconfig names. May or may not include .eb extension
# A dict where keys are easyconfig names, values are dictionary of options that should be
# applied for that easyconfig
self.ec_opts = {}

def compose_ec_filenames(self):
"""Returns a list of all easyconfig names"""
ec_filenames = []

# entries specified via 'software' top-level key
for sw in self.software_list:
full_ec_version = det_full_ec_version({
'toolchain': {'name': sw.toolchain_name, 'version': sw.toolchain_version},
'version': sw.version,
'versionsuffix': sw.versionsuffix,
})
ec_filename = '%s-%s.eb' % (sw.name, full_ec_version)
ec_filenames.append(ec_filename)

# entries specified via 'easyconfigs' top-level key
for ec in self.easyconfigs:
ec_filenames.append(ec)
return ec_filenames
self.ec_opt_tuples = [] # A list of tuples (easyconfig_name, eaysconfig_specific_opts)

def __str__(self):
"""
Pretty printing of an EasyStack instance
"""
return pprint.pformat(self.ec_opt_tuples)

# flags applicable to all sw (i.e. robot)
def get_general_options(self):
Expand Down Expand Up @@ -129,37 +114,33 @@ def parse(filepath):
except yaml.YAMLError as err:
raise EasyBuildError("Failed to parse %s: %s" % (filepath, err))

easystack_data = None
top_keys = ('easyconfigs', 'software')
keys_found = []
for key in top_keys:
if key in easystack_raw:
keys_found.append(key)
# For now, we don't support mixing multiple top_keys, so check that only one was defined
if len(keys_found) > 1:
keys_string = ', '.join(keys_found)
msg = "Specifying multiple top level keys (%s) " % keys_string
msg += "in one EasyStack file is currently not supported"
msg += ", see %s for documentation." % EASYSTACK_DOC_URL
raise EasyBuildError(msg)
elif len(keys_found) == 0:
msg = "Not a valid EasyStack YAML file: no 'easyconfigs' or 'software' top-level key found"
msg += ", see %s for documentation." % EASYSTACK_DOC_URL
raise EasyBuildError(msg)
else:
key = keys_found[0]
key = 'easyconfigs'
if key in easystack_raw:
easystack_data = easystack_raw[key]

parse_method_name = 'parse_by_' + key
parse_method = getattr(EasyStackParser, 'parse_by_%s' % key, None)
if parse_method is None:
raise EasyBuildError("Easystack parse method '%s' not found!", parse_method_name)
if isinstance(easystack_data, dict) or isinstance(easystack_data, str):
datatype = 'dict' if isinstance(easystack_data, dict) else 'str'
msg = '\n'.join([
"Found %s value for '%s' in %s, should be list." % (datatype, key, filepath),
"Make sure you use '-' to create list items under '%s', for example:" % key,
" easyconfigs:",
" - example-1.0.eb",
" - example-2.0.eb:",
" options:"
" ...",
])
raise EasyBuildError(msg)
elif not isinstance(easystack_data, list):
raise EasyBuildError("Value type for '%s' in %s should be list, found %s",
key, filepath, type(easystack_data))
else:
raise EasyBuildError("Top-level key '%s' missing in easystack file %s", key, filepath)

# assign general easystack attributes
easybuild_version = easystack_raw.get('easybuild_version', None)
robot = easystack_raw.get('robot', False)

return parse_method(filepath, easystack_data, easybuild_version=easybuild_version, robot=robot)
return EasyStackParser.parse_by_easyconfigs(filepath, easystack_data,
easybuild_version=easybuild_version, robot=robot)

@staticmethod
def parse_by_easyconfigs(filepath, easyconfigs, easybuild_version=None, robot=False):
Expand All @@ -173,7 +154,7 @@ def parse_by_easyconfigs(filepath, easyconfigs, easybuild_version=None, robot=Fa
if isinstance(easyconfig, str):
if not easyconfig.endswith('.eb'):
easyconfig = easyconfig + '.eb'
easystack.easyconfigs.append(easyconfig)
easystack.ec_opt_tuples.append((easyconfig, None))
elif isinstance(easyconfig, dict):
if len(easyconfig) == 1:
# Get single key from dictionary 'easyconfig'
Expand All @@ -183,132 +164,25 @@ def parse_by_easyconfigs(filepath, easyconfigs, easybuild_version=None, robot=Fa
easyconf_name_with_eb = easyconf_name + '.eb'
else:
easyconf_name_with_eb = easyconf_name
easystack.easyconfigs.append(easyconf_name_with_eb)
# Add options to the ec_opts dict
if 'options' in easyconfig[easyconf_name].keys():
easystack.ec_opts[easyconf_name_with_eb] = easyconfig[easyconf_name]['options']
# Get options
ec_dict = easyconfig[easyconf_name] or {}

# make sure only 'options' key is used (for now)
if any(x != 'options' for x in ec_dict):
msg = "Found one or more invalid keys for %s (only 'options' supported): %s"
raise EasyBuildError(msg, easyconf_name, ', '.join(sorted(ec_dict.keys())))

opts = ec_dict.get('options')
easystack.ec_opt_tuples.append((easyconf_name_with_eb, opts))
else:
dict_keys = ', '.join(easyconfig.keys())
dict_keys = ', '.join(sorted(easyconfig.keys()))
msg = "Failed to parse easystack file: expected a dictionary with one key (the EasyConfig name), "
msg += "instead found keys: %s" % dict_keys
msg += ", see %s for documentation." % EASYSTACK_DOC_URL
raise EasyBuildError(msg)

return easystack

@staticmethod
def parse_by_software(filepath, software, easybuild_version=None, robot=False):
"""
Parse easystack file with 'software' as top-level key.
"""

easystack = EasyStack()

# assign software-specific easystack attributes
for name in software:
# ensure we have a string value (YAML parser returns type = dict
# if levels under the current attribute are present)
check_value(name, "software name")
try:
toolchains = software[name]['toolchains']
except KeyError:
raise EasyBuildError("Toolchains for software '%s' are not defined in %s", name, filepath)
for toolchain in toolchains:
check_value(toolchain, "software %s" % name)

if toolchain == 'SYSTEM':
toolchain_name, toolchain_version = 'system', ''
else:
toolchain_parts = toolchain.split('-', 1)
if len(toolchain_parts) == 2:
toolchain_name, toolchain_version = toolchain_parts
elif len(toolchain_parts) == 1:
toolchain_name, toolchain_version = toolchain, ''
else:
raise EasyBuildError("Incorrect toolchain specification for '%s' in %s, too many parts: %s",
name, filepath, toolchain_parts)

try:
# if version string containts asterisk or labels, raise error (asterisks not supported)
versions = toolchains[toolchain]['versions']
except TypeError as err:
wrong_structure_err = "An error occurred when interpreting "
wrong_structure_err += "the data for software %s: %s" % (name, err)
raise EasyBuildError(wrong_structure_err)
if '*' in str(versions):
asterisk_err = "EasyStack specifications of '%s' in %s contain asterisk. "
asterisk_err += "Wildcard feature is not supported yet."
raise EasyBuildError(asterisk_err, name, filepath)

# yaml versions can be in different formats in yaml file
# firstly, check if versions in yaml file are read as a dictionary.
# Example of yaml structure:
# ========================================================================
# versions:
# '2.25':
# '2.23':
# versionsuffix: '-R-4.0.0'
# ========================================================================
if isinstance(versions, dict):
for version in versions:
check_value(version, "%s (with %s toolchain)" % (name, toolchain_name))
if versions[version] is not None:
version_spec = versions[version]
if 'versionsuffix' in version_spec:
versionsuffix = str(version_spec['versionsuffix'])
else:
versionsuffix = ''
if 'exclude-labels' in str(version_spec) or 'include-labels' in str(version_spec):
lab_err = "EasyStack specifications of '%s' in %s "
lab_err += "contain labels. Labels aren't supported yet."
raise EasyBuildError(lab_err, name, filepath)
else:
versionsuffix = ''

specs = {
'name': name,
'toolchain_name': toolchain_name,
'toolchain_version': toolchain_version,
'version': version,
'versionsuffix': versionsuffix,
}
sw = SoftwareSpecs(**specs)

# append newly created class instance to the list in instance of EasyStack class
easystack.software_list.append(sw)
continue

elif isinstance(versions, (list, tuple)):
pass

# multiple lines without ':' is read as a single string; example:
# versions:
# '2.24'
# '2.51'
elif isinstance(versions, string_type):
versions = versions.split()

# single values like '2.24' should be wrapped in a list
else:
versions = [versions]

# if version is not a dictionary, versionsuffix is not specified
versionsuffix = ''

for version in versions:
check_value(version, "%s (with %s toolchain)" % (name, toolchain_name))
sw = SoftwareSpecs(
name=name, version=version, versionsuffix=versionsuffix,
toolchain_name=toolchain_name, toolchain_version=toolchain_version)
# append newly created class instance to the list in instance of EasyStack class
easystack.software_list.append(sw)

# assign general easystack attributes
easystack.easybuild_version = easybuild_version
easystack.robot = robot

return easystack


@only_if_module_is_available('yaml', pkgname='PyYAML')
def parse_easystack(filepath):
Expand All @@ -321,18 +195,17 @@ def parse_easystack(filepath):
# class instance which contains all info about planned build
easystack = EasyStackParser.parse(filepath)

easyconfig_names = easystack.compose_ec_filenames()

# Disabled general options for now. We weren't using them, and first want support for EasyConfig-specific options.
# Then, we need a method to resolve conflicts (specific options should win)
# general_options = easystack.get_general_options()
boegel marked this conversation as resolved.
Show resolved Hide resolved

_log.debug("EasyStack parsed. Proceeding to install these Easyconfigs: %s" % ', '.join(sorted(easyconfig_names)))
_log.debug("Using EasyConfig specific options based on the following dict:")
_log.debug(easystack.ec_opts)
_log.debug("Parsed easystack:\n%s" % easystack)

# _log.debug("Using EasyConfig specific options based on the following dict:")
# _log.debug(easystack.ec_opts)
# if len(general_options) != 0:
# _log.debug("General options for installation are: \n%s" % str(general_options))
# else:
# _log.debug("No general options were specified in easystack")

return easyconfig_names, easystack.ec_opts
return easystack
Loading