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