Skip to content

Commit 2691297

Browse files
authored
Merge pull request #4580 from Flamefire/deprecate_parallel-5
Deprecate use of `parallel` easyconfig parameter and fix updating the template value
2 parents e9fcef8 + 192a329 commit 2691297

17 files changed

Lines changed: 216 additions & 78 deletions

easybuild/framework/easyblock.py

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,7 @@ def post_init(self):
340340
# but needs to be correct if the build is performed in the installation directory
341341
self.log.info("Changing build dir to %s", self.installdir)
342342
self.builddir = self.installdir
343+
self.set_parallel()
343344

344345
# INIT/CLOSE LOG
345346
def _init_log(self):
@@ -1978,7 +1979,7 @@ def skip_extensions_parallel(self, exts_filter):
19781979
exts_cnt = len(self.ext_instances)
19791980
cmds = [resolve_exts_filter_template(exts_filter, ext) for ext in self.ext_instances]
19801981

1981-
with ThreadPoolExecutor(max_workers=self.cfg['parallel']) as thread_pool:
1982+
with ThreadPoolExecutor(max_workers=self.cfg.parallel) as thread_pool:
19821983

19831984
# list of command to run asynchronously
19841985
async_cmds = [thread_pool.submit(run_shell_cmd, cmd, stdin=stdin, hidden=True, fail_on_error=False,
@@ -2108,7 +2109,7 @@ def install_extensions_parallel(self, install=True):
21082109
"""
21092110
self.log.info("Installing extensions in parallel...")
21102111

2111-
thread_pool = ThreadPoolExecutor(max_workers=self.cfg['parallel'])
2112+
thread_pool = ThreadPoolExecutor(max_workers=self.cfg.parallel)
21122113

21132114
running_exts = []
21142115
installed_ext_names = []
@@ -2170,7 +2171,7 @@ def update_exts_progress_bar_helper(running_exts, progress_size):
21702171

21712172
for _ in range(max_iter):
21722173

2173-
if not (exts_queue and len(running_exts) < self.cfg['parallel']):
2174+
if not (exts_queue and len(running_exts) < self.cfg.parallel):
21742175
break
21752176

21762177
# check whether extension at top of the queue is ready to install
@@ -2409,19 +2410,21 @@ def set_parallel(self):
24092410
"""Set 'parallel' easyconfig parameter to determine how many cores can/should be used for parallel builds."""
24102411
# set level of parallelism for build
24112412
par = build_option('parallel')
2412-
cfg_par = self.cfg['parallel']
2413-
if cfg_par is None:
2413+
if par is not None:
24142414
self.log.debug("Desired parallelism specified via 'parallel' build option: %s", par)
2415-
elif par is None:
2416-
par = cfg_par
2417-
self.log.debug("Desired parallelism specified via 'parallel' easyconfig parameter: %s", par)
2418-
else:
2419-
par = min(int(par), int(cfg_par))
2420-
self.log.debug("Desired parallelism: minimum of 'parallel' build option/easyconfig parameter: %s", par)
24212415

2422-
par = det_parallelism(par, maxpar=self.cfg['maxparallel'])
2416+
# Transitional only in case some easyblocks still set/change cfg['parallel']
2417+
# Use _parallelLegacy to avoid deprecation warnings
2418+
cfg_par = self.cfg['_parallelLegacy']
2419+
if cfg_par is not None:
2420+
if par is None:
2421+
par = cfg_par
2422+
else:
2423+
par = min(int(par), int(cfg_par))
2424+
2425+
par = det_parallelism(par, maxpar=self.cfg['max_parallel'])
24232426
self.log.info("Setting parallelism: %s" % par)
2424-
self.cfg['parallel'] = par
2427+
self.cfg.parallel = par
24252428

24262429
def remove_module_file(self):
24272430
"""Remove module file (if it exists), and check for ghost installation directory (and deal with it)."""
@@ -2467,8 +2470,6 @@ def check_readiness_step(self):
24672470
"""
24682471
Verify if all is ok to start build.
24692472
"""
2470-
self.set_parallel()
2471-
24722473
# check whether modules are loaded
24732474
loadedmods = self.modules_tool.loaded_modules()
24742475
if len(loadedmods) > 0:

easybuild/framework/easyconfig/default.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,6 @@
110110
'installopts': ['', 'Extra options for installation', BUILD],
111111
'maxparallel': [16, 'Max degree of parallelism', BUILD],
112112
'module_only': [False, 'Only generate module file', BUILD],
113-
'parallel': [None, ('Degree of parallelism for e.g. make (default: based on the number of '
114-
'cores, active cpuset and restrictions in ulimit)'), BUILD],
115113
'patches': [[], "List of patches to apply", BUILD],
116114
'prebuildopts': ['', 'Extra options pre-passed to build command.', BUILD],
117115
'preconfigopts': ['', 'Extra options pre-passed to configure.', BUILD],

easybuild/framework/easyconfig/easyconfig.py

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,18 @@ def handle_deprecated_or_replaced_easyconfig_parameters(ec_method):
120120
def new_ec_method(self, key, *args, **kwargs):
121121
"""Check whether any replace easyconfig parameters are still used"""
122122
# map deprecated parameters to their replacements, issue deprecation warning(/error)
123-
if key in ALTERNATIVE_EASYCONFIG_PARAMETERS:
123+
if key == 'parallel':
124+
_log.deprecated("Easyconfig parameter 'parallel' is deprecated, "
125+
"use 'max_parallel' or the parallel property instead.", '6.0')
126+
# This hidden parameter allows easyblocks to continue using self.cfg['parallel'] which contains
127+
# the computed parallelism after the ready step but the easyconfig parameter before that step.
128+
129+
# Easyblocks using `max_parallel` always get the value from the easyconfig unmodified.
130+
# Easyblocks should use either the parallel property or `max_parallel` such that the semantic is clear.
131+
# In particular writes to self.cfg['parallel'] do NOT update the %(parallel)s template.
132+
# It can be removed when the deprecation expires.
133+
key = '_parallelLegacy'
134+
elif key in ALTERNATIVE_EASYCONFIG_PARAMETERS:
124135
key = ALTERNATIVE_EASYCONFIG_PARAMETERS[key]
125136
elif key in DEPRECATED_EASYCONFIG_PARAMETERS:
126137
depr_key = key
@@ -144,7 +155,10 @@ def is_local_var_name(name):
144155
"""
145156
res = False
146157
if name.startswith(LOCAL_VAR_PREFIX) or name.startswith('_'):
147-
res = True
158+
# make exception for '_parallelLegacy' hidden easyconfig parameter,
159+
# which is used to deprecate use of 'paralell' easyconfig parameter
160+
if name != '_parallelLegacy':
161+
res = True
148162
# __builtins__ is always defined as a 'local' variables
149163
# single-letter local variable names are allowed (mainly for use in list comprehensions)
150164
# in Python 2, variables defined in list comprehensions leak to the outside (no longer the case in Python 3)
@@ -502,6 +516,12 @@ def __init__(self, path, extra_options=None, build_specs=None, validate=True, hi
502516
self.iterate_options = []
503517
self.iterating = False
504518

519+
# Storage for parallel property. Mark as unset initially
520+
self._parallel = None
521+
# introduce hidden '_parallelLegacy' easyconfig parameter,
522+
# used to deprecate use of 'parallel' easyconfig parameter
523+
self._config['_parallelLegacy'] = [None, '', ('', )]
524+
505525
# parse easyconfig file
506526
self.build_specs = build_specs
507527
self.parse()
@@ -598,6 +618,7 @@ def copy(self, validate=None):
598618
ec = EasyConfig(self.path, validate=validate, hidden=self.hidden, rawtxt=self.rawtxt)
599619
# take a copy of the actual config dictionary (which already contains the extra options)
600620
ec._config = copy.deepcopy(self._config)
621+
ec._parallel = self._parallel # Might be already set, e.g. for extensions
601622
# since rawtxt is defined, self.path may not get inherited, make sure it does
602623
if self.path:
603624
ec.path = self.path
@@ -702,6 +723,12 @@ def parse(self):
702723
if missing_mandatory_keys:
703724
raise EasyBuildError("mandatory parameters not provided in %s: %s", self.path, missing_mandatory_keys)
704725

726+
if 'parallel' in ec_vars:
727+
# Replace value and issue better warning for easyconfig parameters,
728+
# as opposed to warnings meant for easyblocks)
729+
self.log.deprecated("Easyconfig parameter 'parallel' is deprecated, use 'max_parallel' instead.", '6.0')
730+
ec_vars['_parallelLegacy'] = ec_vars.pop('parallel')
731+
705732
# provide suggestions for typos. Local variable names are excluded from this check
706733
possible_typos = [(key, difflib.get_close_matches(key.lower(), self._config.keys(), 1, 0.85))
707734
for key in ec_vars if not is_local_var_name(key) and key not in self]
@@ -1235,6 +1262,32 @@ def all_dependencies(self):
12351262

12361263
return self._all_dependencies
12371264

1265+
@property
1266+
def is_parallel_set(self):
1267+
"""Return if the desired parallelism has been determined yet"""
1268+
return self._parallel is not None
1269+
1270+
@property
1271+
def parallel(self):
1272+
"""Degree of parallellism (number of cores) to be used for building, etc."""
1273+
if not self.is_parallel_set:
1274+
raise ValueError("Parallelism not set yet, 'set_parallel' method of easyblock must be called first")
1275+
1276+
# This gets set when an easyblock changes ec['parallel'].
1277+
# It also gets set/updated in set_parallel to mirror the old behavior during the deprecation phase
1278+
parallelLegacy = self._config['_parallelLegacy'][0]
1279+
if parallelLegacy is not None:
1280+
return max(1, parallelLegacy)
1281+
return self._parallel
1282+
1283+
@parallel.setter
1284+
def parallel(self, value):
1285+
# Update backstorage and template value
1286+
self._parallel = max(1, value) # Also handles False
1287+
self.template_values['parallel'] = self._parallel
1288+
# Backwards compatibility, only for easyblocks still reading self.cfg['parallel']
1289+
self._config['_parallelLegacy'][0] = self._parallel
1290+
12381291
def dump(self, fp, always_overwrite=True, backup=False, explicit_toolchains=False):
12391292
"""
12401293
Dump this easyconfig to file, with the given filename.

easybuild/framework/easyconfig/format/format.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
['preconfigopts', 'configopts'],
6969
['prebuildopts', 'buildopts'],
7070
['preinstallopts', 'installopts'],
71-
['parallel', 'maxparallel'],
71+
['maxparallel'],
7272
]
7373
LAST_PARAMS = ['exts_default_options', 'exts_list',
7474
'sanity_check_paths', 'sanity_check_commands',

easybuild/framework/easyconfig/templates.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@
5959
'bitbucket_account',
6060
'github_account',
6161
'name',
62-
'parallel',
6362
'version',
6463
'versionsuffix',
6564
'versionprefix',
@@ -103,12 +102,12 @@
103102
'cuda_sm_comma_sep': 'Comma-separated list of sm_* values that correspond with CUDA compute capabilities',
104103
'cuda_sm_space_sep': 'Space-separated list of sm_* values that correspond with CUDA compute capabilities',
105104
'mpi_cmd_prefix': 'Prefix command for running MPI programs (with default number of ranks)',
105+
'parallel': "Degree of parallelism for e.g. make",
106106
# can't be a boolean (True/False), must be a string value since it's a string template
107107
'rpath_enabled': "String value indicating whether or not RPATH linking is used ('true' or 'false')",
108108
'software_commit': "Git commit id to use for the software as specified by --software-commit command line option",
109109
'sysroot': "Location root directory of system, prefix for standard paths like /usr/lib and /usr/include"
110110
"as specify by the --sysroot configuration option",
111-
112111
}
113112

114113
# constant templates that can be used in easyconfigs

easybuild/framework/extension.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,11 @@ def __init__(self, mself, ext, extra_params=None):
132132
self.options = resolve_template(copy.deepcopy(self.ext.get('options', {})),
133133
self.cfg.template_values,
134134
expect_resolved=False)
135+
if 'parallel' in self.options:
136+
# Replace value and issue better warning for easyconfig parameters,
137+
# as opposed to warnings meant for easyblocks
138+
self.log.deprecated("Easyconfig parameter 'parallel' is deprecated, use 'max_parallel' instead.", '6.0')
139+
self.options['max_parallel'] = self.options.pop('parallel')
135140

136141
if extra_params:
137142
self.cfg.extend_params(extra_params, overwrite=False)
@@ -149,6 +154,12 @@ def __init__(self, mself, ext, extra_params=None):
149154
self.log.debug("Skipping unknown custom easyconfig parameter '%s' for extension %s/%s: %s",
150155
key, name, version, value)
151156

157+
# If parallelism has been set already take potentially new limitation into account
158+
if self.cfg.is_parallel_set:
159+
max_par = self.cfg['max_parallel']
160+
if max_par is not None and max_par < self.cfg.parallel:
161+
self.cfg.parallel = max_par
162+
152163
self.sanity_check_fail_msgs = []
153164
self.sanity_check_module_loaded = False
154165
self.fake_mod_data = None

easybuild/scripts/mk_tmpl_easyblock_for.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ def build_step(self):
165165
comp_fam = comp_map[self.toolchain.comp_family()]
166166
167167
# enable parallel build
168-
par = self.cfg['parallel']
168+
par = self.cfg.parallel
169169
cmd = "build command --parallel %%d --compiler-family %%s" %% (par, comp_fam)
170170
run_shell_cmd(cmd)
171171

easybuild/tools/systemtools.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1214,6 +1214,8 @@ def get_default_parallelism():
12141214
raise EasyBuildError("Specified level of parallelism '%s' is not an integer value: %s", par, err)
12151215

12161216
if maxpar is not None and maxpar < par:
1217+
if maxpar is False:
1218+
maxpar = 1
12171219
_log.info("Limiting parallelism from %s to %s", par, maxpar)
12181220
par = maxpar
12191221

0 commit comments

Comments
 (0)