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