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