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