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