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