]> git.ipfire.org Git - thirdparty/glibc.git/blob - scripts/build-many-glibcs.py
posix: Do not include testcases.h, ptestcases.h in source tree
[thirdparty/glibc.git] / scripts / build-many-glibcs.py
1 #!/usr/bin/python3
2 # Build many configurations of glibc.
3 # Copyright (C) 2016-2018 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='hppa',
185 os_name='linux-gnu')
186 self.add_config(arch='i686',
187 os_name='gnu')
188 self.add_config(arch='ia64',
189 os_name='linux-gnu',
190 first_gcc_cfg=['--with-system-libunwind'])
191 self.add_config(arch='m68k',
192 os_name='linux-gnu',
193 gcc_cfg=['--disable-multilib'])
194 self.add_config(arch='m68k',
195 os_name='linux-gnu',
196 variant='coldfire',
197 gcc_cfg=['--with-arch=cf', '--disable-multilib'])
198 self.add_config(arch='m68k',
199 os_name='linux-gnu',
200 variant='coldfire-soft',
201 gcc_cfg=['--with-arch=cf', '--with-cpu=54455',
202 '--disable-multilib'])
203 self.add_config(arch='microblaze',
204 os_name='linux-gnu',
205 gcc_cfg=['--disable-multilib'])
206 self.add_config(arch='microblazeel',
207 os_name='linux-gnu',
208 gcc_cfg=['--disable-multilib'])
209 self.add_config(arch='mips64',
210 os_name='linux-gnu',
211 gcc_cfg=['--with-mips-plt'],
212 glibcs=[{'variant': 'n32'},
213 {'arch': 'mips',
214 'ccopts': '-mabi=32'},
215 {'variant': 'n64',
216 'ccopts': '-mabi=64'}])
217 self.add_config(arch='mips64',
218 os_name='linux-gnu',
219 variant='soft',
220 gcc_cfg=['--with-mips-plt', '--with-float=soft'],
221 glibcs=[{'variant': 'n32-soft'},
222 {'variant': 'soft',
223 'arch': 'mips',
224 'ccopts': '-mabi=32'},
225 {'variant': 'n64-soft',
226 'ccopts': '-mabi=64'}])
227 self.add_config(arch='mips64',
228 os_name='linux-gnu',
229 variant='nan2008',
230 gcc_cfg=['--with-mips-plt', '--with-nan=2008',
231 '--with-arch-64=mips64r2',
232 '--with-arch-32=mips32r2'],
233 glibcs=[{'variant': 'n32-nan2008'},
234 {'variant': 'nan2008',
235 'arch': 'mips',
236 'ccopts': '-mabi=32'},
237 {'variant': 'n64-nan2008',
238 'ccopts': '-mabi=64'}])
239 self.add_config(arch='mips64',
240 os_name='linux-gnu',
241 variant='nan2008-soft',
242 gcc_cfg=['--with-mips-plt', '--with-nan=2008',
243 '--with-arch-64=mips64r2',
244 '--with-arch-32=mips32r2',
245 '--with-float=soft'],
246 glibcs=[{'variant': 'n32-nan2008-soft'},
247 {'variant': 'nan2008-soft',
248 'arch': 'mips',
249 'ccopts': '-mabi=32'},
250 {'variant': 'n64-nan2008-soft',
251 'ccopts': '-mabi=64'}])
252 self.add_config(arch='mips64el',
253 os_name='linux-gnu',
254 gcc_cfg=['--with-mips-plt'],
255 glibcs=[{'variant': 'n32'},
256 {'arch': 'mipsel',
257 'ccopts': '-mabi=32'},
258 {'variant': 'n64',
259 'ccopts': '-mabi=64'}])
260 self.add_config(arch='mips64el',
261 os_name='linux-gnu',
262 variant='soft',
263 gcc_cfg=['--with-mips-plt', '--with-float=soft'],
264 glibcs=[{'variant': 'n32-soft'},
265 {'variant': 'soft',
266 'arch': 'mipsel',
267 'ccopts': '-mabi=32'},
268 {'variant': 'n64-soft',
269 'ccopts': '-mabi=64'}])
270 self.add_config(arch='mips64el',
271 os_name='linux-gnu',
272 variant='nan2008',
273 gcc_cfg=['--with-mips-plt', '--with-nan=2008',
274 '--with-arch-64=mips64r2',
275 '--with-arch-32=mips32r2'],
276 glibcs=[{'variant': 'n32-nan2008'},
277 {'variant': 'nan2008',
278 'arch': 'mipsel',
279 'ccopts': '-mabi=32'},
280 {'variant': 'n64-nan2008',
281 'ccopts': '-mabi=64'}])
282 self.add_config(arch='mips64el',
283 os_name='linux-gnu',
284 variant='nan2008-soft',
285 gcc_cfg=['--with-mips-plt', '--with-nan=2008',
286 '--with-arch-64=mips64r2',
287 '--with-arch-32=mips32r2',
288 '--with-float=soft'],
289 glibcs=[{'variant': 'n32-nan2008-soft'},
290 {'variant': 'nan2008-soft',
291 'arch': 'mipsel',
292 'ccopts': '-mabi=32'},
293 {'variant': 'n64-nan2008-soft',
294 'ccopts': '-mabi=64'}])
295 self.add_config(arch='nios2',
296 os_name='linux-gnu')
297 self.add_config(arch='powerpc',
298 os_name='linux-gnu',
299 gcc_cfg=['--disable-multilib', '--enable-secureplt'],
300 extra_glibcs=[{'variant': 'power4',
301 'ccopts': '-mcpu=power4',
302 'cfg': ['--with-cpu=power4']}])
303 self.add_config(arch='powerpc',
304 os_name='linux-gnu',
305 variant='soft',
306 gcc_cfg=['--disable-multilib', '--with-float=soft',
307 '--enable-secureplt'])
308 self.add_config(arch='powerpc64',
309 os_name='linux-gnu',
310 gcc_cfg=['--disable-multilib', '--enable-secureplt'])
311 self.add_config(arch='powerpc64le',
312 os_name='linux-gnu',
313 gcc_cfg=['--disable-multilib', '--enable-secureplt'])
314 self.add_config(arch='powerpc',
315 os_name='linux-gnuspe',
316 gcc_cfg=['--disable-multilib', '--enable-secureplt',
317 '--enable-e500-double', '--enable-obsolete'])
318 self.add_config(arch='powerpc',
319 os_name='linux-gnuspe',
320 variant='e500v1',
321 gcc_cfg=['--disable-multilib', '--enable-secureplt',
322 '--enable-obsolete'])
323 self.add_config(arch='riscv64',
324 os_name='linux-gnu',
325 variant='rv64imac-lp64',
326 gcc_cfg=['--with-arch=rv64imac', '--with-abi=lp64',
327 '--disable-multilib'])
328 self.add_config(arch='riscv64',
329 os_name='linux-gnu',
330 variant='rv64imafdc-lp64',
331 gcc_cfg=['--with-arch=rv64imafdc', '--with-abi=lp64',
332 '--disable-multilib'])
333 self.add_config(arch='riscv64',
334 os_name='linux-gnu',
335 variant='rv64imafdc-lp64d',
336 gcc_cfg=['--with-arch=rv64imafdc', '--with-abi=lp64d',
337 '--disable-multilib'])
338 self.add_config(arch='s390x',
339 os_name='linux-gnu',
340 glibcs=[{},
341 {'arch': 's390', 'ccopts': '-m31'}])
342 self.add_config(arch='sh3',
343 os_name='linux-gnu')
344 self.add_config(arch='sh3eb',
345 os_name='linux-gnu')
346 self.add_config(arch='sh4',
347 os_name='linux-gnu')
348 self.add_config(arch='sh4eb',
349 os_name='linux-gnu')
350 self.add_config(arch='sh4',
351 os_name='linux-gnu',
352 variant='soft',
353 gcc_cfg=['--without-fp'])
354 self.add_config(arch='sh4eb',
355 os_name='linux-gnu',
356 variant='soft',
357 gcc_cfg=['--without-fp'])
358 self.add_config(arch='sparc64',
359 os_name='linux-gnu',
360 glibcs=[{},
361 {'arch': 'sparcv9',
362 'ccopts': '-m32 -mlong-double-128'}],
363 extra_glibcs=[{'variant': 'disable-multi-arch',
364 'cfg': ['--disable-multi-arch']},
365 {'variant': 'disable-multi-arch',
366 'arch': 'sparcv9',
367 'ccopts': '-m32 -mlong-double-128',
368 'cfg': ['--disable-multi-arch']}])
369 self.add_config(arch='x86_64',
370 os_name='linux-gnu',
371 gcc_cfg=['--with-multilib-list=m64,m32,mx32'],
372 glibcs=[{},
373 {'variant': 'x32', 'ccopts': '-mx32'},
374 {'arch': 'i686', 'ccopts': '-m32 -march=i686'}],
375 extra_glibcs=[{'variant': 'disable-multi-arch',
376 'cfg': ['--disable-multi-arch']},
377 {'variant': 'enable-obsolete',
378 'cfg': ['--enable-obsolete-rpc',
379 '--enable-obsolete-nsl']},
380 {'variant': 'static-pie',
381 'cfg': ['--enable-static-pie']},
382 {'variant': 'x32-static-pie',
383 'ccopts': '-mx32',
384 'cfg': ['--enable-static-pie']},
385 {'variant': 'static-pie',
386 'arch': 'i686',
387 'ccopts': '-m32 -march=i686',
388 'cfg': ['--enable-static-pie']},
389 {'variant': 'disable-multi-arch',
390 'arch': 'i686',
391 'ccopts': '-m32 -march=i686',
392 'cfg': ['--disable-multi-arch']},
393 {'variant': 'enable-obsolete',
394 'arch': 'i686',
395 'ccopts': '-m32 -march=i686',
396 'cfg': ['--enable-obsolete-rpc',
397 '--enable-obsolete-nsl']},
398 {'arch': 'i486',
399 'ccopts': '-m32 -march=i486'},
400 {'arch': 'i586',
401 'ccopts': '-m32 -march=i586'}])
402
403 def add_config(self, **args):
404 """Add an individual build configuration."""
405 cfg = Config(self, **args)
406 if cfg.name in self.configs:
407 print('error: duplicate config %s' % cfg.name)
408 exit(1)
409 self.configs[cfg.name] = cfg
410 for c in cfg.all_glibcs:
411 if c.name in self.glibc_configs:
412 print('error: duplicate glibc config %s' % c.name)
413 exit(1)
414 self.glibc_configs[c.name] = c
415
416 def component_srcdir(self, component):
417 """Return the source directory for a given component, e.g. gcc."""
418 return os.path.join(self.srcdir, component)
419
420 def component_builddir(self, action, config, component, subconfig=None):
421 """Return the directory to use for a build."""
422 if config is None:
423 # Host libraries.
424 assert subconfig is None
425 return os.path.join(self.builddir, action, component)
426 if subconfig is None:
427 return os.path.join(self.builddir, action, config, component)
428 else:
429 # glibc build as part of compiler build.
430 return os.path.join(self.builddir, action, config, component,
431 subconfig)
432
433 def compiler_installdir(self, config):
434 """Return the directory in which to install a compiler."""
435 return os.path.join(self.installdir, 'compilers', config)
436
437 def compiler_bindir(self, config):
438 """Return the directory in which to find compiler binaries."""
439 return os.path.join(self.compiler_installdir(config), 'bin')
440
441 def compiler_sysroot(self, config):
442 """Return the sysroot directory for a compiler."""
443 return os.path.join(self.compiler_installdir(config), 'sysroot')
444
445 def glibc_installdir(self, config):
446 """Return the directory in which to install glibc."""
447 return os.path.join(self.installdir, 'glibcs', config)
448
449 def run_builds(self, action, configs):
450 """Run the requested builds."""
451 if action == 'checkout':
452 self.checkout(configs)
453 return
454 if action == 'bot-cycle':
455 if configs:
456 print('error: configurations specified for bot-cycle')
457 exit(1)
458 self.bot_cycle()
459 return
460 if action == 'bot':
461 if configs:
462 print('error: configurations specified for bot')
463 exit(1)
464 self.bot()
465 return
466 if action == 'host-libraries' and configs:
467 print('error: configurations specified for host-libraries')
468 exit(1)
469 self.clear_last_build_state(action)
470 build_time = datetime.datetime.utcnow()
471 if action == 'host-libraries':
472 build_components = ('gmp', 'mpfr', 'mpc')
473 old_components = ()
474 old_versions = {}
475 self.build_host_libraries()
476 elif action == 'compilers':
477 build_components = ('binutils', 'gcc', 'glibc', 'linux', 'mig',
478 'gnumach', 'hurd')
479 old_components = ('gmp', 'mpfr', 'mpc')
480 old_versions = self.build_state['host-libraries']['build-versions']
481 self.build_compilers(configs)
482 else:
483 build_components = ('glibc',)
484 old_components = ('gmp', 'mpfr', 'mpc', 'binutils', 'gcc', 'linux',
485 'mig', 'gnumach', 'hurd')
486 old_versions = self.build_state['compilers']['build-versions']
487 self.build_glibcs(configs)
488 self.write_files()
489 self.do_build()
490 if configs:
491 # Partial build, do not update stored state.
492 return
493 build_versions = {}
494 for k in build_components:
495 if k in self.versions:
496 build_versions[k] = {'version': self.versions[k]['version'],
497 'revision': self.versions[k]['revision']}
498 for k in old_components:
499 if k in old_versions:
500 build_versions[k] = {'version': old_versions[k]['version'],
501 'revision': old_versions[k]['revision']}
502 self.update_build_state(action, build_time, build_versions)
503
504 @staticmethod
505 def remove_dirs(*args):
506 """Remove directories and their contents if they exist."""
507 for dir in args:
508 shutil.rmtree(dir, ignore_errors=True)
509
510 @staticmethod
511 def remove_recreate_dirs(*args):
512 """Remove directories if they exist, and create them as empty."""
513 Context.remove_dirs(*args)
514 for dir in args:
515 os.makedirs(dir, exist_ok=True)
516
517 def add_makefile_cmdlist(self, target, cmdlist, logsdir):
518 """Add makefile text for a list of commands."""
519 commands = cmdlist.makefile_commands(self.wrapper, logsdir)
520 self.makefile_pieces.append('all: %s\n.PHONY: %s\n%s:\n%s\n' %
521 (target, target, target, commands))
522 self.status_log_list.extend(cmdlist.status_logs(logsdir))
523
524 def write_files(self):
525 """Write out the Makefile and wrapper script."""
526 mftext = ''.join(self.makefile_pieces)
527 with open(self.makefile, 'w') as f:
528 f.write(mftext)
529 wrapper_text = (
530 '#!/bin/sh\n'
531 'prev_base=$1\n'
532 'this_base=$2\n'
533 'desc=$3\n'
534 'dir=$4\n'
535 'path=$5\n'
536 'shift 5\n'
537 'prev_status=$prev_base-status.txt\n'
538 'this_status=$this_base-status.txt\n'
539 'this_log=$this_base-log.txt\n'
540 'date > "$this_log"\n'
541 'echo >> "$this_log"\n'
542 'echo "Description: $desc" >> "$this_log"\n'
543 'printf "%s" "Command:" >> "$this_log"\n'
544 'for word in "$@"; do\n'
545 ' if expr "$word" : "[]+,./0-9@A-Z_a-z-]\\\\{1,\\\\}\\$" > /dev/null; then\n'
546 ' printf " %s" "$word"\n'
547 ' else\n'
548 ' printf " \'"\n'
549 ' printf "%s" "$word" | sed -e "s/\'/\'\\\\\\\\\'\'/"\n'
550 ' printf "\'"\n'
551 ' fi\n'
552 'done >> "$this_log"\n'
553 'echo >> "$this_log"\n'
554 'echo "Directory: $dir" >> "$this_log"\n'
555 'echo "Path addition: $path" >> "$this_log"\n'
556 'echo >> "$this_log"\n'
557 'record_status ()\n'
558 '{\n'
559 ' echo >> "$this_log"\n'
560 ' echo "$1: $desc" > "$this_status"\n'
561 ' echo "$1: $desc" >> "$this_log"\n'
562 ' echo >> "$this_log"\n'
563 ' date >> "$this_log"\n'
564 ' echo "$1: $desc"\n'
565 ' exit 0\n'
566 '}\n'
567 'check_error ()\n'
568 '{\n'
569 ' if [ "$1" != "0" ]; then\n'
570 ' record_status FAIL\n'
571 ' fi\n'
572 '}\n'
573 'if [ "$prev_base" ] && ! grep -q "^PASS" "$prev_status"; then\n'
574 ' record_status UNRESOLVED\n'
575 'fi\n'
576 'if [ "$dir" ]; then\n'
577 ' cd "$dir"\n'
578 ' check_error "$?"\n'
579 'fi\n'
580 'if [ "$path" ]; then\n'
581 ' PATH=$path:$PATH\n'
582 'fi\n'
583 '"$@" < /dev/null >> "$this_log" 2>&1\n'
584 'check_error "$?"\n'
585 'record_status PASS\n')
586 with open(self.wrapper, 'w') as f:
587 f.write(wrapper_text)
588 # Mode 0o755.
589 mode_exec = (stat.S_IRWXU|stat.S_IRGRP|stat.S_IXGRP|
590 stat.S_IROTH|stat.S_IXOTH)
591 os.chmod(self.wrapper, mode_exec)
592 save_logs_text = (
593 '#!/bin/sh\n'
594 'if ! [ -f tests.sum ]; then\n'
595 ' echo "No test summary available."\n'
596 ' exit 0\n'
597 'fi\n'
598 'save_file ()\n'
599 '{\n'
600 ' echo "Contents of $1:"\n'
601 ' echo\n'
602 ' cat "$1"\n'
603 ' echo\n'
604 ' echo "End of contents of $1."\n'
605 ' echo\n'
606 '}\n'
607 'save_file tests.sum\n'
608 'non_pass_tests=$(grep -v "^PASS: " tests.sum | sed -e "s/^PASS: //")\n'
609 'for t in $non_pass_tests; do\n'
610 ' if [ -f "$t.out" ]; then\n'
611 ' save_file "$t.out"\n'
612 ' fi\n'
613 'done\n')
614 with open(self.save_logs, 'w') as f:
615 f.write(save_logs_text)
616 os.chmod(self.save_logs, mode_exec)
617
618 def do_build(self):
619 """Do the actual build."""
620 cmd = ['make', '-j%d' % self.parallelism]
621 subprocess.run(cmd, cwd=self.builddir, check=True)
622
623 def build_host_libraries(self):
624 """Build the host libraries."""
625 installdir = self.host_libraries_installdir
626 builddir = os.path.join(self.builddir, 'host-libraries')
627 logsdir = os.path.join(self.logsdir, 'host-libraries')
628 self.remove_recreate_dirs(installdir, builddir, logsdir)
629 cmdlist = CommandList('host-libraries', self.keep)
630 self.build_host_library(cmdlist, 'gmp')
631 self.build_host_library(cmdlist, 'mpfr',
632 ['--with-gmp=%s' % installdir])
633 self.build_host_library(cmdlist, 'mpc',
634 ['--with-gmp=%s' % installdir,
635 '--with-mpfr=%s' % installdir])
636 cmdlist.add_command('done', ['touch', os.path.join(installdir, 'ok')])
637 self.add_makefile_cmdlist('host-libraries', cmdlist, logsdir)
638
639 def build_host_library(self, cmdlist, lib, extra_opts=None):
640 """Build one host library."""
641 srcdir = self.component_srcdir(lib)
642 builddir = self.component_builddir('host-libraries', None, lib)
643 installdir = self.host_libraries_installdir
644 cmdlist.push_subdesc(lib)
645 cmdlist.create_use_dir(builddir)
646 cfg_cmd = [os.path.join(srcdir, 'configure'),
647 '--prefix=%s' % installdir,
648 '--disable-shared']
649 if extra_opts:
650 cfg_cmd.extend (extra_opts)
651 cmdlist.add_command('configure', cfg_cmd)
652 cmdlist.add_command('build', ['make'])
653 cmdlist.add_command('check', ['make', 'check'])
654 cmdlist.add_command('install', ['make', 'install'])
655 cmdlist.cleanup_dir()
656 cmdlist.pop_subdesc()
657
658 def build_compilers(self, configs):
659 """Build the compilers."""
660 if not configs:
661 self.remove_dirs(os.path.join(self.builddir, 'compilers'))
662 self.remove_dirs(os.path.join(self.installdir, 'compilers'))
663 self.remove_dirs(os.path.join(self.logsdir, 'compilers'))
664 configs = sorted(self.configs.keys())
665 for c in configs:
666 self.configs[c].build()
667
668 def build_glibcs(self, configs):
669 """Build the glibcs."""
670 if not configs:
671 self.remove_dirs(os.path.join(self.builddir, 'glibcs'))
672 self.remove_dirs(os.path.join(self.installdir, 'glibcs'))
673 self.remove_dirs(os.path.join(self.logsdir, 'glibcs'))
674 configs = sorted(self.glibc_configs.keys())
675 for c in configs:
676 self.glibc_configs[c].build()
677
678 def load_versions_json(self):
679 """Load information about source directory versions."""
680 if not os.access(self.versions_json, os.F_OK):
681 self.versions = {}
682 return
683 with open(self.versions_json, 'r') as f:
684 self.versions = json.load(f)
685
686 def store_json(self, data, filename):
687 """Store information in a JSON file."""
688 filename_tmp = filename + '.tmp'
689 with open(filename_tmp, 'w') as f:
690 json.dump(data, f, indent=2, sort_keys=True)
691 os.rename(filename_tmp, filename)
692
693 def store_versions_json(self):
694 """Store information about source directory versions."""
695 self.store_json(self.versions, self.versions_json)
696
697 def set_component_version(self, component, version, explicit, revision):
698 """Set the version information for a component."""
699 self.versions[component] = {'version': version,
700 'explicit': explicit,
701 'revision': revision}
702 self.store_versions_json()
703
704 def checkout(self, versions):
705 """Check out the desired component versions."""
706 default_versions = {'binutils': 'vcs-2.31',
707 'gcc': 'vcs-8',
708 'glibc': 'vcs-mainline',
709 'gmp': '6.1.2',
710 'linux': '4.19',
711 'mpc': '1.1.0',
712 'mpfr': '4.0.1',
713 'mig': 'vcs-mainline',
714 'gnumach': 'vcs-mainline',
715 'hurd': 'vcs-mainline'}
716 use_versions = {}
717 explicit_versions = {}
718 for v in versions:
719 found_v = False
720 for k in default_versions.keys():
721 kx = k + '-'
722 if v.startswith(kx):
723 vx = v[len(kx):]
724 if k in use_versions:
725 print('error: multiple versions for %s' % k)
726 exit(1)
727 use_versions[k] = vx
728 explicit_versions[k] = True
729 found_v = True
730 break
731 if not found_v:
732 print('error: unknown component in %s' % v)
733 exit(1)
734 for k in default_versions.keys():
735 if k not in use_versions:
736 if k in self.versions and self.versions[k]['explicit']:
737 use_versions[k] = self.versions[k]['version']
738 explicit_versions[k] = True
739 else:
740 use_versions[k] = default_versions[k]
741 explicit_versions[k] = False
742 os.makedirs(self.srcdir, exist_ok=True)
743 for k in sorted(default_versions.keys()):
744 update = os.access(self.component_srcdir(k), os.F_OK)
745 v = use_versions[k]
746 if (update and
747 k in self.versions and
748 v != self.versions[k]['version']):
749 if not self.replace_sources:
750 print('error: version of %s has changed from %s to %s, '
751 'use --replace-sources to check out again' %
752 (k, self.versions[k]['version'], v))
753 exit(1)
754 shutil.rmtree(self.component_srcdir(k))
755 update = False
756 if v.startswith('vcs-'):
757 revision = self.checkout_vcs(k, v[4:], update)
758 else:
759 self.checkout_tar(k, v, update)
760 revision = v
761 self.set_component_version(k, v, explicit_versions[k], revision)
762 if self.get_script_text() != self.script_text:
763 # Rerun the checkout process in case the updated script
764 # uses different default versions or new components.
765 self.exec_self()
766
767 def checkout_vcs(self, component, version, update):
768 """Check out the given version of the given component from version
769 control. Return a revision identifier."""
770 if component == 'binutils':
771 git_url = 'git://sourceware.org/git/binutils-gdb.git'
772 if version == 'mainline':
773 git_branch = 'master'
774 else:
775 trans = str.maketrans({'.': '_'})
776 git_branch = 'binutils-%s-branch' % version.translate(trans)
777 return self.git_checkout(component, git_url, git_branch, update)
778 elif component == 'gcc':
779 if version == 'mainline':
780 branch = 'trunk'
781 else:
782 trans = str.maketrans({'.': '_'})
783 branch = 'branches/gcc-%s-branch' % version.translate(trans)
784 svn_url = 'svn://gcc.gnu.org/svn/gcc/%s' % branch
785 return self.gcc_checkout(svn_url, update)
786 elif component == 'glibc':
787 git_url = 'git://sourceware.org/git/glibc.git'
788 if version == 'mainline':
789 git_branch = 'master'
790 else:
791 git_branch = 'release/%s/master' % version
792 r = self.git_checkout(component, git_url, git_branch, update)
793 self.fix_glibc_timestamps()
794 return r
795 elif component == 'gnumach':
796 git_url = 'git://git.savannah.gnu.org/hurd/gnumach.git'
797 git_branch = 'master'
798 r = self.git_checkout(component, git_url, git_branch, update)
799 subprocess.run(['autoreconf', '-i'],
800 cwd=self.component_srcdir(component), check=True)
801 return r
802 elif component == 'mig':
803 git_url = 'git://git.savannah.gnu.org/hurd/mig.git'
804 git_branch = 'master'
805 r = self.git_checkout(component, git_url, git_branch, update)
806 subprocess.run(['autoreconf', '-i'],
807 cwd=self.component_srcdir(component), check=True)
808 return r
809 elif component == 'hurd':
810 git_url = 'git://git.savannah.gnu.org/hurd/hurd.git'
811 git_branch = 'master'
812 r = self.git_checkout(component, git_url, git_branch, update)
813 subprocess.run(['autoconf'],
814 cwd=self.component_srcdir(component), check=True)
815 return r
816 else:
817 print('error: component %s coming from VCS' % component)
818 exit(1)
819
820 def git_checkout(self, component, git_url, git_branch, update):
821 """Check out a component from git. Return a commit identifier."""
822 if update:
823 subprocess.run(['git', 'remote', 'prune', 'origin'],
824 cwd=self.component_srcdir(component), check=True)
825 if self.replace_sources:
826 subprocess.run(['git', 'clean', '-dxfq'],
827 cwd=self.component_srcdir(component), check=True)
828 subprocess.run(['git', 'pull', '-q'],
829 cwd=self.component_srcdir(component), check=True)
830 else:
831 subprocess.run(['git', 'clone', '-q', '-b', git_branch, git_url,
832 self.component_srcdir(component)], check=True)
833 r = subprocess.run(['git', 'rev-parse', 'HEAD'],
834 cwd=self.component_srcdir(component),
835 stdout=subprocess.PIPE,
836 check=True, universal_newlines=True).stdout
837 return r.rstrip()
838
839 def fix_glibc_timestamps(self):
840 """Fix timestamps in a glibc checkout."""
841 # Ensure that builds do not try to regenerate generated files
842 # in the source tree.
843 srcdir = self.component_srcdir('glibc')
844 # These files have Makefile dependencies to regenerate them in
845 # the source tree that may be active during a normal build.
846 # Some other files have such dependencies but do not need to
847 # be touched because nothing in a build depends on the files
848 # in question.
849 for f in ('locale/C-translit.h', 'sysdeps/gnu/errlist.c',
850 'sysdeps/mach/hurd/bits/errno.h',
851 'sysdeps/sparc/sparc32/rem.S',
852 'sysdeps/sparc/sparc32/sdiv.S',
853 'sysdeps/sparc/sparc32/udiv.S',
854 'sysdeps/sparc/sparc32/urem.S'):
855 to_touch = os.path.join(srcdir, f)
856 subprocess.run(['touch', '-c', to_touch], check=True)
857 for dirpath, dirnames, filenames in os.walk(srcdir):
858 for f in filenames:
859 if (f == 'configure' or
860 f == 'preconfigure' or
861 f.endswith('-kw.h')):
862 to_touch = os.path.join(dirpath, f)
863 subprocess.run(['touch', to_touch], check=True)
864
865 def gcc_checkout(self, svn_url, update):
866 """Check out GCC from SVN. Return the revision number."""
867 if not update:
868 subprocess.run(['svn', 'co', '-q', svn_url,
869 self.component_srcdir('gcc')], check=True)
870 subprocess.run(['contrib/gcc_update', '--silent'],
871 cwd=self.component_srcdir('gcc'), check=True)
872 r = subprocess.run(['svnversion', self.component_srcdir('gcc')],
873 stdout=subprocess.PIPE,
874 check=True, universal_newlines=True).stdout
875 return r.rstrip()
876
877 def checkout_tar(self, component, version, update):
878 """Check out the given version of the given component from a
879 tarball."""
880 if update:
881 return
882 url_map = {'binutils': 'https://ftp.gnu.org/gnu/binutils/binutils-%(version)s.tar.bz2',
883 'gcc': 'https://ftp.gnu.org/gnu/gcc/gcc-%(version)s/gcc-%(version)s.tar.gz',
884 'gmp': 'https://ftp.gnu.org/gnu/gmp/gmp-%(version)s.tar.xz',
885 'linux': 'https://www.kernel.org/pub/linux/kernel/v4.x/linux-%(version)s.tar.xz',
886 'mpc': 'https://ftp.gnu.org/gnu/mpc/mpc-%(version)s.tar.gz',
887 'mpfr': 'https://ftp.gnu.org/gnu/mpfr/mpfr-%(version)s.tar.xz',
888 'mig': 'https://ftp.gnu.org/gnu/mig/mig-%(version)s.tar.bz2',
889 'gnumach': 'https://ftp.gnu.org/gnu/gnumach/gnumach-%(version)s.tar.bz2',
890 'hurd': 'https://ftp.gnu.org/gnu/hurd/hurd-%(version)s.tar.bz2'}
891 if component not in url_map:
892 print('error: component %s coming from tarball' % component)
893 exit(1)
894 url = url_map[component] % {'version': version}
895 filename = os.path.join(self.srcdir, url.split('/')[-1])
896 response = urllib.request.urlopen(url)
897 data = response.read()
898 with open(filename, 'wb') as f:
899 f.write(data)
900 subprocess.run(['tar', '-C', self.srcdir, '-x', '-f', filename],
901 check=True)
902 os.rename(os.path.join(self.srcdir, '%s-%s' % (component, version)),
903 self.component_srcdir(component))
904 os.remove(filename)
905
906 def load_build_state_json(self):
907 """Load information about the state of previous builds."""
908 if os.access(self.build_state_json, os.F_OK):
909 with open(self.build_state_json, 'r') as f:
910 self.build_state = json.load(f)
911 else:
912 self.build_state = {}
913 for k in ('host-libraries', 'compilers', 'glibcs'):
914 if k not in self.build_state:
915 self.build_state[k] = {}
916 if 'build-time' not in self.build_state[k]:
917 self.build_state[k]['build-time'] = ''
918 if 'build-versions' not in self.build_state[k]:
919 self.build_state[k]['build-versions'] = {}
920 if 'build-results' not in self.build_state[k]:
921 self.build_state[k]['build-results'] = {}
922 if 'result-changes' not in self.build_state[k]:
923 self.build_state[k]['result-changes'] = {}
924 if 'ever-passed' not in self.build_state[k]:
925 self.build_state[k]['ever-passed'] = []
926
927 def store_build_state_json(self):
928 """Store information about the state of previous builds."""
929 self.store_json(self.build_state, self.build_state_json)
930
931 def clear_last_build_state(self, action):
932 """Clear information about the state of part of the build."""
933 # We clear the last build time and versions when starting a
934 # new build. The results of the last build are kept around,
935 # as comparison is still meaningful if this build is aborted
936 # and a new one started.
937 self.build_state[action]['build-time'] = ''
938 self.build_state[action]['build-versions'] = {}
939 self.store_build_state_json()
940
941 def update_build_state(self, action, build_time, build_versions):
942 """Update the build state after a build."""
943 build_time = build_time.replace(microsecond=0)
944 self.build_state[action]['build-time'] = str(build_time)
945 self.build_state[action]['build-versions'] = build_versions
946 build_results = {}
947 for log in self.status_log_list:
948 with open(log, 'r') as f:
949 log_text = f.read()
950 log_text = log_text.rstrip()
951 m = re.fullmatch('([A-Z]+): (.*)', log_text)
952 result = m.group(1)
953 test_name = m.group(2)
954 assert test_name not in build_results
955 build_results[test_name] = result
956 old_build_results = self.build_state[action]['build-results']
957 self.build_state[action]['build-results'] = build_results
958 result_changes = {}
959 all_tests = set(old_build_results.keys()) | set(build_results.keys())
960 for t in all_tests:
961 if t in old_build_results:
962 old_res = old_build_results[t]
963 else:
964 old_res = '(New test)'
965 if t in build_results:
966 new_res = build_results[t]
967 else:
968 new_res = '(Test removed)'
969 if old_res != new_res:
970 result_changes[t] = '%s -> %s' % (old_res, new_res)
971 self.build_state[action]['result-changes'] = result_changes
972 old_ever_passed = {t for t in self.build_state[action]['ever-passed']
973 if t in build_results}
974 new_passes = {t for t in build_results if build_results[t] == 'PASS'}
975 self.build_state[action]['ever-passed'] = sorted(old_ever_passed |
976 new_passes)
977 self.store_build_state_json()
978
979 def load_bot_config_json(self):
980 """Load bot configuration."""
981 with open(self.bot_config_json, 'r') as f:
982 self.bot_config = json.load(f)
983
984 def part_build_old(self, action, delay):
985 """Return whether the last build for a given action was at least a
986 given number of seconds ago, or does not have a time recorded."""
987 old_time_str = self.build_state[action]['build-time']
988 if not old_time_str:
989 return True
990 old_time = datetime.datetime.strptime(old_time_str,
991 '%Y-%m-%d %H:%M:%S')
992 new_time = datetime.datetime.utcnow()
993 delta = new_time - old_time
994 return delta.total_seconds() >= delay
995
996 def bot_cycle(self):
997 """Run a single round of checkout and builds."""
998 print('Bot cycle starting %s.' % str(datetime.datetime.utcnow()))
999 self.load_bot_config_json()
1000 actions = ('host-libraries', 'compilers', 'glibcs')
1001 self.bot_run_self(['--replace-sources'], 'checkout')
1002 self.load_versions_json()
1003 if self.get_script_text() != self.script_text:
1004 print('Script changed, re-execing.')
1005 # On script change, all parts of the build should be rerun.
1006 for a in actions:
1007 self.clear_last_build_state(a)
1008 self.exec_self()
1009 check_components = {'host-libraries': ('gmp', 'mpfr', 'mpc'),
1010 'compilers': ('binutils', 'gcc', 'glibc', 'linux',
1011 'mig', 'gnumach', 'hurd'),
1012 'glibcs': ('glibc',)}
1013 must_build = {}
1014 for a in actions:
1015 build_vers = self.build_state[a]['build-versions']
1016 must_build[a] = False
1017 if not self.build_state[a]['build-time']:
1018 must_build[a] = True
1019 old_vers = {}
1020 new_vers = {}
1021 for c in check_components[a]:
1022 if c in build_vers:
1023 old_vers[c] = build_vers[c]
1024 new_vers[c] = {'version': self.versions[c]['version'],
1025 'revision': self.versions[c]['revision']}
1026 if new_vers == old_vers:
1027 print('Versions for %s unchanged.' % a)
1028 else:
1029 print('Versions changed or rebuild forced for %s.' % a)
1030 if a == 'compilers' and not self.part_build_old(
1031 a, self.bot_config['compilers-rebuild-delay']):
1032 print('Not requiring rebuild of compilers this soon.')
1033 else:
1034 must_build[a] = True
1035 if must_build['host-libraries']:
1036 must_build['compilers'] = True
1037 if must_build['compilers']:
1038 must_build['glibcs'] = True
1039 for a in actions:
1040 if must_build[a]:
1041 print('Must rebuild %s.' % a)
1042 self.clear_last_build_state(a)
1043 else:
1044 print('No need to rebuild %s.' % a)
1045 if os.access(self.logsdir, os.F_OK):
1046 shutil.rmtree(self.logsdir_old, ignore_errors=True)
1047 shutil.copytree(self.logsdir, self.logsdir_old)
1048 for a in actions:
1049 if must_build[a]:
1050 build_time = datetime.datetime.utcnow()
1051 print('Rebuilding %s at %s.' % (a, str(build_time)))
1052 self.bot_run_self([], a)
1053 self.load_build_state_json()
1054 self.bot_build_mail(a, build_time)
1055 print('Bot cycle done at %s.' % str(datetime.datetime.utcnow()))
1056
1057 def bot_build_mail(self, action, build_time):
1058 """Send email with the results of a build."""
1059 if not ('email-from' in self.bot_config and
1060 'email-server' in self.bot_config and
1061 'email-subject' in self.bot_config and
1062 'email-to' in self.bot_config):
1063 if not self.email_warning:
1064 print("Email not configured, not sending.")
1065 self.email_warning = True
1066 return
1067
1068 build_time = build_time.replace(microsecond=0)
1069 subject = (self.bot_config['email-subject'] %
1070 {'action': action,
1071 'build-time': str(build_time)})
1072 results = self.build_state[action]['build-results']
1073 changes = self.build_state[action]['result-changes']
1074 ever_passed = set(self.build_state[action]['ever-passed'])
1075 versions = self.build_state[action]['build-versions']
1076 new_regressions = {k for k in changes if changes[k] == 'PASS -> FAIL'}
1077 all_regressions = {k for k in ever_passed if results[k] == 'FAIL'}
1078 all_fails = {k for k in results if results[k] == 'FAIL'}
1079 if new_regressions:
1080 new_reg_list = sorted(['FAIL: %s' % k for k in new_regressions])
1081 new_reg_text = ('New regressions:\n\n%s\n\n' %
1082 '\n'.join(new_reg_list))
1083 else:
1084 new_reg_text = ''
1085 if all_regressions:
1086 all_reg_list = sorted(['FAIL: %s' % k for k in all_regressions])
1087 all_reg_text = ('All regressions:\n\n%s\n\n' %
1088 '\n'.join(all_reg_list))
1089 else:
1090 all_reg_text = ''
1091 if all_fails:
1092 all_fail_list = sorted(['FAIL: %s' % k for k in all_fails])
1093 all_fail_text = ('All failures:\n\n%s\n\n' %
1094 '\n'.join(all_fail_list))
1095 else:
1096 all_fail_text = ''
1097 if changes:
1098 changes_list = sorted(changes.keys())
1099 changes_list = ['%s: %s' % (changes[k], k) for k in changes_list]
1100 changes_text = ('All changed results:\n\n%s\n\n' %
1101 '\n'.join(changes_list))
1102 else:
1103 changes_text = ''
1104 results_text = (new_reg_text + all_reg_text + all_fail_text +
1105 changes_text)
1106 if not results_text:
1107 results_text = 'Clean build with unchanged results.\n\n'
1108 versions_list = sorted(versions.keys())
1109 versions_list = ['%s: %s (%s)' % (k, versions[k]['version'],
1110 versions[k]['revision'])
1111 for k in versions_list]
1112 versions_text = ('Component versions for this build:\n\n%s\n' %
1113 '\n'.join(versions_list))
1114 body_text = results_text + versions_text
1115 msg = email.mime.text.MIMEText(body_text)
1116 msg['Subject'] = subject
1117 msg['From'] = self.bot_config['email-from']
1118 msg['To'] = self.bot_config['email-to']
1119 msg['Message-ID'] = email.utils.make_msgid()
1120 msg['Date'] = email.utils.format_datetime(datetime.datetime.utcnow())
1121 with smtplib.SMTP(self.bot_config['email-server']) as s:
1122 s.send_message(msg)
1123
1124 def bot_run_self(self, opts, action, check=True):
1125 """Run a copy of this script with given options."""
1126 cmd = [sys.executable, sys.argv[0], '--keep=none',
1127 '-j%d' % self.parallelism]
1128 if self.full_gcc:
1129 cmd.append('--full-gcc')
1130 cmd.extend(opts)
1131 cmd.extend([self.topdir, action])
1132 sys.stdout.flush()
1133 subprocess.run(cmd, check=check)
1134
1135 def bot(self):
1136 """Run repeated rounds of checkout and builds."""
1137 while True:
1138 self.load_bot_config_json()
1139 if not self.bot_config['run']:
1140 print('Bot exiting by request.')
1141 exit(0)
1142 self.bot_run_self([], 'bot-cycle', check=False)
1143 self.load_bot_config_json()
1144 if not self.bot_config['run']:
1145 print('Bot exiting by request.')
1146 exit(0)
1147 time.sleep(self.bot_config['delay'])
1148 if self.get_script_text() != self.script_text:
1149 print('Script changed, bot re-execing.')
1150 self.exec_self()
1151
1152
1153 class Config(object):
1154 """A configuration for building a compiler and associated libraries."""
1155
1156 def __init__(self, ctx, arch, os_name, variant=None, gcc_cfg=None,
1157 first_gcc_cfg=None, glibcs=None, extra_glibcs=None):
1158 """Initialize a Config object."""
1159 self.ctx = ctx
1160 self.arch = arch
1161 self.os = os_name
1162 self.variant = variant
1163 if variant is None:
1164 self.name = '%s-%s' % (arch, os_name)
1165 else:
1166 self.name = '%s-%s-%s' % (arch, os_name, variant)
1167 self.triplet = '%s-glibc-%s' % (arch, os_name)
1168 if gcc_cfg is None:
1169 self.gcc_cfg = []
1170 else:
1171 self.gcc_cfg = gcc_cfg
1172 if first_gcc_cfg is None:
1173 self.first_gcc_cfg = []
1174 else:
1175 self.first_gcc_cfg = first_gcc_cfg
1176 if glibcs is None:
1177 glibcs = [{'variant': variant}]
1178 if extra_glibcs is None:
1179 extra_glibcs = []
1180 glibcs = [Glibc(self, **g) for g in glibcs]
1181 extra_glibcs = [Glibc(self, **g) for g in extra_glibcs]
1182 self.all_glibcs = glibcs + extra_glibcs
1183 self.compiler_glibcs = glibcs
1184 self.installdir = ctx.compiler_installdir(self.name)
1185 self.bindir = ctx.compiler_bindir(self.name)
1186 self.sysroot = ctx.compiler_sysroot(self.name)
1187 self.builddir = os.path.join(ctx.builddir, 'compilers', self.name)
1188 self.logsdir = os.path.join(ctx.logsdir, 'compilers', self.name)
1189
1190 def component_builddir(self, component):
1191 """Return the directory to use for a (non-glibc) build."""
1192 return self.ctx.component_builddir('compilers', self.name, component)
1193
1194 def build(self):
1195 """Generate commands to build this compiler."""
1196 self.ctx.remove_recreate_dirs(self.installdir, self.builddir,
1197 self.logsdir)
1198 cmdlist = CommandList('compilers-%s' % self.name, self.ctx.keep)
1199 cmdlist.add_command('check-host-libraries',
1200 ['test', '-f',
1201 os.path.join(self.ctx.host_libraries_installdir,
1202 'ok')])
1203 cmdlist.use_path(self.bindir)
1204 self.build_cross_tool(cmdlist, 'binutils', 'binutils',
1205 ['--disable-gdb',
1206 '--disable-libdecnumber',
1207 '--disable-readline',
1208 '--disable-sim'])
1209 if self.os.startswith('linux'):
1210 self.install_linux_headers(cmdlist)
1211 self.build_gcc(cmdlist, True)
1212 if self.os == 'gnu':
1213 self.install_gnumach_headers(cmdlist)
1214 self.build_cross_tool(cmdlist, 'mig', 'mig')
1215 self.install_hurd_headers(cmdlist)
1216 for g in self.compiler_glibcs:
1217 cmdlist.push_subdesc('glibc')
1218 cmdlist.push_subdesc(g.name)
1219 g.build_glibc(cmdlist, True)
1220 cmdlist.pop_subdesc()
1221 cmdlist.pop_subdesc()
1222 self.build_gcc(cmdlist, False)
1223 cmdlist.add_command('done', ['touch',
1224 os.path.join(self.installdir, 'ok')])
1225 self.ctx.add_makefile_cmdlist('compilers-%s' % self.name, cmdlist,
1226 self.logsdir)
1227
1228 def build_cross_tool(self, cmdlist, tool_src, tool_build, extra_opts=None):
1229 """Build one cross tool."""
1230 srcdir = self.ctx.component_srcdir(tool_src)
1231 builddir = self.component_builddir(tool_build)
1232 cmdlist.push_subdesc(tool_build)
1233 cmdlist.create_use_dir(builddir)
1234 cfg_cmd = [os.path.join(srcdir, 'configure'),
1235 '--prefix=%s' % self.installdir,
1236 '--build=%s' % self.ctx.build_triplet,
1237 '--host=%s' % self.ctx.build_triplet,
1238 '--target=%s' % self.triplet,
1239 '--with-sysroot=%s' % self.sysroot]
1240 if extra_opts:
1241 cfg_cmd.extend(extra_opts)
1242 cmdlist.add_command('configure', cfg_cmd)
1243 cmdlist.add_command('build', ['make'])
1244 # Parallel "make install" for GCC has race conditions that can
1245 # cause it to fail; see
1246 # <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=42980>. Such
1247 # problems are not known for binutils, but doing the
1248 # installation in parallel within a particular toolchain build
1249 # (as opposed to installation of one toolchain from
1250 # build-many-glibcs.py running in parallel to the installation
1251 # of other toolchains being built) is not known to be
1252 # significantly beneficial, so it is simplest just to disable
1253 # parallel install for cross tools here.
1254 cmdlist.add_command('install', ['make', '-j1', 'install'])
1255 cmdlist.cleanup_dir()
1256 cmdlist.pop_subdesc()
1257
1258 def install_linux_headers(self, cmdlist):
1259 """Install Linux kernel headers."""
1260 arch_map = {'aarch64': 'arm64',
1261 'alpha': 'alpha',
1262 'arm': 'arm',
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:])