2 # Build many configurations of glibc.
3 # Copyright (C) 2016-2021 Free Software Foundation, Inc.
4 # This file is part of the GNU C Library.
6 # The GNU C Library is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU Lesser General Public
8 # License as published by the Free Software Foundation; either
9 # version 2.1 of the License, or (at your option) any later version.
11 # The GNU C Library is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # Lesser General Public License for more details.
16 # You should have received a copy of the GNU Lesser General Public
17 # License along with the GNU C Library; if not, see
18 # <https://www.gnu.org/licenses/>.
20 """Build many configurations of glibc.
22 This script takes as arguments a directory name (containing a src
23 subdirectory with sources of the relevant toolchain components) and a
24 description of what to do: 'checkout', to check out sources into that
25 directory, 'bot-cycle', to run a series of checkout and build steps,
26 'bot', to run 'bot-cycle' repeatedly, 'host-libraries', to build
27 libraries required by the toolchain, 'compilers', to build
28 cross-compilers for various configurations, or 'glibcs', to build
29 glibc for various configurations and run the compilation parts of the
30 testsuite. Subsequent arguments name the versions of components to
31 check out (<component>-<version), for 'checkout', or, for actions
32 other than 'checkout' and 'bot-cycle', name configurations for which
33 compilers or glibc are to be built.
35 The 'list-compilers' command prints the name of each available
36 compiler configuration, without building anything. The 'list-glibcs'
37 command prints the name of each glibc compiler configuration, followed
38 by the space, followed by the name of the compiler configuration used
39 for building this glibc variant.
45 import email
.mime
.text
61 class _CompletedProcess
:
62 def __init__(self
, args
, returncode
, stdout
=None, stderr
=None):
64 self
.returncode
= returncode
68 def _run(*popenargs
, input=None, timeout
=None, check
=False, **kwargs
):
69 assert(timeout
is None)
70 with subprocess
.Popen(*popenargs
, **kwargs
) as process
:
72 stdout
, stderr
= process
.communicate(input)
77 returncode
= process
.poll()
78 if check
and returncode
:
79 raise subprocess
.CalledProcessError(returncode
, popenargs
)
80 return _CompletedProcess(popenargs
, returncode
, stdout
, stderr
)
85 class Context(object):
86 """The global state associated with builds in a given directory."""
88 def __init__(self
, topdir
, parallelism
, keep
, replace_sources
, strip
,
89 full_gcc
, action
, shallow
=False):
90 """Initialize the context."""
92 self
.parallelism
= parallelism
94 self
.replace_sources
= replace_sources
96 self
.full_gcc
= full_gcc
97 self
.shallow
= shallow
98 self
.srcdir
= os
.path
.join(topdir
, 'src')
99 self
.versions_json
= os
.path
.join(self
.srcdir
, 'versions.json')
100 self
.build_state_json
= os
.path
.join(topdir
, 'build-state.json')
101 self
.bot_config_json
= os
.path
.join(topdir
, 'bot-config.json')
102 self
.installdir
= os
.path
.join(topdir
, 'install')
103 self
.host_libraries_installdir
= os
.path
.join(self
.installdir
,
105 self
.builddir
= os
.path
.join(topdir
, 'build')
106 self
.logsdir
= os
.path
.join(topdir
, 'logs')
107 self
.logsdir_old
= os
.path
.join(topdir
, 'logs-old')
108 self
.makefile
= os
.path
.join(self
.builddir
, 'Makefile')
109 self
.wrapper
= os
.path
.join(self
.builddir
, 'wrapper')
110 self
.save_logs
= os
.path
.join(self
.builddir
, 'save-logs')
111 self
.script_text
= self
.get_script_text()
112 if action
not in ('checkout', 'list-compilers', 'list-glibcs'):
113 self
.build_triplet
= self
.get_build_triplet()
114 self
.glibc_version
= self
.get_glibc_version()
116 self
.glibc_configs
= {}
117 self
.makefile_pieces
= ['.PHONY: all\n']
118 self
.add_all_configs()
119 self
.load_versions_json()
120 self
.load_build_state_json()
121 self
.status_log_list
= []
122 self
.email_warning
= False
124 def get_script_text(self
):
125 """Return the text of this script."""
126 with
open(sys
.argv
[0], 'r') as f
:
130 """Re-execute this script with the same arguments."""
132 os
.execv(sys
.executable
, [sys
.executable
] + sys
.argv
)
134 def get_build_triplet(self
):
135 """Determine the build triplet with config.guess."""
136 config_guess
= os
.path
.join(self
.component_srcdir('gcc'),
138 cg_out
= subprocess
.run([config_guess
], stdout
=subprocess
.PIPE
,
139 check
=True, universal_newlines
=True).stdout
140 return cg_out
.rstrip()
142 def get_glibc_version(self
):
143 """Determine the glibc version number (major.minor)."""
144 version_h
= os
.path
.join(self
.component_srcdir('glibc'), 'version.h')
145 with
open(version_h
, 'r') as f
:
146 lines
= f
.readlines()
147 starttext
= '#define VERSION "'
149 if l
.startswith(starttext
):
150 l
= l
[len(starttext
):]
152 m
= re
.fullmatch('([0-9]+)\.([0-9]+)[.0-9]*', l
)
153 return '%s.%s' % m
.group(1, 2)
154 print('error: could not determine glibc version')
157 def add_all_configs(self
):
158 """Add all known glibc build configurations."""
159 self
.add_config(arch
='aarch64',
161 extra_glibcs
=[{'variant': 'disable-multi-arch',
162 'cfg': ['--disable-multi-arch']}])
163 self
.add_config(arch
='aarch64_be',
165 self
.add_config(arch
='arc',
167 gcc_cfg
=['--disable-multilib', '--with-cpu=hs38'])
168 self
.add_config(arch
='arc',
169 os_name
='linux-gnuhf',
170 gcc_cfg
=['--disable-multilib', '--with-cpu=hs38_linux'])
171 self
.add_config(arch
='arceb',
173 gcc_cfg
=['--disable-multilib', '--with-cpu=hs38'])
174 self
.add_config(arch
='alpha',
176 self
.add_config(arch
='arm',
177 os_name
='linux-gnueabi',
178 extra_glibcs
=[{'variant': 'v4t',
179 'ccopts': '-march=armv4t'}])
180 self
.add_config(arch
='armeb',
181 os_name
='linux-gnueabi')
182 self
.add_config(arch
='armeb',
183 os_name
='linux-gnueabi',
185 gcc_cfg
=['--with-arch=armv7-a'])
186 self
.add_config(arch
='arm',
187 os_name
='linux-gnueabihf',
188 gcc_cfg
=['--with-float=hard', '--with-cpu=arm926ej-s'],
189 extra_glibcs
=[{'variant': 'v7a',
190 'ccopts': '-march=armv7-a -mfpu=vfpv3'},
193 '-mthumb -march=armv7-a -mfpu=vfpv3'},
194 {'variant': 'v7a-disable-multi-arch',
195 'ccopts': '-march=armv7-a -mfpu=vfpv3',
196 'cfg': ['--disable-multi-arch']}])
197 self
.add_config(arch
='armeb',
198 os_name
='linux-gnueabihf',
199 gcc_cfg
=['--with-float=hard', '--with-cpu=arm926ej-s'])
200 self
.add_config(arch
='armeb',
201 os_name
='linux-gnueabihf',
203 gcc_cfg
=['--with-float=hard', '--with-arch=armv7-a',
205 self
.add_config(arch
='csky',
206 os_name
='linux-gnuabiv2',
208 gcc_cfg
=['--disable-multilib'])
209 self
.add_config(arch
='csky',
210 os_name
='linux-gnuabiv2',
211 gcc_cfg
=['--with-float=hard', '--disable-multilib'])
212 self
.add_config(arch
='hppa',
214 self
.add_config(arch
='i686',
216 self
.add_config(arch
='ia64',
218 first_gcc_cfg
=['--with-system-libunwind'],
219 binutils_cfg
=['--enable-obsolete'])
220 self
.add_config(arch
='m68k',
222 gcc_cfg
=['--disable-multilib'])
223 self
.add_config(arch
='m68k',
226 gcc_cfg
=['--with-arch=cf', '--disable-multilib'])
227 self
.add_config(arch
='m68k',
229 variant
='coldfire-soft',
230 gcc_cfg
=['--with-arch=cf', '--with-cpu=54455',
231 '--disable-multilib'])
232 self
.add_config(arch
='microblaze',
234 gcc_cfg
=['--disable-multilib'])
235 self
.add_config(arch
='microblazeel',
237 gcc_cfg
=['--disable-multilib'])
238 self
.add_config(arch
='mips64',
240 gcc_cfg
=['--with-mips-plt'],
241 glibcs
=[{'variant': 'n32'},
243 'ccopts': '-mabi=32'},
245 'ccopts': '-mabi=64'}])
246 self
.add_config(arch
='mips64',
249 gcc_cfg
=['--with-mips-plt', '--with-float=soft'],
250 glibcs
=[{'variant': 'n32-soft'},
253 'ccopts': '-mabi=32'},
254 {'variant': 'n64-soft',
255 'ccopts': '-mabi=64'}])
256 self
.add_config(arch
='mips64',
259 gcc_cfg
=['--with-mips-plt', '--with-nan=2008',
260 '--with-arch-64=mips64r2',
261 '--with-arch-32=mips32r2'],
262 glibcs
=[{'variant': 'n32-nan2008'},
263 {'variant': 'nan2008',
265 'ccopts': '-mabi=32'},
266 {'variant': 'n64-nan2008',
267 'ccopts': '-mabi=64'}])
268 self
.add_config(arch
='mips64',
270 variant
='nan2008-soft',
271 gcc_cfg
=['--with-mips-plt', '--with-nan=2008',
272 '--with-arch-64=mips64r2',
273 '--with-arch-32=mips32r2',
274 '--with-float=soft'],
275 glibcs
=[{'variant': 'n32-nan2008-soft'},
276 {'variant': 'nan2008-soft',
278 'ccopts': '-mabi=32'},
279 {'variant': 'n64-nan2008-soft',
280 'ccopts': '-mabi=64'}])
281 self
.add_config(arch
='mips64el',
283 gcc_cfg
=['--with-mips-plt'],
284 glibcs
=[{'variant': 'n32'},
286 'ccopts': '-mabi=32'},
288 'ccopts': '-mabi=64'}])
289 self
.add_config(arch
='mips64el',
292 gcc_cfg
=['--with-mips-plt', '--with-float=soft'],
293 glibcs
=[{'variant': 'n32-soft'},
296 'ccopts': '-mabi=32'},
297 {'variant': 'n64-soft',
298 'ccopts': '-mabi=64'}])
299 self
.add_config(arch
='mips64el',
302 gcc_cfg
=['--with-mips-plt', '--with-nan=2008',
303 '--with-arch-64=mips64r2',
304 '--with-arch-32=mips32r2'],
305 glibcs
=[{'variant': 'n32-nan2008'},
306 {'variant': 'nan2008',
308 'ccopts': '-mabi=32'},
309 {'variant': 'n64-nan2008',
310 'ccopts': '-mabi=64'}])
311 self
.add_config(arch
='mips64el',
313 variant
='nan2008-soft',
314 gcc_cfg
=['--with-mips-plt', '--with-nan=2008',
315 '--with-arch-64=mips64r2',
316 '--with-arch-32=mips32r2',
317 '--with-float=soft'],
318 glibcs
=[{'variant': 'n32-nan2008-soft'},
319 {'variant': 'nan2008-soft',
321 'ccopts': '-mabi=32'},
322 {'variant': 'n64-nan2008-soft',
323 'ccopts': '-mabi=64'}])
324 self
.add_config(arch
='mipsisa64r6el',
326 gcc_cfg
=['--with-mips-plt', '--with-nan=2008',
327 '--with-arch-64=mips64r6',
328 '--with-arch-32=mips32r6',
329 '--with-float=hard'],
330 glibcs
=[{'variant': 'n32'},
331 {'arch': 'mipsisa32r6el',
332 'ccopts': '-mabi=32'},
334 'ccopts': '-mabi=64'}])
335 self
.add_config(arch
='nios2',
337 self
.add_config(arch
='powerpc',
339 gcc_cfg
=['--disable-multilib', '--enable-secureplt'],
340 extra_glibcs
=[{'variant': 'power4',
341 'ccopts': '-mcpu=power4',
342 'cfg': ['--with-cpu=power4']}])
343 self
.add_config(arch
='powerpc',
346 gcc_cfg
=['--disable-multilib', '--with-float=soft',
347 '--enable-secureplt'])
348 self
.add_config(arch
='powerpc64',
350 gcc_cfg
=['--disable-multilib', '--enable-secureplt'])
351 self
.add_config(arch
='powerpc64le',
353 gcc_cfg
=['--disable-multilib', '--enable-secureplt'])
354 self
.add_config(arch
='riscv32',
356 variant
='rv32imac-ilp32',
357 gcc_cfg
=['--with-arch=rv32imac', '--with-abi=ilp32',
358 '--disable-multilib'])
359 self
.add_config(arch
='riscv32',
361 variant
='rv32imafdc-ilp32',
362 gcc_cfg
=['--with-arch=rv32imafdc', '--with-abi=ilp32',
363 '--disable-multilib'])
364 self
.add_config(arch
='riscv32',
366 variant
='rv32imafdc-ilp32d',
367 gcc_cfg
=['--with-arch=rv32imafdc', '--with-abi=ilp32d',
368 '--disable-multilib'])
369 self
.add_config(arch
='riscv64',
371 variant
='rv64imac-lp64',
372 gcc_cfg
=['--with-arch=rv64imac', '--with-abi=lp64',
373 '--disable-multilib'])
374 self
.add_config(arch
='riscv64',
376 variant
='rv64imafdc-lp64',
377 gcc_cfg
=['--with-arch=rv64imafdc', '--with-abi=lp64',
378 '--disable-multilib'])
379 self
.add_config(arch
='riscv64',
381 variant
='rv64imafdc-lp64d',
382 gcc_cfg
=['--with-arch=rv64imafdc', '--with-abi=lp64d',
383 '--disable-multilib'])
384 self
.add_config(arch
='s390x',
387 {'arch': 's390', 'ccopts': '-m31'}],
388 extra_glibcs
=[{'variant': 'O3',
390 self
.add_config(arch
='sh3',
392 self
.add_config(arch
='sh3eb',
394 self
.add_config(arch
='sh4',
396 self
.add_config(arch
='sh4eb',
398 self
.add_config(arch
='sh4',
401 gcc_cfg
=['--without-fp'])
402 self
.add_config(arch
='sh4eb',
405 gcc_cfg
=['--without-fp'])
406 self
.add_config(arch
='sparc64',
410 'ccopts': '-m32 -mlong-double-128 -mcpu=v9'}],
411 extra_glibcs
=[{'variant': 'leon3',
413 'ccopts' : '-m32 -mlong-double-128 -mcpu=leon3'},
414 {'variant': 'disable-multi-arch',
415 'cfg': ['--disable-multi-arch']},
416 {'variant': 'disable-multi-arch',
418 'ccopts': '-m32 -mlong-double-128 -mcpu=v9',
419 'cfg': ['--disable-multi-arch']}])
420 self
.add_config(arch
='x86_64',
422 gcc_cfg
=['--with-multilib-list=m64,m32,mx32'],
424 {'variant': 'x32', 'ccopts': '-mx32'},
425 {'arch': 'i686', 'ccopts': '-m32 -march=i686'}],
426 extra_glibcs
=[{'variant': 'disable-multi-arch',
427 'cfg': ['--disable-multi-arch']},
428 {'variant': 'minimal',
429 'cfg': ['--disable-multi-arch',
431 '--disable-timezone-tools',
433 '--disable-tunables',
435 '--disable-experimental-malloc',
436 '--disable-build-nscd',
438 {'variant': 'static-pie',
439 'cfg': ['--enable-static-pie']},
440 {'variant': 'x32-static-pie',
442 'cfg': ['--enable-static-pie']},
443 {'variant': 'static-pie',
445 'ccopts': '-m32 -march=i686',
446 'cfg': ['--enable-static-pie']},
447 {'variant': 'disable-multi-arch',
449 'ccopts': '-m32 -march=i686',
450 'cfg': ['--disable-multi-arch']},
452 'ccopts': '-m32 -march=i486'},
454 'ccopts': '-m32 -march=i586'}])
456 def add_config(self
, **args
):
457 """Add an individual build configuration."""
458 cfg
= Config(self
, **args
)
459 if cfg
.name
in self
.configs
:
460 print('error: duplicate config %s' % cfg
.name
)
462 self
.configs
[cfg
.name
] = cfg
463 for c
in cfg
.all_glibcs
:
464 if c
.name
in self
.glibc_configs
:
465 print('error: duplicate glibc config %s' % c
.name
)
467 self
.glibc_configs
[c
.name
] = c
469 def component_srcdir(self
, component
):
470 """Return the source directory for a given component, e.g. gcc."""
471 return os
.path
.join(self
.srcdir
, component
)
473 def component_builddir(self
, action
, config
, component
, subconfig
=None):
474 """Return the directory to use for a build."""
477 assert subconfig
is None
478 return os
.path
.join(self
.builddir
, action
, component
)
479 if subconfig
is None:
480 return os
.path
.join(self
.builddir
, action
, config
, component
)
482 # glibc build as part of compiler build.
483 return os
.path
.join(self
.builddir
, action
, config
, component
,
486 def compiler_installdir(self
, config
):
487 """Return the directory in which to install a compiler."""
488 return os
.path
.join(self
.installdir
, 'compilers', config
)
490 def compiler_bindir(self
, config
):
491 """Return the directory in which to find compiler binaries."""
492 return os
.path
.join(self
.compiler_installdir(config
), 'bin')
494 def compiler_sysroot(self
, config
):
495 """Return the sysroot directory for a compiler."""
496 return os
.path
.join(self
.compiler_installdir(config
), 'sysroot')
498 def glibc_installdir(self
, config
):
499 """Return the directory in which to install glibc."""
500 return os
.path
.join(self
.installdir
, 'glibcs', config
)
502 def run_builds(self
, action
, configs
):
503 """Run the requested builds."""
504 if action
== 'checkout':
505 self
.checkout(configs
)
507 if action
== 'bot-cycle':
509 print('error: configurations specified for bot-cycle')
515 print('error: configurations specified for bot')
519 if action
in ('host-libraries', 'list-compilers',
520 'list-glibcs') and configs
:
521 print('error: configurations specified for ' + action
)
523 if action
== 'list-compilers':
524 for name
in sorted(self
.configs
.keys()):
527 if action
== 'list-glibcs':
528 for config
in sorted(self
.glibc_configs
.values(),
529 key
=lambda c
: c
.name
):
530 print(config
.name
, config
.compiler
.name
)
532 self
.clear_last_build_state(action
)
533 build_time
= datetime
.datetime
.utcnow()
534 if action
== 'host-libraries':
535 build_components
= ('gmp', 'mpfr', 'mpc')
538 self
.build_host_libraries()
539 elif action
== 'compilers':
540 build_components
= ('binutils', 'gcc', 'glibc', 'linux', 'mig',
542 old_components
= ('gmp', 'mpfr', 'mpc')
543 old_versions
= self
.build_state
['host-libraries']['build-versions']
544 self
.build_compilers(configs
)
546 build_components
= ('glibc',)
547 old_components
= ('gmp', 'mpfr', 'mpc', 'binutils', 'gcc', 'linux',
548 'mig', 'gnumach', 'hurd')
549 old_versions
= self
.build_state
['compilers']['build-versions']
550 if action
== 'update-syscalls':
551 self
.update_syscalls(configs
)
553 self
.build_glibcs(configs
)
557 # Partial build, do not update stored state.
560 for k
in build_components
:
561 if k
in self
.versions
:
562 build_versions
[k
] = {'version': self
.versions
[k
]['version'],
563 'revision': self
.versions
[k
]['revision']}
564 for k
in old_components
:
565 if k
in old_versions
:
566 build_versions
[k
] = {'version': old_versions
[k
]['version'],
567 'revision': old_versions
[k
]['revision']}
568 self
.update_build_state(action
, build_time
, build_versions
)
571 def remove_dirs(*args
):
572 """Remove directories and their contents if they exist."""
574 shutil
.rmtree(dir, ignore_errors
=True)
577 def remove_recreate_dirs(*args
):
578 """Remove directories if they exist, and create them as empty."""
579 Context
.remove_dirs(*args
)
581 os
.makedirs(dir, exist_ok
=True)
583 def add_makefile_cmdlist(self
, target
, cmdlist
, logsdir
):
584 """Add makefile text for a list of commands."""
585 commands
= cmdlist
.makefile_commands(self
.wrapper
, logsdir
)
586 self
.makefile_pieces
.append('all: %s\n.PHONY: %s\n%s:\n%s\n' %
587 (target
, target
, target
, commands
))
588 self
.status_log_list
.extend(cmdlist
.status_logs(logsdir
))
590 def write_files(self
):
591 """Write out the Makefile and wrapper script."""
592 mftext
= ''.join(self
.makefile_pieces
)
593 with
open(self
.makefile
, 'w') as f
:
603 'prev_status=$prev_base-status.txt\n'
604 'this_status=$this_base-status.txt\n'
605 'this_log=$this_base-log.txt\n'
606 'date > "$this_log"\n'
607 'echo >> "$this_log"\n'
608 'echo "Description: $desc" >> "$this_log"\n'
609 'printf "%s" "Command:" >> "$this_log"\n'
610 'for word in "$@"; do\n'
611 ' if expr "$word" : "[]+,./0-9@A-Z_a-z-]\\\\{1,\\\\}\\$" > /dev/null; then\n'
612 ' printf " %s" "$word"\n'
615 ' printf "%s" "$word" | sed -e "s/\'/\'\\\\\\\\\'\'/"\n'
618 'done >> "$this_log"\n'
619 'echo >> "$this_log"\n'
620 'echo "Directory: $dir" >> "$this_log"\n'
621 'echo "Path addition: $path" >> "$this_log"\n'
622 'echo >> "$this_log"\n'
625 ' echo >> "$this_log"\n'
626 ' echo "$1: $desc" > "$this_status"\n'
627 ' echo "$1: $desc" >> "$this_log"\n'
628 ' echo >> "$this_log"\n'
629 ' date >> "$this_log"\n'
630 ' echo "$1: $desc"\n'
635 ' if [ "$1" != "0" ]; then\n'
636 ' record_status FAIL\n'
639 'if [ "$prev_base" ] && ! grep -q "^PASS" "$prev_status"; then\n'
640 ' record_status UNRESOLVED\n'
642 'if [ "$dir" ]; then\n'
644 ' check_error "$?"\n'
646 'if [ "$path" ]; then\n'
647 ' PATH=$path:$PATH\n'
649 '"$@" < /dev/null >> "$this_log" 2>&1\n'
651 'record_status PASS\n')
652 with
open(self
.wrapper
, 'w') as f
:
653 f
.write(wrapper_text
)
655 mode_exec
= (stat
.S_IRWXU|stat
.S_IRGRP|stat
.S_IXGRP|
656 stat
.S_IROTH|stat
.S_IXOTH
)
657 os
.chmod(self
.wrapper
, mode_exec
)
660 'if ! [ -f tests.sum ]; then\n'
661 ' echo "No test summary available."\n'
666 ' echo "Contents of $1:"\n'
670 ' echo "End of contents of $1."\n'
673 'save_file tests.sum\n'
674 'non_pass_tests=$(grep -v "^PASS: " tests.sum | sed -e "s/^PASS: //")\n'
675 'for t in $non_pass_tests; do\n'
676 ' if [ -f "$t.out" ]; then\n'
677 ' save_file "$t.out"\n'
680 with
open(self
.save_logs
, 'w') as f
:
681 f
.write(save_logs_text
)
682 os
.chmod(self
.save_logs
, mode_exec
)
685 """Do the actual build."""
686 cmd
= ['make', '-O', '-j%d' % self
.parallelism
]
687 subprocess
.run(cmd
, cwd
=self
.builddir
, check
=True)
689 def build_host_libraries(self
):
690 """Build the host libraries."""
691 installdir
= self
.host_libraries_installdir
692 builddir
= os
.path
.join(self
.builddir
, 'host-libraries')
693 logsdir
= os
.path
.join(self
.logsdir
, 'host-libraries')
694 self
.remove_recreate_dirs(installdir
, builddir
, logsdir
)
695 cmdlist
= CommandList('host-libraries', self
.keep
)
696 self
.build_host_library(cmdlist
, 'gmp')
697 self
.build_host_library(cmdlist
, 'mpfr',
698 ['--with-gmp=%s' % installdir
])
699 self
.build_host_library(cmdlist
, 'mpc',
700 ['--with-gmp=%s' % installdir
,
701 '--with-mpfr=%s' % installdir
])
702 cmdlist
.add_command('done', ['touch', os
.path
.join(installdir
, 'ok')])
703 self
.add_makefile_cmdlist('host-libraries', cmdlist
, logsdir
)
705 def build_host_library(self
, cmdlist
, lib
, extra_opts
=None):
706 """Build one host library."""
707 srcdir
= self
.component_srcdir(lib
)
708 builddir
= self
.component_builddir('host-libraries', None, lib
)
709 installdir
= self
.host_libraries_installdir
710 cmdlist
.push_subdesc(lib
)
711 cmdlist
.create_use_dir(builddir
)
712 cfg_cmd
= [os
.path
.join(srcdir
, 'configure'),
713 '--prefix=%s' % installdir
,
716 cfg_cmd
.extend (extra_opts
)
717 cmdlist
.add_command('configure', cfg_cmd
)
718 cmdlist
.add_command('build', ['make'])
719 cmdlist
.add_command('check', ['make', 'check'])
720 cmdlist
.add_command('install', ['make', 'install'])
721 cmdlist
.cleanup_dir()
722 cmdlist
.pop_subdesc()
724 def build_compilers(self
, configs
):
725 """Build the compilers."""
727 self
.remove_dirs(os
.path
.join(self
.builddir
, 'compilers'))
728 self
.remove_dirs(os
.path
.join(self
.installdir
, 'compilers'))
729 self
.remove_dirs(os
.path
.join(self
.logsdir
, 'compilers'))
730 configs
= sorted(self
.configs
.keys())
732 self
.configs
[c
].build()
734 def build_glibcs(self
, configs
):
735 """Build the glibcs."""
737 self
.remove_dirs(os
.path
.join(self
.builddir
, 'glibcs'))
738 self
.remove_dirs(os
.path
.join(self
.installdir
, 'glibcs'))
739 self
.remove_dirs(os
.path
.join(self
.logsdir
, 'glibcs'))
740 configs
= sorted(self
.glibc_configs
.keys())
742 self
.glibc_configs
[c
].build()
744 def update_syscalls(self
, configs
):
745 """Update the glibc syscall lists."""
747 self
.remove_dirs(os
.path
.join(self
.builddir
, 'update-syscalls'))
748 self
.remove_dirs(os
.path
.join(self
.logsdir
, 'update-syscalls'))
749 configs
= sorted(self
.glibc_configs
.keys())
751 self
.glibc_configs
[c
].update_syscalls()
753 def load_versions_json(self
):
754 """Load information about source directory versions."""
755 if not os
.access(self
.versions_json
, os
.F_OK
):
758 with
open(self
.versions_json
, 'r') as f
:
759 self
.versions
= json
.load(f
)
761 def store_json(self
, data
, filename
):
762 """Store information in a JSON file."""
763 filename_tmp
= filename
+ '.tmp'
764 with
open(filename_tmp
, 'w') as f
:
765 json
.dump(data
, f
, indent
=2, sort_keys
=True)
766 os
.rename(filename_tmp
, filename
)
768 def store_versions_json(self
):
769 """Store information about source directory versions."""
770 self
.store_json(self
.versions
, self
.versions_json
)
772 def set_component_version(self
, component
, version
, explicit
, revision
):
773 """Set the version information for a component."""
774 self
.versions
[component
] = {'version': version
,
775 'explicit': explicit
,
776 'revision': revision
}
777 self
.store_versions_json()
779 def checkout(self
, versions
):
780 """Check out the desired component versions."""
781 default_versions
= {'binutils': 'vcs-2.37',
783 'glibc': 'vcs-mainline',
788 'mig': 'vcs-mainline',
789 'gnumach': 'vcs-mainline',
790 'hurd': 'vcs-mainline'}
792 explicit_versions
= {}
795 for k
in default_versions
.keys():
799 if k
in use_versions
:
800 print('error: multiple versions for %s' % k
)
803 explicit_versions
[k
] = True
807 print('error: unknown component in %s' % v
)
809 for k
in default_versions
.keys():
810 if k
not in use_versions
:
811 if k
in self
.versions
and self
.versions
[k
]['explicit']:
812 use_versions
[k
] = self
.versions
[k
]['version']
813 explicit_versions
[k
] = True
815 use_versions
[k
] = default_versions
[k
]
816 explicit_versions
[k
] = False
817 os
.makedirs(self
.srcdir
, exist_ok
=True)
818 for k
in sorted(default_versions
.keys()):
819 update
= os
.access(self
.component_srcdir(k
), os
.F_OK
)
822 k
in self
.versions
and
823 v
!= self
.versions
[k
]['version']):
824 if not self
.replace_sources
:
825 print('error: version of %s has changed from %s to %s, '
826 'use --replace-sources to check out again' %
827 (k
, self
.versions
[k
]['version'], v
))
829 shutil
.rmtree(self
.component_srcdir(k
))
831 if v
.startswith('vcs-'):
832 revision
= self
.checkout_vcs(k
, v
[4:], update
)
834 self
.checkout_tar(k
, v
, update
)
836 self
.set_component_version(k
, v
, explicit_versions
[k
], revision
)
837 if self
.get_script_text() != self
.script_text
:
838 # Rerun the checkout process in case the updated script
839 # uses different default versions or new components.
842 def checkout_vcs(self
, component
, version
, update
):
843 """Check out the given version of the given component from version
844 control. Return a revision identifier."""
845 if component
== 'binutils':
846 git_url
= 'git://sourceware.org/git/binutils-gdb.git'
847 if version
== 'mainline':
848 git_branch
= 'master'
850 trans
= str.maketrans({'.': '_'})
851 git_branch
= 'binutils-%s-branch' % version
.translate(trans
)
852 return self
.git_checkout(component
, git_url
, git_branch
, update
)
853 elif component
== 'gcc':
854 if version
== 'mainline':
857 branch
= 'releases/gcc-%s' % version
858 return self
.gcc_checkout(branch
, update
)
859 elif component
== 'glibc':
860 git_url
= 'git://sourceware.org/git/glibc.git'
861 if version
== 'mainline':
862 git_branch
= 'master'
864 git_branch
= 'release/%s/master' % version
865 r
= self
.git_checkout(component
, git_url
, git_branch
, update
)
866 self
.fix_glibc_timestamps()
868 elif component
== 'gnumach':
869 git_url
= 'git://git.savannah.gnu.org/hurd/gnumach.git'
870 git_branch
= 'master'
871 r
= self
.git_checkout(component
, git_url
, git_branch
, update
)
872 subprocess
.run(['autoreconf', '-i'],
873 cwd
=self
.component_srcdir(component
), check
=True)
875 elif component
== 'mig':
876 git_url
= 'git://git.savannah.gnu.org/hurd/mig.git'
877 git_branch
= 'master'
878 r
= self
.git_checkout(component
, git_url
, git_branch
, update
)
879 subprocess
.run(['autoreconf', '-i'],
880 cwd
=self
.component_srcdir(component
), check
=True)
882 elif component
== 'hurd':
883 git_url
= 'git://git.savannah.gnu.org/hurd/hurd.git'
884 git_branch
= 'master'
885 r
= self
.git_checkout(component
, git_url
, git_branch
, update
)
886 subprocess
.run(['autoconf'],
887 cwd
=self
.component_srcdir(component
), check
=True)
890 print('error: component %s coming from VCS' % component
)
893 def git_checkout(self
, component
, git_url
, git_branch
, update
):
894 """Check out a component from git. Return a commit identifier."""
896 subprocess
.run(['git', 'remote', 'prune', 'origin'],
897 cwd
=self
.component_srcdir(component
), check
=True)
898 if self
.replace_sources
:
899 subprocess
.run(['git', 'clean', '-dxfq'],
900 cwd
=self
.component_srcdir(component
), check
=True)
901 subprocess
.run(['git', 'pull', '-q'],
902 cwd
=self
.component_srcdir(component
), check
=True)
905 depth_arg
= ('--depth', '1')
908 subprocess
.run(['git', 'clone', '-q', '-b', git_branch
,
910 self
.component_srcdir(component
)], check
=True)
911 r
= subprocess
.run(['git', 'rev-parse', 'HEAD'],
912 cwd
=self
.component_srcdir(component
),
913 stdout
=subprocess
.PIPE
,
914 check
=True, universal_newlines
=True).stdout
917 def fix_glibc_timestamps(self
):
918 """Fix timestamps in a glibc checkout."""
919 # Ensure that builds do not try to regenerate generated files
920 # in the source tree.
921 srcdir
= self
.component_srcdir('glibc')
922 # These files have Makefile dependencies to regenerate them in
923 # the source tree that may be active during a normal build.
924 # Some other files have such dependencies but do not need to
925 # be touched because nothing in a build depends on the files
927 for f
in ('sysdeps/mach/hurd/bits/errno.h',):
928 to_touch
= os
.path
.join(srcdir
, f
)
929 subprocess
.run(['touch', '-c', to_touch
], check
=True)
930 for dirpath
, dirnames
, filenames
in os
.walk(srcdir
):
932 if (f
== 'configure' or
933 f
== 'preconfigure' or
934 f
.endswith('-kw.h')):
935 to_touch
= os
.path
.join(dirpath
, f
)
936 subprocess
.run(['touch', to_touch
], check
=True)
938 def gcc_checkout(self
, branch
, update
):
939 """Check out GCC from git. Return the commit identifier."""
940 if os
.access(os
.path
.join(self
.component_srcdir('gcc'), '.svn'),
942 if not self
.replace_sources
:
943 print('error: GCC has moved from SVN to git, use '
944 '--replace-sources to check out again')
946 shutil
.rmtree(self
.component_srcdir('gcc'))
949 self
.git_checkout('gcc', 'git://gcc.gnu.org/git/gcc.git',
951 subprocess
.run(['contrib/gcc_update', '--silent'],
952 cwd
=self
.component_srcdir('gcc'), check
=True)
953 r
= subprocess
.run(['git', 'rev-parse', 'HEAD'],
954 cwd
=self
.component_srcdir('gcc'),
955 stdout
=subprocess
.PIPE
,
956 check
=True, universal_newlines
=True).stdout
959 def checkout_tar(self
, component
, version
, update
):
960 """Check out the given version of the given component from a
964 url_map
= {'binutils': 'https://ftp.gnu.org/gnu/binutils/binutils-%(version)s.tar.bz2',
965 'gcc': 'https://ftp.gnu.org/gnu/gcc/gcc-%(version)s/gcc-%(version)s.tar.gz',
966 'gmp': 'https://ftp.gnu.org/gnu/gmp/gmp-%(version)s.tar.xz',
967 'linux': 'https://www.kernel.org/pub/linux/kernel/v%(major)s.x/linux-%(version)s.tar.xz',
968 'mpc': 'https://ftp.gnu.org/gnu/mpc/mpc-%(version)s.tar.gz',
969 'mpfr': 'https://ftp.gnu.org/gnu/mpfr/mpfr-%(version)s.tar.xz',
970 'mig': 'https://ftp.gnu.org/gnu/mig/mig-%(version)s.tar.bz2',
971 'gnumach': 'https://ftp.gnu.org/gnu/gnumach/gnumach-%(version)s.tar.bz2',
972 'hurd': 'https://ftp.gnu.org/gnu/hurd/hurd-%(version)s.tar.bz2'}
973 if component
not in url_map
:
974 print('error: component %s coming from tarball' % component
)
976 version_major
= version
.split('.')[0]
977 url
= url_map
[component
] % {'version': version
, 'major': version_major
}
978 filename
= os
.path
.join(self
.srcdir
, url
.split('/')[-1])
979 response
= urllib
.request
.urlopen(url
)
980 data
= response
.read()
981 with
open(filename
, 'wb') as f
:
983 subprocess
.run(['tar', '-C', self
.srcdir
, '-x', '-f', filename
],
985 os
.rename(os
.path
.join(self
.srcdir
, '%s-%s' % (component
, version
)),
986 self
.component_srcdir(component
))
989 def load_build_state_json(self
):
990 """Load information about the state of previous builds."""
991 if os
.access(self
.build_state_json
, os
.F_OK
):
992 with
open(self
.build_state_json
, 'r') as f
:
993 self
.build_state
= json
.load(f
)
995 self
.build_state
= {}
996 for k
in ('host-libraries', 'compilers', 'glibcs', 'update-syscalls'):
997 if k
not in self
.build_state
:
998 self
.build_state
[k
] = {}
999 if 'build-time' not in self
.build_state
[k
]:
1000 self
.build_state
[k
]['build-time'] = ''
1001 if 'build-versions' not in self
.build_state
[k
]:
1002 self
.build_state
[k
]['build-versions'] = {}
1003 if 'build-results' not in self
.build_state
[k
]:
1004 self
.build_state
[k
]['build-results'] = {}
1005 if 'result-changes' not in self
.build_state
[k
]:
1006 self
.build_state
[k
]['result-changes'] = {}
1007 if 'ever-passed' not in self
.build_state
[k
]:
1008 self
.build_state
[k
]['ever-passed'] = []
1010 def store_build_state_json(self
):
1011 """Store information about the state of previous builds."""
1012 self
.store_json(self
.build_state
, self
.build_state_json
)
1014 def clear_last_build_state(self
, action
):
1015 """Clear information about the state of part of the build."""
1016 # We clear the last build time and versions when starting a
1017 # new build. The results of the last build are kept around,
1018 # as comparison is still meaningful if this build is aborted
1019 # and a new one started.
1020 self
.build_state
[action
]['build-time'] = ''
1021 self
.build_state
[action
]['build-versions'] = {}
1022 self
.store_build_state_json()
1024 def update_build_state(self
, action
, build_time
, build_versions
):
1025 """Update the build state after a build."""
1026 build_time
= build_time
.replace(microsecond
=0)
1027 self
.build_state
[action
]['build-time'] = str(build_time
)
1028 self
.build_state
[action
]['build-versions'] = build_versions
1030 for log
in self
.status_log_list
:
1031 with
open(log
, 'r') as f
:
1033 log_text
= log_text
.rstrip()
1034 m
= re
.fullmatch('([A-Z]+): (.*)', log_text
)
1036 test_name
= m
.group(2)
1037 assert test_name
not in build_results
1038 build_results
[test_name
] = result
1039 old_build_results
= self
.build_state
[action
]['build-results']
1040 self
.build_state
[action
]['build-results'] = build_results
1042 all_tests
= set(old_build_results
.keys()) |
set(build_results
.keys())
1044 if t
in old_build_results
:
1045 old_res
= old_build_results
[t
]
1047 old_res
= '(New test)'
1048 if t
in build_results
:
1049 new_res
= build_results
[t
]
1051 new_res
= '(Test removed)'
1052 if old_res
!= new_res
:
1053 result_changes
[t
] = '%s -> %s' % (old_res
, new_res
)
1054 self
.build_state
[action
]['result-changes'] = result_changes
1055 old_ever_passed
= {t
for t
in self
.build_state
[action
]['ever-passed']
1056 if t
in build_results
}
1057 new_passes
= {t
for t
in build_results
if build_results
[t
] == 'PASS'}
1058 self
.build_state
[action
]['ever-passed'] = sorted(old_ever_passed |
1060 self
.store_build_state_json()
1062 def load_bot_config_json(self
):
1063 """Load bot configuration."""
1064 with
open(self
.bot_config_json
, 'r') as f
:
1065 self
.bot_config
= json
.load(f
)
1067 def part_build_old(self
, action
, delay
):
1068 """Return whether the last build for a given action was at least a
1069 given number of seconds ago, or does not have a time recorded."""
1070 old_time_str
= self
.build_state
[action
]['build-time']
1071 if not old_time_str
:
1073 old_time
= datetime
.datetime
.strptime(old_time_str
,
1074 '%Y-%m-%d %H:%M:%S')
1075 new_time
= datetime
.datetime
.utcnow()
1076 delta
= new_time
- old_time
1077 return delta
.total_seconds() >= delay
1079 def bot_cycle(self
):
1080 """Run a single round of checkout and builds."""
1081 print('Bot cycle starting %s.' % str(datetime
.datetime
.utcnow()))
1082 self
.load_bot_config_json()
1083 actions
= ('host-libraries', 'compilers', 'glibcs')
1084 self
.bot_run_self(['--replace-sources'], 'checkout')
1085 self
.load_versions_json()
1086 if self
.get_script_text() != self
.script_text
:
1087 print('Script changed, re-execing.')
1088 # On script change, all parts of the build should be rerun.
1090 self
.clear_last_build_state(a
)
1092 check_components
= {'host-libraries': ('gmp', 'mpfr', 'mpc'),
1093 'compilers': ('binutils', 'gcc', 'glibc', 'linux',
1094 'mig', 'gnumach', 'hurd'),
1095 'glibcs': ('glibc',)}
1098 build_vers
= self
.build_state
[a
]['build-versions']
1099 must_build
[a
] = False
1100 if not self
.build_state
[a
]['build-time']:
1101 must_build
[a
] = True
1104 for c
in check_components
[a
]:
1106 old_vers
[c
] = build_vers
[c
]
1107 new_vers
[c
] = {'version': self
.versions
[c
]['version'],
1108 'revision': self
.versions
[c
]['revision']}
1109 if new_vers
== old_vers
:
1110 print('Versions for %s unchanged.' % a
)
1112 print('Versions changed or rebuild forced for %s.' % a
)
1113 if a
== 'compilers' and not self
.part_build_old(
1114 a
, self
.bot_config
['compilers-rebuild-delay']):
1115 print('Not requiring rebuild of compilers this soon.')
1117 must_build
[a
] = True
1118 if must_build
['host-libraries']:
1119 must_build
['compilers'] = True
1120 if must_build
['compilers']:
1121 must_build
['glibcs'] = True
1124 print('Must rebuild %s.' % a
)
1125 self
.clear_last_build_state(a
)
1127 print('No need to rebuild %s.' % a
)
1128 if os
.access(self
.logsdir
, os
.F_OK
):
1129 shutil
.rmtree(self
.logsdir_old
, ignore_errors
=True)
1130 shutil
.copytree(self
.logsdir
, self
.logsdir_old
)
1133 build_time
= datetime
.datetime
.utcnow()
1134 print('Rebuilding %s at %s.' % (a
, str(build_time
)))
1135 self
.bot_run_self([], a
)
1136 self
.load_build_state_json()
1137 self
.bot_build_mail(a
, build_time
)
1138 print('Bot cycle done at %s.' % str(datetime
.datetime
.utcnow()))
1140 def bot_build_mail(self
, action
, build_time
):
1141 """Send email with the results of a build."""
1142 if not ('email-from' in self
.bot_config
and
1143 'email-server' in self
.bot_config
and
1144 'email-subject' in self
.bot_config
and
1145 'email-to' in self
.bot_config
):
1146 if not self
.email_warning
:
1147 print("Email not configured, not sending.")
1148 self
.email_warning
= True
1151 build_time
= build_time
.replace(microsecond
=0)
1152 subject
= (self
.bot_config
['email-subject'] %
1154 'build-time': str(build_time
)})
1155 results
= self
.build_state
[action
]['build-results']
1156 changes
= self
.build_state
[action
]['result-changes']
1157 ever_passed
= set(self
.build_state
[action
]['ever-passed'])
1158 versions
= self
.build_state
[action
]['build-versions']
1159 new_regressions
= {k
for k
in changes
if changes
[k
] == 'PASS -> FAIL'}
1160 all_regressions
= {k
for k
in ever_passed
if results
[k
] == 'FAIL'}
1161 all_fails
= {k
for k
in results
if results
[k
] == 'FAIL'}
1163 new_reg_list
= sorted(['FAIL: %s' % k
for k
in new_regressions
])
1164 new_reg_text
= ('New regressions:\n\n%s\n\n' %
1165 '\n'.join(new_reg_list
))
1169 all_reg_list
= sorted(['FAIL: %s' % k
for k
in all_regressions
])
1170 all_reg_text
= ('All regressions:\n\n%s\n\n' %
1171 '\n'.join(all_reg_list
))
1175 all_fail_list
= sorted(['FAIL: %s' % k
for k
in all_fails
])
1176 all_fail_text
= ('All failures:\n\n%s\n\n' %
1177 '\n'.join(all_fail_list
))
1181 changes_list
= sorted(changes
.keys())
1182 changes_list
= ['%s: %s' % (changes
[k
], k
) for k
in changes_list
]
1183 changes_text
= ('All changed results:\n\n%s\n\n' %
1184 '\n'.join(changes_list
))
1187 results_text
= (new_reg_text
+ all_reg_text
+ all_fail_text
+
1189 if not results_text
:
1190 results_text
= 'Clean build with unchanged results.\n\n'
1191 versions_list
= sorted(versions
.keys())
1192 versions_list
= ['%s: %s (%s)' % (k
, versions
[k
]['version'],
1193 versions
[k
]['revision'])
1194 for k
in versions_list
]
1195 versions_text
= ('Component versions for this build:\n\n%s\n' %
1196 '\n'.join(versions_list
))
1197 body_text
= results_text
+ versions_text
1198 msg
= email
.mime
.text
.MIMEText(body_text
)
1199 msg
['Subject'] = subject
1200 msg
['From'] = self
.bot_config
['email-from']
1201 msg
['To'] = self
.bot_config
['email-to']
1202 msg
['Message-ID'] = email
.utils
.make_msgid()
1203 msg
['Date'] = email
.utils
.format_datetime(datetime
.datetime
.utcnow())
1204 with smtplib
.SMTP(self
.bot_config
['email-server']) as s
:
1207 def bot_run_self(self
, opts
, action
, check
=True):
1208 """Run a copy of this script with given options."""
1209 cmd
= [sys
.executable
, sys
.argv
[0], '--keep=none',
1210 '-j%d' % self
.parallelism
]
1212 cmd
.append('--full-gcc')
1214 cmd
.extend([self
.topdir
, action
])
1216 subprocess
.run(cmd
, check
=check
)
1219 """Run repeated rounds of checkout and builds."""
1221 self
.load_bot_config_json()
1222 if not self
.bot_config
['run']:
1223 print('Bot exiting by request.')
1225 self
.bot_run_self([], 'bot-cycle', check
=False)
1226 self
.load_bot_config_json()
1227 if not self
.bot_config
['run']:
1228 print('Bot exiting by request.')
1230 time
.sleep(self
.bot_config
['delay'])
1231 if self
.get_script_text() != self
.script_text
:
1232 print('Script changed, bot re-execing.')
1235 class LinuxHeadersPolicyForBuild(object):
1236 """Names and directories for installing Linux headers. Build variant."""
1238 def __init__(self
, config
):
1239 self
.arch
= config
.arch
1240 self
.srcdir
= config
.ctx
.component_srcdir('linux')
1241 self
.builddir
= config
.component_builddir('linux')
1242 self
.headers_dir
= os
.path
.join(config
.sysroot
, 'usr')
1244 class LinuxHeadersPolicyForUpdateSyscalls(object):
1245 """Names and directories for Linux headers. update-syscalls variant."""
1247 def __init__(self
, glibc
, headers_dir
):
1248 self
.arch
= glibc
.compiler
.arch
1249 self
.srcdir
= glibc
.compiler
.ctx
.component_srcdir('linux')
1250 self
.builddir
= glibc
.ctx
.component_builddir(
1251 'update-syscalls', glibc
.name
, 'build-linux')
1252 self
.headers_dir
= headers_dir
1254 def install_linux_headers(policy
, cmdlist
):
1255 """Install Linux kernel headers."""
1256 arch_map
= {'aarch64': 'arm64',
1268 'microblaze': 'microblaze',
1271 'powerpc': 'powerpc',
1280 if policy
.arch
.startswith(k
):
1281 linux_arch
= arch_map
[k
]
1283 assert linux_arch
is not None
1284 cmdlist
.push_subdesc('linux')
1285 cmdlist
.create_use_dir(policy
.builddir
)
1286 cmdlist
.add_command('install-headers',
1287 ['make', '-C', policy
.srcdir
, 'O=%s' % policy
.builddir
,
1288 'ARCH=%s' % linux_arch
,
1289 'INSTALL_HDR_PATH=%s' % policy
.headers_dir
,
1291 cmdlist
.cleanup_dir()
1292 cmdlist
.pop_subdesc()
1294 class Config(object):
1295 """A configuration for building a compiler and associated libraries."""
1297 def __init__(self
, ctx
, arch
, os_name
, variant
=None, gcc_cfg
=None,
1298 first_gcc_cfg
=None, binutils_cfg
=None, glibcs
=None,
1300 """Initialize a Config object."""
1304 self
.variant
= variant
1306 self
.name
= '%s-%s' % (arch
, os_name
)
1308 self
.name
= '%s-%s-%s' % (arch
, os_name
, variant
)
1309 self
.triplet
= '%s-glibc-%s' % (arch
, os_name
)
1313 self
.gcc_cfg
= gcc_cfg
1314 if first_gcc_cfg
is None:
1315 self
.first_gcc_cfg
= []
1317 self
.first_gcc_cfg
= first_gcc_cfg
1318 if binutils_cfg
is None:
1319 self
.binutils_cfg
= []
1321 self
.binutils_cfg
= binutils_cfg
1323 glibcs
= [{'variant': variant
}]
1324 if extra_glibcs
is None:
1326 glibcs
= [Glibc(self
, **g
) for g
in glibcs
]
1327 extra_glibcs
= [Glibc(self
, **g
) for g
in extra_glibcs
]
1328 self
.all_glibcs
= glibcs
+ extra_glibcs
1329 self
.compiler_glibcs
= glibcs
1330 self
.installdir
= ctx
.compiler_installdir(self
.name
)
1331 self
.bindir
= ctx
.compiler_bindir(self
.name
)
1332 self
.sysroot
= ctx
.compiler_sysroot(self
.name
)
1333 self
.builddir
= os
.path
.join(ctx
.builddir
, 'compilers', self
.name
)
1334 self
.logsdir
= os
.path
.join(ctx
.logsdir
, 'compilers', self
.name
)
1336 def component_builddir(self
, component
):
1337 """Return the directory to use for a (non-glibc) build."""
1338 return self
.ctx
.component_builddir('compilers', self
.name
, component
)
1341 """Generate commands to build this compiler."""
1342 self
.ctx
.remove_recreate_dirs(self
.installdir
, self
.builddir
,
1344 cmdlist
= CommandList('compilers-%s' % self
.name
, self
.ctx
.keep
)
1345 cmdlist
.add_command('check-host-libraries',
1347 os
.path
.join(self
.ctx
.host_libraries_installdir
,
1349 cmdlist
.use_path(self
.bindir
)
1350 self
.build_cross_tool(cmdlist
, 'binutils', 'binutils',
1352 '--disable-gdbserver',
1353 '--disable-libdecnumber',
1354 '--disable-readline',
1355 '--disable-sim'] + self
.binutils_cfg
)
1356 if self
.os
.startswith('linux'):
1357 install_linux_headers(LinuxHeadersPolicyForBuild(self
), cmdlist
)
1358 self
.build_gcc(cmdlist
, True)
1359 if self
.os
== 'gnu':
1360 self
.install_gnumach_headers(cmdlist
)
1361 self
.build_cross_tool(cmdlist
, 'mig', 'mig')
1362 self
.install_hurd_headers(cmdlist
)
1363 for g
in self
.compiler_glibcs
:
1364 cmdlist
.push_subdesc('glibc')
1365 cmdlist
.push_subdesc(g
.name
)
1366 g
.build_glibc(cmdlist
, GlibcPolicyForCompiler(g
))
1367 cmdlist
.pop_subdesc()
1368 cmdlist
.pop_subdesc()
1369 self
.build_gcc(cmdlist
, False)
1370 cmdlist
.add_command('done', ['touch',
1371 os
.path
.join(self
.installdir
, 'ok')])
1372 self
.ctx
.add_makefile_cmdlist('compilers-%s' % self
.name
, cmdlist
,
1375 def build_cross_tool(self
, cmdlist
, tool_src
, tool_build
, extra_opts
=None):
1376 """Build one cross tool."""
1377 srcdir
= self
.ctx
.component_srcdir(tool_src
)
1378 builddir
= self
.component_builddir(tool_build
)
1379 cmdlist
.push_subdesc(tool_build
)
1380 cmdlist
.create_use_dir(builddir
)
1381 cfg_cmd
= [os
.path
.join(srcdir
, 'configure'),
1382 '--prefix=%s' % self
.installdir
,
1383 '--build=%s' % self
.ctx
.build_triplet
,
1384 '--host=%s' % self
.ctx
.build_triplet
,
1385 '--target=%s' % self
.triplet
,
1386 '--with-sysroot=%s' % self
.sysroot
]
1388 cfg_cmd
.extend(extra_opts
)
1389 cmdlist
.add_command('configure', cfg_cmd
)
1390 cmdlist
.add_command('build', ['make'])
1391 # Parallel "make install" for GCC has race conditions that can
1392 # cause it to fail; see
1393 # <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=42980>. Such
1394 # problems are not known for binutils, but doing the
1395 # installation in parallel within a particular toolchain build
1396 # (as opposed to installation of one toolchain from
1397 # build-many-glibcs.py running in parallel to the installation
1398 # of other toolchains being built) is not known to be
1399 # significantly beneficial, so it is simplest just to disable
1400 # parallel install for cross tools here.
1401 cmdlist
.add_command('install', ['make', '-j1', 'install'])
1402 cmdlist
.cleanup_dir()
1403 cmdlist
.pop_subdesc()
1405 def install_gnumach_headers(self
, cmdlist
):
1406 """Install GNU Mach headers."""
1407 srcdir
= self
.ctx
.component_srcdir('gnumach')
1408 builddir
= self
.component_builddir('gnumach')
1409 cmdlist
.push_subdesc('gnumach')
1410 cmdlist
.create_use_dir(builddir
)
1411 cmdlist
.add_command('configure',
1412 [os
.path
.join(srcdir
, 'configure'),
1413 '--build=%s' % self
.ctx
.build_triplet
,
1414 '--host=%s' % self
.triplet
,
1416 'CC=%s-gcc -nostdlib' % self
.triplet
])
1417 cmdlist
.add_command('install', ['make', 'DESTDIR=%s' % self
.sysroot
,
1419 cmdlist
.cleanup_dir()
1420 cmdlist
.pop_subdesc()
1422 def install_hurd_headers(self
, cmdlist
):
1423 """Install Hurd headers."""
1424 srcdir
= self
.ctx
.component_srcdir('hurd')
1425 builddir
= self
.component_builddir('hurd')
1426 cmdlist
.push_subdesc('hurd')
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 '--disable-profile', '--without-parted',
1434 'CC=%s-gcc -nostdlib' % self
.triplet
])
1435 cmdlist
.add_command('install', ['make', 'prefix=%s' % self
.sysroot
,
1436 'no_deps=t', 'install-headers'])
1437 cmdlist
.cleanup_dir()
1438 cmdlist
.pop_subdesc()
1440 def build_gcc(self
, cmdlist
, bootstrap
):
1442 # libssp is of little relevance with glibc's own stack
1443 # checking support. libcilkrts does not support GNU/Hurd (and
1444 # has been removed in GCC 8, so --disable-libcilkrts can be
1445 # removed once glibc no longer supports building with older
1447 cfg_opts
= list(self
.gcc_cfg
)
1448 cfg_opts
+= ['--disable-libssp', '--disable-libcilkrts']
1449 host_libs
= self
.ctx
.host_libraries_installdir
1450 cfg_opts
+= ['--with-gmp=%s' % host_libs
,
1451 '--with-mpfr=%s' % host_libs
,
1452 '--with-mpc=%s' % host_libs
]
1454 tool_build
= 'gcc-first'
1455 # Building a static-only, C-only compiler that is
1456 # sufficient to build glibc. Various libraries and
1457 # features that may require libc headers must be disabled.
1458 # When configuring with a sysroot, --with-newlib is
1459 # required to define inhibit_libc (to stop some parts of
1460 # libgcc including libc headers); --without-headers is not
1462 cfg_opts
+= ['--enable-languages=c', '--disable-shared',
1463 '--disable-threads',
1464 '--disable-libatomic',
1465 '--disable-decimal-float',
1467 '--disable-libgomp',
1470 '--disable-libquadmath',
1471 '--disable-libsanitizer',
1472 '--without-headers', '--with-newlib',
1473 '--with-glibc-version=%s' % self
.ctx
.glibc_version
1475 cfg_opts
+= self
.first_gcc_cfg
1478 # libsanitizer commonly breaks because of glibc header
1479 # changes, or on unusual targets. C++ pre-compiled
1480 # headers are not used during the glibc build and are
1481 # expensive to create.
1482 if not self
.ctx
.full_gcc
:
1483 cfg_opts
+= ['--disable-libsanitizer',
1484 '--disable-libstdcxx-pch']
1485 langs
= 'all' if self
.ctx
.full_gcc
else 'c,c++'
1486 cfg_opts
+= ['--enable-languages=%s' % langs
,
1487 '--enable-shared', '--enable-threads']
1488 self
.build_cross_tool(cmdlist
, 'gcc', tool_build
, cfg_opts
)
1490 class GlibcPolicyDefault(object):
1491 """Build policy for glibc: common defaults."""
1493 def __init__(self
, glibc
):
1494 self
.srcdir
= glibc
.ctx
.component_srcdir('glibc')
1495 self
.use_usr
= glibc
.os
!= 'gnu'
1496 self
.prefix
= '/usr' if self
.use_usr
else ''
1497 self
.configure_args
= [
1498 '--prefix=%s' % self
.prefix
,
1500 '--build=%s' % glibc
.ctx
.build_triplet
,
1501 '--host=%s' % glibc
.triplet
,
1502 'CC=%s' % glibc
.tool_name('gcc'),
1503 'CXX=%s' % glibc
.tool_name('g++'),
1504 'AR=%s' % glibc
.tool_name('ar'),
1505 'AS=%s' % glibc
.tool_name('as'),
1506 'LD=%s' % glibc
.tool_name('ld'),
1507 'NM=%s' % glibc
.tool_name('nm'),
1508 'OBJCOPY=%s' % glibc
.tool_name('objcopy'),
1509 'OBJDUMP=%s' % glibc
.tool_name('objdump'),
1510 'RANLIB=%s' % glibc
.tool_name('ranlib'),
1511 'READELF=%s' % glibc
.tool_name('readelf'),
1512 'STRIP=%s' % glibc
.tool_name('strip'),
1514 if glibc
.os
== 'gnu':
1515 self
.configure_args
.append('MIG=%s' % glibc
.tool_name('mig'))
1517 self
.configure_args
.append('CFLAGS=%s' % glibc
.cflags
)
1518 self
.configure_args
.append('CXXFLAGS=%s' % glibc
.cflags
)
1519 self
.configure_args
+= glibc
.cfg
1521 def configure(self
, cmdlist
):
1522 """Invoked to add the configure command to the command list."""
1523 cmdlist
.add_command('configure',
1524 [os
.path
.join(self
.srcdir
, 'configure'),
1525 *self
.configure_args
])
1527 def extra_commands(self
, cmdlist
):
1528 """Invoked to inject additional commands (make check) after build."""
1531 class GlibcPolicyForCompiler(GlibcPolicyDefault
):
1532 """Build policy for glibc during the compilers stage."""
1534 def __init__(self
, glibc
):
1535 super().__init
__(glibc
)
1536 self
.builddir
= glibc
.ctx
.component_builddir(
1537 'compilers', glibc
.compiler
.name
, 'glibc', glibc
.name
)
1538 self
.installdir
= glibc
.compiler
.sysroot
1540 class GlibcPolicyForBuild(GlibcPolicyDefault
):
1541 """Build policy for glibc during the glibcs stage."""
1543 def __init__(self
, glibc
):
1544 super().__init
__(glibc
)
1545 self
.builddir
= glibc
.ctx
.component_builddir(
1546 'glibcs', glibc
.name
, 'glibc')
1547 self
.installdir
= glibc
.ctx
.glibc_installdir(glibc
.name
)
1549 self
.strip
= glibc
.tool_name('strip')
1552 self
.save_logs
= glibc
.ctx
.save_logs
1554 def extra_commands(self
, cmdlist
):
1556 # Avoid picking up libc.so and libpthread.so, which are
1557 # linker scripts stored in /lib on Hurd. libc and
1558 # libpthread are still stripped via their libc-X.YY.so
1559 # implementation files.
1560 find_command
= (('find %s/lib* -name "*.so"'
1561 + r
' \! -name libc.so \! -name libpthread.so')
1563 cmdlist
.add_command('strip', ['sh', '-c', ('%s $(%s)' %
1564 (self
.strip
, find_command
))])
1565 cmdlist
.add_command('check', ['make', 'check'])
1566 cmdlist
.add_command('save-logs', [self
.save_logs
], always_run
=True)
1568 class GlibcPolicyForUpdateSyscalls(GlibcPolicyDefault
):
1569 """Build policy for glibc during update-syscalls."""
1571 def __init__(self
, glibc
):
1572 super().__init
__(glibc
)
1573 self
.builddir
= glibc
.ctx
.component_builddir(
1574 'update-syscalls', glibc
.name
, 'glibc')
1575 self
.linuxdir
= glibc
.ctx
.component_builddir(
1576 'update-syscalls', glibc
.name
, 'linux')
1577 self
.linux_policy
= LinuxHeadersPolicyForUpdateSyscalls(
1578 glibc
, self
.linuxdir
)
1579 self
.configure_args
.insert(
1580 0, '--with-headers=%s' % os
.path
.join(self
.linuxdir
, 'include'))
1581 # self.installdir not set because installation is not supported
1583 class Glibc(object):
1584 """A configuration for building glibc."""
1586 def __init__(self
, compiler
, arch
=None, os_name
=None, variant
=None,
1587 cfg
=None, ccopts
=None, cflags
=None):
1588 """Initialize a Glibc object."""
1589 self
.ctx
= compiler
.ctx
1590 self
.compiler
= compiler
1592 self
.arch
= compiler
.arch
1596 self
.os
= compiler
.os
1599 self
.variant
= variant
1601 self
.name
= '%s-%s' % (self
.arch
, self
.os
)
1603 self
.name
= '%s-%s-%s' % (self
.arch
, self
.os
, variant
)
1604 self
.triplet
= '%s-glibc-%s' % (self
.arch
, self
.os
)
1609 # ccopts contain ABI options and are passed to configure as CC / CXX.
1610 self
.ccopts
= ccopts
1611 # cflags contain non-ABI options like -g or -O and are passed to
1612 # configure as CFLAGS / CXXFLAGS.
1613 self
.cflags
= cflags
1615 def tool_name(self
, tool
):
1616 """Return the name of a cross-compilation tool."""
1617 ctool
= '%s-%s' % (self
.compiler
.triplet
, tool
)
1618 if self
.ccopts
and (tool
== 'gcc' or tool
== 'g++'):
1619 ctool
= '%s %s' % (ctool
, self
.ccopts
)
1623 """Generate commands to build this glibc."""
1624 builddir
= self
.ctx
.component_builddir('glibcs', self
.name
, 'glibc')
1625 installdir
= self
.ctx
.glibc_installdir(self
.name
)
1626 logsdir
= os
.path
.join(self
.ctx
.logsdir
, 'glibcs', self
.name
)
1627 self
.ctx
.remove_recreate_dirs(installdir
, builddir
, logsdir
)
1628 cmdlist
= CommandList('glibcs-%s' % self
.name
, self
.ctx
.keep
)
1629 cmdlist
.add_command('check-compilers',
1631 os
.path
.join(self
.compiler
.installdir
, 'ok')])
1632 cmdlist
.use_path(self
.compiler
.bindir
)
1633 self
.build_glibc(cmdlist
, GlibcPolicyForBuild(self
))
1634 self
.ctx
.add_makefile_cmdlist('glibcs-%s' % self
.name
, cmdlist
,
1637 def build_glibc(self
, cmdlist
, policy
):
1638 """Generate commands to build this glibc, either as part of a compiler
1639 build or with the bootstrapped compiler (and in the latter case, run
1641 cmdlist
.create_use_dir(policy
.builddir
)
1642 policy
.configure(cmdlist
)
1643 cmdlist
.add_command('build', ['make'])
1644 cmdlist
.add_command('install', ['make', 'install',
1645 'install_root=%s' % policy
.installdir
])
1646 # GCC uses paths such as lib/../lib64, so make sure lib
1647 # directories always exist.
1648 mkdir_cmd
= ['mkdir', '-p',
1649 os
.path
.join(policy
.installdir
, 'lib')]
1651 mkdir_cmd
+= [os
.path
.join(policy
.installdir
, 'usr', 'lib')]
1652 cmdlist
.add_command('mkdir-lib', mkdir_cmd
)
1653 policy
.extra_commands(cmdlist
)
1654 cmdlist
.cleanup_dir()
1656 def update_syscalls(self
):
1657 if self
.os
== 'gnu':
1658 # Hurd does not have system call tables that need updating.
1661 policy
= GlibcPolicyForUpdateSyscalls(self
)
1662 logsdir
= os
.path
.join(self
.ctx
.logsdir
, 'update-syscalls', self
.name
)
1663 self
.ctx
.remove_recreate_dirs(policy
.builddir
, logsdir
)
1664 cmdlist
= CommandList('update-syscalls-%s' % self
.name
, self
.ctx
.keep
)
1665 cmdlist
.add_command('check-compilers',
1667 os
.path
.join(self
.compiler
.installdir
, 'ok')])
1668 cmdlist
.use_path(self
.compiler
.bindir
)
1670 install_linux_headers(policy
.linux_policy
, cmdlist
)
1672 cmdlist
.create_use_dir(policy
.builddir
)
1673 policy
.configure(cmdlist
)
1674 cmdlist
.add_command('build', ['make', 'update-syscall-lists'])
1675 cmdlist
.cleanup_dir()
1676 self
.ctx
.add_makefile_cmdlist('update-syscalls-%s' % self
.name
,
1679 class Command(object):
1680 """A command run in the build process."""
1682 def __init__(self
, desc
, num
, dir, path
, command
, always_run
=False):
1683 """Initialize a Command object."""
1687 trans
= str.maketrans({' ': '-'})
1688 self
.logbase
= '%03d-%s' % (num
, desc
.translate(trans
))
1689 self
.command
= command
1690 self
.always_run
= always_run
1693 def shell_make_quote_string(s
):
1694 """Given a string not containing a newline, quote it for use by the
1696 assert '\n' not in s
1697 if re
.fullmatch('[]+,./0-9@A-Z_a-z-]+', s
):
1699 strans
= str.maketrans({"'": "'\\''"})
1700 s
= "'%s'" % s
.translate(strans
)
1701 mtrans
= str.maketrans({'$': '$$'})
1702 return s
.translate(mtrans
)
1705 def shell_make_quote_list(l
, translate_make
):
1706 """Given a list of strings not containing newlines, quote them for use
1707 by the shell and make, returning a single string. If translate_make
1708 is true and the first string is 'make', change it to $(MAKE)."""
1709 l
= [Command
.shell_make_quote_string(s
) for s
in l
]
1710 if translate_make
and l
[0] == 'make':
1714 def shell_make_quote(self
):
1715 """Return this command quoted for the shell and make."""
1716 return self
.shell_make_quote_list(self
.command
, True)
1719 class CommandList(object):
1720 """A list of commands run in the build process."""
1722 def __init__(self
, desc
, keep
):
1723 """Initialize a CommandList object."""
1730 def desc_txt(self
, desc
):
1731 """Return the description to use for a command."""
1732 return '%s %s' % (' '.join(self
.desc
), desc
)
1734 def use_dir(self
, dir):
1735 """Set the default directory for subsequent commands."""
1738 def use_path(self
, path
):
1739 """Set a directory to be prepended to the PATH for subsequent
1743 def push_subdesc(self
, subdesc
):
1744 """Set the default subdescription for subsequent commands (e.g., the
1745 name of a component being built, within the series of commands
1747 self
.desc
.append(subdesc
)
1749 def pop_subdesc(self
):
1750 """Pop a subdescription from the list of descriptions."""
1753 def create_use_dir(self
, dir):
1754 """Remove and recreate a directory and use it for subsequent
1756 self
.add_command_dir('rm', None, ['rm', '-rf', dir])
1757 self
.add_command_dir('mkdir', None, ['mkdir', '-p', dir])
1760 def add_command_dir(self
, desc
, dir, command
, always_run
=False):
1761 """Add a command to run in a given directory."""
1762 cmd
= Command(self
.desc_txt(desc
), len(self
.cmdlist
), dir, self
.path
,
1763 command
, always_run
)
1764 self
.cmdlist
.append(cmd
)
1766 def add_command(self
, desc
, command
, always_run
=False):
1767 """Add a command to run in the default directory."""
1768 cmd
= Command(self
.desc_txt(desc
), len(self
.cmdlist
), self
.dir,
1769 self
.path
, command
, always_run
)
1770 self
.cmdlist
.append(cmd
)
1772 def cleanup_dir(self
, desc
='cleanup', dir=None):
1773 """Clean up a build directory. If no directory is specified, the
1774 default directory is cleaned up and ceases to be the default
1779 if self
.keep
!= 'all':
1780 self
.add_command_dir(desc
, None, ['rm', '-rf', dir],
1781 always_run
=(self
.keep
== 'none'))
1783 def makefile_commands(self
, wrapper
, logsdir
):
1784 """Return the sequence of commands in the form of text for a Makefile.
1785 The given wrapper script takes arguments: base of logs for
1786 previous command, or empty; base of logs for this command;
1787 description; directory; PATH addition; the command itself."""
1788 # prev_base is the base of the name for logs of the previous
1789 # command that is not always-run (that is, a build command,
1790 # whose failure should stop subsequent build commands from
1791 # being run, as opposed to a cleanup command, which is run
1792 # even if previous commands failed).
1795 for c
in self
.cmdlist
:
1796 ctxt
= c
.shell_make_quote()
1797 if prev_base
and not c
.always_run
:
1798 prev_log
= os
.path
.join(logsdir
, prev_base
)
1801 this_log
= os
.path
.join(logsdir
, c
.logbase
)
1802 if not c
.always_run
:
1803 prev_base
= c
.logbase
1812 prelims
= [wrapper
, prev_log
, this_log
, c
.desc
, dir, path
]
1813 prelim_txt
= Command
.shell_make_quote_list(prelims
, False)
1814 cmds
.append('\t@%s %s' % (prelim_txt
, ctxt
))
1815 return '\n'.join(cmds
)
1817 def status_logs(self
, logsdir
):
1818 """Return the list of log files with command status."""
1819 return [os
.path
.join(logsdir
, '%s-status.txt' % c
.logbase
)
1820 for c
in self
.cmdlist
]
1824 """Return an argument parser for this module."""
1825 parser
= argparse
.ArgumentParser(description
=__doc__
)
1826 parser
.add_argument('-j', dest
='parallelism',
1827 help='Run this number of jobs in parallel',
1828 type=int, default
=os
.cpu_count())
1829 parser
.add_argument('--keep', dest
='keep',
1830 help='Whether to keep all build directories, '
1831 'none or only those from failed builds',
1832 default
='none', choices
=('none', 'all', 'failed'))
1833 parser
.add_argument('--replace-sources', action
='store_true',
1834 help='Remove and replace source directories '
1835 'with the wrong version of a component')
1836 parser
.add_argument('--strip', action
='store_true',
1837 help='Strip installed glibc libraries')
1838 parser
.add_argument('--full-gcc', action
='store_true',
1839 help='Build GCC with all languages and libsanitizer')
1840 parser
.add_argument('--shallow', action
='store_true',
1841 help='Do not download Git history during checkout')
1842 parser
.add_argument('topdir',
1843 help='Toplevel working directory')
1844 parser
.add_argument('action',
1846 choices
=('checkout', 'bot-cycle', 'bot',
1847 'host-libraries', 'compilers', 'glibcs',
1848 'update-syscalls', 'list-compilers',
1850 parser
.add_argument('configs',
1851 help='Versions to check out or configurations to build',
1857 """The main entry point."""
1858 parser
= get_parser()
1859 opts
= parser
.parse_args(argv
)
1860 topdir
= os
.path
.abspath(opts
.topdir
)
1861 ctx
= Context(topdir
, opts
.parallelism
, opts
.keep
, opts
.replace_sources
,
1862 opts
.strip
, opts
.full_gcc
, opts
.action
,
1863 shallow
=opts
.shallow
)
1864 ctx
.run_builds(opts
.action
, opts
.configs
)
1867 if __name__
== '__main__':