forked from easybuilders/easybuild-easyblocks
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathclang.py
More file actions
640 lines (542 loc) · 32.5 KB
/
Copy pathclang.py
File metadata and controls
640 lines (542 loc) · 32.5 KB
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
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
##
# Copyright 2013 Dmitri Gribenko
# Copyright 2013-2022 Ghent University
#
# This file is triple-licensed under GPLv2 (see below), MIT, and
# BSD three-clause licenses.
#
# This file is part of EasyBuild,
# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en),
# with support of Ghent University (http://ugent.be/hpc),
# the Flemish Supercomputer Centre (VSC) (https://www.vscentrum.be),
# Flemish Research Foundation (FWO) (http://www.fwo.be/en)
# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en).
#
# https://github.com/easybuilders/easybuild
#
# EasyBuild is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation v2.
#
# EasyBuild is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with EasyBuild. If not, see <http://www.gnu.org/licenses/>.
##
"""
Support for building and installing Clang, implemented as an easyblock.
@author: Dmitri Gribenko (National Technical University of Ukraine "KPI")
@author: Ward Poelmans (Ghent University)
@author: Alan O'Cais (Juelich Supercomputing Centre)
"""
import glob
import os
import shutil
from distutils.version import LooseVersion
from easybuild.easyblocks.generic.cmakemake import CMakeMake
from easybuild.framework.easyconfig import CUSTOM
from easybuild.toolchains.compiler.clang import Clang
from easybuild.tools import run
from easybuild.tools.build_log import EasyBuildError, print_warning
from easybuild.tools.config import build_option
from easybuild.tools.filetools import apply_regex_substitutions, change_dir, mkdir, which
from easybuild.tools.modules import get_software_root
from easybuild.tools.run import run_cmd
from easybuild.tools.systemtools import AARCH32, AARCH64, POWER, X86_64
from easybuild.tools.systemtools import get_cpu_architecture, get_os_name, get_os_version, get_shared_lib_ext
from easybuild.tools.environment import setvar
# List of all possible build targets for Clang
CLANG_TARGETS = ["all", "AArch64", "AMDGPU", "ARM", "CppBackend", "Hexagon", "Mips",
"MBlaze", "MSP430", "NVPTX", "PowerPC", "R600", "Sparc",
"SystemZ", "X86", "XCore"]
# Mapping of EasyBuild CPU architecture names to list of default LLVM target names
DEFAULT_TARGETS_MAP = {
AARCH32: ['ARM'],
AARCH64: ['AArch64'],
POWER: ['PowerPC'],
X86_64: ['X86'],
}
# List of all possible AMDGPU gfx targets supported by LLVM
AMDGPU_GFX_SUPPORT = ['gfx700', 'gfx701', 'gfx801', 'gfx803', 'gfx900',
'gfx902', 'gfx906', 'gfx908', 'gfx90a', 'gfx90c',
'gfx1010', 'gfx1030', 'gfx1031']
# List of all supported CUDA toolkit versions supported by LLVM
CUDA_TOOLKIT_SUPPORT = ['80', '90', '91', '92', '100', '101', '102', '110', '111', '112']
class EB_Clang(CMakeMake):
"""Support for bootstrapping Clang."""
@staticmethod
def extra_options():
extra_vars = CMakeMake.extra_options()
extra_vars.update({
'amd_gfx_list': [None, "List of AMDGPU targets to build for. Possible values: " +
', '.join(AMDGPU_GFX_SUPPORT), CUSTOM],
'assertions': [True, "Enable assertions. Helps to catch bugs in Clang.", CUSTOM],
'bootstrap': [True, "Bootstrap Clang using GCC", CUSTOM],
'build_extra_clang_tools': [False, "Build extra Clang tools", CUSTOM],
'build_lld': [False, "Build the LLVM lld linker", CUSTOM],
'build_targets': [None, "Build targets for LLVM (host architecture if None). Possible values: " +
', '.join(CLANG_TARGETS), CUSTOM],
'default_cuda_capability': [None, "Default CUDA capability specified for clang, e.g. '7.5'", CUSTOM],
'default_openmp_runtime': [None, "Default OpenMP runtime for clang (for example, 'libomp')", CUSTOM],
'enable_rtti': [False, "Enable Clang RTTI", CUSTOM],
'python_bindings': [False, "Install python bindings", CUSTOM],
'libcxx': [False, "Build the LLVM C++ standard library", CUSTOM],
'skip_all_tests': [False, "Skip running of tests", CUSTOM],
'static_analyzer': [True, "Install the static analyser of Clang", CUSTOM],
# The sanitizer tests often fail on HPC systems due to the 'weird' environment.
'skip_sanitizer_tests': [True, "Do not run the sanitizer tests", CUSTOM],
'usepolly': [False, "Build Clang with polly", CUSTOM],
})
# disable regular out-of-source build, too simplistic for Clang to work
extra_vars['separate_build_dir'][0] = False
return extra_vars
def __init__(self, *args, **kwargs):
"""Initialize custom class variables for Clang."""
super(EB_Clang, self).__init__(*args, **kwargs)
self.llvm_src_dir = None
self.llvm_obj_dir_stage1 = None
self.llvm_obj_dir_stage2 = None
self.llvm_obj_dir_stage3 = None
self.make_parallel_opts = ""
def check_readiness_step(self):
"""Fail early on RHEL 5.x and derivatives because of known bug in libc."""
super(EB_Clang, self).check_readiness_step()
# RHEL 5.x have a buggy libc. Building stage 2 will fail.
if get_os_name() in ['redhat', 'RHEL', 'centos', 'SL'] and get_os_version().startswith('5.'):
raise EasyBuildError("Can not build Clang on %s v5.x: libc is buggy, building stage 2 will fail. "
"See http://stackoverflow.com/questions/7276828/", get_os_name())
def extract_step(self):
"""
Prepare a combined LLVM source tree. The layout is:
llvm/ Unpack llvm-*.tar.gz here
projects/
compiler-rt/ Unpack compiler-rt-*.tar.gz here
openmp/ Unpack openmp-*.tar.xz here
tools/
clang/ Unpack clang-*.tar.gz here
tools/
extra/ Unpack clang-tools-extra-*.tar.gz here
polly/ Unpack polly-*.tar.gz here
libcxx/ Unpack libcxx-*.tar.gz here
libcxxabi/ Unpack libcxxabi-*.tar.gz here
lld/ Unpack lld-*.tar.gz here
libunwind/ Unpack libunwind-*.tar.gz here
"""
# Extract everything into separate directories.
super(EB_Clang, self).extract_step()
# Find the full path to the directory that was unpacked from llvm-*.tar.gz.
for tmp in self.src:
if tmp['name'].startswith("llvm-"):
self.llvm_src_dir = tmp['finalpath']
break
if self.llvm_src_dir is None:
raise EasyBuildError("Could not determine LLVM source root (LLVM source was not unpacked?)")
src_dirs = {}
def find_source_dir(globpatterns, targetdir):
"""Search for directory with globpattern and rename it to targetdir"""
if not isinstance(globpatterns, list):
globpatterns = [globpatterns]
glob_src_dirs = [glob_dir for globpattern in globpatterns for glob_dir in glob.glob(globpattern)]
if len(glob_src_dirs) != 1:
raise EasyBuildError("Failed to find exactly one source directory for pattern %s: %s", globpatterns,
glob_src_dirs)
src_dirs[glob_src_dirs[0]] = targetdir
find_source_dir('compiler-rt-*', os.path.join(self.llvm_src_dir, 'projects', 'compiler-rt'))
if self.cfg["usepolly"]:
find_source_dir('polly-*', os.path.join(self.llvm_src_dir, 'tools', 'polly'))
if self.cfg["build_lld"]:
find_source_dir('lld-*', os.path.join(self.llvm_src_dir, 'tools', 'lld'))
if LooseVersion(self.version) >= LooseVersion('12.0.1'):
find_source_dir('libunwind-*', os.path.normpath(os.path.join(self.llvm_src_dir, '..', 'libunwind')))
if self.cfg["libcxx"]:
find_source_dir('libcxx-*', os.path.join(self.llvm_src_dir, 'projects', 'libcxx'))
find_source_dir('libcxxabi-*', os.path.join(self.llvm_src_dir, 'projects', 'libcxxabi'))
find_source_dir(['clang-[1-9]*', 'cfe-*'], os.path.join(self.llvm_src_dir, 'tools', 'clang'))
if self.cfg["build_extra_clang_tools"]:
find_source_dir('clang-tools-extra-*', os.path.join(self.llvm_src_dir, 'tools', 'clang', 'tools', 'extra'))
if LooseVersion(self.version) >= LooseVersion('3.8'):
find_source_dir('openmp-*', os.path.join(self.llvm_src_dir, 'projects', 'openmp'))
for src in self.src:
for (dirname, new_path) in src_dirs.items():
if src['name'].startswith(dirname):
old_path = os.path.join(src['finalpath'], dirname)
try:
shutil.move(old_path, new_path)
except IOError as err:
raise EasyBuildError("Failed to move %s to %s: %s", old_path, new_path, err)
src['finalpath'] = new_path
break
def prepare_step(self, *args, **kwargs):
"""Prepare build environment."""
super(EB_Clang, self).prepare_step(*args, **kwargs)
build_targets = self.cfg['build_targets']
if build_targets is None:
arch = get_cpu_architecture()
try:
default_targets = DEFAULT_TARGETS_MAP[arch][:]
# If CUDA is included as a dep, add NVPTX as a target
if get_software_root("CUDA"):
default_targets += ["NVPTX"]
# For AMDGPU support we need ROCR-Runtime and
# ROCT-Thunk-Interface, however, since ROCT is a dependency of
# ROCR we only check for the ROCR-Runtime here
# https://openmp.llvm.org/SupportAndFAQ.html#q-how-to-build-an-openmp-amdgpu-offload-capable-compiler
if get_software_root("ROCR-Runtime"):
default_targets += ["AMDGPU"]
self.cfg['build_targets'] = build_targets = default_targets
self.log.debug("Using %s as default build targets for CPU/GPU architecture %s.", default_targets, arch)
except KeyError:
raise EasyBuildError("No default build targets defined for CPU architecture %s.", arch)
# carry on with empty list from this point forward if no build targets are specified
if build_targets is None:
self.cfg['build_targets'] = build_targets = []
unknown_targets = [target for target in build_targets if target not in CLANG_TARGETS]
if unknown_targets:
raise EasyBuildError("Some of the chosen build targets (%s) are not in %s.",
', '.join(unknown_targets), ', '.join(CLANG_TARGETS))
if LooseVersion(self.version) < LooseVersion('3.4') and "R600" in build_targets:
raise EasyBuildError("Build target R600 not supported in < Clang-3.4")
if LooseVersion(self.version) > LooseVersion('3.3') and "MBlaze" in build_targets:
raise EasyBuildError("Build target MBlaze is not supported anymore in > Clang-3.3")
def configure_step(self):
"""Run CMake for stage 1 Clang."""
if all(dep['name'] != 'ncurses' for dep in self.cfg['dependencies']):
print_warning('Clang requires ncurses to run, did you forgot to add it to dependencies?')
self.llvm_obj_dir_stage1 = os.path.join(self.builddir, 'llvm.obj.1')
if self.cfg['bootstrap']:
self.llvm_obj_dir_stage2 = os.path.join(self.builddir, 'llvm.obj.2')
self.llvm_obj_dir_stage3 = os.path.join(self.builddir, 'llvm.obj.3')
if LooseVersion(self.version) >= LooseVersion('3.3'):
disable_san_tests = False
# all sanitizer tests will fail when there's a limit on the vmem
# this is ugly but I haven't found a cleaner way so far
(vmemlim, ec) = run_cmd("ulimit -v", regexp=False)
if not vmemlim.startswith("unlimited"):
disable_san_tests = True
self.log.warn("There is a virtual memory limit set of %s KB. The tests of the "
"sanitizers will be disabled as they need unlimited virtual "
"memory unless --strict=error is used." % vmemlim.strip())
# the same goes for unlimited stacksize
(stacklim, ec) = run_cmd("ulimit -s", regexp=False)
if stacklim.startswith("unlimited"):
disable_san_tests = True
self.log.warn("The stacksize limit is set to unlimited. This causes the ThreadSanitizer "
"to fail. The sanitizers tests will be disabled unless --strict=error is used.")
if (disable_san_tests or self.cfg['skip_sanitizer_tests']) and build_option('strict') != run.ERROR:
self.log.debug("Disabling the sanitizer tests")
self.disable_sanitizer_tests()
# Create and enter build directory.
mkdir(self.llvm_obj_dir_stage1)
change_dir(self.llvm_obj_dir_stage1)
# GCC and Clang are installed in different prefixes and Clang will not
# find the GCC installation on its own.
# First try with GCCcore, as GCC built on top of GCCcore is just a wrapper for GCCcore and binutils,
# instead of a full-fledge compiler
gcc_prefix = get_software_root('GCCcore')
# If that doesn't work, try with GCC
if gcc_prefix is None:
gcc_prefix = get_software_root('GCC')
# If that doesn't work either, print error and exit
if gcc_prefix is None:
raise EasyBuildError("Can't find GCC or GCCcore to use")
self.cfg.update('configopts', "-DGCC_INSTALL_PREFIX='%s'" % gcc_prefix)
self.log.debug("Using %s as GCC_INSTALL_PREFIX", gcc_prefix)
# Configure some default options
if self.cfg["enable_rtti"]:
self.cfg.update('configopts', '-DLLVM_REQUIRES_RTTI=ON')
self.cfg.update('configopts', '-DLLVM_ENABLE_RTTI=ON')
self.cfg.update('configopts', '-DLLVM_ENABLE_EH=ON')
if self.cfg["default_openmp_runtime"]:
self.cfg.update(
'configopts',
'-DCLANG_DEFAULT_OPENMP_RUNTIME=%s' % self.cfg["default_openmp_runtime"]
)
if self.cfg['assertions']:
self.cfg.update('configopts', "-DLLVM_ENABLE_ASSERTIONS=ON")
else:
self.cfg.update('configopts', "-DLLVM_ENABLE_ASSERTIONS=OFF")
if self.cfg["usepolly"]:
self.cfg.update('configopts', "-DLINK_POLLY_INTO_TOOLS=ON")
# If Z3 is included as a dep, enable support in static analyzer (if enabled)
if self.cfg["static_analyzer"] and LooseVersion(self.version) >= LooseVersion('9.0.0'):
z3_root = get_software_root("Z3")
if z3_root:
self.cfg.update('configopts', "-DLLVM_ENABLE_Z3_SOLVER=ON")
self.cfg.update('configopts', "-DLLVM_Z3_INSTALL_DIR=%s" % z3_root)
build_targets = self.cfg['build_targets']
if self.cfg["usepolly"] and "NVPTX" in build_targets:
self.cfg.update('configopts', "-DPOLLY_ENABLE_GPGPU_CODEGEN=ON")
self.cfg.update('configopts', '-DLLVM_TARGETS_TO_BUILD="%s"' % ';'.join(build_targets))
if self.cfg['parallel']:
self.make_parallel_opts = "-j %s" % self.cfg['parallel']
# If hwloc is included as a dep, use it in OpenMP runtime for affinity
hwloc_root = get_software_root('hwloc')
if hwloc_root:
self.cfg.update('configopts', '-DLIBOMP_USE_HWLOC=ON')
self.cfg.update('configopts', '-DLIBOMP_HWLOC_INSTALL_DIR=%s' % hwloc_root)
# If 'NVPTX' is in the build targets we assume the user would like OpenMP offload support as well
if 'NVPTX' in build_targets:
# list of CUDA compute capabilities to use can be specifed in two ways (where (2) overrules (1)):
# (1) in the easyconfig file, via the custom cuda_compute_capabilities;
# (2) in the EasyBuild configuration, via --cuda-compute-capabilities configuration option;
ec_cuda_cc = self.cfg['cuda_compute_capabilities']
cfg_cuda_cc = build_option('cuda_compute_capabilities')
cuda_cc = cfg_cuda_cc or ec_cuda_cc or []
if not cuda_cc:
raise EasyBuildError("Can't build Clang with CUDA support "
"without specifying 'cuda-compute-capabilities'")
default_cc = self.cfg['default_cuda_capability'] or min(cuda_cc)
if not self.cfg['default_cuda_capability']:
print_warning("No default CUDA capability defined! "
"Using '%s' taken as minimum from 'cuda_compute_capabilities'" % default_cc)
cuda_cc = [cc.replace('.', '') for cc in cuda_cc]
default_cc = default_cc.replace('.', '')
self.cfg.update('configopts', '-DCLANG_OPENMP_NVPTX_DEFAULT_ARCH=sm_%s' % default_cc)
self.cfg.update('configopts', '-DLIBOMPTARGET_NVPTX_COMPUTE_CAPABILITIES=%s' % ','.join(cuda_cc))
# If we don't want to build with CUDA (not in dependencies) trick CMakes FindCUDA module into not finding it by
# using the environment variable which is used as-is and later checked for a falsy value when determining
# whether CUDA was found
if not get_software_root('CUDA'):
setvar('CUDA_NVCC_EXECUTABLE', 'IGNORE')
# If 'AMDGPU' is in the build targets we assume the user would like OpenMP offload support for AMD
if 'AMDGPU' in build_targets:
if not get_software_root('ROCR-Runtime'):
raise EasyBuildError("Can't build Clang with AMDGPU support "
"without dependency 'ROCR-Runtime'")
ec_amdgfx = self.cfg['amd_gfx_list']
if not ec_amdgfx:
raise EasyBuildError("Can't build Clang with AMDGPU support "
"without specifying 'amd_gfx_list'")
self.cfg.update('configopts', '-DLIBOMPTARGET_AMDGCN_GFXLIST=%s' % ' '.join(ec_amdgfx))
self.log.info("Configuring")
super(EB_Clang, self).configure_step(srcdir=self.llvm_src_dir)
def disable_sanitizer_tests(self):
"""Disable the tests of all the sanitizers by removing the test directories from the build system"""
if LooseVersion(self.version) < LooseVersion('3.6'):
# for Clang 3.5 and lower, the tests are scattered over several CMakeLists.
# We loop over them, and patch out the rule that adds the sanitizers tests to the testsuite
patchfiles = ['lib/asan', 'lib/dfsan', 'lib/lsan', 'lib/msan', 'lib/tsan', 'lib/ubsan']
for patchfile in patchfiles:
cmakelists = os.path.join(self.llvm_src_dir, 'projects/compiler-rt', patchfile, 'CMakeLists.txt')
if os.path.exists(cmakelists):
regex_subs = [(r'.*add_subdirectory\(lit_tests\).*', '')]
apply_regex_substitutions(cmakelists, regex_subs)
# There is a common part seperate for the specific saniters, we disable all the common tests
cmakelists = os.path.join('projects', 'compiler-rt', 'lib', 'sanitizer_common', 'CMakeLists.txt')
regex_subs = [(r'.*add_subdirectory\(tests\).*', '')]
apply_regex_substitutions(cmakelists, regex_subs)
else:
# In Clang 3.6, the sanitizer tests are grouped together in one CMakeLists
# We patch out adding the subdirectories with the sanitizer tests
cmakelists_tests = os.path.join(self.llvm_src_dir, 'projects', 'compiler-rt', 'test', 'CMakeLists.txt')
regex_subs = []
if LooseVersion(self.version) >= LooseVersion('5.0'):
regex_subs.append((r'compiler_rt_test_runtime.*san.*', ''))
else:
regex_subs.append((r'add_subdirectory\((.*san|sanitizer_common)\)', ''))
apply_regex_substitutions(cmakelists_tests, regex_subs)
def build_with_prev_stage(self, prev_obj, next_obj):
"""Build Clang stage N using Clang stage N-1"""
# Create and enter build directory.
mkdir(next_obj)
change_dir(next_obj)
# Make sure clang and clang++ compilers from the previous stage are (temporarily) in PATH
# The call to prepare_rpath_wrappers() requires the compilers-to-be-wrapped on the PATH
# Also, the call to 'which' in later in this current function also requires that
orig_path = os.getenv('PATH')
prev_obj_path = os.path.join(prev_obj, 'bin')
setvar('PATH', prev_obj_path + ":" + orig_path)
# If building with rpath, create RPATH wrappers for the Clang compilers for stage 2 and 3
if build_option('rpath'):
my_clang_toolchain = Clang(name='Clang', version='1')
my_clang_toolchain.prepare_rpath_wrappers()
self.log.info("Prepared clang rpath wrappers")
# RPATH wrappers add -Wl,rpath arguments to all command lines, including when it is just compiling
# Clang by default warns about that, and then some configure tests use -Werror which turns those warnings
# into errors. As a result, those configure tests fail, even though the compiler supports the requested
# functionality (e.g. the test that checks if -fPIC is supported would fail, and it compiles without
# resulting in relocation errors).
# See https://github.com/easybuilders/easybuild-easyblocks/pull/2799#issuecomment-1270621100
# Here, we add -Wno-unused-command-line-argument to CXXFLAGS to avoid these warnings alltogether
cflags = os.getenv('CFLAGS')
cxxflags = os.getenv('CXXFLAGS')
setvar('CFLAGS', "%s %s" % (cflags, '-Wno-unused-command-line-argument'))
setvar('CXXFLAGS', "%s %s" % (cxxflags, '-Wno-unused-command-line-argument'))
# determine full path to clang/clang++ (which may be wrapper scripts in case of RPATH linking)
clang = which('clang')
clangxx = which('clang++')
# Configure.
options = [
"-DCMAKE_INSTALL_PREFIX=%s " % self.installdir,
"-DCMAKE_C_COMPILER='%s' " % clang,
"-DCMAKE_CXX_COMPILER='%s' " % clangxx,
self.cfg['configopts'],
"-DCMAKE_BUILD_TYPE=%s " % self.build_type,
]
# Cmake looks for llvm-link by default in the same directory as the compiler
# However, when compiling with rpath, the clang 'compiler' is not actually the compiler, but the wrapper
# Clearly, the wrapper directory won't llvm-link. Thus, we pass the linker to be used by full path.
# See https://github.com/easybuilders/easybuild-easyblocks/pull/2799#issuecomment-1275916186
if build_option('rpath'):
llvm_link = which('llvm-link')
options.append("-DLIBOMPTARGET_NVPTX_BC_LINKER=%s" % llvm_link)
self.log.info("Configuring")
run_cmd("cmake %s %s" % (' '.join(options), self.llvm_src_dir), log_all=True)
self.log.info("Building")
run_cmd("make %s VERBOSE=1" % self.make_parallel_opts, log_all=True)
# restore $PATH
setvar('PATH', orig_path)
def run_clang_tests(self, obj_dir):
"""Run Clang tests in specified directory (unless disabled)."""
if not self.cfg['skip_all_tests']:
change_dir(obj_dir)
self.log.info("Running tests")
run_cmd("make %s check-all" % self.make_parallel_opts, log_all=True)
def build_step(self):
"""Build Clang stage 1, 2, 3"""
# Stage 1: build using system compiler.
self.log.info("Building stage 1")
change_dir(self.llvm_obj_dir_stage1)
super(EB_Clang, self).build_step()
if self.cfg['bootstrap']:
# Stage 1: run tests.
self.run_clang_tests(self.llvm_obj_dir_stage1)
self.log.info("Building stage 2")
self.build_with_prev_stage(self.llvm_obj_dir_stage1, self.llvm_obj_dir_stage2)
self.run_clang_tests(self.llvm_obj_dir_stage2)
self.log.info("Building stage 3")
self.build_with_prev_stage(self.llvm_obj_dir_stage2, self.llvm_obj_dir_stage3)
# Don't run stage 3 tests here, do it in the test step.
def test_step(self):
"""Run Clang tests."""
if self.cfg['bootstrap']:
self.run_clang_tests(self.llvm_obj_dir_stage3)
else:
self.run_clang_tests(self.llvm_obj_dir_stage1)
def install_step(self):
"""Install stage 3 binaries."""
if self.cfg['bootstrap']:
change_dir(self.llvm_obj_dir_stage3)
else:
change_dir(self.llvm_obj_dir_stage1)
super(EB_Clang, self).install_step()
# the static analyzer is not installed by default
# we do it by hand
if self.cfg['static_analyzer'] and LooseVersion(self.version) < LooseVersion('3.8'):
try:
tools_src_dir = os.path.join(self.llvm_src_dir, 'tools', 'clang', 'tools')
analyzer_target_dir = os.path.join(self.installdir, 'libexec', 'clang-analyzer')
bindir = os.path.join(self.installdir, 'bin')
for scan_dir in ['scan-build', 'scan-view']:
shutil.copytree(os.path.join(tools_src_dir, scan_dir), os.path.join(analyzer_target_dir, scan_dir))
os.symlink(os.path.relpath(bindir, os.path.join(analyzer_target_dir, scan_dir)),
os.path.join(analyzer_target_dir, scan_dir, 'bin'))
os.symlink(os.path.relpath(os.path.join(analyzer_target_dir, scan_dir, scan_dir), bindir),
os.path.join(bindir, scan_dir))
mandir = os.path.join(self.installdir, 'share', 'man', 'man1')
os.makedirs(mandir)
shutil.copy2(os.path.join(tools_src_dir, 'scan-build', 'scan-build.1'), mandir)
except OSError as err:
raise EasyBuildError("Failed to copy static analyzer dirs to install dir: %s", err)
def post_install_step(self):
"""Install python bindings."""
super(EB_Clang, self).post_install_step()
# copy Python bindings here in post-install step so that it is not done more than once in multi_deps context
if self.cfg['python_bindings']:
python_bindings_source_dir = os.path.join(self.llvm_src_dir, "tools", "clang", "bindings", "python")
python_bindins_target_dir = os.path.join(self.installdir, 'lib', 'python')
shutil.copytree(python_bindings_source_dir, python_bindins_target_dir)
def sanity_check_step(self):
"""Custom sanity check for Clang."""
shlib_ext = get_shared_lib_ext()
custom_paths = {
'files': [
"bin/clang", "bin/clang++", "bin/llvm-ar", "bin/llvm-nm", "bin/llvm-as", "bin/opt", "bin/llvm-link",
"bin/llvm-config", "bin/llvm-symbolizer", "include/llvm-c/Core.h", "include/clang-c/Index.h",
"lib/libclang.%s" % shlib_ext, "lib/clang/%s/include/stddef.h" % self.version,
],
'dirs': ["include/clang", "include/llvm", "lib/clang/%s/lib" % self.version],
}
if self.cfg['static_analyzer']:
custom_paths['files'].extend(["bin/scan-build", "bin/scan-view"])
if self.cfg['build_extra_clang_tools'] and LooseVersion(self.version) >= LooseVersion('3.4'):
custom_paths['files'].extend(["bin/clang-tidy"])
if self.cfg["usepolly"]:
custom_paths['files'].extend(["lib/LLVMPolly.%s" % shlib_ext])
custom_paths['dirs'].extend(["include/polly"])
if self.cfg["build_lld"]:
custom_paths['files'].extend(["bin/lld"])
if self.cfg["libcxx"]:
custom_paths['files'].extend(["lib/libc++.%s" % shlib_ext])
custom_paths['files'].extend(["lib/libc++abi.%s" % shlib_ext])
if LooseVersion(self.version) >= LooseVersion('3.8'):
custom_paths['files'].extend(["lib/libomp.%s" % shlib_ext, "lib/clang/%s/include/omp.h" % self.version])
# Detect OpenMP support for CPU architecture
arch = get_cpu_architecture()
# Check architecture explicitly since Clang uses potentially
# different names
if arch == X86_64:
arch = 'x86_64'
elif arch == POWER:
arch = 'ppc64'
elif arch == AARCH64:
arch = 'aarch64'
else:
print_warning("Unknown CPU architecture (%s) for OpenMP library check!" % arch)
custom_paths['files'].extend(["lib/libomptarget.%s" % shlib_ext,
"lib/libomptarget.rtl.%s.%s" % (arch, shlib_ext)])
# If building for CUDA check that OpenMP target library was created
if 'NVPTX' in self.cfg['build_targets']:
custom_paths['files'].append("lib/libomptarget.rtl.cuda.%s" % shlib_ext)
# The static 'nvptx.a' library is not built from version 12 onwards
if LooseVersion(self.version) < LooseVersion('12.0'):
custom_paths['files'].append("lib/libomptarget-nvptx.a")
ec_cuda_cc = self.cfg['cuda_compute_capabilities']
cfg_cuda_cc = build_option('cuda_compute_capabilities')
cuda_cc = cfg_cuda_cc or ec_cuda_cc or []
# We need the CUDA capability in the form of '75' and not '7.5'
cuda_cc = [cc.replace('.', '') for cc in cuda_cc]
if LooseVersion('12.0') < LooseVersion(self.version) < LooseVersion('13.0'):
custom_paths['files'].extend(["lib/libomptarget-nvptx-cuda_%s-sm_%s.bc" % (x, y)
for x in CUDA_TOOLKIT_SUPPORT for y in cuda_cc])
else:
custom_paths['files'].extend(["lib/libomptarget-nvptx-sm_%s.bc" % cc
for cc in cuda_cc])
# From version 13, and hopefully onwards, the naming of the CUDA
# '.bc' files became a bit simpler and now we don't need to take
# into account the CUDA version Clang was compiled with, making it
# easier to check for the bitcode files we expect
if LooseVersion(self.version) >= LooseVersion('13.0'):
custom_paths['files'].extend(["lib/libomptarget-new-nvptx-sm_%s.bc" % cc
for cc in cuda_cc])
# If building for AMDGPU check that OpenMP target library was created
if 'AMDGPU' in self.cfg['build_targets']:
custom_paths['files'].append("lib/libLLVMAMDGPUCodeGen.a")
# OpenMP offloading support to AMDGPU was not added until version
# 13, however, building for the AMDGPU target predates this and so
# doesn't necessarily mean that the AMDGPU target failed
if LooseVersion(self.version) >= LooseVersion('13.0'):
custom_paths['files'].append("lib/libomptarget.rtl.amdgpu.%s" % shlib_ext)
custom_paths['files'].extend(["lib/libomptarget-amdgcn-%s.bc" % gfx
for gfx in self.cfg['amd_gfx_list']])
custom_paths['files'].append("bin/amdgpu-arch")
if LooseVersion(self.version) >= LooseVersion('14.0'):
custom_paths['files'].extend(["lib/libomptarget-new-amdgpu-%s.bc" % gfx
for gfx in self.cfg['amd_gfx_list']])
custom_commands = ['clang --help', 'clang++ --help', 'llvm-config --cxxflags']
if self.cfg['python_bindings']:
custom_paths['files'].extend([os.path.join("lib", "python", "clang", "cindex.py")])
custom_commands.extend(["python -c 'import clang'"])
super(EB_Clang, self).sanity_check_step(custom_paths=custom_paths, custom_commands=custom_commands)
def make_module_extra(self):
"""Custom variables for Clang module."""
txt = super(EB_Clang, self).make_module_extra()
# we set the symbolizer path so that asan/tsan give meanfull output by default
asan_symbolizer_path = os.path.join(self.installdir, 'bin', 'llvm-symbolizer')
txt += self.module_generator.set_environment('ASAN_SYMBOLIZER_PATH', asan_symbolizer_path)
if self.cfg['python_bindings']:
txt += self.module_generator.prepend_paths('PYTHONPATH', os.path.join("lib", "python"))
return txt