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