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