2 # Build many configurations of glibc.
3 # Copyright (C) 2016-2023 Free Software Foundation, Inc.
4 # Copyright The GNU Toolchain Authors.
5 # This file is part of the GNU C Library.
7 # The GNU C Library is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU Lesser General Public
9 # License as published by the Free Software Foundation; either
10 # version 2.1 of the License, or (at your option) any later version.
12 # The GNU C Library is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 # Lesser General Public License for more details.
17 # You should have received a copy of the GNU Lesser General Public
18 # License along with the GNU C Library; if not, see
19 # <https://www.gnu.org/licenses/>.
21 """Build many configurations of glibc.
23 This script takes as arguments a directory name (containing a src
24 subdirectory with sources of the relevant toolchain components) and a
25 description of what to do: 'checkout', to check out sources into that
26 directory, 'bot-cycle', to run a series of checkout and build steps,
27 'bot', to run 'bot-cycle' repeatedly, 'host-libraries', to build
28 libraries required by the toolchain, 'compilers', to build
29 cross-compilers for various configurations, or 'glibcs', to build
30 glibc for various configurations and run the compilation parts of the
31 testsuite. Subsequent arguments name the versions of components to
32 check out (<component>-<version), for 'checkout', or, for actions
33 other than 'checkout' and 'bot-cycle', name configurations for which
34 compilers or glibc are to be built.
36 The 'list-compilers' command prints the name of each available
37 compiler configuration, without building anything. The 'list-glibcs'
38 command prints the name of each glibc compiler configuration, followed
39 by the space, followed by the name of the compiler configuration used
40 for building this glibc variant.
46 import email
.mime
.text
62 class _CompletedProcess
:
63 def __init__(self
, args
, returncode
, stdout
=None, stderr
=None):
65 self
.returncode
= returncode
69 def _run(*popenargs
, input=None, timeout
=None, check
=False, **kwargs
):
70 assert(timeout
is None)
71 with subprocess
.Popen(*popenargs
, **kwargs
) as process
:
73 stdout
, stderr
= process
.communicate(input)
78 returncode
= process
.poll()
79 if check
and returncode
:
80 raise subprocess
.CalledProcessError(returncode
, popenargs
)
81 return _CompletedProcess(popenargs
, returncode
, stdout
, stderr
)
86 class Context(object):
87 """The global state associated with builds in a given directory."""
89 def __init__(self
, topdir
, parallelism
, keep
, replace_sources
, strip
,
90 full_gcc
, action
, shallow
=False):
91 """Initialize the context."""
93 self
.parallelism
= parallelism
95 self
.replace_sources
= replace_sources
97 self
.full_gcc
= full_gcc
98 self
.shallow
= shallow
99 self
.srcdir
= os
.path
.join(topdir
, 'src')
100 self
.versions_json
= os
.path
.join(self
.srcdir
, 'versions.json')
101 self
.build_state_json
= os
.path
.join(topdir
, 'build-state.json')
102 self
.bot_config_json
= os
.path
.join(topdir
, 'bot-config.json')
103 self
.installdir
= os
.path
.join(topdir
, 'install')
104 self
.host_libraries_installdir
= os
.path
.join(self
.installdir
,
106 self
.builddir
= os
.path
.join(topdir
, 'build')
107 self
.logsdir
= os
.path
.join(topdir
, 'logs')
108 self
.logsdir_old
= os
.path
.join(topdir
, 'logs-old')
109 self
.makefile
= os
.path
.join(self
.builddir
, 'Makefile')
110 self
.wrapper
= os
.path
.join(self
.builddir
, 'wrapper')
111 self
.save_logs
= os
.path
.join(self
.builddir
, 'save-logs')
112 self
.script_text
= self
.get_script_text()
113 if action
not in ('checkout', 'list-compilers', 'list-glibcs'):
114 self
.build_triplet
= self
.get_build_triplet()
115 self
.glibc_version
= self
.get_glibc_version()
117 self
.glibc_configs
= {}
118 self
.makefile_pieces
= ['.PHONY: all\n']
119 self
.add_all_configs()
120 self
.load_versions_json()
121 self
.load_build_state_json()
122 self
.status_log_list
= []
123 self
.email_warning
= False
125 def get_script_text(self
):
126 """Return the text of this script."""
127 with
open(sys
.argv
[0], 'r') as f
:
131 """Re-execute this script with the same arguments."""
133 os
.execv(sys
.executable
, [sys
.executable
] + sys
.argv
)
135 def get_build_triplet(self
):
136 """Determine the build triplet with config.guess."""
137 config_guess
= os
.path
.join(self
.component_srcdir('gcc'),
139 cg_out
= subprocess
.run([config_guess
], stdout
=subprocess
.PIPE
,
140 check
=True, universal_newlines
=True).stdout
141 return cg_out
.rstrip()
143 def get_glibc_version(self
):
144 """Determine the glibc version number (major.minor)."""
145 version_h
= os
.path
.join(self
.component_srcdir('glibc'), 'version.h')
146 with
open(version_h
, 'r') as f
:
147 lines
= f
.readlines()
148 starttext
= '#define VERSION "'
150 if l
.startswith(starttext
):
151 l
= l
[len(starttext
):]
153 m
= re
.fullmatch('([0-9]+)\.([0-9]+)[.0-9]*', l
)
154 return '%s.%s' % m
.group(1, 2)
155 print('error: could not determine glibc version')
158 def add_all_configs(self
):
159 """Add all known glibc build configurations."""
160 self
.add_config(arch
='aarch64',
162 extra_glibcs
=[{'variant': 'disable-multi-arch',
163 'cfg': ['--disable-multi-arch']}])
164 self
.add_config(arch
='aarch64_be',
166 self
.add_config(arch
='arc',
168 gcc_cfg
=['--disable-multilib', '--with-cpu=hs38'])
169 self
.add_config(arch
='arc',
170 os_name
='linux-gnuhf',
171 gcc_cfg
=['--disable-multilib', '--with-cpu=hs38_linux'])
172 self
.add_config(arch
='arceb',
174 gcc_cfg
=['--disable-multilib', '--with-cpu=hs38'])
175 self
.add_config(arch
='alpha',
177 self
.add_config(arch
='arm',
178 os_name
='linux-gnueabi',
179 extra_glibcs
=[{'variant': 'v4t',
180 'ccopts': '-march=armv4t'}])
181 self
.add_config(arch
='armeb',
182 os_name
='linux-gnueabi')
183 self
.add_config(arch
='armeb',
184 os_name
='linux-gnueabi',
186 gcc_cfg
=['--with-arch=armv7-a'])
187 self
.add_config(arch
='arm',
188 os_name
='linux-gnueabihf',
189 gcc_cfg
=['--with-float=hard', '--with-cpu=arm926ej-s'],
190 extra_glibcs
=[{'variant': 'v7a',
191 'ccopts': '-march=armv7-a -mfpu=vfpv3'},
194 '-mthumb -march=armv7-a -mfpu=vfpv3'},
195 {'variant': 'v7a-disable-multi-arch',
196 'ccopts': '-march=armv7-a -mfpu=vfpv3',
197 'cfg': ['--disable-multi-arch']}])
198 self
.add_config(arch
='armeb',
199 os_name
='linux-gnueabihf',
200 gcc_cfg
=['--with-float=hard', '--with-cpu=arm926ej-s'])
201 self
.add_config(arch
='armeb',
202 os_name
='linux-gnueabihf',
204 gcc_cfg
=['--with-float=hard', '--with-arch=armv7-a',
206 self
.add_config(arch
='csky',
207 os_name
='linux-gnuabiv2',
209 gcc_cfg
=['--disable-multilib'])
210 self
.add_config(arch
='csky',
211 os_name
='linux-gnuabiv2',
212 gcc_cfg
=['--with-float=hard', '--disable-multilib'])
213 self
.add_config(arch
='hppa',
215 self
.add_config(arch
='i686',
217 self
.add_config(arch
='ia64',
219 first_gcc_cfg
=['--with-system-libunwind'],
220 binutils_cfg
=['--enable-obsolete'])
221 self
.add_config(arch
='loongarch64',
224 gcc_cfg
=['--with-abi=lp64d','--disable-multilib'])
225 self
.add_config(arch
='loongarch64',
228 gcc_cfg
=['--with-abi=lp64s','--disable-multilib'])
229 self
.add_config(arch
='m68k',
231 gcc_cfg
=['--disable-multilib'])
232 self
.add_config(arch
='m68k',
235 gcc_cfg
=['--with-arch=cf', '--disable-multilib'])
236 self
.add_config(arch
='m68k',
238 variant
='coldfire-soft',
239 gcc_cfg
=['--with-arch=cf', '--with-cpu=54455',
240 '--disable-multilib'])
241 self
.add_config(arch
='microblaze',
243 gcc_cfg
=['--disable-multilib'])
244 self
.add_config(arch
='microblazeel',
246 gcc_cfg
=['--disable-multilib'])
247 self
.add_config(arch
='mips64',
249 gcc_cfg
=['--with-mips-plt'],
250 glibcs
=[{'variant': 'n32'},
252 'ccopts': '-mabi=32'},
254 'ccopts': '-mabi=64'}])
255 self
.add_config(arch
='mips64',
258 gcc_cfg
=['--with-mips-plt', '--with-float=soft'],
259 glibcs
=[{'variant': 'n32-soft'},
262 'ccopts': '-mabi=32'},
263 {'variant': 'n64-soft',
264 'ccopts': '-mabi=64'}])
265 self
.add_config(arch
='mips64',
268 gcc_cfg
=['--with-mips-plt', '--with-nan=2008',
269 '--with-arch-64=mips64r2',
270 '--with-arch-32=mips32r2'],
271 glibcs
=[{'variant': 'n32-nan2008'},
272 {'variant': 'nan2008',
274 'ccopts': '-mabi=32'},
275 {'variant': 'n64-nan2008',
276 'ccopts': '-mabi=64'}])
277 self
.add_config(arch
='mips64',
279 variant
='nan2008-soft',
280 gcc_cfg
=['--with-mips-plt', '--with-nan=2008',
281 '--with-arch-64=mips64r2',
282 '--with-arch-32=mips32r2',
283 '--with-float=soft'],
284 glibcs
=[{'variant': 'n32-nan2008-soft'},
285 {'variant': 'nan2008-soft',
287 'ccopts': '-mabi=32'},
288 {'variant': 'n64-nan2008-soft',
289 'ccopts': '-mabi=64'}])
290 self
.add_config(arch
='mips64el',
292 gcc_cfg
=['--with-mips-plt'],
293 glibcs
=[{'variant': 'n32'},
295 'ccopts': '-mabi=32'},
297 'ccopts': '-mabi=64'}])
298 self
.add_config(arch
='mips64el',
301 gcc_cfg
=['--with-mips-plt', '--with-float=soft'],
302 glibcs
=[{'variant': 'n32-soft'},
305 'ccopts': '-mabi=32'},
306 {'variant': 'n64-soft',
307 'ccopts': '-mabi=64'}])
308 self
.add_config(arch
='mips64el',
311 gcc_cfg
=['--with-mips-plt', '--with-nan=2008',
312 '--with-arch-64=mips64r2',
313 '--with-arch-32=mips32r2'],
314 glibcs
=[{'variant': 'n32-nan2008'},
315 {'variant': 'nan2008',
317 'ccopts': '-mabi=32'},
318 {'variant': 'n64-nan2008',
319 'ccopts': '-mabi=64'}])
320 self
.add_config(arch
='mips64el',
322 variant
='nan2008-soft',
323 gcc_cfg
=['--with-mips-plt', '--with-nan=2008',
324 '--with-arch-64=mips64r2',
325 '--with-arch-32=mips32r2',
326 '--with-float=soft'],
327 glibcs
=[{'variant': 'n32-nan2008-soft'},
328 {'variant': 'nan2008-soft',
330 'ccopts': '-mabi=32'},
331 {'variant': 'n64-nan2008-soft',
332 'ccopts': '-mabi=64'}])
333 self
.add_config(arch
='mipsisa64r6el',
335 gcc_cfg
=['--with-mips-plt', '--with-nan=2008',
336 '--with-arch-64=mips64r6',
337 '--with-arch-32=mips32r6',
338 '--with-float=hard'],
339 glibcs
=[{'variant': 'n32'},
340 {'arch': 'mipsisa32r6el',
341 'ccopts': '-mabi=32'},
343 'ccopts': '-mabi=64'}])
344 self
.add_config(arch
='nios2',
346 self
.add_config(arch
='or1k',
349 gcc_cfg
=['--with-multilib-list=mcmov'])
350 self
.add_config(arch
='powerpc',
352 gcc_cfg
=['--disable-multilib', '--enable-secureplt'],
353 extra_glibcs
=[{'variant': 'power4',
354 'ccopts': '-mcpu=power4',
355 'cfg': ['--with-cpu=power4']}])
356 self
.add_config(arch
='powerpc',
359 gcc_cfg
=['--disable-multilib', '--with-float=soft',
360 '--enable-secureplt'])
361 self
.add_config(arch
='powerpc64',
363 gcc_cfg
=['--disable-multilib', '--enable-secureplt'])
364 self
.add_config(arch
='powerpc64le',
366 gcc_cfg
=['--disable-multilib', '--enable-secureplt'],
367 extra_glibcs
=[{'variant': 'disable-multi-arch',
368 'cfg': ['--disable-multi-arch']}])
369 self
.add_config(arch
='riscv32',
371 variant
='rv32imac-ilp32',
372 gcc_cfg
=['--with-arch=rv32imac', '--with-abi=ilp32',
373 '--disable-multilib'])
374 self
.add_config(arch
='riscv32',
376 variant
='rv32imafdc-ilp32',
377 gcc_cfg
=['--with-arch=rv32imafdc', '--with-abi=ilp32',
378 '--disable-multilib'])
379 self
.add_config(arch
='riscv32',
381 variant
='rv32imafdc-ilp32d',
382 gcc_cfg
=['--with-arch=rv32imafdc', '--with-abi=ilp32d',
383 '--disable-multilib'])
384 self
.add_config(arch
='riscv64',
386 variant
='rv64imac-lp64',
387 gcc_cfg
=['--with-arch=rv64imac', '--with-abi=lp64',
388 '--disable-multilib'])
389 self
.add_config(arch
='riscv64',
391 variant
='rv64imafdc-lp64',
392 gcc_cfg
=['--with-arch=rv64imafdc', '--with-abi=lp64',
393 '--disable-multilib'])
394 self
.add_config(arch
='riscv64',
396 variant
='rv64imafdc-lp64d',
397 gcc_cfg
=['--with-arch=rv64imafdc', '--with-abi=lp64d',
398 '--disable-multilib'])
399 self
.add_config(arch
='s390x',
402 {'arch': 's390', 'ccopts': '-m31'}],
403 extra_glibcs
=[{'variant': 'O3',
405 self
.add_config(arch
='sh3',
407 self
.add_config(arch
='sh3eb',
409 self
.add_config(arch
='sh4',
411 self
.add_config(arch
='sh4eb',
413 self
.add_config(arch
='sh4',
416 gcc_cfg
=['--without-fp'])
417 self
.add_config(arch
='sh4eb',
420 gcc_cfg
=['--without-fp'])
421 self
.add_config(arch
='sparc64',
425 'ccopts': '-m32 -mlong-double-128 -mcpu=v9'}],
426 extra_glibcs
=[{'variant': 'leon3',
428 'ccopts' : '-m32 -mlong-double-128 -mcpu=leon3'},
429 {'variant': 'disable-multi-arch',
430 'cfg': ['--disable-multi-arch']},
431 {'variant': 'disable-multi-arch',
433 'ccopts': '-m32 -mlong-double-128 -mcpu=v9',
434 'cfg': ['--disable-multi-arch']}])
435 self
.add_config(arch
='x86_64',
437 gcc_cfg
=['--with-multilib-list=m64,m32,mx32'],
439 {'variant': 'x32', 'ccopts': '-mx32'},
440 {'arch': 'i686', 'ccopts': '-m32 -march=i686'}],
441 extra_glibcs
=[{'variant': 'disable-multi-arch',
442 'cfg': ['--disable-multi-arch']},
443 {'variant': 'minimal',
444 'cfg': ['--disable-multi-arch',
446 '--disable-timezone-tools',
449 '--disable-build-nscd',
451 {'variant': 'no-pie',
452 'cfg': ['--disable-default-pie']},
453 {'variant': 'x32-no-pie',
455 'cfg': ['--disable-default-pie']},
456 {'variant': 'no-pie',
458 'ccopts': '-m32 -march=i686',
459 'cfg': ['--disable-default-pie']},
460 {'variant': 'disable-multi-arch',
462 'ccopts': '-m32 -march=i686',
463 'cfg': ['--disable-multi-arch']},
465 'ccopts': '-m32 -march=i486'},
467 'ccopts': '-m32 -march=i586'}])
468 self
.add_config(arch
='x86_64',
470 gcc_cfg
=['--disable-multilib'])
472 def add_config(self
, **args
):
473 """Add an individual build configuration."""
474 cfg
= Config(self
, **args
)
475 if cfg
.name
in self
.configs
:
476 print('error: duplicate config %s' % cfg
.name
)
478 self
.configs
[cfg
.name
] = cfg
479 for c
in cfg
.all_glibcs
:
480 if c
.name
in self
.glibc_configs
:
481 print('error: duplicate glibc config %s' % c
.name
)
483 self
.glibc_configs
[c
.name
] = c
485 def component_srcdir(self
, component
):
486 """Return the source directory for a given component, e.g. gcc."""
487 return os
.path
.join(self
.srcdir
, component
)
489 def component_builddir(self
, action
, config
, component
, subconfig
=None):
490 """Return the directory to use for a build."""
493 assert subconfig
is None
494 return os
.path
.join(self
.builddir
, action
, component
)
495 if subconfig
is None:
496 return os
.path
.join(self
.builddir
, action
, config
, component
)
498 # glibc build as part of compiler build.
499 return os
.path
.join(self
.builddir
, action
, config
, component
,
502 def compiler_installdir(self
, config
):
503 """Return the directory in which to install a compiler."""
504 return os
.path
.join(self
.installdir
, 'compilers', config
)
506 def compiler_bindir(self
, config
):
507 """Return the directory in which to find compiler binaries."""
508 return os
.path
.join(self
.compiler_installdir(config
), 'bin')
510 def compiler_sysroot(self
, config
):
511 """Return the sysroot directory for a compiler."""
512 return os
.path
.join(self
.compiler_installdir(config
), 'sysroot')
514 def glibc_installdir(self
, config
):
515 """Return the directory in which to install glibc."""
516 return os
.path
.join(self
.installdir
, 'glibcs', config
)
518 def run_builds(self
, action
, configs
):
519 """Run the requested builds."""
520 if action
== 'checkout':
521 self
.checkout(configs
)
523 if action
== 'bot-cycle':
525 print('error: configurations specified for bot-cycle')
531 print('error: configurations specified for bot')
535 if action
in ('host-libraries', 'list-compilers',
536 'list-glibcs') and configs
:
537 print('error: configurations specified for ' + action
)
539 if action
== 'list-compilers':
540 for name
in sorted(self
.configs
.keys()):
543 if action
== 'list-glibcs':
544 for config
in sorted(self
.glibc_configs
.values(),
545 key
=lambda c
: c
.name
):
546 print(config
.name
, config
.compiler
.name
)
548 self
.clear_last_build_state(action
)
549 build_time
= datetime
.datetime
.utcnow()
550 if action
== 'host-libraries':
551 build_components
= ('gmp', 'mpfr', 'mpc')
554 self
.build_host_libraries()
555 elif action
== 'compilers':
556 build_components
= ('binutils', 'gcc', 'glibc', 'linux', 'mig',
558 old_components
= ('gmp', 'mpfr', 'mpc')
559 old_versions
= self
.build_state
['host-libraries']['build-versions']
560 self
.build_compilers(configs
)
562 build_components
= ('glibc',)
563 old_components
= ('gmp', 'mpfr', 'mpc', 'binutils', 'gcc', 'linux',
564 'mig', 'gnumach', 'hurd')
565 old_versions
= self
.build_state
['compilers']['build-versions']
566 if action
== 'update-syscalls':
567 self
.update_syscalls(configs
)
569 self
.build_glibcs(configs
)
573 # Partial build, do not update stored state.
576 for k
in build_components
:
577 if k
in self
.versions
:
578 build_versions
[k
] = {'version': self
.versions
[k
]['version'],
579 'revision': self
.versions
[k
]['revision']}
580 for k
in old_components
:
581 if k
in old_versions
:
582 build_versions
[k
] = {'version': old_versions
[k
]['version'],
583 'revision': old_versions
[k
]['revision']}
584 self
.update_build_state(action
, build_time
, build_versions
)
587 def remove_dirs(*args
):
588 """Remove directories and their contents if they exist."""
590 shutil
.rmtree(dir, ignore_errors
=True)
593 def remove_recreate_dirs(*args
):
594 """Remove directories if they exist, and create them as empty."""
595 Context
.remove_dirs(*args
)
597 os
.makedirs(dir, exist_ok
=True)
599 def add_makefile_cmdlist(self
, target
, cmdlist
, logsdir
):
600 """Add makefile text for a list of commands."""
601 commands
= cmdlist
.makefile_commands(self
.wrapper
, logsdir
)
602 self
.makefile_pieces
.append('all: %s\n.PHONY: %s\n%s:\n%s\n' %
603 (target
, target
, target
, commands
))
604 self
.status_log_list
.extend(cmdlist
.status_logs(logsdir
))
606 def write_files(self
):
607 """Write out the Makefile and wrapper script."""
608 mftext
= ''.join(self
.makefile_pieces
)
609 with
open(self
.makefile
, 'w') as f
:
619 'prev_status=$prev_base-status.txt\n'
620 'this_status=$this_base-status.txt\n'
621 'this_log=$this_base-log.txt\n'
622 'date > "$this_log"\n'
623 'echo >> "$this_log"\n'
624 'echo "Description: $desc" >> "$this_log"\n'
625 'printf "%s" "Command:" >> "$this_log"\n'
626 'for word in "$@"; do\n'
627 ' if expr "$word" : "[]+,./0-9@A-Z_a-z-]\\\\{1,\\\\}\\$" > /dev/null; then\n'
628 ' printf " %s" "$word"\n'
631 ' printf "%s" "$word" | sed -e "s/\'/\'\\\\\\\\\'\'/"\n'
634 'done >> "$this_log"\n'
635 'echo >> "$this_log"\n'
636 'echo "Directory: $dir" >> "$this_log"\n'
637 'echo "Path addition: $path" >> "$this_log"\n'
638 'echo >> "$this_log"\n'
641 ' echo >> "$this_log"\n'
642 ' echo "$1: $desc" > "$this_status"\n'
643 ' echo "$1: $desc" >> "$this_log"\n'
644 ' echo >> "$this_log"\n'
645 ' date >> "$this_log"\n'
646 ' echo "$1: $desc"\n'
651 ' if [ "$1" != "0" ]; then\n'
652 ' record_status FAIL\n'
655 'if [ "$prev_base" ] && ! grep -q "^PASS" "$prev_status"; then\n'
656 ' record_status UNRESOLVED\n'
658 'if [ "$dir" ]; then\n'
660 ' check_error "$?"\n'
662 'if [ "$path" ]; then\n'
663 ' PATH=$path:$PATH\n'
665 '"$@" < /dev/null >> "$this_log" 2>&1\n'
667 'record_status PASS\n')
668 with
open(self
.wrapper
, 'w') as f
:
669 f
.write(wrapper_text
)
671 mode_exec
= (stat
.S_IRWXU|stat
.S_IRGRP|stat
.S_IXGRP|
672 stat
.S_IROTH|stat
.S_IXOTH
)
673 os
.chmod(self
.wrapper
, mode_exec
)
676 'if ! [ -f tests.sum ]; then\n'
677 ' echo "No test summary available."\n'
682 ' echo "Contents of $1:"\n'
686 ' echo "End of contents of $1."\n'
689 'save_file tests.sum\n'
690 'non_pass_tests=$(grep -v "^PASS: " tests.sum | sed -e "s/^PASS: //")\n'
691 'for t in $non_pass_tests; do\n'
692 ' if [ -f "$t.out" ]; then\n'
693 ' save_file "$t.out"\n'
696 with
open(self
.save_logs
, 'w') as f
:
697 f
.write(save_logs_text
)
698 os
.chmod(self
.save_logs
, mode_exec
)
701 """Do the actual build."""
702 cmd
= ['make', '-O', '-j%d' % self
.parallelism
]
703 subprocess
.run(cmd
, cwd
=self
.builddir
, check
=True)
705 def build_host_libraries(self
):
706 """Build the host libraries."""
707 installdir
= self
.host_libraries_installdir
708 builddir
= os
.path
.join(self
.builddir
, 'host-libraries')
709 logsdir
= os
.path
.join(self
.logsdir
, 'host-libraries')
710 self
.remove_recreate_dirs(installdir
, builddir
, logsdir
)
711 cmdlist
= CommandList('host-libraries', self
.keep
)
712 self
.build_host_library(cmdlist
, 'gmp')
713 self
.build_host_library(cmdlist
, 'mpfr',
714 ['--with-gmp=%s' % installdir
])
715 self
.build_host_library(cmdlist
, 'mpc',
716 ['--with-gmp=%s' % installdir
,
717 '--with-mpfr=%s' % installdir
])
718 cmdlist
.add_command('done', ['touch', os
.path
.join(installdir
, 'ok')])
719 self
.add_makefile_cmdlist('host-libraries', cmdlist
, logsdir
)
721 def build_host_library(self
, cmdlist
, lib
, extra_opts
=None):
722 """Build one host library."""
723 srcdir
= self
.component_srcdir(lib
)
724 builddir
= self
.component_builddir('host-libraries', None, lib
)
725 installdir
= self
.host_libraries_installdir
726 cmdlist
.push_subdesc(lib
)
727 cmdlist
.create_use_dir(builddir
)
728 cfg_cmd
= [os
.path
.join(srcdir
, 'configure'),
729 '--prefix=%s' % installdir
,
732 cfg_cmd
.extend (extra_opts
)
733 cmdlist
.add_command('configure', cfg_cmd
)
734 cmdlist
.add_command('build', ['make'])
735 cmdlist
.add_command('check', ['make', 'check'])
736 cmdlist
.add_command('install', ['make', 'install'])
737 cmdlist
.cleanup_dir()
738 cmdlist
.pop_subdesc()
740 def build_compilers(self
, configs
):
741 """Build the compilers."""
743 self
.remove_dirs(os
.path
.join(self
.builddir
, 'compilers'))
744 self
.remove_dirs(os
.path
.join(self
.installdir
, 'compilers'))
745 self
.remove_dirs(os
.path
.join(self
.logsdir
, 'compilers'))
746 configs
= sorted(self
.configs
.keys())
748 self
.configs
[c
].build()
750 def build_glibcs(self
, configs
):
751 """Build the glibcs."""
753 self
.remove_dirs(os
.path
.join(self
.builddir
, 'glibcs'))
754 self
.remove_dirs(os
.path
.join(self
.installdir
, 'glibcs'))
755 self
.remove_dirs(os
.path
.join(self
.logsdir
, 'glibcs'))
756 configs
= sorted(self
.glibc_configs
.keys())
758 self
.glibc_configs
[c
].build()
760 def update_syscalls(self
, configs
):
761 """Update the glibc syscall lists."""
763 self
.remove_dirs(os
.path
.join(self
.builddir
, 'update-syscalls'))
764 self
.remove_dirs(os
.path
.join(self
.logsdir
, 'update-syscalls'))
765 configs
= sorted(self
.glibc_configs
.keys())
767 self
.glibc_configs
[c
].update_syscalls()
769 def load_versions_json(self
):
770 """Load information about source directory versions."""
771 if not os
.access(self
.versions_json
, os
.F_OK
):
774 with
open(self
.versions_json
, 'r') as f
:
775 self
.versions
= json
.load(f
)
777 def store_json(self
, data
, filename
):
778 """Store information in a JSON file."""
779 filename_tmp
= filename
+ '.tmp'
780 with
open(filename_tmp
, 'w') as f
:
781 json
.dump(data
, f
, indent
=2, sort_keys
=True)
782 os
.rename(filename_tmp
, filename
)
784 def store_versions_json(self
):
785 """Store information about source directory versions."""
786 self
.store_json(self
.versions
, self
.versions_json
)
788 def set_component_version(self
, component
, version
, explicit
, revision
):
789 """Set the version information for a component."""
790 self
.versions
[component
] = {'version': version
,
791 'explicit': explicit
,
792 'revision': revision
}
793 self
.store_versions_json()
795 def checkout(self
, versions
):
796 """Check out the desired component versions."""
797 default_versions
= {'binutils': 'vcs-2.40',
799 'glibc': 'vcs-mainline',
804 'mig': 'vcs-mainline',
805 'gnumach': 'vcs-mainline',
806 'hurd': 'vcs-mainline'}
808 explicit_versions
= {}
811 for k
in default_versions
.keys():
815 if k
in use_versions
:
816 print('error: multiple versions for %s' % k
)
819 explicit_versions
[k
] = True
823 print('error: unknown component in %s' % v
)
825 for k
in default_versions
.keys():
826 if k
not in use_versions
:
827 if k
in self
.versions
and self
.versions
[k
]['explicit']:
828 use_versions
[k
] = self
.versions
[k
]['version']
829 explicit_versions
[k
] = True
831 use_versions
[k
] = default_versions
[k
]
832 explicit_versions
[k
] = False
833 os
.makedirs(self
.srcdir
, exist_ok
=True)
834 for k
in sorted(default_versions
.keys()):
835 update
= os
.access(self
.component_srcdir(k
), os
.F_OK
)
838 k
in self
.versions
and
839 v
!= self
.versions
[k
]['version']):
840 if not self
.replace_sources
:
841 print('error: version of %s has changed from %s to %s, '
842 'use --replace-sources to check out again' %
843 (k
, self
.versions
[k
]['version'], v
))
845 shutil
.rmtree(self
.component_srcdir(k
))
847 if v
.startswith('vcs-'):
848 revision
= self
.checkout_vcs(k
, v
[4:], update
)
850 self
.checkout_tar(k
, v
, update
)
852 self
.set_component_version(k
, v
, explicit_versions
[k
], revision
)
853 if self
.get_script_text() != self
.script_text
:
854 # Rerun the checkout process in case the updated script
855 # uses different default versions or new components.
858 def checkout_vcs(self
, component
, version
, update
):
859 """Check out the given version of the given component from version
860 control. Return a revision identifier."""
861 if component
== 'binutils':
862 git_url
= 'https://sourceware.org/git/binutils-gdb.git'
863 if version
== 'mainline':
864 git_branch
= 'master'
866 trans
= str.maketrans({'.': '_'})
867 git_branch
= 'binutils-%s-branch' % version
.translate(trans
)
868 return self
.git_checkout(component
, git_url
, git_branch
, update
)
869 elif component
== 'gcc':
870 if version
== 'mainline':
873 branch
= 'releases/gcc-%s' % version
874 return self
.gcc_checkout(branch
, update
)
875 elif component
== 'glibc':
876 git_url
= 'https://sourceware.org/git/glibc.git'
877 if version
== 'mainline':
878 git_branch
= 'master'
880 git_branch
= 'release/%s/master' % version
881 r
= self
.git_checkout(component
, git_url
, git_branch
, update
)
882 self
.fix_glibc_timestamps()
884 elif component
== 'gnumach':
885 git_url
= 'git://git.savannah.gnu.org/hurd/gnumach.git'
886 git_branch
= 'master'
887 r
= self
.git_checkout(component
, git_url
, git_branch
, update
)
888 subprocess
.run(['autoreconf', '-i'],
889 cwd
=self
.component_srcdir(component
), check
=True)
891 elif component
== 'mig':
892 git_url
= 'git://git.savannah.gnu.org/hurd/mig.git'
893 git_branch
= 'master'
894 r
= self
.git_checkout(component
, git_url
, git_branch
, update
)
895 subprocess
.run(['autoreconf', '-i'],
896 cwd
=self
.component_srcdir(component
), check
=True)
898 elif component
== 'hurd':
899 git_url
= 'git://git.savannah.gnu.org/hurd/hurd.git'
900 git_branch
= 'master'
901 r
= self
.git_checkout(component
, git_url
, git_branch
, update
)
902 subprocess
.run(['autoconf'],
903 cwd
=self
.component_srcdir(component
), check
=True)
906 print('error: component %s coming from VCS' % component
)
909 def git_checkout(self
, component
, git_url
, git_branch
, update
):
910 """Check out a component from git. Return a commit identifier."""
912 subprocess
.run(['git', 'remote', 'prune', 'origin'],
913 cwd
=self
.component_srcdir(component
), check
=True)
914 if self
.replace_sources
:
915 subprocess
.run(['git', 'clean', '-dxfq'],
916 cwd
=self
.component_srcdir(component
), check
=True)
917 subprocess
.run(['git', 'pull', '-q'],
918 cwd
=self
.component_srcdir(component
), check
=True)
921 depth_arg
= ('--depth', '1')
924 subprocess
.run(['git', 'clone', '-q', '-b', git_branch
,
926 self
.component_srcdir(component
)], check
=True)
927 r
= subprocess
.run(['git', 'rev-parse', 'HEAD'],
928 cwd
=self
.component_srcdir(component
),
929 stdout
=subprocess
.PIPE
,
930 check
=True, universal_newlines
=True).stdout
933 def fix_glibc_timestamps(self
):
934 """Fix timestamps in a glibc checkout."""
935 # Ensure that builds do not try to regenerate generated files
936 # in the source tree.
937 srcdir
= self
.component_srcdir('glibc')
938 # These files have Makefile dependencies to regenerate them in
939 # the source tree that may be active during a normal build.
940 # Some other files have such dependencies but do not need to
941 # be touched because nothing in a build depends on the files
943 for f
in ('sysdeps/mach/hurd/bits/errno.h',):
944 to_touch
= os
.path
.join(srcdir
, f
)
945 subprocess
.run(['touch', '-c', to_touch
], check
=True)
946 for dirpath
, dirnames
, filenames
in os
.walk(srcdir
):
948 if (f
== 'configure' or
949 f
== 'preconfigure' or
950 f
.endswith('-kw.h')):
951 to_touch
= os
.path
.join(dirpath
, f
)
952 subprocess
.run(['touch', to_touch
], check
=True)
954 def gcc_checkout(self
, branch
, update
):
955 """Check out GCC from git. Return the commit identifier."""
956 if os
.access(os
.path
.join(self
.component_srcdir('gcc'), '.svn'),
958 if not self
.replace_sources
:
959 print('error: GCC has moved from SVN to git, use '
960 '--replace-sources to check out again')
962 shutil
.rmtree(self
.component_srcdir('gcc'))
965 self
.git_checkout('gcc', 'https://gcc.gnu.org/git/gcc.git',
967 subprocess
.run(['contrib/gcc_update', '--silent'],
968 cwd
=self
.component_srcdir('gcc'), check
=True)
969 r
= subprocess
.run(['git', 'rev-parse', 'HEAD'],
970 cwd
=self
.component_srcdir('gcc'),
971 stdout
=subprocess
.PIPE
,
972 check
=True, universal_newlines
=True).stdout
975 def checkout_tar(self
, component
, version
, update
):
976 """Check out the given version of the given component from a
980 url_map
= {'binutils': 'https://ftp.gnu.org/gnu/binutils/binutils-%(version)s.tar.bz2',
981 'gcc': 'https://ftp.gnu.org/gnu/gcc/gcc-%(version)s/gcc-%(version)s.tar.gz',
982 'gmp': 'https://ftp.gnu.org/gnu/gmp/gmp-%(version)s.tar.xz',
983 'linux': 'https://www.kernel.org/pub/linux/kernel/v%(major)s.x/linux-%(version)s.tar.xz',
984 'mpc': 'https://ftp.gnu.org/gnu/mpc/mpc-%(version)s.tar.gz',
985 'mpfr': 'https://ftp.gnu.org/gnu/mpfr/mpfr-%(version)s.tar.xz',
986 'mig': 'https://ftp.gnu.org/gnu/mig/mig-%(version)s.tar.bz2',
987 'gnumach': 'https://ftp.gnu.org/gnu/gnumach/gnumach-%(version)s.tar.bz2',
988 'hurd': 'https://ftp.gnu.org/gnu/hurd/hurd-%(version)s.tar.bz2'}
989 if component
not in url_map
:
990 print('error: component %s coming from tarball' % component
)
992 version_major
= version
.split('.')[0]
993 url
= url_map
[component
] % {'version': version
, 'major': version_major
}
994 filename
= os
.path
.join(self
.srcdir
, url
.split('/')[-1])
995 response
= urllib
.request
.urlopen(url
)
996 data
= response
.read()
997 with
open(filename
, 'wb') as f
:
999 subprocess
.run(['tar', '-C', self
.srcdir
, '-x', '-f', filename
],
1001 os
.rename(os
.path
.join(self
.srcdir
, '%s-%s' % (component
, version
)),
1002 self
.component_srcdir(component
))
1005 def load_build_state_json(self
):
1006 """Load information about the state of previous builds."""
1007 if os
.access(self
.build_state_json
, os
.F_OK
):
1008 with
open(self
.build_state_json
, 'r') as f
:
1009 self
.build_state
= json
.load(f
)
1011 self
.build_state
= {}
1012 for k
in ('host-libraries', 'compilers', 'glibcs', 'update-syscalls'):
1013 if k
not in self
.build_state
:
1014 self
.build_state
[k
] = {}
1015 if 'build-time' not in self
.build_state
[k
]:
1016 self
.build_state
[k
]['build-time'] = ''
1017 if 'build-versions' not in self
.build_state
[k
]:
1018 self
.build_state
[k
]['build-versions'] = {}
1019 if 'build-results' not in self
.build_state
[k
]:
1020 self
.build_state
[k
]['build-results'] = {}
1021 if 'result-changes' not in self
.build_state
[k
]:
1022 self
.build_state
[k
]['result-changes'] = {}
1023 if 'ever-passed' not in self
.build_state
[k
]:
1024 self
.build_state
[k
]['ever-passed'] = []
1026 def store_build_state_json(self
):
1027 """Store information about the state of previous builds."""
1028 self
.store_json(self
.build_state
, self
.build_state_json
)
1030 def clear_last_build_state(self
, action
):
1031 """Clear information about the state of part of the build."""
1032 # We clear the last build time and versions when starting a
1033 # new build. The results of the last build are kept around,
1034 # as comparison is still meaningful if this build is aborted
1035 # and a new one started.
1036 self
.build_state
[action
]['build-time'] = ''
1037 self
.build_state
[action
]['build-versions'] = {}
1038 self
.store_build_state_json()
1040 def update_build_state(self
, action
, build_time
, build_versions
):
1041 """Update the build state after a build."""
1042 build_time
= build_time
.replace(microsecond
=0)
1043 self
.build_state
[action
]['build-time'] = str(build_time
)
1044 self
.build_state
[action
]['build-versions'] = build_versions
1046 for log
in self
.status_log_list
:
1047 with
open(log
, 'r') as f
:
1049 log_text
= log_text
.rstrip()
1050 m
= re
.fullmatch('([A-Z]+): (.*)', log_text
)
1052 test_name
= m
.group(2)
1053 assert test_name
not in build_results
1054 build_results
[test_name
] = result
1055 old_build_results
= self
.build_state
[action
]['build-results']
1056 self
.build_state
[action
]['build-results'] = build_results
1058 all_tests
= set(old_build_results
.keys()) |
set(build_results
.keys())
1060 if t
in old_build_results
:
1061 old_res
= old_build_results
[t
]
1063 old_res
= '(New test)'
1064 if t
in build_results
:
1065 new_res
= build_results
[t
]
1067 new_res
= '(Test removed)'
1068 if old_res
!= new_res
:
1069 result_changes
[t
] = '%s -> %s' % (old_res
, new_res
)
1070 self
.build_state
[action
]['result-changes'] = result_changes
1071 old_ever_passed
= {t
for t
in self
.build_state
[action
]['ever-passed']
1072 if t
in build_results
}
1073 new_passes
= {t
for t
in build_results
if build_results
[t
] == 'PASS'}
1074 self
.build_state
[action
]['ever-passed'] = sorted(old_ever_passed |
1076 self
.store_build_state_json()
1078 def load_bot_config_json(self
):
1079 """Load bot configuration."""
1080 with
open(self
.bot_config_json
, 'r') as f
:
1081 self
.bot_config
= json
.load(f
)
1083 def part_build_old(self
, action
, delay
):
1084 """Return whether the last build for a given action was at least a
1085 given number of seconds ago, or does not have a time recorded."""
1086 old_time_str
= self
.build_state
[action
]['build-time']
1087 if not old_time_str
:
1089 old_time
= datetime
.datetime
.strptime(old_time_str
,
1090 '%Y-%m-%d %H:%M:%S')
1091 new_time
= datetime
.datetime
.utcnow()
1092 delta
= new_time
- old_time
1093 return delta
.total_seconds() >= delay
1095 def bot_cycle(self
):
1096 """Run a single round of checkout and builds."""
1097 print('Bot cycle starting %s.' % str(datetime
.datetime
.utcnow()))
1098 self
.load_bot_config_json()
1099 actions
= ('host-libraries', 'compilers', 'glibcs')
1100 self
.bot_run_self(['--replace-sources'], 'checkout')
1101 self
.load_versions_json()
1102 if self
.get_script_text() != self
.script_text
:
1103 print('Script changed, re-execing.')
1104 # On script change, all parts of the build should be rerun.
1106 self
.clear_last_build_state(a
)
1108 check_components
= {'host-libraries': ('gmp', 'mpfr', 'mpc'),
1109 'compilers': ('binutils', 'gcc', 'glibc', 'linux',
1110 'mig', 'gnumach', 'hurd'),
1111 'glibcs': ('glibc',)}
1114 build_vers
= self
.build_state
[a
]['build-versions']
1115 must_build
[a
] = False
1116 if not self
.build_state
[a
]['build-time']:
1117 must_build
[a
] = True
1120 for c
in check_components
[a
]:
1122 old_vers
[c
] = build_vers
[c
]
1123 new_vers
[c
] = {'version': self
.versions
[c
]['version'],
1124 'revision': self
.versions
[c
]['revision']}
1125 if new_vers
== old_vers
:
1126 print('Versions for %s unchanged.' % a
)
1128 print('Versions changed or rebuild forced for %s.' % a
)
1129 if a
== 'compilers' and not self
.part_build_old(
1130 a
, self
.bot_config
['compilers-rebuild-delay']):
1131 print('Not requiring rebuild of compilers this soon.')
1133 must_build
[a
] = True
1134 if must_build
['host-libraries']:
1135 must_build
['compilers'] = True
1136 if must_build
['compilers']:
1137 must_build
['glibcs'] = True
1140 print('Must rebuild %s.' % a
)
1141 self
.clear_last_build_state(a
)
1143 print('No need to rebuild %s.' % a
)
1144 if os
.access(self
.logsdir
, os
.F_OK
):
1145 shutil
.rmtree(self
.logsdir_old
, ignore_errors
=True)
1146 shutil
.copytree(self
.logsdir
, self
.logsdir_old
)
1149 build_time
= datetime
.datetime
.utcnow()
1150 print('Rebuilding %s at %s.' % (a
, str(build_time
)))
1151 self
.bot_run_self([], a
)
1152 self
.load_build_state_json()
1153 self
.bot_build_mail(a
, build_time
)
1154 print('Bot cycle done at %s.' % str(datetime
.datetime
.utcnow()))
1156 def bot_build_mail(self
, action
, build_time
):
1157 """Send email with the results of a build."""
1158 if not ('email-from' in self
.bot_config
and
1159 'email-server' in self
.bot_config
and
1160 'email-subject' in self
.bot_config
and
1161 'email-to' in self
.bot_config
):
1162 if not self
.email_warning
:
1163 print("Email not configured, not sending.")
1164 self
.email_warning
= True
1167 build_time
= build_time
.replace(microsecond
=0)
1168 subject
= (self
.bot_config
['email-subject'] %
1170 'build-time': str(build_time
)})
1171 results
= self
.build_state
[action
]['build-results']
1172 changes
= self
.build_state
[action
]['result-changes']
1173 ever_passed
= set(self
.build_state
[action
]['ever-passed'])
1174 versions
= self
.build_state
[action
]['build-versions']
1175 new_regressions
= {k
for k
in changes
if changes
[k
] == 'PASS -> FAIL'}
1176 all_regressions
= {k
for k
in ever_passed
if results
[k
] == 'FAIL'}
1177 all_fails
= {k
for k
in results
if results
[k
] == 'FAIL'}
1179 new_reg_list
= sorted(['FAIL: %s' % k
for k
in new_regressions
])
1180 new_reg_text
= ('New regressions:\n\n%s\n\n' %
1181 '\n'.join(new_reg_list
))
1185 all_reg_list
= sorted(['FAIL: %s' % k
for k
in all_regressions
])
1186 all_reg_text
= ('All regressions:\n\n%s\n\n' %
1187 '\n'.join(all_reg_list
))
1191 all_fail_list
= sorted(['FAIL: %s' % k
for k
in all_fails
])
1192 all_fail_text
= ('All failures:\n\n%s\n\n' %
1193 '\n'.join(all_fail_list
))
1197 changes_list
= sorted(changes
.keys())
1198 changes_list
= ['%s: %s' % (changes
[k
], k
) for k
in changes_list
]
1199 changes_text
= ('All changed results:\n\n%s\n\n' %
1200 '\n'.join(changes_list
))
1203 results_text
= (new_reg_text
+ all_reg_text
+ all_fail_text
+
1205 if not results_text
:
1206 results_text
= 'Clean build with unchanged results.\n\n'
1207 versions_list
= sorted(versions
.keys())
1208 versions_list
= ['%s: %s (%s)' % (k
, versions
[k
]['version'],
1209 versions
[k
]['revision'])
1210 for k
in versions_list
]
1211 versions_text
= ('Component versions for this build:\n\n%s\n' %
1212 '\n'.join(versions_list
))
1213 body_text
= results_text
+ versions_text
1214 msg
= email
.mime
.text
.MIMEText(body_text
)
1215 msg
['Subject'] = subject
1216 msg
['From'] = self
.bot_config
['email-from']
1217 msg
['To'] = self
.bot_config
['email-to']
1218 msg
['Message-ID'] = email
.utils
.make_msgid()
1219 msg
['Date'] = email
.utils
.format_datetime(datetime
.datetime
.utcnow())
1220 with smtplib
.SMTP(self
.bot_config
['email-server']) as s
:
1223 def bot_run_self(self
, opts
, action
, check
=True):
1224 """Run a copy of this script with given options."""
1225 cmd
= [sys
.executable
, sys
.argv
[0], '--keep=none',
1226 '-j%d' % self
.parallelism
]
1228 cmd
.append('--full-gcc')
1230 cmd
.extend([self
.topdir
, action
])
1232 subprocess
.run(cmd
, check
=check
)
1235 """Run repeated rounds of checkout and builds."""
1237 self
.load_bot_config_json()
1238 if not self
.bot_config
['run']:
1239 print('Bot exiting by request.')
1241 self
.bot_run_self([], 'bot-cycle', check
=False)
1242 self
.load_bot_config_json()
1243 if not self
.bot_config
['run']:
1244 print('Bot exiting by request.')
1246 time
.sleep(self
.bot_config
['delay'])
1247 if self
.get_script_text() != self
.script_text
:
1248 print('Script changed, bot re-execing.')
1251 class LinuxHeadersPolicyForBuild(object):
1252 """Names and directories for installing Linux headers. Build variant."""
1254 def __init__(self
, config
):
1255 self
.arch
= config
.arch
1256 self
.srcdir
= config
.ctx
.component_srcdir('linux')
1257 self
.builddir
= config
.component_builddir('linux')
1258 self
.headers_dir
= os
.path
.join(config
.sysroot
, 'usr')
1260 class LinuxHeadersPolicyForUpdateSyscalls(object):
1261 """Names and directories for Linux headers. update-syscalls variant."""
1263 def __init__(self
, glibc
, headers_dir
):
1264 self
.arch
= glibc
.compiler
.arch
1265 self
.srcdir
= glibc
.compiler
.ctx
.component_srcdir('linux')
1266 self
.builddir
= glibc
.ctx
.component_builddir(
1267 'update-syscalls', glibc
.name
, 'build-linux')
1268 self
.headers_dir
= headers_dir
1270 def install_linux_headers(policy
, cmdlist
):
1271 """Install Linux kernel headers."""
1272 arch_map
= {'aarch64': 'arm64',
1283 'loongarch64': 'loongarch',
1285 'microblaze': 'microblaze',
1289 'powerpc': 'powerpc',
1298 if policy
.arch
.startswith(k
):
1299 linux_arch
= arch_map
[k
]
1301 assert linux_arch
is not None
1302 cmdlist
.push_subdesc('linux')
1303 cmdlist
.create_use_dir(policy
.builddir
)
1304 cmdlist
.add_command('install-headers',
1305 ['make', '-C', policy
.srcdir
, 'O=%s' % policy
.builddir
,
1306 'ARCH=%s' % linux_arch
,
1307 'INSTALL_HDR_PATH=%s' % policy
.headers_dir
,
1309 cmdlist
.cleanup_dir()
1310 cmdlist
.pop_subdesc()
1312 class Config(object):
1313 """A configuration for building a compiler and associated libraries."""
1315 def __init__(self
, ctx
, arch
, os_name
, variant
=None, gcc_cfg
=None,
1316 first_gcc_cfg
=None, binutils_cfg
=None, glibcs
=None,
1318 """Initialize a Config object."""
1322 self
.variant
= variant
1324 self
.name
= '%s-%s' % (arch
, os_name
)
1326 self
.name
= '%s-%s-%s' % (arch
, os_name
, variant
)
1327 self
.triplet
= '%s-glibc-%s' % (arch
, os_name
)
1331 self
.gcc_cfg
= gcc_cfg
1332 if first_gcc_cfg
is None:
1333 self
.first_gcc_cfg
= []
1335 self
.first_gcc_cfg
= first_gcc_cfg
1336 if binutils_cfg
is None:
1337 self
.binutils_cfg
= []
1339 self
.binutils_cfg
= binutils_cfg
1341 glibcs
= [{'variant': variant
}]
1342 if extra_glibcs
is None:
1344 glibcs
= [Glibc(self
, **g
) for g
in glibcs
]
1345 extra_glibcs
= [Glibc(self
, **g
) for g
in extra_glibcs
]
1346 self
.all_glibcs
= glibcs
+ extra_glibcs
1347 self
.compiler_glibcs
= glibcs
1348 self
.installdir
= ctx
.compiler_installdir(self
.name
)
1349 self
.bindir
= ctx
.compiler_bindir(self
.name
)
1350 self
.sysroot
= ctx
.compiler_sysroot(self
.name
)
1351 self
.builddir
= os
.path
.join(ctx
.builddir
, 'compilers', self
.name
)
1352 self
.logsdir
= os
.path
.join(ctx
.logsdir
, 'compilers', self
.name
)
1354 def component_builddir(self
, component
):
1355 """Return the directory to use for a (non-glibc) build."""
1356 return self
.ctx
.component_builddir('compilers', self
.name
, component
)
1359 """Generate commands to build this compiler."""
1360 self
.ctx
.remove_recreate_dirs(self
.installdir
, self
.builddir
,
1362 cmdlist
= CommandList('compilers-%s' % self
.name
, self
.ctx
.keep
)
1363 cmdlist
.add_command('check-host-libraries',
1365 os
.path
.join(self
.ctx
.host_libraries_installdir
,
1367 cmdlist
.use_path(self
.bindir
)
1368 self
.build_cross_tool(cmdlist
, 'binutils', 'binutils',
1370 '--disable-gdbserver',
1371 '--disable-libdecnumber',
1372 '--disable-readline',
1373 '--disable-sim'] + self
.binutils_cfg
)
1374 if self
.os
.startswith('linux'):
1375 install_linux_headers(LinuxHeadersPolicyForBuild(self
), cmdlist
)
1376 self
.build_gcc(cmdlist
, True)
1377 if self
.os
== 'gnu':
1378 self
.install_gnumach_headers(cmdlist
)
1379 self
.build_cross_tool(cmdlist
, 'mig', 'mig')
1380 self
.install_hurd_headers(cmdlist
)
1381 for g
in self
.compiler_glibcs
:
1382 cmdlist
.push_subdesc('glibc')
1383 cmdlist
.push_subdesc(g
.name
)
1384 g
.build_glibc(cmdlist
, GlibcPolicyForCompiler(g
))
1385 cmdlist
.pop_subdesc()
1386 cmdlist
.pop_subdesc()
1387 self
.build_gcc(cmdlist
, False)
1388 cmdlist
.add_command('done', ['touch',
1389 os
.path
.join(self
.installdir
, 'ok')])
1390 self
.ctx
.add_makefile_cmdlist('compilers-%s' % self
.name
, cmdlist
,
1393 def build_cross_tool(self
, cmdlist
, tool_src
, tool_build
, extra_opts
=None):
1394 """Build one cross tool."""
1395 srcdir
= self
.ctx
.component_srcdir(tool_src
)
1396 builddir
= self
.component_builddir(tool_build
)
1397 cmdlist
.push_subdesc(tool_build
)
1398 cmdlist
.create_use_dir(builddir
)
1399 cfg_cmd
= [os
.path
.join(srcdir
, 'configure'),
1400 '--prefix=%s' % self
.installdir
,
1401 '--build=%s' % self
.ctx
.build_triplet
,
1402 '--host=%s' % self
.ctx
.build_triplet
,
1403 '--target=%s' % self
.triplet
,
1404 '--with-sysroot=%s' % self
.sysroot
]
1406 cfg_cmd
.extend(extra_opts
)
1407 cmdlist
.add_command('configure', cfg_cmd
)
1408 cmdlist
.add_command('build', ['make'])
1409 # Parallel "make install" for GCC has race conditions that can
1410 # cause it to fail; see
1411 # <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=42980>. Such
1412 # problems are not known for binutils, but doing the
1413 # installation in parallel within a particular toolchain build
1414 # (as opposed to installation of one toolchain from
1415 # build-many-glibcs.py running in parallel to the installation
1416 # of other toolchains being built) is not known to be
1417 # significantly beneficial, so it is simplest just to disable
1418 # parallel install for cross tools here.
1419 cmdlist
.add_command('install', ['make', '-j1', 'install'])
1420 cmdlist
.cleanup_dir()
1421 cmdlist
.pop_subdesc()
1423 def install_gnumach_headers(self
, cmdlist
):
1424 """Install GNU Mach headers."""
1425 srcdir
= self
.ctx
.component_srcdir('gnumach')
1426 builddir
= self
.component_builddir('gnumach')
1427 cmdlist
.push_subdesc('gnumach')
1428 cmdlist
.create_use_dir(builddir
)
1429 cmdlist
.add_command('configure',
1430 [os
.path
.join(srcdir
, 'configure'),
1431 '--build=%s' % self
.ctx
.build_triplet
,
1432 '--host=%s' % self
.triplet
,
1435 'CC=%s-gcc -nostdlib' % self
.triplet
])
1436 cmdlist
.add_command('install', ['make', 'DESTDIR=%s' % self
.sysroot
,
1438 cmdlist
.cleanup_dir()
1439 cmdlist
.pop_subdesc()
1441 def install_hurd_headers(self
, cmdlist
):
1442 """Install Hurd headers."""
1443 srcdir
= self
.ctx
.component_srcdir('hurd')
1444 builddir
= self
.component_builddir('hurd')
1445 cmdlist
.push_subdesc('hurd')
1446 cmdlist
.create_use_dir(builddir
)
1447 cmdlist
.add_command('configure',
1448 [os
.path
.join(srcdir
, 'configure'),
1449 '--build=%s' % self
.ctx
.build_triplet
,
1450 '--host=%s' % self
.triplet
,
1452 '--disable-profile', '--without-parted',
1453 'CC=%s-gcc -nostdlib' % self
.triplet
])
1454 cmdlist
.add_command('install', ['make', 'prefix=%s' % self
.sysroot
,
1455 'no_deps=t', 'install-headers'])
1456 cmdlist
.cleanup_dir()
1457 cmdlist
.pop_subdesc()
1459 def build_gcc(self
, cmdlist
, bootstrap
):
1461 # libssp is of little relevance with glibc's own stack
1462 # checking support. libcilkrts does not support GNU/Hurd (and
1463 # has been removed in GCC 8, so --disable-libcilkrts can be
1464 # removed once glibc no longer supports building with older
1465 # GCC versions). --enable-initfini-array is enabled by default
1466 # in GCC 12, which can be removed when GCC 12 becomes the
1467 # minimum requirement.
1468 cfg_opts
= list(self
.gcc_cfg
)
1469 cfg_opts
+= ['--enable-initfini-array']
1470 cfg_opts
+= ['--disable-libssp', '--disable-libcilkrts']
1471 host_libs
= self
.ctx
.host_libraries_installdir
1472 cfg_opts
+= ['--with-gmp=%s' % host_libs
,
1473 '--with-mpfr=%s' % host_libs
,
1474 '--with-mpc=%s' % host_libs
]
1476 tool_build
= 'gcc-first'
1477 # Building a static-only, C-only compiler that is
1478 # sufficient to build glibc. Various libraries and
1479 # features that may require libc headers must be disabled.
1480 # When configuring with a sysroot, --with-newlib is
1481 # required to define inhibit_libc (to stop some parts of
1482 # libgcc including libc headers); --without-headers is not
1484 cfg_opts
+= ['--enable-languages=c', '--disable-shared',
1485 '--disable-threads',
1486 '--disable-libatomic',
1487 '--disable-decimal-float',
1490 '--disable-libgomp',
1493 '--disable-libquadmath',
1494 '--disable-libsanitizer',
1495 '--without-headers', '--with-newlib',
1496 '--with-glibc-version=%s' % self
.ctx
.glibc_version
1498 cfg_opts
+= self
.first_gcc_cfg
1501 # libsanitizer commonly breaks because of glibc header
1502 # changes, or on unusual targets. C++ pre-compiled
1503 # headers are not used during the glibc build and are
1504 # expensive to create.
1505 if not self
.ctx
.full_gcc
:
1506 cfg_opts
+= ['--disable-libsanitizer',
1507 '--disable-libstdcxx-pch']
1508 langs
= 'all' if self
.ctx
.full_gcc
else 'c,c++'
1509 cfg_opts
+= ['--enable-languages=%s' % langs
,
1510 '--enable-shared', '--enable-threads']
1511 self
.build_cross_tool(cmdlist
, 'gcc', tool_build
, cfg_opts
)
1513 class GlibcPolicyDefault(object):
1514 """Build policy for glibc: common defaults."""
1516 def __init__(self
, glibc
):
1517 self
.srcdir
= glibc
.ctx
.component_srcdir('glibc')
1518 self
.use_usr
= glibc
.os
!= 'gnu'
1519 self
.prefix
= '/usr' if self
.use_usr
else ''
1520 self
.configure_args
= [
1521 '--prefix=%s' % self
.prefix
,
1523 '--build=%s' % glibc
.ctx
.build_triplet
,
1524 '--host=%s' % glibc
.triplet
,
1525 'CC=%s' % glibc
.tool_name('gcc'),
1526 'CXX=%s' % glibc
.tool_name('g++'),
1528 if glibc
.os
== 'gnu':
1529 self
.configure_args
.append('MIG=%s' % glibc
.tool_name('mig'))
1531 self
.configure_args
.append('CFLAGS=%s' % glibc
.cflags
)
1532 self
.configure_args
.append('CXXFLAGS=%s' % glibc
.cflags
)
1533 self
.configure_args
+= glibc
.cfg
1535 def configure(self
, cmdlist
):
1536 """Invoked to add the configure command to the command list."""
1537 cmdlist
.add_command('configure',
1538 [os
.path
.join(self
.srcdir
, 'configure'),
1539 *self
.configure_args
])
1541 def extra_commands(self
, cmdlist
):
1542 """Invoked to inject additional commands (make check) after build."""
1545 class GlibcPolicyForCompiler(GlibcPolicyDefault
):
1546 """Build policy for glibc during the compilers stage."""
1548 def __init__(self
, glibc
):
1549 super().__init
__(glibc
)
1550 self
.builddir
= glibc
.ctx
.component_builddir(
1551 'compilers', glibc
.compiler
.name
, 'glibc', glibc
.name
)
1552 self
.installdir
= glibc
.compiler
.sysroot
1554 class GlibcPolicyForBuild(GlibcPolicyDefault
):
1555 """Build policy for glibc during the glibcs stage."""
1557 def __init__(self
, glibc
):
1558 super().__init
__(glibc
)
1559 self
.builddir
= glibc
.ctx
.component_builddir(
1560 'glibcs', glibc
.name
, 'glibc')
1561 self
.installdir
= glibc
.ctx
.glibc_installdir(glibc
.name
)
1563 self
.strip
= glibc
.tool_name('strip')
1566 self
.save_logs
= glibc
.ctx
.save_logs
1568 def extra_commands(self
, cmdlist
):
1570 # Avoid stripping libc.so and libpthread.so, which are
1571 # linker scripts stored in /lib on Hurd.
1572 find_command
= 'find %s/lib* -name "*.so*"' % self
.installdir
1573 cmdlist
.add_command('strip', ['sh', '-c', (
1574 'set -e; for f in $(%s); do '
1575 'if ! head -c16 $f | grep -q "GNU ld script"; then %s $f; fi; '
1576 'done' % (find_command
, self
.strip
))])
1577 cmdlist
.add_command('check', ['make', 'check'])
1578 cmdlist
.add_command('save-logs', [self
.save_logs
], always_run
=True)
1580 class GlibcPolicyForUpdateSyscalls(GlibcPolicyDefault
):
1581 """Build policy for glibc during update-syscalls."""
1583 def __init__(self
, glibc
):
1584 super().__init
__(glibc
)
1585 self
.builddir
= glibc
.ctx
.component_builddir(
1586 'update-syscalls', glibc
.name
, 'glibc')
1587 self
.linuxdir
= glibc
.ctx
.component_builddir(
1588 'update-syscalls', glibc
.name
, 'linux')
1589 self
.linux_policy
= LinuxHeadersPolicyForUpdateSyscalls(
1590 glibc
, self
.linuxdir
)
1591 self
.configure_args
.insert(
1592 0, '--with-headers=%s' % os
.path
.join(self
.linuxdir
, 'include'))
1593 # self.installdir not set because installation is not supported
1595 class Glibc(object):
1596 """A configuration for building glibc."""
1598 def __init__(self
, compiler
, arch
=None, os_name
=None, variant
=None,
1599 cfg
=None, ccopts
=None, cflags
=None):
1600 """Initialize a Glibc object."""
1601 self
.ctx
= compiler
.ctx
1602 self
.compiler
= compiler
1604 self
.arch
= compiler
.arch
1608 self
.os
= compiler
.os
1611 self
.variant
= variant
1613 self
.name
= '%s-%s' % (self
.arch
, self
.os
)
1615 self
.name
= '%s-%s-%s' % (self
.arch
, self
.os
, variant
)
1616 self
.triplet
= '%s-glibc-%s' % (self
.arch
, self
.os
)
1621 # ccopts contain ABI options and are passed to configure as CC / CXX.
1622 self
.ccopts
= ccopts
1623 # cflags contain non-ABI options like -g or -O and are passed to
1624 # configure as CFLAGS / CXXFLAGS.
1625 self
.cflags
= cflags
1627 def tool_name(self
, tool
):
1628 """Return the name of a cross-compilation tool."""
1629 ctool
= '%s-%s' % (self
.compiler
.triplet
, tool
)
1630 if self
.ccopts
and (tool
== 'gcc' or tool
== 'g++'):
1631 ctool
= '%s %s' % (ctool
, self
.ccopts
)
1635 """Generate commands to build this glibc."""
1636 builddir
= self
.ctx
.component_builddir('glibcs', self
.name
, 'glibc')
1637 installdir
= self
.ctx
.glibc_installdir(self
.name
)
1638 logsdir
= os
.path
.join(self
.ctx
.logsdir
, 'glibcs', self
.name
)
1639 self
.ctx
.remove_recreate_dirs(installdir
, builddir
, logsdir
)
1640 cmdlist
= CommandList('glibcs-%s' % self
.name
, self
.ctx
.keep
)
1641 cmdlist
.add_command('check-compilers',
1643 os
.path
.join(self
.compiler
.installdir
, 'ok')])
1644 cmdlist
.use_path(self
.compiler
.bindir
)
1645 self
.build_glibc(cmdlist
, GlibcPolicyForBuild(self
))
1646 self
.ctx
.add_makefile_cmdlist('glibcs-%s' % self
.name
, cmdlist
,
1649 def build_glibc(self
, cmdlist
, policy
):
1650 """Generate commands to build this glibc, either as part of a compiler
1651 build or with the bootstrapped compiler (and in the latter case, run
1653 cmdlist
.create_use_dir(policy
.builddir
)
1654 policy
.configure(cmdlist
)
1655 cmdlist
.add_command('build', ['make'])
1656 cmdlist
.add_command('install', ['make', 'install',
1657 'install_root=%s' % policy
.installdir
])
1658 # GCC uses paths such as lib/../lib64, so make sure lib
1659 # directories always exist.
1660 mkdir_cmd
= ['mkdir', '-p',
1661 os
.path
.join(policy
.installdir
, 'lib')]
1663 mkdir_cmd
+= [os
.path
.join(policy
.installdir
, 'usr', 'lib')]
1664 cmdlist
.add_command('mkdir-lib', mkdir_cmd
)
1665 policy
.extra_commands(cmdlist
)
1666 cmdlist
.cleanup_dir()
1668 def update_syscalls(self
):
1669 if self
.os
== 'gnu':
1670 # Hurd does not have system call tables that need updating.
1673 policy
= GlibcPolicyForUpdateSyscalls(self
)
1674 logsdir
= os
.path
.join(self
.ctx
.logsdir
, 'update-syscalls', self
.name
)
1675 self
.ctx
.remove_recreate_dirs(policy
.builddir
, logsdir
)
1676 cmdlist
= CommandList('update-syscalls-%s' % self
.name
, self
.ctx
.keep
)
1677 cmdlist
.add_command('check-compilers',
1679 os
.path
.join(self
.compiler
.installdir
, 'ok')])
1680 cmdlist
.use_path(self
.compiler
.bindir
)
1682 install_linux_headers(policy
.linux_policy
, cmdlist
)
1684 cmdlist
.create_use_dir(policy
.builddir
)
1685 policy
.configure(cmdlist
)
1686 cmdlist
.add_command('build', ['make', 'update-syscall-lists'])
1687 cmdlist
.cleanup_dir()
1688 self
.ctx
.add_makefile_cmdlist('update-syscalls-%s' % self
.name
,
1691 class Command(object):
1692 """A command run in the build process."""
1694 def __init__(self
, desc
, num
, dir, path
, command
, always_run
=False):
1695 """Initialize a Command object."""
1699 trans
= str.maketrans({' ': '-'})
1700 self
.logbase
= '%03d-%s' % (num
, desc
.translate(trans
))
1701 self
.command
= command
1702 self
.always_run
= always_run
1705 def shell_make_quote_string(s
):
1706 """Given a string not containing a newline, quote it for use by the
1708 assert '\n' not in s
1709 if re
.fullmatch('[]+,./0-9@A-Z_a-z-]+', s
):
1711 strans
= str.maketrans({"'": "'\\''"})
1712 s
= "'%s'" % s
.translate(strans
)
1713 mtrans
= str.maketrans({'$': '$$'})
1714 return s
.translate(mtrans
)
1717 def shell_make_quote_list(l
, translate_make
):
1718 """Given a list of strings not containing newlines, quote them for use
1719 by the shell and make, returning a single string. If translate_make
1720 is true and the first string is 'make', change it to $(MAKE)."""
1721 l
= [Command
.shell_make_quote_string(s
) for s
in l
]
1722 if translate_make
and l
[0] == 'make':
1726 def shell_make_quote(self
):
1727 """Return this command quoted for the shell and make."""
1728 return self
.shell_make_quote_list(self
.command
, True)
1731 class CommandList(object):
1732 """A list of commands run in the build process."""
1734 def __init__(self
, desc
, keep
):
1735 """Initialize a CommandList object."""
1742 def desc_txt(self
, desc
):
1743 """Return the description to use for a command."""
1744 return '%s %s' % (' '.join(self
.desc
), desc
)
1746 def use_dir(self
, dir):
1747 """Set the default directory for subsequent commands."""
1750 def use_path(self
, path
):
1751 """Set a directory to be prepended to the PATH for subsequent
1755 def push_subdesc(self
, subdesc
):
1756 """Set the default subdescription for subsequent commands (e.g., the
1757 name of a component being built, within the series of commands
1759 self
.desc
.append(subdesc
)
1761 def pop_subdesc(self
):
1762 """Pop a subdescription from the list of descriptions."""
1765 def create_use_dir(self
, dir):
1766 """Remove and recreate a directory and use it for subsequent
1768 self
.add_command_dir('rm', None, ['rm', '-rf', dir])
1769 self
.add_command_dir('mkdir', None, ['mkdir', '-p', dir])
1772 def add_command_dir(self
, desc
, dir, command
, always_run
=False):
1773 """Add a command to run in a given directory."""
1774 cmd
= Command(self
.desc_txt(desc
), len(self
.cmdlist
), dir, self
.path
,
1775 command
, always_run
)
1776 self
.cmdlist
.append(cmd
)
1778 def add_command(self
, desc
, command
, always_run
=False):
1779 """Add a command to run in the default directory."""
1780 cmd
= Command(self
.desc_txt(desc
), len(self
.cmdlist
), self
.dir,
1781 self
.path
, command
, always_run
)
1782 self
.cmdlist
.append(cmd
)
1784 def cleanup_dir(self
, desc
='cleanup', dir=None):
1785 """Clean up a build directory. If no directory is specified, the
1786 default directory is cleaned up and ceases to be the default
1791 if self
.keep
!= 'all':
1792 self
.add_command_dir(desc
, None, ['rm', '-rf', dir],
1793 always_run
=(self
.keep
== 'none'))
1795 def makefile_commands(self
, wrapper
, logsdir
):
1796 """Return the sequence of commands in the form of text for a Makefile.
1797 The given wrapper script takes arguments: base of logs for
1798 previous command, or empty; base of logs for this command;
1799 description; directory; PATH addition; the command itself."""
1800 # prev_base is the base of the name for logs of the previous
1801 # command that is not always-run (that is, a build command,
1802 # whose failure should stop subsequent build commands from
1803 # being run, as opposed to a cleanup command, which is run
1804 # even if previous commands failed).
1807 for c
in self
.cmdlist
:
1808 ctxt
= c
.shell_make_quote()
1809 if prev_base
and not c
.always_run
:
1810 prev_log
= os
.path
.join(logsdir
, prev_base
)
1813 this_log
= os
.path
.join(logsdir
, c
.logbase
)
1814 if not c
.always_run
:
1815 prev_base
= c
.logbase
1824 prelims
= [wrapper
, prev_log
, this_log
, c
.desc
, dir, path
]
1825 prelim_txt
= Command
.shell_make_quote_list(prelims
, False)
1826 cmds
.append('\t@%s %s' % (prelim_txt
, ctxt
))
1827 return '\n'.join(cmds
)
1829 def status_logs(self
, logsdir
):
1830 """Return the list of log files with command status."""
1831 return [os
.path
.join(logsdir
, '%s-status.txt' % c
.logbase
)
1832 for c
in self
.cmdlist
]
1836 """Return an argument parser for this module."""
1837 parser
= argparse
.ArgumentParser(description
=__doc__
)
1838 parser
.add_argument('-j', dest
='parallelism',
1839 help='Run this number of jobs in parallel',
1840 type=int, default
=os
.cpu_count())
1841 parser
.add_argument('--keep', dest
='keep',
1842 help='Whether to keep all build directories, '
1843 'none or only those from failed builds',
1844 default
='none', choices
=('none', 'all', 'failed'))
1845 parser
.add_argument('--replace-sources', action
='store_true',
1846 help='Remove and replace source directories '
1847 'with the wrong version of a component')
1848 parser
.add_argument('--strip', action
='store_true',
1849 help='Strip installed glibc libraries')
1850 parser
.add_argument('--full-gcc', action
='store_true',
1851 help='Build GCC with all languages and libsanitizer')
1852 parser
.add_argument('--shallow', action
='store_true',
1853 help='Do not download Git history during checkout')
1854 parser
.add_argument('topdir',
1855 help='Toplevel working directory')
1856 parser
.add_argument('action',
1858 choices
=('checkout', 'bot-cycle', 'bot',
1859 'host-libraries', 'compilers', 'glibcs',
1860 'update-syscalls', 'list-compilers',
1862 parser
.add_argument('configs',
1863 help='Versions to check out or configurations to build',
1869 """The main entry point."""
1870 parser
= get_parser()
1871 opts
= parser
.parse_args(argv
)
1872 topdir
= os
.path
.abspath(opts
.topdir
)
1873 ctx
= Context(topdir
, opts
.parallelism
, opts
.keep
, opts
.replace_sources
,
1874 opts
.strip
, opts
.full_gcc
, opts
.action
,
1875 shallow
=opts
.shallow
)
1876 ctx
.run_builds(opts
.action
, opts
.configs
)
1879 if __name__
== '__main__':