1# SPDX-License-Identifier: GPL-2.0+ 2# Copyright (c) 2013 The Chromium OS Authors. 3# 4 5"""Control module for buildman 6 7This holds the main control logic for buildman, when not running tests. 8""" 9 10import getpass 11import multiprocessing 12import os 13import shutil 14import sys 15import tempfile 16import time 17 18from buildman import boards 19from buildman import bsettings 20from buildman import cfgutil 21from buildman import toolchain 22from buildman.builder import Builder 23from patman import patchstream 24from u_boot_pylib import command 25from u_boot_pylib import gitutil 26from u_boot_pylib import terminal 27from u_boot_pylib import tools 28from u_boot_pylib.terminal import print_clear, tprint 29 30TEST_BUILDER = None 31 32# Space-separated list of buildman process IDs currently running jobs 33RUNNING_FNAME = f'buildmanq.{getpass.getuser()}' 34 35# Lock file for access to RUNNING_FILE 36LOCK_FNAME = f'{RUNNING_FNAME}.lock' 37 38# Wait time for access to lock (seconds) 39LOCK_WAIT_S = 10 40 41# Wait time to start running 42RUN_WAIT_S = 300 43 44def get_plural(count): 45 """Returns a plural 's' if count is not 1""" 46 return 's' if count != 1 else '' 47 48 49def count_build_commits(commits, step): 50 """Calculate the number of commits to be built 51 52 Args: 53 commits (list of Commit): Commits to build or None 54 step (int): Step value for commits, typically 1 55 56 Returns: 57 Number of commits that will be built 58 """ 59 if commits: 60 count = len(commits) 61 return (count + step - 1) // step 62 return 0 63 64 65def get_action_summary(is_summary, commit_count, selected, threads, jobs): 66 """Return a string summarising the intended action. 67 68 Args: 69 is_summary (bool): True if this is a summary (otherwise it is building) 70 commits (list): List of commits being built 71 selected (list of Board): List of Board objects that are marked 72 step (int): Step increment through commits 73 threads (int): Number of processor threads being used 74 jobs (int): Number of jobs to build at once 75 76 Returns: 77 Summary string. 78 """ 79 if commit_count: 80 commit_str = f'{commit_count} commit{get_plural(commit_count)}' 81 else: 82 commit_str = 'current source' 83 msg = (f"{'Summary of' if is_summary else 'Building'} " 84 f'{commit_str} for {len(selected)} boards') 85 msg += (f' ({threads} thread{get_plural(threads)}, ' 86 f'{jobs} job{get_plural(jobs)} per thread)') 87 return msg 88 89# pylint: disable=R0913 90def show_actions(series, why_selected, boards_selected, output_dir, 91 board_warnings, step, threads, jobs, verbose): 92 """Display a list of actions that we would take, if not a dry run. 93 94 Args: 95 series: Series object 96 why_selected: Dictionary where each key is a buildman argument 97 provided by the user, and the value is the list of boards 98 brought in by that argument. For example, 'arm' might bring 99 in 400 boards, so in this case the key would be 'arm' and 100 the value would be a list of board names. 101 boards_selected: Dict of selected boards, key is target name, 102 value is Board object 103 output_dir (str): Output directory for builder 104 board_warnings: List of warnings obtained from board selected 105 step (int): Step increment through commits 106 threads (int): Number of processor threads being used 107 jobs (int): Number of jobs to build at once 108 verbose (bool): True to indicate why each board was selected 109 """ 110 col = terminal.Color() 111 print('Dry run, so not doing much. But I would do this:') 112 print() 113 if series: 114 commits = series.commits 115 else: 116 commits = None 117 print(get_action_summary(False, count_build_commits(commits, step), 118 boards_selected, threads, jobs)) 119 print(f'Build directory: {output_dir}') 120 if commits: 121 for upto in range(0, len(series.commits), step): 122 commit = series.commits[upto] 123 print(' ', col.build(col.YELLOW, commit.hash[:8], bright=False), end=' ') 124 print(commit.subject) 125 print() 126 for arg in why_selected: 127 # When -x is used, only the 'all' member exists 128 if arg != 'all' or len(why_selected) == 1: 129 print(arg, f': {len(why_selected[arg])} boards') 130 if verbose: 131 print(f" {' '.join(why_selected[arg])}") 132 print('Total boards to build for each ' 133 f"commit: {len(why_selected['all'])}\n") 134 if board_warnings: 135 for warning in board_warnings: 136 print(col.build(col.YELLOW, warning)) 137 138def show_toolchain_prefix(brds, toolchains): 139 """Show information about a the tool chain used by one or more boards 140 141 The function checks that all boards use the same toolchain, then prints 142 the correct value for CROSS_COMPILE. 143 144 Args: 145 boards: Boards object containing selected boards 146 toolchains: Toolchains object containing available toolchains 147 148 Return: 149 None on success, string error message otherwise 150 """ 151 board_selected = brds.get_selected_dict() 152 tc_set = set() 153 for brd in board_selected.values(): 154 tc_set.add(toolchains.Select(brd.arch)) 155 if len(tc_set) != 1: 156 sys.exit('Supplied boards must share one toolchain') 157 tchain = tc_set.pop() 158 print(tchain.GetEnvArgs(toolchain.VAR_CROSS_COMPILE)) 159 160def show_arch(brds): 161 """Show information about a the architecture used by one or more boards 162 163 The function checks that all boards use the same architecture, then prints 164 the correct value for ARCH. 165 166 Args: 167 boards: Boards object containing selected boards 168 169 Return: 170 None on success, string error message otherwise 171 """ 172 board_selected = brds.get_selected_dict() 173 arch_set = set() 174 for brd in board_selected.values(): 175 arch_set.add(brd.arch) 176 if len(arch_set) != 1: 177 sys.exit('Supplied boards must share one arch') 178 print(arch_set.pop()) 179 180def get_allow_missing(opt_allow, opt_no_allow, num_selected, has_branch): 181 """Figure out whether to allow external blobs 182 183 Uses the allow-missing setting and the provided arguments to decide whether 184 missing external blobs should be allowed 185 186 Args: 187 opt_allow (bool): True if --allow-missing flag is set 188 opt_no_allow (bool): True if --no-allow-missing flag is set 189 num_selected (int): Number of selected board 190 has_branch (bool): True if a git branch (to build) has been provided 191 192 Returns: 193 bool: True to allow missing external blobs, False to produce an error if 194 external blobs are used 195 """ 196 allow_missing = False 197 am_setting = bsettings.get_global_item_value('allow-missing') 198 if am_setting: 199 if am_setting == 'always': 200 allow_missing = True 201 if 'multiple' in am_setting and num_selected > 1: 202 allow_missing = True 203 if 'branch' in am_setting and has_branch: 204 allow_missing = True 205 206 if opt_allow: 207 allow_missing = True 208 if opt_no_allow: 209 allow_missing = False 210 return allow_missing 211 212 213def count_commits(branch, count, col, git_dir): 214 """Could the number of commits in the branch/ranch being built 215 216 Args: 217 branch (str): Name of branch to build, or None if none 218 count (int): Number of commits to build, or -1 for all 219 col (Terminal.Color): Color object to use 220 git_dir (str): Git directory to use, e.g. './.git' 221 222 Returns: 223 tuple: 224 Number of commits being built 225 True if the 'branch' string contains a range rather than a simple 226 name 227 """ 228 has_range = branch and '..' in branch 229 if count == -1: 230 if not branch: 231 count = 1 232 else: 233 if has_range: 234 count, msg = gitutil.count_commits_in_range(git_dir, branch) 235 else: 236 count, msg = gitutil.count_commits_in_branch(git_dir, branch) 237 if count is None: 238 sys.exit(col.build(col.RED, msg)) 239 elif count == 0: 240 sys.exit(col.build(col.RED, 241 f"Range '{branch}' has no commits")) 242 if msg: 243 print(col.build(col.YELLOW, msg)) 244 count += 1 # Build upstream commit also 245 246 if not count: 247 msg = (f"No commits found to process in branch '{branch}': " 248 "set branch's upstream or use -c flag") 249 sys.exit(col.build(col.RED, msg)) 250 return count, has_range 251 252 253def determine_series(selected, col, git_dir, count, branch, work_in_output): 254 """Determine the series which is to be built, if any 255 256 If there is a series, the commits in that series are numbered by setting 257 their sequence value (starting from 0). This is used by tests. 258 259 Args: 260 selected (list of Board): List of Board objects that are marked 261 selected 262 col (Terminal.Color): Color object to use 263 git_dir (str): Git directory to use, e.g. './.git' 264 count (int): Number of commits in branch 265 branch (str): Name of branch to build, or None if none 266 work_in_output (bool): True to work in the output directory 267 268 Returns: 269 Series: Series to build, or None for none 270 271 Read the metadata from the commits. First look at the upstream commit, 272 then the ones in the branch. We would like to do something like 273 upstream/master~..branch but that isn't possible if upstream/master is 274 a merge commit (it will list all the commits that form part of the 275 merge) 276 277 Conflicting tags are not a problem for buildman, since it does not use 278 them. For example, Series-version is not useful for buildman. On the 279 other hand conflicting tags will cause an error. So allow later tags 280 to overwrite earlier ones by setting allow_overwrite=True 281 """ 282 283 # Work out how many commits to build. We want to build everything on the 284 # branch. We also build the upstream commit as a control so we can see 285 # problems introduced by the first commit on the branch. 286 count, has_range = count_commits(branch, count, col, git_dir) 287 if work_in_output: 288 if len(selected) != 1: 289 sys.exit(col.build(col.RED, 290 '-w can only be used with a single board')) 291 if count != 1: 292 sys.exit(col.build(col.RED, 293 '-w can only be used with a single commit')) 294 295 if branch: 296 if count == -1: 297 if has_range: 298 range_expr = branch 299 else: 300 range_expr = gitutil.get_range_in_branch(git_dir, branch) 301 upstream_commit = gitutil.get_upstream(git_dir, branch) 302 series = patchstream.get_metadata_for_list(upstream_commit, 303 git_dir, 1, series=None, allow_overwrite=True) 304 305 series = patchstream.get_metadata_for_list(range_expr, 306 git_dir, None, series, allow_overwrite=True) 307 else: 308 # Honour the count 309 series = patchstream.get_metadata_for_list(branch, 310 git_dir, count, series=None, allow_overwrite=True) 311 312 # Number the commits for test purposes 313 for i, commit in enumerate(series.commits): 314 commit.sequence = i 315 else: 316 series = None 317 return series 318 319 320def do_fetch_arch(toolchains, col, fetch_arch): 321 """Handle the --fetch-arch option 322 323 Args: 324 toolchains (Toolchains): Tool chains to use 325 col (terminal.Color): Color object to build 326 fetch_arch (str): Argument passed to the --fetch-arch option 327 328 Returns: 329 int: Return code for buildman 330 """ 331 if fetch_arch == 'list': 332 sorted_list = toolchains.ListArchs() 333 print(col.build( 334 col.BLUE, 335 f"Available architectures: {' '.join(sorted_list)}\n")) 336 return 0 337 338 if fetch_arch == 'all': 339 fetch_arch = ','.join(toolchains.ListArchs()) 340 print(col.build(col.CYAN, 341 f'\nDownloading toolchains: {fetch_arch}')) 342 for arch in fetch_arch.split(','): 343 print() 344 ret = toolchains.FetchAndInstall(arch) 345 if ret: 346 return ret 347 return 0 348 349 350def get_toolchains(toolchains, col, override_toolchain, fetch_arch, 351 list_tool_chains, verbose): 352 """Get toolchains object to use 353 354 Args: 355 toolchains (Toolchains or None): Toolchains to use. If None, then a 356 Toolchains object will be created and scanned 357 col (Terminal.Color): Color object 358 override_toolchain (str or None): Override value for toolchain, or None 359 fetch_arch (bool): True to fetch the toolchain for the architectures 360 list_tool_chains (bool): True to list all tool chains 361 verbose (bool): True for verbose output when listing toolchains 362 363 Returns: 364 Either: 365 int: Operation completed and buildman should exit with exit code 366 Toolchains: Toolchains object to use 367 """ 368 no_toolchains = toolchains is None 369 if no_toolchains: 370 toolchains = toolchain.Toolchains(override_toolchain) 371 372 if fetch_arch: 373 return do_fetch_arch(toolchains, col, fetch_arch) 374 375 if no_toolchains: 376 toolchains.GetSettings() 377 toolchains.Scan(list_tool_chains and verbose) 378 if list_tool_chains: 379 toolchains.List() 380 print() 381 return 0 382 return toolchains 383 384 385def get_boards_obj(output_dir, regen_board_list, maintainer_check, full_check, 386 threads, verbose): 387 """Object the Boards object to use 388 389 Creates the output directory and ensures there is a boards.cfg file, then 390 read it in. 391 392 Args: 393 output_dir (str): Output directory to use, or None to use current dir 394 regen_board_list (bool): True to just regenerate the board list 395 maintainer_check (bool): True to just run a maintainer check 396 full_check (bool): True to just run a full check of Kconfig and 397 maintainers 398 threads (int or None): Number of threads to use to create boards file 399 verbose (bool): False to suppress output from boards-file generation 400 401 Returns: 402 Either: 403 int: Operation completed and buildman should exit with exit code 404 Boards: Boards object to use 405 """ 406 brds = boards.Boards() 407 nr_cpus = threads or multiprocessing.cpu_count() 408 if maintainer_check or full_check: 409 warnings = brds.build_board_list(jobs=nr_cpus, 410 warn_targets=full_check)[1] 411 if warnings: 412 for warn in warnings: 413 print(warn, file=sys.stderr) 414 return 2 415 return 0 416 417 if output_dir and not os.path.exists(output_dir): 418 os.makedirs(output_dir) 419 board_file = os.path.join(output_dir or '', 'boards.cfg') 420 if regen_board_list and regen_board_list != '-': 421 board_file = regen_board_list 422 423 okay = brds.ensure_board_list(board_file, nr_cpus, force=regen_board_list, 424 quiet=not verbose) 425 if regen_board_list: 426 return 0 if okay else 2 427 brds.read_boards(board_file) 428 return brds 429 430 431def determine_boards(brds, args, col, opt_boards, exclude_list): 432 """Determine which boards to build 433 434 Each element of args and exclude can refer to a board name, arch or SoC 435 436 Args: 437 brds (Boards): Boards object 438 args (list of str): Arguments describing boards to build 439 col (Terminal.Color): Color object 440 opt_boards (list of str): Specific boards to build, or None for all 441 exclude_list (list of str): Arguments describing boards to exclude 442 443 Returns: 444 tuple: 445 list of Board: List of Board objects that are marked selected 446 why_selected: Dictionary where each key is a buildman argument 447 provided by the user, and the value is the list of boards 448 brought in by that argument. For example, 'arm' might bring 449 in 400 boards, so in this case the key would be 'arm' and 450 the value would be a list of board names. 451 board_warnings: List of warnings obtained from board selected 452 """ 453 exclude = [] 454 if exclude_list: 455 for arg in exclude_list: 456 exclude += arg.split(',') 457 458 if opt_boards: 459 requested_boards = [] 460 for brd in opt_boards: 461 requested_boards += brd.split(',') 462 else: 463 requested_boards = None 464 why_selected, board_warnings = brds.select_boards(args, exclude, 465 requested_boards) 466 selected = brds.get_selected() 467 if not selected: 468 sys.exit(col.build(col.RED, 'No matching boards found')) 469 return selected, why_selected, board_warnings 470 471 472def adjust_args(args, series, selected): 473 """Adjust arguments according to various constraints 474 475 Updates verbose, show_errors, threads, jobs and step 476 477 Args: 478 args (Namespace): Namespace object to adjust 479 series (Series): Series being built / summarised 480 selected (list of Board): List of Board objects that are marked 481 """ 482 if not series and not args.dry_run: 483 args.verbose = True 484 if not args.summary: 485 args.show_errors = True 486 487 # By default we have one thread per CPU. But if there are not enough jobs 488 # we can have fewer threads and use a high '-j' value for make. 489 if args.threads is None: 490 args.threads = min(multiprocessing.cpu_count(), len(selected)) 491 if not args.jobs: 492 args.jobs = max(1, (multiprocessing.cpu_count() + 493 len(selected) - 1) // len(selected)) 494 495 if not args.step: 496 args.step = len(series.commits) - 1 497 498 # We can't show function sizes without board details at present 499 if args.show_bloat: 500 args.show_detail = True 501 502 503def setup_output_dir(output_dir, work_in_output, branch, no_subdirs, col, 504 in_tree, clean_dir): 505 """Set up the output directory 506 507 Args: 508 output_dir (str): Output directory provided by the user, or None if none 509 work_in_output (bool): True to work in the output directory 510 branch (str): Name of branch to build, or None if none 511 no_subdirs (bool): True to put the output in the top-level output dir 512 in_tree (bool): True if doing an in-tree build 513 clean_dir: Used for tests only, indicates that the existing output_dir 514 should be removed before starting the build 515 516 Returns: 517 str: Updated output directory pathname 518 """ 519 if not output_dir: 520 output_dir = '..' 521 if work_in_output: 522 if not in_tree: 523 sys.exit(col.build(col.RED, '-w requires that you specify -o')) 524 output_dir = None 525 if branch and not no_subdirs: 526 # As a special case allow the board directory to be placed in the 527 # output directory itself rather than any subdirectory. 528 dirname = branch.replace('/', '_') 529 output_dir = os.path.join(output_dir, dirname) 530 if clean_dir and os.path.exists(output_dir): 531 shutil.rmtree(output_dir) 532 return output_dir 533 534 535def run_builder(builder, commits, board_selected, args): 536 """Run the builder or show the summary 537 538 Args: 539 commits (list of Commit): List of commits being built, None if no branch 540 boards_selected (dict): Dict of selected boards: 541 key: target name 542 value: Board object 543 args (Namespace): Namespace to use 544 545 Returns: 546 int: Return code for buildman 547 """ 548 gnu_make = command.output(os.path.join(args.git, 549 'scripts/show-gnu-make'), raise_on_error=False).rstrip() 550 if not gnu_make: 551 sys.exit('GNU Make not found') 552 builder.gnu_make = gnu_make 553 554 if not args.ide: 555 commit_count = count_build_commits(commits, args.step) 556 tprint(get_action_summary(args.summary, commit_count, board_selected, 557 args.threads, args.jobs)) 558 559 builder.set_display_options( 560 args.show_errors, args.show_sizes, args.show_detail, args.show_bloat, 561 args.list_error_boards, args.show_config, args.show_environment, 562 args.filter_dtb_warnings, args.filter_migration_warnings, args.ide) 563 if args.summary: 564 builder.show_summary(commits, board_selected) 565 else: 566 fail, warned, excs = builder.build_boards( 567 commits, board_selected, args.keep_outputs, args.verbose) 568 if excs: 569 return 102 570 if fail: 571 return 100 572 if warned and not args.ignore_warnings: 573 return 101 574 return 0 575 576 577def calc_adjust_cfg(adjust_cfg, reproducible_builds): 578 """Calculate the value to use for adjust_cfg 579 580 Args: 581 adjust_cfg (list of str): List of configuration changes. See cfgutil for 582 details 583 reproducible_builds (bool): True to adjust the configuration to get 584 reproduceable builds 585 586 Returns: 587 adjust_cfg (list of str): List of configuration changes 588 """ 589 adjust_cfg = cfgutil.convert_list_to_dict(adjust_cfg) 590 591 # Drop LOCALVERSION_AUTO since it changes the version string on every commit 592 if reproducible_builds: 593 # If these are mentioned, leave the local version alone 594 if 'LOCALVERSION' in adjust_cfg or 'LOCALVERSION_AUTO' in adjust_cfg: 595 print('Not dropping LOCALVERSION_AUTO for reproducible build') 596 else: 597 adjust_cfg['LOCALVERSION_AUTO'] = '~' 598 return adjust_cfg 599 600 601def read_procs(tmpdir=tempfile.gettempdir()): 602 """Read the list of running buildman processes 603 604 If the list is corrupted, returns an empty list 605 606 Args: 607 tmpdir (str): Temporary directory to use (for testing only) 608 """ 609 running_fname = os.path.join(tmpdir, RUNNING_FNAME) 610 procs = [] 611 if os.path.exists(running_fname): 612 items = tools.read_file(running_fname, binary=False).split() 613 try: 614 procs = [int(x) for x in items] 615 except ValueError: # Handle invalid format 616 pass 617 return procs 618 619 620def check_pid(pid): 621 """Check for existence of a unix PID 622 623 https://stackoverflow.com/questions/568271/how-to-check-if-there-exists-a-process-with-a-given-pid-in-python 624 625 Args: 626 pid (int): PID to check 627 628 Returns: 629 True if it exists, else False 630 """ 631 try: 632 os.kill(pid, 0) 633 except OSError: 634 return False 635 else: 636 return True 637 638 639def write_procs(procs, tmpdir=tempfile.gettempdir()): 640 """Write the list of running buildman processes 641 642 Args: 643 tmpdir (str): Temporary directory to use (for testing only) 644 """ 645 running_fname = os.path.join(tmpdir, RUNNING_FNAME) 646 tools.write_file(running_fname, ' '.join([str(p) for p in procs]), 647 binary=False) 648 649 # Allow another user to access the file 650 os.chmod(running_fname, 0o666) 651 652def wait_for_process_limit(limit, tmpdir=tempfile.gettempdir(), 653 pid=os.getpid()): 654 """Wait until the number of buildman processes drops to the limit 655 656 This uses FileLock to protect a 'running' file, which contains a list of 657 PIDs of running buildman processes. The number of PIDs in the file indicates 658 the number of running processes. 659 660 When buildman starts up, it calls this function to wait until it is OK to 661 start the build. 662 663 On exit, no attempt is made to remove the PID from the file, since other 664 buildman processes will notice that the PID is no-longer valid, and ignore 665 it. 666 667 Two timeouts are provided: 668 LOCK_WAIT_S: length of time to wait for the lock; if this occurs, the 669 lock is busted / removed before trying again 670 RUN_WAIT_S: length of time to wait to be allowed to run; if this occurs, 671 the build starts, with the PID being added to the file. 672 673 Args: 674 limit (int): Maximum number of buildman processes, including this one; 675 must be > 0 676 tmpdir (str): Temporary directory to use (for testing only) 677 pid (int): Current process ID (for testing only) 678 """ 679 from filelock import Timeout, FileLock 680 681 running_fname = os.path.join(tmpdir, RUNNING_FNAME) 682 lock_fname = os.path.join(tmpdir, LOCK_FNAME) 683 lock = FileLock(lock_fname) 684 685 # Allow another user to access the file 686 col = terminal.Color() 687 tprint('Waiting for other buildman processes...', newline=False, 688 colour=col.RED) 689 690 claimed = False 691 deadline = time.time() + RUN_WAIT_S 692 while True: 693 try: 694 with lock.acquire(timeout=LOCK_WAIT_S): 695 os.chmod(lock_fname, 0o666) 696 procs = read_procs(tmpdir) 697 698 # Drop PIDs which are not running 699 procs = list(filter(check_pid, procs)) 700 701 # If we haven't hit the limit, add ourself 702 if len(procs) < limit: 703 tprint('done...', newline=False) 704 claimed = True 705 if time.time() >= deadline: 706 tprint('timeout...', newline=False) 707 claimed = True 708 if claimed: 709 write_procs(procs + [pid], tmpdir) 710 break 711 712 except Timeout: 713 tprint('failed to get lock: busting...', newline=False) 714 os.remove(lock_fname) 715 716 time.sleep(1) 717 tprint('starting build', newline=False) 718 print_clear() 719 720def do_buildman(args, toolchains=None, make_func=None, brds=None, 721 clean_dir=False, test_thread_exceptions=False): 722 """The main control code for buildman 723 724 Args: 725 args: ArgumentParser object 726 args: Command line arguments (list of strings) 727 toolchains: Toolchains to use - this should be a Toolchains() 728 object. If None, then it will be created and scanned 729 make_func: Make function to use for the builder. This is called 730 to execute 'make'. If this is None, the normal function 731 will be used, which calls the 'make' tool with suitable 732 arguments. This setting is useful for tests. 733 brds: Boards() object to use, containing a list of available 734 boards. If this is None it will be created and scanned. 735 clean_dir: Used for tests only, indicates that the existing output_dir 736 should be removed before starting the build 737 test_thread_exceptions: Uses for tests only, True to make the threads 738 raise an exception instead of reporting their result. This simulates 739 a failure in the code somewhere 740 """ 741 # Used so testing can obtain the builder: pylint: disable=W0603 742 global TEST_BUILDER 743 744 gitutil.setup() 745 col = terminal.Color() 746 747 git_dir = os.path.join(args.git, '.git') 748 749 toolchains = get_toolchains(toolchains, col, args.override_toolchain, 750 args.fetch_arch, args.list_tool_chains, 751 args.verbose) 752 if isinstance(toolchains, int): 753 return toolchains 754 755 output_dir = setup_output_dir( 756 args.output_dir, args.work_in_output, args.branch, 757 args.no_subdirs, col, args.in_tree, clean_dir) 758 759 # Work out what subset of the boards we are building 760 if not brds: 761 brds = get_boards_obj(output_dir, args.regen_board_list, 762 args.maintainer_check, args.full_check, 763 args.threads, args.verbose and 764 not args.print_arch and not args.print_prefix) 765 if isinstance(brds, int): 766 return brds 767 768 selected, why_selected, board_warnings = determine_boards( 769 brds, args.terms, col, args.boards, args.exclude) 770 771 if args.print_prefix: 772 show_toolchain_prefix(brds, toolchains) 773 return 0 774 775 if args.print_arch: 776 show_arch(brds) 777 return 0 778 779 series = determine_series(selected, col, git_dir, args.count, 780 args.branch, args.work_in_output) 781 782 adjust_args(args, series, selected) 783 784 # For a dry run, just show our actions as a sanity check 785 if args.dry_run: 786 show_actions(series, why_selected, selected, output_dir, board_warnings, 787 args.step, args.threads, args.jobs, 788 args.verbose) 789 return 0 790 791 if args.config_only and args.target: 792 raise ValueError('Cannot use --config-only with --target') 793 794 # Create a new builder with the selected args 795 builder = Builder(toolchains, output_dir, git_dir, 796 args.threads, args.jobs, checkout=True, 797 show_unknown=args.show_unknown, step=args.step, 798 no_subdirs=args.no_subdirs, full_path=args.full_path, 799 verbose_build=args.verbose_build, 800 mrproper=args.mrproper, 801 fallback_mrproper=args.fallback_mrproper, 802 per_board_out_dir=args.per_board_out_dir, 803 config_only=args.config_only, 804 squash_config_y=not args.preserve_config_y, 805 warnings_as_errors=args.warnings_as_errors, 806 work_in_output=args.work_in_output, 807 test_thread_exceptions=test_thread_exceptions, 808 adjust_cfg=calc_adjust_cfg(args.adjust_cfg, 809 args.reproducible_builds), 810 allow_missing=get_allow_missing(args.allow_missing, 811 args.no_allow_missing, 812 len(selected), args.branch), 813 no_lto=args.no_lto, 814 reproducible_builds=args.reproducible_builds, 815 force_build = args.force_build, 816 force_build_failures = args.force_build_failures, 817 force_reconfig = args.force_reconfig, in_tree = args.in_tree, 818 force_config_on_failure=not args.quick, make_func=make_func, 819 dtc_skip=args.dtc_skip, build_target=args.target) 820 821 TEST_BUILDER = builder 822 823 if args.process_limit: 824 wait_for_process_limit(args.process_limit) 825 826 return run_builder(builder, series.commits if series else None, 827 brds.get_selected_dict(), args) 828