summaryrefslogtreecommitdiff
path: root/tools/buildman/control.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/buildman/control.py')
-rw-r--r--tools/buildman/control.py827
1 files changed, 827 insertions, 0 deletions
diff --git a/tools/buildman/control.py b/tools/buildman/control.py
new file mode 100644
index 00000000000..4dedd333551
--- /dev/null
+++ b/tools/buildman/control.py
@@ -0,0 +1,827 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright (c) 2013 The Chromium OS Authors.
+#
+
+"""Control module for buildman
+
+This holds the main control logic for buildman, when not running tests.
+"""
+
+import getpass
+import multiprocessing
+import os
+import shutil
+import sys
+import tempfile
+import time
+
+from buildman import boards
+from buildman import bsettings
+from buildman import cfgutil
+from buildman import toolchain
+from buildman.builder import Builder
+from patman import patchstream
+from u_boot_pylib import command
+from u_boot_pylib import gitutil
+from u_boot_pylib import terminal
+from u_boot_pylib import tools
+from u_boot_pylib.terminal import print_clear, tprint
+
+TEST_BUILDER = None
+
+# Space-separated list of buildman process IDs currently running jobs
+RUNNING_FNAME = f'buildmanq.{getpass.getuser()}'
+
+# Lock file for access to RUNNING_FILE
+LOCK_FNAME = f'{RUNNING_FNAME}.lock'
+
+# Wait time for access to lock (seconds)
+LOCK_WAIT_S = 10
+
+# Wait time to start running
+RUN_WAIT_S = 300
+
+def get_plural(count):
+ """Returns a plural 's' if count is not 1"""
+ return 's' if count != 1 else ''
+
+
+def count_build_commits(commits, step):
+ """Calculate the number of commits to be built
+
+ Args:
+ commits (list of Commit): Commits to build or None
+ step (int): Step value for commits, typically 1
+
+ Returns:
+ Number of commits that will be built
+ """
+ if commits:
+ count = len(commits)
+ return (count + step - 1) // step
+ return 0
+
+
+def get_action_summary(is_summary, commit_count, selected, threads, jobs):
+ """Return a string summarising the intended action.
+
+ Args:
+ is_summary (bool): True if this is a summary (otherwise it is building)
+ commits (list): List of commits being built
+ selected (list of Board): List of Board objects that are marked
+ step (int): Step increment through commits
+ threads (int): Number of processor threads being used
+ jobs (int): Number of jobs to build at once
+
+ Returns:
+ Summary string.
+ """
+ if commit_count:
+ commit_str = f'{commit_count} commit{get_plural(commit_count)}'
+ else:
+ commit_str = 'current source'
+ msg = (f"{'Summary of' if is_summary else 'Building'} "
+ f'{commit_str} for {len(selected)} boards')
+ msg += (f' ({threads} thread{get_plural(threads)}, '
+ f'{jobs} job{get_plural(jobs)} per thread)')
+ return msg
+
+# pylint: disable=R0913
+def show_actions(series, why_selected, boards_selected, output_dir,
+ board_warnings, step, threads, jobs, verbose):
+ """Display a list of actions that we would take, if not a dry run.
+
+ Args:
+ series: Series object
+ why_selected: Dictionary where each key is a buildman argument
+ provided by the user, and the value is the list of boards
+ brought in by that argument. For example, 'arm' might bring
+ in 400 boards, so in this case the key would be 'arm' and
+ the value would be a list of board names.
+ boards_selected: Dict of selected boards, key is target name,
+ value is Board object
+ output_dir (str): Output directory for builder
+ board_warnings: List of warnings obtained from board selected
+ step (int): Step increment through commits
+ threads (int): Number of processor threads being used
+ jobs (int): Number of jobs to build at once
+ verbose (bool): True to indicate why each board was selected
+ """
+ col = terminal.Color()
+ print('Dry run, so not doing much. But I would do this:')
+ print()
+ if series:
+ commits = series.commits
+ else:
+ commits = None
+ print(get_action_summary(False, count_build_commits(commits, step),
+ boards_selected, threads, jobs))
+ print(f'Build directory: {output_dir}')
+ if commits:
+ for upto in range(0, len(series.commits), step):
+ commit = series.commits[upto]
+ print(' ', col.build(col.YELLOW, commit.hash[:8], bright=False), end=' ')
+ print(commit.subject)
+ print()
+ for arg in why_selected:
+ # When -x is used, only the 'all' member exists
+ if arg != 'all' or len(why_selected) == 1:
+ print(arg, f': {len(why_selected[arg])} boards')
+ if verbose:
+ print(f" {' '.join(why_selected[arg])}")
+ print('Total boards to build for each '
+ f"commit: {len(why_selected['all'])}\n")
+ if board_warnings:
+ for warning in board_warnings:
+ print(col.build(col.YELLOW, warning))
+
+def show_toolchain_prefix(brds, toolchains):
+ """Show information about a the tool chain used by one or more boards
+
+ The function checks that all boards use the same toolchain, then prints
+ the correct value for CROSS_COMPILE.
+
+ Args:
+ boards: Boards object containing selected boards
+ toolchains: Toolchains object containing available toolchains
+
+ Return:
+ None on success, string error message otherwise
+ """
+ board_selected = brds.get_selected_dict()
+ tc_set = set()
+ for brd in board_selected.values():
+ tc_set.add(toolchains.Select(brd.arch))
+ if len(tc_set) != 1:
+ sys.exit('Supplied boards must share one toolchain')
+ tchain = tc_set.pop()
+ print(tchain.GetEnvArgs(toolchain.VAR_CROSS_COMPILE))
+
+def show_arch(brds):
+ """Show information about a the architecture used by one or more boards
+
+ The function checks that all boards use the same architecture, then prints
+ the correct value for ARCH.
+
+ Args:
+ boards: Boards object containing selected boards
+
+ Return:
+ None on success, string error message otherwise
+ """
+ board_selected = brds.get_selected_dict()
+ arch_set = set()
+ for brd in board_selected.values():
+ arch_set.add(brd.arch)
+ if len(arch_set) != 1:
+ sys.exit('Supplied boards must share one arch')
+ print(arch_set.pop())
+
+def get_allow_missing(opt_allow, opt_no_allow, num_selected, has_branch):
+ """Figure out whether to allow external blobs
+
+ Uses the allow-missing setting and the provided arguments to decide whether
+ missing external blobs should be allowed
+
+ Args:
+ opt_allow (bool): True if --allow-missing flag is set
+ opt_no_allow (bool): True if --no-allow-missing flag is set
+ num_selected (int): Number of selected board
+ has_branch (bool): True if a git branch (to build) has been provided
+
+ Returns:
+ bool: True to allow missing external blobs, False to produce an error if
+ external blobs are used
+ """
+ allow_missing = False
+ am_setting = bsettings.get_global_item_value('allow-missing')
+ if am_setting:
+ if am_setting == 'always':
+ allow_missing = True
+ if 'multiple' in am_setting and num_selected > 1:
+ allow_missing = True
+ if 'branch' in am_setting and has_branch:
+ allow_missing = True
+
+ if opt_allow:
+ allow_missing = True
+ if opt_no_allow:
+ allow_missing = False
+ return allow_missing
+
+
+def count_commits(branch, count, col, git_dir):
+ """Could the number of commits in the branch/ranch being built
+
+ Args:
+ branch (str): Name of branch to build, or None if none
+ count (int): Number of commits to build, or -1 for all
+ col (Terminal.Color): Color object to use
+ git_dir (str): Git directory to use, e.g. './.git'
+
+ Returns:
+ tuple:
+ Number of commits being built
+ True if the 'branch' string contains a range rather than a simple
+ name
+ """
+ has_range = branch and '..' in branch
+ if count == -1:
+ if not branch:
+ count = 1
+ else:
+ if has_range:
+ count, msg = gitutil.count_commits_in_range(git_dir, branch)
+ else:
+ count, msg = gitutil.count_commits_in_branch(git_dir, branch)
+ if count is None:
+ sys.exit(col.build(col.RED, msg))
+ elif count == 0:
+ sys.exit(col.build(col.RED,
+ f"Range '{branch}' has no commits"))
+ if msg:
+ print(col.build(col.YELLOW, msg))
+ count += 1 # Build upstream commit also
+
+ if not count:
+ msg = (f"No commits found to process in branch '{branch}': "
+ "set branch's upstream or use -c flag")
+ sys.exit(col.build(col.RED, msg))
+ return count, has_range
+
+
+def determine_series(selected, col, git_dir, count, branch, work_in_output):
+ """Determine the series which is to be built, if any
+
+ If there is a series, the commits in that series are numbered by setting
+ their sequence value (starting from 0). This is used by tests.
+
+ Args:
+ selected (list of Board): List of Board objects that are marked
+ selected
+ col (Terminal.Color): Color object to use
+ git_dir (str): Git directory to use, e.g. './.git'
+ count (int): Number of commits in branch
+ branch (str): Name of branch to build, or None if none
+ work_in_output (bool): True to work in the output directory
+
+ Returns:
+ Series: Series to build, or None for none
+
+ Read the metadata from the commits. First look at the upstream commit,
+ then the ones in the branch. We would like to do something like
+ upstream/master~..branch but that isn't possible if upstream/master is
+ a merge commit (it will list all the commits that form part of the
+ merge)
+
+ Conflicting tags are not a problem for buildman, since it does not use
+ them. For example, Series-version is not useful for buildman. On the
+ other hand conflicting tags will cause an error. So allow later tags
+ to overwrite earlier ones by setting allow_overwrite=True
+ """
+
+ # Work out how many commits to build. We want to build everything on the
+ # branch. We also build the upstream commit as a control so we can see
+ # problems introduced by the first commit on the branch.
+ count, has_range = count_commits(branch, count, col, git_dir)
+ if work_in_output:
+ if len(selected) != 1:
+ sys.exit(col.build(col.RED,
+ '-w can only be used with a single board'))
+ if count != 1:
+ sys.exit(col.build(col.RED,
+ '-w can only be used with a single commit'))
+
+ if branch:
+ if count == -1:
+ if has_range:
+ range_expr = branch
+ else:
+ range_expr = gitutil.get_range_in_branch(git_dir, branch)
+ upstream_commit = gitutil.get_upstream(git_dir, branch)
+ series = patchstream.get_metadata_for_list(upstream_commit,
+ git_dir, 1, series=None, allow_overwrite=True)
+
+ series = patchstream.get_metadata_for_list(range_expr,
+ git_dir, None, series, allow_overwrite=True)
+ else:
+ # Honour the count
+ series = patchstream.get_metadata_for_list(branch,
+ git_dir, count, series=None, allow_overwrite=True)
+
+ # Number the commits for test purposes
+ for i, commit in enumerate(series.commits):
+ commit.sequence = i
+ else:
+ series = None
+ return series
+
+
+def do_fetch_arch(toolchains, col, fetch_arch):
+ """Handle the --fetch-arch option
+
+ Args:
+ toolchains (Toolchains): Tool chains to use
+ col (terminal.Color): Color object to build
+ fetch_arch (str): Argument passed to the --fetch-arch option
+
+ Returns:
+ int: Return code for buildman
+ """
+ if fetch_arch == 'list':
+ sorted_list = toolchains.ListArchs()
+ print(col.build(
+ col.BLUE,
+ f"Available architectures: {' '.join(sorted_list)}\n"))
+ return 0
+
+ if fetch_arch == 'all':
+ fetch_arch = ','.join(toolchains.ListArchs())
+ print(col.build(col.CYAN,
+ f'\nDownloading toolchains: {fetch_arch}'))
+ for arch in fetch_arch.split(','):
+ print()
+ ret = toolchains.FetchAndInstall(arch)
+ if ret:
+ return ret
+ return 0
+
+
+def get_toolchains(toolchains, col, override_toolchain, fetch_arch,
+ list_tool_chains, verbose):
+ """Get toolchains object to use
+
+ Args:
+ toolchains (Toolchains or None): Toolchains to use. If None, then a
+ Toolchains object will be created and scanned
+ col (Terminal.Color): Color object
+ override_toolchain (str or None): Override value for toolchain, or None
+ fetch_arch (bool): True to fetch the toolchain for the architectures
+ list_tool_chains (bool): True to list all tool chains
+ verbose (bool): True for verbose output when listing toolchains
+
+ Returns:
+ Either:
+ int: Operation completed and buildman should exit with exit code
+ Toolchains: Toolchains object to use
+ """
+ no_toolchains = toolchains is None
+ if no_toolchains:
+ toolchains = toolchain.Toolchains(override_toolchain)
+
+ if fetch_arch:
+ return do_fetch_arch(toolchains, col, fetch_arch)
+
+ if no_toolchains:
+ toolchains.GetSettings()
+ toolchains.Scan(list_tool_chains and verbose)
+ if list_tool_chains:
+ toolchains.List()
+ print()
+ return 0
+ return toolchains
+
+
+def get_boards_obj(output_dir, regen_board_list, maintainer_check, full_check,
+ threads, verbose):
+ """Object the Boards object to use
+
+ Creates the output directory and ensures there is a boards.cfg file, then
+ read it in.
+
+ Args:
+ output_dir (str): Output directory to use, or None to use current dir
+ regen_board_list (bool): True to just regenerate the board list
+ maintainer_check (bool): True to just run a maintainer check
+ full_check (bool): True to just run a full check of Kconfig and
+ maintainers
+ threads (int or None): Number of threads to use to create boards file
+ verbose (bool): False to suppress output from boards-file generation
+
+ Returns:
+ Either:
+ int: Operation completed and buildman should exit with exit code
+ Boards: Boards object to use
+ """
+ brds = boards.Boards()
+ nr_cpus = threads or multiprocessing.cpu_count()
+ if maintainer_check or full_check:
+ warnings = brds.build_board_list(jobs=nr_cpus,
+ warn_targets=full_check)[1]
+ if warnings:
+ for warn in warnings:
+ print(warn, file=sys.stderr)
+ return 2
+ return 0
+
+ if output_dir and not os.path.exists(output_dir):
+ os.makedirs(output_dir)
+ board_file = os.path.join(output_dir or '', 'boards.cfg')
+ if regen_board_list and regen_board_list != '-':
+ board_file = regen_board_list
+
+ okay = brds.ensure_board_list(board_file, nr_cpus, force=regen_board_list,
+ quiet=not verbose)
+ if regen_board_list:
+ return 0 if okay else 2
+ brds.read_boards(board_file)
+ return brds
+
+
+def determine_boards(brds, args, col, opt_boards, exclude_list):
+ """Determine which boards to build
+
+ Each element of args and exclude can refer to a board name, arch or SoC
+
+ Args:
+ brds (Boards): Boards object
+ args (list of str): Arguments describing boards to build
+ col (Terminal.Color): Color object
+ opt_boards (list of str): Specific boards to build, or None for all
+ exclude_list (list of str): Arguments describing boards to exclude
+
+ Returns:
+ tuple:
+ list of Board: List of Board objects that are marked selected
+ why_selected: Dictionary where each key is a buildman argument
+ provided by the user, and the value is the list of boards
+ brought in by that argument. For example, 'arm' might bring
+ in 400 boards, so in this case the key would be 'arm' and
+ the value would be a list of board names.
+ board_warnings: List of warnings obtained from board selected
+ """
+ exclude = []
+ if exclude_list:
+ for arg in exclude_list:
+ exclude += arg.split(',')
+
+ if opt_boards:
+ requested_boards = []
+ for brd in opt_boards:
+ requested_boards += brd.split(',')
+ else:
+ requested_boards = None
+ why_selected, board_warnings = brds.select_boards(args, exclude,
+ requested_boards)
+ selected = brds.get_selected()
+ if not selected:
+ sys.exit(col.build(col.RED, 'No matching boards found'))
+ return selected, why_selected, board_warnings
+
+
+def adjust_args(args, series, selected):
+ """Adjust arguments according to various constraints
+
+ Updates verbose, show_errors, threads, jobs and step
+
+ Args:
+ args (Namespace): Namespace object to adjust
+ series (Series): Series being built / summarised
+ selected (list of Board): List of Board objects that are marked
+ """
+ if not series and not args.dry_run:
+ args.verbose = True
+ if not args.summary:
+ args.show_errors = True
+
+ # By default we have one thread per CPU. But if there are not enough jobs
+ # we can have fewer threads and use a high '-j' value for make.
+ if args.threads is None:
+ args.threads = min(multiprocessing.cpu_count(), len(selected))
+ if not args.jobs:
+ args.jobs = max(1, (multiprocessing.cpu_count() +
+ len(selected) - 1) // len(selected))
+
+ if not args.step:
+ args.step = len(series.commits) - 1
+
+ # We can't show function sizes without board details at present
+ if args.show_bloat:
+ args.show_detail = True
+
+
+def setup_output_dir(output_dir, work_in_output, branch, no_subdirs, col,
+ in_tree, clean_dir):
+ """Set up the output directory
+
+ Args:
+ output_dir (str): Output directory provided by the user, or None if none
+ work_in_output (bool): True to work in the output directory
+ branch (str): Name of branch to build, or None if none
+ no_subdirs (bool): True to put the output in the top-level output dir
+ in_tree (bool): True if doing an in-tree build
+ clean_dir: Used for tests only, indicates that the existing output_dir
+ should be removed before starting the build
+
+ Returns:
+ str: Updated output directory pathname
+ """
+ if not output_dir:
+ output_dir = '..'
+ if work_in_output:
+ if not in_tree:
+ sys.exit(col.build(col.RED, '-w requires that you specify -o'))
+ output_dir = None
+ if branch and not no_subdirs:
+ # As a special case allow the board directory to be placed in the
+ # output directory itself rather than any subdirectory.
+ dirname = branch.replace('/', '_')
+ output_dir = os.path.join(output_dir, dirname)
+ if clean_dir and os.path.exists(output_dir):
+ shutil.rmtree(output_dir)
+ return output_dir
+
+
+def run_builder(builder, commits, board_selected, args):
+ """Run the builder or show the summary
+
+ Args:
+ commits (list of Commit): List of commits being built, None if no branch
+ boards_selected (dict): Dict of selected boards:
+ key: target name
+ value: Board object
+ args (Namespace): Namespace to use
+
+ Returns:
+ int: Return code for buildman
+ """
+ gnu_make = command.output(os.path.join(args.git,
+ 'scripts/show-gnu-make'), raise_on_error=False).rstrip()
+ if not gnu_make:
+ sys.exit('GNU Make not found')
+ builder.gnu_make = gnu_make
+
+ if not args.ide:
+ commit_count = count_build_commits(commits, args.step)
+ tprint(get_action_summary(args.summary, commit_count, board_selected,
+ args.threads, args.jobs))
+
+ builder.set_display_options(
+ args.show_errors, args.show_sizes, args.show_detail, args.show_bloat,
+ args.list_error_boards, args.show_config, args.show_environment,
+ args.filter_dtb_warnings, args.filter_migration_warnings, args.ide)
+ if args.summary:
+ builder.show_summary(commits, board_selected)
+ else:
+ fail, warned, excs = builder.build_boards(
+ commits, board_selected, args.keep_outputs, args.verbose)
+ if excs:
+ return 102
+ if fail:
+ return 100
+ if warned and not args.ignore_warnings:
+ return 101
+ return 0
+
+
+def calc_adjust_cfg(adjust_cfg, reproducible_builds):
+ """Calculate the value to use for adjust_cfg
+
+ Args:
+ adjust_cfg (list of str): List of configuration changes. See cfgutil for
+ details
+ reproducible_builds (bool): True to adjust the configuration to get
+ reproduceable builds
+
+ Returns:
+ adjust_cfg (list of str): List of configuration changes
+ """
+ adjust_cfg = cfgutil.convert_list_to_dict(adjust_cfg)
+
+ # Drop LOCALVERSION_AUTO since it changes the version string on every commit
+ if reproducible_builds:
+ # If these are mentioned, leave the local version alone
+ if 'LOCALVERSION' in adjust_cfg or 'LOCALVERSION_AUTO' in adjust_cfg:
+ print('Not dropping LOCALVERSION_AUTO for reproducible build')
+ else:
+ adjust_cfg['LOCALVERSION_AUTO'] = '~'
+ return adjust_cfg
+
+
+def read_procs(tmpdir=tempfile.gettempdir()):
+ """Read the list of running buildman processes
+
+ If the list is corrupted, returns an empty list
+
+ Args:
+ tmpdir (str): Temporary directory to use (for testing only)
+ """
+ running_fname = os.path.join(tmpdir, RUNNING_FNAME)
+ procs = []
+ if os.path.exists(running_fname):
+ items = tools.read_file(running_fname, binary=False).split()
+ try:
+ procs = [int(x) for x in items]
+ except ValueError: # Handle invalid format
+ pass
+ return procs
+
+
+def check_pid(pid):
+ """Check for existence of a unix PID
+
+ https://stackoverflow.com/questions/568271/how-to-check-if-there-exists-a-process-with-a-given-pid-in-python
+
+ Args:
+ pid (int): PID to check
+
+ Returns:
+ True if it exists, else False
+ """
+ try:
+ os.kill(pid, 0)
+ except OSError:
+ return False
+ else:
+ return True
+
+
+def write_procs(procs, tmpdir=tempfile.gettempdir()):
+ """Write the list of running buildman processes
+
+ Args:
+ tmpdir (str): Temporary directory to use (for testing only)
+ """
+ running_fname = os.path.join(tmpdir, RUNNING_FNAME)
+ tools.write_file(running_fname, ' '.join([str(p) for p in procs]),
+ binary=False)
+
+ # Allow another user to access the file
+ os.chmod(running_fname, 0o666)
+
+def wait_for_process_limit(limit, tmpdir=tempfile.gettempdir(),
+ pid=os.getpid()):
+ """Wait until the number of buildman processes drops to the limit
+
+ This uses FileLock to protect a 'running' file, which contains a list of
+ PIDs of running buildman processes. The number of PIDs in the file indicates
+ the number of running processes.
+
+ When buildman starts up, it calls this function to wait until it is OK to
+ start the build.
+
+ On exit, no attempt is made to remove the PID from the file, since other
+ buildman processes will notice that the PID is no-longer valid, and ignore
+ it.
+
+ Two timeouts are provided:
+ LOCK_WAIT_S: length of time to wait for the lock; if this occurs, the
+ lock is busted / removed before trying again
+ RUN_WAIT_S: length of time to wait to be allowed to run; if this occurs,
+ the build starts, with the PID being added to the file.
+
+ Args:
+ limit (int): Maximum number of buildman processes, including this one;
+ must be > 0
+ tmpdir (str): Temporary directory to use (for testing only)
+ pid (int): Current process ID (for testing only)
+ """
+ from filelock import Timeout, FileLock
+
+ running_fname = os.path.join(tmpdir, RUNNING_FNAME)
+ lock_fname = os.path.join(tmpdir, LOCK_FNAME)
+ lock = FileLock(lock_fname)
+
+ # Allow another user to access the file
+ col = terminal.Color()
+ tprint('Waiting for other buildman processes...', newline=False,
+ colour=col.RED)
+
+ claimed = False
+ deadline = time.time() + RUN_WAIT_S
+ while True:
+ try:
+ with lock.acquire(timeout=LOCK_WAIT_S):
+ os.chmod(lock_fname, 0o666)
+ procs = read_procs(tmpdir)
+
+ # Drop PIDs which are not running
+ procs = list(filter(check_pid, procs))
+
+ # If we haven't hit the limit, add ourself
+ if len(procs) < limit:
+ tprint('done...', newline=False)
+ claimed = True
+ if time.time() >= deadline:
+ tprint('timeout...', newline=False)
+ claimed = True
+ if claimed:
+ write_procs(procs + [pid], tmpdir)
+ break
+
+ except Timeout:
+ tprint('failed to get lock: busting...', newline=False)
+ os.remove(lock_fname)
+
+ time.sleep(1)
+ tprint('starting build', newline=False)
+ print_clear()
+
+def do_buildman(args, toolchains=None, make_func=None, brds=None,
+ clean_dir=False, test_thread_exceptions=False):
+ """The main control code for buildman
+
+ Args:
+ args: ArgumentParser object
+ args: Command line arguments (list of strings)
+ toolchains: Toolchains to use - this should be a Toolchains()
+ object. If None, then it will be created and scanned
+ make_func: Make function to use for the builder. This is called
+ to execute 'make'. If this is None, the normal function
+ will be used, which calls the 'make' tool with suitable
+ arguments. This setting is useful for tests.
+ brds: Boards() object to use, containing a list of available
+ boards. If this is None it will be created and scanned.
+ clean_dir: Used for tests only, indicates that the existing output_dir
+ should be removed before starting the build
+ test_thread_exceptions: Uses for tests only, True to make the threads
+ raise an exception instead of reporting their result. This simulates
+ a failure in the code somewhere
+ """
+ # Used so testing can obtain the builder: pylint: disable=W0603
+ global TEST_BUILDER
+
+ gitutil.setup()
+ col = terminal.Color()
+
+ git_dir = os.path.join(args.git, '.git')
+
+ toolchains = get_toolchains(toolchains, col, args.override_toolchain,
+ args.fetch_arch, args.list_tool_chains,
+ args.verbose)
+ if isinstance(toolchains, int):
+ return toolchains
+
+ output_dir = setup_output_dir(
+ args.output_dir, args.work_in_output, args.branch,
+ args.no_subdirs, col, args.in_tree, clean_dir)
+
+ # Work out what subset of the boards we are building
+ if not brds:
+ brds = get_boards_obj(output_dir, args.regen_board_list,
+ args.maintainer_check, args.full_check,
+ args.threads, args.verbose and
+ not args.print_arch and not args.print_prefix)
+ if isinstance(brds, int):
+ return brds
+
+ selected, why_selected, board_warnings = determine_boards(
+ brds, args.terms, col, args.boards, args.exclude)
+
+ if args.print_prefix:
+ show_toolchain_prefix(brds, toolchains)
+ return 0
+
+ if args.print_arch:
+ show_arch(brds)
+ return 0
+
+ series = determine_series(selected, col, git_dir, args.count,
+ args.branch, args.work_in_output)
+
+ adjust_args(args, series, selected)
+
+ # For a dry run, just show our actions as a sanity check
+ if args.dry_run:
+ show_actions(series, why_selected, selected, output_dir, board_warnings,
+ args.step, args.threads, args.jobs,
+ args.verbose)
+ return 0
+
+ if args.config_only and args.target:
+ raise ValueError('Cannot use --config-only with --target')
+
+ # Create a new builder with the selected args
+ builder = Builder(toolchains, output_dir, git_dir,
+ args.threads, args.jobs, checkout=True,
+ show_unknown=args.show_unknown, step=args.step,
+ no_subdirs=args.no_subdirs, full_path=args.full_path,
+ verbose_build=args.verbose_build,
+ mrproper=args.mrproper,
+ fallback_mrproper=args.fallback_mrproper,
+ per_board_out_dir=args.per_board_out_dir,
+ config_only=args.config_only,
+ squash_config_y=not args.preserve_config_y,
+ warnings_as_errors=args.warnings_as_errors,
+ work_in_output=args.work_in_output,
+ test_thread_exceptions=test_thread_exceptions,
+ adjust_cfg=calc_adjust_cfg(args.adjust_cfg,
+ args.reproducible_builds),
+ allow_missing=get_allow_missing(args.allow_missing,
+ args.no_allow_missing,
+ len(selected), args.branch),
+ no_lto=args.no_lto,
+ reproducible_builds=args.reproducible_builds,
+ force_build = args.force_build,
+ force_build_failures = args.force_build_failures,
+ force_reconfig = args.force_reconfig, in_tree = args.in_tree,
+ force_config_on_failure=not args.quick, make_func=make_func,
+ dtc_skip=args.dtc_skip, build_target=args.target)
+
+ TEST_BUILDER = builder
+
+ if args.process_limit:
+ wait_for_process_limit(args.process_limit)
+
+ return run_builder(builder, series.commits if series else None,
+ brds.get_selected_dict(), args)