forked from EESSI/software-layer
-
Notifications
You must be signed in to change notification settings - Fork 0
/
eb_hooks.py
380 lines (305 loc) · 16.9 KB
/
eb_hooks.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
# Hooks to customize how EasyBuild installs software in EESSI
# see https://docs.easybuild.io/en/latest/Hooks.html
import os
import re
from easybuild.easyblocks.generic.configuremake import obtain_config_guess
from easybuild.tools.build_log import EasyBuildError, print_msg
from easybuild.tools.config import build_option, update_build_option
from easybuild.tools.filetools import apply_regex_substitutions, copy_file, which
from easybuild.tools.run import run_cmd
from easybuild.tools.systemtools import AARCH64, POWER, X86_64, get_cpu_architecture, get_cpu_features
from easybuild.tools.toolchain.compiler import OPTARCH_GENERIC
# prefer importing LooseVersion from easybuild.tools, but fall back to distuils in case EasyBuild <= 4.7.0 is used
try:
from easybuild.tools import LooseVersion
except ImportError:
from distutils.version import LooseVersion
CPU_TARGET_NEOVERSE_V1 = 'aarch64/neoverse_v1'
CPU_TARGET_AARCH64_GENERIC = 'aarch64/generic'
EESSI_RPATH_OVERRIDE_ATTR = 'orig_rpath_override_dirs'
def get_eessi_envvar(eessi_envvar):
"""Get an EESSI environment variable from the environment"""
eessi_envvar_value = os.getenv(eessi_envvar)
if eessi_envvar_value is None:
raise EasyBuildError("$%s is not defined!", eessi_envvar)
return eessi_envvar_value
def get_rpath_override_dirs(software_name):
# determine path to installations in software layer via $EESSI_SOFTWARE_PATH
eessi_software_path = get_eessi_envvar('EESSI_SOFTWARE_PATH')
# construct the rpath override directory stub
rpath_injection_stub = os.path.join(
# Make sure we are looking inside the `host_injections` directory
eessi_software_path.replace('versions', 'host_injections', 1),
# Add the subdirectory for the specific software
'rpath_overrides',
software_name,
# We can't know the version, but this allows the use of a symlink
# to facilitate version upgrades without removing files
'system',
)
# Allow for libraries in lib or lib64
rpath_injection_dirs = [os.path.join(rpath_injection_stub, x) for x in ('lib', 'lib64')]
return rpath_injection_dirs
def parse_hook(ec, *args, **kwargs):
"""Main parse hook: trigger custom functions based on software name."""
# determine path to Prefix installation in compat layer via $EPREFIX
eprefix = get_eessi_envvar('EPREFIX')
if ec.name in PARSE_HOOKS:
PARSE_HOOKS[ec.name](ec, eprefix)
def pre_prepare_hook(self, *args, **kwargs):
"""Main pre-prepare hook: trigger custom functions."""
# Check if we have an MPI family in the toolchain (returns None if there is not)
mpi_family = self.toolchain.mpi_family()
# Inject an RPATH override for MPI (if needed)
if mpi_family:
# Get list of override directories
mpi_rpath_override_dirs = get_rpath_override_dirs(mpi_family)
# update the relevant option (but keep the original value so we can reset it later)
if hasattr(self, EESSI_RPATH_OVERRIDE_ATTR):
raise EasyBuildError("'self' already has attribute %s! Can't use pre_prepare hook.",
EESSI_RPATH_OVERRIDE_ATTR)
setattr(self, EESSI_RPATH_OVERRIDE_ATTR, build_option('rpath_override_dirs'))
if getattr(self, EESSI_RPATH_OVERRIDE_ATTR):
# self.EESSI_RPATH_OVERRIDE_ATTR is (already) a colon separated string, let's make it a list
orig_rpath_override_dirs = [getattr(self, EESSI_RPATH_OVERRIDE_ATTR)]
rpath_override_dirs = ':'.join(orig_rpath_override_dirs + mpi_rpath_override_dirs)
else:
rpath_override_dirs = ':'.join(mpi_rpath_override_dirs)
update_build_option('rpath_override_dirs', rpath_override_dirs)
print_msg("Updated rpath_override_dirs (to allow overriding MPI family %s): %s",
mpi_family, rpath_override_dirs)
def post_prepare_hook_gcc_prefixed_ld_rpath_wrapper(self, *args, **kwargs):
"""
Post-configure hook for GCCcore:
- copy RPATH wrapper script for linker commands to also have a wrapper in place with system type prefix like 'x86_64-pc-linux-gnu'
"""
if self.name == 'GCCcore':
config_guess = obtain_config_guess()
system_type, _ = run_cmd(config_guess, log_all=True)
cmd_prefix = '%s-' % system_type.strip()
for cmd in ('ld', 'ld.gold', 'ld.bfd'):
wrapper = which(cmd)
self.log.info("Path to %s wrapper: %s" % (cmd, wrapper))
wrapper_dir = os.path.dirname(wrapper)
prefix_wrapper = os.path.join(wrapper_dir, cmd_prefix + cmd)
copy_file(wrapper, prefix_wrapper)
self.log.info("Path to %s wrapper with '%s' prefix: %s" % (cmd, cmd_prefix, which(prefix_wrapper)))
# we need to tweak the copied wrapper script, so that:
regex_subs = [
# - CMD in the script is set to the command name without prefix, because EasyBuild's rpath_args.py
# script that is used by the wrapper script only checks for 'ld', 'ld.gold', etc.
# when checking whether or not to use -Wl
('^CMD=.*', 'CMD=%s' % cmd),
# - the path to the correct actual binary is logged and called
('/%s ' % cmd, '/%s ' % (cmd_prefix + cmd)),
]
apply_regex_substitutions(prefix_wrapper, regex_subs)
else:
raise EasyBuildError("GCCcore-specific hook triggered for non-GCCcore easyconfig?!")
def post_prepare_hook(self, *args, **kwargs):
"""Main post-prepare hook: trigger custom functions."""
if hasattr(self, EESSI_RPATH_OVERRIDE_ATTR):
# Reset the value of 'rpath_override_dirs' now that we are finished with it
update_build_option('rpath_override_dirs', getattr(self, EESSI_RPATH_OVERRIDE_ATTR))
print_msg("Resetting rpath_override_dirs to original value: %s", getattr(self, EESSI_RPATH_OVERRIDE_ATTR))
delattr(self, EESSI_RPATH_OVERRIDE_ATTR)
if self.name in POST_PREPARE_HOOKS:
POST_PREPARE_HOOKS[self.name](self, *args, **kwargs)
def parse_hook_cgal_toolchainopts_precise(ec, eprefix):
"""Enable 'precise' rather than 'strict' toolchain option for CGAL on POWER."""
if ec.name == 'CGAL':
if get_cpu_architecture() == POWER:
# 'strict' implies '-mieee-fp', which is not supported on POWER
# see https://github.com/easybuilders/easybuild-framework/issues/2077
ec['toolchainopts']['strict'] = False
ec['toolchainopts']['precise'] = True
print_msg("Tweaked toochainopts for %s: %s", ec.name, ec['toolchainopts'])
else:
raise EasyBuildError("CGAL-specific hook triggered for non-CGAL easyconfig?!")
def parse_hook_fontconfig_add_fonts(ec, eprefix):
"""Inject --with-add-fonts configure option for fontconfig."""
if ec.name == 'fontconfig':
# make fontconfig aware of fonts included with compat layer
with_add_fonts = '--with-add-fonts=%s' % os.path.join(eprefix, 'usr', 'share', 'fonts')
ec.update('configopts', with_add_fonts)
print_msg("Added '%s' configure option for %s", with_add_fonts, ec.name)
else:
raise EasyBuildError("fontconfig-specific hook triggered for non-fontconfig easyconfig?!")
def parse_hook_openblas_relax_lapack_tests_num_errors(ec, eprefix):
"""Relax number of failing numerical LAPACK tests for aarch64/neoverse_v1 CPU target."""
cpu_target = get_eessi_envvar('EESSI_SOFTWARE_SUBDIR')
if ec.name == 'OpenBLAS':
# relax maximum number of failed numerical LAPACK tests for aarch64/neoverse_v1 CPU target
# since the default setting of 150 that works well on other aarch64 targets and x86_64 is a bit too strict
# See https://github.com/EESSI/software-layer/issues/314
cfg_option = 'max_failing_lapack_tests_num_errors'
if cpu_target == CPU_TARGET_NEOVERSE_V1:
orig_value = ec[cfg_option]
ec[cfg_option] = 400
print_msg("Maximum number of failing LAPACK tests with numerical errors for %s relaxed to %s (was %s)",
ec.name, ec[cfg_option], orig_value)
else:
print_msg("Not changing option %s for %s on non-AARCH64", cfg_option, ec.name)
else:
raise EasyBuildError("OpenBLAS-specific hook triggered for non-OpenBLAS easyconfig?!")
def parse_hook_qt5_check_qtwebengine_disable(ec, eprefix):
"""
Disable check for QtWebEngine in Qt5 as workaround for problem with determining glibc version.
"""
if ec.name == 'Qt5':
# workaround for glibc version being reported as "UNKNOWN" in Gentoo Prefix environment by EasyBuild v4.7.2,
# see also https://github.com/easybuilders/easybuild-framework/pull/4290
ec['check_qtwebengine'] = False
print_msg("Checking for QtWebEgine in Qt5 installation has been disabled")
else:
raise EasyBuildError("Qt5-specific hook triggered for non-Qt5 easyconfig?!")
def parse_hook_ucx_eprefix(ec, eprefix):
"""Make UCX aware of compatibility layer via additional configuration options."""
if ec.name == 'UCX':
ec.update('configopts', '--with-sysroot=%s' % eprefix)
ec.update('configopts', '--with-rdmacm=%s' % os.path.join(eprefix, 'usr'))
print_msg("Using custom configure options for %s: %s", ec.name, ec['configopts'])
else:
raise EasyBuildError("UCX-specific hook triggered for non-UCX easyconfig?!")
def pre_configure_hook(self, *args, **kwargs):
"""Main pre-configure hook: trigger custom functions based on software name."""
if self.name in PRE_CONFIGURE_HOOKS:
PRE_CONFIGURE_HOOKS[self.name](self, *args, **kwargs)
def pre_configure_hook_openblas_optarch_generic(self, *args, **kwargs):
"""
Pre-configure hook for OpenBLAS: add DYNAMIC_ARCH=1 to build/test/install options when using --optarch=GENERIC
"""
if self.name == 'OpenBLAS':
if build_option('optarch') == OPTARCH_GENERIC:
for step in ('build', 'test', 'install'):
self.cfg.update(f'{step}opts', "DYNAMIC_ARCH=1")
else:
raise EasyBuildError("OpenBLAS-specific hook triggered for non-OpenBLAS easyconfig?!")
def pre_configure_hook_libfabric_disable_psm3_x86_64_generic(self, *args, **kwargs):
"""Add --disable-psm3 to libfabric configure options when building with --optarch=GENERIC on x86_64."""
if self.name == 'libfabric':
if get_cpu_architecture() == X86_64:
generic = build_option('optarch') == OPTARCH_GENERIC
no_avx = 'avx' not in get_cpu_features()
if generic or no_avx:
self.cfg.update('configopts', '--disable-psm3')
print_msg("Using custom configure options for %s: %s", self.name, self.cfg['configopts'])
else:
raise EasyBuildError("libfabric-specific hook triggered for non-libfabric easyconfig?!")
def pre_configure_hook_metabat_filtered_zlib_dep(self, *args, **kwargs):
"""
Pre-configure hook for MetaBAT:
- take into account that zlib is a filtered dependency,
and that there's no libz.a in the EESSI compat layer
"""
if self.name == 'MetaBAT':
configopts = self.cfg['configopts']
regex = re.compile(r"\$EBROOTZLIB/lib/libz.a")
self.cfg['configopts'] = regex.sub('$EPREFIX/usr/lib64/libz.so', configopts)
else:
raise EasyBuildError("MetaBAT-specific hook triggered for non-MetaBAT easyconfig?!")
def pre_configure_hook_wrf_aarch64(self, *args, **kwargs):
"""
Pre-configure hook for WRF:
- patch arch/configure_new.defaults so building WRF with foss toolchain works on aarch64
"""
if self.name == 'WRF':
if get_cpu_architecture() == AARCH64:
pattern = "Linux x86_64 ppc64le, gfortran"
repl = "Linux x86_64 aarch64 ppc64le, gfortran"
if LooseVersion(self.version) <= LooseVersion('3.9.0'):
self.cfg.update('preconfigopts', "sed -i 's/%s/%s/g' arch/configure_new.defaults && " % (pattern, repl))
print_msg("Using custom preconfigopts for %s: %s", self.name, self.cfg['preconfigopts'])
if LooseVersion('4.0.0') <= LooseVersion(self.version) <= LooseVersion('4.2.1'):
self.cfg.update('preconfigopts', "sed -i 's/%s/%s/g' arch/configure.defaults && " % (pattern, repl))
print_msg("Using custom preconfigopts for %s: %s", self.name, self.cfg['preconfigopts'])
else:
raise EasyBuildError("WRF-specific hook triggered for non-WRF easyconfig?!")
def pre_configure_hook_LAMMPS_aarch64(self, *args, **kwargs):
"""
pre-configure hook for LAMMPS:
- set kokkos_arch on Aarch64
"""
cpu_target = get_eessi_envvar('EESSI_SOFTWARE_SUBDIR')
if self.name == 'LAMMPS':
if self.version == '23Jun2022':
if get_cpu_architecture() == AARCH64:
if cpu_target == CPU_TARGET_AARCH64_GENERIC:
self.cfg['kokkos_arch'] = 'ARM80'
else:
self.cfg['kokkos_arch'] = 'ARM81'
else:
raise EasyBuildError("LAMMPS-specific hook triggered for non-LAMMPS easyconfig?!")
def pre_test_hook(self,*args, **kwargs):
"""Main pre-test hook: trigger custom functions based on software name."""
if self.name in PRE_TEST_HOOKS:
PRE_TEST_HOOKS[self.name](self, *args, **kwargs)
def pre_test_hook_ignore_failing_tests_ESPResSo(self, *args, **kwargs):
"""
Pre-test hook for ESPResSo: skip failing tests, tests frequently timeout due to known bugs in ESPResSo v4.2.1
cfr. https://github.com/EESSI/software-layer/issues/363
"""
if self.name == 'ESPResSo' and self.version == '4.2.1':
self.cfg['testopts'] = "|| echo 'ignoring failing tests (probably due to timeouts)'"
def pre_test_hook_ignore_failing_tests_FFTWMPI(self, *args, **kwargs):
"""
Pre-test hook for FFTW.MPI: skip failing tests for FFTW.MPI 3.3.10 on neoverse_v1
cfr. https://github.com/EESSI/software-layer/issues/325
"""
cpu_target = get_eessi_envvar('EESSI_SOFTWARE_SUBDIR')
if self.name == 'FFTW.MPI' and self.version == '3.3.10' and cpu_target == CPU_TARGET_NEOVERSE_V1:
self.cfg['testopts'] = "|| echo ignoring failing tests"
def pre_test_hook_ignore_failing_tests_SciPybundle(self, *args, **kwargs):
"""
Pre-test hook for SciPy-bundle: skip failing tests for SciPy-bundle 2021.10 (currently the only version that is failing).
In previous versions we were not as strict yet on the numpy/SciPy tests
"""
cpu_target = get_eessi_envvar('EESSI_SOFTWARE_SUBDIR')
if self.name == 'SciPy-bundle' and self.version == '2021.10' and cpu_target == CPU_TARGET_NEOVERSE_V1:
self.cfg['testopts'] = "|| echo ignoring failing tests"
def pre_single_extension_hook(ext, *args, **kwargs):
"""Main pre-configure hook: trigger custom functions based on software name."""
if ext.name in PRE_SINGLE_EXTENSION_HOOKS:
PRE_SINGLE_EXTENSION_HOOKS[ext.name](ext, *args, **kwargs)
def pre_single_extension_testthat(ext, *args, **kwargs):
"""
Pre-extension hook for testthat R package, to fix build on top of recent glibc.
"""
if ext.name == 'testthat' and LooseVersion(ext.version) < LooseVersion('3.1.0'):
# use constant value instead of SIGSTKSZ for stack size,
# cfr. https://github.com/r-lib/testthat/issues/1373 + https://github.com/r-lib/testthat/pull/1403
ext.cfg['preinstallopts'] = "sed -i 's/SIGSTKSZ/32768/g' inst/include/testthat/vendor/catch.h && "
def pre_single_extension_isoband(ext, *args, **kwargs):
"""
Pre-extension hook for isoband R package, to fix build on top of recent glibc.
"""
if ext.name == 'isoband' and LooseVersion(ext.version) < LooseVersion('0.2.5'):
# use constant value instead of SIGSTKSZ for stack size in vendored testthat included in isoband sources,
# cfr. https://github.com/r-lib/isoband/commit/6984e6ce8d977f06e0b5ff73f5d88e5c9a44c027
ext.cfg['preinstallopts'] = "sed -i 's/SIGSTKSZ/32768/g' src/testthat/vendor/catch.h && "
PARSE_HOOKS = {
'CGAL': parse_hook_cgal_toolchainopts_precise,
'fontconfig': parse_hook_fontconfig_add_fonts,
'OpenBLAS': parse_hook_openblas_relax_lapack_tests_num_errors,
'Qt5': parse_hook_qt5_check_qtwebengine_disable,
'UCX': parse_hook_ucx_eprefix,
}
POST_PREPARE_HOOKS = {
'GCCcore': post_prepare_hook_gcc_prefixed_ld_rpath_wrapper,
}
PRE_CONFIGURE_HOOKS = {
'libfabric': pre_configure_hook_libfabric_disable_psm3_x86_64_generic,
'MetaBAT': pre_configure_hook_metabat_filtered_zlib_dep,
'OpenBLAS': pre_configure_hook_openblas_optarch_generic,
'WRF': pre_configure_hook_wrf_aarch64,
'LAMMPS': pre_configure_hook_LAMMPS_aarch64,
}
PRE_TEST_HOOKS = {
'ESPResSo': pre_test_hook_ignore_failing_tests_ESPResSo,
'FFTW.MPI': pre_test_hook_ignore_failing_tests_FFTWMPI,
'SciPy-bundle': pre_test_hook_ignore_failing_tests_SciPybundle,
}
PRE_SINGLE_EXTENSION_HOOKS = {
'isoband': pre_single_extension_isoband,
'testthat': pre_single_extension_testthat,
}