]> git.ipfire.org Git - thirdparty/glibc.git/blame - scripts/build-many-glibcs.py
Update copyright dates with scripts/update-copyrights.
[thirdparty/glibc.git] / scripts / build-many-glibcs.py
CommitLineData
14f95a42
JM
1#!/usr/bin/python3
2# Build many configurations of glibc.
04277e02 3# Copyright (C) 2016-2019 Free Software Foundation, Inc.
14f95a42
JM
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
22This script takes as arguments a directory name (containing a src
23subdirectory with sources of the relevant toolchain components) and a
24description of what to do: 'checkout', to check out sources into that
4d602bce 25directory, 'bot-cycle', to run a series of checkout and build steps,
a1f6a9ab
JM
26'bot', to run 'bot-cycle' repeatedly, 'host-libraries', to build
27libraries required by the toolchain, 'compilers', to build
28cross-compilers for various configurations, or 'glibcs', to build
29glibc for various configurations and run the compilation parts of the
30testsuite. Subsequent arguments name the versions of components to
31check out (<component>-<version), for 'checkout', or, for actions
32other than 'checkout' and 'bot-cycle', name configurations for which
33compilers or glibc are to be built.
4d602bce 34
14f95a42
JM
35"""
36
37import argparse
bf469f0c 38import datetime
4d602bce
JM
39import email.mime.text
40import email.utils
02c78f02 41import json
14f95a42 42import os
14f95a42
JM
43import re
44import shutil
4d602bce 45import smtplib
14f95a42
JM
46import stat
47import subprocess
48import sys
a1f6a9ab 49import time
14f95a42
JM
50import urllib.request
51
64235ccc
SN
52try:
53 subprocess.run
54except:
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
14f95a42 78
0c95f51d 79class Context(object):
14f95a42
JM
80 """The global state associated with builds in a given directory."""
81
297635d8 82 def __init__(self, topdir, parallelism, keep, replace_sources, strip,
b1176270 83 full_gcc, action):
14f95a42
JM
84 """Initialize the context."""
85 self.topdir = topdir
86 self.parallelism = parallelism
87 self.keep = keep
02c78f02 88 self.replace_sources = replace_sources
297635d8 89 self.strip = strip
b1176270 90 self.full_gcc = full_gcc
14f95a42 91 self.srcdir = os.path.join(topdir, 'src')
02c78f02 92 self.versions_json = os.path.join(self.srcdir, 'versions.json')
bf469f0c 93 self.build_state_json = os.path.join(topdir, 'build-state.json')
4d602bce 94 self.bot_config_json = os.path.join(topdir, 'bot-config.json')
14f95a42
JM
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')
a1f6a9ab 100 self.logsdir_old = os.path.join(topdir, 'logs-old')
14f95a42
JM
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')
a1c9859b 104 self.script_text = self.get_script_text()
14f95a42
JM
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()
02c78f02 112 self.load_versions_json()
bf469f0c
JM
113 self.load_build_state_json()
114 self.status_log_list = []
f0166c16 115 self.email_warning = False
14f95a42 116
a1c9859b
JM
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."""
36820ce9 124 sys.stdout.flush()
a1c9859b
JM
125 os.execv(sys.executable, [sys.executable] + sys.argv)
126
14f95a42
JM
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',
6cb86fd2
SN
153 os_name='linux-gnu',
154 extra_glibcs=[{'variant': 'disable-multi-arch',
155 'cfg': ['--disable-multi-arch']}])
14f95a42
JM
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',
3d265601 169 os_name='linux-gnueabihf',
8e52f573 170 gcc_cfg=['--with-float=hard', '--with-cpu=arm926ej-s'],
3d265601 171 extra_glibcs=[{'variant': 'v7a',
8e52f573 172 'ccopts': '-march=armv7-a -mfpu=vfpv3'},
3d265601 173 {'variant': 'v7a-disable-multi-arch',
8e52f573 174 'ccopts': '-march=armv7-a -mfpu=vfpv3',
3d265601 175 'cfg': ['--disable-multi-arch']}])
14f95a42 176 self.add_config(arch='armeb',
5170fa49 177 os_name='linux-gnueabihf',
8e52f573 178 gcc_cfg=['--with-float=hard', '--with-cpu=arm926ej-s'])
14f95a42
JM
179 self.add_config(arch='armeb',
180 os_name='linux-gnueabihf',
181 variant='be8',
8e52f573
JM
182 gcc_cfg=['--with-float=hard', '--with-arch=armv7-a',
183 '--with-fpu=vfpv3'])
5f72b005
MH
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'])
14f95a42
JM
191 self.add_config(arch='hppa',
192 os_name='linux-gnu')
394b5bac
JM
193 self.add_config(arch='i686',
194 os_name='gnu')
14f95a42
JM
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'])
3005b705
JM
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'])
14f95a42
JM
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'],
8df5d347 228 glibcs=[{'variant': 'n32-soft'},
14f95a42
JM
229 {'variant': 'soft',
230 'arch': 'mips',
8df5d347 231 'ccopts': '-mabi=32'},
14f95a42 232 {'variant': 'n64-soft',
8df5d347 233 'ccopts': '-mabi=64'}])
14f95a42
JM
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'],
8df5d347 253 glibcs=[{'variant': 'n32-nan2008-soft'},
14f95a42
JM
254 {'variant': 'nan2008-soft',
255 'arch': 'mips',
8df5d347 256 'ccopts': '-mabi=32'},
14f95a42 257 {'variant': 'n64-nan2008-soft',
8df5d347 258 'ccopts': '-mabi=64'}])
14f95a42
JM
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'],
8df5d347 271 glibcs=[{'variant': 'n32-soft'},
14f95a42
JM
272 {'variant': 'soft',
273 'arch': 'mipsel',
8df5d347 274 'ccopts': '-mabi=32'},
14f95a42 275 {'variant': 'n64-soft',
8df5d347 276 'ccopts': '-mabi=64'}])
14f95a42
JM
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'],
8df5d347 296 glibcs=[{'variant': 'n32-nan2008-soft'},
14f95a42
JM
297 {'variant': 'nan2008-soft',
298 'arch': 'mipsel',
8df5d347 299 'ccopts': '-mabi=32'},
14f95a42 300 {'variant': 'n64-nan2008-soft',
8df5d347 301 'ccopts': '-mabi=64'}])
14f95a42
JM
302 self.add_config(arch='nios2',
303 os_name='linux-gnu')
304 self.add_config(arch='powerpc',
305 os_name='linux-gnu',
4179178b
JM
306 gcc_cfg=['--disable-multilib', '--enable-secureplt'],
307 extra_glibcs=[{'variant': 'power4',
308 'ccopts': '-mcpu=power4',
309 'cfg': ['--with-cpu=power4']}])
14f95a42
JM
310 self.add_config(arch='powerpc',
311 os_name='linux-gnu',
312 variant='soft',
313 gcc_cfg=['--disable-multilib', '--with-float=soft',
8df5d347 314 '--enable-secureplt'])
14f95a42
JM
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',
d6397de3 324 '--enable-e500-double', '--enable-obsolete'])
14f95a42
JM
325 self.add_config(arch='powerpc',
326 os_name='linux-gnuspe',
327 variant='e500v1',
d6397de3
JM
328 gcc_cfg=['--disable-multilib', '--enable-secureplt',
329 '--enable-obsolete'])
0fd5d876
PD
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'])
14f95a42
JM
345 self.add_config(arch='s390x',
346 os_name='linux-gnu',
347 glibcs=[{},
348 {'arch': 's390', 'ccopts': '-m31'}])
14f95a42 349 self.add_config(arch='sh3',
c89721e2 350 os_name='linux-gnu')
14f95a42 351 self.add_config(arch='sh3eb',
c89721e2 352 os_name='linux-gnu')
14f95a42 353 self.add_config(arch='sh4',
c89721e2 354 os_name='linux-gnu')
14f95a42 355 self.add_config(arch='sh4eb',
c89721e2 356 os_name='linux-gnu')
14f95a42
JM
357 self.add_config(arch='sh4',
358 os_name='linux-gnu',
359 variant='soft',
8df5d347 360 gcc_cfg=['--without-fp'])
14f95a42
JM
361 self.add_config(arch='sh4eb',
362 os_name='linux-gnu',
363 variant='soft',
8df5d347 364 gcc_cfg=['--without-fp'])
14f95a42
JM
365 self.add_config(arch='sparc64',
366 os_name='linux-gnu',
367 glibcs=[{},
368 {'arch': 'sparcv9',
0c097378
JM
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']}])
14f95a42
JM
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']},
7244f13d
JM
384 {'variant': 'enable-obsolete',
385 'cfg': ['--enable-obsolete-rpc',
386 '--enable-obsolete-nsl']},
1a49fc59
L
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']},
14f95a42
JM
396 {'variant': 'disable-multi-arch',
397 'arch': 'i686',
398 'ccopts': '-m32 -march=i686',
399 'cfg': ['--disable-multi-arch']},
7244f13d
JM
400 {'variant': 'enable-obsolete',
401 'arch': 'i686',
402 'ccopts': '-m32 -march=i686',
403 'cfg': ['--enable-obsolete-rpc',
404 '--enable-obsolete-nsl']},
14f95a42
JM
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
4d602bce
JM
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
a1f6a9ab
JM
467 if action == 'bot':
468 if configs:
469 print('error: configurations specified for bot')
470 exit(1)
471 self.bot()
472 return
bf469f0c
JM
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 = {}
14f95a42
JM
482 self.build_host_libraries()
483 elif action == 'compilers':
394b5bac
JM
484 build_components = ('binutils', 'gcc', 'glibc', 'linux', 'mig',
485 'gnumach', 'hurd')
bf469f0c
JM
486 old_components = ('gmp', 'mpfr', 'mpc')
487 old_versions = self.build_state['host-libraries']['build-versions']
14f95a42
JM
488 self.build_compilers(configs)
489 else:
bf469f0c 490 build_components = ('glibc',)
394b5bac
JM
491 old_components = ('gmp', 'mpfr', 'mpc', 'binutils', 'gcc', 'linux',
492 'mig', 'gnumach', 'hurd')
bf469f0c 493 old_versions = self.build_state['compilers']['build-versions']
14f95a42
JM
494 self.build_glibcs(configs)
495 self.write_files()
496 self.do_build()
bf469f0c
JM
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)
14f95a42
JM
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))
bf469f0c 529 self.status_log_list.extend(cmdlist.status_logs(logsdir))
14f95a42
JM
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'
8885f979
JM
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'
14f95a42
JM
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)
0c95f51d
JM
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)
14f95a42
JM
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)
0c95f51d 623 os.chmod(self.save_logs, mode_exec)
14f95a42
JM
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
02c78f02
JM
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
14f95a42
JM
711 def checkout(self, versions):
712 """Check out the desired component versions."""
b99f1c95 713 default_versions = {'binutils': 'vcs-2.31',
42a2bf58 714 'gcc': 'vcs-8',
14f95a42 715 'glibc': 'vcs-mainline',
f0107724 716 'gmp': '6.1.2',
01047fa6 717 'linux': '4.20',
16d0f6ac 718 'mpc': '1.1.0',
d80441dd 719 'mpfr': '4.0.1',
f4d3cee3 720 'mig': 'vcs-mainline',
19fd8d0d 721 'gnumach': 'vcs-mainline',
47c4b4b0 722 'hurd': 'vcs-mainline'}
14f95a42 723 use_versions = {}
02c78f02 724 explicit_versions = {}
14f95a42
JM
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
02c78f02 735 explicit_versions[k] = True
14f95a42
JM
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:
02c78f02
JM
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
14f95a42
JM
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]
02c78f02
JM
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
14f95a42 763 if v.startswith('vcs-'):
02c78f02 764 revision = self.checkout_vcs(k, v[4:], update)
14f95a42
JM
765 else:
766 self.checkout_tar(k, v, update)
02c78f02
JM
767 revision = v
768 self.set_component_version(k, v, explicit_versions[k], revision)
a1c9859b
JM
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()
14f95a42
JM
773
774 def checkout_vcs(self, component, version, update):
775 """Check out the given version of the given component from version
02c78f02 776 control. Return a revision identifier."""
14f95a42
JM
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)
02c78f02 784 return self.git_checkout(component, git_url, git_branch, update)
14f95a42
JM
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
02c78f02 792 return self.gcc_checkout(svn_url, update)
14f95a42
JM
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
02c78f02 799 r = self.git_checkout(component, git_url, git_branch, update)
14f95a42 800 self.fix_glibc_timestamps()
02c78f02 801 return r
19fd8d0d
ST
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
f4d3cee3
ST
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
f9015003
ST
816 elif component == 'hurd':
817 git_url = 'git://git.savannah.gnu.org/hurd/hurd.git'
818 git_branch = 'master'
48130690
ST
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
14f95a42
JM
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):
02c78f02 828 """Check out a component from git. Return a commit identifier."""
14f95a42
JM
829 if update:
830 subprocess.run(['git', 'remote', 'prune', 'origin'],
831 cwd=self.component_srcdir(component), check=True)
e3aa6999
JM
832 if self.replace_sources:
833 subprocess.run(['git', 'clean', '-dxfq'],
834 cwd=self.component_srcdir(component), check=True)
14f95a42
JM
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)
02c78f02
JM
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()
14f95a42
JM
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')
2bd0bfcc
JM
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.
053c52b1 856 for f in ('sysdeps/gnu/errlist.c',
2bd0bfcc
JM
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)
14f95a42
JM
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):
02c78f02 873 """Check out GCC from SVN. Return the revision number."""
14f95a42
JM
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)
02c78f02
JM
879 r = subprocess.run(['svnversion', self.component_srcdir('gcc')],
880 stdout=subprocess.PIPE,
881 check=True, universal_newlines=True).stdout
882 return r.rstrip()
14f95a42
JM
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',
c8fabb84 890 'gcc': 'https://ftp.gnu.org/gnu/gcc/gcc-%(version)s/gcc-%(version)s.tar.gz',
14f95a42
JM
891 'gmp': 'https://ftp.gnu.org/gnu/gmp/gmp-%(version)s.tar.xz',
892 'linux': 'https://www.kernel.org/pub/linux/kernel/v4.x/linux-%(version)s.tar.xz',
893 'mpc': 'https://ftp.gnu.org/gnu/mpc/mpc-%(version)s.tar.gz',
394b5bac
JM
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'}
14f95a42
JM
898 if component not in url_map:
899 print('error: component %s coming from tarball' % component)
900 exit(1)
901 url = url_map[component] % {'version': version}
902 filename = os.path.join(self.srcdir, url.split('/')[-1])
903 response = urllib.request.urlopen(url)
904 data = response.read()
905 with open(filename, 'wb') as f:
906 f.write(data)
907 subprocess.run(['tar', '-C', self.srcdir, '-x', '-f', filename],
908 check=True)
909 os.rename(os.path.join(self.srcdir, '%s-%s' % (component, version)),
910 self.component_srcdir(component))
911 os.remove(filename)
912
bf469f0c
JM
913 def load_build_state_json(self):
914 """Load information about the state of previous builds."""
915 if os.access(self.build_state_json, os.F_OK):
916 with open(self.build_state_json, 'r') as f:
917 self.build_state = json.load(f)
918 else:
919 self.build_state = {}
920 for k in ('host-libraries', 'compilers', 'glibcs'):
921 if k not in self.build_state:
922 self.build_state[k] = {}
923 if 'build-time' not in self.build_state[k]:
924 self.build_state[k]['build-time'] = ''
925 if 'build-versions' not in self.build_state[k]:
926 self.build_state[k]['build-versions'] = {}
927 if 'build-results' not in self.build_state[k]:
928 self.build_state[k]['build-results'] = {}
929 if 'result-changes' not in self.build_state[k]:
930 self.build_state[k]['result-changes'] = {}
931 if 'ever-passed' not in self.build_state[k]:
932 self.build_state[k]['ever-passed'] = []
933
934 def store_build_state_json(self):
935 """Store information about the state of previous builds."""
936 self.store_json(self.build_state, self.build_state_json)
937
938 def clear_last_build_state(self, action):
939 """Clear information about the state of part of the build."""
940 # We clear the last build time and versions when starting a
941 # new build. The results of the last build are kept around,
942 # as comparison is still meaningful if this build is aborted
943 # and a new one started.
944 self.build_state[action]['build-time'] = ''
945 self.build_state[action]['build-versions'] = {}
946 self.store_build_state_json()
947
948 def update_build_state(self, action, build_time, build_versions):
949 """Update the build state after a build."""
950 build_time = build_time.replace(microsecond=0)
951 self.build_state[action]['build-time'] = str(build_time)
952 self.build_state[action]['build-versions'] = build_versions
953 build_results = {}
954 for log in self.status_log_list:
955 with open(log, 'r') as f:
956 log_text = f.read()
957 log_text = log_text.rstrip()
958 m = re.fullmatch('([A-Z]+): (.*)', log_text)
959 result = m.group(1)
960 test_name = m.group(2)
961 assert test_name not in build_results
962 build_results[test_name] = result
963 old_build_results = self.build_state[action]['build-results']
964 self.build_state[action]['build-results'] = build_results
965 result_changes = {}
966 all_tests = set(old_build_results.keys()) | set(build_results.keys())
967 for t in all_tests:
968 if t in old_build_results:
969 old_res = old_build_results[t]
970 else:
971 old_res = '(New test)'
972 if t in build_results:
973 new_res = build_results[t]
974 else:
975 new_res = '(Test removed)'
976 if old_res != new_res:
977 result_changes[t] = '%s -> %s' % (old_res, new_res)
978 self.build_state[action]['result-changes'] = result_changes
979 old_ever_passed = {t for t in self.build_state[action]['ever-passed']
980 if t in build_results}
981 new_passes = {t for t in build_results if build_results[t] == 'PASS'}
982 self.build_state[action]['ever-passed'] = sorted(old_ever_passed |
983 new_passes)
984 self.store_build_state_json()
985
4d602bce
JM
986 def load_bot_config_json(self):
987 """Load bot configuration."""
988 with open(self.bot_config_json, 'r') as f:
989 self.bot_config = json.load(f)
990
991 def part_build_old(self, action, delay):
992 """Return whether the last build for a given action was at least a
993 given number of seconds ago, or does not have a time recorded."""
994 old_time_str = self.build_state[action]['build-time']
995 if not old_time_str:
996 return True
997 old_time = datetime.datetime.strptime(old_time_str,
998 '%Y-%m-%d %H:%M:%S')
999 new_time = datetime.datetime.utcnow()
1000 delta = new_time - old_time
1001 return delta.total_seconds() >= delay
1002
1003 def bot_cycle(self):
1004 """Run a single round of checkout and builds."""
1005 print('Bot cycle starting %s.' % str(datetime.datetime.utcnow()))
1006 self.load_bot_config_json()
1007 actions = ('host-libraries', 'compilers', 'glibcs')
1008 self.bot_run_self(['--replace-sources'], 'checkout')
1009 self.load_versions_json()
1010 if self.get_script_text() != self.script_text:
1011 print('Script changed, re-execing.')
1012 # On script change, all parts of the build should be rerun.
1013 for a in actions:
1014 self.clear_last_build_state(a)
1015 self.exec_self()
1016 check_components = {'host-libraries': ('gmp', 'mpfr', 'mpc'),
394b5bac
JM
1017 'compilers': ('binutils', 'gcc', 'glibc', 'linux',
1018 'mig', 'gnumach', 'hurd'),
4d602bce
JM
1019 'glibcs': ('glibc',)}
1020 must_build = {}
1021 for a in actions:
1022 build_vers = self.build_state[a]['build-versions']
1023 must_build[a] = False
1024 if not self.build_state[a]['build-time']:
1025 must_build[a] = True
1026 old_vers = {}
1027 new_vers = {}
1028 for c in check_components[a]:
1029 if c in build_vers:
1030 old_vers[c] = build_vers[c]
1031 new_vers[c] = {'version': self.versions[c]['version'],
1032 'revision': self.versions[c]['revision']}
1033 if new_vers == old_vers:
1034 print('Versions for %s unchanged.' % a)
1035 else:
1036 print('Versions changed or rebuild forced for %s.' % a)
1037 if a == 'compilers' and not self.part_build_old(
1038 a, self.bot_config['compilers-rebuild-delay']):
1039 print('Not requiring rebuild of compilers this soon.')
1040 else:
1041 must_build[a] = True
1042 if must_build['host-libraries']:
1043 must_build['compilers'] = True
1044 if must_build['compilers']:
1045 must_build['glibcs'] = True
1046 for a in actions:
1047 if must_build[a]:
1048 print('Must rebuild %s.' % a)
1049 self.clear_last_build_state(a)
1050 else:
1051 print('No need to rebuild %s.' % a)
a1f6a9ab
JM
1052 if os.access(self.logsdir, os.F_OK):
1053 shutil.rmtree(self.logsdir_old, ignore_errors=True)
1054 shutil.copytree(self.logsdir, self.logsdir_old)
4d602bce
JM
1055 for a in actions:
1056 if must_build[a]:
1057 build_time = datetime.datetime.utcnow()
1058 print('Rebuilding %s at %s.' % (a, str(build_time)))
1059 self.bot_run_self([], a)
1060 self.load_build_state_json()
1061 self.bot_build_mail(a, build_time)
1062 print('Bot cycle done at %s.' % str(datetime.datetime.utcnow()))
1063
1064 def bot_build_mail(self, action, build_time):
1065 """Send email with the results of a build."""
f0166c16
ZW
1066 if not ('email-from' in self.bot_config and
1067 'email-server' in self.bot_config and
1068 'email-subject' in self.bot_config and
1069 'email-to' in self.bot_config):
1070 if not self.email_warning:
1071 print("Email not configured, not sending.")
1072 self.email_warning = True
1073 return
1074
4d602bce
JM
1075 build_time = build_time.replace(microsecond=0)
1076 subject = (self.bot_config['email-subject'] %
1077 {'action': action,
1078 'build-time': str(build_time)})
1079 results = self.build_state[action]['build-results']
1080 changes = self.build_state[action]['result-changes']
1081 ever_passed = set(self.build_state[action]['ever-passed'])
1082 versions = self.build_state[action]['build-versions']
1083 new_regressions = {k for k in changes if changes[k] == 'PASS -> FAIL'}
1084 all_regressions = {k for k in ever_passed if results[k] == 'FAIL'}
1085 all_fails = {k for k in results if results[k] == 'FAIL'}
1086 if new_regressions:
1087 new_reg_list = sorted(['FAIL: %s' % k for k in new_regressions])
1088 new_reg_text = ('New regressions:\n\n%s\n\n' %
1089 '\n'.join(new_reg_list))
1090 else:
1091 new_reg_text = ''
1092 if all_regressions:
1093 all_reg_list = sorted(['FAIL: %s' % k for k in all_regressions])
1094 all_reg_text = ('All regressions:\n\n%s\n\n' %
1095 '\n'.join(all_reg_list))
1096 else:
1097 all_reg_text = ''
1098 if all_fails:
1099 all_fail_list = sorted(['FAIL: %s' % k for k in all_fails])
1100 all_fail_text = ('All failures:\n\n%s\n\n' %
1101 '\n'.join(all_fail_list))
1102 else:
1103 all_fail_text = ''
1104 if changes:
1105 changes_list = sorted(changes.keys())
1106 changes_list = ['%s: %s' % (changes[k], k) for k in changes_list]
1107 changes_text = ('All changed results:\n\n%s\n\n' %
1108 '\n'.join(changes_list))
1109 else:
1110 changes_text = ''
1111 results_text = (new_reg_text + all_reg_text + all_fail_text +
1112 changes_text)
1113 if not results_text:
1114 results_text = 'Clean build with unchanged results.\n\n'
1115 versions_list = sorted(versions.keys())
1116 versions_list = ['%s: %s (%s)' % (k, versions[k]['version'],
1117 versions[k]['revision'])
1118 for k in versions_list]
1119 versions_text = ('Component versions for this build:\n\n%s\n' %
1120 '\n'.join(versions_list))
1121 body_text = results_text + versions_text
1122 msg = email.mime.text.MIMEText(body_text)
1123 msg['Subject'] = subject
1124 msg['From'] = self.bot_config['email-from']
1125 msg['To'] = self.bot_config['email-to']
1126 msg['Message-ID'] = email.utils.make_msgid()
1127 msg['Date'] = email.utils.format_datetime(datetime.datetime.utcnow())
1128 with smtplib.SMTP(self.bot_config['email-server']) as s:
1129 s.send_message(msg)
1130
a1f6a9ab 1131 def bot_run_self(self, opts, action, check=True):
4d602bce
JM
1132 """Run a copy of this script with given options."""
1133 cmd = [sys.executable, sys.argv[0], '--keep=none',
1134 '-j%d' % self.parallelism]
b1176270
JM
1135 if self.full_gcc:
1136 cmd.append('--full-gcc')
4d602bce
JM
1137 cmd.extend(opts)
1138 cmd.extend([self.topdir, action])
a1f6a9ab
JM
1139 sys.stdout.flush()
1140 subprocess.run(cmd, check=check)
1141
1142 def bot(self):
1143 """Run repeated rounds of checkout and builds."""
1144 while True:
1145 self.load_bot_config_json()
1146 if not self.bot_config['run']:
1147 print('Bot exiting by request.')
1148 exit(0)
1149 self.bot_run_self([], 'bot-cycle', check=False)
1150 self.load_bot_config_json()
1151 if not self.bot_config['run']:
1152 print('Bot exiting by request.')
1153 exit(0)
1154 time.sleep(self.bot_config['delay'])
1155 if self.get_script_text() != self.script_text:
1156 print('Script changed, bot re-execing.')
1157 self.exec_self()
4d602bce 1158
14f95a42 1159
0c95f51d 1160class Config(object):
14f95a42
JM
1161 """A configuration for building a compiler and associated libraries."""
1162
1163 def __init__(self, ctx, arch, os_name, variant=None, gcc_cfg=None,
1164 first_gcc_cfg=None, glibcs=None, extra_glibcs=None):
1165 """Initialize a Config object."""
1166 self.ctx = ctx
1167 self.arch = arch
1168 self.os = os_name
1169 self.variant = variant
1170 if variant is None:
1171 self.name = '%s-%s' % (arch, os_name)
1172 else:
1173 self.name = '%s-%s-%s' % (arch, os_name, variant)
1174 self.triplet = '%s-glibc-%s' % (arch, os_name)
1175 if gcc_cfg is None:
1176 self.gcc_cfg = []
1177 else:
1178 self.gcc_cfg = gcc_cfg
1179 if first_gcc_cfg is None:
1180 self.first_gcc_cfg = []
1181 else:
1182 self.first_gcc_cfg = first_gcc_cfg
1183 if glibcs is None:
1184 glibcs = [{'variant': variant}]
1185 if extra_glibcs is None:
1186 extra_glibcs = []
1187 glibcs = [Glibc(self, **g) for g in glibcs]
1188 extra_glibcs = [Glibc(self, **g) for g in extra_glibcs]
1189 self.all_glibcs = glibcs + extra_glibcs
1190 self.compiler_glibcs = glibcs
1191 self.installdir = ctx.compiler_installdir(self.name)
1192 self.bindir = ctx.compiler_bindir(self.name)
1193 self.sysroot = ctx.compiler_sysroot(self.name)
1194 self.builddir = os.path.join(ctx.builddir, 'compilers', self.name)
1195 self.logsdir = os.path.join(ctx.logsdir, 'compilers', self.name)
1196
1197 def component_builddir(self, component):
1198 """Return the directory to use for a (non-glibc) build."""
1199 return self.ctx.component_builddir('compilers', self.name, component)
1200
1201 def build(self):
1202 """Generate commands to build this compiler."""
1203 self.ctx.remove_recreate_dirs(self.installdir, self.builddir,
1204 self.logsdir)
1205 cmdlist = CommandList('compilers-%s' % self.name, self.ctx.keep)
1206 cmdlist.add_command('check-host-libraries',
1207 ['test', '-f',
1208 os.path.join(self.ctx.host_libraries_installdir,
1209 'ok')])
1210 cmdlist.use_path(self.bindir)
1211 self.build_cross_tool(cmdlist, 'binutils', 'binutils',
1212 ['--disable-gdb',
1213 '--disable-libdecnumber',
1214 '--disable-readline',
1215 '--disable-sim'])
1216 if self.os.startswith('linux'):
1217 self.install_linux_headers(cmdlist)
1218 self.build_gcc(cmdlist, True)
394b5bac
JM
1219 if self.os == 'gnu':
1220 self.install_gnumach_headers(cmdlist)
1221 self.build_cross_tool(cmdlist, 'mig', 'mig')
1222 self.install_hurd_headers(cmdlist)
14f95a42
JM
1223 for g in self.compiler_glibcs:
1224 cmdlist.push_subdesc('glibc')
1225 cmdlist.push_subdesc(g.name)
1226 g.build_glibc(cmdlist, True)
1227 cmdlist.pop_subdesc()
1228 cmdlist.pop_subdesc()
1229 self.build_gcc(cmdlist, False)
1230 cmdlist.add_command('done', ['touch',
1231 os.path.join(self.installdir, 'ok')])
1232 self.ctx.add_makefile_cmdlist('compilers-%s' % self.name, cmdlist,
1233 self.logsdir)
1234
1235 def build_cross_tool(self, cmdlist, tool_src, tool_build, extra_opts=None):
1236 """Build one cross tool."""
1237 srcdir = self.ctx.component_srcdir(tool_src)
1238 builddir = self.component_builddir(tool_build)
1239 cmdlist.push_subdesc(tool_build)
1240 cmdlist.create_use_dir(builddir)
1241 cfg_cmd = [os.path.join(srcdir, 'configure'),
1242 '--prefix=%s' % self.installdir,
1243 '--build=%s' % self.ctx.build_triplet,
1244 '--host=%s' % self.ctx.build_triplet,
1245 '--target=%s' % self.triplet,
1246 '--with-sysroot=%s' % self.sysroot]
1247 if extra_opts:
1248 cfg_cmd.extend(extra_opts)
1249 cmdlist.add_command('configure', cfg_cmd)
1250 cmdlist.add_command('build', ['make'])
cd880aa2
JM
1251 # Parallel "make install" for GCC has race conditions that can
1252 # cause it to fail; see
1253 # <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=42980>. Such
1254 # problems are not known for binutils, but doing the
1255 # installation in parallel within a particular toolchain build
1256 # (as opposed to installation of one toolchain from
1257 # build-many-glibcs.py running in parallel to the installation
1258 # of other toolchains being built) is not known to be
1259 # significantly beneficial, so it is simplest just to disable
1260 # parallel install for cross tools here.
1261 cmdlist.add_command('install', ['make', '-j1', 'install'])
14f95a42
JM
1262 cmdlist.cleanup_dir()
1263 cmdlist.pop_subdesc()
1264
1265 def install_linux_headers(self, cmdlist):
1266 """Install Linux kernel headers."""
1267 arch_map = {'aarch64': 'arm64',
1268 'alpha': 'alpha',
1269 'arm': 'arm',
5f72b005 1270 'csky': 'csky',
14f95a42
JM
1271 'hppa': 'parisc',
1272 'i486': 'x86',
1273 'i586': 'x86',
1274 'i686': 'x86',
1275 'i786': 'x86',
1276 'ia64': 'ia64',
1277 'm68k': 'm68k',
1278 'microblaze': 'microblaze',
1279 'mips': 'mips',
1280 'nios2': 'nios2',
1281 'powerpc': 'powerpc',
1282 's390': 's390',
0fd5d876
PD
1283 'riscv32': 'riscv',
1284 'riscv64': 'riscv',
14f95a42
JM
1285 'sh': 'sh',
1286 'sparc': 'sparc',
14f95a42
JM
1287 'x86_64': 'x86'}
1288 linux_arch = None
1289 for k in arch_map:
1290 if self.arch.startswith(k):
1291 linux_arch = arch_map[k]
1292 break
1293 assert linux_arch is not None
1294 srcdir = self.ctx.component_srcdir('linux')
1295 builddir = self.component_builddir('linux')
1296 headers_dir = os.path.join(self.sysroot, 'usr')
1297 cmdlist.push_subdesc('linux')
1298 cmdlist.create_use_dir(builddir)
1299 cmdlist.add_command('install-headers',
1300 ['make', '-C', srcdir, 'O=%s' % builddir,
1301 'ARCH=%s' % linux_arch,
1302 'INSTALL_HDR_PATH=%s' % headers_dir,
1303 'headers_install'])
1304 cmdlist.cleanup_dir()
1305 cmdlist.pop_subdesc()
1306
394b5bac
JM
1307 def install_gnumach_headers(self, cmdlist):
1308 """Install GNU Mach headers."""
1309 srcdir = self.ctx.component_srcdir('gnumach')
1310 builddir = self.component_builddir('gnumach')
1311 cmdlist.push_subdesc('gnumach')
1312 cmdlist.create_use_dir(builddir)
1313 cmdlist.add_command('configure',
1314 [os.path.join(srcdir, 'configure'),
1315 '--build=%s' % self.ctx.build_triplet,
1316 '--host=%s' % self.triplet,
1317 '--prefix=',
1318 'CC=%s-gcc -nostdlib' % self.triplet])
1319 cmdlist.add_command('install', ['make', 'DESTDIR=%s' % self.sysroot,
1320 'install-data'])
1321 cmdlist.cleanup_dir()
1322 cmdlist.pop_subdesc()
1323
1324 def install_hurd_headers(self, cmdlist):
1325 """Install Hurd headers."""
1326 srcdir = self.ctx.component_srcdir('hurd')
1327 builddir = self.component_builddir('hurd')
1328 cmdlist.push_subdesc('hurd')
1329 cmdlist.create_use_dir(builddir)
1330 cmdlist.add_command('configure',
1331 [os.path.join(srcdir, 'configure'),
1332 '--build=%s' % self.ctx.build_triplet,
1333 '--host=%s' % self.triplet,
1334 '--prefix=',
1335 '--disable-profile', '--without-parted',
1336 'CC=%s-gcc -nostdlib' % self.triplet])
1337 cmdlist.add_command('install', ['make', 'prefix=%s' % self.sysroot,
1338 'no_deps=t', 'install-headers'])
1339 cmdlist.cleanup_dir()
1340 cmdlist.pop_subdesc()
1341
14f95a42
JM
1342 def build_gcc(self, cmdlist, bootstrap):
1343 """Build GCC."""
b1176270
JM
1344 # libssp is of little relevance with glibc's own stack
1345 # checking support. libcilkrts does not support GNU/Hurd (and
1346 # has been removed in GCC 8, so --disable-libcilkrts can be
1347 # removed once glibc no longer supports building with older
1348 # GCC versions).
14f95a42 1349 cfg_opts = list(self.gcc_cfg)
b1176270 1350 cfg_opts += ['--disable-libssp', '--disable-libcilkrts']
c440d5d5
JM
1351 host_libs = self.ctx.host_libraries_installdir
1352 cfg_opts += ['--with-gmp=%s' % host_libs,
1353 '--with-mpfr=%s' % host_libs,
1354 '--with-mpc=%s' % host_libs]
14f95a42
JM
1355 if bootstrap:
1356 tool_build = 'gcc-first'
1357 # Building a static-only, C-only compiler that is
1358 # sufficient to build glibc. Various libraries and
1359 # features that may require libc headers must be disabled.
1360 # When configuring with a sysroot, --with-newlib is
1361 # required to define inhibit_libc (to stop some parts of
1362 # libgcc including libc headers); --without-headers is not
1363 # sufficient.
1364 cfg_opts += ['--enable-languages=c', '--disable-shared',
1365 '--disable-threads',
1366 '--disable-libatomic',
1367 '--disable-decimal-float',
1368 '--disable-libffi',
1369 '--disable-libgomp',
1370 '--disable-libitm',
1371 '--disable-libmpx',
1372 '--disable-libquadmath',
b1176270 1373 '--disable-libsanitizer',
14f95a42
JM
1374 '--without-headers', '--with-newlib',
1375 '--with-glibc-version=%s' % self.ctx.glibc_version
1376 ]
1377 cfg_opts += self.first_gcc_cfg
1378 else:
1379 tool_build = 'gcc'
b1176270
JM
1380 # libsanitizer commonly breaks because of glibc header
1381 # changes, or on unusual targets.
1382 if not self.ctx.full_gcc:
1383 cfg_opts += ['--disable-libsanitizer']
1384 langs = 'all' if self.ctx.full_gcc else 'c,c++'
1385 cfg_opts += ['--enable-languages=%s' % langs,
1386 '--enable-shared', '--enable-threads']
14f95a42
JM
1387 self.build_cross_tool(cmdlist, 'gcc', tool_build, cfg_opts)
1388
1389
0c95f51d 1390class Glibc(object):
14f95a42
JM
1391 """A configuration for building glibc."""
1392
1393 def __init__(self, compiler, arch=None, os_name=None, variant=None,
1394 cfg=None, ccopts=None):
1395 """Initialize a Glibc object."""
1396 self.ctx = compiler.ctx
1397 self.compiler = compiler
1398 if arch is None:
1399 self.arch = compiler.arch
1400 else:
1401 self.arch = arch
1402 if os_name is None:
1403 self.os = compiler.os
1404 else:
1405 self.os = os_name
1406 self.variant = variant
1407 if variant is None:
1408 self.name = '%s-%s' % (self.arch, self.os)
1409 else:
1410 self.name = '%s-%s-%s' % (self.arch, self.os, variant)
1411 self.triplet = '%s-glibc-%s' % (self.arch, self.os)
1412 if cfg is None:
1413 self.cfg = []
1414 else:
1415 self.cfg = cfg
1416 self.ccopts = ccopts
1417
1418 def tool_name(self, tool):
1419 """Return the name of a cross-compilation tool."""
1420 ctool = '%s-%s' % (self.compiler.triplet, tool)
1421 if self.ccopts and (tool == 'gcc' or tool == 'g++'):
1422 ctool = '%s %s' % (ctool, self.ccopts)
1423 return ctool
1424
1425 def build(self):
1426 """Generate commands to build this glibc."""
1427 builddir = self.ctx.component_builddir('glibcs', self.name, 'glibc')
1428 installdir = self.ctx.glibc_installdir(self.name)
1429 logsdir = os.path.join(self.ctx.logsdir, 'glibcs', self.name)
1430 self.ctx.remove_recreate_dirs(installdir, builddir, logsdir)
1431 cmdlist = CommandList('glibcs-%s' % self.name, self.ctx.keep)
1432 cmdlist.add_command('check-compilers',
1433 ['test', '-f',
1434 os.path.join(self.compiler.installdir, 'ok')])
1435 cmdlist.use_path(self.compiler.bindir)
1436 self.build_glibc(cmdlist, False)
1437 self.ctx.add_makefile_cmdlist('glibcs-%s' % self.name, cmdlist,
1438 logsdir)
1439
1440 def build_glibc(self, cmdlist, for_compiler):
1441 """Generate commands to build this glibc, either as part of a compiler
1442 build or with the bootstrapped compiler (and in the latter case, run
1443 tests as well)."""
1444 srcdir = self.ctx.component_srcdir('glibc')
1445 if for_compiler:
1446 builddir = self.ctx.component_builddir('compilers',
1447 self.compiler.name, 'glibc',
1448 self.name)
1449 installdir = self.compiler.sysroot
14f95a42
JM
1450 else:
1451 builddir = self.ctx.component_builddir('glibcs', self.name,
1452 'glibc')
1453 installdir = self.ctx.glibc_installdir(self.name)
14f95a42 1454 cmdlist.create_use_dir(builddir)
394b5bac
JM
1455 use_usr = self.os != 'gnu'
1456 prefix = '/usr' if use_usr else ''
530504e3 1457 cfg_cmd = [os.path.join(srcdir, 'configure'),
394b5bac 1458 '--prefix=%s' % prefix,
218bb835 1459 '--enable-profile',
14f95a42
JM
1460 '--build=%s' % self.ctx.build_triplet,
1461 '--host=%s' % self.triplet,
1462 'CC=%s' % self.tool_name('gcc'),
1463 'CXX=%s' % self.tool_name('g++'),
1464 'AR=%s' % self.tool_name('ar'),
1465 'AS=%s' % self.tool_name('as'),
1466 'LD=%s' % self.tool_name('ld'),
1467 'NM=%s' % self.tool_name('nm'),
1468 'OBJCOPY=%s' % self.tool_name('objcopy'),
1469 'OBJDUMP=%s' % self.tool_name('objdump'),
1470 'RANLIB=%s' % self.tool_name('ranlib'),
1471 'READELF=%s' % self.tool_name('readelf'),
1472 'STRIP=%s' % self.tool_name('strip')]
394b5bac
JM
1473 if self.os == 'gnu':
1474 cfg_cmd += ['MIG=%s' % self.tool_name('mig')]
14f95a42
JM
1475 cfg_cmd += self.cfg
1476 cmdlist.add_command('configure', cfg_cmd)
1477 cmdlist.add_command('build', ['make'])
1478 cmdlist.add_command('install', ['make', 'install',
1479 'install_root=%s' % installdir])
1480 # GCC uses paths such as lib/../lib64, so make sure lib
1481 # directories always exist.
394b5bac
JM
1482 mkdir_cmd = ['mkdir', '-p',
1483 os.path.join(installdir, 'lib')]
1484 if use_usr:
1485 mkdir_cmd += [os.path.join(installdir, 'usr', 'lib')]
1486 cmdlist.add_command('mkdir-lib', mkdir_cmd)
14f95a42 1487 if not for_compiler:
297635d8
JM
1488 if self.ctx.strip:
1489 cmdlist.add_command('strip',
1490 ['sh', '-c',
1598f3ad 1491 ('%s $(find %s/lib* -name "*.so")' %
297635d8 1492 (self.tool_name('strip'), installdir))])
14f95a42
JM
1493 cmdlist.add_command('check', ['make', 'check'])
1494 cmdlist.add_command('save-logs', [self.ctx.save_logs],
1495 always_run=True)
14f95a42
JM
1496 cmdlist.cleanup_dir()
1497
1498
0c95f51d 1499class Command(object):
14f95a42
JM
1500 """A command run in the build process."""
1501
1502 def __init__(self, desc, num, dir, path, command, always_run=False):
1503 """Initialize a Command object."""
1504 self.dir = dir
1505 self.path = path
1506 self.desc = desc
1507 trans = str.maketrans({' ': '-'})
1508 self.logbase = '%03d-%s' % (num, desc.translate(trans))
1509 self.command = command
1510 self.always_run = always_run
1511
1512 @staticmethod
1513 def shell_make_quote_string(s):
1514 """Given a string not containing a newline, quote it for use by the
1515 shell and make."""
1516 assert '\n' not in s
1517 if re.fullmatch('[]+,./0-9@A-Z_a-z-]+', s):
1518 return s
1519 strans = str.maketrans({"'": "'\\''"})
1520 s = "'%s'" % s.translate(strans)
1521 mtrans = str.maketrans({'$': '$$'})
1522 return s.translate(mtrans)
1523
1524 @staticmethod
1525 def shell_make_quote_list(l, translate_make):
1526 """Given a list of strings not containing newlines, quote them for use
1527 by the shell and make, returning a single string. If translate_make
1528 is true and the first string is 'make', change it to $(MAKE)."""
1529 l = [Command.shell_make_quote_string(s) for s in l]
1530 if translate_make and l[0] == 'make':
1531 l[0] = '$(MAKE)'
1532 return ' '.join(l)
1533
1534 def shell_make_quote(self):
1535 """Return this command quoted for the shell and make."""
1536 return self.shell_make_quote_list(self.command, True)
1537
1538
0c95f51d 1539class CommandList(object):
14f95a42
JM
1540 """A list of commands run in the build process."""
1541
1542 def __init__(self, desc, keep):
1543 """Initialize a CommandList object."""
1544 self.cmdlist = []
1545 self.dir = None
1546 self.path = None
1547 self.desc = [desc]
1548 self.keep = keep
1549
1550 def desc_txt(self, desc):
1551 """Return the description to use for a command."""
1552 return '%s %s' % (' '.join(self.desc), desc)
1553
1554 def use_dir(self, dir):
1555 """Set the default directory for subsequent commands."""
1556 self.dir = dir
1557
1558 def use_path(self, path):
1559 """Set a directory to be prepended to the PATH for subsequent
1560 commands."""
1561 self.path = path
1562
1563 def push_subdesc(self, subdesc):
1564 """Set the default subdescription for subsequent commands (e.g., the
1565 name of a component being built, within the series of commands
1566 building it)."""
1567 self.desc.append(subdesc)
1568
1569 def pop_subdesc(self):
1570 """Pop a subdescription from the list of descriptions."""
1571 self.desc.pop()
1572
1573 def create_use_dir(self, dir):
1574 """Remove and recreate a directory and use it for subsequent
1575 commands."""
1576 self.add_command_dir('rm', None, ['rm', '-rf', dir])
1577 self.add_command_dir('mkdir', None, ['mkdir', '-p', dir])
1578 self.use_dir(dir)
1579
14f95a42
JM
1580 def add_command_dir(self, desc, dir, command, always_run=False):
1581 """Add a command to run in a given directory."""
1582 cmd = Command(self.desc_txt(desc), len(self.cmdlist), dir, self.path,
1583 command, always_run)
1584 self.cmdlist.append(cmd)
1585
1586 def add_command(self, desc, command, always_run=False):
1587 """Add a command to run in the default directory."""
1588 cmd = Command(self.desc_txt(desc), len(self.cmdlist), self.dir,
1589 self.path, command, always_run)
1590 self.cmdlist.append(cmd)
1591
1592 def cleanup_dir(self, desc='cleanup', dir=None):
1593 """Clean up a build directory. If no directory is specified, the
1594 default directory is cleaned up and ceases to be the default
1595 directory."""
1596 if dir is None:
1597 dir = self.dir
1598 self.use_dir(None)
1599 if self.keep != 'all':
1600 self.add_command_dir(desc, None, ['rm', '-rf', dir],
1601 always_run=(self.keep == 'none'))
1602
1603 def makefile_commands(self, wrapper, logsdir):
1604 """Return the sequence of commands in the form of text for a Makefile.
1605 The given wrapper script takes arguments: base of logs for
1606 previous command, or empty; base of logs for this command;
1607 description; directory; PATH addition; the command itself."""
1608 # prev_base is the base of the name for logs of the previous
1609 # command that is not always-run (that is, a build command,
1610 # whose failure should stop subsequent build commands from
1611 # being run, as opposed to a cleanup command, which is run
1612 # even if previous commands failed).
1613 prev_base = ''
1614 cmds = []
1615 for c in self.cmdlist:
1616 ctxt = c.shell_make_quote()
1617 if prev_base and not c.always_run:
1618 prev_log = os.path.join(logsdir, prev_base)
1619 else:
1620 prev_log = ''
1621 this_log = os.path.join(logsdir, c.logbase)
1622 if not c.always_run:
1623 prev_base = c.logbase
1624 if c.dir is None:
1625 dir = ''
1626 else:
1627 dir = c.dir
1628 if c.path is None:
1629 path = ''
1630 else:
1631 path = c.path
1632 prelims = [wrapper, prev_log, this_log, c.desc, dir, path]
1633 prelim_txt = Command.shell_make_quote_list(prelims, False)
1634 cmds.append('\t@%s %s' % (prelim_txt, ctxt))
1635 return '\n'.join(cmds)
1636
bf469f0c
JM
1637 def status_logs(self, logsdir):
1638 """Return the list of log files with command status."""
1639 return [os.path.join(logsdir, '%s-status.txt' % c.logbase)
1640 for c in self.cmdlist]
1641
14f95a42
JM
1642
1643def get_parser():
1644 """Return an argument parser for this module."""
1645 parser = argparse.ArgumentParser(description=__doc__)
1646 parser.add_argument('-j', dest='parallelism',
1647 help='Run this number of jobs in parallel',
1648 type=int, default=os.cpu_count())
1649 parser.add_argument('--keep', dest='keep',
1650 help='Whether to keep all build directories, '
1651 'none or only those from failed builds',
1652 default='none', choices=('none', 'all', 'failed'))
02c78f02
JM
1653 parser.add_argument('--replace-sources', action='store_true',
1654 help='Remove and replace source directories '
1655 'with the wrong version of a component')
297635d8
JM
1656 parser.add_argument('--strip', action='store_true',
1657 help='Strip installed glibc libraries')
b1176270
JM
1658 parser.add_argument('--full-gcc', action='store_true',
1659 help='Build GCC with all languages and libsanitizer')
14f95a42
JM
1660 parser.add_argument('topdir',
1661 help='Toplevel working directory')
1662 parser.add_argument('action',
1663 help='What to do',
a1f6a9ab
JM
1664 choices=('checkout', 'bot-cycle', 'bot',
1665 'host-libraries', 'compilers', 'glibcs'))
14f95a42
JM
1666 parser.add_argument('configs',
1667 help='Versions to check out or configurations to build',
1668 nargs='*')
1669 return parser
1670
1671
1672def main(argv):
1673 """The main entry point."""
1674 parser = get_parser()
1675 opts = parser.parse_args(argv)
1676 topdir = os.path.abspath(opts.topdir)
02c78f02 1677 ctx = Context(topdir, opts.parallelism, opts.keep, opts.replace_sources,
b1176270 1678 opts.strip, opts.full_gcc, opts.action)
14f95a42
JM
1679 ctx.run_builds(opts.action, opts.configs)
1680
1681
1682if __name__ == '__main__':
1683 main(sys.argv[1:])