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