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