2 # Build many configurations of glibc.
3 # Copyright (C) 2016-2022 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',
448 '--disable-tunables',
450 '--disable-experimental-malloc',
451 '--disable-build-nscd',
453 {'variant': 'no-pie',
454 'cfg': ['--disable-default-pie']},
455 {'variant': 'x32-no-pie',
457 'cfg': ['--disable-default-pie']},
458 {'variant': 'no-pie',
460 'ccopts': '-m32 -march=i686',
461 'cfg': ['--disable-default-pie']},
462 {'variant': 'disable-multi-arch',
464 'ccopts': '-m32 -march=i686',
465 'cfg': ['--disable-multi-arch']},
467 'ccopts': '-m32 -march=i486'},
469 'ccopts': '-m32 -march=i586'}])
471 def add_config(self
, **args
):
472 """Add an individual build configuration."""
473 cfg
= Config(self
, **args
)
474 if cfg
.name
in self
.configs
:
475 print('error: duplicate config %s' % cfg
.name
)
477 self
.configs
[cfg
.name
] = cfg
478 for c
in cfg
.all_glibcs
:
479 if c
.name
in self
.glibc_configs
:
480 print('error: duplicate glibc config %s' % c
.name
)
482 self
.glibc_configs
[c
.name
] = c
484 def component_srcdir(self
, component
):
485 """Return the source directory for a given component, e.g. gcc."""
486 return os
.path
.join(self
.srcdir
, component
)
488 def component_builddir(self
, action
, config
, component
, subconfig
=None):
489 """Return the directory to use for a build."""
492 assert subconfig
is None
493 return os
.path
.join(self
.builddir
, action
, component
)
494 if subconfig
is None:
495 return os
.path
.join(self
.builddir
, action
, config
, component
)
497 # glibc build as part of compiler build.
498 return os
.path
.join(self
.builddir
, action
, config
, component
,
501 def compiler_installdir(self
, config
):
502 """Return the directory in which to install a compiler."""
503 return os
.path
.join(self
.installdir
, 'compilers', config
)
505 def compiler_bindir(self
, config
):
506 """Return the directory in which to find compiler binaries."""
507 return os
.path
.join(self
.compiler_installdir(config
), 'bin')
509 def compiler_sysroot(self
, config
):
510 """Return the sysroot directory for a compiler."""
511 return os
.path
.join(self
.compiler_installdir(config
), 'sysroot')
513 def glibc_installdir(self
, config
):
514 """Return the directory in which to install glibc."""
515 return os
.path
.join(self
.installdir
, 'glibcs', config
)
517 def run_builds(self
, action
, configs
):
518 """Run the requested builds."""
519 if action
== 'checkout':
520 self
.checkout(configs
)
522 if action
== 'bot-cycle':
524 print('error: configurations specified for bot-cycle')
530 print('error: configurations specified for bot')
534 if action
in ('host-libraries', 'list-compilers',
535 'list-glibcs') and configs
:
536 print('error: configurations specified for ' + action
)
538 if action
== 'list-compilers':
539 for name
in sorted(self
.configs
.keys()):
542 if action
== 'list-glibcs':
543 for config
in sorted(self
.glibc_configs
.values(),
544 key
=lambda c
: c
.name
):
545 print(config
.name
, config
.compiler
.name
)
547 self
.clear_last_build_state(action
)
548 build_time
= datetime
.datetime
.utcnow()
549 if action
== 'host-libraries':
550 build_components
= ('gmp', 'mpfr', 'mpc')
553 self
.build_host_libraries()
554 elif action
== 'compilers':
555 build_components
= ('binutils', 'gcc', 'glibc', 'linux', 'mig',
557 old_components
= ('gmp', 'mpfr', 'mpc')
558 old_versions
= self
.build_state
['host-libraries']['build-versions']
559 self
.build_compilers(configs
)
561 build_components
= ('glibc',)
562 old_components
= ('gmp', 'mpfr', 'mpc', 'binutils', 'gcc', 'linux',
563 'mig', 'gnumach', 'hurd')
564 old_versions
= self
.build_state
['compilers']['build-versions']
565 if action
== 'update-syscalls':
566 self
.update_syscalls(configs
)
568 self
.build_glibcs(configs
)
572 # Partial build, do not update stored state.
575 for k
in build_components
:
576 if k
in self
.versions
:
577 build_versions
[k
] = {'version': self
.versions
[k
]['version'],
578 'revision': self
.versions
[k
]['revision']}
579 for k
in old_components
:
580 if k
in old_versions
:
581 build_versions
[k
] = {'version': old_versions
[k
]['version'],
582 'revision': old_versions
[k
]['revision']}
583 self
.update_build_state(action
, build_time
, build_versions
)
586 def remove_dirs(*args
):
587 """Remove directories and their contents if they exist."""
589 shutil
.rmtree(dir, ignore_errors
=True)
592 def remove_recreate_dirs(*args
):
593 """Remove directories if they exist, and create them as empty."""
594 Context
.remove_dirs(*args
)
596 os
.makedirs(dir, exist_ok
=True)
598 def add_makefile_cmdlist(self
, target
, cmdlist
, logsdir
):
599 """Add makefile text for a list of commands."""
600 commands
= cmdlist
.makefile_commands(self
.wrapper
, logsdir
)
601 self
.makefile_pieces
.append('all: %s\n.PHONY: %s\n%s:\n%s\n' %
602 (target
, target
, target
, commands
))
603 self
.status_log_list
.extend(cmdlist
.status_logs(logsdir
))
605 def write_files(self
):
606 """Write out the Makefile and wrapper script."""
607 mftext
= ''.join(self
.makefile_pieces
)
608 with
open(self
.makefile
, 'w') as f
:
618 'prev_status=$prev_base-status.txt\n'
619 'this_status=$this_base-status.txt\n'
620 'this_log=$this_base-log.txt\n'
621 'date > "$this_log"\n'
622 'echo >> "$this_log"\n'
623 'echo "Description: $desc" >> "$this_log"\n'
624 'printf "%s" "Command:" >> "$this_log"\n'
625 'for word in "$@"; do\n'
626 ' if expr "$word" : "[]+,./0-9@A-Z_a-z-]\\\\{1,\\\\}\\$" > /dev/null; then\n'
627 ' printf " %s" "$word"\n'
630 ' printf "%s" "$word" | sed -e "s/\'/\'\\\\\\\\\'\'/"\n'
633 'done >> "$this_log"\n'
634 'echo >> "$this_log"\n'
635 'echo "Directory: $dir" >> "$this_log"\n'
636 'echo "Path addition: $path" >> "$this_log"\n'
637 'echo >> "$this_log"\n'
640 ' echo >> "$this_log"\n'
641 ' echo "$1: $desc" > "$this_status"\n'
642 ' echo "$1: $desc" >> "$this_log"\n'
643 ' echo >> "$this_log"\n'
644 ' date >> "$this_log"\n'
645 ' echo "$1: $desc"\n'
650 ' if [ "$1" != "0" ]; then\n'
651 ' record_status FAIL\n'
654 'if [ "$prev_base" ] && ! grep -q "^PASS" "$prev_status"; then\n'
655 ' record_status UNRESOLVED\n'
657 'if [ "$dir" ]; then\n'
659 ' check_error "$?"\n'
661 'if [ "$path" ]; then\n'
662 ' PATH=$path:$PATH\n'
664 '"$@" < /dev/null >> "$this_log" 2>&1\n'
666 'record_status PASS\n')
667 with
open(self
.wrapper
, 'w') as f
:
668 f
.write(wrapper_text
)
670 mode_exec
= (stat
.S_IRWXU|stat
.S_IRGRP|stat
.S_IXGRP|
671 stat
.S_IROTH|stat
.S_IXOTH
)
672 os
.chmod(self
.wrapper
, mode_exec
)
675 'if ! [ -f tests.sum ]; then\n'
676 ' echo "No test summary available."\n'
681 ' echo "Contents of $1:"\n'
685 ' echo "End of contents of $1."\n'
688 'save_file tests.sum\n'
689 'non_pass_tests=$(grep -v "^PASS: " tests.sum | sed -e "s/^PASS: //")\n'
690 'for t in $non_pass_tests; do\n'
691 ' if [ -f "$t.out" ]; then\n'
692 ' save_file "$t.out"\n'
695 with
open(self
.save_logs
, 'w') as f
:
696 f
.write(save_logs_text
)
697 os
.chmod(self
.save_logs
, mode_exec
)
700 """Do the actual build."""
701 cmd
= ['make', '-O', '-j%d' % self
.parallelism
]
702 subprocess
.run(cmd
, cwd
=self
.builddir
, check
=True)
704 def build_host_libraries(self
):
705 """Build the host libraries."""
706 installdir
= self
.host_libraries_installdir
707 builddir
= os
.path
.join(self
.builddir
, 'host-libraries')
708 logsdir
= os
.path
.join(self
.logsdir
, 'host-libraries')
709 self
.remove_recreate_dirs(installdir
, builddir
, logsdir
)
710 cmdlist
= CommandList('host-libraries', self
.keep
)
711 self
.build_host_library(cmdlist
, 'gmp')
712 self
.build_host_library(cmdlist
, 'mpfr',
713 ['--with-gmp=%s' % installdir
])
714 self
.build_host_library(cmdlist
, 'mpc',
715 ['--with-gmp=%s' % installdir
,
716 '--with-mpfr=%s' % installdir
])
717 cmdlist
.add_command('done', ['touch', os
.path
.join(installdir
, 'ok')])
718 self
.add_makefile_cmdlist('host-libraries', cmdlist
, logsdir
)
720 def build_host_library(self
, cmdlist
, lib
, extra_opts
=None):
721 """Build one host library."""
722 srcdir
= self
.component_srcdir(lib
)
723 builddir
= self
.component_builddir('host-libraries', None, lib
)
724 installdir
= self
.host_libraries_installdir
725 cmdlist
.push_subdesc(lib
)
726 cmdlist
.create_use_dir(builddir
)
727 cfg_cmd
= [os
.path
.join(srcdir
, 'configure'),
728 '--prefix=%s' % installdir
,
731 cfg_cmd
.extend (extra_opts
)
732 cmdlist
.add_command('configure', cfg_cmd
)
733 cmdlist
.add_command('build', ['make'])
734 cmdlist
.add_command('check', ['make', 'check'])
735 cmdlist
.add_command('install', ['make', 'install'])
736 cmdlist
.cleanup_dir()
737 cmdlist
.pop_subdesc()
739 def build_compilers(self
, configs
):
740 """Build the compilers."""
742 self
.remove_dirs(os
.path
.join(self
.builddir
, 'compilers'))
743 self
.remove_dirs(os
.path
.join(self
.installdir
, 'compilers'))
744 self
.remove_dirs(os
.path
.join(self
.logsdir
, 'compilers'))
745 configs
= sorted(self
.configs
.keys())
747 self
.configs
[c
].build()
749 def build_glibcs(self
, configs
):
750 """Build the glibcs."""
752 self
.remove_dirs(os
.path
.join(self
.builddir
, 'glibcs'))
753 self
.remove_dirs(os
.path
.join(self
.installdir
, 'glibcs'))
754 self
.remove_dirs(os
.path
.join(self
.logsdir
, 'glibcs'))
755 configs
= sorted(self
.glibc_configs
.keys())
757 self
.glibc_configs
[c
].build()
759 def update_syscalls(self
, configs
):
760 """Update the glibc syscall lists."""
762 self
.remove_dirs(os
.path
.join(self
.builddir
, 'update-syscalls'))
763 self
.remove_dirs(os
.path
.join(self
.logsdir
, 'update-syscalls'))
764 configs
= sorted(self
.glibc_configs
.keys())
766 self
.glibc_configs
[c
].update_syscalls()
768 def load_versions_json(self
):
769 """Load information about source directory versions."""
770 if not os
.access(self
.versions_json
, os
.F_OK
):
773 with
open(self
.versions_json
, 'r') as f
:
774 self
.versions
= json
.load(f
)
776 def store_json(self
, data
, filename
):
777 """Store information in a JSON file."""
778 filename_tmp
= filename
+ '.tmp'
779 with
open(filename_tmp
, 'w') as f
:
780 json
.dump(data
, f
, indent
=2, sort_keys
=True)
781 os
.rename(filename_tmp
, filename
)
783 def store_versions_json(self
):
784 """Store information about source directory versions."""
785 self
.store_json(self
.versions
, self
.versions_json
)
787 def set_component_version(self
, component
, version
, explicit
, revision
):
788 """Set the version information for a component."""
789 self
.versions
[component
] = {'version': version
,
790 'explicit': explicit
,
791 'revision': revision
}
792 self
.store_versions_json()
794 def checkout(self
, versions
):
795 """Check out the desired component versions."""
796 default_versions
= {'binutils': 'vcs-2.39',
798 'glibc': 'vcs-mainline',
803 'mig': 'vcs-mainline',
804 'gnumach': 'vcs-mainline',
805 'hurd': 'vcs-mainline'}
807 explicit_versions
= {}
810 for k
in default_versions
.keys():
814 if k
in use_versions
:
815 print('error: multiple versions for %s' % k
)
818 explicit_versions
[k
] = True
822 print('error: unknown component in %s' % v
)
824 for k
in default_versions
.keys():
825 if k
not in use_versions
:
826 if k
in self
.versions
and self
.versions
[k
]['explicit']:
827 use_versions
[k
] = self
.versions
[k
]['version']
828 explicit_versions
[k
] = True
830 use_versions
[k
] = default_versions
[k
]
831 explicit_versions
[k
] = False
832 os
.makedirs(self
.srcdir
, exist_ok
=True)
833 for k
in sorted(default_versions
.keys()):
834 update
= os
.access(self
.component_srcdir(k
), os
.F_OK
)
837 k
in self
.versions
and
838 v
!= self
.versions
[k
]['version']):
839 if not self
.replace_sources
:
840 print('error: version of %s has changed from %s to %s, '
841 'use --replace-sources to check out again' %
842 (k
, self
.versions
[k
]['version'], v
))
844 shutil
.rmtree(self
.component_srcdir(k
))
846 if v
.startswith('vcs-'):
847 revision
= self
.checkout_vcs(k
, v
[4:], update
)
849 self
.checkout_tar(k
, v
, update
)
851 self
.set_component_version(k
, v
, explicit_versions
[k
], revision
)
852 if self
.get_script_text() != self
.script_text
:
853 # Rerun the checkout process in case the updated script
854 # uses different default versions or new components.
857 def checkout_vcs(self
, component
, version
, update
):
858 """Check out the given version of the given component from version
859 control. Return a revision identifier."""
860 if component
== 'binutils':
861 git_url
= 'https://sourceware.org/git/binutils-gdb.git'
862 if version
== 'mainline':
863 git_branch
= 'master'
865 trans
= str.maketrans({'.': '_'})
866 git_branch
= 'binutils-%s-branch' % version
.translate(trans
)
867 return self
.git_checkout(component
, git_url
, git_branch
, update
)
868 elif component
== 'gcc':
869 if version
== 'mainline':
872 branch
= 'releases/gcc-%s' % version
873 return self
.gcc_checkout(branch
, update
)
874 elif component
== 'glibc':
875 git_url
= 'https://sourceware.org/git/glibc.git'
876 if version
== 'mainline':
877 git_branch
= 'master'
879 git_branch
= 'release/%s/master' % version
880 r
= self
.git_checkout(component
, git_url
, git_branch
, update
)
881 self
.fix_glibc_timestamps()
883 elif component
== 'gnumach':
884 git_url
= 'git://git.savannah.gnu.org/hurd/gnumach.git'
885 git_branch
= 'master'
886 r
= self
.git_checkout(component
, git_url
, git_branch
, update
)
887 subprocess
.run(['autoreconf', '-i'],
888 cwd
=self
.component_srcdir(component
), check
=True)
890 elif component
== 'mig':
891 git_url
= 'git://git.savannah.gnu.org/hurd/mig.git'
892 git_branch
= 'master'
893 r
= self
.git_checkout(component
, git_url
, git_branch
, update
)
894 subprocess
.run(['autoreconf', '-i'],
895 cwd
=self
.component_srcdir(component
), check
=True)
897 elif component
== 'hurd':
898 git_url
= 'git://git.savannah.gnu.org/hurd/hurd.git'
899 git_branch
= 'master'
900 r
= self
.git_checkout(component
, git_url
, git_branch
, update
)
901 subprocess
.run(['autoconf'],
902 cwd
=self
.component_srcdir(component
), check
=True)
905 print('error: component %s coming from VCS' % component
)
908 def git_checkout(self
, component
, git_url
, git_branch
, update
):
909 """Check out a component from git. Return a commit identifier."""
911 subprocess
.run(['git', 'remote', 'prune', 'origin'],
912 cwd
=self
.component_srcdir(component
), check
=True)
913 if self
.replace_sources
:
914 subprocess
.run(['git', 'clean', '-dxfq'],
915 cwd
=self
.component_srcdir(component
), check
=True)
916 subprocess
.run(['git', 'pull', '-q'],
917 cwd
=self
.component_srcdir(component
), check
=True)
920 depth_arg
= ('--depth', '1')
923 subprocess
.run(['git', 'clone', '-q', '-b', git_branch
,
925 self
.component_srcdir(component
)], check
=True)
926 r
= subprocess
.run(['git', 'rev-parse', 'HEAD'],
927 cwd
=self
.component_srcdir(component
),
928 stdout
=subprocess
.PIPE
,
929 check
=True, universal_newlines
=True).stdout
932 def fix_glibc_timestamps(self
):
933 """Fix timestamps in a glibc checkout."""
934 # Ensure that builds do not try to regenerate generated files
935 # in the source tree.
936 srcdir
= self
.component_srcdir('glibc')
937 # These files have Makefile dependencies to regenerate them in
938 # the source tree that may be active during a normal build.
939 # Some other files have such dependencies but do not need to
940 # be touched because nothing in a build depends on the files
942 for f
in ('sysdeps/mach/hurd/bits/errno.h',):
943 to_touch
= os
.path
.join(srcdir
, f
)
944 subprocess
.run(['touch', '-c', to_touch
], check
=True)
945 for dirpath
, dirnames
, filenames
in os
.walk(srcdir
):
947 if (f
== 'configure' or
948 f
== 'preconfigure' or
949 f
.endswith('-kw.h')):
950 to_touch
= os
.path
.join(dirpath
, f
)
951 subprocess
.run(['touch', to_touch
], check
=True)
953 def gcc_checkout(self
, branch
, update
):
954 """Check out GCC from git. Return the commit identifier."""
955 if os
.access(os
.path
.join(self
.component_srcdir('gcc'), '.svn'),
957 if not self
.replace_sources
:
958 print('error: GCC has moved from SVN to git, use '
959 '--replace-sources to check out again')
961 shutil
.rmtree(self
.component_srcdir('gcc'))
964 self
.git_checkout('gcc', 'https://gcc.gnu.org/git/gcc.git',
966 subprocess
.run(['contrib/gcc_update', '--silent'],
967 cwd
=self
.component_srcdir('gcc'), check
=True)
968 r
= subprocess
.run(['git', 'rev-parse', 'HEAD'],
969 cwd
=self
.component_srcdir('gcc'),
970 stdout
=subprocess
.PIPE
,
971 check
=True, universal_newlines
=True).stdout
974 def checkout_tar(self
, component
, version
, update
):
975 """Check out the given version of the given component from a
979 url_map
= {'binutils': 'https://ftp.gnu.org/gnu/binutils/binutils-%(version)s.tar.bz2',
980 'gcc': 'https://ftp.gnu.org/gnu/gcc/gcc-%(version)s/gcc-%(version)s.tar.gz',
981 'gmp': 'https://ftp.gnu.org/gnu/gmp/gmp-%(version)s.tar.xz',
982 'linux': 'https://www.kernel.org/pub/linux/kernel/v%(major)s.x/linux-%(version)s.tar.xz',
983 'mpc': 'https://ftp.gnu.org/gnu/mpc/mpc-%(version)s.tar.gz',
984 'mpfr': 'https://ftp.gnu.org/gnu/mpfr/mpfr-%(version)s.tar.xz',
985 'mig': 'https://ftp.gnu.org/gnu/mig/mig-%(version)s.tar.bz2',
986 'gnumach': 'https://ftp.gnu.org/gnu/gnumach/gnumach-%(version)s.tar.bz2',
987 'hurd': 'https://ftp.gnu.org/gnu/hurd/hurd-%(version)s.tar.bz2'}
988 if component
not in url_map
:
989 print('error: component %s coming from tarball' % component
)
991 version_major
= version
.split('.')[0]
992 url
= url_map
[component
] % {'version': version
, 'major': version_major
}
993 filename
= os
.path
.join(self
.srcdir
, url
.split('/')[-1])
994 response
= urllib
.request
.urlopen(url
)
995 data
= response
.read()
996 with
open(filename
, 'wb') as f
:
998 subprocess
.run(['tar', '-C', self
.srcdir
, '-x', '-f', filename
],
1000 os
.rename(os
.path
.join(self
.srcdir
, '%s-%s' % (component
, version
)),
1001 self
.component_srcdir(component
))
1004 def load_build_state_json(self
):
1005 """Load information about the state of previous builds."""
1006 if os
.access(self
.build_state_json
, os
.F_OK
):
1007 with
open(self
.build_state_json
, 'r') as f
:
1008 self
.build_state
= json
.load(f
)
1010 self
.build_state
= {}
1011 for k
in ('host-libraries', 'compilers', 'glibcs', 'update-syscalls'):
1012 if k
not in self
.build_state
:
1013 self
.build_state
[k
] = {}
1014 if 'build-time' not in self
.build_state
[k
]:
1015 self
.build_state
[k
]['build-time'] = ''
1016 if 'build-versions' not in self
.build_state
[k
]:
1017 self
.build_state
[k
]['build-versions'] = {}
1018 if 'build-results' not in self
.build_state
[k
]:
1019 self
.build_state
[k
]['build-results'] = {}
1020 if 'result-changes' not in self
.build_state
[k
]:
1021 self
.build_state
[k
]['result-changes'] = {}
1022 if 'ever-passed' not in self
.build_state
[k
]:
1023 self
.build_state
[k
]['ever-passed'] = []
1025 def store_build_state_json(self
):
1026 """Store information about the state of previous builds."""
1027 self
.store_json(self
.build_state
, self
.build_state_json
)
1029 def clear_last_build_state(self
, action
):
1030 """Clear information about the state of part of the build."""
1031 # We clear the last build time and versions when starting a
1032 # new build. The results of the last build are kept around,
1033 # as comparison is still meaningful if this build is aborted
1034 # and a new one started.
1035 self
.build_state
[action
]['build-time'] = ''
1036 self
.build_state
[action
]['build-versions'] = {}
1037 self
.store_build_state_json()
1039 def update_build_state(self
, action
, build_time
, build_versions
):
1040 """Update the build state after a build."""
1041 build_time
= build_time
.replace(microsecond
=0)
1042 self
.build_state
[action
]['build-time'] = str(build_time
)
1043 self
.build_state
[action
]['build-versions'] = build_versions
1045 for log
in self
.status_log_list
:
1046 with
open(log
, 'r') as f
:
1048 log_text
= log_text
.rstrip()
1049 m
= re
.fullmatch('([A-Z]+): (.*)', log_text
)
1051 test_name
= m
.group(2)
1052 assert test_name
not in build_results
1053 build_results
[test_name
] = result
1054 old_build_results
= self
.build_state
[action
]['build-results']
1055 self
.build_state
[action
]['build-results'] = build_results
1057 all_tests
= set(old_build_results
.keys()) |
set(build_results
.keys())
1059 if t
in old_build_results
:
1060 old_res
= old_build_results
[t
]
1062 old_res
= '(New test)'
1063 if t
in build_results
:
1064 new_res
= build_results
[t
]
1066 new_res
= '(Test removed)'
1067 if old_res
!= new_res
:
1068 result_changes
[t
] = '%s -> %s' % (old_res
, new_res
)
1069 self
.build_state
[action
]['result-changes'] = result_changes
1070 old_ever_passed
= {t
for t
in self
.build_state
[action
]['ever-passed']
1071 if t
in build_results
}
1072 new_passes
= {t
for t
in build_results
if build_results
[t
] == 'PASS'}
1073 self
.build_state
[action
]['ever-passed'] = sorted(old_ever_passed |
1075 self
.store_build_state_json()
1077 def load_bot_config_json(self
):
1078 """Load bot configuration."""
1079 with
open(self
.bot_config_json
, 'r') as f
:
1080 self
.bot_config
= json
.load(f
)
1082 def part_build_old(self
, action
, delay
):
1083 """Return whether the last build for a given action was at least a
1084 given number of seconds ago, or does not have a time recorded."""
1085 old_time_str
= self
.build_state
[action
]['build-time']
1086 if not old_time_str
:
1088 old_time
= datetime
.datetime
.strptime(old_time_str
,
1089 '%Y-%m-%d %H:%M:%S')
1090 new_time
= datetime
.datetime
.utcnow()
1091 delta
= new_time
- old_time
1092 return delta
.total_seconds() >= delay
1094 def bot_cycle(self
):
1095 """Run a single round of checkout and builds."""
1096 print('Bot cycle starting %s.' % str(datetime
.datetime
.utcnow()))
1097 self
.load_bot_config_json()
1098 actions
= ('host-libraries', 'compilers', 'glibcs')
1099 self
.bot_run_self(['--replace-sources'], 'checkout')
1100 self
.load_versions_json()
1101 if self
.get_script_text() != self
.script_text
:
1102 print('Script changed, re-execing.')
1103 # On script change, all parts of the build should be rerun.
1105 self
.clear_last_build_state(a
)
1107 check_components
= {'host-libraries': ('gmp', 'mpfr', 'mpc'),
1108 'compilers': ('binutils', 'gcc', 'glibc', 'linux',
1109 'mig', 'gnumach', 'hurd'),
1110 'glibcs': ('glibc',)}
1113 build_vers
= self
.build_state
[a
]['build-versions']
1114 must_build
[a
] = False
1115 if not self
.build_state
[a
]['build-time']:
1116 must_build
[a
] = True
1119 for c
in check_components
[a
]:
1121 old_vers
[c
] = build_vers
[c
]
1122 new_vers
[c
] = {'version': self
.versions
[c
]['version'],
1123 'revision': self
.versions
[c
]['revision']}
1124 if new_vers
== old_vers
:
1125 print('Versions for %s unchanged.' % a
)
1127 print('Versions changed or rebuild forced for %s.' % a
)
1128 if a
== 'compilers' and not self
.part_build_old(
1129 a
, self
.bot_config
['compilers-rebuild-delay']):
1130 print('Not requiring rebuild of compilers this soon.')
1132 must_build
[a
] = True
1133 if must_build
['host-libraries']:
1134 must_build
['compilers'] = True
1135 if must_build
['compilers']:
1136 must_build
['glibcs'] = True
1139 print('Must rebuild %s.' % a
)
1140 self
.clear_last_build_state(a
)
1142 print('No need to rebuild %s.' % a
)
1143 if os
.access(self
.logsdir
, os
.F_OK
):
1144 shutil
.rmtree(self
.logsdir_old
, ignore_errors
=True)
1145 shutil
.copytree(self
.logsdir
, self
.logsdir_old
)
1148 build_time
= datetime
.datetime
.utcnow()
1149 print('Rebuilding %s at %s.' % (a
, str(build_time
)))
1150 self
.bot_run_self([], a
)
1151 self
.load_build_state_json()
1152 self
.bot_build_mail(a
, build_time
)
1153 print('Bot cycle done at %s.' % str(datetime
.datetime
.utcnow()))
1155 def bot_build_mail(self
, action
, build_time
):
1156 """Send email with the results of a build."""
1157 if not ('email-from' in self
.bot_config
and
1158 'email-server' in self
.bot_config
and
1159 'email-subject' in self
.bot_config
and
1160 'email-to' in self
.bot_config
):
1161 if not self
.email_warning
:
1162 print("Email not configured, not sending.")
1163 self
.email_warning
= True
1166 build_time
= build_time
.replace(microsecond
=0)
1167 subject
= (self
.bot_config
['email-subject'] %
1169 'build-time': str(build_time
)})
1170 results
= self
.build_state
[action
]['build-results']
1171 changes
= self
.build_state
[action
]['result-changes']
1172 ever_passed
= set(self
.build_state
[action
]['ever-passed'])
1173 versions
= self
.build_state
[action
]['build-versions']
1174 new_regressions
= {k
for k
in changes
if changes
[k
] == 'PASS -> FAIL'}
1175 all_regressions
= {k
for k
in ever_passed
if results
[k
] == 'FAIL'}
1176 all_fails
= {k
for k
in results
if results
[k
] == 'FAIL'}
1178 new_reg_list
= sorted(['FAIL: %s' % k
for k
in new_regressions
])
1179 new_reg_text
= ('New regressions:\n\n%s\n\n' %
1180 '\n'.join(new_reg_list
))
1184 all_reg_list
= sorted(['FAIL: %s' % k
for k
in all_regressions
])
1185 all_reg_text
= ('All regressions:\n\n%s\n\n' %
1186 '\n'.join(all_reg_list
))
1190 all_fail_list
= sorted(['FAIL: %s' % k
for k
in all_fails
])
1191 all_fail_text
= ('All failures:\n\n%s\n\n' %
1192 '\n'.join(all_fail_list
))
1196 changes_list
= sorted(changes
.keys())
1197 changes_list
= ['%s: %s' % (changes
[k
], k
) for k
in changes_list
]
1198 changes_text
= ('All changed results:\n\n%s\n\n' %
1199 '\n'.join(changes_list
))
1202 results_text
= (new_reg_text
+ all_reg_text
+ all_fail_text
+
1204 if not results_text
:
1205 results_text
= 'Clean build with unchanged results.\n\n'
1206 versions_list
= sorted(versions
.keys())
1207 versions_list
= ['%s: %s (%s)' % (k
, versions
[k
]['version'],
1208 versions
[k
]['revision'])
1209 for k
in versions_list
]
1210 versions_text
= ('Component versions for this build:\n\n%s\n' %
1211 '\n'.join(versions_list
))
1212 body_text
= results_text
+ versions_text
1213 msg
= email
.mime
.text
.MIMEText(body_text
)
1214 msg
['Subject'] = subject
1215 msg
['From'] = self
.bot_config
['email-from']
1216 msg
['To'] = self
.bot_config
['email-to']
1217 msg
['Message-ID'] = email
.utils
.make_msgid()
1218 msg
['Date'] = email
.utils
.format_datetime(datetime
.datetime
.utcnow())
1219 with smtplib
.SMTP(self
.bot_config
['email-server']) as s
:
1222 def bot_run_self(self
, opts
, action
, check
=True):
1223 """Run a copy of this script with given options."""
1224 cmd
= [sys
.executable
, sys
.argv
[0], '--keep=none',
1225 '-j%d' % self
.parallelism
]
1227 cmd
.append('--full-gcc')
1229 cmd
.extend([self
.topdir
, action
])
1231 subprocess
.run(cmd
, check
=check
)
1234 """Run repeated rounds of checkout and builds."""
1236 self
.load_bot_config_json()
1237 if not self
.bot_config
['run']:
1238 print('Bot exiting by request.')
1240 self
.bot_run_self([], 'bot-cycle', check
=False)
1241 self
.load_bot_config_json()
1242 if not self
.bot_config
['run']:
1243 print('Bot exiting by request.')
1245 time
.sleep(self
.bot_config
['delay'])
1246 if self
.get_script_text() != self
.script_text
:
1247 print('Script changed, bot re-execing.')
1250 class LinuxHeadersPolicyForBuild(object):
1251 """Names and directories for installing Linux headers. Build variant."""
1253 def __init__(self
, config
):
1254 self
.arch
= config
.arch
1255 self
.srcdir
= config
.ctx
.component_srcdir('linux')
1256 self
.builddir
= config
.component_builddir('linux')
1257 self
.headers_dir
= os
.path
.join(config
.sysroot
, 'usr')
1259 class LinuxHeadersPolicyForUpdateSyscalls(object):
1260 """Names and directories for Linux headers. update-syscalls variant."""
1262 def __init__(self
, glibc
, headers_dir
):
1263 self
.arch
= glibc
.compiler
.arch
1264 self
.srcdir
= glibc
.compiler
.ctx
.component_srcdir('linux')
1265 self
.builddir
= glibc
.ctx
.component_builddir(
1266 'update-syscalls', glibc
.name
, 'build-linux')
1267 self
.headers_dir
= headers_dir
1269 def install_linux_headers(policy
, cmdlist
):
1270 """Install Linux kernel headers."""
1271 arch_map
= {'aarch64': 'arm64',
1282 'loongarch64': 'loongarch',
1284 'microblaze': 'microblaze',
1288 'powerpc': 'powerpc',
1297 if policy
.arch
.startswith(k
):
1298 linux_arch
= arch_map
[k
]
1300 assert linux_arch
is not None
1301 cmdlist
.push_subdesc('linux')
1302 cmdlist
.create_use_dir(policy
.builddir
)
1303 cmdlist
.add_command('install-headers',
1304 ['make', '-C', policy
.srcdir
, 'O=%s' % policy
.builddir
,
1305 'ARCH=%s' % linux_arch
,
1306 'INSTALL_HDR_PATH=%s' % policy
.headers_dir
,
1308 cmdlist
.cleanup_dir()
1309 cmdlist
.pop_subdesc()
1311 class Config(object):
1312 """A configuration for building a compiler and associated libraries."""
1314 def __init__(self
, ctx
, arch
, os_name
, variant
=None, gcc_cfg
=None,
1315 first_gcc_cfg
=None, binutils_cfg
=None, glibcs
=None,
1317 """Initialize a Config object."""
1321 self
.variant
= variant
1323 self
.name
= '%s-%s' % (arch
, os_name
)
1325 self
.name
= '%s-%s-%s' % (arch
, os_name
, variant
)
1326 self
.triplet
= '%s-glibc-%s' % (arch
, os_name
)
1330 self
.gcc_cfg
= gcc_cfg
1331 if first_gcc_cfg
is None:
1332 self
.first_gcc_cfg
= []
1334 self
.first_gcc_cfg
= first_gcc_cfg
1335 if binutils_cfg
is None:
1336 self
.binutils_cfg
= []
1338 self
.binutils_cfg
= binutils_cfg
1340 glibcs
= [{'variant': variant
}]
1341 if extra_glibcs
is None:
1343 glibcs
= [Glibc(self
, **g
) for g
in glibcs
]
1344 extra_glibcs
= [Glibc(self
, **g
) for g
in extra_glibcs
]
1345 self
.all_glibcs
= glibcs
+ extra_glibcs
1346 self
.compiler_glibcs
= glibcs
1347 self
.installdir
= ctx
.compiler_installdir(self
.name
)
1348 self
.bindir
= ctx
.compiler_bindir(self
.name
)
1349 self
.sysroot
= ctx
.compiler_sysroot(self
.name
)
1350 self
.builddir
= os
.path
.join(ctx
.builddir
, 'compilers', self
.name
)
1351 self
.logsdir
= os
.path
.join(ctx
.logsdir
, 'compilers', self
.name
)
1353 def component_builddir(self
, component
):
1354 """Return the directory to use for a (non-glibc) build."""
1355 return self
.ctx
.component_builddir('compilers', self
.name
, component
)
1358 """Generate commands to build this compiler."""
1359 self
.ctx
.remove_recreate_dirs(self
.installdir
, self
.builddir
,
1361 cmdlist
= CommandList('compilers-%s' % self
.name
, self
.ctx
.keep
)
1362 cmdlist
.add_command('check-host-libraries',
1364 os
.path
.join(self
.ctx
.host_libraries_installdir
,
1366 cmdlist
.use_path(self
.bindir
)
1367 self
.build_cross_tool(cmdlist
, 'binutils', 'binutils',
1369 '--disable-gdbserver',
1370 '--disable-libdecnumber',
1371 '--disable-readline',
1372 '--disable-sim'] + self
.binutils_cfg
)
1373 if self
.os
.startswith('linux'):
1374 install_linux_headers(LinuxHeadersPolicyForBuild(self
), cmdlist
)
1375 self
.build_gcc(cmdlist
, True)
1376 if self
.os
== 'gnu':
1377 self
.install_gnumach_headers(cmdlist
)
1378 self
.build_cross_tool(cmdlist
, 'mig', 'mig')
1379 self
.install_hurd_headers(cmdlist
)
1380 for g
in self
.compiler_glibcs
:
1381 cmdlist
.push_subdesc('glibc')
1382 cmdlist
.push_subdesc(g
.name
)
1383 g
.build_glibc(cmdlist
, GlibcPolicyForCompiler(g
))
1384 cmdlist
.pop_subdesc()
1385 cmdlist
.pop_subdesc()
1386 self
.build_gcc(cmdlist
, False)
1387 cmdlist
.add_command('done', ['touch',
1388 os
.path
.join(self
.installdir
, 'ok')])
1389 self
.ctx
.add_makefile_cmdlist('compilers-%s' % self
.name
, cmdlist
,
1392 def build_cross_tool(self
, cmdlist
, tool_src
, tool_build
, extra_opts
=None):
1393 """Build one cross tool."""
1394 srcdir
= self
.ctx
.component_srcdir(tool_src
)
1395 builddir
= self
.component_builddir(tool_build
)
1396 cmdlist
.push_subdesc(tool_build
)
1397 cmdlist
.create_use_dir(builddir
)
1398 cfg_cmd
= [os
.path
.join(srcdir
, 'configure'),
1399 '--prefix=%s' % self
.installdir
,
1400 '--build=%s' % self
.ctx
.build_triplet
,
1401 '--host=%s' % self
.ctx
.build_triplet
,
1402 '--target=%s' % self
.triplet
,
1403 '--with-sysroot=%s' % self
.sysroot
]
1405 cfg_cmd
.extend(extra_opts
)
1406 cmdlist
.add_command('configure', cfg_cmd
)
1407 cmdlist
.add_command('build', ['make'])
1408 # Parallel "make install" for GCC has race conditions that can
1409 # cause it to fail; see
1410 # <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=42980>. Such
1411 # problems are not known for binutils, but doing the
1412 # installation in parallel within a particular toolchain build
1413 # (as opposed to installation of one toolchain from
1414 # build-many-glibcs.py running in parallel to the installation
1415 # of other toolchains being built) is not known to be
1416 # significantly beneficial, so it is simplest just to disable
1417 # parallel install for cross tools here.
1418 cmdlist
.add_command('install', ['make', '-j1', 'install'])
1419 cmdlist
.cleanup_dir()
1420 cmdlist
.pop_subdesc()
1422 def install_gnumach_headers(self
, cmdlist
):
1423 """Install GNU Mach headers."""
1424 srcdir
= self
.ctx
.component_srcdir('gnumach')
1425 builddir
= self
.component_builddir('gnumach')
1426 cmdlist
.push_subdesc('gnumach')
1427 cmdlist
.create_use_dir(builddir
)
1428 cmdlist
.add_command('configure',
1429 [os
.path
.join(srcdir
, 'configure'),
1430 '--build=%s' % self
.ctx
.build_triplet
,
1431 '--host=%s' % self
.triplet
,
1433 'CC=%s-gcc -nostdlib' % self
.triplet
])
1434 cmdlist
.add_command('install', ['make', 'DESTDIR=%s' % self
.sysroot
,
1436 cmdlist
.cleanup_dir()
1437 cmdlist
.pop_subdesc()
1439 def install_hurd_headers(self
, cmdlist
):
1440 """Install Hurd headers."""
1441 srcdir
= self
.ctx
.component_srcdir('hurd')
1442 builddir
= self
.component_builddir('hurd')
1443 cmdlist
.push_subdesc('hurd')
1444 cmdlist
.create_use_dir(builddir
)
1445 cmdlist
.add_command('configure',
1446 [os
.path
.join(srcdir
, 'configure'),
1447 '--build=%s' % self
.ctx
.build_triplet
,
1448 '--host=%s' % self
.triplet
,
1450 '--disable-profile', '--without-parted',
1451 'CC=%s-gcc -nostdlib' % self
.triplet
])
1452 cmdlist
.add_command('install', ['make', 'prefix=%s' % self
.sysroot
,
1453 'no_deps=t', 'install-headers'])
1454 cmdlist
.cleanup_dir()
1455 cmdlist
.pop_subdesc()
1457 def build_gcc(self
, cmdlist
, bootstrap
):
1459 # libssp is of little relevance with glibc's own stack
1460 # checking support. libcilkrts does not support GNU/Hurd (and
1461 # has been removed in GCC 8, so --disable-libcilkrts can be
1462 # removed once glibc no longer supports building with older
1463 # GCC versions). --enable-initfini-array is enabled by default
1464 # in GCC 12, which can be removed when GCC 12 becomes the
1465 # minimum requirement.
1466 cfg_opts
= list(self
.gcc_cfg
)
1467 cfg_opts
+= ['--enable-initfini-array']
1468 cfg_opts
+= ['--disable-libssp', '--disable-libcilkrts']
1469 host_libs
= self
.ctx
.host_libraries_installdir
1470 cfg_opts
+= ['--with-gmp=%s' % host_libs
,
1471 '--with-mpfr=%s' % host_libs
,
1472 '--with-mpc=%s' % host_libs
]
1474 tool_build
= 'gcc-first'
1475 # Building a static-only, C-only compiler that is
1476 # sufficient to build glibc. Various libraries and
1477 # features that may require libc headers must be disabled.
1478 # When configuring with a sysroot, --with-newlib is
1479 # required to define inhibit_libc (to stop some parts of
1480 # libgcc including libc headers); --without-headers is not
1482 cfg_opts
+= ['--enable-languages=c', '--disable-shared',
1483 '--disable-threads',
1484 '--disable-libatomic',
1485 '--disable-decimal-float',
1487 '--disable-libgomp',
1490 '--disable-libquadmath',
1491 '--disable-libsanitizer',
1492 '--without-headers', '--with-newlib',
1493 '--with-glibc-version=%s' % self
.ctx
.glibc_version
1495 cfg_opts
+= self
.first_gcc_cfg
1498 # libsanitizer commonly breaks because of glibc header
1499 # changes, or on unusual targets. C++ pre-compiled
1500 # headers are not used during the glibc build and are
1501 # expensive to create.
1502 if not self
.ctx
.full_gcc
:
1503 cfg_opts
+= ['--disable-libsanitizer',
1504 '--disable-libstdcxx-pch']
1505 langs
= 'all' if self
.ctx
.full_gcc
else 'c,c++'
1506 cfg_opts
+= ['--enable-languages=%s' % langs
,
1507 '--enable-shared', '--enable-threads']
1508 self
.build_cross_tool(cmdlist
, 'gcc', tool_build
, cfg_opts
)
1510 class GlibcPolicyDefault(object):
1511 """Build policy for glibc: common defaults."""
1513 def __init__(self
, glibc
):
1514 self
.srcdir
= glibc
.ctx
.component_srcdir('glibc')
1515 self
.use_usr
= glibc
.os
!= 'gnu'
1516 self
.prefix
= '/usr' if self
.use_usr
else ''
1517 self
.configure_args
= [
1518 '--prefix=%s' % self
.prefix
,
1520 '--build=%s' % glibc
.ctx
.build_triplet
,
1521 '--host=%s' % glibc
.triplet
,
1522 'CC=%s' % glibc
.tool_name('gcc'),
1523 'CXX=%s' % glibc
.tool_name('g++'),
1524 'AR=%s' % glibc
.tool_name('ar'),
1525 'AS=%s' % glibc
.tool_name('as'),
1526 'LD=%s' % glibc
.tool_name('ld'),
1527 'NM=%s' % glibc
.tool_name('nm'),
1528 'OBJCOPY=%s' % glibc
.tool_name('objcopy'),
1529 'OBJDUMP=%s' % glibc
.tool_name('objdump'),
1530 'RANLIB=%s' % glibc
.tool_name('ranlib'),
1531 'READELF=%s' % glibc
.tool_name('readelf'),
1532 'STRIP=%s' % glibc
.tool_name('strip'),
1534 if glibc
.os
== 'gnu':
1535 self
.configure_args
.append('MIG=%s' % glibc
.tool_name('mig'))
1537 self
.configure_args
.append('CFLAGS=%s' % glibc
.cflags
)
1538 self
.configure_args
.append('CXXFLAGS=%s' % glibc
.cflags
)
1539 self
.configure_args
+= glibc
.cfg
1541 def configure(self
, cmdlist
):
1542 """Invoked to add the configure command to the command list."""
1543 cmdlist
.add_command('configure',
1544 [os
.path
.join(self
.srcdir
, 'configure'),
1545 *self
.configure_args
])
1547 def extra_commands(self
, cmdlist
):
1548 """Invoked to inject additional commands (make check) after build."""
1551 class GlibcPolicyForCompiler(GlibcPolicyDefault
):
1552 """Build policy for glibc during the compilers stage."""
1554 def __init__(self
, glibc
):
1555 super().__init
__(glibc
)
1556 self
.builddir
= glibc
.ctx
.component_builddir(
1557 'compilers', glibc
.compiler
.name
, 'glibc', glibc
.name
)
1558 self
.installdir
= glibc
.compiler
.sysroot
1560 class GlibcPolicyForBuild(GlibcPolicyDefault
):
1561 """Build policy for glibc during the glibcs stage."""
1563 def __init__(self
, glibc
):
1564 super().__init
__(glibc
)
1565 self
.builddir
= glibc
.ctx
.component_builddir(
1566 'glibcs', glibc
.name
, 'glibc')
1567 self
.installdir
= glibc
.ctx
.glibc_installdir(glibc
.name
)
1569 self
.strip
= glibc
.tool_name('strip')
1572 self
.save_logs
= glibc
.ctx
.save_logs
1574 def extra_commands(self
, cmdlist
):
1576 # Avoid stripping libc.so and libpthread.so, which are
1577 # linker scripts stored in /lib on Hurd.
1578 find_command
= 'find %s/lib* -name "*.so*"' % self
.installdir
1579 cmdlist
.add_command('strip', ['sh', '-c', (
1580 'set -e; for f in $(%s); do '
1581 'if ! head -c16 $f | grep -q "GNU ld script"; then %s $f; fi; '
1582 'done' % (find_command
, self
.strip
))])
1583 cmdlist
.add_command('check', ['make', 'check'])
1584 cmdlist
.add_command('save-logs', [self
.save_logs
], always_run
=True)
1586 class GlibcPolicyForUpdateSyscalls(GlibcPolicyDefault
):
1587 """Build policy for glibc during update-syscalls."""
1589 def __init__(self
, glibc
):
1590 super().__init
__(glibc
)
1591 self
.builddir
= glibc
.ctx
.component_builddir(
1592 'update-syscalls', glibc
.name
, 'glibc')
1593 self
.linuxdir
= glibc
.ctx
.component_builddir(
1594 'update-syscalls', glibc
.name
, 'linux')
1595 self
.linux_policy
= LinuxHeadersPolicyForUpdateSyscalls(
1596 glibc
, self
.linuxdir
)
1597 self
.configure_args
.insert(
1598 0, '--with-headers=%s' % os
.path
.join(self
.linuxdir
, 'include'))
1599 # self.installdir not set because installation is not supported
1601 class Glibc(object):
1602 """A configuration for building glibc."""
1604 def __init__(self
, compiler
, arch
=None, os_name
=None, variant
=None,
1605 cfg
=None, ccopts
=None, cflags
=None):
1606 """Initialize a Glibc object."""
1607 self
.ctx
= compiler
.ctx
1608 self
.compiler
= compiler
1610 self
.arch
= compiler
.arch
1614 self
.os
= compiler
.os
1617 self
.variant
= variant
1619 self
.name
= '%s-%s' % (self
.arch
, self
.os
)
1621 self
.name
= '%s-%s-%s' % (self
.arch
, self
.os
, variant
)
1622 self
.triplet
= '%s-glibc-%s' % (self
.arch
, self
.os
)
1627 # ccopts contain ABI options and are passed to configure as CC / CXX.
1628 self
.ccopts
= ccopts
1629 # cflags contain non-ABI options like -g or -O and are passed to
1630 # configure as CFLAGS / CXXFLAGS.
1631 self
.cflags
= cflags
1633 def tool_name(self
, tool
):
1634 """Return the name of a cross-compilation tool."""
1635 ctool
= '%s-%s' % (self
.compiler
.triplet
, tool
)
1636 if self
.ccopts
and (tool
== 'gcc' or tool
== 'g++'):
1637 ctool
= '%s %s' % (ctool
, self
.ccopts
)
1641 """Generate commands to build this glibc."""
1642 builddir
= self
.ctx
.component_builddir('glibcs', self
.name
, 'glibc')
1643 installdir
= self
.ctx
.glibc_installdir(self
.name
)
1644 logsdir
= os
.path
.join(self
.ctx
.logsdir
, 'glibcs', self
.name
)
1645 self
.ctx
.remove_recreate_dirs(installdir
, builddir
, logsdir
)
1646 cmdlist
= CommandList('glibcs-%s' % self
.name
, self
.ctx
.keep
)
1647 cmdlist
.add_command('check-compilers',
1649 os
.path
.join(self
.compiler
.installdir
, 'ok')])
1650 cmdlist
.use_path(self
.compiler
.bindir
)
1651 self
.build_glibc(cmdlist
, GlibcPolicyForBuild(self
))
1652 self
.ctx
.add_makefile_cmdlist('glibcs-%s' % self
.name
, cmdlist
,
1655 def build_glibc(self
, cmdlist
, policy
):
1656 """Generate commands to build this glibc, either as part of a compiler
1657 build or with the bootstrapped compiler (and in the latter case, run
1659 cmdlist
.create_use_dir(policy
.builddir
)
1660 policy
.configure(cmdlist
)
1661 cmdlist
.add_command('build', ['make'])
1662 cmdlist
.add_command('install', ['make', 'install',
1663 'install_root=%s' % policy
.installdir
])
1664 # GCC uses paths such as lib/../lib64, so make sure lib
1665 # directories always exist.
1666 mkdir_cmd
= ['mkdir', '-p',
1667 os
.path
.join(policy
.installdir
, 'lib')]
1669 mkdir_cmd
+= [os
.path
.join(policy
.installdir
, 'usr', 'lib')]
1670 cmdlist
.add_command('mkdir-lib', mkdir_cmd
)
1671 policy
.extra_commands(cmdlist
)
1672 cmdlist
.cleanup_dir()
1674 def update_syscalls(self
):
1675 if self
.os
== 'gnu':
1676 # Hurd does not have system call tables that need updating.
1679 policy
= GlibcPolicyForUpdateSyscalls(self
)
1680 logsdir
= os
.path
.join(self
.ctx
.logsdir
, 'update-syscalls', self
.name
)
1681 self
.ctx
.remove_recreate_dirs(policy
.builddir
, logsdir
)
1682 cmdlist
= CommandList('update-syscalls-%s' % self
.name
, self
.ctx
.keep
)
1683 cmdlist
.add_command('check-compilers',
1685 os
.path
.join(self
.compiler
.installdir
, 'ok')])
1686 cmdlist
.use_path(self
.compiler
.bindir
)
1688 install_linux_headers(policy
.linux_policy
, cmdlist
)
1690 cmdlist
.create_use_dir(policy
.builddir
)
1691 policy
.configure(cmdlist
)
1692 cmdlist
.add_command('build', ['make', 'update-syscall-lists'])
1693 cmdlist
.cleanup_dir()
1694 self
.ctx
.add_makefile_cmdlist('update-syscalls-%s' % self
.name
,
1697 class Command(object):
1698 """A command run in the build process."""
1700 def __init__(self
, desc
, num
, dir, path
, command
, always_run
=False):
1701 """Initialize a Command object."""
1705 trans
= str.maketrans({' ': '-'})
1706 self
.logbase
= '%03d-%s' % (num
, desc
.translate(trans
))
1707 self
.command
= command
1708 self
.always_run
= always_run
1711 def shell_make_quote_string(s
):
1712 """Given a string not containing a newline, quote it for use by the
1714 assert '\n' not in s
1715 if re
.fullmatch('[]+,./0-9@A-Z_a-z-]+', s
):
1717 strans
= str.maketrans({"'": "'\\''"})
1718 s
= "'%s'" % s
.translate(strans
)
1719 mtrans
= str.maketrans({'$': '$$'})
1720 return s
.translate(mtrans
)
1723 def shell_make_quote_list(l
, translate_make
):
1724 """Given a list of strings not containing newlines, quote them for use
1725 by the shell and make, returning a single string. If translate_make
1726 is true and the first string is 'make', change it to $(MAKE)."""
1727 l
= [Command
.shell_make_quote_string(s
) for s
in l
]
1728 if translate_make
and l
[0] == 'make':
1732 def shell_make_quote(self
):
1733 """Return this command quoted for the shell and make."""
1734 return self
.shell_make_quote_list(self
.command
, True)
1737 class CommandList(object):
1738 """A list of commands run in the build process."""
1740 def __init__(self
, desc
, keep
):
1741 """Initialize a CommandList object."""
1748 def desc_txt(self
, desc
):
1749 """Return the description to use for a command."""
1750 return '%s %s' % (' '.join(self
.desc
), desc
)
1752 def use_dir(self
, dir):
1753 """Set the default directory for subsequent commands."""
1756 def use_path(self
, path
):
1757 """Set a directory to be prepended to the PATH for subsequent
1761 def push_subdesc(self
, subdesc
):
1762 """Set the default subdescription for subsequent commands (e.g., the
1763 name of a component being built, within the series of commands
1765 self
.desc
.append(subdesc
)
1767 def pop_subdesc(self
):
1768 """Pop a subdescription from the list of descriptions."""
1771 def create_use_dir(self
, dir):
1772 """Remove and recreate a directory and use it for subsequent
1774 self
.add_command_dir('rm', None, ['rm', '-rf', dir])
1775 self
.add_command_dir('mkdir', None, ['mkdir', '-p', dir])
1778 def add_command_dir(self
, desc
, dir, command
, always_run
=False):
1779 """Add a command to run in a given directory."""
1780 cmd
= Command(self
.desc_txt(desc
), len(self
.cmdlist
), dir, self
.path
,
1781 command
, always_run
)
1782 self
.cmdlist
.append(cmd
)
1784 def add_command(self
, desc
, command
, always_run
=False):
1785 """Add a command to run in the default directory."""
1786 cmd
= Command(self
.desc_txt(desc
), len(self
.cmdlist
), self
.dir,
1787 self
.path
, command
, always_run
)
1788 self
.cmdlist
.append(cmd
)
1790 def cleanup_dir(self
, desc
='cleanup', dir=None):
1791 """Clean up a build directory. If no directory is specified, the
1792 default directory is cleaned up and ceases to be the default
1797 if self
.keep
!= 'all':
1798 self
.add_command_dir(desc
, None, ['rm', '-rf', dir],
1799 always_run
=(self
.keep
== 'none'))
1801 def makefile_commands(self
, wrapper
, logsdir
):
1802 """Return the sequence of commands in the form of text for a Makefile.
1803 The given wrapper script takes arguments: base of logs for
1804 previous command, or empty; base of logs for this command;
1805 description; directory; PATH addition; the command itself."""
1806 # prev_base is the base of the name for logs of the previous
1807 # command that is not always-run (that is, a build command,
1808 # whose failure should stop subsequent build commands from
1809 # being run, as opposed to a cleanup command, which is run
1810 # even if previous commands failed).
1813 for c
in self
.cmdlist
:
1814 ctxt
= c
.shell_make_quote()
1815 if prev_base
and not c
.always_run
:
1816 prev_log
= os
.path
.join(logsdir
, prev_base
)
1819 this_log
= os
.path
.join(logsdir
, c
.logbase
)
1820 if not c
.always_run
:
1821 prev_base
= c
.logbase
1830 prelims
= [wrapper
, prev_log
, this_log
, c
.desc
, dir, path
]
1831 prelim_txt
= Command
.shell_make_quote_list(prelims
, False)
1832 cmds
.append('\t@%s %s' % (prelim_txt
, ctxt
))
1833 return '\n'.join(cmds
)
1835 def status_logs(self
, logsdir
):
1836 """Return the list of log files with command status."""
1837 return [os
.path
.join(logsdir
, '%s-status.txt' % c
.logbase
)
1838 for c
in self
.cmdlist
]
1842 """Return an argument parser for this module."""
1843 parser
= argparse
.ArgumentParser(description
=__doc__
)
1844 parser
.add_argument('-j', dest
='parallelism',
1845 help='Run this number of jobs in parallel',
1846 type=int, default
=os
.cpu_count())
1847 parser
.add_argument('--keep', dest
='keep',
1848 help='Whether to keep all build directories, '
1849 'none or only those from failed builds',
1850 default
='none', choices
=('none', 'all', 'failed'))
1851 parser
.add_argument('--replace-sources', action
='store_true',
1852 help='Remove and replace source directories '
1853 'with the wrong version of a component')
1854 parser
.add_argument('--strip', action
='store_true',
1855 help='Strip installed glibc libraries')
1856 parser
.add_argument('--full-gcc', action
='store_true',
1857 help='Build GCC with all languages and libsanitizer')
1858 parser
.add_argument('--shallow', action
='store_true',
1859 help='Do not download Git history during checkout')
1860 parser
.add_argument('topdir',
1861 help='Toplevel working directory')
1862 parser
.add_argument('action',
1864 choices
=('checkout', 'bot-cycle', 'bot',
1865 'host-libraries', 'compilers', 'glibcs',
1866 'update-syscalls', 'list-compilers',
1868 parser
.add_argument('configs',
1869 help='Versions to check out or configurations to build',
1875 """The main entry point."""
1876 parser
= get_parser()
1877 opts
= parser
.parse_args(argv
)
1878 topdir
= os
.path
.abspath(opts
.topdir
)
1879 ctx
= Context(topdir
, opts
.parallelism
, opts
.keep
, opts
.replace_sources
,
1880 opts
.strip
, opts
.full_gcc
, opts
.action
,
1881 shallow
=opts
.shallow
)
1882 ctx
.run_builds(opts
.action
, opts
.configs
)
1885 if __name__
== '__main__':