"Das U-Boot" Source Tree
at master 788 lines 33 kB view raw
1# SPDX-License-Identifier: GPL-2.0+ 2# Copyright (c) 2014 Google, Inc 3# 4 5"""Implementation the bulider threads 6 7This module provides the BuilderThread class, which handles calling the builder 8based on the jobs provided. 9""" 10 11import errno 12import glob 13import io 14import os 15import shutil 16import sys 17import threading 18 19from buildman import cfgutil 20from patman import gitutil 21from u_boot_pylib import command 22from u_boot_pylib import tools 23 24RETURN_CODE_RETRY = -1 25BASE_ELF_FILENAMES = ['u-boot', 'spl/u-boot-spl', 'tpl/u-boot-tpl'] 26 27# Common extensions for images 28COMMON_EXTS = ['.bin', '.rom', '.itb', '.img'] 29 30def mkdir(dirname, parents=False): 31 """Make a directory if it doesn't already exist. 32 33 Args: 34 dirname (str): Directory to create 35 parents (bool): True to also make parent directories 36 37 Raises: 38 OSError: File already exists 39 """ 40 try: 41 if parents: 42 os.makedirs(dirname) 43 else: 44 os.mkdir(dirname) 45 except OSError as err: 46 if err.errno == errno.EEXIST: 47 if os.path.realpath('.') == os.path.realpath(dirname): 48 print(f"Cannot create the current working directory '{dirname}'!") 49 sys.exit(1) 50 else: 51 raise 52 53 54def _remove_old_outputs(out_dir): 55 """Remove any old output-target files 56 57 Args: 58 out_dir (str): Output directory for the build 59 60 Since we use a build directory that was previously used by another 61 board, it may have produced an SPL image. If we don't remove it (i.e. 62 see do_config and self.mrproper below) then it will appear to be the 63 output of this build, even if it does not produce SPL images. 64 """ 65 for elf in BASE_ELF_FILENAMES: 66 fname = os.path.join(out_dir, elf) 67 if os.path.exists(fname): 68 os.remove(fname) 69 70 71def copy_files(out_dir, build_dir, dirname, patterns): 72 """Copy files from the build directory to the output. 73 74 Args: 75 out_dir (str): Path to output directory containing the files 76 build_dir (str): Place to copy the files 77 dirname (str): Source directory, '' for normal U-Boot, 'spl' for SPL 78 patterns (list of str): A list of filenames to copy, each relative 79 to the build directory 80 """ 81 for pattern in patterns: 82 file_list = glob.glob(os.path.join(out_dir, dirname, pattern)) 83 for fname in file_list: 84 target = os.path.basename(fname) 85 if dirname: 86 base, ext = os.path.splitext(target) 87 if ext: 88 target = f'{base}-{dirname}{ext}' 89 shutil.copy(fname, os.path.join(build_dir, target)) 90 91 92# pylint: disable=R0903 93class BuilderJob: 94 """Holds information about a job to be performed by a thread 95 96 Members: 97 brd: Board object to build 98 commits: List of Commit objects to build 99 keep_outputs: True to save build output files 100 step: 1 to process every commit, n to process every nth commit 101 work_in_output: Use the output directory as the work directory and 102 don't write to a separate output directory. 103 """ 104 def __init__(self): 105 self.brd = None 106 self.commits = [] 107 self.keep_outputs = False 108 self.step = 1 109 self.work_in_output = False 110 111 112class ResultThread(threading.Thread): 113 """This thread processes results from builder threads. 114 115 It simply passes the results on to the builder. There is only one 116 result thread, and this helps to serialise the build output. 117 """ 118 def __init__(self, builder): 119 """Set up a new result thread 120 121 Args: 122 builder: Builder which will be sent each result 123 """ 124 threading.Thread.__init__(self) 125 self.builder = builder 126 127 def run(self): 128 """Called to start up the result thread. 129 130 We collect the next result job and pass it on to the build. 131 """ 132 while True: 133 result = self.builder.out_queue.get() 134 self.builder.process_result(result) 135 self.builder.out_queue.task_done() 136 137 138class BuilderThread(threading.Thread): 139 """This thread builds U-Boot for a particular board. 140 141 An input queue provides each new job. We run 'make' to build U-Boot 142 and then pass the results on to the output queue. 143 144 Members: 145 builder: The builder which contains information we might need 146 thread_num: Our thread number (0-n-1), used to decide on a 147 temporary directory. If this is -1 then there are no threads 148 and we are the (only) main process 149 mrproper: Use 'make mrproper' before each reconfigure 150 per_board_out_dir: True to build in a separate persistent directory per 151 board rather than a thread-specific directory 152 test_exception: Used for testing; True to raise an exception instead of 153 reporting the build result 154 """ 155 def __init__(self, builder, thread_num, mrproper, per_board_out_dir, 156 test_exception=False): 157 """Set up a new builder thread""" 158 threading.Thread.__init__(self) 159 self.builder = builder 160 self.thread_num = thread_num 161 self.mrproper = mrproper 162 self.per_board_out_dir = per_board_out_dir 163 self.test_exception = test_exception 164 self.toolchain = None 165 166 def make(self, commit, brd, stage, cwd, *args, **kwargs): 167 """Run 'make' on a particular commit and board. 168 169 The source code will already be checked out, so the 'commit' 170 argument is only for information. 171 172 Args: 173 commit (Commit): Commit that is being built 174 brd (Board): Board that is being built 175 stage (str): Stage of the build. Valid stages are: 176 mrproper - can be called to clean source 177 config - called to configure for a board 178 build - the main make invocation - it does the build 179 cwd (str): Working directory to set, or None to leave it alone 180 *args (list of str): Arguments to pass to 'make' 181 **kwargs (dict): A list of keyword arguments to pass to 182 command.run_pipe() 183 184 Returns: 185 CommandResult object 186 """ 187 return self.builder.do_make(commit, brd, stage, cwd, *args, 188 **kwargs) 189 190 def _build_args(self, brd, out_dir, out_rel_dir, work_dir, commit_upto): 191 """Set up arguments to the args list based on the settings 192 193 Args: 194 brd (Board): Board to create arguments for 195 out_dir (str): Path to output directory containing the files 196 out_rel_dir (str): Output directory relative to the current dir 197 work_dir (str): Directory to which the source will be checked out 198 commit_upto (int): Commit number to build (0...n-1) 199 200 Returns: 201 tuple: 202 list of str: Arguments to pass to make 203 str: Current working directory, or None if no commit 204 str: Source directory (typically the work directory) 205 """ 206 args = [] 207 cwd = work_dir 208 src_dir = os.path.realpath(work_dir) 209 if not self.builder.in_tree: 210 if commit_upto is None: 211 # In this case we are building in the original source directory 212 # (i.e. the current directory where buildman is invoked. The 213 # output directory is set to this thread's selected work 214 # directory. 215 # 216 # Symlinks can confuse U-Boot's Makefile since we may use '..' 217 # in our path, so remove them. 218 real_dir = os.path.realpath(out_dir) 219 args.append(f'O={real_dir}') 220 cwd = None 221 src_dir = os.getcwd() 222 else: 223 args.append(f'O={out_rel_dir}') 224 if self.builder.verbose_build: 225 args.append('V=1') 226 else: 227 args.append('-s') 228 if self.builder.num_jobs is not None: 229 args.extend(['-j', str(self.builder.num_jobs)]) 230 if self.builder.warnings_as_errors: 231 args.append('KCFLAGS=-Werror') 232 args.append('HOSTCFLAGS=-Werror') 233 if self.builder.allow_missing: 234 args.append('BINMAN_ALLOW_MISSING=1') 235 if self.builder.no_lto: 236 args.append('NO_LTO=1') 237 if self.builder.reproducible_builds: 238 args.append('SOURCE_DATE_EPOCH=0') 239 args.extend(self.builder.toolchains.GetMakeArguments(brd)) 240 args.extend(self.toolchain.MakeArgs()) 241 return args, cwd, src_dir 242 243 def _reconfigure(self, commit, brd, cwd, args, env, config_args, config_out, 244 cmd_list, mrproper): 245 """Reconfigure the build 246 247 Args: 248 commit (Commit): Commit only being built 249 brd (Board): Board being built 250 cwd (str): Current working directory 251 args (list of str): Arguments to pass to make 252 env (dict): Environment strings 253 config_args (list of str): defconfig arg for this board 254 cmd_list (list of str): List to add the commands to, for logging 255 mrproper (bool): True to run mrproper first 256 257 Returns: 258 CommandResult object 259 """ 260 if mrproper: 261 result = self.make(commit, brd, 'mrproper', cwd, 'mrproper', *args, 262 env=env) 263 config_out.write(result.combined) 264 cmd_list.append([self.builder.gnu_make, 'mrproper', *args]) 265 result = self.make(commit, brd, 'config', cwd, *(args + config_args), 266 env=env) 267 cmd_list.append([self.builder.gnu_make] + args + config_args) 268 config_out.write(result.combined) 269 return result 270 271 def _build(self, commit, brd, cwd, args, env, cmd_list, config_only): 272 """Perform the build 273 274 Args: 275 commit (Commit): Commit only being built 276 brd (Board): Board being built 277 cwd (str): Current working directory 278 args (list of str): Arguments to pass to make 279 env (dict): Environment strings 280 cmd_list (list of str): List to add the commands to, for logging 281 config_only (bool): True if this is a config-only build (using the 282 'make cfg' target) 283 284 Returns: 285 CommandResult object 286 """ 287 if config_only: 288 args.append('cfg') 289 result = self.make(commit, brd, 'build', cwd, *args, env=env) 290 cmd_list.append([self.builder.gnu_make] + args) 291 if (result.return_code == 2 and 292 ('Some images are invalid' in result.stderr)): 293 # This is handled later by the check for output in stderr 294 result.return_code = 0 295 return result 296 297 def _read_done_file(self, commit_upto, brd, force_build, 298 force_build_failures): 299 """Check the 'done' file and see if this commit should be built 300 301 Args: 302 commit (Commit): Commit only being built 303 brd (Board): Board being built 304 force_build (bool): Force a build even if one was previously done 305 force_build_failures (bool): Force a bulid if the previous result 306 showed failure 307 308 Returns: 309 tuple: 310 bool: True if build should be built 311 CommandResult: if there was a previous run: 312 - already_done set to True 313 - return_code set to return code 314 - result.stderr set to 'bad' if stderr output was recorded 315 """ 316 result = command.CommandResult() 317 done_file = self.builder.get_done_file(commit_upto, brd.target) 318 result.already_done = os.path.exists(done_file) 319 will_build = (force_build or force_build_failures or 320 not result.already_done) 321 if result.already_done: 322 with open(done_file, 'r', encoding='utf-8') as outf: 323 try: 324 result.return_code = int(outf.readline()) 325 except ValueError: 326 # The file may be empty due to running out of disk space. 327 # Try a rebuild 328 result.return_code = RETURN_CODE_RETRY 329 330 # Check the signal that the build needs to be retried 331 if result.return_code == RETURN_CODE_RETRY: 332 will_build = True 333 elif will_build: 334 err_file = self.builder.get_err_file(commit_upto, brd.target) 335 if os.path.exists(err_file) and os.stat(err_file).st_size: 336 result.stderr = 'bad' 337 elif not force_build: 338 # The build passed, so no need to build it again 339 will_build = False 340 return will_build, result 341 342 def _decide_dirs(self, brd, work_dir, work_in_output): 343 """Decide the output directory to use 344 345 Args: 346 work_dir (str): Directory to which the source will be checked out 347 work_in_output (bool): Use the output directory as the work 348 directory and don't write to a separate output directory. 349 350 Returns: 351 tuple: 352 out_dir (str): Output directory for the build 353 out_rel_dir (str): Output directory relatie to the current dir 354 """ 355 if work_in_output or self.builder.in_tree: 356 out_rel_dir = None 357 out_dir = work_dir 358 else: 359 if self.per_board_out_dir: 360 out_rel_dir = os.path.join('..', brd.target) 361 else: 362 out_rel_dir = 'build' 363 out_dir = os.path.join(work_dir, out_rel_dir) 364 return out_dir, out_rel_dir 365 366 def _checkout(self, commit_upto, work_dir): 367 """Checkout the right commit 368 369 Args: 370 commit_upto (int): Commit number to build (0...n-1) 371 work_dir (str): Directory to which the source will be checked out 372 373 Returns: 374 Commit: Commit being built, or 'current' for current source 375 """ 376 if self.builder.commits: 377 commit = self.builder.commits[commit_upto] 378 if self.builder.checkout: 379 git_dir = os.path.join(work_dir, '.git') 380 gitutil.checkout(commit.hash, git_dir, work_dir, force=True) 381 else: 382 commit = 'current' 383 return commit 384 385 def _config_and_build(self, commit_upto, brd, work_dir, do_config, mrproper, 386 config_only, adjust_cfg, commit, out_dir, out_rel_dir, 387 result): 388 """Do the build, configuring first if necessary 389 390 Args: 391 commit_upto (int): Commit number to build (0...n-1) 392 brd (Board): Board to create arguments for 393 work_dir (str): Directory to which the source will be checked out 394 do_config (bool): True to run a make <board>_defconfig on the source 395 mrproper (bool): True to run mrproper first 396 config_only (bool): Only configure the source, do not build it 397 adjust_cfg (list of str): See the cfgutil module and run_commit() 398 commit (Commit): Commit only being built 399 out_dir (str): Output directory for the build 400 out_rel_dir (str): Output directory relatie to the current dir 401 result (CommandResult): Previous result 402 403 Returns: 404 tuple: 405 result (CommandResult): Result of the build 406 do_config (bool): indicates whether 'make config' is needed on 407 the next incremental build 408 """ 409 # Set up the environment and command line 410 env = self.builder.make_environment(self.toolchain) 411 mkdir(out_dir) 412 413 args, cwd, src_dir = self._build_args(brd, out_dir, out_rel_dir, 414 work_dir, commit_upto) 415 config_args = [f'{brd.target}_defconfig'] 416 config_out = io.StringIO() 417 418 _remove_old_outputs(out_dir) 419 420 # If we need to reconfigure, do that now 421 cfg_file = os.path.join(out_dir, '.config') 422 cmd_list = [] 423 if do_config or adjust_cfg: 424 result = self._reconfigure( 425 commit, brd, cwd, args, env, config_args, config_out, cmd_list, 426 mrproper) 427 do_config = False # No need to configure next time 428 if adjust_cfg: 429 cfgutil.adjust_cfg_file(cfg_file, adjust_cfg) 430 431 # Now do the build, if everything looks OK 432 if result.return_code == 0: 433 if adjust_cfg: 434 oldc_args = list(args) + ['oldconfig'] 435 oldc_result = self.make(commit, brd, 'oldconfig', cwd, 436 *oldc_args, env=env) 437 if oldc_result.return_code: 438 return oldc_result 439 result = self._build(commit, brd, cwd, args, env, cmd_list, 440 config_only) 441 if adjust_cfg: 442 errs = cfgutil.check_cfg_file(cfg_file, adjust_cfg) 443 if errs: 444 result.stderr += errs 445 result.return_code = 1 446 result.stderr = result.stderr.replace(src_dir + '/', '') 447 if self.builder.verbose_build: 448 result.stdout = config_out.getvalue() + result.stdout 449 result.cmd_list = cmd_list 450 return result, do_config 451 452 def run_commit(self, commit_upto, brd, work_dir, do_config, mrproper, 453 config_only, force_build, force_build_failures, 454 work_in_output, adjust_cfg): 455 """Build a particular commit. 456 457 If the build is already done, and we are not forcing a build, we skip 458 the build and just return the previously-saved results. 459 460 Args: 461 commit_upto (int): Commit number to build (0...n-1) 462 brd (Board): Board to build 463 work_dir (str): Directory to which the source will be checked out 464 do_config (bool): True to run a make <board>_defconfig on the source 465 mrproper (bool): True to run mrproper first 466 config_only (bool): Only configure the source, do not build it 467 force_build (bool): Force a build even if one was previously done 468 force_build_failures (bool): Force a bulid if the previous result 469 showed failure 470 work_in_output (bool) : Use the output directory as the work 471 directory and don't write to a separate output directory. 472 adjust_cfg (list of str): List of changes to make to .config file 473 before building. Each is one of (where C is either CONFIG_xxx 474 or just xxx): 475 C to enable C 476 ~C to disable C 477 C=val to set the value of C (val must have quotes if C is 478 a string Kconfig 479 480 Returns: 481 tuple containing: 482 - CommandResult object containing the results of the build 483 - boolean indicating whether 'make config' is still needed 484 """ 485 # Create a default result - it will be overwritte by the call to 486 # self.make() below, in the event that we do a build. 487 out_dir, out_rel_dir = self._decide_dirs(brd, work_dir, work_in_output) 488 489 # Check if the job was already completed last time 490 will_build, result = self._read_done_file(commit_upto, brd, force_build, 491 force_build_failures) 492 493 if will_build: 494 # We are going to have to build it. First, get a toolchain 495 if not self.toolchain: 496 try: 497 self.toolchain = self.builder.toolchains.Select(brd.arch) 498 except ValueError as err: 499 result.return_code = 10 500 result.stdout = '' 501 result.stderr = f'Tool chain error for {brd.arch}: {str(err)}' 502 503 if self.toolchain: 504 commit = self._checkout(commit_upto, work_dir) 505 result, do_config = self._config_and_build( 506 commit_upto, brd, work_dir, do_config, mrproper, 507 config_only, adjust_cfg, commit, out_dir, out_rel_dir, 508 result) 509 result.already_done = False 510 511 result.toolchain = self.toolchain 512 result.brd = brd 513 result.commit_upto = commit_upto 514 result.out_dir = out_dir 515 return result, do_config 516 517 def _write_result(self, result, keep_outputs, work_in_output): 518 """Write a built result to the output directory. 519 520 Args: 521 result (CommandResult): result to write 522 keep_outputs (bool): True to store the output binaries, False 523 to delete them 524 work_in_output (bool): Use the output directory as the work 525 directory and don't write to a separate output directory. 526 """ 527 # If we think this might have been aborted with Ctrl-C, record the 528 # failure but not that we are 'done' with this board. A retry may fix 529 # it. 530 maybe_aborted = result.stderr and 'No child processes' in result.stderr 531 532 if result.return_code >= 0 and result.already_done: 533 return 534 535 # Write the output and stderr 536 output_dir = self.builder.get_output_dir(result.commit_upto) 537 mkdir(output_dir) 538 build_dir = self.builder.get_build_dir(result.commit_upto, 539 result.brd.target) 540 mkdir(build_dir) 541 542 outfile = os.path.join(build_dir, 'log') 543 with open(outfile, 'w', encoding='utf-8') as outf: 544 if result.stdout: 545 outf.write(result.stdout) 546 547 errfile = self.builder.get_err_file(result.commit_upto, 548 result.brd.target) 549 if result.stderr: 550 with open(errfile, 'w', encoding='utf-8') as outf: 551 outf.write(result.stderr) 552 elif os.path.exists(errfile): 553 os.remove(errfile) 554 555 # Fatal error 556 if result.return_code < 0: 557 return 558 559 done_file = self.builder.get_done_file(result.commit_upto, 560 result.brd.target) 561 if result.toolchain: 562 # Write the build result and toolchain information. 563 with open(done_file, 'w', encoding='utf-8') as outf: 564 if maybe_aborted: 565 # Special code to indicate we need to retry 566 outf.write(f'{RETURN_CODE_RETRY}') 567 else: 568 outf.write(f'{result.return_code}') 569 with open(os.path.join(build_dir, 'toolchain'), 'w', 570 encoding='utf-8') as outf: 571 print('gcc', result.toolchain.gcc, file=outf) 572 print('path', result.toolchain.path, file=outf) 573 print('cross', result.toolchain.cross, file=outf) 574 print('arch', result.toolchain.arch, file=outf) 575 outf.write(f'{result.return_code}') 576 577 # Write out the image and function size information and an objdump 578 env = self.builder.make_environment(self.toolchain) 579 with open(os.path.join(build_dir, 'out-env'), 'wb') as outf: 580 for var in sorted(env.keys()): 581 outf.write(b'%s="%s"' % (var, env[var])) 582 583 with open(os.path.join(build_dir, 'out-cmd'), 'w', 584 encoding='utf-8') as outf: 585 for cmd in result.cmd_list: 586 print(' '.join(cmd), file=outf) 587 588 lines = [] 589 for fname in BASE_ELF_FILENAMES: 590 cmd = [f'{self.toolchain.cross}nm', '--size-sort', fname] 591 nm_result = command.run_pipe([cmd], capture=True, 592 capture_stderr=True, cwd=result.out_dir, 593 raise_on_error=False, env=env) 594 if nm_result.stdout: 595 nm_fname = self.builder.get_func_sizes_file( 596 result.commit_upto, result.brd.target, fname) 597 with open(nm_fname, 'w', encoding='utf-8') as outf: 598 print(nm_result.stdout, end=' ', file=outf) 599 600 cmd = [f'{self.toolchain.cross}objdump', '-h', fname] 601 dump_result = command.run_pipe([cmd], capture=True, 602 capture_stderr=True, cwd=result.out_dir, 603 raise_on_error=False, env=env) 604 rodata_size = '' 605 if dump_result.stdout: 606 objdump = self.builder.get_objdump_file(result.commit_upto, 607 result.brd.target, fname) 608 with open(objdump, 'w', encoding='utf-8') as outf: 609 print(dump_result.stdout, end=' ', file=outf) 610 for line in dump_result.stdout.splitlines(): 611 fields = line.split() 612 if len(fields) > 5 and fields[1] == '.rodata': 613 rodata_size = fields[2] 614 615 cmd = [f'{self.toolchain.cross}size', fname] 616 size_result = command.run_pipe([cmd], capture=True, 617 capture_stderr=True, cwd=result.out_dir, 618 raise_on_error=False, env=env) 619 if size_result.stdout: 620 lines.append(size_result.stdout.splitlines()[1] + ' ' + 621 rodata_size) 622 623 # Extract the environment from U-Boot and dump it out 624 cmd = [f'{self.toolchain.cross}objcopy', '-O', 'binary', 625 '-j', '.rodata.default_environment', 626 'env/built-in.o', 'uboot.env'] 627 command.run_pipe([cmd], capture=True, 628 capture_stderr=True, cwd=result.out_dir, 629 raise_on_error=False, env=env) 630 if not work_in_output: 631 copy_files(result.out_dir, build_dir, '', ['uboot.env']) 632 633 # Write out the image sizes file. This is similar to the output 634 # of binutil's 'size' utility, but it omits the header line and 635 # adds an additional hex value at the end of each line for the 636 # rodata size 637 if lines: 638 sizes = self.builder.get_sizes_file(result.commit_upto, 639 result.brd.target) 640 with open(sizes, 'w', encoding='utf-8') as outf: 641 print('\n'.join(lines), file=outf) 642 else: 643 # Indicate that the build failure due to lack of toolchain 644 tools.write_file(done_file, '2\n', binary=False) 645 646 if not work_in_output: 647 # Write out the configuration files, with a special case for SPL 648 for dirname in ['', 'spl', 'tpl']: 649 copy_files( 650 result.out_dir, build_dir, dirname, 651 ['u-boot.cfg', 'spl/u-boot-spl.cfg', 'tpl/u-boot-tpl.cfg', 652 '.config', 'include/autoconf.mk', 653 'include/generated/autoconf.h']) 654 655 # Now write the actual build output 656 if keep_outputs: 657 to_copy = ['u-boot*', '*.map', 'MLO', 'SPL', 658 'include/autoconf.mk', 'spl/u-boot-spl*', 659 'tpl/u-boot-tpl*', 'vpl/u-boot-vpl*'] 660 to_copy += [f'*{ext}' for ext in COMMON_EXTS] 661 copy_files(result.out_dir, build_dir, '', to_copy) 662 663 def _send_result(self, result): 664 """Send a result to the builder for processing 665 666 Args: 667 result (CommandResult): results of the build 668 669 Raises: 670 ValueError: self.test_exception is true (for testing) 671 """ 672 if self.test_exception: 673 raise ValueError('test exception') 674 if self.thread_num != -1: 675 self.builder.out_queue.put(result) 676 else: 677 self.builder.process_result(result) 678 679 def run_job(self, job): 680 """Run a single job 681 682 A job consists of a building a list of commits for a particular board. 683 684 Args: 685 job (Job): Job to build 686 687 Raises: 688 ValueError: Thread was interrupted 689 """ 690 brd = job.brd 691 work_dir = self.builder.get_thread_dir(self.thread_num) 692 self.toolchain = None 693 if job.commits: 694 # Run 'make board_defconfig' on the first commit 695 do_config = True 696 commit_upto = 0 697 force_build = False 698 for commit_upto in range(0, len(job.commits), job.step): 699 result, request_config = self.run_commit(commit_upto, brd, 700 work_dir, do_config, self.mrproper, 701 self.builder.config_only, 702 force_build or self.builder.force_build, 703 self.builder.force_build_failures, 704 job.work_in_output, job.adjust_cfg) 705 failed = result.return_code or result.stderr 706 did_config = do_config 707 if failed and not do_config and not self.mrproper: 708 # If our incremental build failed, try building again 709 # with a reconfig. 710 if self.builder.force_config_on_failure: 711 result, request_config = self.run_commit(commit_upto, 712 brd, work_dir, True, 713 self.mrproper or self.builder.fallback_mrproper, 714 False, True, False, job.work_in_output, 715 job.adjust_cfg) 716 did_config = True 717 if not self.builder.force_reconfig: 718 do_config = request_config 719 720 # If we built that commit, then config is done. But if we got 721 # an warning, reconfig next time to force it to build the same 722 # files that created warnings this time. Otherwise an 723 # incremental build may not build the same file, and we will 724 # think that the warning has gone away. 725 # We could avoid this by using -Werror everywhere... 726 # For errors, the problem doesn't happen, since presumably 727 # the build stopped and didn't generate output, so will retry 728 # that file next time. So we could detect warnings and deal 729 # with them specially here. For now, we just reconfigure if 730 # anything goes work. 731 # Of course this is substantially slower if there are build 732 # errors/warnings (e.g. 2-3x slower even if only 10% of builds 733 # have problems). 734 if (failed and not result.already_done and not did_config and 735 self.builder.force_config_on_failure): 736 # If this build failed, try the next one with a 737 # reconfigure. 738 # Sometimes if the board_config.h file changes it can mess 739 # with dependencies, and we get: 740 # make: *** No rule to make target `include/autoconf.mk', 741 # needed by `depend'. 742 do_config = True 743 force_build = True 744 else: 745 force_build = False 746 if self.builder.force_config_on_failure: 747 if failed: 748 do_config = True 749 result.commit_upto = commit_upto 750 if result.return_code < 0: 751 raise ValueError('Interrupt') 752 753 # We have the build results, so output the result 754 self._write_result(result, job.keep_outputs, job.work_in_output) 755 self._send_result(result) 756 else: 757 # Just build the currently checked-out build 758 result, request_config = self.run_commit(None, brd, work_dir, True, 759 self.mrproper, self.builder.config_only, True, 760 self.builder.force_build_failures, job.work_in_output, 761 job.adjust_cfg) 762 failed = result.return_code or result.stderr 763 if failed and not self.mrproper: 764 result, request_config = self.run_commit(None, brd, work_dir, 765 True, self.builder.fallback_mrproper, 766 self.builder.config_only, True, 767 self.builder.force_build_failures, 768 job.work_in_output, job.adjust_cfg) 769 770 result.commit_upto = 0 771 self._write_result(result, job.keep_outputs, job.work_in_output) 772 self._send_result(result) 773 774 def run(self): 775 """Our thread's run function 776 777 This thread picks a job from the queue, runs it, and then goes to the 778 next job. 779 """ 780 while True: 781 job = self.builder.queue.get() 782 try: 783 self.run_job(job) 784 except Exception as exc: 785 print('Thread exception (use -T0 to run without threads):', 786 exc) 787 self.builder.thread_exceptions.append(exc) 788 self.builder.queue.task_done()