Remove finite-math tests
[glibc.git] / scripts / build-many-glibcs.py
blob4b3c4bfb76eba40c6b4bdd18a3c8f202a557e9b7
1 #!/usr/bin/python3
2 # Build many configurations of glibc.
3 # Copyright (C) 2016-2019 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 """
37 import argparse
38 import datetime
39 import email.mime.text
40 import email.utils
41 import json
42 import os
43 import re
44 import shutil
45 import smtplib
46 import stat
47 import subprocess
48 import sys
49 import time
50 import urllib.request
52 try:
53 subprocess.run
54 except:
55 class _CompletedProcess:
56 def __init__(self, args, returncode, stdout=None, stderr=None):
57 self.args = args
58 self.returncode = returncode
59 self.stdout = stdout
60 self.stderr = stderr
62 def _run(*popenargs, input=None, timeout=None, check=False, **kwargs):
63 assert(timeout is None)
64 with subprocess.Popen(*popenargs, **kwargs) as process:
65 try:
66 stdout, stderr = process.communicate(input)
67 except:
68 process.kill()
69 process.wait()
70 raise
71 returncode = process.poll()
72 if check and returncode:
73 raise subprocess.CalledProcessError(returncode, popenargs)
74 return _CompletedProcess(popenargs, returncode, stdout, stderr)
76 subprocess.run = _run
79 class Context(object):
80 """The global state associated with builds in a given directory."""
82 def __init__(self, topdir, parallelism, keep, replace_sources, strip,
83 full_gcc, action):
84 """Initialize the context."""
85 self.topdir = topdir
86 self.parallelism = parallelism
87 self.keep = keep
88 self.replace_sources = replace_sources
89 self.strip = strip
90 self.full_gcc = full_gcc
91 self.srcdir = os.path.join(topdir, 'src')
92 self.versions_json = os.path.join(self.srcdir, 'versions.json')
93 self.build_state_json = os.path.join(topdir, 'build-state.json')
94 self.bot_config_json = os.path.join(topdir, 'bot-config.json')
95 self.installdir = os.path.join(topdir, 'install')
96 self.host_libraries_installdir = os.path.join(self.installdir,
97 'host-libraries')
98 self.builddir = os.path.join(topdir, 'build')
99 self.logsdir = os.path.join(topdir, 'logs')
100 self.logsdir_old = os.path.join(topdir, 'logs-old')
101 self.makefile = os.path.join(self.builddir, 'Makefile')
102 self.wrapper = os.path.join(self.builddir, 'wrapper')
103 self.save_logs = os.path.join(self.builddir, 'save-logs')
104 self.script_text = self.get_script_text()
105 if action != 'checkout':
106 self.build_triplet = self.get_build_triplet()
107 self.glibc_version = self.get_glibc_version()
108 self.configs = {}
109 self.glibc_configs = {}
110 self.makefile_pieces = ['.PHONY: all\n']
111 self.add_all_configs()
112 self.load_versions_json()
113 self.load_build_state_json()
114 self.status_log_list = []
115 self.email_warning = False
117 def get_script_text(self):
118 """Return the text of this script."""
119 with open(sys.argv[0], 'r') as f:
120 return f.read()
122 def exec_self(self):
123 """Re-execute this script with the same arguments."""
124 sys.stdout.flush()
125 os.execv(sys.executable, [sys.executable] + sys.argv)
127 def get_build_triplet(self):
128 """Determine the build triplet with config.guess."""
129 config_guess = os.path.join(self.component_srcdir('gcc'),
130 'config.guess')
131 cg_out = subprocess.run([config_guess], stdout=subprocess.PIPE,
132 check=True, universal_newlines=True).stdout
133 return cg_out.rstrip()
135 def get_glibc_version(self):
136 """Determine the glibc version number (major.minor)."""
137 version_h = os.path.join(self.component_srcdir('glibc'), 'version.h')
138 with open(version_h, 'r') as f:
139 lines = f.readlines()
140 starttext = '#define VERSION "'
141 for l in lines:
142 if l.startswith(starttext):
143 l = l[len(starttext):]
144 l = l.rstrip('"\n')
145 m = re.fullmatch('([0-9]+)\.([0-9]+)[.0-9]*', l)
146 return '%s.%s' % m.group(1, 2)
147 print('error: could not determine glibc version')
148 exit(1)
150 def add_all_configs(self):
151 """Add all known glibc build configurations."""
152 self.add_config(arch='aarch64',
153 os_name='linux-gnu',
154 extra_glibcs=[{'variant': 'disable-multi-arch',
155 'cfg': ['--disable-multi-arch']}])
156 self.add_config(arch='aarch64_be',
157 os_name='linux-gnu')
158 self.add_config(arch='alpha',
159 os_name='linux-gnu')
160 self.add_config(arch='arm',
161 os_name='linux-gnueabi',
162 extra_glibcs=[{'variant': 'v4t',
163 'ccopts': '-march=armv4t'}])
164 self.add_config(arch='armeb',
165 os_name='linux-gnueabi')
166 self.add_config(arch='armeb',
167 os_name='linux-gnueabi',
168 variant='be8',
169 gcc_cfg=['--with-arch=armv7-a'])
170 self.add_config(arch='arm',
171 os_name='linux-gnueabihf',
172 gcc_cfg=['--with-float=hard', '--with-cpu=arm926ej-s'],
173 extra_glibcs=[{'variant': 'v7a',
174 'ccopts': '-march=armv7-a -mfpu=vfpv3'},
175 {'variant': 'v7a-disable-multi-arch',
176 'ccopts': '-march=armv7-a -mfpu=vfpv3',
177 'cfg': ['--disable-multi-arch']}])
178 self.add_config(arch='armeb',
179 os_name='linux-gnueabihf',
180 gcc_cfg=['--with-float=hard', '--with-cpu=arm926ej-s'])
181 self.add_config(arch='armeb',
182 os_name='linux-gnueabihf',
183 variant='be8',
184 gcc_cfg=['--with-float=hard', '--with-arch=armv7-a',
185 '--with-fpu=vfpv3'])
186 self.add_config(arch='csky',
187 os_name='linux-gnuabiv2',
188 variant='soft',
189 gcc_cfg=['--disable-multilib'])
190 self.add_config(arch='csky',
191 os_name='linux-gnuabiv2',
192 gcc_cfg=['--with-float=hard', '--disable-multilib'])
193 self.add_config(arch='hppa',
194 os_name='linux-gnu')
195 self.add_config(arch='i686',
196 os_name='gnu')
197 self.add_config(arch='ia64',
198 os_name='linux-gnu',
199 first_gcc_cfg=['--with-system-libunwind'])
200 self.add_config(arch='m68k',
201 os_name='linux-gnu',
202 gcc_cfg=['--disable-multilib'])
203 self.add_config(arch='m68k',
204 os_name='linux-gnu',
205 variant='coldfire',
206 gcc_cfg=['--with-arch=cf', '--disable-multilib'])
207 self.add_config(arch='m68k',
208 os_name='linux-gnu',
209 variant='coldfire-soft',
210 gcc_cfg=['--with-arch=cf', '--with-cpu=54455',
211 '--disable-multilib'])
212 self.add_config(arch='microblaze',
213 os_name='linux-gnu',
214 gcc_cfg=['--disable-multilib'])
215 self.add_config(arch='microblazeel',
216 os_name='linux-gnu',
217 gcc_cfg=['--disable-multilib'])
218 self.add_config(arch='mips64',
219 os_name='linux-gnu',
220 gcc_cfg=['--with-mips-plt'],
221 glibcs=[{'variant': 'n32'},
222 {'arch': 'mips',
223 'ccopts': '-mabi=32'},
224 {'variant': 'n64',
225 'ccopts': '-mabi=64'}])
226 self.add_config(arch='mips64',
227 os_name='linux-gnu',
228 variant='soft',
229 gcc_cfg=['--with-mips-plt', '--with-float=soft'],
230 glibcs=[{'variant': 'n32-soft'},
231 {'variant': 'soft',
232 'arch': 'mips',
233 'ccopts': '-mabi=32'},
234 {'variant': 'n64-soft',
235 'ccopts': '-mabi=64'}])
236 self.add_config(arch='mips64',
237 os_name='linux-gnu',
238 variant='nan2008',
239 gcc_cfg=['--with-mips-plt', '--with-nan=2008',
240 '--with-arch-64=mips64r2',
241 '--with-arch-32=mips32r2'],
242 glibcs=[{'variant': 'n32-nan2008'},
243 {'variant': 'nan2008',
244 'arch': 'mips',
245 'ccopts': '-mabi=32'},
246 {'variant': 'n64-nan2008',
247 'ccopts': '-mabi=64'}])
248 self.add_config(arch='mips64',
249 os_name='linux-gnu',
250 variant='nan2008-soft',
251 gcc_cfg=['--with-mips-plt', '--with-nan=2008',
252 '--with-arch-64=mips64r2',
253 '--with-arch-32=mips32r2',
254 '--with-float=soft'],
255 glibcs=[{'variant': 'n32-nan2008-soft'},
256 {'variant': 'nan2008-soft',
257 'arch': 'mips',
258 'ccopts': '-mabi=32'},
259 {'variant': 'n64-nan2008-soft',
260 'ccopts': '-mabi=64'}])
261 self.add_config(arch='mips64el',
262 os_name='linux-gnu',
263 gcc_cfg=['--with-mips-plt'],
264 glibcs=[{'variant': 'n32'},
265 {'arch': 'mipsel',
266 'ccopts': '-mabi=32'},
267 {'variant': 'n64',
268 'ccopts': '-mabi=64'}])
269 self.add_config(arch='mips64el',
270 os_name='linux-gnu',
271 variant='soft',
272 gcc_cfg=['--with-mips-plt', '--with-float=soft'],
273 glibcs=[{'variant': 'n32-soft'},
274 {'variant': 'soft',
275 'arch': 'mipsel',
276 'ccopts': '-mabi=32'},
277 {'variant': 'n64-soft',
278 'ccopts': '-mabi=64'}])
279 self.add_config(arch='mips64el',
280 os_name='linux-gnu',
281 variant='nan2008',
282 gcc_cfg=['--with-mips-plt', '--with-nan=2008',
283 '--with-arch-64=mips64r2',
284 '--with-arch-32=mips32r2'],
285 glibcs=[{'variant': 'n32-nan2008'},
286 {'variant': 'nan2008',
287 'arch': 'mipsel',
288 'ccopts': '-mabi=32'},
289 {'variant': 'n64-nan2008',
290 'ccopts': '-mabi=64'}])
291 self.add_config(arch='mips64el',
292 os_name='linux-gnu',
293 variant='nan2008-soft',
294 gcc_cfg=['--with-mips-plt', '--with-nan=2008',
295 '--with-arch-64=mips64r2',
296 '--with-arch-32=mips32r2',
297 '--with-float=soft'],
298 glibcs=[{'variant': 'n32-nan2008-soft'},
299 {'variant': 'nan2008-soft',
300 'arch': 'mipsel',
301 'ccopts': '-mabi=32'},
302 {'variant': 'n64-nan2008-soft',
303 'ccopts': '-mabi=64'}])
304 self.add_config(arch='nios2',
305 os_name='linux-gnu')
306 self.add_config(arch='powerpc',
307 os_name='linux-gnu',
308 gcc_cfg=['--disable-multilib', '--enable-secureplt'],
309 extra_glibcs=[{'variant': 'power4',
310 'ccopts': '-mcpu=power4',
311 'cfg': ['--with-cpu=power4']}])
312 self.add_config(arch='powerpc',
313 os_name='linux-gnu',
314 variant='soft',
315 gcc_cfg=['--disable-multilib', '--with-float=soft',
316 '--enable-secureplt'])
317 self.add_config(arch='powerpc64',
318 os_name='linux-gnu',
319 gcc_cfg=['--disable-multilib', '--enable-secureplt'])
320 self.add_config(arch='powerpc64le',
321 os_name='linux-gnu',
322 gcc_cfg=['--disable-multilib', '--enable-secureplt'])
323 self.add_config(arch='riscv64',
324 os_name='linux-gnu',
325 variant='rv64imac-lp64',
326 gcc_cfg=['--with-arch=rv64imac', '--with-abi=lp64',
327 '--disable-multilib'])
328 self.add_config(arch='riscv64',
329 os_name='linux-gnu',
330 variant='rv64imafdc-lp64',
331 gcc_cfg=['--with-arch=rv64imafdc', '--with-abi=lp64',
332 '--disable-multilib'])
333 self.add_config(arch='riscv64',
334 os_name='linux-gnu',
335 variant='rv64imafdc-lp64d',
336 gcc_cfg=['--with-arch=rv64imafdc', '--with-abi=lp64d',
337 '--disable-multilib'])
338 self.add_config(arch='s390x',
339 os_name='linux-gnu',
340 glibcs=[{},
341 {'arch': 's390', 'ccopts': '-m31'}])
342 self.add_config(arch='sh3',
343 os_name='linux-gnu')
344 self.add_config(arch='sh3eb',
345 os_name='linux-gnu')
346 self.add_config(arch='sh4',
347 os_name='linux-gnu')
348 self.add_config(arch='sh4eb',
349 os_name='linux-gnu')
350 self.add_config(arch='sh4',
351 os_name='linux-gnu',
352 variant='soft',
353 gcc_cfg=['--without-fp'])
354 self.add_config(arch='sh4eb',
355 os_name='linux-gnu',
356 variant='soft',
357 gcc_cfg=['--without-fp'])
358 self.add_config(arch='sparc64',
359 os_name='linux-gnu',
360 glibcs=[{},
361 {'arch': 'sparcv9',
362 'ccopts': '-m32 -mlong-double-128'}],
363 extra_glibcs=[{'variant': 'disable-multi-arch',
364 'cfg': ['--disable-multi-arch']},
365 {'variant': 'disable-multi-arch',
366 'arch': 'sparcv9',
367 'ccopts': '-m32 -mlong-double-128',
368 'cfg': ['--disable-multi-arch']}])
369 self.add_config(arch='x86_64',
370 os_name='linux-gnu',
371 gcc_cfg=['--with-multilib-list=m64,m32,mx32'],
372 glibcs=[{},
373 {'variant': 'x32', 'ccopts': '-mx32'},
374 {'arch': 'i686', 'ccopts': '-m32 -march=i686'}],
375 extra_glibcs=[{'variant': 'disable-multi-arch',
376 'cfg': ['--disable-multi-arch']},
377 {'variant': 'enable-obsolete',
378 'cfg': ['--enable-obsolete-rpc',
379 '--enable-obsolete-nsl']},
380 {'variant': 'static-pie',
381 'cfg': ['--enable-static-pie']},
382 {'variant': 'x32-static-pie',
383 'ccopts': '-mx32',
384 'cfg': ['--enable-static-pie']},
385 {'variant': 'static-pie',
386 'arch': 'i686',
387 'ccopts': '-m32 -march=i686',
388 'cfg': ['--enable-static-pie']},
389 {'variant': 'disable-multi-arch',
390 'arch': 'i686',
391 'ccopts': '-m32 -march=i686',
392 'cfg': ['--disable-multi-arch']},
393 {'variant': 'enable-obsolete',
394 'arch': 'i686',
395 'ccopts': '-m32 -march=i686',
396 'cfg': ['--enable-obsolete-rpc',
397 '--enable-obsolete-nsl']},
398 {'arch': 'i486',
399 'ccopts': '-m32 -march=i486'},
400 {'arch': 'i586',
401 'ccopts': '-m32 -march=i586'}])
403 def add_config(self, **args):
404 """Add an individual build configuration."""
405 cfg = Config(self, **args)
406 if cfg.name in self.configs:
407 print('error: duplicate config %s' % cfg.name)
408 exit(1)
409 self.configs[cfg.name] = cfg
410 for c in cfg.all_glibcs:
411 if c.name in self.glibc_configs:
412 print('error: duplicate glibc config %s' % c.name)
413 exit(1)
414 self.glibc_configs[c.name] = c
416 def component_srcdir(self, component):
417 """Return the source directory for a given component, e.g. gcc."""
418 return os.path.join(self.srcdir, component)
420 def component_builddir(self, action, config, component, subconfig=None):
421 """Return the directory to use for a build."""
422 if config is None:
423 # Host libraries.
424 assert subconfig is None
425 return os.path.join(self.builddir, action, component)
426 if subconfig is None:
427 return os.path.join(self.builddir, action, config, component)
428 else:
429 # glibc build as part of compiler build.
430 return os.path.join(self.builddir, action, config, component,
431 subconfig)
433 def compiler_installdir(self, config):
434 """Return the directory in which to install a compiler."""
435 return os.path.join(self.installdir, 'compilers', config)
437 def compiler_bindir(self, config):
438 """Return the directory in which to find compiler binaries."""
439 return os.path.join(self.compiler_installdir(config), 'bin')
441 def compiler_sysroot(self, config):
442 """Return the sysroot directory for a compiler."""
443 return os.path.join(self.compiler_installdir(config), 'sysroot')
445 def glibc_installdir(self, config):
446 """Return the directory in which to install glibc."""
447 return os.path.join(self.installdir, 'glibcs', config)
449 def run_builds(self, action, configs):
450 """Run the requested builds."""
451 if action == 'checkout':
452 self.checkout(configs)
453 return
454 if action == 'bot-cycle':
455 if configs:
456 print('error: configurations specified for bot-cycle')
457 exit(1)
458 self.bot_cycle()
459 return
460 if action == 'bot':
461 if configs:
462 print('error: configurations specified for bot')
463 exit(1)
464 self.bot()
465 return
466 if action == 'host-libraries' and configs:
467 print('error: configurations specified for host-libraries')
468 exit(1)
469 self.clear_last_build_state(action)
470 build_time = datetime.datetime.utcnow()
471 if action == 'host-libraries':
472 build_components = ('gmp', 'mpfr', 'mpc')
473 old_components = ()
474 old_versions = {}
475 self.build_host_libraries()
476 elif action == 'compilers':
477 build_components = ('binutils', 'gcc', 'glibc', 'linux', 'mig',
478 'gnumach', 'hurd')
479 old_components = ('gmp', 'mpfr', 'mpc')
480 old_versions = self.build_state['host-libraries']['build-versions']
481 self.build_compilers(configs)
482 else:
483 build_components = ('glibc',)
484 old_components = ('gmp', 'mpfr', 'mpc', 'binutils', 'gcc', 'linux',
485 'mig', 'gnumach', 'hurd')
486 old_versions = self.build_state['compilers']['build-versions']
487 self.build_glibcs(configs)
488 self.write_files()
489 self.do_build()
490 if configs:
491 # Partial build, do not update stored state.
492 return
493 build_versions = {}
494 for k in build_components:
495 if k in self.versions:
496 build_versions[k] = {'version': self.versions[k]['version'],
497 'revision': self.versions[k]['revision']}
498 for k in old_components:
499 if k in old_versions:
500 build_versions[k] = {'version': old_versions[k]['version'],
501 'revision': old_versions[k]['revision']}
502 self.update_build_state(action, build_time, build_versions)
504 @staticmethod
505 def remove_dirs(*args):
506 """Remove directories and their contents if they exist."""
507 for dir in args:
508 shutil.rmtree(dir, ignore_errors=True)
510 @staticmethod
511 def remove_recreate_dirs(*args):
512 """Remove directories if they exist, and create them as empty."""
513 Context.remove_dirs(*args)
514 for dir in args:
515 os.makedirs(dir, exist_ok=True)
517 def add_makefile_cmdlist(self, target, cmdlist, logsdir):
518 """Add makefile text for a list of commands."""
519 commands = cmdlist.makefile_commands(self.wrapper, logsdir)
520 self.makefile_pieces.append('all: %s\n.PHONY: %s\n%s:\n%s\n' %
521 (target, target, target, commands))
522 self.status_log_list.extend(cmdlist.status_logs(logsdir))
524 def write_files(self):
525 """Write out the Makefile and wrapper script."""
526 mftext = ''.join(self.makefile_pieces)
527 with open(self.makefile, 'w') as f:
528 f.write(mftext)
529 wrapper_text = (
530 '#!/bin/sh\n'
531 'prev_base=$1\n'
532 'this_base=$2\n'
533 'desc=$3\n'
534 'dir=$4\n'
535 'path=$5\n'
536 'shift 5\n'
537 'prev_status=$prev_base-status.txt\n'
538 'this_status=$this_base-status.txt\n'
539 'this_log=$this_base-log.txt\n'
540 'date > "$this_log"\n'
541 'echo >> "$this_log"\n'
542 'echo "Description: $desc" >> "$this_log"\n'
543 'printf "%s" "Command:" >> "$this_log"\n'
544 'for word in "$@"; do\n'
545 ' if expr "$word" : "[]+,./0-9@A-Z_a-z-]\\\\{1,\\\\}\\$" > /dev/null; then\n'
546 ' printf " %s" "$word"\n'
547 ' else\n'
548 ' printf " \'"\n'
549 ' printf "%s" "$word" | sed -e "s/\'/\'\\\\\\\\\'\'/"\n'
550 ' printf "\'"\n'
551 ' fi\n'
552 'done >> "$this_log"\n'
553 'echo >> "$this_log"\n'
554 'echo "Directory: $dir" >> "$this_log"\n'
555 'echo "Path addition: $path" >> "$this_log"\n'
556 'echo >> "$this_log"\n'
557 'record_status ()\n'
558 '{\n'
559 ' echo >> "$this_log"\n'
560 ' echo "$1: $desc" > "$this_status"\n'
561 ' echo "$1: $desc" >> "$this_log"\n'
562 ' echo >> "$this_log"\n'
563 ' date >> "$this_log"\n'
564 ' echo "$1: $desc"\n'
565 ' exit 0\n'
566 '}\n'
567 'check_error ()\n'
568 '{\n'
569 ' if [ "$1" != "0" ]; then\n'
570 ' record_status FAIL\n'
571 ' fi\n'
572 '}\n'
573 'if [ "$prev_base" ] && ! grep -q "^PASS" "$prev_status"; then\n'
574 ' record_status UNRESOLVED\n'
575 'fi\n'
576 'if [ "$dir" ]; then\n'
577 ' cd "$dir"\n'
578 ' check_error "$?"\n'
579 'fi\n'
580 'if [ "$path" ]; then\n'
581 ' PATH=$path:$PATH\n'
582 'fi\n'
583 '"$@" < /dev/null >> "$this_log" 2>&1\n'
584 'check_error "$?"\n'
585 'record_status PASS\n')
586 with open(self.wrapper, 'w') as f:
587 f.write(wrapper_text)
588 # Mode 0o755.
589 mode_exec = (stat.S_IRWXU|stat.S_IRGRP|stat.S_IXGRP|
590 stat.S_IROTH|stat.S_IXOTH)
591 os.chmod(self.wrapper, mode_exec)
592 save_logs_text = (
593 '#!/bin/sh\n'
594 'if ! [ -f tests.sum ]; then\n'
595 ' echo "No test summary available."\n'
596 ' exit 0\n'
597 'fi\n'
598 'save_file ()\n'
599 '{\n'
600 ' echo "Contents of $1:"\n'
601 ' echo\n'
602 ' cat "$1"\n'
603 ' echo\n'
604 ' echo "End of contents of $1."\n'
605 ' echo\n'
606 '}\n'
607 'save_file tests.sum\n'
608 'non_pass_tests=$(grep -v "^PASS: " tests.sum | sed -e "s/^PASS: //")\n'
609 'for t in $non_pass_tests; do\n'
610 ' if [ -f "$t.out" ]; then\n'
611 ' save_file "$t.out"\n'
612 ' fi\n'
613 'done\n')
614 with open(self.save_logs, 'w') as f:
615 f.write(save_logs_text)
616 os.chmod(self.save_logs, mode_exec)
618 def do_build(self):
619 """Do the actual build."""
620 cmd = ['make', '-j%d' % self.parallelism]
621 subprocess.run(cmd, cwd=self.builddir, check=True)
623 def build_host_libraries(self):
624 """Build the host libraries."""
625 installdir = self.host_libraries_installdir
626 builddir = os.path.join(self.builddir, 'host-libraries')
627 logsdir = os.path.join(self.logsdir, 'host-libraries')
628 self.remove_recreate_dirs(installdir, builddir, logsdir)
629 cmdlist = CommandList('host-libraries', self.keep)
630 self.build_host_library(cmdlist, 'gmp')
631 self.build_host_library(cmdlist, 'mpfr',
632 ['--with-gmp=%s' % installdir])
633 self.build_host_library(cmdlist, 'mpc',
634 ['--with-gmp=%s' % installdir,
635 '--with-mpfr=%s' % installdir])
636 cmdlist.add_command('done', ['touch', os.path.join(installdir, 'ok')])
637 self.add_makefile_cmdlist('host-libraries', cmdlist, logsdir)
639 def build_host_library(self, cmdlist, lib, extra_opts=None):
640 """Build one host library."""
641 srcdir = self.component_srcdir(lib)
642 builddir = self.component_builddir('host-libraries', None, lib)
643 installdir = self.host_libraries_installdir
644 cmdlist.push_subdesc(lib)
645 cmdlist.create_use_dir(builddir)
646 cfg_cmd = [os.path.join(srcdir, 'configure'),
647 '--prefix=%s' % installdir,
648 '--disable-shared']
649 if extra_opts:
650 cfg_cmd.extend (extra_opts)
651 cmdlist.add_command('configure', cfg_cmd)
652 cmdlist.add_command('build', ['make'])
653 cmdlist.add_command('check', ['make', 'check'])
654 cmdlist.add_command('install', ['make', 'install'])
655 cmdlist.cleanup_dir()
656 cmdlist.pop_subdesc()
658 def build_compilers(self, configs):
659 """Build the compilers."""
660 if not configs:
661 self.remove_dirs(os.path.join(self.builddir, 'compilers'))
662 self.remove_dirs(os.path.join(self.installdir, 'compilers'))
663 self.remove_dirs(os.path.join(self.logsdir, 'compilers'))
664 configs = sorted(self.configs.keys())
665 for c in configs:
666 self.configs[c].build()
668 def build_glibcs(self, configs):
669 """Build the glibcs."""
670 if not configs:
671 self.remove_dirs(os.path.join(self.builddir, 'glibcs'))
672 self.remove_dirs(os.path.join(self.installdir, 'glibcs'))
673 self.remove_dirs(os.path.join(self.logsdir, 'glibcs'))
674 configs = sorted(self.glibc_configs.keys())
675 for c in configs:
676 self.glibc_configs[c].build()
678 def load_versions_json(self):
679 """Load information about source directory versions."""
680 if not os.access(self.versions_json, os.F_OK):
681 self.versions = {}
682 return
683 with open(self.versions_json, 'r') as f:
684 self.versions = json.load(f)
686 def store_json(self, data, filename):
687 """Store information in a JSON file."""
688 filename_tmp = filename + '.tmp'
689 with open(filename_tmp, 'w') as f:
690 json.dump(data, f, indent=2, sort_keys=True)
691 os.rename(filename_tmp, filename)
693 def store_versions_json(self):
694 """Store information about source directory versions."""
695 self.store_json(self.versions, self.versions_json)
697 def set_component_version(self, component, version, explicit, revision):
698 """Set the version information for a component."""
699 self.versions[component] = {'version': version,
700 'explicit': explicit,
701 'revision': revision}
702 self.store_versions_json()
704 def checkout(self, versions):
705 """Check out the desired component versions."""
706 default_versions = {'binutils': 'vcs-2.33',
707 'gcc': 'vcs-9',
708 'glibc': 'vcs-mainline',
709 'gmp': '6.1.2',
710 'linux': '5.3',
711 'mpc': '1.1.0',
712 'mpfr': '4.0.2',
713 'mig': 'vcs-mainline',
714 'gnumach': 'vcs-mainline',
715 'hurd': 'vcs-mainline'}
716 use_versions = {}
717 explicit_versions = {}
718 for v in versions:
719 found_v = False
720 for k in default_versions.keys():
721 kx = k + '-'
722 if v.startswith(kx):
723 vx = v[len(kx):]
724 if k in use_versions:
725 print('error: multiple versions for %s' % k)
726 exit(1)
727 use_versions[k] = vx
728 explicit_versions[k] = True
729 found_v = True
730 break
731 if not found_v:
732 print('error: unknown component in %s' % v)
733 exit(1)
734 for k in default_versions.keys():
735 if k not in use_versions:
736 if k in self.versions and self.versions[k]['explicit']:
737 use_versions[k] = self.versions[k]['version']
738 explicit_versions[k] = True
739 else:
740 use_versions[k] = default_versions[k]
741 explicit_versions[k] = False
742 os.makedirs(self.srcdir, exist_ok=True)
743 for k in sorted(default_versions.keys()):
744 update = os.access(self.component_srcdir(k), os.F_OK)
745 v = use_versions[k]
746 if (update and
747 k in self.versions and
748 v != self.versions[k]['version']):
749 if not self.replace_sources:
750 print('error: version of %s has changed from %s to %s, '
751 'use --replace-sources to check out again' %
752 (k, self.versions[k]['version'], v))
753 exit(1)
754 shutil.rmtree(self.component_srcdir(k))
755 update = False
756 if v.startswith('vcs-'):
757 revision = self.checkout_vcs(k, v[4:], update)
758 else:
759 self.checkout_tar(k, v, update)
760 revision = v
761 self.set_component_version(k, v, explicit_versions[k], revision)
762 if self.get_script_text() != self.script_text:
763 # Rerun the checkout process in case the updated script
764 # uses different default versions or new components.
765 self.exec_self()
767 def checkout_vcs(self, component, version, update):
768 """Check out the given version of the given component from version
769 control. Return a revision identifier."""
770 if component == 'binutils':
771 git_url = 'git://sourceware.org/git/binutils-gdb.git'
772 if version == 'mainline':
773 git_branch = 'master'
774 else:
775 trans = str.maketrans({'.': '_'})
776 git_branch = 'binutils-%s-branch' % version.translate(trans)
777 return self.git_checkout(component, git_url, git_branch, update)
778 elif component == 'gcc':
779 if version == 'mainline':
780 branch = 'trunk'
781 else:
782 trans = str.maketrans({'.': '_'})
783 branch = 'branches/gcc-%s-branch' % version.translate(trans)
784 svn_url = 'svn://gcc.gnu.org/svn/gcc/%s' % branch
785 return self.gcc_checkout(svn_url, update)
786 elif component == 'glibc':
787 git_url = 'git://sourceware.org/git/glibc.git'
788 if version == 'mainline':
789 git_branch = 'master'
790 else:
791 git_branch = 'release/%s/master' % version
792 r = self.git_checkout(component, git_url, git_branch, update)
793 self.fix_glibc_timestamps()
794 return r
795 elif component == 'gnumach':
796 git_url = 'git://git.savannah.gnu.org/hurd/gnumach.git'
797 git_branch = 'master'
798 r = self.git_checkout(component, git_url, git_branch, update)
799 subprocess.run(['autoreconf', '-i'],
800 cwd=self.component_srcdir(component), check=True)
801 return r
802 elif component == 'mig':
803 git_url = 'git://git.savannah.gnu.org/hurd/mig.git'
804 git_branch = 'master'
805 r = self.git_checkout(component, git_url, git_branch, update)
806 subprocess.run(['autoreconf', '-i'],
807 cwd=self.component_srcdir(component), check=True)
808 return r
809 elif component == 'hurd':
810 git_url = 'git://git.savannah.gnu.org/hurd/hurd.git'
811 git_branch = 'master'
812 r = self.git_checkout(component, git_url, git_branch, update)
813 subprocess.run(['autoconf'],
814 cwd=self.component_srcdir(component), check=True)
815 return r
816 else:
817 print('error: component %s coming from VCS' % component)
818 exit(1)
820 def git_checkout(self, component, git_url, git_branch, update):
821 """Check out a component from git. Return a commit identifier."""
822 if update:
823 subprocess.run(['git', 'remote', 'prune', 'origin'],
824 cwd=self.component_srcdir(component), check=True)
825 if self.replace_sources:
826 subprocess.run(['git', 'clean', '-dxfq'],
827 cwd=self.component_srcdir(component), check=True)
828 subprocess.run(['git', 'pull', '-q'],
829 cwd=self.component_srcdir(component), check=True)
830 else:
831 subprocess.run(['git', 'clone', '-q', '-b', git_branch, git_url,
832 self.component_srcdir(component)], check=True)
833 r = subprocess.run(['git', 'rev-parse', 'HEAD'],
834 cwd=self.component_srcdir(component),
835 stdout=subprocess.PIPE,
836 check=True, universal_newlines=True).stdout
837 return r.rstrip()
839 def fix_glibc_timestamps(self):
840 """Fix timestamps in a glibc checkout."""
841 # Ensure that builds do not try to regenerate generated files
842 # in the source tree.
843 srcdir = self.component_srcdir('glibc')
844 # These files have Makefile dependencies to regenerate them in
845 # the source tree that may be active during a normal build.
846 # Some other files have such dependencies but do not need to
847 # be touched because nothing in a build depends on the files
848 # in question.
849 for f in ('sysdeps/gnu/errlist.c',
850 'sysdeps/mach/hurd/bits/errno.h',
851 'sysdeps/sparc/sparc32/rem.S',
852 'sysdeps/sparc/sparc32/sdiv.S',
853 'sysdeps/sparc/sparc32/udiv.S',
854 'sysdeps/sparc/sparc32/urem.S'):
855 to_touch = os.path.join(srcdir, f)
856 subprocess.run(['touch', '-c', to_touch], check=True)
857 for dirpath, dirnames, filenames in os.walk(srcdir):
858 for f in filenames:
859 if (f == 'configure' or
860 f == 'preconfigure' or
861 f.endswith('-kw.h')):
862 to_touch = os.path.join(dirpath, f)
863 subprocess.run(['touch', to_touch], check=True)
865 def gcc_checkout(self, svn_url, update):
866 """Check out GCC from SVN. Return the revision number."""
867 if not update:
868 subprocess.run(['svn', 'co', '-q', svn_url,
869 self.component_srcdir('gcc')], check=True)
870 subprocess.run(['contrib/gcc_update', '--silent'],
871 cwd=self.component_srcdir('gcc'), check=True)
872 r = subprocess.run(['svnversion', self.component_srcdir('gcc')],
873 stdout=subprocess.PIPE,
874 check=True, universal_newlines=True).stdout
875 return r.rstrip()
877 def checkout_tar(self, component, version, update):
878 """Check out the given version of the given component from a
879 tarball."""
880 if update:
881 return
882 url_map = {'binutils': 'https://ftp.gnu.org/gnu/binutils/binutils-%(version)s.tar.bz2',
883 'gcc': 'https://ftp.gnu.org/gnu/gcc/gcc-%(version)s/gcc-%(version)s.tar.gz',
884 'gmp': 'https://ftp.gnu.org/gnu/gmp/gmp-%(version)s.tar.xz',
885 'linux': 'https://www.kernel.org/pub/linux/kernel/v%(major)s.x/linux-%(version)s.tar.xz',
886 'mpc': 'https://ftp.gnu.org/gnu/mpc/mpc-%(version)s.tar.gz',
887 'mpfr': 'https://ftp.gnu.org/gnu/mpfr/mpfr-%(version)s.tar.xz',
888 'mig': 'https://ftp.gnu.org/gnu/mig/mig-%(version)s.tar.bz2',
889 'gnumach': 'https://ftp.gnu.org/gnu/gnumach/gnumach-%(version)s.tar.bz2',
890 'hurd': 'https://ftp.gnu.org/gnu/hurd/hurd-%(version)s.tar.bz2'}
891 if component not in url_map:
892 print('error: component %s coming from tarball' % component)
893 exit(1)
894 version_major = version.split('.')[0]
895 url = url_map[component] % {'version': version, 'major': version_major}
896 filename = os.path.join(self.srcdir, url.split('/')[-1])
897 response = urllib.request.urlopen(url)
898 data = response.read()
899 with open(filename, 'wb') as f:
900 f.write(data)
901 subprocess.run(['tar', '-C', self.srcdir, '-x', '-f', filename],
902 check=True)
903 os.rename(os.path.join(self.srcdir, '%s-%s' % (component, version)),
904 self.component_srcdir(component))
905 os.remove(filename)
907 def load_build_state_json(self):
908 """Load information about the state of previous builds."""
909 if os.access(self.build_state_json, os.F_OK):
910 with open(self.build_state_json, 'r') as f:
911 self.build_state = json.load(f)
912 else:
913 self.build_state = {}
914 for k in ('host-libraries', 'compilers', 'glibcs'):
915 if k not in self.build_state:
916 self.build_state[k] = {}
917 if 'build-time' not in self.build_state[k]:
918 self.build_state[k]['build-time'] = ''
919 if 'build-versions' not in self.build_state[k]:
920 self.build_state[k]['build-versions'] = {}
921 if 'build-results' not in self.build_state[k]:
922 self.build_state[k]['build-results'] = {}
923 if 'result-changes' not in self.build_state[k]:
924 self.build_state[k]['result-changes'] = {}
925 if 'ever-passed' not in self.build_state[k]:
926 self.build_state[k]['ever-passed'] = []
928 def store_build_state_json(self):
929 """Store information about the state of previous builds."""
930 self.store_json(self.build_state, self.build_state_json)
932 def clear_last_build_state(self, action):
933 """Clear information about the state of part of the build."""
934 # We clear the last build time and versions when starting a
935 # new build. The results of the last build are kept around,
936 # as comparison is still meaningful if this build is aborted
937 # and a new one started.
938 self.build_state[action]['build-time'] = ''
939 self.build_state[action]['build-versions'] = {}
940 self.store_build_state_json()
942 def update_build_state(self, action, build_time, build_versions):
943 """Update the build state after a build."""
944 build_time = build_time.replace(microsecond=0)
945 self.build_state[action]['build-time'] = str(build_time)
946 self.build_state[action]['build-versions'] = build_versions
947 build_results = {}
948 for log in self.status_log_list:
949 with open(log, 'r') as f:
950 log_text = f.read()
951 log_text = log_text.rstrip()
952 m = re.fullmatch('([A-Z]+): (.*)', log_text)
953 result = m.group(1)
954 test_name = m.group(2)
955 assert test_name not in build_results
956 build_results[test_name] = result
957 old_build_results = self.build_state[action]['build-results']
958 self.build_state[action]['build-results'] = build_results
959 result_changes = {}
960 all_tests = set(old_build_results.keys()) | set(build_results.keys())
961 for t in all_tests:
962 if t in old_build_results:
963 old_res = old_build_results[t]
964 else:
965 old_res = '(New test)'
966 if t in build_results:
967 new_res = build_results[t]
968 else:
969 new_res = '(Test removed)'
970 if old_res != new_res:
971 result_changes[t] = '%s -> %s' % (old_res, new_res)
972 self.build_state[action]['result-changes'] = result_changes
973 old_ever_passed = {t for t in self.build_state[action]['ever-passed']
974 if t in build_results}
975 new_passes = {t for t in build_results if build_results[t] == 'PASS'}
976 self.build_state[action]['ever-passed'] = sorted(old_ever_passed |
977 new_passes)
978 self.store_build_state_json()
980 def load_bot_config_json(self):
981 """Load bot configuration."""
982 with open(self.bot_config_json, 'r') as f:
983 self.bot_config = json.load(f)
985 def part_build_old(self, action, delay):
986 """Return whether the last build for a given action was at least a
987 given number of seconds ago, or does not have a time recorded."""
988 old_time_str = self.build_state[action]['build-time']
989 if not old_time_str:
990 return True
991 old_time = datetime.datetime.strptime(old_time_str,
992 '%Y-%m-%d %H:%M:%S')
993 new_time = datetime.datetime.utcnow()
994 delta = new_time - old_time
995 return delta.total_seconds() >= delay
997 def bot_cycle(self):
998 """Run a single round of checkout and builds."""
999 print('Bot cycle starting %s.' % str(datetime.datetime.utcnow()))
1000 self.load_bot_config_json()
1001 actions = ('host-libraries', 'compilers', 'glibcs')
1002 self.bot_run_self(['--replace-sources'], 'checkout')
1003 self.load_versions_json()
1004 if self.get_script_text() != self.script_text:
1005 print('Script changed, re-execing.')
1006 # On script change, all parts of the build should be rerun.
1007 for a in actions:
1008 self.clear_last_build_state(a)
1009 self.exec_self()
1010 check_components = {'host-libraries': ('gmp', 'mpfr', 'mpc'),
1011 'compilers': ('binutils', 'gcc', 'glibc', 'linux',
1012 'mig', 'gnumach', 'hurd'),
1013 'glibcs': ('glibc',)}
1014 must_build = {}
1015 for a in actions:
1016 build_vers = self.build_state[a]['build-versions']
1017 must_build[a] = False
1018 if not self.build_state[a]['build-time']:
1019 must_build[a] = True
1020 old_vers = {}
1021 new_vers = {}
1022 for c in check_components[a]:
1023 if c in build_vers:
1024 old_vers[c] = build_vers[c]
1025 new_vers[c] = {'version': self.versions[c]['version'],
1026 'revision': self.versions[c]['revision']}
1027 if new_vers == old_vers:
1028 print('Versions for %s unchanged.' % a)
1029 else:
1030 print('Versions changed or rebuild forced for %s.' % a)
1031 if a == 'compilers' and not self.part_build_old(
1032 a, self.bot_config['compilers-rebuild-delay']):
1033 print('Not requiring rebuild of compilers this soon.')
1034 else:
1035 must_build[a] = True
1036 if must_build['host-libraries']:
1037 must_build['compilers'] = True
1038 if must_build['compilers']:
1039 must_build['glibcs'] = True
1040 for a in actions:
1041 if must_build[a]:
1042 print('Must rebuild %s.' % a)
1043 self.clear_last_build_state(a)
1044 else:
1045 print('No need to rebuild %s.' % a)
1046 if os.access(self.logsdir, os.F_OK):
1047 shutil.rmtree(self.logsdir_old, ignore_errors=True)
1048 shutil.copytree(self.logsdir, self.logsdir_old)
1049 for a in actions:
1050 if must_build[a]:
1051 build_time = datetime.datetime.utcnow()
1052 print('Rebuilding %s at %s.' % (a, str(build_time)))
1053 self.bot_run_self([], a)
1054 self.load_build_state_json()
1055 self.bot_build_mail(a, build_time)
1056 print('Bot cycle done at %s.' % str(datetime.datetime.utcnow()))
1058 def bot_build_mail(self, action, build_time):
1059 """Send email with the results of a build."""
1060 if not ('email-from' in self.bot_config and
1061 'email-server' in self.bot_config and
1062 'email-subject' in self.bot_config and
1063 'email-to' in self.bot_config):
1064 if not self.email_warning:
1065 print("Email not configured, not sending.")
1066 self.email_warning = True
1067 return
1069 build_time = build_time.replace(microsecond=0)
1070 subject = (self.bot_config['email-subject'] %
1071 {'action': action,
1072 'build-time': str(build_time)})
1073 results = self.build_state[action]['build-results']
1074 changes = self.build_state[action]['result-changes']
1075 ever_passed = set(self.build_state[action]['ever-passed'])
1076 versions = self.build_state[action]['build-versions']
1077 new_regressions = {k for k in changes if changes[k] == 'PASS -> FAIL'}
1078 all_regressions = {k for k in ever_passed if results[k] == 'FAIL'}
1079 all_fails = {k for k in results if results[k] == 'FAIL'}
1080 if new_regressions:
1081 new_reg_list = sorted(['FAIL: %s' % k for k in new_regressions])
1082 new_reg_text = ('New regressions:\n\n%s\n\n' %
1083 '\n'.join(new_reg_list))
1084 else:
1085 new_reg_text = ''
1086 if all_regressions:
1087 all_reg_list = sorted(['FAIL: %s' % k for k in all_regressions])
1088 all_reg_text = ('All regressions:\n\n%s\n\n' %
1089 '\n'.join(all_reg_list))
1090 else:
1091 all_reg_text = ''
1092 if all_fails:
1093 all_fail_list = sorted(['FAIL: %s' % k for k in all_fails])
1094 all_fail_text = ('All failures:\n\n%s\n\n' %
1095 '\n'.join(all_fail_list))
1096 else:
1097 all_fail_text = ''
1098 if changes:
1099 changes_list = sorted(changes.keys())
1100 changes_list = ['%s: %s' % (changes[k], k) for k in changes_list]
1101 changes_text = ('All changed results:\n\n%s\n\n' %
1102 '\n'.join(changes_list))
1103 else:
1104 changes_text = ''
1105 results_text = (new_reg_text + all_reg_text + all_fail_text +
1106 changes_text)
1107 if not results_text:
1108 results_text = 'Clean build with unchanged results.\n\n'
1109 versions_list = sorted(versions.keys())
1110 versions_list = ['%s: %s (%s)' % (k, versions[k]['version'],
1111 versions[k]['revision'])
1112 for k in versions_list]
1113 versions_text = ('Component versions for this build:\n\n%s\n' %
1114 '\n'.join(versions_list))
1115 body_text = results_text + versions_text
1116 msg = email.mime.text.MIMEText(body_text)
1117 msg['Subject'] = subject
1118 msg['From'] = self.bot_config['email-from']
1119 msg['To'] = self.bot_config['email-to']
1120 msg['Message-ID'] = email.utils.make_msgid()
1121 msg['Date'] = email.utils.format_datetime(datetime.datetime.utcnow())
1122 with smtplib.SMTP(self.bot_config['email-server']) as s:
1123 s.send_message(msg)
1125 def bot_run_self(self, opts, action, check=True):
1126 """Run a copy of this script with given options."""
1127 cmd = [sys.executable, sys.argv[0], '--keep=none',
1128 '-j%d' % self.parallelism]
1129 if self.full_gcc:
1130 cmd.append('--full-gcc')
1131 cmd.extend(opts)
1132 cmd.extend([self.topdir, action])
1133 sys.stdout.flush()
1134 subprocess.run(cmd, check=check)
1136 def bot(self):
1137 """Run repeated rounds of checkout and builds."""
1138 while True:
1139 self.load_bot_config_json()
1140 if not self.bot_config['run']:
1141 print('Bot exiting by request.')
1142 exit(0)
1143 self.bot_run_self([], 'bot-cycle', check=False)
1144 self.load_bot_config_json()
1145 if not self.bot_config['run']:
1146 print('Bot exiting by request.')
1147 exit(0)
1148 time.sleep(self.bot_config['delay'])
1149 if self.get_script_text() != self.script_text:
1150 print('Script changed, bot re-execing.')
1151 self.exec_self()
1154 class Config(object):
1155 """A configuration for building a compiler and associated libraries."""
1157 def __init__(self, ctx, arch, os_name, variant=None, gcc_cfg=None,
1158 first_gcc_cfg=None, glibcs=None, extra_glibcs=None):
1159 """Initialize a Config object."""
1160 self.ctx = ctx
1161 self.arch = arch
1162 self.os = os_name
1163 self.variant = variant
1164 if variant is None:
1165 self.name = '%s-%s' % (arch, os_name)
1166 else:
1167 self.name = '%s-%s-%s' % (arch, os_name, variant)
1168 self.triplet = '%s-glibc-%s' % (arch, os_name)
1169 if gcc_cfg is None:
1170 self.gcc_cfg = []
1171 else:
1172 self.gcc_cfg = gcc_cfg
1173 if first_gcc_cfg is None:
1174 self.first_gcc_cfg = []
1175 else:
1176 self.first_gcc_cfg = first_gcc_cfg
1177 if glibcs is None:
1178 glibcs = [{'variant': variant}]
1179 if extra_glibcs is None:
1180 extra_glibcs = []
1181 glibcs = [Glibc(self, **g) for g in glibcs]
1182 extra_glibcs = [Glibc(self, **g) for g in extra_glibcs]
1183 self.all_glibcs = glibcs + extra_glibcs
1184 self.compiler_glibcs = glibcs
1185 self.installdir = ctx.compiler_installdir(self.name)
1186 self.bindir = ctx.compiler_bindir(self.name)
1187 self.sysroot = ctx.compiler_sysroot(self.name)
1188 self.builddir = os.path.join(ctx.builddir, 'compilers', self.name)
1189 self.logsdir = os.path.join(ctx.logsdir, 'compilers', self.name)
1191 def component_builddir(self, component):
1192 """Return the directory to use for a (non-glibc) build."""
1193 return self.ctx.component_builddir('compilers', self.name, component)
1195 def build(self):
1196 """Generate commands to build this compiler."""
1197 self.ctx.remove_recreate_dirs(self.installdir, self.builddir,
1198 self.logsdir)
1199 cmdlist = CommandList('compilers-%s' % self.name, self.ctx.keep)
1200 cmdlist.add_command('check-host-libraries',
1201 ['test', '-f',
1202 os.path.join(self.ctx.host_libraries_installdir,
1203 'ok')])
1204 cmdlist.use_path(self.bindir)
1205 self.build_cross_tool(cmdlist, 'binutils', 'binutils',
1206 ['--disable-gdb',
1207 '--disable-libdecnumber',
1208 '--disable-readline',
1209 '--disable-sim'])
1210 if self.os.startswith('linux'):
1211 self.install_linux_headers(cmdlist)
1212 self.build_gcc(cmdlist, True)
1213 if self.os == 'gnu':
1214 self.install_gnumach_headers(cmdlist)
1215 self.build_cross_tool(cmdlist, 'mig', 'mig')
1216 self.install_hurd_headers(cmdlist)
1217 for g in self.compiler_glibcs:
1218 cmdlist.push_subdesc('glibc')
1219 cmdlist.push_subdesc(g.name)
1220 g.build_glibc(cmdlist, True)
1221 cmdlist.pop_subdesc()
1222 cmdlist.pop_subdesc()
1223 self.build_gcc(cmdlist, False)
1224 cmdlist.add_command('done', ['touch',
1225 os.path.join(self.installdir, 'ok')])
1226 self.ctx.add_makefile_cmdlist('compilers-%s' % self.name, cmdlist,
1227 self.logsdir)
1229 def build_cross_tool(self, cmdlist, tool_src, tool_build, extra_opts=None):
1230 """Build one cross tool."""
1231 srcdir = self.ctx.component_srcdir(tool_src)
1232 builddir = self.component_builddir(tool_build)
1233 cmdlist.push_subdesc(tool_build)
1234 cmdlist.create_use_dir(builddir)
1235 cfg_cmd = [os.path.join(srcdir, 'configure'),
1236 '--prefix=%s' % self.installdir,
1237 '--build=%s' % self.ctx.build_triplet,
1238 '--host=%s' % self.ctx.build_triplet,
1239 '--target=%s' % self.triplet,
1240 '--with-sysroot=%s' % self.sysroot]
1241 if extra_opts:
1242 cfg_cmd.extend(extra_opts)
1243 cmdlist.add_command('configure', cfg_cmd)
1244 cmdlist.add_command('build', ['make'])
1245 # Parallel "make install" for GCC has race conditions that can
1246 # cause it to fail; see
1247 # <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=42980>. Such
1248 # problems are not known for binutils, but doing the
1249 # installation in parallel within a particular toolchain build
1250 # (as opposed to installation of one toolchain from
1251 # build-many-glibcs.py running in parallel to the installation
1252 # of other toolchains being built) is not known to be
1253 # significantly beneficial, so it is simplest just to disable
1254 # parallel install for cross tools here.
1255 cmdlist.add_command('install', ['make', '-j1', 'install'])
1256 cmdlist.cleanup_dir()
1257 cmdlist.pop_subdesc()
1259 def install_linux_headers(self, cmdlist):
1260 """Install Linux kernel headers."""
1261 arch_map = {'aarch64': 'arm64',
1262 'alpha': 'alpha',
1263 'arm': 'arm',
1264 'csky': 'csky',
1265 'hppa': 'parisc',
1266 'i486': 'x86',
1267 'i586': 'x86',
1268 'i686': 'x86',
1269 'i786': 'x86',
1270 'ia64': 'ia64',
1271 'm68k': 'm68k',
1272 'microblaze': 'microblaze',
1273 'mips': 'mips',
1274 'nios2': 'nios2',
1275 'powerpc': 'powerpc',
1276 's390': 's390',
1277 'riscv32': 'riscv',
1278 'riscv64': 'riscv',
1279 'sh': 'sh',
1280 'sparc': 'sparc',
1281 'x86_64': 'x86'}
1282 linux_arch = None
1283 for k in arch_map:
1284 if self.arch.startswith(k):
1285 linux_arch = arch_map[k]
1286 break
1287 assert linux_arch is not None
1288 srcdir = self.ctx.component_srcdir('linux')
1289 builddir = self.component_builddir('linux')
1290 headers_dir = os.path.join(self.sysroot, 'usr')
1291 cmdlist.push_subdesc('linux')
1292 cmdlist.create_use_dir(builddir)
1293 cmdlist.add_command('install-headers',
1294 ['make', '-C', srcdir, 'O=%s' % builddir,
1295 'ARCH=%s' % linux_arch,
1296 'INSTALL_HDR_PATH=%s' % headers_dir,
1297 'headers_install'])
1298 cmdlist.cleanup_dir()
1299 cmdlist.pop_subdesc()
1301 def install_gnumach_headers(self, cmdlist):
1302 """Install GNU Mach headers."""
1303 srcdir = self.ctx.component_srcdir('gnumach')
1304 builddir = self.component_builddir('gnumach')
1305 cmdlist.push_subdesc('gnumach')
1306 cmdlist.create_use_dir(builddir)
1307 cmdlist.add_command('configure',
1308 [os.path.join(srcdir, 'configure'),
1309 '--build=%s' % self.ctx.build_triplet,
1310 '--host=%s' % self.triplet,
1311 '--prefix=',
1312 'CC=%s-gcc -nostdlib' % self.triplet])
1313 cmdlist.add_command('install', ['make', 'DESTDIR=%s' % self.sysroot,
1314 'install-data'])
1315 cmdlist.cleanup_dir()
1316 cmdlist.pop_subdesc()
1318 def install_hurd_headers(self, cmdlist):
1319 """Install Hurd headers."""
1320 srcdir = self.ctx.component_srcdir('hurd')
1321 builddir = self.component_builddir('hurd')
1322 cmdlist.push_subdesc('hurd')
1323 cmdlist.create_use_dir(builddir)
1324 cmdlist.add_command('configure',
1325 [os.path.join(srcdir, 'configure'),
1326 '--build=%s' % self.ctx.build_triplet,
1327 '--host=%s' % self.triplet,
1328 '--prefix=',
1329 '--disable-profile', '--without-parted',
1330 'CC=%s-gcc -nostdlib' % self.triplet])
1331 cmdlist.add_command('install', ['make', 'prefix=%s' % self.sysroot,
1332 'no_deps=t', 'install-headers'])
1333 cmdlist.cleanup_dir()
1334 cmdlist.pop_subdesc()
1336 def build_gcc(self, cmdlist, bootstrap):
1337 """Build GCC."""
1338 # libssp is of little relevance with glibc's own stack
1339 # checking support. libcilkrts does not support GNU/Hurd (and
1340 # has been removed in GCC 8, so --disable-libcilkrts can be
1341 # removed once glibc no longer supports building with older
1342 # GCC versions).
1343 cfg_opts = list(self.gcc_cfg)
1344 cfg_opts += ['--disable-libssp', '--disable-libcilkrts']
1345 host_libs = self.ctx.host_libraries_installdir
1346 cfg_opts += ['--with-gmp=%s' % host_libs,
1347 '--with-mpfr=%s' % host_libs,
1348 '--with-mpc=%s' % host_libs]
1349 if bootstrap:
1350 tool_build = 'gcc-first'
1351 # Building a static-only, C-only compiler that is
1352 # sufficient to build glibc. Various libraries and
1353 # features that may require libc headers must be disabled.
1354 # When configuring with a sysroot, --with-newlib is
1355 # required to define inhibit_libc (to stop some parts of
1356 # libgcc including libc headers); --without-headers is not
1357 # sufficient.
1358 cfg_opts += ['--enable-languages=c', '--disable-shared',
1359 '--disable-threads',
1360 '--disable-libatomic',
1361 '--disable-decimal-float',
1362 '--disable-libffi',
1363 '--disable-libgomp',
1364 '--disable-libitm',
1365 '--disable-libmpx',
1366 '--disable-libquadmath',
1367 '--disable-libsanitizer',
1368 '--without-headers', '--with-newlib',
1369 '--with-glibc-version=%s' % self.ctx.glibc_version
1371 cfg_opts += self.first_gcc_cfg
1372 else:
1373 tool_build = 'gcc'
1374 # libsanitizer commonly breaks because of glibc header
1375 # changes, or on unusual targets.
1376 if not self.ctx.full_gcc:
1377 cfg_opts += ['--disable-libsanitizer']
1378 langs = 'all' if self.ctx.full_gcc else 'c,c++'
1379 cfg_opts += ['--enable-languages=%s' % langs,
1380 '--enable-shared', '--enable-threads']
1381 self.build_cross_tool(cmdlist, 'gcc', tool_build, cfg_opts)
1384 class Glibc(object):
1385 """A configuration for building glibc."""
1387 def __init__(self, compiler, arch=None, os_name=None, variant=None,
1388 cfg=None, ccopts=None):
1389 """Initialize a Glibc object."""
1390 self.ctx = compiler.ctx
1391 self.compiler = compiler
1392 if arch is None:
1393 self.arch = compiler.arch
1394 else:
1395 self.arch = arch
1396 if os_name is None:
1397 self.os = compiler.os
1398 else:
1399 self.os = os_name
1400 self.variant = variant
1401 if variant is None:
1402 self.name = '%s-%s' % (self.arch, self.os)
1403 else:
1404 self.name = '%s-%s-%s' % (self.arch, self.os, variant)
1405 self.triplet = '%s-glibc-%s' % (self.arch, self.os)
1406 if cfg is None:
1407 self.cfg = []
1408 else:
1409 self.cfg = cfg
1410 self.ccopts = ccopts
1412 def tool_name(self, tool):
1413 """Return the name of a cross-compilation tool."""
1414 ctool = '%s-%s' % (self.compiler.triplet, tool)
1415 if self.ccopts and (tool == 'gcc' or tool == 'g++'):
1416 ctool = '%s %s' % (ctool, self.ccopts)
1417 return ctool
1419 def build(self):
1420 """Generate commands to build this glibc."""
1421 builddir = self.ctx.component_builddir('glibcs', self.name, 'glibc')
1422 installdir = self.ctx.glibc_installdir(self.name)
1423 logsdir = os.path.join(self.ctx.logsdir, 'glibcs', self.name)
1424 self.ctx.remove_recreate_dirs(installdir, builddir, logsdir)
1425 cmdlist = CommandList('glibcs-%s' % self.name, self.ctx.keep)
1426 cmdlist.add_command('check-compilers',
1427 ['test', '-f',
1428 os.path.join(self.compiler.installdir, 'ok')])
1429 cmdlist.use_path(self.compiler.bindir)
1430 self.build_glibc(cmdlist, False)
1431 self.ctx.add_makefile_cmdlist('glibcs-%s' % self.name, cmdlist,
1432 logsdir)
1434 def build_glibc(self, cmdlist, for_compiler):
1435 """Generate commands to build this glibc, either as part of a compiler
1436 build or with the bootstrapped compiler (and in the latter case, run
1437 tests as well)."""
1438 srcdir = self.ctx.component_srcdir('glibc')
1439 if for_compiler:
1440 builddir = self.ctx.component_builddir('compilers',
1441 self.compiler.name, 'glibc',
1442 self.name)
1443 installdir = self.compiler.sysroot
1444 else:
1445 builddir = self.ctx.component_builddir('glibcs', self.name,
1446 'glibc')
1447 installdir = self.ctx.glibc_installdir(self.name)
1448 cmdlist.create_use_dir(builddir)
1449 use_usr = self.os != 'gnu'
1450 prefix = '/usr' if use_usr else ''
1451 cfg_cmd = [os.path.join(srcdir, 'configure'),
1452 '--prefix=%s' % prefix,
1453 '--enable-profile',
1454 '--build=%s' % self.ctx.build_triplet,
1455 '--host=%s' % self.triplet,
1456 'CC=%s' % self.tool_name('gcc'),
1457 'CXX=%s' % self.tool_name('g++'),
1458 'AR=%s' % self.tool_name('ar'),
1459 'AS=%s' % self.tool_name('as'),
1460 'LD=%s' % self.tool_name('ld'),
1461 'NM=%s' % self.tool_name('nm'),
1462 'OBJCOPY=%s' % self.tool_name('objcopy'),
1463 'OBJDUMP=%s' % self.tool_name('objdump'),
1464 'RANLIB=%s' % self.tool_name('ranlib'),
1465 'READELF=%s' % self.tool_name('readelf'),
1466 'STRIP=%s' % self.tool_name('strip')]
1467 if self.os == 'gnu':
1468 cfg_cmd += ['MIG=%s' % self.tool_name('mig')]
1469 cfg_cmd += self.cfg
1470 cmdlist.add_command('configure', cfg_cmd)
1471 cmdlist.add_command('build', ['make'])
1472 cmdlist.add_command('install', ['make', 'install',
1473 'install_root=%s' % installdir])
1474 # GCC uses paths such as lib/../lib64, so make sure lib
1475 # directories always exist.
1476 mkdir_cmd = ['mkdir', '-p',
1477 os.path.join(installdir, 'lib')]
1478 if use_usr:
1479 mkdir_cmd += [os.path.join(installdir, 'usr', 'lib')]
1480 cmdlist.add_command('mkdir-lib', mkdir_cmd)
1481 if not for_compiler:
1482 if self.ctx.strip:
1483 cmdlist.add_command('strip',
1484 ['sh', '-c',
1485 ('%s $(find %s/lib* -name "*.so")' %
1486 (self.tool_name('strip'), installdir))])
1487 cmdlist.add_command('check', ['make', 'check'])
1488 cmdlist.add_command('save-logs', [self.ctx.save_logs],
1489 always_run=True)
1490 cmdlist.cleanup_dir()
1493 class Command(object):
1494 """A command run in the build process."""
1496 def __init__(self, desc, num, dir, path, command, always_run=False):
1497 """Initialize a Command object."""
1498 self.dir = dir
1499 self.path = path
1500 self.desc = desc
1501 trans = str.maketrans({' ': '-'})
1502 self.logbase = '%03d-%s' % (num, desc.translate(trans))
1503 self.command = command
1504 self.always_run = always_run
1506 @staticmethod
1507 def shell_make_quote_string(s):
1508 """Given a string not containing a newline, quote it for use by the
1509 shell and make."""
1510 assert '\n' not in s
1511 if re.fullmatch('[]+,./0-9@A-Z_a-z-]+', s):
1512 return s
1513 strans = str.maketrans({"'": "'\\''"})
1514 s = "'%s'" % s.translate(strans)
1515 mtrans = str.maketrans({'$': '$$'})
1516 return s.translate(mtrans)
1518 @staticmethod
1519 def shell_make_quote_list(l, translate_make):
1520 """Given a list of strings not containing newlines, quote them for use
1521 by the shell and make, returning a single string. If translate_make
1522 is true and the first string is 'make', change it to $(MAKE)."""
1523 l = [Command.shell_make_quote_string(s) for s in l]
1524 if translate_make and l[0] == 'make':
1525 l[0] = '$(MAKE)'
1526 return ' '.join(l)
1528 def shell_make_quote(self):
1529 """Return this command quoted for the shell and make."""
1530 return self.shell_make_quote_list(self.command, True)
1533 class CommandList(object):
1534 """A list of commands run in the build process."""
1536 def __init__(self, desc, keep):
1537 """Initialize a CommandList object."""
1538 self.cmdlist = []
1539 self.dir = None
1540 self.path = None
1541 self.desc = [desc]
1542 self.keep = keep
1544 def desc_txt(self, desc):
1545 """Return the description to use for a command."""
1546 return '%s %s' % (' '.join(self.desc), desc)
1548 def use_dir(self, dir):
1549 """Set the default directory for subsequent commands."""
1550 self.dir = dir
1552 def use_path(self, path):
1553 """Set a directory to be prepended to the PATH for subsequent
1554 commands."""
1555 self.path = path
1557 def push_subdesc(self, subdesc):
1558 """Set the default subdescription for subsequent commands (e.g., the
1559 name of a component being built, within the series of commands
1560 building it)."""
1561 self.desc.append(subdesc)
1563 def pop_subdesc(self):
1564 """Pop a subdescription from the list of descriptions."""
1565 self.desc.pop()
1567 def create_use_dir(self, dir):
1568 """Remove and recreate a directory and use it for subsequent
1569 commands."""
1570 self.add_command_dir('rm', None, ['rm', '-rf', dir])
1571 self.add_command_dir('mkdir', None, ['mkdir', '-p', dir])
1572 self.use_dir(dir)
1574 def add_command_dir(self, desc, dir, command, always_run=False):
1575 """Add a command to run in a given directory."""
1576 cmd = Command(self.desc_txt(desc), len(self.cmdlist), dir, self.path,
1577 command, always_run)
1578 self.cmdlist.append(cmd)
1580 def add_command(self, desc, command, always_run=False):
1581 """Add a command to run in the default directory."""
1582 cmd = Command(self.desc_txt(desc), len(self.cmdlist), self.dir,
1583 self.path, command, always_run)
1584 self.cmdlist.append(cmd)
1586 def cleanup_dir(self, desc='cleanup', dir=None):
1587 """Clean up a build directory. If no directory is specified, the
1588 default directory is cleaned up and ceases to be the default
1589 directory."""
1590 if dir is None:
1591 dir = self.dir
1592 self.use_dir(None)
1593 if self.keep != 'all':
1594 self.add_command_dir(desc, None, ['rm', '-rf', dir],
1595 always_run=(self.keep == 'none'))
1597 def makefile_commands(self, wrapper, logsdir):
1598 """Return the sequence of commands in the form of text for a Makefile.
1599 The given wrapper script takes arguments: base of logs for
1600 previous command, or empty; base of logs for this command;
1601 description; directory; PATH addition; the command itself."""
1602 # prev_base is the base of the name for logs of the previous
1603 # command that is not always-run (that is, a build command,
1604 # whose failure should stop subsequent build commands from
1605 # being run, as opposed to a cleanup command, which is run
1606 # even if previous commands failed).
1607 prev_base = ''
1608 cmds = []
1609 for c in self.cmdlist:
1610 ctxt = c.shell_make_quote()
1611 if prev_base and not c.always_run:
1612 prev_log = os.path.join(logsdir, prev_base)
1613 else:
1614 prev_log = ''
1615 this_log = os.path.join(logsdir, c.logbase)
1616 if not c.always_run:
1617 prev_base = c.logbase
1618 if c.dir is None:
1619 dir = ''
1620 else:
1621 dir = c.dir
1622 if c.path is None:
1623 path = ''
1624 else:
1625 path = c.path
1626 prelims = [wrapper, prev_log, this_log, c.desc, dir, path]
1627 prelim_txt = Command.shell_make_quote_list(prelims, False)
1628 cmds.append('\t@%s %s' % (prelim_txt, ctxt))
1629 return '\n'.join(cmds)
1631 def status_logs(self, logsdir):
1632 """Return the list of log files with command status."""
1633 return [os.path.join(logsdir, '%s-status.txt' % c.logbase)
1634 for c in self.cmdlist]
1637 def get_parser():
1638 """Return an argument parser for this module."""
1639 parser = argparse.ArgumentParser(description=__doc__)
1640 parser.add_argument('-j', dest='parallelism',
1641 help='Run this number of jobs in parallel',
1642 type=int, default=os.cpu_count())
1643 parser.add_argument('--keep', dest='keep',
1644 help='Whether to keep all build directories, '
1645 'none or only those from failed builds',
1646 default='none', choices=('none', 'all', 'failed'))
1647 parser.add_argument('--replace-sources', action='store_true',
1648 help='Remove and replace source directories '
1649 'with the wrong version of a component')
1650 parser.add_argument('--strip', action='store_true',
1651 help='Strip installed glibc libraries')
1652 parser.add_argument('--full-gcc', action='store_true',
1653 help='Build GCC with all languages and libsanitizer')
1654 parser.add_argument('topdir',
1655 help='Toplevel working directory')
1656 parser.add_argument('action',
1657 help='What to do',
1658 choices=('checkout', 'bot-cycle', 'bot',
1659 'host-libraries', 'compilers', 'glibcs'))
1660 parser.add_argument('configs',
1661 help='Versions to check out or configurations to build',
1662 nargs='*')
1663 return parser
1666 def main(argv):
1667 """The main entry point."""
1668 parser = get_parser()
1669 opts = parser.parse_args(argv)
1670 topdir = os.path.abspath(opts.topdir)
1671 ctx = Context(topdir, opts.parallelism, opts.keep, opts.replace_sources,
1672 opts.strip, opts.full_gcc, opts.action)
1673 ctx.run_builds(opts.action, opts.configs)
1676 if __name__ == '__main__':
1677 main(sys.argv[1:])