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