forked from ClangBuiltLinux/tc-build
-
Notifications
You must be signed in to change notification settings - Fork 3
/
build-llvm.py
executable file
·1627 lines (1349 loc) · 66 KB
/
build-llvm.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
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
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python3
# pylint: disable=invalid-name
# Description: Builds an LLVM toolchain suitable for kernel development
import argparse
import glob
import pathlib
import platform
import os
import subprocess
import shutil
import sys
import textwrap
import time
import re
from urllib import request
from urllib.error import URLError
import utils
# This is a known good revision of LLVM for building the kernel
GOOD_REVISION = 'c972e1c8b59b144599b47e8a7946ff8e531a2049'
class Directories:
def __init__(self, build_folder, install_folder, linux_folder, llvm_folder,
root_folder):
self.build_folder = build_folder
self.install_folder = install_folder
self.linux_folder = linux_folder
self.llvm_folder = llvm_folder
self.root_folder = root_folder
class EnvVars:
def __init__(self, cc, cxx, ld):
self.cc = cc
self.cxx = cxx
self.ld = ld
def clang_version(cc, root_folder):
"""
Returns Clang's version as an integer
:param cc: The compiler to check the version of
:param root_folder: Top of the script folder
:return: an int denoting the version of the given compiler
"""
command = [root_folder.joinpath("clang-version.sh"), cc]
return int(subprocess.check_output(command).decode())
def parse_parameters(root_folder):
"""
Parses parameters passed to the script into options
:param root_folder: The directory where the script is being invoked from
:return: A 'Namespace' object with all the options parsed from supplied parameters
"""
parser = argparse.ArgumentParser(
formatter_class=argparse.RawTextHelpFormatter)
clone_options = parser.add_mutually_exclusive_group()
opt_options = parser.add_mutually_exclusive_group()
parser.add_argument("--assertions",
help=textwrap.dedent("""\
In a release configuration, assertions are not enabled. Assertions can help catch
issues when compiling but it will increase compile times by 15-20%%.
"""),
action="store_true")
parser.add_argument("-b",
"--branch",
help=textwrap.dedent("""\
By default, the script builds the main branch (tip of tree) of LLVM. If you would
like to build an older branch, use this parameter. This may be helpful in tracking
down an older bug to properly bisect. This value is just passed along to 'git checkout'
so it can be a branch name, tag name, or hash (unless '--shallow-clone' is used, which
means a hash cannot be used because GitHub does not allow it).
"""),
type=str,
default="main")
parser.add_argument("-B",
"--build-folder",
help=textwrap.dedent("""\
By default, the script will create a "build" folder in the same folder as this script,
then an "llvm" folder within that one and build the files there. If you would like
that done somewhere else, pass it to this parameter. This can either be an absolute
or relative path.
"""),
type=str,
default=root_folder.joinpath("build", "llvm"))
parser.add_argument("--bolt",
help=textwrap.dedent("""\
Optimize the final clang binary with BOLT (Binary Optimization and Layout Tool), which can
often improve compile time performance by 5-7%% on average.
This is similar to Profile Guided Optimization (PGO) but it happens against the final
binary that is built. The script will:
1. Figure out if perf can be used with branch sampling. You can test this ahead of time by
running:
$ perf record --branch-filter any,u --event cycles:u --output /dev/null -- sleep 1
2. If perf cannot be used, the clang binary will be instrumented by llvm-bolt, which will
result in a much slower clang binary.
NOTE #1: When this instrumentation is combined with a build of LLVM that has already
been PGO'd (i.e., the '--pgo' flag) without LLVM's internal assertions (i.e.,
no '--assertions' flag), there might be a crash when attempting to run the
instrumented clang:
https://github.com/llvm/llvm-project/issues/55004
To avoid this, pass '--assertions' with '--bolt --pgo'.
NOTE #2: BOLT's instrumentation might not be compatible with architectures other than
x86_64 and build-llvm.py's implementation has only been validated on x86_64
machines:
https://github.com/llvm/llvm-project/issues/55005
BOLT itself only appears to support AArch64 and x86_64 as of LLVM commit
a0b8ab1ba3165d468792cf0032fce274c7d624e1.
3. A kernel will be built and profiled. This will either be the host architecture's
defconfig or the first target's defconfig if '--targets' is specified without support
for the host architecture. The profiling data will be quite large, so it is imperative
that you have ample disk space and memory when attempting to do this. With instrumentation,
a profile will be generated for each invocation (PID) of clang, so this data could easily
be a couple hundred gigabytes large.
4. The clang binary will be optimized with BOLT using the profile generated above. This can
take some time.
NOTE #3: Versions of BOLT without commit 7d7771f34d14 ("[BOLT] Compact legacy profiles")
will use significantly more memory during this stage if instrumentation is used
because the merged profile is not as slim as it could be. Either upgrade to a
version of LLVM that contains that change or pick it yourself, switch to perf if
your machine supports it, upgrade the amount of memory you have (if possible),
or run build-llvm.py without '--bolt'.
"""),
action="store_true")
opt_options.add_argument("--build-stage1-only",
help=textwrap.dedent("""\
By default, the script does a multi-stage build: it builds a more lightweight version of
LLVM first (stage 1) then uses that build to build the full toolchain (stage 2). This
is also known as bootstrapping.
This option avoids that, building the first stage as if it were the final stage. Note,
this does not install the first stage only toolchain by default to avoid overwritting an
installed mutlt-stage LLVM toolchain; this option is more intended for quick testing
and verification of issues and not regular use. However, if your system is slow or can't
handle 2+ stage builds, you may need this flag. If you would like to install a toolchain
built with this flag, see '--install-stage1-only' below.
"""),
action="store_true")
# yapf: disable
parser.add_argument("--build-type",
metavar='BUILD_TYPE',
help=textwrap.dedent("""\
By default, the script does a Release build; Debug may be useful for tracking down
particularly nasty bugs.
See https://llvm.org/docs/GettingStarted.html#compiling-the-llvm-suite-source-code for
more information.
"""),
type=str,
choices=['Release', 'Debug', 'RelWithDebInfo', 'MinSizeRel'],
default="Release")
# yapf: enable
parser.add_argument("--check-targets",
help=textwrap.dedent("""\
By default, no testing is run on the toolchain. If you would like to run unit/regression
tests, use this parameter to specify a list of check targets to run with ninja. Common
ones include check-llvm, check-clang, and check-lld.
The values passed to this parameter will be automatically concatenated with 'check-'.
Example: '--check-targets clang llvm' will make ninja invokve 'check-clang' and 'check-llvm'.
"""),
nargs="+")
parser.add_argument("--clang-vendor",
help=textwrap.dedent("""\
Add this value to the clang version string (like "Apple clang version..." or
"Android clang version..."). Useful when reverting or applying patches on top
of upstream clang to differentiate a toolchain built with this script from
upstream clang or to distinguish a toolchain built with this script from the
system's clang. Defaults to ClangBuiltLinux, can be set to an empty string to
override this and have no vendor in the version string.
"""),
type=str,
default="ClangBuiltLinux")
parser.add_argument("-D",
"--defines",
help=textwrap.dedent("""\
Specify additional cmake values. These will be applied to all cmake invocations.
Example: -D LLVM_PARALLEL_COMPILE_JOBS=2 LLVM_PARALLEL_LINK_JOBS=2
See https://llvm.org/docs/CMake.html for various cmake values. Note that some of
the options to this script correspond to cmake values.
"""),
nargs="+")
parser.add_argument("-f",
"--full-toolchain",
help=textwrap.dedent("""\
By default, the script tunes LLVM for building the Linux kernel by disabling several
projects, targets, and configuration options, which speeds up build times but limits
how the toolchain could be used.
With this option, all projects and targets are enabled and the script tries to avoid
unnecessarily turning off configuration options. The '--projects' and '--targets' options
to the script can still be used to change the list of projects and targets. This is
useful when using the script to do upstream LLVM development or trying to use LLVM as a
system-wide toolchain.
"""),
action="store_true")
parser.add_argument("-i",
"--incremental",
help=textwrap.dedent("""\
By default, the script removes all build artifacts from previous compiles. This
prevents that, allowing for dirty builds and faster compiles.
"""),
action="store_true")
parser.add_argument("-I",
"--install-folder",
help=textwrap.dedent("""\
By default, the script will create an "install" folder in the same folder as this script
and install the LLVM toolchain there. If you'd like to have it installed somewhere
else, pass it to this parameter. This can either be an absolute or relative path.
"""),
type=str,
default=root_folder.joinpath("install"))
parser.add_argument("--install-stage1-only",
help=textwrap.dedent("""\
When doing a stage 1 only build with '--build-stage1-only', install the toolchain to
the value of INSTALL_FOLDER.
"""),
action="store_true")
parser.add_argument("-l",
"--llvm-folder",
help=textwrap.dedent("""\
By default, the script will clone the llvm-project into the tc-build repo. If you have
another LLVM checkout that you would like to work out of, pass it to this parameter.
This can either be an absolute or relative path. Implies '--no-update'.
"""),
type=str)
parser.add_argument("-L",
"--linux-folder",
help=textwrap.dedent("""\
If building with PGO, use this kernel source for building profiles instead of downloading
a tarball from kernel.org. This should be the full or relative path to a complete kernel
source directory, not a tarball or zip file.
"""),
type=str)
parser.add_argument("--lto",
metavar="LTO_TYPE",
help=textwrap.dedent("""\
Build the final compiler with either ThinLTO (thin) or full LTO (full), which can
often improve compile time performance by 3-5%% on average.
Only use full LTO if you have more than 64 GB of memory. ThinLTO uses way less memory,
compiles faster because it is fully multithreaded, and it has almost identical
performance (within 1%% usually) to full LTO. The compile time impact of ThinLTO is about
5x the speed of a '--build-stage1-only' build and 3.5x the speed of a default build. LTO
is much worse and is not worth considering unless you have a server available to build on.
This option should not be used with '--build-stage1-only' unless you know that your
host compiler and linker support it. See the two links below for more information.
https://llvm.org/docs/LinkTimeOptimization.html
https://clang.llvm.org/docs/ThinLTO.html
"""),
type=str,
choices=['thin', 'full'])
parser.add_argument("-n",
"--no-update",
help=textwrap.dedent("""\
By default, the script always updates the LLVM repo before building. This prevents
that, which can be helpful during something like bisecting or manually managing the
repo to pin it to a particular revision.
"""),
action="store_true")
parser.add_argument("--no-ccache",
help=textwrap.dedent("""\
By default, the script adds LLVM_CCACHE_BUILD to the cmake options so that ccache is
used for the stage one build. This helps speed up compiles but it is only useful for
stage one, which is built using the host compiler, which usually does not change,
resulting in more cache hits. Subsequent stages will be always completely clean builds
since ccache will have no hits due to using a new compiler and it will unnecessarily
fill up the cache with files that will never be called again due to changing compilers
on the next build. This option prevents ccache from being used even at stage one, which
could be useful for benchmarking clean builds.
"""),
action="store_true")
parser.add_argument("-p",
"--projects",
help=textwrap.dedent("""\
Currently, the script only enables the clang, compiler-rt, lld, and polly folders in LLVM.
If you would like to override this, you can use this parameter and supply a list that is
supported by LLVM_ENABLE_PROJECTS.
See step #5 here: https://llvm.org/docs/GettingStarted.html#getting-started-quickly-a-summary
Example: -p \"clang;lld;libcxx\"
"""),
type=str)
opt_options.add_argument("--pgo",
metavar="PGO_BENCHMARK",
help=textwrap.dedent("""\
Build the final compiler with Profile Guided Optimization, which can often improve compile
time performance by 15-20%% on average. The script will:
1. Build a small bootstrap compiler like usual (stage 1).
2. Build an instrumented compiler with that compiler (stage 2).
3. Run the specified benchmark(s).
kernel-defconfig, kernel-allmodconfig, kernel-allyesconfig:
Download and extract kernel source from kernel.org (unless '--linux-folder' is
specified), build the necessary binutils if not found in PATH, and build some
kernels based on the requested config with the instrumented compiler (based on the
'--targets' option). If there is a build error with one of the kernels, build-llvm.py
will fail as well.
kernel-defconfig-slim, kernel-allmodconfig-slim, kernel-allyesconfig-slim:
Same as above but only one kernel will be built. If the host architecture is in the list
of targets, that architecture's requested config will be built; otherwise, the config of
the first architecture in '--targets' will be built. This will result in a less optimized
toolchain than the full variant above but it will result in less time spent profiling,
which means less build time overall. This might be worthwhile if you want to take advantage
of PGO on slower machines.
llvm:
The script will run the LLVM tests if they were requested via '--check-targets' then
build a full LLVM toolchain with the instrumented compiler.
4. Build a final compiler with the profile data generated from step 3 (stage 3).
Due to the nature of this process, '--build-stage1-only' cannot be used. There will be
three distinct LLVM build folders/compilers and several kernel builds done by default so
ensure that you have enough space on your disk to hold this (25GB should be enough) and the
time/patience to build three toolchains and kernels (will often take 5x the amount of time
as '--build-stage1-only' and 4x the amount of time as the default two-stage build that the
script does). When combined with '--lto', the compile time impact is about 9-10x of a one or
two stage builds.
See https://llvm.org/docs/HowToBuildWithPGO.html for more information.
"""),
nargs="+",
choices=[
'kernel-defconfig', 'kernel-allmodconfig',
'kernel-allyesconfig',
'kernel-defconfig-slim',
'kernel-allmodconfig-slim',
'kernel-allyesconfig-slim', 'llvm'
])
parser.add_argument("--quiet-cmake",
help=textwrap.dedent("""\
By default, the script shows all output from cmake. When this option is enabled, the
invocations of cmake will only show warnings and errors.
"""),
action="store_true")
clone_options.add_argument("-s",
"--shallow-clone",
help=textwrap.dedent("""\
Only fetch the required objects and omit history when cloning the LLVM repo. This
option is only used for the initial clone, not subsequent fetches. This can break
the script's ability to automatically update the repo to newer revisions or branches
so be careful using this. This option is really designed for continuous integration
runs, where a one off clone is necessary. A better option is usually managing the repo
yourself:
https://github.com/ClangBuiltLinux/tc-build#build-llvmpy
A couple of notes:
1. This cannot be used with '--use-good-revision'.
2. When no '--branch' is specified, only main is fetched. To work with other branches,
a branch other than main needs to be specified when the repo is first cloned.
"""),
action="store_true")
parser.add_argument("--show-build-commands",
help=textwrap.dedent("""\
By default, the script only shows the output of the comands it is running. When this option
is enabled, the invocations of cmake, ninja, and kernel/build.sh will be shown to help with
reproducing issues outside of the script.
"""),
action="store_true")
parser.add_argument("-t",
"--targets",
help=textwrap.dedent("""\
LLVM is multitargeted by default. Currently, this script only enables the arm32, aarch64,
bpf, mips, powerpc, riscv, s390, and x86 backends because that's what the Linux kernel is
currently concerned with. If you would like to override this, you can use this parameter
and supply a list that is supported by LLVM_TARGETS_TO_BUILD:
https://llvm.org/docs/CMake.html#llvm-specific-variables
Example: -t "AArch64;X86"
"""),
type=str)
clone_options.add_argument("--use-good-revision",
help=textwrap.dedent("""\
By default, the script updates LLVM to the latest tip of tree revision, which may at times be
broken or not work right. With this option, it will checkout a known good revision of LLVM
that builds and works properly. If you use this option often, please remember to update the
script as the known good revision will change.
NOTE: This option cannot be used with '--shallow-clone'.
"""),
action="store_true")
return parser.parse_args()
def linker_test(cc, ld):
"""
Test to see if the supplied ld value will work with cc -fuse=ld
:param cc: A working C compiler to compile the test program
:param ld: A linker to test -fuse=ld against
:return: 0 if the linker supports -fuse=ld, 1 otherwise
"""
cc_cmd = [cc, f'-fuse-ld={ld}', '-o', '/dev/null', '-x', 'c', '-']
try:
subprocess.run(cc_cmd,
capture_output=True,
check=True,
input='int main() { return 0; }',
text=True)
except subprocess.CalledProcessError:
return False
return True
def versioned_binaries(binary_name):
"""
Returns a list of versioned binaries that may be used on Debian/Ubuntu
:param binary_name: The name of the binary that we're checking for
:return: List of versioned binaries
"""
# There might be clang-7 to clang-16
tot_llvm_ver = 16
try:
cmakelists_url = 'https://raw.githubusercontent.com/llvm/llvm-project/main/llvm/CMakeLists.txt'
with request.urlopen(cmakelists_url) as response:
data = response.readlines()
for line in data:
line = line.decode('utf-8').strip()
if "set(LLVM_VERSION_MAJOR" in line:
tot_llvm_ver = re.search(r'\d+', line).group(0)
break
except URLError:
pass
return [f'{binary_name}-{i}' for i in range(int(tot_llvm_ver), 6, -1)]
def check_cc_ld_variables(root_folder):
"""
Sets the cc, cxx, and ld variables, which will be passed to cmake
:return: A tuple of valid cc, cxx, ld values that can be used to compile LLVM
"""
utils.print_header("Checking CC and LD")
cc, linker, ld = None, None, None
# If the user specified a C compiler, get its full path
if 'CC' in os.environ:
cc = shutil.which(os.environ['CC'])
# Otherwise, try to find one
else:
possible_compilers = versioned_binaries("clang") + ['clang', 'gcc']
for compiler in possible_compilers:
cc = shutil.which(compiler)
if cc is not None:
break
if cc is None:
raise RuntimeError(
"Neither gcc nor clang could be found on your system!")
# Evaluate if CC is a symlink. Certain packages of clang (like from
# apt.llvm.org) symlink the clang++ binary to clang++-<version> in
# /usr/bin, which then points to something like /usr/lib/llvm-<version/bin.
# This won't be found by the dumb logic below and trying to parse and figure
# out a heuristic for that is a lot more effort than just going into the
# folder that clang is actually installed in and getting clang++ from there.
cc = pathlib.Path(cc).resolve()
cc_folder = cc.parent
# If the user specified a C++ compiler, get its full path
if 'CXX' in os.environ:
cxx = shutil.which(os.environ['CXX'])
# Otherwise, use the one where CC is
else:
if "clang" in cc.stem:
cxx = "clang++"
else:
cxx = "g++"
cxx = shutil.which(cxx, path=f"{cc_folder}:{os.environ['PATH']}")
cxx = pathlib.Path(cxx.strip())
# If the user specified a linker
if 'LD' in os.environ:
# evaluate its full path with clang to avoid weird issues and check to
# see if it will work with '-fuse-ld', which is what cmake will do. Doing
# it now prevents a hard error later.
ld = os.environ['LD']
if "clang" in cc.stem and clang_version(cc, root_folder) >= 30900:
ld = shutil.which(ld)
if linker_test(cc, ld):
print(
f"LD won't work with {cc}, saving you from yourself by ignoring LD value",
flush=True)
ld = None
# If the user didn't specify a linker
else:
# and we're using clang, try to find the fastest one
if "clang" in cc.stem:
possible_linkers = ['lld', 'gold', 'bfd']
for linker in possible_linkers:
# We want to find lld wherever the clang we are using is located
ld = shutil.which(f"ld.{linker}",
path=f"{cc_folder}:{os.environ['PATH']}")
if ld is not None:
break
# If clang is older than 3.9, it won't accept absolute paths so we
# need to just pass it the name (and modify PATH so that it is found properly)
# https://github.com/llvm/llvm-project/commit/e43b7413597d8102a4412f9de41102e55f4f2ec9
if clang_version(cc, root_folder) < 30900:
os.environ['PATH'] = f"{cc_folder}:{os.environ['PATH']}"
ld = linker
# and we're using gcc, try to use gold
else:
ld = "gold"
if linker_test(cc, ld):
ld = None
# Print what binaries we are using to compile/link with so the user can
# decide if that is proper or not
print(f"CC: {cc}")
print(f"CXX: {cxx}")
if ld is not None:
ld = ld.strip()
ld_to_print = shutil.which("ld.{ld}")
if ld_to_print is None:
ld_to_print = shutil.which(ld)
print(f"LD: {ld_to_print}")
utils.flush_std_err_out()
return cc, cxx, ld
def check_dependencies():
"""
Makes sure that the base dependencies of cmake, curl, git, and ninja are installed
"""
utils.print_header("Checking dependencies")
required_commands = ["cmake", "curl", "git", "ninja"]
for command in required_commands:
output = shutil.which(command)
if output is None:
raise RuntimeError(
f"{command} could not be found, please install it!")
print(output, flush=True)
def repo_is_shallow(repo):
"""
Check if repo is a shallow clone already (looks for <repo>/.git/shallow)
:param repo: The path to the repo to check
:return: True if the repo is shallow, False if not
"""
git_dir = subprocess.check_output(["git", "rev-parse", "--git-dir"],
cwd=repo).decode().strip()
return pathlib.Path(repo).resolve().joinpath(git_dir, "shallow").exists()
def ref_exists(repo, ref):
"""
Check if ref exists using show-branch (works for branches, tags, and raw SHAs)
:param repo: The path to the repo to check
:param ref: The ref to check
:return: True if ref exits, False if not
"""
try:
subprocess.run(["git", "show-branch", ref],
capture_output=True,
check=True,
cwd=repo)
except subprocess.CalledProcessError:
return False
return True
def fetch_llvm(llvm_folder, update, shallow, ref):
"""
Download llvm if it does not exist, update it if it does
:param llvm_folder: llvm-project repo directory
:param update: Boolean indicating whether sources need to be updated or not
:param ref: The ref to checkout the monorepo to
"""
if llvm_folder.is_dir():
if update:
utils.print_header("Updating LLVM")
# Make sure repo is up to date before trying to see if checkout is possible
subprocess.run(["git", "fetch", "origin"],
check=True,
cwd=llvm_folder)
# Explain to the user how to avoid issues if their ref does not exist with
# a shallow clone.
if repo_is_shallow(llvm_folder) and not ref_exists(
llvm_folder, ref):
utils.print_error(
f"\nSupplied ref ({ref}) does not exist, cannot checkout.")
utils.print_error("To proceed, either:")
utils.print_error(
"\t1. Manage the repo yourself and pass '--no-update' to the script."
)
utils.print_error(
f"\t2. Run 'git -C {llvm_folder} fetch --unshallow origin' to get a complete repository."
)
utils.print_error(
f"\t3. Delete '{llvm_folder}' and re-run the script with '-s' + '-b <ref>' to get a full set of refs."
)
sys.exit(1)
# Do the update
subprocess.run(["git", "checkout", ref],
check=True,
cwd=llvm_folder)
local_ref = None
try:
local_ref = subprocess.check_output(
["git", "symbolic-ref", "-q", "HEAD"],
cwd=llvm_folder).decode("utf-8")
except subprocess.CalledProcessError:
# This is thrown when we're on a revision that cannot be mapped to a symbolic reference, like a tag
# or a git hash. Swallow and move on with the rest of our business.
pass
if local_ref and local_ref.startswith("refs/heads/"):
# This is a branch, pull from remote
git_pull_cmd = [
"git", "pull", "--rebase", "origin",
local_ref.strip().replace("refs/heads/", "")
]
subprocess.run(git_pull_cmd, check=True, cwd=llvm_folder)
else:
utils.print_header("Downloading LLVM")
extra_args = ()
if shallow:
extra_args = ("--depth", "1")
if ref != "main":
extra_args += ("--no-single-branch", )
git_clone_cmd = [
"git", "clone", *extra_args,
"https://github.com/llvm/llvm-project", llvm_folder
]
subprocess.run(git_clone_cmd, check=True)
subprocess.run(["git", "checkout", ref], check=True, cwd=llvm_folder)
def cleanup(build_folder, incremental):
"""
Clean up and create the build folder
:param build_folder: The build directory
:param incremental: Whether the build is incremental or not.
:return:
"""
if not incremental and build_folder.is_dir():
shutil.rmtree(build_folder)
build_folder.mkdir(parents=True, exist_ok=True)
def get_final_stage(args):
"""
Gets the final stage number, which depends on PGO or a stage one only build
:param args: The args variable generated by parse_parameters
:return: The final stage number
"""
if args.build_stage1_only:
return 1
if args.pgo:
return 3
return 2
def should_install_toolchain(args, stage):
"""
Returns true if the just built toolchain should be installed
:param args: The args variable generated by parse_parameters
:param stage: What stage we are at
:return: True when the toolchain should be installed; see function comments for more details
"""
# We shouldn't install the toolchain if we are not on the final stage
if stage != get_final_stage(args):
return False
# We shouldn't install the toolchain if the user is only building stage 1 build
# and they didn't explicitly request an install
if args.build_stage1_only and not args.install_stage1_only:
return False
# Otherwise, we should install the toolchain to the install folder
return True
def bootstrap_stage(args, stage):
"""
Returns true if we are doing a multistage build and on stage 1
:param args: The args variable generated by parse_parameters
:param stage: What stage we are at
:return: True if doing a multistage build and on stage 1, false if not
"""
return not args.build_stage1_only and stage == 1
def instrumented_stage(args, stage):
"""
Returns true if we are using PGO and on stage 2
:param args: The args variable generated by parse_parameters
:param stage: What stage we are at
:return: True if using PGO and on stage 2, false if not
"""
return args.pgo and stage == 2
def pgo_stage(stage):
"""
Returns true if LLVM is being built as a PGO benchmark
:return: True if LLVM is being built as a PGO benchmark, false if not
"""
return stage == "pgo"
def slim_cmake_defines():
"""
Generate a set of cmake defines to slim down the LLVM toolchain
:return: A set of defines
"""
# yapf: disable
defines = {
# Objective-C Automatic Reference Counting (we don't use Objective-C)
# https://clang.llvm.org/docs/AutomaticReferenceCounting.html
'CLANG_ENABLE_ARCMT': 'OFF',
# We don't (currently) use the static analyzer and it saves cycles
# according to Chromium OS:
# https://crrev.com/44702077cc9b5185fc21e99485ee4f0507722f82
'CLANG_ENABLE_STATIC_ANALYZER': 'OFF',
# We don't use the plugin system and it will remove unused symbols:
# https://crbug.com/917404
'CLANG_PLUGIN_SUPPORT': 'OFF',
# Don't build bindings; they are for other languages that the kernel does not use
'LLVM_ENABLE_BINDINGS': 'OFF',
# Don't build Ocaml documentation
'LLVM_ENABLE_OCAMLDOC': 'OFF',
# Don't build clang-tools-extras to cut down on build targets (about 400 files or so)
'LLVM_EXTERNAL_CLANG_TOOLS_EXTRA_SOURCE_DIR': '',
# Don't include documentation build targets because it is available on the web
'LLVM_INCLUDE_DOCS': 'OFF',
# Don't include example build targets to save on cmake cycles
'LLVM_INCLUDE_EXAMPLES': 'OFF'
}
# yapf: enable
return defines
def get_stage_binary(binary, dirs, stage):
"""
Generate a path from the stage bin directory for the requested binary
:param binary: Name of the binary
:param dirs: An instance of the Directories class with the paths to use
:param stage: The staged binary to use
:return: A path suitable for a cmake define
"""
return dirs.build_folder.joinpath(f"stage{stage}", "bin", binary)
def if_binary_exists(binary_name, cc):
"""
Returns the path of the requested binary if it exists and clang is being used, None if not
:param binary_name: Name of the binary
:param cc: Path to CC binary
:return: A path to binary if it exists and clang is being used, None if either condition is false
"""
binary = None
if "clang" in cc.stem:
binary = shutil.which(binary_name,
path=f"{cc.parent}:{os.environ['PATH']}")
return binary
def cc_ld_cmake_defines(dirs, env_vars, stage):
"""
Generate compiler and linker cmake defines, which change depending on what
stage we are at
:param dirs: An instance of the Directories class with the paths to use
:param env_vars: An instance of the EnvVars class with the compilers/linker to use
:param stage: What stage we are at
:return: A set of defines
"""
defines = {}
if stage == 1:
# Already figured out above
cc = env_vars.cc
cxx = env_vars.cxx
ld = env_vars.ld
# Optional to have
ar = if_binary_exists("llvm-ar", cc)
ranlib = if_binary_exists("llvm-ranlib", cc)
# Cannot be used from host due to potential incompatibilities
clang_tblgen = None
llvm_tblgen = None
else:
if pgo_stage(stage):
stage = 2
else:
stage = 1
ar = get_stage_binary("llvm-ar", dirs, stage)
cc = get_stage_binary("clang", dirs, stage)
clang_tblgen = get_stage_binary("clang-tblgen", dirs, stage)
cxx = get_stage_binary("clang++", dirs, stage)
ld = get_stage_binary("ld.lld", dirs, stage)
llvm_tblgen = get_stage_binary("llvm-tblgen", dirs, stage)
ranlib = get_stage_binary("llvm-ranlib", dirs, stage)
# Use llvm-ar for stage 2+ builds to avoid errors with bfd plugin
# bfd plugin: LLVM gold plugin has failed to create LTO module: Unknown attribute kind (60) (Producer: 'LLVM9.0.0svn' Reader: 'LLVM 8.0.0')
if ar:
defines['CMAKE_AR'] = ar
# The C compiler to use
defines['CMAKE_C_COMPILER'] = cc
if clang_tblgen:
defines['CLANG_TABLEGEN'] = clang_tblgen
# The C++ compiler to use
defines['CMAKE_CXX_COMPILER'] = cxx
# If we have a linker, use it
if ld:
defines['LLVM_USE_LINKER'] = ld
if llvm_tblgen:
defines['LLVM_TABLEGEN'] = llvm_tblgen
# Use llvm-ranlib for stage 2+ builds
if ranlib:
defines['CMAKE_RANLIB'] = ranlib
return defines
def distro_cmake_defines():
"""
Generate distribution specific cmake defines
:return: A set of defines
"""
defines = {}
# Clear Linux needs a different target to find all of the C++ header files, otherwise
# stage 2+ compiles will fail without this
# We figure this out based on the existence of x86_64-generic-linux in the C++ headers path
if glob.glob("/usr/include/c++/*/x86_64-generic-linux"):
defines['LLVM_HOST_TRIPLE'] = "x86_64-generic-linux"
# By default, the Linux triples are for glibc, which might not work on
# musl-based systems. If clang is available, get the default target triple
# from it so that clang without a '--target' flag always works.
if shutil.which("clang"):
clang_cmd = ["clang", "-print-target-triple"]
clang_cmd_out = subprocess.run(clang_cmd,
capture_output=True,
check=True)
default_target_triple = clang_cmd_out.stdout.decode('UTF-8').strip()
defines['LLVM_DEFAULT_TARGET_TRIPLE'] = default_target_triple
return defines
def project_cmake_defines(args, stage):
"""
Generate lists of projects, depending on whether a full or
kernel-focused LLVM build is being done and the stage
:param args: The args variable generated by parse_parameters
:param stage: What stage we are at
:return: A set of defines
"""
defines = {}
if args.full_toolchain:
if args.projects:
projects = args.projects
else:
projects = "all"
else:
if bootstrap_stage(args, stage):
projects = "clang;lld"
if args.bolt:
projects += ';bolt'
if args.pgo:
projects += ';compiler-rt'
elif instrumented_stage(args, stage):
projects = "clang;lld"
elif args.projects:
projects = args.projects
else:
projects = "clang;compiler-rt;lld;polly"
# Add "bolt" in the list of projects if the user is doing a one stage build
# and it is not already in the list
if args.bolt and args.build_stage1_only and projects != "all" and "bolt" not in projects:
projects += ";bolt"
defines['LLVM_ENABLE_PROJECTS'] = projects
if "compiler-rt" in projects:
if not args.full_toolchain:
# Don't build libfuzzer when compiler-rt is enabled, it invokes cmake again and we don't use it
defines['COMPILER_RT_BUILD_LIBFUZZER'] = 'OFF'
# We only use compiler-rt for the sanitizers, disable some extra stuff we don't need
# Chromium OS also does this: https://crrev.com/c/1629950
defines['COMPILER_RT_BUILD_CRT'] = 'OFF'
defines['COMPILER_RT_BUILD_XRAY'] = 'OFF'
# We don't need the sanitizers for the stage 1 bootstrap
if bootstrap_stage(args, stage):
defines['COMPILER_RT_BUILD_SANITIZERS'] = 'OFF'
# execinfo.h might not exist (Alpine Linux) but the GWP ASAN library
# depends on it. Disable the option to avoid breaking the build, the
# kernel does not depend on it.
if not pathlib.Path('/usr/include/execinfo.h').exists():
defines['COMPILER_RT_BUILD_GWP_ASAN'] = 'OFF'
return defines
def get_targets(args):
"""
Gets the list of targets for cmake and kernel/build.sh
:param args: The args variable generated by parse_parameters
:return: A string of targets suitable for cmake or kernel/build.sh
"""
if args.targets:
targets = args.targets
elif args.full_toolchain:
targets = "all"
else:
targets = "AArch64;ARM;BPF;Hexagon;Mips;PowerPC;RISCV;SystemZ;X86"
return targets
def target_cmake_defines(args, stage):
"""
Generate target cmake define, which change depending on what
stage we are at
:param args: The args variable generated by parse_parameters
:param stage: What stage we are at
:return: A set of defines
"""
defines = {}