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