elf: Enable TLS descriptor tests on aarch64
[glibc.git] / scripts / build-many-glibcs.py
blobecc743e672c5017036940121dc6958b05ef86f08
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 {'variant': 'zEC12',
422 'ccopts': '-march=zEC12'},
423 {'variant': 'z13',
424 'ccopts': '-march=z13'},
425 {'variant': 'z15',
426 'ccopts': '-march=z15'},
427 {'variant': 'zEC12-disable-multi-arch',
428 'ccopts': '-march=zEC12',
429 'cfg': ['--disable-multi-arch']},
430 {'variant': 'z13-disable-multi-arch',
431 'ccopts': '-march=z13',
432 'cfg': ['--disable-multi-arch']},
433 {'variant': 'z15-disable-multi-arch',
434 'ccopts': '-march=z15',
435 'cfg': ['--disable-multi-arch']}])
436 self.add_config(arch='sh3',
437 os_name='linux-gnu')
438 self.add_config(arch='sh3eb',
439 os_name='linux-gnu')
440 self.add_config(arch='sh4',
441 os_name='linux-gnu')
442 self.add_config(arch='sh4eb',
443 os_name='linux-gnu')
444 self.add_config(arch='sh4',
445 os_name='linux-gnu',
446 variant='soft',
447 gcc_cfg=['--without-fp'])
448 self.add_config(arch='sh4eb',
449 os_name='linux-gnu',
450 variant='soft',
451 gcc_cfg=['--without-fp'])
452 self.add_config(arch='sparc64',
453 os_name='linux-gnu',
454 glibcs=[{'cfg' : ['--disable-default-pie']},
455 {'cfg' : ['--disable-default-pie'],
456 'arch': 'sparcv9',
457 'ccopts': '-m32 -mlong-double-128 -mcpu=v9'}],
458 extra_glibcs=[{'variant': 'leon3',
459 'cfg' : ['--disable-default-pie'],
460 'arch' : 'sparcv8',
461 'ccopts' : '-m32 -mlong-double-128 -mcpu=leon3'},
462 {'variant': 'disable-multi-arch',
463 'cfg': ['--disable-multi-arch', '--disable-default-pie']},
464 {'variant': 'disable-multi-arch',
465 'arch': 'sparcv9',
466 'ccopts': '-m32 -mlong-double-128 -mcpu=v9',
467 'cfg': ['--disable-multi-arch', '--disable-default-pie']}])
468 self.add_config(arch='x86_64',
469 os_name='linux-gnu',
470 gcc_cfg=['--with-multilib-list=m64,m32,mx32'],
471 glibcs=[{},
472 {'variant': 'x32', 'ccopts': '-mx32'},
473 {'arch': 'i686', 'ccopts': '-m32 -march=i686'}],
474 extra_glibcs=[{'variant': 'disable-multi-arch',
475 'cfg': ['--disable-multi-arch']},
476 {'variant': 'minimal',
477 'cfg': ['--disable-multi-arch',
478 '--disable-profile',
479 '--disable-timezone-tools',
480 '--disable-mathvec',
481 '--disable-build-nscd',
482 '--disable-nscd']},
483 {'variant': 'no-pie',
484 'cfg': ['--disable-default-pie']},
485 {'variant': 'x32-no-pie',
486 'ccopts': '-mx32',
487 'cfg': ['--disable-default-pie']},
488 {'variant': 'no-pie',
489 'arch': 'i686',
490 'ccopts': '-m32 -march=i686',
491 'cfg': ['--disable-default-pie']},
492 {'variant': 'disable-multi-arch',
493 'arch': 'i686',
494 'ccopts': '-m32 -march=i686',
495 'cfg': ['--disable-multi-arch']},
496 {'arch': 'i486',
497 'ccopts': '-m32 -march=i486'},
498 {'arch': 'i586',
499 'ccopts': '-m32 -march=i586'},
500 {'variant': 'enable-fortify-source',
501 'cfg': ['--enable-fortify-source']}])
502 self.add_config(arch='x86_64',
503 os_name='gnu',
504 gcc_cfg=['--disable-multilib'])
506 def add_config(self, **args):
507 """Add an individual build configuration."""
508 cfg = Config(self, **args)
509 if cfg.name in self.configs:
510 print('error: duplicate config %s' % cfg.name)
511 exit(1)
512 self.configs[cfg.name] = cfg
513 for c in cfg.all_glibcs:
514 if c.name in self.glibc_configs:
515 print('error: duplicate glibc config %s' % c.name)
516 exit(1)
517 self.glibc_configs[c.name] = c
519 def component_srcdir(self, component):
520 """Return the source directory for a given component, e.g. gcc."""
521 return os.path.join(self.srcdir, component)
523 def component_builddir(self, action, config, component, subconfig=None):
524 """Return the directory to use for a build."""
525 if config is None:
526 # Host libraries.
527 assert subconfig is None
528 return os.path.join(self.builddir, action, component)
529 if subconfig is None:
530 return os.path.join(self.builddir, action, config, component)
531 else:
532 # glibc build as part of compiler build.
533 return os.path.join(self.builddir, action, config, component,
534 subconfig)
536 def compiler_installdir(self, config):
537 """Return the directory in which to install a compiler."""
538 return os.path.join(self.installdir, 'compilers', config)
540 def compiler_bindir(self, config):
541 """Return the directory in which to find compiler binaries."""
542 return os.path.join(self.compiler_installdir(config), 'bin')
544 def compiler_sysroot(self, config):
545 """Return the sysroot directory for a compiler."""
546 return os.path.join(self.compiler_installdir(config), 'sysroot')
548 def glibc_installdir(self, config):
549 """Return the directory in which to install glibc."""
550 return os.path.join(self.installdir, 'glibcs', config)
552 def run_builds(self, action, configs):
553 """Run the requested builds."""
554 if action == 'checkout':
555 self.checkout(configs)
556 return
557 if action == 'bot-cycle':
558 if configs:
559 print('error: configurations specified for bot-cycle')
560 exit(1)
561 self.bot_cycle()
562 return
563 if action == 'bot':
564 if configs:
565 print('error: configurations specified for bot')
566 exit(1)
567 self.bot()
568 return
569 if action in ('host-libraries', 'list-compilers',
570 'list-glibcs') and configs:
571 print('error: configurations specified for ' + action)
572 exit(1)
573 if action == 'list-compilers':
574 for name in sorted(self.configs.keys()):
575 print(name)
576 return
577 if action == 'list-glibcs':
578 for config in sorted(self.glibc_configs.values(),
579 key=lambda c: c.name):
580 print(config.name, config.compiler.name)
581 return
582 self.clear_last_build_state(action)
583 build_time = datetime.datetime.now(datetime.timezone.utc)
584 if action == 'host-libraries':
585 build_components = ('gmp', 'mpfr', 'mpc')
586 old_components = ()
587 old_versions = {}
588 self.build_host_libraries()
589 elif action == 'compilers':
590 build_components = ('binutils', 'gcc', 'glibc', 'linux', 'mig',
591 'gnumach', 'hurd')
592 old_components = ('gmp', 'mpfr', 'mpc')
593 old_versions = self.build_state['host-libraries']['build-versions']
594 self.build_compilers(configs)
595 else:
596 build_components = ('glibc',)
597 old_components = ('gmp', 'mpfr', 'mpc', 'binutils', 'gcc', 'linux',
598 'mig', 'gnumach', 'hurd')
599 old_versions = self.build_state['compilers']['build-versions']
600 if action == 'update-syscalls':
601 self.update_syscalls(configs)
602 else:
603 self.build_glibcs(configs)
604 self.write_files()
605 self.do_build()
606 if configs:
607 # Partial build, do not update stored state.
608 return
609 build_versions = {}
610 for k in build_components:
611 if k in self.versions:
612 build_versions[k] = {'version': self.versions[k]['version'],
613 'revision': self.versions[k]['revision']}
614 for k in old_components:
615 if k in old_versions:
616 build_versions[k] = {'version': old_versions[k]['version'],
617 'revision': old_versions[k]['revision']}
618 self.update_build_state(action, build_time, build_versions)
620 @staticmethod
621 def remove_dirs(*args):
622 """Remove directories and their contents if they exist."""
623 for dir in args:
624 shutil.rmtree(dir, ignore_errors=True)
626 @staticmethod
627 def remove_recreate_dirs(*args):
628 """Remove directories if they exist, and create them as empty."""
629 Context.remove_dirs(*args)
630 for dir in args:
631 os.makedirs(dir, exist_ok=True)
633 def add_makefile_cmdlist(self, target, cmdlist, logsdir):
634 """Add makefile text for a list of commands."""
635 commands = cmdlist.makefile_commands(self.wrapper, logsdir)
636 self.makefile_pieces.append('all: %s\n.PHONY: %s\n%s:\n%s\n' %
637 (target, target, target, commands))
638 self.status_log_list.extend(cmdlist.status_logs(logsdir))
640 def write_files(self):
641 """Write out the Makefile and wrapper script."""
642 mftext = ''.join(self.makefile_pieces)
643 with open(self.makefile, 'w') as f:
644 f.write(mftext)
645 wrapper_text = (
646 '#!/bin/sh\n'
647 'prev_base=$1\n'
648 'this_base=$2\n'
649 'desc=$3\n'
650 'dir=$4\n'
651 'path=$5\n'
652 'shift 5\n'
653 'prev_status=$prev_base-status.txt\n'
654 'this_status=$this_base-status.txt\n'
655 'this_log=$this_base-log.txt\n'
656 'date > "$this_log"\n'
657 'echo >> "$this_log"\n'
658 'echo "Description: $desc" >> "$this_log"\n'
659 'printf "%s" "Command:" >> "$this_log"\n'
660 'for word in "$@"; do\n'
661 ' if expr "$word" : "[]+,./0-9@A-Z_a-z-]\\\\{1,\\\\}\\$" > /dev/null; then\n'
662 ' printf " %s" "$word"\n'
663 ' else\n'
664 ' printf " \'"\n'
665 ' printf "%s" "$word" | sed -e "s/\'/\'\\\\\\\\\'\'/"\n'
666 ' printf "\'"\n'
667 ' fi\n'
668 'done >> "$this_log"\n'
669 'echo >> "$this_log"\n'
670 'echo "Directory: $dir" >> "$this_log"\n'
671 'echo "Path addition: $path" >> "$this_log"\n'
672 'echo >> "$this_log"\n'
673 'record_status ()\n'
674 '{\n'
675 ' echo >> "$this_log"\n'
676 ' echo "$1: $desc" > "$this_status"\n'
677 ' echo "$1: $desc" >> "$this_log"\n'
678 ' echo >> "$this_log"\n'
679 ' date >> "$this_log"\n'
680 ' echo "$1: $desc"\n'
681 ' exit 0\n'
682 '}\n'
683 'check_error ()\n'
684 '{\n'
685 ' if [ "$1" != "0" ]; then\n'
686 ' record_status FAIL\n'
687 ' fi\n'
688 '}\n'
689 'if [ "$prev_base" ] && ! grep -q "^PASS" "$prev_status"; then\n'
690 ' record_status UNRESOLVED\n'
691 'fi\n'
692 'if [ "$dir" ]; then\n'
693 ' cd "$dir"\n'
694 ' check_error "$?"\n'
695 'fi\n'
696 'if [ "$path" ]; then\n'
697 ' PATH=$path:$PATH\n'
698 'fi\n'
699 '"$@" < /dev/null >> "$this_log" 2>&1\n'
700 'check_error "$?"\n'
701 'record_status PASS\n')
702 with open(self.wrapper, 'w') as f:
703 f.write(wrapper_text)
704 # Mode 0o755.
705 mode_exec = (stat.S_IRWXU|stat.S_IRGRP|stat.S_IXGRP|
706 stat.S_IROTH|stat.S_IXOTH)
707 os.chmod(self.wrapper, mode_exec)
708 save_logs_text = (
709 '#!/bin/sh\n'
710 'if ! [ -f tests.sum ]; then\n'
711 ' echo "No test summary available."\n'
712 ' exit 0\n'
713 'fi\n'
714 'save_file ()\n'
715 '{\n'
716 ' echo "Contents of $1:"\n'
717 ' echo\n'
718 ' cat "$1"\n'
719 ' echo\n'
720 ' echo "End of contents of $1."\n'
721 ' echo\n'
722 '}\n'
723 'save_file tests.sum\n'
724 'non_pass_tests=$(grep -v "^PASS: " tests.sum | sed -e "s/^PASS: //")\n'
725 'for t in $non_pass_tests; do\n'
726 ' if [ -f "$t.out" ]; then\n'
727 ' save_file "$t.out"\n'
728 ' fi\n'
729 'done\n')
730 with open(self.save_logs, 'w') as f:
731 f.write(save_logs_text)
732 os.chmod(self.save_logs, mode_exec)
734 def do_build(self):
735 """Do the actual build."""
736 cmd = ['make', '-O', '-j%d' % self.parallelism]
737 subprocess.run(cmd, cwd=self.builddir, check=True)
739 def build_host_libraries(self):
740 """Build the host libraries."""
741 installdir = self.host_libraries_installdir
742 builddir = os.path.join(self.builddir, 'host-libraries')
743 logsdir = os.path.join(self.logsdir, 'host-libraries')
744 self.remove_recreate_dirs(installdir, builddir, logsdir)
745 cmdlist = CommandList('host-libraries', self.keep)
746 self.build_host_library(cmdlist, 'gmp')
747 self.build_host_library(cmdlist, 'mpfr',
748 ['--with-gmp=%s' % installdir])
749 self.build_host_library(cmdlist, 'mpc',
750 ['--with-gmp=%s' % installdir,
751 '--with-mpfr=%s' % installdir])
752 cmdlist.add_command('done', ['touch', os.path.join(installdir, 'ok')])
753 self.add_makefile_cmdlist('host-libraries', cmdlist, logsdir)
755 def build_host_library(self, cmdlist, lib, extra_opts=None):
756 """Build one host library."""
757 srcdir = self.component_srcdir(lib)
758 builddir = self.component_builddir('host-libraries', None, lib)
759 installdir = self.host_libraries_installdir
760 cmdlist.push_subdesc(lib)
761 cmdlist.create_use_dir(builddir)
762 cfg_cmd = [os.path.join(srcdir, 'configure'),
763 '--prefix=%s' % installdir,
764 '--disable-shared']
765 if extra_opts:
766 cfg_cmd.extend (extra_opts)
767 cmdlist.add_command('configure', cfg_cmd)
768 cmdlist.add_command('build', ['make'])
769 cmdlist.add_command('check', ['make', 'check'])
770 cmdlist.add_command('install', ['make', 'install'])
771 cmdlist.cleanup_dir()
772 cmdlist.pop_subdesc()
774 def build_compilers(self, configs):
775 """Build the compilers."""
776 if not configs:
777 self.remove_dirs(os.path.join(self.builddir, 'compilers'))
778 self.remove_dirs(os.path.join(self.installdir, 'compilers'))
779 self.remove_dirs(os.path.join(self.logsdir, 'compilers'))
780 configs = sorted(self.configs.keys())
781 for c in configs:
782 self.configs[c].build()
784 def build_glibcs(self, configs):
785 """Build the glibcs."""
786 if not configs:
787 self.remove_dirs(os.path.join(self.builddir, 'glibcs'))
788 self.remove_dirs(os.path.join(self.installdir, 'glibcs'))
789 self.remove_dirs(os.path.join(self.logsdir, 'glibcs'))
790 configs = sorted(self.glibc_configs.keys())
791 for c in configs:
792 self.glibc_configs[c].build()
794 def update_syscalls(self, configs):
795 """Update the glibc syscall lists."""
796 if not configs:
797 self.remove_dirs(os.path.join(self.builddir, 'update-syscalls'))
798 self.remove_dirs(os.path.join(self.logsdir, 'update-syscalls'))
799 configs = sorted(self.glibc_configs.keys())
800 for c in configs:
801 self.glibc_configs[c].update_syscalls()
803 def load_versions_json(self):
804 """Load information about source directory versions."""
805 if not os.access(self.versions_json, os.F_OK):
806 self.versions = {}
807 return
808 with open(self.versions_json, 'r') as f:
809 self.versions = json.load(f)
811 def store_json(self, data, filename):
812 """Store information in a JSON file."""
813 filename_tmp = filename + '.tmp'
814 with open(filename_tmp, 'w') as f:
815 json.dump(data, f, indent=2, sort_keys=True)
816 os.rename(filename_tmp, filename)
818 def store_versions_json(self):
819 """Store information about source directory versions."""
820 self.store_json(self.versions, self.versions_json)
822 def set_component_version(self, component, version, explicit, revision):
823 """Set the version information for a component."""
824 self.versions[component] = {'version': version,
825 'explicit': explicit,
826 'revision': revision}
827 self.store_versions_json()
829 def checkout(self, versions):
830 """Check out the desired component versions."""
831 default_versions = {'binutils': 'vcs-2.42',
832 'gcc': 'vcs-13',
833 'glibc': 'vcs-mainline',
834 'gmp': '6.3.0',
835 'linux': '6.8',
836 'mpc': '1.3.1',
837 'mpfr': '4.2.1',
838 'mig': 'vcs-mainline',
839 'gnumach': 'vcs-mainline',
840 'hurd': 'vcs-mainline'}
841 use_versions = {}
842 explicit_versions = {}
843 for v in versions:
844 found_v = False
845 for k in default_versions.keys():
846 kx = k + '-'
847 if v.startswith(kx):
848 vx = v[len(kx):]
849 if k in use_versions:
850 print('error: multiple versions for %s' % k)
851 exit(1)
852 use_versions[k] = vx
853 explicit_versions[k] = True
854 found_v = True
855 break
856 if not found_v:
857 print('error: unknown component in %s' % v)
858 exit(1)
859 for k in default_versions.keys():
860 if k not in use_versions:
861 if k in self.versions and self.versions[k]['explicit']:
862 use_versions[k] = self.versions[k]['version']
863 explicit_versions[k] = True
864 else:
865 use_versions[k] = default_versions[k]
866 explicit_versions[k] = False
867 os.makedirs(self.srcdir, exist_ok=True)
868 for k in sorted(default_versions.keys()):
869 update = os.access(self.component_srcdir(k), os.F_OK)
870 v = use_versions[k]
871 if (update and
872 k in self.versions and
873 v != self.versions[k]['version']):
874 if not self.replace_sources:
875 print('error: version of %s has changed from %s to %s, '
876 'use --replace-sources to check out again' %
877 (k, self.versions[k]['version'], v))
878 exit(1)
879 shutil.rmtree(self.component_srcdir(k))
880 update = False
881 if v.startswith('vcs-'):
882 revision = self.checkout_vcs(k, v[4:], update)
883 else:
884 self.checkout_tar(k, v, update)
885 revision = v
886 self.set_component_version(k, v, explicit_versions[k], revision)
887 if self.get_script_text() != self.script_text:
888 # Rerun the checkout process in case the updated script
889 # uses different default versions or new components.
890 self.exec_self()
892 def checkout_vcs(self, component, version, update):
893 """Check out the given version of the given component from version
894 control. Return a revision identifier."""
895 if component == 'binutils':
896 git_url = 'https://sourceware.org/git/binutils-gdb.git'
897 if version == 'mainline':
898 git_branch = 'master'
899 else:
900 trans = str.maketrans({'.': '_'})
901 git_branch = 'binutils-%s-branch' % version.translate(trans)
902 return self.git_checkout(component, git_url, git_branch, update)
903 elif component == 'gcc':
904 if version == 'mainline':
905 branch = 'master'
906 else:
907 branch = 'releases/gcc-%s' % version
908 return self.gcc_checkout(branch, update)
909 elif component == 'glibc':
910 git_url = 'https://sourceware.org/git/glibc.git'
911 if version == 'mainline':
912 git_branch = 'master'
913 else:
914 git_branch = 'release/%s/master' % version
915 r = self.git_checkout(component, git_url, git_branch, update)
916 self.fix_glibc_timestamps()
917 return r
918 elif component == 'gnumach':
919 git_url = 'git://git.savannah.gnu.org/hurd/gnumach.git'
920 git_branch = 'master'
921 r = self.git_checkout(component, git_url, git_branch, update)
922 subprocess.run(['autoreconf', '-i'],
923 cwd=self.component_srcdir(component), check=True)
924 return r
925 elif component == 'mig':
926 git_url = 'git://git.savannah.gnu.org/hurd/mig.git'
927 git_branch = 'master'
928 r = self.git_checkout(component, git_url, git_branch, update)
929 subprocess.run(['autoreconf', '-i'],
930 cwd=self.component_srcdir(component), check=True)
931 return r
932 elif component == 'hurd':
933 git_url = 'git://git.savannah.gnu.org/hurd/hurd.git'
934 git_branch = 'master'
935 r = self.git_checkout(component, git_url, git_branch, update)
936 subprocess.run(['autoconf'],
937 cwd=self.component_srcdir(component), check=True)
938 return r
939 else:
940 print('error: component %s coming from VCS' % component)
941 exit(1)
943 def git_checkout(self, component, git_url, git_branch, update):
944 """Check out a component from git. Return a commit identifier."""
945 if update:
946 subprocess.run(['git', 'remote', 'prune', 'origin'],
947 cwd=self.component_srcdir(component), check=True)
948 if self.replace_sources:
949 subprocess.run(['git', 'clean', '-dxfq'],
950 cwd=self.component_srcdir(component), check=True)
951 subprocess.run(['git', 'pull', '-q'],
952 cwd=self.component_srcdir(component), check=True)
953 else:
954 if self.shallow:
955 depth_arg = ('--depth', '1')
956 else:
957 depth_arg = ()
958 subprocess.run(['git', 'clone', '-q', '-b', git_branch,
959 *depth_arg, git_url,
960 self.component_srcdir(component)], check=True)
961 r = subprocess.run(['git', 'rev-parse', 'HEAD'],
962 cwd=self.component_srcdir(component),
963 stdout=subprocess.PIPE,
964 check=True, universal_newlines=True).stdout
965 return r.rstrip()
967 def fix_glibc_timestamps(self):
968 """Fix timestamps in a glibc checkout."""
969 # Ensure that builds do not try to regenerate generated files
970 # in the source tree.
971 srcdir = self.component_srcdir('glibc')
972 # These files have Makefile dependencies to regenerate them in
973 # the source tree that may be active during a normal build.
974 # Some other files have such dependencies but do not need to
975 # be touched because nothing in a build depends on the files
976 # in question.
977 for f in ('sysdeps/mach/hurd/bits/errno.h',):
978 to_touch = os.path.join(srcdir, f)
979 subprocess.run(['touch', '-c', to_touch], check=True)
980 for dirpath, dirnames, filenames in os.walk(srcdir):
981 for f in filenames:
982 if (f == 'configure' or
983 f == 'preconfigure' or
984 f.endswith('-kw.h')):
985 to_touch = os.path.join(dirpath, f)
986 subprocess.run(['touch', to_touch], check=True)
988 def gcc_checkout(self, branch, update):
989 """Check out GCC from git. Return the commit identifier."""
990 if os.access(os.path.join(self.component_srcdir('gcc'), '.svn'),
991 os.F_OK):
992 if not self.replace_sources:
993 print('error: GCC has moved from SVN to git, use '
994 '--replace-sources to check out again')
995 exit(1)
996 shutil.rmtree(self.component_srcdir('gcc'))
997 update = False
998 if not update:
999 self.git_checkout('gcc', 'https://gcc.gnu.org/git/gcc.git',
1000 branch, update)
1001 subprocess.run(['contrib/gcc_update', '--silent'],
1002 cwd=self.component_srcdir('gcc'), check=True)
1003 r = subprocess.run(['git', 'rev-parse', 'HEAD'],
1004 cwd=self.component_srcdir('gcc'),
1005 stdout=subprocess.PIPE,
1006 check=True, universal_newlines=True).stdout
1007 return r.rstrip()
1009 def checkout_tar(self, component, version, update):
1010 """Check out the given version of the given component from a
1011 tarball."""
1012 if update:
1013 return
1014 url_map = {'binutils': 'https://ftp.gnu.org/gnu/binutils/binutils-%(version)s.tar.bz2',
1015 'gcc': 'https://ftp.gnu.org/gnu/gcc/gcc-%(version)s/gcc-%(version)s.tar.gz',
1016 'gmp': 'https://ftp.gnu.org/gnu/gmp/gmp-%(version)s.tar.xz',
1017 'linux': 'https://www.kernel.org/pub/linux/kernel/v%(major)s.x/linux-%(version)s.tar.xz',
1018 'mpc': 'https://ftp.gnu.org/gnu/mpc/mpc-%(version)s.tar.gz',
1019 'mpfr': 'https://ftp.gnu.org/gnu/mpfr/mpfr-%(version)s.tar.xz',
1020 'mig': 'https://ftp.gnu.org/gnu/mig/mig-%(version)s.tar.bz2',
1021 'gnumach': 'https://ftp.gnu.org/gnu/gnumach/gnumach-%(version)s.tar.bz2',
1022 'hurd': 'https://ftp.gnu.org/gnu/hurd/hurd-%(version)s.tar.bz2'}
1023 if component not in url_map:
1024 print('error: component %s coming from tarball' % component)
1025 exit(1)
1026 version_major = version.split('.')[0]
1027 url = url_map[component] % {'version': version, 'major': version_major}
1028 filename = os.path.join(self.srcdir, url.split('/')[-1])
1029 response = urllib.request.urlopen(url)
1030 data = response.read()
1031 with open(filename, 'wb') as f:
1032 f.write(data)
1033 subprocess.run(['tar', '-C', self.srcdir, '-x', '-f', filename],
1034 check=True)
1035 os.rename(os.path.join(self.srcdir, '%s-%s' % (component, version)),
1036 self.component_srcdir(component))
1037 os.remove(filename)
1039 def load_build_state_json(self):
1040 """Load information about the state of previous builds."""
1041 if os.access(self.build_state_json, os.F_OK):
1042 with open(self.build_state_json, 'r') as f:
1043 self.build_state = json.load(f)
1044 else:
1045 self.build_state = {}
1046 for k in ('host-libraries', 'compilers', 'glibcs', 'update-syscalls'):
1047 if k not in self.build_state:
1048 self.build_state[k] = {}
1049 if 'build-time' not in self.build_state[k]:
1050 self.build_state[k]['build-time'] = ''
1051 if 'build-versions' not in self.build_state[k]:
1052 self.build_state[k]['build-versions'] = {}
1053 if 'build-results' not in self.build_state[k]:
1054 self.build_state[k]['build-results'] = {}
1055 if 'result-changes' not in self.build_state[k]:
1056 self.build_state[k]['result-changes'] = {}
1057 if 'ever-passed' not in self.build_state[k]:
1058 self.build_state[k]['ever-passed'] = []
1060 def store_build_state_json(self):
1061 """Store information about the state of previous builds."""
1062 self.store_json(self.build_state, self.build_state_json)
1064 def clear_last_build_state(self, action):
1065 """Clear information about the state of part of the build."""
1066 # We clear the last build time and versions when starting a
1067 # new build. The results of the last build are kept around,
1068 # as comparison is still meaningful if this build is aborted
1069 # and a new one started.
1070 self.build_state[action]['build-time'] = ''
1071 self.build_state[action]['build-versions'] = {}
1072 self.store_build_state_json()
1074 def update_build_state(self, action, build_time, build_versions):
1075 """Update the build state after a build."""
1076 build_time = build_time.replace(microsecond=0)
1077 self.build_state[action]['build-time'] = build_time.strftime(
1078 '%Y-%m-%d %H:%M:%S')
1079 self.build_state[action]['build-versions'] = build_versions
1080 build_results = {}
1081 for log in self.status_log_list:
1082 with open(log, 'r') as f:
1083 log_text = f.read()
1084 log_text = log_text.rstrip()
1085 m = re.fullmatch('([A-Z]+): (.*)', log_text)
1086 result = m.group(1)
1087 test_name = m.group(2)
1088 assert test_name not in build_results
1089 build_results[test_name] = result
1090 old_build_results = self.build_state[action]['build-results']
1091 self.build_state[action]['build-results'] = build_results
1092 result_changes = {}
1093 all_tests = set(old_build_results.keys()) | set(build_results.keys())
1094 for t in all_tests:
1095 if t in old_build_results:
1096 old_res = old_build_results[t]
1097 else:
1098 old_res = '(New test)'
1099 if t in build_results:
1100 new_res = build_results[t]
1101 else:
1102 new_res = '(Test removed)'
1103 if old_res != new_res:
1104 result_changes[t] = '%s -> %s' % (old_res, new_res)
1105 self.build_state[action]['result-changes'] = result_changes
1106 old_ever_passed = {t for t in self.build_state[action]['ever-passed']
1107 if t in build_results}
1108 new_passes = {t for t in build_results if build_results[t] == 'PASS'}
1109 self.build_state[action]['ever-passed'] = sorted(old_ever_passed |
1110 new_passes)
1111 self.store_build_state_json()
1113 def load_bot_config_json(self):
1114 """Load bot configuration."""
1115 with open(self.bot_config_json, 'r') as f:
1116 self.bot_config = json.load(f)
1118 def part_build_old(self, action, delay):
1119 """Return whether the last build for a given action was at least a
1120 given number of seconds ago, or does not have a time recorded."""
1121 old_time_str = self.build_state[action]['build-time']
1122 if not old_time_str:
1123 return True
1124 old_time = datetime.datetime.strptime(
1125 old_time_str, '%Y-%m-%d %H:%M:%S').replace(
1126 tzinfo=datetime.timezone.utc)
1127 new_time = datetime.datetime.now(datetime.timezone.utc)
1128 delta = new_time - old_time
1129 return delta.total_seconds() >= delay
1131 def bot_cycle(self):
1132 """Run a single round of checkout and builds."""
1133 print('Bot cycle starting %s.'
1134 % str(datetime.datetime.now(datetime.timezone.utc)))
1135 self.load_bot_config_json()
1136 actions = ('host-libraries', 'compilers', 'glibcs')
1137 self.bot_run_self(['--replace-sources'], 'checkout')
1138 self.load_versions_json()
1139 if self.get_script_text() != self.script_text:
1140 print('Script changed, re-execing.')
1141 # On script change, all parts of the build should be rerun.
1142 for a in actions:
1143 self.clear_last_build_state(a)
1144 self.exec_self()
1145 check_components = {'host-libraries': ('gmp', 'mpfr', 'mpc'),
1146 'compilers': ('binutils', 'gcc', 'glibc', 'linux',
1147 'mig', 'gnumach', 'hurd'),
1148 'glibcs': ('glibc',)}
1149 must_build = {}
1150 for a in actions:
1151 build_vers = self.build_state[a]['build-versions']
1152 must_build[a] = False
1153 if not self.build_state[a]['build-time']:
1154 must_build[a] = True
1155 old_vers = {}
1156 new_vers = {}
1157 for c in check_components[a]:
1158 if c in build_vers:
1159 old_vers[c] = build_vers[c]
1160 new_vers[c] = {'version': self.versions[c]['version'],
1161 'revision': self.versions[c]['revision']}
1162 if new_vers == old_vers:
1163 print('Versions for %s unchanged.' % a)
1164 else:
1165 print('Versions changed or rebuild forced for %s.' % a)
1166 if a == 'compilers' and not self.part_build_old(
1167 a, self.bot_config['compilers-rebuild-delay']):
1168 print('Not requiring rebuild of compilers this soon.')
1169 else:
1170 must_build[a] = True
1171 if must_build['host-libraries']:
1172 must_build['compilers'] = True
1173 if must_build['compilers']:
1174 must_build['glibcs'] = True
1175 for a in actions:
1176 if must_build[a]:
1177 print('Must rebuild %s.' % a)
1178 self.clear_last_build_state(a)
1179 else:
1180 print('No need to rebuild %s.' % a)
1181 if os.access(self.logsdir, os.F_OK):
1182 shutil.rmtree(self.logsdir_old, ignore_errors=True)
1183 shutil.copytree(self.logsdir, self.logsdir_old)
1184 for a in actions:
1185 if must_build[a]:
1186 build_time = datetime.datetime.now(datetime.timezone.utc)
1187 print('Rebuilding %s at %s.' % (a, str(build_time)))
1188 self.bot_run_self([], a)
1189 self.load_build_state_json()
1190 self.bot_build_mail(a, build_time)
1191 print('Bot cycle done at %s.'
1192 % str(datetime.datetime.now(datetime.timezone.utc)))
1194 def bot_build_mail(self, action, build_time):
1195 """Send email with the results of a build."""
1196 if not ('email-from' in self.bot_config and
1197 'email-server' in self.bot_config and
1198 'email-subject' in self.bot_config and
1199 'email-to' in self.bot_config):
1200 if not self.email_warning:
1201 print("Email not configured, not sending.")
1202 self.email_warning = True
1203 return
1205 build_time = build_time.replace(microsecond=0)
1206 subject = (self.bot_config['email-subject'] %
1207 {'action': action,
1208 'build-time': build_time.strftime('%Y-%m-%d %H:%M:%S')})
1209 results = self.build_state[action]['build-results']
1210 changes = self.build_state[action]['result-changes']
1211 ever_passed = set(self.build_state[action]['ever-passed'])
1212 versions = self.build_state[action]['build-versions']
1213 new_regressions = {k for k in changes if changes[k] == 'PASS -> FAIL'}
1214 all_regressions = {k for k in ever_passed if results[k] == 'FAIL'}
1215 all_fails = {k for k in results if results[k] == 'FAIL'}
1216 if new_regressions:
1217 new_reg_list = sorted(['FAIL: %s' % k for k in new_regressions])
1218 new_reg_text = ('New regressions:\n\n%s\n\n' %
1219 '\n'.join(new_reg_list))
1220 else:
1221 new_reg_text = ''
1222 if all_regressions:
1223 all_reg_list = sorted(['FAIL: %s' % k for k in all_regressions])
1224 all_reg_text = ('All regressions:\n\n%s\n\n' %
1225 '\n'.join(all_reg_list))
1226 else:
1227 all_reg_text = ''
1228 if all_fails:
1229 all_fail_list = sorted(['FAIL: %s' % k for k in all_fails])
1230 all_fail_text = ('All failures:\n\n%s\n\n' %
1231 '\n'.join(all_fail_list))
1232 else:
1233 all_fail_text = ''
1234 if changes:
1235 changes_list = sorted(changes.keys())
1236 changes_list = ['%s: %s' % (changes[k], k) for k in changes_list]
1237 changes_text = ('All changed results:\n\n%s\n\n' %
1238 '\n'.join(changes_list))
1239 else:
1240 changes_text = ''
1241 results_text = (new_reg_text + all_reg_text + all_fail_text +
1242 changes_text)
1243 if not results_text:
1244 results_text = 'Clean build with unchanged results.\n\n'
1245 versions_list = sorted(versions.keys())
1246 versions_list = ['%s: %s (%s)' % (k, versions[k]['version'],
1247 versions[k]['revision'])
1248 for k in versions_list]
1249 versions_text = ('Component versions for this build:\n\n%s\n' %
1250 '\n'.join(versions_list))
1251 body_text = results_text + versions_text
1252 msg = email.mime.text.MIMEText(body_text)
1253 msg['Subject'] = subject
1254 msg['From'] = self.bot_config['email-from']
1255 msg['To'] = self.bot_config['email-to']
1256 msg['Message-ID'] = email.utils.make_msgid()
1257 msg['Date'] = email.utils.format_datetime(
1258 datetime.datetime.now(datetime.timezone.utc))
1259 with smtplib.SMTP(self.bot_config['email-server']) as s:
1260 s.send_message(msg)
1262 def bot_run_self(self, opts, action, check=True):
1263 """Run a copy of this script with given options."""
1264 cmd = [sys.executable, sys.argv[0], '--keep=none',
1265 '-j%d' % self.parallelism]
1266 if self.full_gcc:
1267 cmd.append('--full-gcc')
1268 cmd.extend(opts)
1269 cmd.extend([self.topdir, action])
1270 sys.stdout.flush()
1271 subprocess.run(cmd, check=check)
1273 def bot(self):
1274 """Run repeated rounds of checkout and builds."""
1275 while True:
1276 self.load_bot_config_json()
1277 if not self.bot_config['run']:
1278 print('Bot exiting by request.')
1279 exit(0)
1280 self.bot_run_self([], 'bot-cycle', check=False)
1281 self.load_bot_config_json()
1282 if not self.bot_config['run']:
1283 print('Bot exiting by request.')
1284 exit(0)
1285 time.sleep(self.bot_config['delay'])
1286 if self.get_script_text() != self.script_text:
1287 print('Script changed, bot re-execing.')
1288 self.exec_self()
1290 class LinuxHeadersPolicyForBuild(object):
1291 """Names and directories for installing Linux headers. Build variant."""
1293 def __init__(self, config):
1294 self.arch = config.arch
1295 self.srcdir = config.ctx.component_srcdir('linux')
1296 self.builddir = config.component_builddir('linux')
1297 self.headers_dir = os.path.join(config.sysroot, 'usr')
1299 class LinuxHeadersPolicyForUpdateSyscalls(object):
1300 """Names and directories for Linux headers. update-syscalls variant."""
1302 def __init__(self, glibc, headers_dir):
1303 self.arch = glibc.compiler.arch
1304 self.srcdir = glibc.compiler.ctx.component_srcdir('linux')
1305 self.builddir = glibc.ctx.component_builddir(
1306 'update-syscalls', glibc.name, 'build-linux')
1307 self.headers_dir = headers_dir
1309 def install_linux_headers(policy, cmdlist):
1310 """Install Linux kernel headers."""
1311 arch_map = {'aarch64': 'arm64',
1312 'alpha': 'alpha',
1313 'arc': 'arc',
1314 'arm': 'arm',
1315 'csky': 'csky',
1316 'hppa': 'parisc',
1317 'i486': 'x86',
1318 'i586': 'x86',
1319 'i686': 'x86',
1320 'i786': 'x86',
1321 'loongarch64': 'loongarch',
1322 'm68k': 'm68k',
1323 'microblaze': 'microblaze',
1324 'mips': 'mips',
1325 'nios2': 'nios2',
1326 'or1k': 'openrisc',
1327 'powerpc': 'powerpc',
1328 's390': 's390',
1329 'riscv32': 'riscv',
1330 'riscv64': 'riscv',
1331 'sh': 'sh',
1332 'sparc': 'sparc',
1333 'x86_64': 'x86'}
1334 linux_arch = None
1335 for k in arch_map:
1336 if policy.arch.startswith(k):
1337 linux_arch = arch_map[k]
1338 break
1339 assert linux_arch is not None
1340 cmdlist.push_subdesc('linux')
1341 cmdlist.create_use_dir(policy.builddir)
1342 cmdlist.add_command('install-headers',
1343 ['make', '-C', policy.srcdir, 'O=%s' % policy.builddir,
1344 'ARCH=%s' % linux_arch,
1345 'INSTALL_HDR_PATH=%s' % policy.headers_dir,
1346 'headers_install'])
1347 cmdlist.cleanup_dir()
1348 cmdlist.pop_subdesc()
1350 class Config(object):
1351 """A configuration for building a compiler and associated libraries."""
1353 def __init__(self, ctx, arch, os_name, variant=None, gcc_cfg=None,
1354 first_gcc_cfg=None, binutils_cfg=None, glibcs=None,
1355 extra_glibcs=None):
1356 """Initialize a Config object."""
1357 self.ctx = ctx
1358 self.arch = arch
1359 self.os = os_name
1360 self.variant = variant
1361 if variant is None:
1362 self.name = '%s-%s' % (arch, os_name)
1363 else:
1364 self.name = '%s-%s-%s' % (arch, os_name, variant)
1365 self.triplet = '%s-glibc-%s' % (arch, os_name)
1366 if gcc_cfg is None:
1367 self.gcc_cfg = []
1368 else:
1369 self.gcc_cfg = gcc_cfg
1370 if first_gcc_cfg is None:
1371 self.first_gcc_cfg = []
1372 else:
1373 self.first_gcc_cfg = first_gcc_cfg
1374 if binutils_cfg is None:
1375 self.binutils_cfg = []
1376 else:
1377 self.binutils_cfg = binutils_cfg
1378 if glibcs is None:
1379 glibcs = [{'variant': variant}]
1380 if extra_glibcs is None:
1381 extra_glibcs = []
1382 glibcs = [Glibc(self, **g) for g in glibcs]
1383 extra_glibcs = [Glibc(self, **g) for g in extra_glibcs]
1384 self.all_glibcs = glibcs + extra_glibcs
1385 self.compiler_glibcs = glibcs
1386 self.installdir = ctx.compiler_installdir(self.name)
1387 self.bindir = ctx.compiler_bindir(self.name)
1388 self.sysroot = ctx.compiler_sysroot(self.name)
1389 self.builddir = os.path.join(ctx.builddir, 'compilers', self.name)
1390 self.logsdir = os.path.join(ctx.logsdir, 'compilers', self.name)
1392 def component_builddir(self, component):
1393 """Return the directory to use for a (non-glibc) build."""
1394 return self.ctx.component_builddir('compilers', self.name, component)
1396 def build(self):
1397 """Generate commands to build this compiler."""
1398 self.ctx.remove_recreate_dirs(self.installdir, self.builddir,
1399 self.logsdir)
1400 cmdlist = CommandList('compilers-%s' % self.name, self.ctx.keep)
1401 cmdlist.add_command('check-host-libraries',
1402 ['test', '-f',
1403 os.path.join(self.ctx.host_libraries_installdir,
1404 'ok')])
1405 cmdlist.use_path(self.bindir)
1406 self.build_cross_tool(cmdlist, 'binutils', 'binutils',
1407 ['--disable-gdb',
1408 '--disable-gdbserver',
1409 '--disable-libdecnumber',
1410 '--disable-readline',
1411 '--disable-sim'] + self.binutils_cfg)
1412 if self.os.startswith('linux'):
1413 install_linux_headers(LinuxHeadersPolicyForBuild(self), cmdlist)
1414 self.build_gcc(cmdlist, True)
1415 if self.os == 'gnu':
1416 self.install_gnumach_headers(cmdlist)
1417 self.build_cross_tool(cmdlist, 'mig', 'mig')
1418 self.install_hurd_headers(cmdlist)
1419 for g in self.compiler_glibcs:
1420 cmdlist.push_subdesc('glibc')
1421 cmdlist.push_subdesc(g.name)
1422 g.build_glibc(cmdlist, GlibcPolicyForCompiler(g))
1423 cmdlist.pop_subdesc()
1424 cmdlist.pop_subdesc()
1425 self.build_gcc(cmdlist, False)
1426 cmdlist.add_command('done', ['touch',
1427 os.path.join(self.installdir, 'ok')])
1428 self.ctx.add_makefile_cmdlist('compilers-%s' % self.name, cmdlist,
1429 self.logsdir)
1431 def build_cross_tool(self, cmdlist, tool_src, tool_build, extra_opts=None):
1432 """Build one cross tool."""
1433 srcdir = self.ctx.component_srcdir(tool_src)
1434 builddir = self.component_builddir(tool_build)
1435 cmdlist.push_subdesc(tool_build)
1436 cmdlist.create_use_dir(builddir)
1437 cfg_cmd = [os.path.join(srcdir, 'configure'),
1438 '--prefix=%s' % self.installdir,
1439 '--build=%s' % self.ctx.build_triplet,
1440 '--host=%s' % self.ctx.build_triplet,
1441 '--target=%s' % self.triplet,
1442 '--with-sysroot=%s' % self.sysroot]
1443 if extra_opts:
1444 cfg_cmd.extend(extra_opts)
1445 cmdlist.add_command('configure', cfg_cmd)
1446 cmdlist.add_command('build', ['make'])
1447 # Parallel "make install" for GCC has race conditions that can
1448 # cause it to fail; see
1449 # <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=42980>. Such
1450 # problems are not known for binutils, but doing the
1451 # installation in parallel within a particular toolchain build
1452 # (as opposed to installation of one toolchain from
1453 # build-many-glibcs.py running in parallel to the installation
1454 # of other toolchains being built) is not known to be
1455 # significantly beneficial, so it is simplest just to disable
1456 # parallel install for cross tools here.
1457 cmdlist.add_command('install', ['make', '-j1', 'install'])
1458 cmdlist.cleanup_dir()
1459 cmdlist.pop_subdesc()
1461 def install_gnumach_headers(self, cmdlist):
1462 """Install GNU Mach headers."""
1463 srcdir = self.ctx.component_srcdir('gnumach')
1464 builddir = self.component_builddir('gnumach')
1465 cmdlist.push_subdesc('gnumach')
1466 cmdlist.create_use_dir(builddir)
1467 cmdlist.add_command('configure',
1468 [os.path.join(srcdir, 'configure'),
1469 '--build=%s' % self.ctx.build_triplet,
1470 '--host=%s' % self.triplet,
1471 '--prefix=',
1472 '--disable-user32',
1473 'CC=%s-gcc -nostdlib' % self.triplet])
1474 cmdlist.add_command('install', ['make', 'DESTDIR=%s' % self.sysroot,
1475 'install-data'])
1476 cmdlist.cleanup_dir()
1477 cmdlist.pop_subdesc()
1479 def install_hurd_headers(self, cmdlist):
1480 """Install Hurd headers."""
1481 srcdir = self.ctx.component_srcdir('hurd')
1482 builddir = self.component_builddir('hurd')
1483 cmdlist.push_subdesc('hurd')
1484 cmdlist.create_use_dir(builddir)
1485 cmdlist.add_command('configure',
1486 [os.path.join(srcdir, 'configure'),
1487 '--build=%s' % self.ctx.build_triplet,
1488 '--host=%s' % self.triplet,
1489 '--prefix=',
1490 '--disable-profile', '--without-parted',
1491 'CC=%s-gcc -nostdlib' % self.triplet])
1492 cmdlist.add_command('install', ['make', 'prefix=%s' % self.sysroot,
1493 'no_deps=t', 'install-headers'])
1494 cmdlist.cleanup_dir()
1495 cmdlist.pop_subdesc()
1497 def build_gcc(self, cmdlist, bootstrap):
1498 """Build GCC."""
1499 # libssp is of little relevance with glibc's own stack
1500 # checking support. libcilkrts does not support GNU/Hurd (and
1501 # has been removed in GCC 8, so --disable-libcilkrts can be
1502 # removed once glibc no longer supports building with older
1503 # GCC versions). --enable-initfini-array is enabled by default
1504 # in GCC 12, which can be removed when GCC 12 becomes the
1505 # minimum requirement.
1506 cfg_opts = list(self.gcc_cfg)
1507 cfg_opts += ['--enable-initfini-array']
1508 cfg_opts += ['--disable-libssp', '--disable-libcilkrts']
1509 host_libs = self.ctx.host_libraries_installdir
1510 cfg_opts += ['--with-gmp=%s' % host_libs,
1511 '--with-mpfr=%s' % host_libs,
1512 '--with-mpc=%s' % host_libs]
1513 if bootstrap:
1514 tool_build = 'gcc-first'
1515 # Building a static-only, C-only compiler that is
1516 # sufficient to build glibc. Various libraries and
1517 # features that may require libc headers must be disabled.
1518 # When configuring with a sysroot, --with-newlib is
1519 # required to define inhibit_libc (to stop some parts of
1520 # libgcc including libc headers); --without-headers is not
1521 # sufficient.
1522 cfg_opts += ['--enable-languages=c', '--disable-shared',
1523 '--disable-threads',
1524 '--disable-libatomic',
1525 '--disable-decimal-float',
1526 '--disable-gcov',
1527 '--disable-libffi',
1528 '--disable-libgomp',
1529 '--disable-libitm',
1530 '--disable-libmpx',
1531 '--disable-libquadmath',
1532 '--disable-libsanitizer',
1533 '--without-headers', '--with-newlib',
1534 '--with-glibc-version=%s' % self.ctx.glibc_version
1536 cfg_opts += self.first_gcc_cfg
1537 else:
1538 tool_build = 'gcc'
1539 # libsanitizer commonly breaks because of glibc header
1540 # changes, or on unusual targets. C++ pre-compiled
1541 # headers are not used during the glibc build and are
1542 # expensive to create.
1543 if not self.ctx.full_gcc:
1544 cfg_opts += ['--disable-libsanitizer',
1545 '--disable-libstdcxx-pch']
1546 langs = 'all' if self.ctx.full_gcc else 'c,c++'
1547 cfg_opts += ['--enable-languages=%s' % langs,
1548 '--enable-shared', '--enable-threads']
1549 self.build_cross_tool(cmdlist, 'gcc', tool_build, cfg_opts)
1551 class GlibcPolicyDefault(object):
1552 """Build policy for glibc: common defaults."""
1554 def __init__(self, glibc):
1555 self.srcdir = glibc.ctx.component_srcdir('glibc')
1556 self.use_usr = glibc.os != 'gnu'
1557 self.prefix = '/usr' if self.use_usr else ''
1558 self.configure_args = [
1559 '--prefix=%s' % self.prefix,
1560 '--enable-profile',
1561 '--build=%s' % glibc.ctx.build_triplet,
1562 '--host=%s' % glibc.triplet,
1563 'CC=%s' % glibc.tool_name('gcc'),
1564 'CXX=%s' % glibc.tool_name('g++'),
1566 if glibc.os == 'gnu':
1567 self.configure_args.append('MIG=%s' % glibc.tool_name('mig'))
1568 if glibc.cflags:
1569 self.configure_args.append('CFLAGS=%s' % glibc.cflags)
1570 self.configure_args.append('CXXFLAGS=%s' % glibc.cflags)
1571 self.configure_args += glibc.cfg
1573 def configure(self, cmdlist):
1574 """Invoked to add the configure command to the command list."""
1575 cmdlist.add_command('configure',
1576 [os.path.join(self.srcdir, 'configure'),
1577 *self.configure_args])
1579 def extra_commands(self, cmdlist):
1580 """Invoked to inject additional commands (make check) after build."""
1581 pass
1583 class GlibcPolicyForCompiler(GlibcPolicyDefault):
1584 """Build policy for glibc during the compilers stage."""
1586 def __init__(self, glibc):
1587 super().__init__(glibc)
1588 self.builddir = glibc.ctx.component_builddir(
1589 'compilers', glibc.compiler.name, 'glibc', glibc.name)
1590 self.installdir = glibc.compiler.sysroot
1592 class GlibcPolicyForBuild(GlibcPolicyDefault):
1593 """Build policy for glibc during the glibcs stage."""
1595 def __init__(self, glibc):
1596 super().__init__(glibc)
1597 self.builddir = glibc.ctx.component_builddir(
1598 'glibcs', glibc.name, 'glibc')
1599 self.installdir = glibc.ctx.glibc_installdir(glibc.name)
1600 if glibc.ctx.strip:
1601 self.strip = glibc.tool_name('strip')
1602 else:
1603 self.strip = None
1604 self.save_logs = glibc.ctx.save_logs
1606 def extra_commands(self, cmdlist):
1607 if self.strip:
1608 # Avoid stripping libc.so and libpthread.so, which are
1609 # linker scripts stored in /lib on Hurd.
1610 find_command = 'find %s/lib* -name "*.so*"' % self.installdir
1611 cmdlist.add_command('strip', ['sh', '-c', (
1612 'set -e; for f in $(%s); do '
1613 'if ! head -c16 $f | grep -q "GNU ld script"; then %s $f; fi; '
1614 'done' % (find_command, self.strip))])
1615 cmdlist.add_command('check', ['make', 'check'])
1616 cmdlist.add_command('save-logs', [self.save_logs], always_run=True)
1618 class GlibcPolicyForUpdateSyscalls(GlibcPolicyDefault):
1619 """Build policy for glibc during update-syscalls."""
1621 def __init__(self, glibc):
1622 super().__init__(glibc)
1623 self.builddir = glibc.ctx.component_builddir(
1624 'update-syscalls', glibc.name, 'glibc')
1625 self.linuxdir = glibc.ctx.component_builddir(
1626 'update-syscalls', glibc.name, 'linux')
1627 self.linux_policy = LinuxHeadersPolicyForUpdateSyscalls(
1628 glibc, self.linuxdir)
1629 self.configure_args.insert(
1630 0, '--with-headers=%s' % os.path.join(self.linuxdir, 'include'))
1631 # self.installdir not set because installation is not supported
1633 class Glibc(object):
1634 """A configuration for building glibc."""
1636 def __init__(self, compiler, arch=None, os_name=None, variant=None,
1637 cfg=None, ccopts=None, cflags=None):
1638 """Initialize a Glibc object."""
1639 self.ctx = compiler.ctx
1640 self.compiler = compiler
1641 if arch is None:
1642 self.arch = compiler.arch
1643 else:
1644 self.arch = arch
1645 if os_name is None:
1646 self.os = compiler.os
1647 else:
1648 self.os = os_name
1649 self.variant = variant
1650 if variant is None:
1651 self.name = '%s-%s' % (self.arch, self.os)
1652 else:
1653 self.name = '%s-%s-%s' % (self.arch, self.os, variant)
1654 self.triplet = '%s-glibc-%s' % (self.arch, self.os)
1655 if cfg is None:
1656 self.cfg = []
1657 else:
1658 self.cfg = cfg
1659 # ccopts contain ABI options and are passed to configure as CC / CXX.
1660 self.ccopts = ccopts
1661 # cflags contain non-ABI options like -g or -O and are passed to
1662 # configure as CFLAGS / CXXFLAGS.
1663 self.cflags = cflags
1665 def tool_name(self, tool):
1666 """Return the name of a cross-compilation tool."""
1667 ctool = '%s-%s' % (self.compiler.triplet, tool)
1668 if self.ccopts and (tool == 'gcc' or tool == 'g++'):
1669 ctool = '%s %s' % (ctool, self.ccopts)
1670 return ctool
1672 def build(self):
1673 """Generate commands to build this glibc."""
1674 builddir = self.ctx.component_builddir('glibcs', self.name, 'glibc')
1675 installdir = self.ctx.glibc_installdir(self.name)
1676 logsdir = os.path.join(self.ctx.logsdir, 'glibcs', self.name)
1677 self.ctx.remove_recreate_dirs(installdir, builddir, logsdir)
1678 cmdlist = CommandList('glibcs-%s' % self.name, self.ctx.keep)
1679 cmdlist.add_command('check-compilers',
1680 ['test', '-f',
1681 os.path.join(self.compiler.installdir, 'ok')])
1682 cmdlist.use_path(self.compiler.bindir)
1683 self.build_glibc(cmdlist, GlibcPolicyForBuild(self))
1684 self.ctx.add_makefile_cmdlist('glibcs-%s' % self.name, cmdlist,
1685 logsdir)
1687 def build_glibc(self, cmdlist, policy):
1688 """Generate commands to build this glibc, either as part of a compiler
1689 build or with the bootstrapped compiler (and in the latter case, run
1690 tests as well)."""
1691 cmdlist.create_use_dir(policy.builddir)
1692 policy.configure(cmdlist)
1693 cmdlist.add_command('build', ['make'])
1694 cmdlist.add_command('install', ['make', 'install',
1695 'install_root=%s' % policy.installdir])
1696 # GCC uses paths such as lib/../lib64, so make sure lib
1697 # directories always exist.
1698 mkdir_cmd = ['mkdir', '-p',
1699 os.path.join(policy.installdir, 'lib')]
1700 if policy.use_usr:
1701 mkdir_cmd += [os.path.join(policy.installdir, 'usr', 'lib')]
1702 cmdlist.add_command('mkdir-lib', mkdir_cmd)
1703 policy.extra_commands(cmdlist)
1704 cmdlist.cleanup_dir()
1706 def update_syscalls(self):
1707 if self.os == 'gnu':
1708 # Hurd does not have system call tables that need updating.
1709 return
1711 policy = GlibcPolicyForUpdateSyscalls(self)
1712 logsdir = os.path.join(self.ctx.logsdir, 'update-syscalls', self.name)
1713 self.ctx.remove_recreate_dirs(policy.builddir, logsdir)
1714 cmdlist = CommandList('update-syscalls-%s' % self.name, self.ctx.keep)
1715 cmdlist.add_command('check-compilers',
1716 ['test', '-f',
1717 os.path.join(self.compiler.installdir, 'ok')])
1718 cmdlist.use_path(self.compiler.bindir)
1720 install_linux_headers(policy.linux_policy, cmdlist)
1722 cmdlist.create_use_dir(policy.builddir)
1723 policy.configure(cmdlist)
1724 cmdlist.add_command('build', ['make', 'update-syscall-lists'])
1725 cmdlist.cleanup_dir()
1726 self.ctx.add_makefile_cmdlist('update-syscalls-%s' % self.name,
1727 cmdlist, logsdir)
1729 class Command(object):
1730 """A command run in the build process."""
1732 def __init__(self, desc, num, dir, path, command, always_run=False):
1733 """Initialize a Command object."""
1734 self.dir = dir
1735 self.path = path
1736 self.desc = desc
1737 trans = str.maketrans({' ': '-'})
1738 self.logbase = '%03d-%s' % (num, desc.translate(trans))
1739 self.command = command
1740 self.always_run = always_run
1742 @staticmethod
1743 def shell_make_quote_string(s):
1744 """Given a string not containing a newline, quote it for use by the
1745 shell and make."""
1746 assert '\n' not in s
1747 if re.fullmatch('[]+,./0-9@A-Z_a-z-]+', s):
1748 return s
1749 strans = str.maketrans({"'": "'\\''"})
1750 s = "'%s'" % s.translate(strans)
1751 mtrans = str.maketrans({'$': '$$'})
1752 return s.translate(mtrans)
1754 @staticmethod
1755 def shell_make_quote_list(l, translate_make):
1756 """Given a list of strings not containing newlines, quote them for use
1757 by the shell and make, returning a single string. If translate_make
1758 is true and the first string is 'make', change it to $(MAKE)."""
1759 l = [Command.shell_make_quote_string(s) for s in l]
1760 if translate_make and l[0] == 'make':
1761 l[0] = '$(MAKE)'
1762 return ' '.join(l)
1764 def shell_make_quote(self):
1765 """Return this command quoted for the shell and make."""
1766 return self.shell_make_quote_list(self.command, True)
1769 class CommandList(object):
1770 """A list of commands run in the build process."""
1772 def __init__(self, desc, keep):
1773 """Initialize a CommandList object."""
1774 self.cmdlist = []
1775 self.dir = None
1776 self.path = None
1777 self.desc = [desc]
1778 self.keep = keep
1780 def desc_txt(self, desc):
1781 """Return the description to use for a command."""
1782 return '%s %s' % (' '.join(self.desc), desc)
1784 def use_dir(self, dir):
1785 """Set the default directory for subsequent commands."""
1786 self.dir = dir
1788 def use_path(self, path):
1789 """Set a directory to be prepended to the PATH for subsequent
1790 commands."""
1791 self.path = path
1793 def push_subdesc(self, subdesc):
1794 """Set the default subdescription for subsequent commands (e.g., the
1795 name of a component being built, within the series of commands
1796 building it)."""
1797 self.desc.append(subdesc)
1799 def pop_subdesc(self):
1800 """Pop a subdescription from the list of descriptions."""
1801 self.desc.pop()
1803 def create_use_dir(self, dir):
1804 """Remove and recreate a directory and use it for subsequent
1805 commands."""
1806 self.add_command_dir('rm', None, ['rm', '-rf', dir])
1807 self.add_command_dir('mkdir', None, ['mkdir', '-p', dir])
1808 self.use_dir(dir)
1810 def add_command_dir(self, desc, dir, command, always_run=False):
1811 """Add a command to run in a given directory."""
1812 cmd = Command(self.desc_txt(desc), len(self.cmdlist), dir, self.path,
1813 command, always_run)
1814 self.cmdlist.append(cmd)
1816 def add_command(self, desc, command, always_run=False):
1817 """Add a command to run in the default directory."""
1818 cmd = Command(self.desc_txt(desc), len(self.cmdlist), self.dir,
1819 self.path, command, always_run)
1820 self.cmdlist.append(cmd)
1822 def cleanup_dir(self, desc='cleanup', dir=None):
1823 """Clean up a build directory. If no directory is specified, the
1824 default directory is cleaned up and ceases to be the default
1825 directory."""
1826 if dir is None:
1827 dir = self.dir
1828 self.use_dir(None)
1829 if self.keep != 'all':
1830 self.add_command_dir(desc, None, ['rm', '-rf', dir],
1831 always_run=(self.keep == 'none'))
1833 def makefile_commands(self, wrapper, logsdir):
1834 """Return the sequence of commands in the form of text for a Makefile.
1835 The given wrapper script takes arguments: base of logs for
1836 previous command, or empty; base of logs for this command;
1837 description; directory; PATH addition; the command itself."""
1838 # prev_base is the base of the name for logs of the previous
1839 # command that is not always-run (that is, a build command,
1840 # whose failure should stop subsequent build commands from
1841 # being run, as opposed to a cleanup command, which is run
1842 # even if previous commands failed).
1843 prev_base = ''
1844 cmds = []
1845 for c in self.cmdlist:
1846 ctxt = c.shell_make_quote()
1847 if prev_base and not c.always_run:
1848 prev_log = os.path.join(logsdir, prev_base)
1849 else:
1850 prev_log = ''
1851 this_log = os.path.join(logsdir, c.logbase)
1852 if not c.always_run:
1853 prev_base = c.logbase
1854 if c.dir is None:
1855 dir = ''
1856 else:
1857 dir = c.dir
1858 if c.path is None:
1859 path = ''
1860 else:
1861 path = c.path
1862 prelims = [wrapper, prev_log, this_log, c.desc, dir, path]
1863 prelim_txt = Command.shell_make_quote_list(prelims, False)
1864 cmds.append('\t@%s %s' % (prelim_txt, ctxt))
1865 return '\n'.join(cmds)
1867 def status_logs(self, logsdir):
1868 """Return the list of log files with command status."""
1869 return [os.path.join(logsdir, '%s-status.txt' % c.logbase)
1870 for c in self.cmdlist]
1873 def get_parser():
1874 """Return an argument parser for this module."""
1875 parser = argparse.ArgumentParser(description=__doc__)
1876 parser.add_argument('-j', dest='parallelism',
1877 help='Run this number of jobs in parallel',
1878 type=int, default=os.cpu_count())
1879 parser.add_argument('--keep', dest='keep',
1880 help='Whether to keep all build directories, '
1881 'none or only those from failed builds',
1882 default='none', choices=('none', 'all', 'failed'))
1883 parser.add_argument('--replace-sources', action='store_true',
1884 help='Remove and replace source directories '
1885 'with the wrong version of a component')
1886 parser.add_argument('--strip', action='store_true',
1887 help='Strip installed glibc libraries')
1888 parser.add_argument('--full-gcc', action='store_true',
1889 help='Build GCC with all languages and libsanitizer')
1890 parser.add_argument('--shallow', action='store_true',
1891 help='Do not download Git history during checkout')
1892 parser.add_argument('topdir',
1893 help='Toplevel working directory')
1894 parser.add_argument('action',
1895 help='What to do',
1896 choices=('checkout', 'bot-cycle', 'bot',
1897 'host-libraries', 'compilers', 'glibcs',
1898 'update-syscalls', 'list-compilers',
1899 'list-glibcs'))
1900 parser.add_argument('configs',
1901 help='Versions to check out or configurations to build',
1902 nargs='*')
1903 return parser
1906 def get_version_common(progname,line,word,arg1):
1907 try:
1908 out = subprocess.run([progname, arg1],
1909 stdout=subprocess.PIPE,
1910 stderr=subprocess.DEVNULL,
1911 stdin=subprocess.DEVNULL,
1912 check=True, universal_newlines=True)
1913 v = out.stdout.splitlines()[line].split()[word]
1914 v = re.match(r'[0-9]+(.[0-9]+)*', v).group()
1915 return [int(x) for x in v.split('.')]
1916 except:
1917 return 'missing';
1919 def get_version_common_stderr(progname,line,word,arg1):
1920 try:
1921 out = subprocess.run([progname, arg1],
1922 stdout=subprocess.DEVNULL,
1923 stderr=subprocess.PIPE,
1924 stdin=subprocess.DEVNULL,
1925 check=True, universal_newlines=True)
1926 v = out.stderr.splitlines()[line].split()[word]
1927 v = re.match(r'[0-9]+(.[0-9]+)*', v).group()
1928 return [int(x) for x in v.split('.')]
1929 except:
1930 return 'missing';
1932 def get_version(progname):
1933 return get_version_common(progname, 0, -1, '--version');
1935 def get_version_awk(progname):
1936 return get_version_common(progname, 0, 2, '--version');
1938 def get_version_bzip2(progname):
1939 return get_version_common_stderr(progname, 0, 6, '-h');
1941 def check_version(ver, req):
1942 for v, r in zip(ver, req):
1943 if v > r:
1944 return True
1945 if v < r:
1946 return False
1947 return True
1949 def version_str(ver):
1950 return '.'.join([str (x) for x in ver])
1952 def check_for_required_tools():
1953 get_list_of_required_tools()
1954 count_old_tools = 0
1955 count_missing_tools = 0
1957 for k, v in REQUIRED_TOOLS.items():
1958 version = v[0](k)
1959 if version == 'missing':
1960 ok = 'missing'
1961 else:
1962 ok = 'ok' if check_version (version, v[1]) else 'old'
1963 if ok == 'old':
1964 if count_old_tools == 0:
1965 print("One or more required tools are too old:")
1966 count_old_tools = count_old_tools + 1
1967 print('{:9}: {:3} (obtained=\"{}\" required=\"{}\")'.format(k, ok,
1968 version_str(version), version_str(v[1])))
1969 if ok == 'missing':
1970 if count_missing_tools == 0:
1971 print("One or more required tools are missing:")
1972 count_missing_tools = count_missing_tools + 1
1973 print('{:9}: {:3} (required=\"{}\")'.format(k, ok,
1974 version_str(v[1])))
1976 if count_old_tools > 0 or count_missing_tools > 0:
1977 exit (1);
1979 def main(argv):
1980 """The main entry point."""
1981 check_for_required_tools();
1982 parser = get_parser()
1983 opts = parser.parse_args(argv)
1984 topdir = os.path.abspath(opts.topdir)
1985 ctx = Context(topdir, opts.parallelism, opts.keep, opts.replace_sources,
1986 opts.strip, opts.full_gcc, opts.action,
1987 shallow=opts.shallow)
1988 ctx.run_builds(opts.action, opts.configs)
1991 if __name__ == '__main__':
1992 main(sys.argv[1:])