forked from EESSI/software-layer
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy patheb_hooks.py
247 lines (197 loc) · 10.7 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
# 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
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_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"
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'])
else:
raise EasyBuildError("WRF-specific hook triggered for non-WRF easyconfig?!")
PARSE_HOOKS = {
'CGAL': parse_hook_cgal_toolchainopts_precise,
'fontconfig': parse_hook_fontconfig_add_fonts,
'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,
}