2 # Build many configurations of glibc.
3 # Copyright (C) 2016-2020 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='alpha',
167 self.add_config(arch='arm',
168 os_name='linux-gnueabi',
169 extra_glibcs=[{'variant': 'v4t',
170 'ccopts': '-march=armv4t'}])
171 self.add_config(arch='armeb',
172 os_name='linux-gnueabi')
173 self.add_config(arch='armeb',
174 os_name='linux-gnueabi',
176 gcc_cfg=['--with-arch=armv7-a'])
177 self.add_config(arch='arm',
178 os_name='linux-gnueabihf',
179 gcc_cfg=['--with-float=hard', '--with-cpu=arm926ej-s'],
180 extra_glibcs=[{'variant': 'v7a',
181 'ccopts': '-march=armv7-a -mfpu=vfpv3'},
182 {'variant': 'v7a-disable-multi-arch',
183 'ccopts': '-march=armv7-a -mfpu=vfpv3',
184 'cfg': ['--disable-multi-arch']}])
185 self.add_config(arch='armeb',
186 os_name='linux-gnueabihf',
187 gcc_cfg=['--with-float=hard', '--with-cpu=arm926ej-s'])
188 self.add_config(arch='armeb',
189 os_name='linux-gnueabihf',
191 gcc_cfg=['--with-float=hard', '--with-arch=armv7-a',
193 self.add_config(arch='csky',
194 os_name='linux-gnuabiv2',
196 gcc_cfg=['--disable-multilib'])
197 self.add_config(arch='csky',
198 os_name='linux-gnuabiv2',
199 gcc_cfg=['--with-float=hard', '--disable-multilib'])
200 self.add_config(arch='hppa',
202 self.add_config(arch='i686',
204 self.add_config(arch='ia64',
206 first_gcc_cfg=['--with-system-libunwind'])
207 self.add_config(arch='m68k',
209 gcc_cfg=['--disable-multilib'])
210 self.add_config(arch='m68k',
213 gcc_cfg=['--with-arch=cf', '--disable-multilib'])
214 self.add_config(arch='m68k',
216 variant='coldfire-soft',
217 gcc_cfg=['--with-arch=cf', '--with-cpu=54455',
218 '--disable-multilib'])
219 self.add_config(arch='microblaze',
221 gcc_cfg=['--disable-multilib'])
222 self.add_config(arch='microblazeel',
224 gcc_cfg=['--disable-multilib'])
225 self.add_config(arch='mips64',
227 gcc_cfg=['--with-mips-plt'],
228 glibcs=[{'variant': 'n32'},
230 'ccopts': '-mabi=32'},
232 'ccopts': '-mabi=64'}])
233 self.add_config(arch='mips64',
236 gcc_cfg=['--with-mips-plt', '--with-float=soft'],
237 glibcs=[{'variant': 'n32-soft'},
240 'ccopts': '-mabi=32'},
241 {'variant': 'n64-soft',
242 'ccopts': '-mabi=64'}])
243 self.add_config(arch='mips64',
246 gcc_cfg=['--with-mips-plt', '--with-nan=2008',
247 '--with-arch-64=mips64r2',
248 '--with-arch-32=mips32r2'],
249 glibcs=[{'variant': 'n32-nan2008'},
250 {'variant': 'nan2008',
252 'ccopts': '-mabi=32'},
253 {'variant': 'n64-nan2008',
254 'ccopts': '-mabi=64'}])
255 self.add_config(arch='mips64',
257 variant='nan2008-soft',
258 gcc_cfg=['--with-mips-plt', '--with-nan=2008',
259 '--with-arch-64=mips64r2',
260 '--with-arch-32=mips32r2',
261 '--with-float=soft'],
262 glibcs=[{'variant': 'n32-nan2008-soft'},
263 {'variant': 'nan2008-soft',
265 'ccopts': '-mabi=32'},
266 {'variant': 'n64-nan2008-soft',
267 'ccopts': '-mabi=64'}])
268 self.add_config(arch='mips64el',
270 gcc_cfg=['--with-mips-plt'],
271 glibcs=[{'variant': 'n32'},
273 'ccopts': '-mabi=32'},
275 'ccopts': '-mabi=64'}])
276 self.add_config(arch='mips64el',
279 gcc_cfg=['--with-mips-plt', '--with-float=soft'],
280 glibcs=[{'variant': 'n32-soft'},
283 'ccopts': '-mabi=32'},
284 {'variant': 'n64-soft',
285 'ccopts': '-mabi=64'}])
286 self.add_config(arch='mips64el',
289 gcc_cfg=['--with-mips-plt', '--with-nan=2008',
290 '--with-arch-64=mips64r2',
291 '--with-arch-32=mips32r2'],
292 glibcs=[{'variant': 'n32-nan2008'},
293 {'variant': 'nan2008',
295 'ccopts': '-mabi=32'},
296 {'variant': 'n64-nan2008',
297 'ccopts': '-mabi=64'}])
298 self.add_config(arch='mips64el',
300 variant='nan2008-soft',
301 gcc_cfg=['--with-mips-plt', '--with-nan=2008',
302 '--with-arch-64=mips64r2',
303 '--with-arch-32=mips32r2',
304 '--with-float=soft'],
305 glibcs=[{'variant': 'n32-nan2008-soft'},
306 {'variant': 'nan2008-soft',
308 'ccopts': '-mabi=32'},
309 {'variant': 'n64-nan2008-soft',
310 'ccopts': '-mabi=64'}])
311 self.add_config(arch='mipsisa64r6el',
313 gcc_cfg=['--with-mips-plt', '--with-nan=2008',
314 '--with-arch-64=mips64r6',
315 '--with-arch-32=mips32r6',
316 '--with-float=hard'],
317 glibcs=[{'variant': 'n32'},
318 {'arch': 'mipsisa32r6el',
319 'ccopts': '-mabi=32'},
321 'ccopts': '-mabi=64'}])
322 self.add_config(arch='nios2',
324 self.add_config(arch='powerpc',
326 gcc_cfg=['--disable-multilib', '--enable-secureplt'],
327 extra_glibcs=[{'variant': 'power4',
328 'ccopts': '-mcpu=power4',
329 'cfg': ['--with-cpu=power4']}])
330 self.add_config(arch='powerpc',
333 gcc_cfg=['--disable-multilib', '--with-float=soft',
334 '--enable-secureplt'])
335 self.add_config(arch='powerpc64',
337 gcc_cfg=['--disable-multilib', '--enable-secureplt'])
338 self.add_config(arch='powerpc64le',
340 gcc_cfg=['--disable-multilib', '--enable-secureplt'])
341 self.add_config(arch='riscv64',
343 variant='rv64imac-lp64',
344 gcc_cfg=['--with-arch=rv64imac', '--with-abi=lp64',
345 '--disable-multilib'])
346 self.add_config(arch='riscv64',
348 variant='rv64imafdc-lp64',
349 gcc_cfg=['--with-arch=rv64imafdc', '--with-abi=lp64',
350 '--disable-multilib'])
351 self.add_config(arch='riscv64',
353 variant='rv64imafdc-lp64d',
354 gcc_cfg=['--with-arch=rv64imafdc', '--with-abi=lp64d',
355 '--disable-multilib'])
356 self.add_config(arch='s390x',
359 {'arch': 's390', 'ccopts': '-m31'}])
360 self.add_config(arch='sh3',
362 self.add_config(arch='sh3eb',
364 self.add_config(arch='sh4',
366 self.add_config(arch='sh4eb',
368 self.add_config(arch='sh4',
371 gcc_cfg=['--without-fp'])
372 self.add_config(arch='sh4eb',
375 gcc_cfg=['--without-fp'])
376 self.add_config(arch='sparc64',
380 'ccopts': '-m32 -mlong-double-128 -mcpu=v9'}],
381 extra_glibcs=[{'variant': 'leon3',
383 'ccopts' : '-m32 -mlong-double-128 -mcpu=leon3'},
384 {'variant': 'disable-multi-arch',
385 'cfg': ['--disable-multi-arch']},
386 {'variant': 'disable-multi-arch',
388 'ccopts': '-m32 -mlong-double-128 -mcpu=v9',
389 'cfg': ['--disable-multi-arch']}])
390 self.add_config(arch='x86_64',
392 gcc_cfg=['--with-multilib-list=m64,m32,mx32'],
394 {'variant': 'x32', 'ccopts': '-mx32'},
395 {'arch': 'i686', 'ccopts': '-m32 -march=i686'}],
396 extra_glibcs=[{'variant': 'disable-multi-arch',
397 'cfg': ['--disable-multi-arch']},
398 {'variant': 'enable-obsolete',
399 'cfg': ['--enable-obsolete-rpc',
400 '--enable-obsolete-nsl']},
401 {'variant': 'static-pie',
402 'cfg': ['--enable-static-pie']},
403 {'variant': 'x32-static-pie',
405 'cfg': ['--enable-static-pie']},
406 {'variant': 'static-pie',
408 'ccopts': '-m32 -march=i686',
409 'cfg': ['--enable-static-pie']},
410 {'variant': 'disable-multi-arch',
412 'ccopts': '-m32 -march=i686',
413 'cfg': ['--disable-multi-arch']},
414 {'variant': 'enable-obsolete',
416 'ccopts': '-m32 -march=i686',
417 'cfg': ['--enable-obsolete-rpc',
418 '--enable-obsolete-nsl']},
420 'ccopts': '-m32 -march=i486'},
422 'ccopts': '-m32 -march=i586'}])
424 def add_config(self, **args):
425 """Add an individual build configuration."""
426 cfg = Config(self, **args)
427 if cfg.name in self.configs:
428 print('error: duplicate config %s' % cfg.name)
430 self.configs[cfg.name] = cfg
431 for c in cfg.all_glibcs:
432 if c.name in self.glibc_configs:
433 print('error: duplicate glibc config %s' % c.name)
435 self.glibc_configs[c.name] = c
437 def component_srcdir(self, component):
438 """Return the source directory for a given component, e.g. gcc."""
439 return os.path.join(self.srcdir, component)
441 def component_builddir(self, action, config, component, subconfig=None):
442 """Return the directory to use for a build."""
445 assert subconfig is None
446 return os.path.join(self.builddir, action, component)
447 if subconfig is None:
448 return os.path.join(self.builddir, action, config, component)
450 # glibc build as part of compiler build.
451 return os.path.join(self.builddir, action, config, component,
454 def compiler_installdir(self, config):
455 """Return the directory in which to install a compiler."""
456 return os.path.join(self.installdir, 'compilers', config)
458 def compiler_bindir(self, config):
459 """Return the directory in which to find compiler binaries."""
460 return os.path.join(self.compiler_installdir(config), 'bin')
462 def compiler_sysroot(self, config):
463 """Return the sysroot directory for a compiler."""
464 return os.path.join(self.compiler_installdir(config), 'sysroot')
466 def glibc_installdir(self, config):
467 """Return the directory in which to install glibc."""
468 return os.path.join(self.installdir, 'glibcs', config)
470 def run_builds(self, action, configs):
471 """Run the requested builds."""
472 if action == 'checkout':
473 self.checkout(configs)
475 if action == 'bot-cycle':
477 print('error: configurations specified for bot-cycle')
483 print('error: configurations specified for bot')
487 if action in ('host-libraries', 'list-compilers',
488 'list-glibcs') and configs:
489 print('error: configurations specified for ' + action)
491 if action == 'list-compilers':
492 for name in sorted(self.configs.keys()):
495 if action == 'list-glibcs':
496 for config in sorted(self.glibc_configs.values(),
497 key=lambda c: c.name):
498 print(config.name, config.compiler.name)
500 self.clear_last_build_state(action)
501 build_time = datetime.datetime.utcnow()
502 if action == 'host-libraries':
503 build_components = ('gmp', 'mpfr', 'mpc')
506 self.build_host_libraries()
507 elif action == 'compilers':
508 build_components = ('binutils', 'gcc', 'glibc', 'linux', 'mig',
510 old_components = ('gmp', 'mpfr', 'mpc')
511 old_versions = self.build_state['host-libraries']['build-versions']
512 self.build_compilers(configs)
514 build_components = ('glibc',)
515 old_components = ('gmp', 'mpfr', 'mpc', 'binutils', 'gcc', 'linux',
516 'mig', 'gnumach', 'hurd')
517 old_versions = self.build_state['compilers']['build-versions']
518 if action == 'update-syscalls':
519 self.update_syscalls(configs)
521 self.build_glibcs(configs)
525 # Partial build, do not update stored state.
528 for k in build_components:
529 if k in self.versions:
530 build_versions[k] = {'version': self.versions[k]['version'],
531 'revision': self.versions[k]['revision']}
532 for k in old_components:
533 if k in old_versions:
534 build_versions[k] = {'version': old_versions[k]['version'],
535 'revision': old_versions[k]['revision']}
536 self.update_build_state(action, build_time, build_versions)
539 def remove_dirs(*args):
540 """Remove directories and their contents if they exist."""
542 shutil.rmtree(dir, ignore_errors=True)
545 def remove_recreate_dirs(*args):
546 """Remove directories if they exist, and create them as empty."""
547 Context.remove_dirs(*args)
549 os.makedirs(dir, exist_ok=True)
551 def add_makefile_cmdlist(self, target, cmdlist, logsdir):
552 """Add makefile text for a list of commands."""
553 commands = cmdlist.makefile_commands(self.wrapper, logsdir)
554 self.makefile_pieces.append('all: %s\n.PHONY: %s\n%s:\n%s\n' %
555 (target, target, target, commands))
556 self.status_log_list.extend(cmdlist.status_logs(logsdir))
558 def write_files(self):
559 """Write out the Makefile and wrapper script."""
560 mftext = ''.join(self.makefile_pieces)
561 with open(self.makefile, 'w') as f:
571 'prev_status=$prev_base-status.txt\n'
572 'this_status=$this_base-status.txt\n'
573 'this_log=$this_base-log.txt\n'
574 'date > "$this_log"\n'
575 'echo >> "$this_log"\n'
576 'echo "Description: $desc" >> "$this_log"\n'
577 'printf "%s" "Command:" >> "$this_log"\n'
578 'for word in "$@"; do\n'
579 ' if expr "$word" : "[]+,./0-9@A-Z_a-z-]\\\\{1,\\\\}\\$" > /dev/null; then\n'
580 ' printf " %s" "$word"\n'
583 ' printf "%s" "$word" | sed -e "s/\'/\'\\\\\\\\\'\'/"\n'
586 'done >> "$this_log"\n'
587 'echo >> "$this_log"\n'
588 'echo "Directory: $dir" >> "$this_log"\n'
589 'echo "Path addition: $path" >> "$this_log"\n'
590 'echo >> "$this_log"\n'
593 ' echo >> "$this_log"\n'
594 ' echo "$1: $desc" > "$this_status"\n'
595 ' echo "$1: $desc" >> "$this_log"\n'
596 ' echo >> "$this_log"\n'
597 ' date >> "$this_log"\n'
598 ' echo "$1: $desc"\n'
603 ' if [ "$1" != "0" ]; then\n'
604 ' record_status FAIL\n'
607 'if [ "$prev_base" ] && ! grep -q "^PASS" "$prev_status"; then\n'
608 ' record_status UNRESOLVED\n'
610 'if [ "$dir" ]; then\n'
612 ' check_error "$?"\n'
614 'if [ "$path" ]; then\n'
615 ' PATH=$path:$PATH\n'
617 '"$@" < /dev/null >> "$this_log" 2>&1\n'
619 'record_status PASS\n')
620 with open(self.wrapper, 'w') as f:
621 f.write(wrapper_text)
623 mode_exec = (stat.S_IRWXU|stat.S_IRGRP|stat.S_IXGRP|
624 stat.S_IROTH|stat.S_IXOTH)
625 os.chmod(self.wrapper, mode_exec)
628 'if ! [ -f tests.sum ]; then\n'
629 ' echo "No test summary available."\n'
634 ' echo "Contents of $1:"\n'
638 ' echo "End of contents of $1."\n'
641 'save_file tests.sum\n'
642 'non_pass_tests=$(grep -v "^PASS: " tests.sum | sed -e "s/^PASS: //")\n'
643 'for t in $non_pass_tests; do\n'
644 ' if [ -f "$t.out" ]; then\n'
645 ' save_file "$t.out"\n'
648 with open(self.save_logs, 'w') as f:
649 f.write(save_logs_text)
650 os.chmod(self.save_logs, mode_exec)
653 """Do the actual build."""
654 cmd = ['make', '-j%d' % self.parallelism]
655 subprocess.run(cmd, cwd=self.builddir, check=True)
657 def build_host_libraries(self):
658 """Build the host libraries."""
659 installdir = self.host_libraries_installdir
660 builddir = os.path.join(self.builddir, 'host-libraries')
661 logsdir = os.path.join(self.logsdir, 'host-libraries')
662 self.remove_recreate_dirs(installdir, builddir, logsdir)
663 cmdlist = CommandList('host-libraries', self.keep)
664 self.build_host_library(cmdlist, 'gmp')
665 self.build_host_library(cmdlist, 'mpfr',
666 ['--with-gmp=%s' % installdir])
667 self.build_host_library(cmdlist, 'mpc',
668 ['--with-gmp=%s' % installdir,
669 '--with-mpfr=%s' % installdir])
670 cmdlist.add_command('done', ['touch', os.path.join(installdir, 'ok')])
671 self.add_makefile_cmdlist('host-libraries', cmdlist, logsdir)
673 def build_host_library(self, cmdlist, lib, extra_opts=None):
674 """Build one host library."""
675 srcdir = self.component_srcdir(lib)
676 builddir = self.component_builddir('host-libraries', None, lib)
677 installdir = self.host_libraries_installdir
678 cmdlist.push_subdesc(lib)
679 cmdlist.create_use_dir(builddir)
680 cfg_cmd = [os.path.join(srcdir, 'configure'),
681 '--prefix=%s' % installdir,
684 cfg_cmd.extend (extra_opts)
685 cmdlist.add_command('configure', cfg_cmd)
686 cmdlist.add_command('build', ['make'])
687 cmdlist.add_command('check', ['make', 'check'])
688 cmdlist.add_command('install', ['make', 'install'])
689 cmdlist.cleanup_dir()
690 cmdlist.pop_subdesc()
692 def build_compilers(self, configs):
693 """Build the compilers."""
695 self.remove_dirs(os.path.join(self.builddir, 'compilers'))
696 self.remove_dirs(os.path.join(self.installdir, 'compilers'))
697 self.remove_dirs(os.path.join(self.logsdir, 'compilers'))
698 configs = sorted(self.configs.keys())
700 self.configs[c].build()
702 def build_glibcs(self, configs):
703 """Build the glibcs."""
705 self.remove_dirs(os.path.join(self.builddir, 'glibcs'))
706 self.remove_dirs(os.path.join(self.installdir, 'glibcs'))
707 self.remove_dirs(os.path.join(self.logsdir, 'glibcs'))
708 configs = sorted(self.glibc_configs.keys())
710 self.glibc_configs[c].build()
712 def update_syscalls(self, configs):
713 """Update the glibc syscall lists."""
715 self.remove_dirs(os.path.join(self.builddir, 'update-syscalls'))
716 self.remove_dirs(os.path.join(self.logsdir, 'update-syscalls'))
717 configs = sorted(self.glibc_configs.keys())
719 self.glibc_configs[c].update_syscalls()
721 def load_versions_json(self):
722 """Load information about source directory versions."""
723 if not os.access(self.versions_json, os.F_OK):
726 with open(self.versions_json, 'r') as f:
727 self.versions = json.load(f)
729 def store_json(self, data, filename):
730 """Store information in a JSON file."""
731 filename_tmp = filename + '.tmp'
732 with open(filename_tmp, 'w') as f:
733 json.dump(data, f, indent=2, sort_keys=True)
734 os.rename(filename_tmp, filename)
736 def store_versions_json(self):
737 """Store information about source directory versions."""
738 self.store_json(self.versions, self.versions_json)
740 def set_component_version(self, component, version, explicit, revision):
741 """Set the version information for a component."""
742 self.versions[component] = {'version': version,
743 'explicit': explicit,
744 'revision': revision}
745 self.store_versions_json()
747 def checkout(self, versions):
748 """Check out the desired component versions."""
749 default_versions = {'binutils': 'vcs-2.34',
751 'glibc': 'vcs-mainline',
756 'mig': 'vcs-mainline',
757 'gnumach': 'vcs-mainline',
758 'hurd': 'vcs-mainline'}
760 explicit_versions = {}
763 for k in default_versions.keys():
767 if k in use_versions:
768 print('error: multiple versions for %s' % k)
771 explicit_versions[k] = True
775 print('error: unknown component in %s' % v)
777 for k in default_versions.keys():
778 if k not in use_versions:
779 if k in self.versions and self.versions[k]['explicit']:
780 use_versions[k] = self.versions[k]['version']
781 explicit_versions[k] = True
783 use_versions[k] = default_versions[k]
784 explicit_versions[k] = False
785 os.makedirs(self.srcdir, exist_ok=True)
786 for k in sorted(default_versions.keys()):
787 update = os.access(self.component_srcdir(k), os.F_OK)
790 k in self.versions and
791 v != self.versions[k]['version']):
792 if not self.replace_sources:
793 print('error: version of %s has changed from %s to %s, '
794 'use --replace-sources to check out again' %
795 (k, self.versions[k]['version'], v))
797 shutil.rmtree(self.component_srcdir(k))
799 if v.startswith('vcs-'):
800 revision = self.checkout_vcs(k, v[4:], update)
802 self.checkout_tar(k, v, update)
804 self.set_component_version(k, v, explicit_versions[k], revision)
805 if self.get_script_text() != self.script_text:
806 # Rerun the checkout process in case the updated script
807 # uses different default versions or new components.
810 def checkout_vcs(self, component, version, update):
811 """Check out the given version of the given component from version
812 control. Return a revision identifier."""
813 if component == 'binutils':
814 git_url = 'git://sourceware.org/git/binutils-gdb.git'
815 if version == 'mainline':
816 git_branch = 'master'
818 trans = str.maketrans({'.': '_'})
819 git_branch = 'binutils-%s-branch' % version.translate(trans)
820 return self.git_checkout(component, git_url, git_branch, update)
821 elif component == 'gcc':
822 if version == 'mainline':
825 branch = 'releases/gcc-%s' % version
826 return self.gcc_checkout(branch, update)
827 elif component == 'glibc':
828 git_url = 'git://sourceware.org/git/glibc.git'
829 if version == 'mainline':
830 git_branch = 'master'
832 git_branch = 'release/%s/master' % version
833 r = self.git_checkout(component, git_url, git_branch, update)
834 self.fix_glibc_timestamps()
836 elif component == 'gnumach':
837 git_url = 'git://git.savannah.gnu.org/hurd/gnumach.git'
838 git_branch = 'master'
839 r = self.git_checkout(component, git_url, git_branch, update)
840 subprocess.run(['autoreconf', '-i'],
841 cwd=self.component_srcdir(component), check=True)
843 elif component == 'mig':
844 git_url = 'git://git.savannah.gnu.org/hurd/mig.git'
845 git_branch = 'master'
846 r = self.git_checkout(component, git_url, git_branch, update)
847 subprocess.run(['autoreconf', '-i'],
848 cwd=self.component_srcdir(component), check=True)
850 elif component == 'hurd':
851 git_url = 'git://git.savannah.gnu.org/hurd/hurd.git'
852 git_branch = 'master'
853 r = self.git_checkout(component, git_url, git_branch, update)
854 subprocess.run(['autoconf'],
855 cwd=self.component_srcdir(component), check=True)
858 print('error: component %s coming from VCS' % component)
861 def git_checkout(self, component, git_url, git_branch, update):
862 """Check out a component from git. Return a commit identifier."""
864 subprocess.run(['git', 'remote', 'prune', 'origin'],
865 cwd=self.component_srcdir(component), check=True)
866 if self.replace_sources:
867 subprocess.run(['git', 'clean', '-dxfq'],
868 cwd=self.component_srcdir(component), check=True)
869 subprocess.run(['git', 'pull', '-q'],
870 cwd=self.component_srcdir(component), check=True)
873 depth_arg = ('--depth', '1')
876 subprocess.run(['git', 'clone', '-q', '-b', git_branch,
878 self.component_srcdir(component)], check=True)
879 r = subprocess.run(['git', 'rev-parse', 'HEAD'],
880 cwd=self.component_srcdir(component),
881 stdout=subprocess.PIPE,
882 check=True, universal_newlines=True).stdout
885 def fix_glibc_timestamps(self):
886 """Fix timestamps in a glibc checkout."""
887 # Ensure that builds do not try to regenerate generated files
888 # in the source tree.
889 srcdir = self.component_srcdir('glibc')
890 # These files have Makefile dependencies to regenerate them in
891 # the source tree that may be active during a normal build.
892 # Some other files have such dependencies but do not need to
893 # be touched because nothing in a build depends on the files
895 for f in ('sysdeps/mach/hurd/bits/errno.h',):
896 to_touch = os.path.join(srcdir, f)
897 subprocess.run(['touch', '-c', to_touch], check=True)
898 for dirpath, dirnames, filenames in os.walk(srcdir):
900 if (f == 'configure' or
901 f == 'preconfigure' or
902 f.endswith('-kw.h')):
903 to_touch = os.path.join(dirpath, f)
904 subprocess.run(['touch', to_touch], check=True)
906 def gcc_checkout(self, branch, update):
907 """Check out GCC from git. Return the commit identifier."""
908 if os.access(os.path.join(self.component_srcdir('gcc'), '.svn'),
910 if not self.replace_sources:
911 print('error: GCC has moved from SVN to git, use '
912 '--replace-sources to check out again')
914 shutil.rmtree(self.component_srcdir('gcc'))
917 self.git_checkout('gcc', 'git://gcc.gnu.org/git/gcc.git',
919 subprocess.run(['contrib/gcc_update', '--silent'],
920 cwd=self.component_srcdir('gcc'), check=True)
921 r = subprocess.run(['git', 'rev-parse', 'HEAD'],
922 cwd=self.component_srcdir('gcc'),
923 stdout=subprocess.PIPE,
924 check=True, universal_newlines=True).stdout
927 def checkout_tar(self, component, version, update):
928 """Check out the given version of the given component from a
932 url_map = {'binutils': 'https://ftp.gnu.org/gnu/binutils/binutils-%(version)s.tar.bz2',
933 'gcc': 'https://ftp.gnu.org/gnu/gcc/gcc-%(version)s/gcc-%(version)s.tar.gz',
934 'gmp': 'https://ftp.gnu.org/gnu/gmp/gmp-%(version)s.tar.xz',
935 'linux': 'https://www.kernel.org/pub/linux/kernel/v%(major)s.x/linux-%(version)s.tar.xz',
936 'mpc': 'https://ftp.gnu.org/gnu/mpc/mpc-%(version)s.tar.gz',
937 'mpfr': 'https://ftp.gnu.org/gnu/mpfr/mpfr-%(version)s.tar.xz',
938 'mig': 'https://ftp.gnu.org/gnu/mig/mig-%(version)s.tar.bz2',
939 'gnumach': 'https://ftp.gnu.org/gnu/gnumach/gnumach-%(version)s.tar.bz2',
940 'hurd': 'https://ftp.gnu.org/gnu/hurd/hurd-%(version)s.tar.bz2'}
941 if component not in url_map:
942 print('error: component %s coming from tarball' % component)
944 version_major = version.split('.')[0]
945 url = url_map[component] % {'version': version, 'major': version_major}
946 filename = os.path.join(self.srcdir, url.split('/')[-1])
947 response = urllib.request.urlopen(url)
948 data = response.read()
949 with open(filename, 'wb') as f:
951 subprocess.run(['tar', '-C', self.srcdir, '-x', '-f', filename],
953 os.rename(os.path.join(self.srcdir, '%s-%s' % (component, version)),
954 self.component_srcdir(component))
957 def load_build_state_json(self):
958 """Load information about the state of previous builds."""
959 if os.access(self.build_state_json, os.F_OK):
960 with open(self.build_state_json, 'r') as f:
961 self.build_state = json.load(f)
963 self.build_state = {}
964 for k in ('host-libraries', 'compilers', 'glibcs', 'update-syscalls'):
965 if k not in self.build_state:
966 self.build_state[k] = {}
967 if 'build-time' not in self.build_state[k]:
968 self.build_state[k]['build-time'] = ''
969 if 'build-versions' not in self.build_state[k]:
970 self.build_state[k]['build-versions'] = {}
971 if 'build-results' not in self.build_state[k]:
972 self.build_state[k]['build-results'] = {}
973 if 'result-changes' not in self.build_state[k]:
974 self.build_state[k]['result-changes'] = {}
975 if 'ever-passed' not in self.build_state[k]:
976 self.build_state[k]['ever-passed'] = []
978 def store_build_state_json(self):
979 """Store information about the state of previous builds."""
980 self.store_json(self.build_state, self.build_state_json)
982 def clear_last_build_state(self, action):
983 """Clear information about the state of part of the build."""
984 # We clear the last build time and versions when starting a
985 # new build. The results of the last build are kept around,
986 # as comparison is still meaningful if this build is aborted
987 # and a new one started.
988 self.build_state[action]['build-time'] = ''
989 self.build_state[action]['build-versions'] = {}
990 self.store_build_state_json()
992 def update_build_state(self, action, build_time, build_versions):
993 """Update the build state after a build."""
994 build_time = build_time.replace(microsecond=0)
995 self.build_state[action]['build-time'] = str(build_time)
996 self.build_state[action]['build-versions'] = build_versions
998 for log in self.status_log_list:
999 with open(log, 'r') as f:
1001 log_text = log_text.rstrip()
1002 m = re.fullmatch('([A-Z]+): (.*)', log_text)
1004 test_name = m.group(2)
1005 assert test_name not in build_results
1006 build_results[test_name] = result
1007 old_build_results = self.build_state[action]['build-results']
1008 self.build_state[action]['build-results'] = build_results
1010 all_tests = set(old_build_results.keys()) | set(build_results.keys())
1012 if t in old_build_results:
1013 old_res = old_build_results[t]
1015 old_res = '(New test)'
1016 if t in build_results:
1017 new_res = build_results[t]
1019 new_res = '(Test removed)'
1020 if old_res != new_res:
1021 result_changes[t] = '%s -> %s' % (old_res, new_res)
1022 self.build_state[action]['result-changes'] = result_changes
1023 old_ever_passed = {t for t in self.build_state[action]['ever-passed']
1024 if t in build_results}
1025 new_passes = {t for t in build_results if build_results[t] == 'PASS'}
1026 self.build_state[action]['ever-passed'] = sorted(old_ever_passed |
1028 self.store_build_state_json()
1030 def load_bot_config_json(self):
1031 """Load bot configuration."""
1032 with open(self.bot_config_json, 'r') as f:
1033 self.bot_config = json.load(f)
1035 def part_build_old(self, action, delay):
1036 """Return whether the last build for a given action was at least a
1037 given number of seconds ago, or does not have a time recorded."""
1038 old_time_str = self.build_state[action]['build-time']
1039 if not old_time_str:
1041 old_time = datetime.datetime.strptime(old_time_str,
1042 '%Y-%m-%d %H:%M:%S')
1043 new_time = datetime.datetime.utcnow()
1044 delta = new_time - old_time
1045 return delta.total_seconds() >= delay
1047 def bot_cycle(self):
1048 """Run a single round of checkout and builds."""
1049 print('Bot cycle starting %s.' % str(datetime.datetime.utcnow()))
1050 self.load_bot_config_json()
1051 actions = ('host-libraries', 'compilers', 'glibcs')
1052 self.bot_run_self(['--replace-sources'], 'checkout')
1053 self.load_versions_json()
1054 if self.get_script_text() != self.script_text:
1055 print('Script changed, re-execing.')
1056 # On script change, all parts of the build should be rerun.
1058 self.clear_last_build_state(a)
1060 check_components = {'host-libraries': ('gmp', 'mpfr', 'mpc'),
1061 'compilers': ('binutils', 'gcc', 'glibc', 'linux',
1062 'mig', 'gnumach', 'hurd'),
1063 'glibcs': ('glibc',)}
1066 build_vers = self.build_state[a]['build-versions']
1067 must_build[a] = False
1068 if not self.build_state[a]['build-time']:
1069 must_build[a] = True
1072 for c in check_components[a]:
1074 old_vers[c] = build_vers[c]
1075 new_vers[c] = {'version': self.versions[c]['version'],
1076 'revision': self.versions[c]['revision']}
1077 if new_vers == old_vers:
1078 print('Versions for %s unchanged.' % a)
1080 print('Versions changed or rebuild forced for %s.' % a)
1081 if a == 'compilers' and not self.part_build_old(
1082 a, self.bot_config['compilers-rebuild-delay']):
1083 print('Not requiring rebuild of compilers this soon.')
1085 must_build[a] = True
1086 if must_build['host-libraries']:
1087 must_build['compilers'] = True
1088 if must_build['compilers']:
1089 must_build['glibcs'] = True
1092 print('Must rebuild %s.' % a)
1093 self.clear_last_build_state(a)
1095 print('No need to rebuild %s.' % a)
1096 if os.access(self.logsdir, os.F_OK):
1097 shutil.rmtree(self.logsdir_old, ignore_errors=True)
1098 shutil.copytree(self.logsdir, self.logsdir_old)
1101 build_time = datetime.datetime.utcnow()
1102 print('Rebuilding %s at %s.' % (a, str(build_time)))
1103 self.bot_run_self([], a)
1104 self.load_build_state_json()
1105 self.bot_build_mail(a, build_time)
1106 print('Bot cycle done at %s.' % str(datetime.datetime.utcnow()))
1108 def bot_build_mail(self, action, build_time):
1109 """Send email with the results of a build."""
1110 if not ('email-from' in self.bot_config and
1111 'email-server' in self.bot_config and
1112 'email-subject' in self.bot_config and
1113 'email-to' in self.bot_config):
1114 if not self.email_warning:
1115 print("Email not configured, not sending.")
1116 self.email_warning = True
1119 build_time = build_time.replace(microsecond=0)
1120 subject = (self.bot_config['email-subject'] %
1122 'build-time': str(build_time)})
1123 results = self.build_state[action]['build-results']
1124 changes = self.build_state[action]['result-changes']
1125 ever_passed = set(self.build_state[action]['ever-passed'])
1126 versions = self.build_state[action]['build-versions']
1127 new_regressions = {k for k in changes if changes[k] == 'PASS -> FAIL'}
1128 all_regressions = {k for k in ever_passed if results[k] == 'FAIL'}
1129 all_fails = {k for k in results if results[k] == 'FAIL'}
1131 new_reg_list = sorted(['FAIL: %s' % k for k in new_regressions])
1132 new_reg_text = ('New regressions:\n\n%s\n\n' %
1133 '\n'.join(new_reg_list))
1137 all_reg_list = sorted(['FAIL: %s' % k for k in all_regressions])
1138 all_reg_text = ('All regressions:\n\n%s\n\n' %
1139 '\n'.join(all_reg_list))
1143 all_fail_list = sorted(['FAIL: %s' % k for k in all_fails])
1144 all_fail_text = ('All failures:\n\n%s\n\n' %
1145 '\n'.join(all_fail_list))
1149 changes_list = sorted(changes.keys())
1150 changes_list = ['%s: %s' % (changes[k], k) for k in changes_list]
1151 changes_text = ('All changed results:\n\n%s\n\n' %
1152 '\n'.join(changes_list))
1155 results_text = (new_reg_text + all_reg_text + all_fail_text +
1157 if not results_text:
1158 results_text = 'Clean build with unchanged results.\n\n'
1159 versions_list = sorted(versions.keys())
1160 versions_list = ['%s: %s (%s)' % (k, versions[k]['version'],
1161 versions[k]['revision'])
1162 for k in versions_list]
1163 versions_text = ('Component versions for this build:\n\n%s\n' %
1164 '\n'.join(versions_list))
1165 body_text = results_text + versions_text
1166 msg = email.mime.text.MIMEText(body_text)
1167 msg['Subject'] = subject
1168 msg['From'] = self.bot_config['email-from']
1169 msg['To'] = self.bot_config['email-to']
1170 msg['Message-ID'] = email.utils.make_msgid()
1171 msg['Date'] = email.utils.format_datetime(datetime.datetime.utcnow())
1172 with smtplib.SMTP(self.bot_config['email-server']) as s:
1175 def bot_run_self(self, opts, action, check=True):
1176 """Run a copy of this script with given options."""
1177 cmd = [sys.executable, sys.argv[0], '--keep=none',
1178 '-j%d' % self.parallelism]
1180 cmd.append('--full-gcc')
1182 cmd.extend([self.topdir, action])
1184 subprocess.run(cmd, check=check)
1187 """Run repeated rounds of checkout and builds."""
1189 self.load_bot_config_json()
1190 if not self.bot_config['run']:
1191 print('Bot exiting by request.')
1193 self.bot_run_self([], 'bot-cycle', check=False)
1194 self.load_bot_config_json()
1195 if not self.bot_config['run']:
1196 print('Bot exiting by request.')
1198 time.sleep(self.bot_config['delay'])
1199 if self.get_script_text() != self.script_text:
1200 print('Script changed, bot re-execing.')
1203 class LinuxHeadersPolicyForBuild(object):
1204 """Names and directories for installing Linux headers. Build variant."""
1206 def __init__(self, config):
1207 self.arch = config.arch
1208 self.srcdir = config.ctx.component_srcdir('linux')
1209 self.builddir = config.component_builddir('linux')
1210 self.headers_dir = os.path.join(config.sysroot, 'usr')
1212 class LinuxHeadersPolicyForUpdateSyscalls(object):
1213 """Names and directories for Linux headers. update-syscalls variant."""
1215 def __init__(self, glibc, headers_dir):
1216 self.arch = glibc.compiler.arch
1217 self.srcdir = glibc.compiler.ctx.component_srcdir('linux')
1218 self.builddir = glibc.ctx.component_builddir(
1219 'update-syscalls', glibc.name, 'build-linux')
1220 self.headers_dir = headers_dir
1222 def install_linux_headers(policy, cmdlist):
1223 """Install Linux kernel headers."""
1224 arch_map = {'aarch64': 'arm64',
1235 'microblaze': 'microblaze',
1238 'powerpc': 'powerpc',
1247 if policy.arch.startswith(k):
1248 linux_arch = arch_map[k]
1250 assert linux_arch is not None
1251 cmdlist.push_subdesc('linux')
1252 cmdlist.create_use_dir(policy.builddir)
1253 cmdlist.add_command('install-headers',
1254 ['make', '-C', policy.srcdir, 'O=%s' % policy.builddir,
1255 'ARCH=%s' % linux_arch,
1256 'INSTALL_HDR_PATH=%s' % policy.headers_dir,
1258 cmdlist.cleanup_dir()
1259 cmdlist.pop_subdesc()
1261 class Config(object):
1262 """A configuration for building a compiler and associated libraries."""
1264 def __init__(self, ctx, arch, os_name, variant=None, gcc_cfg=None,
1265 first_gcc_cfg=None, glibcs=None, extra_glibcs=None):
1266 """Initialize a Config object."""
1270 self.variant = variant
1272 self.name = '%s-%s' % (arch, os_name)
1274 self.name = '%s-%s-%s' % (arch, os_name, variant)
1275 self.triplet = '%s-glibc-%s' % (arch, os_name)
1279 self.gcc_cfg = gcc_cfg
1280 if first_gcc_cfg is None:
1281 self.first_gcc_cfg = []
1283 self.first_gcc_cfg = first_gcc_cfg
1285 glibcs = [{'variant': variant}]
1286 if extra_glibcs is None:
1288 glibcs = [Glibc(self, **g) for g in glibcs]
1289 extra_glibcs = [Glibc(self, **g) for g in extra_glibcs]
1290 self.all_glibcs = glibcs + extra_glibcs
1291 self.compiler_glibcs = glibcs
1292 self.installdir = ctx.compiler_installdir(self.name)
1293 self.bindir = ctx.compiler_bindir(self.name)
1294 self.sysroot = ctx.compiler_sysroot(self.name)
1295 self.builddir = os.path.join(ctx.builddir, 'compilers', self.name)
1296 self.logsdir = os.path.join(ctx.logsdir, 'compilers', self.name)
1298 def component_builddir(self, component):
1299 """Return the directory to use for a (non-glibc) build."""
1300 return self.ctx.component_builddir('compilers', self.name, component)
1303 """Generate commands to build this compiler."""
1304 self.ctx.remove_recreate_dirs(self.installdir, self.builddir,
1306 cmdlist = CommandList('compilers-%s' % self.name, self.ctx.keep)
1307 cmdlist.add_command('check-host-libraries',
1309 os.path.join(self.ctx.host_libraries_installdir,
1311 cmdlist.use_path(self.bindir)
1312 self.build_cross_tool(cmdlist, 'binutils', 'binutils',
1314 '--disable-gdbserver',
1315 '--disable-libdecnumber',
1316 '--disable-readline',
1318 if self.os.startswith('linux'):
1319 install_linux_headers(LinuxHeadersPolicyForBuild(self), cmdlist)
1320 self.build_gcc(cmdlist, True)
1321 if self.os == 'gnu':
1322 self.install_gnumach_headers(cmdlist)
1323 self.build_cross_tool(cmdlist, 'mig', 'mig')
1324 self.install_hurd_headers(cmdlist)
1325 for g in self.compiler_glibcs:
1326 cmdlist.push_subdesc('glibc')
1327 cmdlist.push_subdesc(g.name)
1328 g.build_glibc(cmdlist, GlibcPolicyForCompiler(g))
1329 cmdlist.pop_subdesc()
1330 cmdlist.pop_subdesc()
1331 self.build_gcc(cmdlist, False)
1332 cmdlist.add_command('done', ['touch',
1333 os.path.join(self.installdir, 'ok')])
1334 self.ctx.add_makefile_cmdlist('compilers-%s' % self.name, cmdlist,
1337 def build_cross_tool(self, cmdlist, tool_src, tool_build, extra_opts=None):
1338 """Build one cross tool."""
1339 srcdir = self.ctx.component_srcdir(tool_src)
1340 builddir = self.component_builddir(tool_build)
1341 cmdlist.push_subdesc(tool_build)
1342 cmdlist.create_use_dir(builddir)
1343 cfg_cmd = [os.path.join(srcdir, 'configure'),
1344 '--prefix=%s' % self.installdir,
1345 '--build=%s' % self.ctx.build_triplet,
1346 '--host=%s' % self.ctx.build_triplet,
1347 '--target=%s' % self.triplet,
1348 '--with-sysroot=%s' % self.sysroot]
1350 cfg_cmd.extend(extra_opts)
1351 cmdlist.add_command('configure', cfg_cmd)
1352 cmdlist.add_command('build', ['make'])
1353 # Parallel "make install" for GCC has race conditions that can
1354 # cause it to fail; see
1355 # <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=42980>. Such
1356 # problems are not known for binutils, but doing the
1357 # installation in parallel within a particular toolchain build
1358 # (as opposed to installation of one toolchain from
1359 # build-many-glibcs.py running in parallel to the installation
1360 # of other toolchains being built) is not known to be
1361 # significantly beneficial, so it is simplest just to disable
1362 # parallel install for cross tools here.
1363 cmdlist.add_command('install', ['make', '-j1', 'install'])
1364 cmdlist.cleanup_dir()
1365 cmdlist.pop_subdesc()
1367 def install_gnumach_headers(self, cmdlist):
1368 """Install GNU Mach headers."""
1369 srcdir = self.ctx.component_srcdir('gnumach')
1370 builddir = self.component_builddir('gnumach')
1371 cmdlist.push_subdesc('gnumach')
1372 cmdlist.create_use_dir(builddir)
1373 cmdlist.add_command('configure',
1374 [os.path.join(srcdir, 'configure'),
1375 '--build=%s' % self.ctx.build_triplet,
1376 '--host=%s' % self.triplet,
1378 'CC=%s-gcc -nostdlib' % self.triplet])
1379 cmdlist.add_command('install', ['make', 'DESTDIR=%s' % self.sysroot,
1381 cmdlist.cleanup_dir()
1382 cmdlist.pop_subdesc()
1384 def install_hurd_headers(self, cmdlist):
1385 """Install Hurd headers."""
1386 srcdir = self.ctx.component_srcdir('hurd')
1387 builddir = self.component_builddir('hurd')
1388 cmdlist.push_subdesc('hurd')
1389 cmdlist.create_use_dir(builddir)
1390 cmdlist.add_command('configure',
1391 [os.path.join(srcdir, 'configure'),
1392 '--build=%s' % self.ctx.build_triplet,
1393 '--host=%s' % self.triplet,
1395 '--disable-profile', '--without-parted',
1396 'CC=%s-gcc -nostdlib' % self.triplet])
1397 cmdlist.add_command('install', ['make', 'prefix=%s' % self.sysroot,
1398 'no_deps=t', 'install-headers'])
1399 cmdlist.cleanup_dir()
1400 cmdlist.pop_subdesc()
1402 def build_gcc(self, cmdlist, bootstrap):
1404 # libssp is of little relevance with glibc's own stack
1405 # checking support. libcilkrts does not support GNU/Hurd (and
1406 # has been removed in GCC 8, so --disable-libcilkrts can be
1407 # removed once glibc no longer supports building with older
1409 cfg_opts = list(self.gcc_cfg)
1410 cfg_opts += ['--disable-libssp', '--disable-libcilkrts']
1411 host_libs = self.ctx.host_libraries_installdir
1412 cfg_opts += ['--with-gmp=%s' % host_libs,
1413 '--with-mpfr=%s' % host_libs,
1414 '--with-mpc=%s' % host_libs]
1416 tool_build = 'gcc-first'
1417 # Building a static-only, C-only compiler that is
1418 # sufficient to build glibc. Various libraries and
1419 # features that may require libc headers must be disabled.
1420 # When configuring with a sysroot, --with-newlib is
1421 # required to define inhibit_libc (to stop some parts of
1422 # libgcc including libc headers); --without-headers is not
1424 cfg_opts += ['--enable-languages=c', '--disable-shared',
1425 '--disable-threads',
1426 '--disable-libatomic',
1427 '--disable-decimal-float',
1429 '--disable-libgomp',
1432 '--disable-libquadmath',
1433 '--disable-libsanitizer',
1434 '--without-headers', '--with-newlib',
1435 '--with-glibc-version=%s' % self.ctx.glibc_version
1437 cfg_opts += self.first_gcc_cfg
1440 # libsanitizer commonly breaks because of glibc header
1441 # changes, or on unusual targets. C++ pre-compiled
1442 # headers are not used during the glibc build and are
1443 # expensive to create.
1444 if not self.ctx.full_gcc:
1445 cfg_opts += ['--disable-libsanitizer',
1446 '--disable-libstdcxx-pch']
1447 langs = 'all' if self.ctx.full_gcc else 'c,c++'
1448 cfg_opts += ['--enable-languages=%s' % langs,
1449 '--enable-shared', '--enable-threads']
1450 self.build_cross_tool(cmdlist, 'gcc', tool_build, cfg_opts)
1452 class GlibcPolicyDefault(object):
1453 """Build policy for glibc: common defaults."""
1455 def __init__(self, glibc):
1456 self.srcdir = glibc.ctx.component_srcdir('glibc')
1457 self.use_usr = glibc.os != 'gnu'
1458 self.prefix = '/usr' if self.use_usr else ''
1459 self.configure_args = [
1460 '--prefix=%s' % self.prefix,
1462 '--build=%s' % glibc.ctx.build_triplet,
1463 '--host=%s' % glibc.triplet,
1464 'CC=%s' % glibc.tool_name('gcc'),
1465 'CXX=%s' % glibc.tool_name('g++'),
1466 'AR=%s' % glibc.tool_name('ar'),
1467 'AS=%s' % glibc.tool_name('as'),
1468 'LD=%s' % glibc.tool_name('ld'),
1469 'NM=%s' % glibc.tool_name('nm'),
1470 'OBJCOPY=%s' % glibc.tool_name('objcopy'),
1471 'OBJDUMP=%s' % glibc.tool_name('objdump'),
1472 'RANLIB=%s' % glibc.tool_name('ranlib'),
1473 'READELF=%s' % glibc.tool_name('readelf'),
1474 'STRIP=%s' % glibc.tool_name('strip'),
1476 if glibc.os == 'gnu':
1477 self.configure_args.append('MIG=%s' % glibc.tool_name('mig'))
1478 self.configure_args += glibc.cfg
1480 def configure(self, cmdlist):
1481 """Invoked to add the configure command to the command list."""
1482 cmdlist.add_command('configure',
1483 [os.path.join(self.srcdir, 'configure'),
1484 *self.configure_args])
1486 def extra_commands(self, cmdlist):
1487 """Invoked to inject additional commands (make check) after build."""
1490 class GlibcPolicyForCompiler(GlibcPolicyDefault):
1491 """Build policy for glibc during the compilers stage."""
1493 def __init__(self, glibc):
1494 super().__init__(glibc)
1495 self.builddir = glibc.ctx.component_builddir(
1496 'compilers', glibc.compiler.name, 'glibc', glibc.name)
1497 self.installdir = glibc.compiler.sysroot
1499 class GlibcPolicyForBuild(GlibcPolicyDefault):
1500 """Build policy for glibc during the glibcs stage."""
1502 def __init__(self, glibc):
1503 super().__init__(glibc)
1504 self.builddir = glibc.ctx.component_builddir(
1505 'glibcs', glibc.name, 'glibc')
1506 self.installdir = glibc.ctx.glibc_installdir(glibc.name)
1508 self.strip = glibc.tool_name('strip')
1511 self.save_logs = glibc.ctx.save_logs
1513 def extra_commands(self, cmdlist):
1515 # Avoid picking up libc.so and libpthread.so, which are
1516 # linker scripts stored in /lib on Hurd. libc and
1517 # libpthread are still stripped via their libc-X.YY.so
1518 # implementation files.
1519 find_command = (('find %s/lib* -name "*.so"'
1520 + r' \! -name libc.so \! -name libpthread.so')
1522 cmdlist.add_command('strip', ['sh', '-c', ('%s $(%s)' %
1523 (self.strip, find_command))])
1524 cmdlist.add_command('check', ['make', 'check'])
1525 cmdlist.add_command('save-logs', [self.save_logs], always_run=True)
1527 class GlibcPolicyForUpdateSyscalls(GlibcPolicyDefault):
1528 """Build policy for glibc during update-syscalls."""
1530 def __init__(self, glibc):
1531 super().__init__(glibc)
1532 self.builddir = glibc.ctx.component_builddir(
1533 'update-syscalls', glibc.name, 'glibc')
1534 self.linuxdir = glibc.ctx.component_builddir(
1535 'update-syscalls', glibc.name, 'linux')
1536 self.linux_policy = LinuxHeadersPolicyForUpdateSyscalls(
1537 glibc, self.linuxdir)
1538 self.configure_args.insert(
1539 0, '--with-headers=%s' % os.path.join(self.linuxdir, 'include'))
1540 # self.installdir not set because installation is not supported
1542 class Glibc(object):
1543 """A configuration for building glibc."""
1545 def __init__(self, compiler, arch=None, os_name=None, variant=None,
1546 cfg=None, ccopts=None):
1547 """Initialize a Glibc object."""
1548 self.ctx = compiler.ctx
1549 self.compiler = compiler
1551 self.arch = compiler.arch
1555 self.os = compiler.os
1558 self.variant = variant
1560 self.name = '%s-%s' % (self.arch, self.os)
1562 self.name = '%s-%s-%s' % (self.arch, self.os, variant)
1563 self.triplet = '%s-glibc-%s' % (self.arch, self.os)
1568 self.ccopts = ccopts
1570 def tool_name(self, tool):
1571 """Return the name of a cross-compilation tool."""
1572 ctool = '%s-%s' % (self.compiler.triplet, tool)
1573 if self.ccopts and (tool == 'gcc' or tool == 'g++'):
1574 ctool = '%s %s' % (ctool, self.ccopts)
1578 """Generate commands to build this glibc."""
1579 builddir = self.ctx.component_builddir('glibcs', self.name, 'glibc')
1580 installdir = self.ctx.glibc_installdir(self.name)
1581 logsdir = os.path.join(self.ctx.logsdir, 'glibcs', self.name)
1582 self.ctx.remove_recreate_dirs(installdir, builddir, logsdir)
1583 cmdlist = CommandList('glibcs-%s' % self.name, self.ctx.keep)
1584 cmdlist.add_command('check-compilers',
1586 os.path.join(self.compiler.installdir, 'ok')])
1587 cmdlist.use_path(self.compiler.bindir)
1588 self.build_glibc(cmdlist, GlibcPolicyForBuild(self))
1589 self.ctx.add_makefile_cmdlist('glibcs-%s' % self.name, cmdlist,
1592 def build_glibc(self, cmdlist, policy):
1593 """Generate commands to build this glibc, either as part of a compiler
1594 build or with the bootstrapped compiler (and in the latter case, run
1596 cmdlist.create_use_dir(policy.builddir)
1597 policy.configure(cmdlist)
1598 cmdlist.add_command('build', ['make'])
1599 cmdlist.add_command('install', ['make', 'install',
1600 'install_root=%s' % policy.installdir])
1601 # GCC uses paths such as lib/../lib64, so make sure lib
1602 # directories always exist.
1603 mkdir_cmd = ['mkdir', '-p',
1604 os.path.join(policy.installdir, 'lib')]
1606 mkdir_cmd += [os.path.join(policy.installdir, 'usr', 'lib')]
1607 cmdlist.add_command('mkdir-lib', mkdir_cmd)
1608 policy.extra_commands(cmdlist)
1609 cmdlist.cleanup_dir()
1611 def update_syscalls(self):
1612 if self.os == 'gnu':
1613 # Hurd does not have system call tables that need updating.
1616 policy = GlibcPolicyForUpdateSyscalls(self)
1617 logsdir = os.path.join(self.ctx.logsdir, 'update-syscalls', self.name)
1618 self.ctx.remove_recreate_dirs(policy.builddir, logsdir)
1619 cmdlist = CommandList('update-syscalls-%s' % self.name, self.ctx.keep)
1620 cmdlist.add_command('check-compilers',
1622 os.path.join(self.compiler.installdir, 'ok')])
1623 cmdlist.use_path(self.compiler.bindir)
1625 install_linux_headers(policy.linux_policy, cmdlist)
1627 cmdlist.create_use_dir(policy.builddir)
1628 policy.configure(cmdlist)
1629 cmdlist.add_command('build', ['make', 'update-syscall-lists'])
1630 cmdlist.cleanup_dir()
1631 self.ctx.add_makefile_cmdlist('update-syscalls-%s' % self.name,
1634 class Command(object):
1635 """A command run in the build process."""
1637 def __init__(self, desc, num, dir, path, command, always_run=False):
1638 """Initialize a Command object."""
1642 trans = str.maketrans({' ': '-'})
1643 self.logbase = '%03d-%s' % (num, desc.translate(trans))
1644 self.command = command
1645 self.always_run = always_run
1648 def shell_make_quote_string(s):
1649 """Given a string not containing a newline, quote it for use by the
1651 assert '\n' not in s
1652 if re.fullmatch('[]+,./0-9@A-Z_a-z-]+', s):
1654 strans = str.maketrans({"'": "'\\''"})
1655 s = "'%s'" % s.translate(strans)
1656 mtrans = str.maketrans({'$': '$$'})
1657 return s.translate(mtrans)
1660 def shell_make_quote_list(l, translate_make):
1661 """Given a list of strings not containing newlines, quote them for use
1662 by the shell and make, returning a single string. If translate_make
1663 is true and the first string is 'make', change it to $(MAKE)."""
1664 l = [Command.shell_make_quote_string(s) for s in l]
1665 if translate_make and l[0] == 'make':
1669 def shell_make_quote(self):
1670 """Return this command quoted for the shell and make."""
1671 return self.shell_make_quote_list(self.command, True)
1674 class CommandList(object):
1675 """A list of commands run in the build process."""
1677 def __init__(self, desc, keep):
1678 """Initialize a CommandList object."""
1685 def desc_txt(self, desc):
1686 """Return the description to use for a command."""
1687 return '%s %s' % (' '.join(self.desc), desc)
1689 def use_dir(self, dir):
1690 """Set the default directory for subsequent commands."""
1693 def use_path(self, path):
1694 """Set a directory to be prepended to the PATH for subsequent
1698 def push_subdesc(self, subdesc):
1699 """Set the default subdescription for subsequent commands (e.g., the
1700 name of a component being built, within the series of commands
1702 self.desc.append(subdesc)
1704 def pop_subdesc(self):
1705 """Pop a subdescription from the list of descriptions."""
1708 def create_use_dir(self, dir):
1709 """Remove and recreate a directory and use it for subsequent
1711 self.add_command_dir('rm', None, ['rm', '-rf', dir])
1712 self.add_command_dir('mkdir', None, ['mkdir', '-p', dir])
1715 def add_command_dir(self, desc, dir, command, always_run=False):
1716 """Add a command to run in a given directory."""
1717 cmd = Command(self.desc_txt(desc), len(self.cmdlist), dir, self.path,
1718 command, always_run)
1719 self.cmdlist.append(cmd)
1721 def add_command(self, desc, command, always_run=False):
1722 """Add a command to run in the default directory."""
1723 cmd = Command(self.desc_txt(desc), len(self.cmdlist), self.dir,
1724 self.path, command, always_run)
1725 self.cmdlist.append(cmd)
1727 def cleanup_dir(self, desc='cleanup', dir=None):
1728 """Clean up a build directory. If no directory is specified, the
1729 default directory is cleaned up and ceases to be the default
1734 if self.keep != 'all':
1735 self.add_command_dir(desc, None, ['rm', '-rf', dir],
1736 always_run=(self.keep == 'none'))
1738 def makefile_commands(self, wrapper, logsdir):
1739 """Return the sequence of commands in the form of text for a Makefile.
1740 The given wrapper script takes arguments: base of logs for
1741 previous command, or empty; base of logs for this command;
1742 description; directory; PATH addition; the command itself."""
1743 # prev_base is the base of the name for logs of the previous
1744 # command that is not always-run (that is, a build command,
1745 # whose failure should stop subsequent build commands from
1746 # being run, as opposed to a cleanup command, which is run
1747 # even if previous commands failed).
1750 for c in self.cmdlist:
1751 ctxt = c.shell_make_quote()
1752 if prev_base and not c.always_run:
1753 prev_log = os.path.join(logsdir, prev_base)
1756 this_log = os.path.join(logsdir, c.logbase)
1757 if not c.always_run:
1758 prev_base = c.logbase
1767 prelims = [wrapper, prev_log, this_log, c.desc, dir, path]
1768 prelim_txt = Command.shell_make_quote_list(prelims, False)
1769 cmds.append('\t@%s %s' % (prelim_txt, ctxt))
1770 return '\n'.join(cmds)
1772 def status_logs(self, logsdir):
1773 """Return the list of log files with command status."""
1774 return [os.path.join(logsdir, '%s-status.txt' % c.logbase)
1775 for c in self.cmdlist]
1779 """Return an argument parser for this module."""
1780 parser = argparse.ArgumentParser(description=__doc__)
1781 parser.add_argument('-j', dest='parallelism',
1782 help='Run this number of jobs in parallel',
1783 type=int, default=os.cpu_count())
1784 parser.add_argument('--keep', dest='keep',
1785 help='Whether to keep all build directories, '
1786 'none or only those from failed builds',
1787 default='none', choices=('none', 'all', 'failed'))
1788 parser.add_argument('--replace-sources', action='store_true',
1789 help='Remove and replace source directories '
1790 'with the wrong version of a component')
1791 parser.add_argument('--strip', action='store_true',
1792 help='Strip installed glibc libraries')
1793 parser.add_argument('--full-gcc', action='store_true',
1794 help='Build GCC with all languages and libsanitizer')
1795 parser.add_argument('--shallow', action='store_true',
1796 help='Do not download Git history during checkout')
1797 parser.add_argument('topdir',
1798 help='Toplevel working directory')
1799 parser.add_argument('action',
1801 choices=('checkout', 'bot-cycle', 'bot',
1802 'host-libraries', 'compilers', 'glibcs',
1803 'update-syscalls', 'list-compilers',
1805 parser.add_argument('configs',
1806 help='Versions to check out or configurations to build',
1812 """The main entry point."""
1813 parser = get_parser()
1814 opts = parser.parse_args(argv)
1815 topdir = os.path.abspath(opts.topdir)
1816 ctx = Context(topdir, opts.parallelism, opts.keep, opts.replace_sources,
1817 opts.strip, opts.full_gcc, opts.action,
1818 shallow=opts.shallow)
1819 ctx.run_builds(opts.action, opts.configs)
1822 if __name__ == '__main__':