summaryrefslogtreecommitdiff
path: root/tools/buildman/builderthread.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/buildman/builderthread.py')
-rw-r--r--tools/buildman/builderthread.py797
1 files changed, 797 insertions, 0 deletions
diff --git a/tools/buildman/builderthread.py b/tools/buildman/builderthread.py
new file mode 100644
index 00000000000..371708c8a98
--- /dev/null
+++ b/tools/buildman/builderthread.py
@@ -0,0 +1,797 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright (c) 2014 Google, Inc
+#
+
+"""Implementation the bulider threads
+
+This module provides the BuilderThread class, which handles calling the builder
+based on the jobs provided.
+"""
+
+import errno
+import glob
+import io
+import os
+import shutil
+import sys
+import threading
+
+from buildman import cfgutil
+from u_boot_pylib import command
+from u_boot_pylib import gitutil
+from u_boot_pylib import tools
+
+RETURN_CODE_RETRY = -1
+BASE_ELF_FILENAMES = ['u-boot', 'spl/u-boot-spl', 'tpl/u-boot-tpl']
+
+# Common extensions for images
+COMMON_EXTS = ['.bin', '.rom', '.itb', '.img']
+
+def mkdir(dirname, parents=False):
+ """Make a directory if it doesn't already exist.
+
+ Args:
+ dirname (str): Directory to create, or None to do nothing
+ parents (bool): True to also make parent directories
+
+ Raises:
+ OSError: File already exists
+ """
+ if not dirname or os.path.exists(dirname):
+ return
+ try:
+ if parents:
+ os.makedirs(dirname)
+ else:
+ os.mkdir(dirname)
+ except OSError as err:
+ if err.errno == errno.EEXIST:
+ if os.path.realpath('.') == os.path.realpath(dirname):
+ raise ValueError(
+ f"Cannot create the current working directory '{dirname}'!")
+ else:
+ raise
+
+
+def _remove_old_outputs(out_dir):
+ """Remove any old output-target files
+
+ Args:
+ out_dir (str): Output directory for the build, or None for current dir
+
+ Since we use a build directory that was previously used by another
+ board, it may have produced an SPL image. If we don't remove it (i.e.
+ see do_config and self.mrproper below) then it will appear to be the
+ output of this build, even if it does not produce SPL images.
+ """
+ for elf in BASE_ELF_FILENAMES:
+ fname = os.path.join(out_dir or '', elf)
+ if os.path.exists(fname):
+ os.remove(fname)
+
+
+def copy_files(out_dir, build_dir, dirname, patterns):
+ """Copy files from the build directory to the output.
+
+ Args:
+ out_dir (str): Path to output directory containing the files
+ build_dir (str): Place to copy the files
+ dirname (str): Source directory, '' for normal U-Boot, 'spl' for SPL
+ patterns (list of str): A list of filenames to copy, each relative
+ to the build directory
+ """
+ for pattern in patterns:
+ file_list = glob.glob(os.path.join(out_dir, dirname, pattern))
+ for fname in file_list:
+ target = os.path.basename(fname)
+ if dirname:
+ base, ext = os.path.splitext(target)
+ if ext:
+ target = f'{base}-{dirname}{ext}'
+ shutil.copy(fname, os.path.join(build_dir, target))
+
+
+# pylint: disable=R0903
+class BuilderJob:
+ """Holds information about a job to be performed by a thread
+
+ Members:
+ brd: Board object to build
+ commits: List of Commit objects to build
+ keep_outputs: True to save build output files
+ step: 1 to process every commit, n to process every nth commit
+ work_in_output: Use the output directory as the work directory and
+ don't write to a separate output directory.
+ """
+ def __init__(self):
+ self.brd = None
+ self.commits = []
+ self.keep_outputs = False
+ self.step = 1
+ self.work_in_output = False
+
+
+class ResultThread(threading.Thread):
+ """This thread processes results from builder threads.
+
+ It simply passes the results on to the builder. There is only one
+ result thread, and this helps to serialise the build output.
+ """
+ def __init__(self, builder):
+ """Set up a new result thread
+
+ Args:
+ builder: Builder which will be sent each result
+ """
+ threading.Thread.__init__(self)
+ self.builder = builder
+
+ def run(self):
+ """Called to start up the result thread.
+
+ We collect the next result job and pass it on to the build.
+ """
+ while True:
+ result = self.builder.out_queue.get()
+ self.builder.process_result(result)
+ self.builder.out_queue.task_done()
+
+
+class BuilderThread(threading.Thread):
+ """This thread builds U-Boot for a particular board.
+
+ An input queue provides each new job. We run 'make' to build U-Boot
+ and then pass the results on to the output queue.
+
+ Members:
+ builder: The builder which contains information we might need
+ thread_num: Our thread number (0-n-1), used to decide on a
+ temporary directory. If this is -1 then there are no threads
+ and we are the (only) main process
+ mrproper: Use 'make mrproper' before each reconfigure
+ per_board_out_dir: True to build in a separate persistent directory per
+ board rather than a thread-specific directory
+ test_exception: Used for testing; True to raise an exception instead of
+ reporting the build result
+ """
+ def __init__(self, builder, thread_num, mrproper, per_board_out_dir,
+ test_exception=False):
+ """Set up a new builder thread"""
+ threading.Thread.__init__(self)
+ self.builder = builder
+ self.thread_num = thread_num
+ self.mrproper = mrproper
+ self.per_board_out_dir = per_board_out_dir
+ self.test_exception = test_exception
+ self.toolchain = None
+
+ def make(self, commit, brd, stage, cwd, *args, **kwargs):
+ """Run 'make' on a particular commit and board.
+
+ The source code will already be checked out, so the 'commit'
+ argument is only for information.
+
+ Args:
+ commit (Commit): Commit that is being built
+ brd (Board): Board that is being built
+ stage (str): Stage of the build. Valid stages are:
+ mrproper - can be called to clean source
+ config - called to configure for a board
+ build - the main make invocation - it does the build
+ cwd (str): Working directory to set, or None to leave it alone
+ *args (list of str): Arguments to pass to 'make'
+ **kwargs (dict): A list of keyword arguments to pass to
+ command.run_one()
+
+ Returns:
+ CommandResult object
+ """
+ return self.builder.do_make(commit, brd, stage, cwd, *args, **kwargs)
+
+ def _build_args(self, brd, out_dir, out_rel_dir, work_dir, commit_upto):
+ """Set up arguments to the args list based on the settings
+
+ Args:
+ brd (Board): Board to create arguments for
+ out_dir (str): Path to output directory containing the files, or
+ or None to not use a separate output directory
+ out_rel_dir (str): Output directory relative to the current dir
+ work_dir (str): Directory to which the source will be checked out,
+ or None to use current directory
+ commit_upto (int): Commit number to build (0...n-1)
+
+ Returns:
+ tuple:
+ list of str: Arguments to pass to make
+ str: Current working directory, or None if no commit
+ str: Source directory (typically the work directory)
+ """
+ args = []
+ cwd = work_dir
+ src_dir = os.path.realpath(work_dir) if work_dir else os.getcwd()
+ if commit_upto is None:
+ # In this case we are building in the original source directory
+ # (i.e. the current directory where buildman is invoked. The
+ # output directory is set to this thread's selected work
+ # directory.
+ #
+ # Symlinks can confuse U-Boot's Makefile since we may use '..'
+ # in our path, so remove them.
+ if out_dir:
+ real_dir = os.path.realpath(out_dir)
+ args.append(f'O={real_dir}')
+ cwd = None
+ src_dir = os.getcwd()
+ elif out_rel_dir:
+ args.append(f'O={out_rel_dir}')
+ if self.builder.verbose_build:
+ args.append('V=1')
+ else:
+ args.append('-s')
+ if self.builder.num_jobs is not None:
+ args.extend(['-j', str(self.builder.num_jobs)])
+ if self.builder.warnings_as_errors:
+ args.append('KCFLAGS=-Werror')
+ args.append('HOSTCFLAGS=-Werror')
+ if self.builder.allow_missing:
+ args.append('BINMAN_ALLOW_MISSING=1')
+ if self.builder.no_lto:
+ args.append('NO_LTO=1')
+ if self.builder.reproducible_builds:
+ args.append('SOURCE_DATE_EPOCH=0')
+ args.extend(self.builder.toolchains.GetMakeArguments(brd))
+ args.extend(self.toolchain.MakeArgs())
+ return args, cwd, src_dir
+
+ def _reconfigure(self, commit, brd, cwd, args, env, config_args, config_out,
+ cmd_list, mrproper):
+ """Reconfigure the build
+
+ Args:
+ commit (Commit): Commit only being built
+ brd (Board): Board being built
+ cwd (str): Current working directory
+ args (list of str): Arguments to pass to make
+ env (dict): Environment strings
+ config_args (list of str): defconfig arg for this board
+ cmd_list (list of str): List to add the commands to, for logging
+ mrproper (bool): True to run mrproper first
+
+ Returns:
+ CommandResult object
+ """
+ if mrproper:
+ result = self.make(commit, brd, 'mrproper', cwd, 'mrproper', *args,
+ env=env)
+ config_out.write(result.combined)
+ cmd_list.append([self.builder.gnu_make, 'mrproper', *args])
+ result = self.make(commit, brd, 'config', cwd, *(args + config_args),
+ env=env)
+ cmd_list.append([self.builder.gnu_make] + args + config_args)
+ config_out.write(result.combined)
+ return result
+
+ def _build(self, commit, brd, cwd, args, env, cmd_list, config_only):
+ """Perform the build
+
+ Args:
+ commit (Commit): Commit only being built
+ brd (Board): Board being built
+ cwd (str): Current working directory
+ args (list of str): Arguments to pass to make
+ env (dict): Environment strings
+ cmd_list (list of str): List to add the commands to, for logging
+ config_only (bool): True if this is a config-only build (using the
+ 'make cfg' target)
+
+ Returns:
+ CommandResult object
+ """
+ if config_only:
+ args.append('cfg')
+ elif self.builder.build_target:
+ args.append(self.builder.build_target)
+ result = self.make(commit, brd, 'build', cwd, *args, env=env)
+ cmd_list.append([self.builder.gnu_make] + args)
+ if (result.return_code == 2 and
+ ('Some images are invalid' in result.stderr)):
+ # This is handled later by the check for output in stderr
+ result.return_code = 0
+ return result
+
+ def _read_done_file(self, commit_upto, brd, force_build,
+ force_build_failures):
+ """Check the 'done' file and see if this commit should be built
+
+ Args:
+ commit (Commit): Commit only being built
+ brd (Board): Board being built
+ force_build (bool): Force a build even if one was previously done
+ force_build_failures (bool): Force a bulid if the previous result
+ showed failure
+
+ Returns:
+ tuple:
+ bool: True if build should be built
+ CommandResult: if there was a previous run:
+ - already_done set to True
+ - return_code set to return code
+ - result.stderr set to 'bad' if stderr output was recorded
+ """
+ result = command.CommandResult()
+ done_file = self.builder.get_done_file(commit_upto, brd.target)
+ result.already_done = os.path.exists(done_file)
+ will_build = (force_build or force_build_failures or
+ not result.already_done)
+ if result.already_done:
+ with open(done_file, 'r', encoding='utf-8') as outf:
+ try:
+ result.return_code = int(outf.readline())
+ except ValueError:
+ # The file may be empty due to running out of disk space.
+ # Try a rebuild
+ result.return_code = RETURN_CODE_RETRY
+
+ # Check the signal that the build needs to be retried
+ if result.return_code == RETURN_CODE_RETRY:
+ will_build = True
+ elif will_build:
+ err_file = self.builder.get_err_file(commit_upto, brd.target)
+ if os.path.exists(err_file) and os.stat(err_file).st_size:
+ result.stderr = 'bad'
+ elif not force_build:
+ # The build passed, so no need to build it again
+ will_build = False
+ return will_build, result
+
+ def _decide_dirs(self, brd, work_dir, work_in_output):
+ """Decide the output directory to use
+
+ Args:
+ work_dir (str): Directory to which the source will be checked out
+ work_in_output (bool): Use the output directory as the work
+ directory and don't write to a separate output directory.
+
+ Returns:
+ tuple:
+ out_dir (str): Output directory for the build
+ out_rel_dir (str): Output directory relatie to the current dir
+ """
+ if work_in_output or self.builder.in_tree:
+ out_rel_dir = None
+ out_dir = work_dir
+ else:
+ if self.per_board_out_dir:
+ out_rel_dir = os.path.join('..', brd.target)
+ else:
+ out_rel_dir = 'build'
+ out_dir = os.path.join(work_dir, out_rel_dir)
+ return out_dir, out_rel_dir
+
+ def _checkout(self, commit_upto, work_dir):
+ """Checkout the right commit
+
+ Args:
+ commit_upto (int): Commit number to build (0...n-1)
+ work_dir (str): Directory to which the source will be checked out
+
+ Returns:
+ Commit: Commit being built, or 'current' for current source
+ """
+ if self.builder.commits:
+ commit = self.builder.commits[commit_upto]
+ if self.builder.checkout:
+ git_dir = os.path.join(work_dir, '.git')
+ gitutil.checkout(commit.hash, git_dir, work_dir, force=True)
+ else:
+ commit = 'current'
+ return commit
+
+ def _config_and_build(self, commit_upto, brd, work_dir, do_config, mrproper,
+ config_only, adjust_cfg, commit, out_dir, out_rel_dir,
+ result):
+ """Do the build, configuring first if necessary
+
+ Args:
+ commit_upto (int): Commit number to build (0...n-1)
+ brd (Board): Board to create arguments for
+ work_dir (str): Directory to which the source will be checked out
+ do_config (bool): True to run a make <board>_defconfig on the source
+ mrproper (bool): True to run mrproper first
+ config_only (bool): Only configure the source, do not build it
+ adjust_cfg (list of str): See the cfgutil module and run_commit()
+ commit (Commit): Commit only being built
+ out_dir (str): Output directory for the build, or None to use
+ current
+ out_rel_dir (str): Output directory relatie to the current dir
+ result (CommandResult): Previous result
+
+ Returns:
+ tuple:
+ result (CommandResult): Result of the build
+ do_config (bool): indicates whether 'make config' is needed on
+ the next incremental build
+ """
+ # Set up the environment and command line
+ env = self.builder.make_environment(self.toolchain)
+ if out_dir and not os.path.exists(out_dir):
+ mkdir(out_dir)
+
+ args, cwd, src_dir = self._build_args(brd, out_dir, out_rel_dir,
+ work_dir, commit_upto)
+ config_args = [f'{brd.target}_defconfig']
+ config_out = io.StringIO()
+
+ _remove_old_outputs(out_dir)
+
+ # If we need to reconfigure, do that now
+ cfg_file = os.path.join(out_dir or '', '.config')
+ cmd_list = []
+ if do_config or adjust_cfg:
+ result = self._reconfigure(
+ commit, brd, cwd, args, env, config_args, config_out, cmd_list,
+ mrproper)
+ do_config = False # No need to configure next time
+ if adjust_cfg:
+ cfgutil.adjust_cfg_file(cfg_file, adjust_cfg)
+
+ # Now do the build, if everything looks OK
+ if result.return_code == 0:
+ if adjust_cfg:
+ oldc_args = list(args) + ['oldconfig']
+ oldc_result = self.make(commit, brd, 'oldconfig', cwd,
+ *oldc_args, env=env)
+ if oldc_result.return_code:
+ return oldc_result
+ result = self._build(commit, brd, cwd, args, env, cmd_list,
+ config_only)
+ if adjust_cfg:
+ errs = cfgutil.check_cfg_file(cfg_file, adjust_cfg)
+ if errs:
+ result.stderr += errs
+ result.return_code = 1
+ result.stderr = result.stderr.replace(src_dir + '/', '')
+ if self.builder.verbose_build:
+ result.stdout = config_out.getvalue() + result.stdout
+ result.cmd_list = cmd_list
+ return result, do_config
+
+ def run_commit(self, commit_upto, brd, work_dir, do_config, mrproper,
+ config_only, force_build, force_build_failures,
+ work_in_output, adjust_cfg):
+ """Build a particular commit.
+
+ If the build is already done, and we are not forcing a build, we skip
+ the build and just return the previously-saved results.
+
+ Args:
+ commit_upto (int): Commit number to build (0...n-1)
+ brd (Board): Board to build
+ work_dir (str): Directory to which the source will be checked out
+ do_config (bool): True to run a make <board>_defconfig on the source
+ mrproper (bool): True to run mrproper first
+ config_only (bool): Only configure the source, do not build it
+ force_build (bool): Force a build even if one was previously done
+ force_build_failures (bool): Force a bulid if the previous result
+ showed failure
+ work_in_output (bool) : Use the output directory as the work
+ directory and don't write to a separate output directory.
+ adjust_cfg (list of str): List of changes to make to .config file
+ before building. Each is one of (where C is either CONFIG_xxx
+ or just xxx):
+ C to enable C
+ ~C to disable C
+ C=val to set the value of C (val must have quotes if C is
+ a string Kconfig
+
+ Returns:
+ tuple containing:
+ - CommandResult object containing the results of the build
+ - boolean indicating whether 'make config' is still needed
+ """
+ # Create a default result - it will be overwritte by the call to
+ # self.make() below, in the event that we do a build.
+ out_dir, out_rel_dir = self._decide_dirs(brd, work_dir, work_in_output)
+
+ # Check if the job was already completed last time
+ will_build, result = self._read_done_file(commit_upto, brd, force_build,
+ force_build_failures)
+
+ if will_build:
+ # We are going to have to build it. First, get a toolchain
+ if not self.toolchain:
+ try:
+ self.toolchain = self.builder.toolchains.Select(brd.arch)
+ except ValueError as err:
+ result.return_code = 10
+ result.stdout = ''
+ result.stderr = f'Tool chain error for {brd.arch}: {str(err)}'
+
+ if self.toolchain:
+ commit = self._checkout(commit_upto, work_dir)
+ result, do_config = self._config_and_build(
+ commit_upto, brd, work_dir, do_config, mrproper,
+ config_only, adjust_cfg, commit, out_dir, out_rel_dir,
+ result)
+ result.already_done = False
+
+ result.toolchain = self.toolchain
+ result.brd = brd
+ result.commit_upto = commit_upto
+ result.out_dir = out_dir
+ return result, do_config
+
+ def _write_result(self, result, keep_outputs, work_in_output):
+ """Write a built result to the output directory.
+
+ Args:
+ result (CommandResult): result to write
+ keep_outputs (bool): True to store the output binaries, False
+ to delete them
+ work_in_output (bool): Use the output directory as the work
+ directory and don't write to a separate output directory.
+ """
+ # If we think this might have been aborted with Ctrl-C, record the
+ # failure but not that we are 'done' with this board. A retry may fix
+ # it.
+ maybe_aborted = result.stderr and 'No child processes' in result.stderr
+
+ if result.return_code >= 0 and result.already_done:
+ return
+
+ # Write the output and stderr
+ output_dir = self.builder.get_output_dir(result.commit_upto)
+ mkdir(output_dir)
+ build_dir = self.builder.get_build_dir(result.commit_upto,
+ result.brd.target)
+ mkdir(build_dir)
+
+ outfile = os.path.join(build_dir, 'log')
+ with open(outfile, 'w', encoding='utf-8') as outf:
+ if result.stdout:
+ outf.write(result.stdout)
+
+ errfile = self.builder.get_err_file(result.commit_upto,
+ result.brd.target)
+ if result.stderr:
+ with open(errfile, 'w', encoding='utf-8') as outf:
+ outf.write(result.stderr)
+ elif os.path.exists(errfile):
+ os.remove(errfile)
+
+ # Fatal error
+ if result.return_code < 0:
+ return
+
+ done_file = self.builder.get_done_file(result.commit_upto,
+ result.brd.target)
+ if result.toolchain:
+ # Write the build result and toolchain information.
+ with open(done_file, 'w', encoding='utf-8') as outf:
+ if maybe_aborted:
+ # Special code to indicate we need to retry
+ outf.write(f'{RETURN_CODE_RETRY}')
+ else:
+ outf.write(f'{result.return_code}')
+ with open(os.path.join(build_dir, 'toolchain'), 'w',
+ encoding='utf-8') as outf:
+ print('gcc', result.toolchain.gcc, file=outf)
+ print('path', result.toolchain.path, file=outf)
+ print('cross', result.toolchain.cross, file=outf)
+ print('arch', result.toolchain.arch, file=outf)
+ outf.write(f'{result.return_code}')
+
+ # Write out the image and function size information and an objdump
+ env = self.builder.make_environment(self.toolchain)
+ with open(os.path.join(build_dir, 'out-env'), 'wb') as outf:
+ for var in sorted(env.keys()):
+ outf.write(b'%s="%s"' % (var, env[var]))
+
+ with open(os.path.join(build_dir, 'out-cmd'), 'w',
+ encoding='utf-8') as outf:
+ for cmd in result.cmd_list:
+ print(' '.join(cmd), file=outf)
+
+ lines = []
+ for fname in BASE_ELF_FILENAMES:
+ cmd = [f'{self.toolchain.cross}nm', '--size-sort', fname]
+ nm_result = command.run_one(*cmd, capture=True,
+ capture_stderr=True,
+ cwd=result.out_dir,
+ raise_on_error=False, env=env)
+ if nm_result.stdout:
+ nm_fname = self.builder.get_func_sizes_file(
+ result.commit_upto, result.brd.target, fname)
+ with open(nm_fname, 'w', encoding='utf-8') as outf:
+ print(nm_result.stdout, end=' ', file=outf)
+
+ cmd = [f'{self.toolchain.cross}objdump', '-h', fname]
+ dump_result = command.run_one(*cmd, capture=True,
+ capture_stderr=True,
+ cwd=result.out_dir,
+ raise_on_error=False, env=env)
+ rodata_size = ''
+ if dump_result.stdout:
+ objdump = self.builder.get_objdump_file(result.commit_upto,
+ result.brd.target, fname)
+ with open(objdump, 'w', encoding='utf-8') as outf:
+ print(dump_result.stdout, end=' ', file=outf)
+ for line in dump_result.stdout.splitlines():
+ fields = line.split()
+ if len(fields) > 5 and fields[1] == '.rodata':
+ rodata_size = fields[2]
+
+ cmd = [f'{self.toolchain.cross}size', fname]
+ size_result = command.run_one(*cmd, capture=True,
+ capture_stderr=True,
+ cwd=result.out_dir,
+ raise_on_error=False, env=env)
+ if size_result.stdout:
+ lines.append(size_result.stdout.splitlines()[1] + ' ' +
+ rodata_size)
+
+ # Extract the environment from U-Boot and dump it out
+ cmd = [f'{self.toolchain.cross}objcopy', '-O', 'binary',
+ '-j', '.rodata.default_environment',
+ 'env/built-in.a', 'uboot.env']
+ command.run_one(*cmd, capture=True, capture_stderr=True,
+ cwd=result.out_dir, raise_on_error=False, env=env)
+ if not work_in_output:
+ copy_files(result.out_dir, build_dir, '', ['uboot.env'])
+
+ # Write out the image sizes file. This is similar to the output
+ # of binutil's 'size' utility, but it omits the header line and
+ # adds an additional hex value at the end of each line for the
+ # rodata size
+ if lines:
+ sizes = self.builder.get_sizes_file(result.commit_upto,
+ result.brd.target)
+ with open(sizes, 'w', encoding='utf-8') as outf:
+ print('\n'.join(lines), file=outf)
+ else:
+ # Indicate that the build failure due to lack of toolchain
+ tools.write_file(done_file, '2\n', binary=False)
+
+ if not work_in_output:
+ # Write out the configuration files, with a special case for SPL
+ for dirname in ['', 'spl', 'tpl']:
+ copy_files(
+ result.out_dir, build_dir, dirname,
+ ['u-boot.cfg', 'spl/u-boot-spl.cfg', 'tpl/u-boot-tpl.cfg',
+ '.config', 'include/autoconf.mk',
+ 'include/generated/autoconf.h'])
+
+ # Now write the actual build output
+ if keep_outputs:
+ to_copy = ['u-boot*', '*.map', 'MLO', 'SPL',
+ 'include/autoconf.mk', 'spl/u-boot-spl*',
+ 'tpl/u-boot-tpl*', 'vpl/u-boot-vpl*']
+ to_copy += [f'*{ext}' for ext in COMMON_EXTS]
+ copy_files(result.out_dir, build_dir, '', to_copy)
+
+ def _send_result(self, result):
+ """Send a result to the builder for processing
+
+ Args:
+ result (CommandResult): results of the build
+
+ Raises:
+ ValueError: self.test_exception is true (for testing)
+ """
+ if self.test_exception:
+ raise ValueError('test exception')
+ if self.thread_num != -1:
+ self.builder.out_queue.put(result)
+ else:
+ self.builder.process_result(result)
+
+ def run_job(self, job):
+ """Run a single job
+
+ A job consists of a building a list of commits for a particular board.
+
+ Args:
+ job (Job): Job to build
+
+ Raises:
+ ValueError: Thread was interrupted
+ """
+ brd = job.brd
+ work_dir = self.builder.get_thread_dir(self.thread_num)
+ self.toolchain = None
+ if job.commits:
+ # Run 'make board_defconfig' on the first commit
+ do_config = True
+ commit_upto = 0
+ force_build = False
+ for commit_upto in range(0, len(job.commits), job.step):
+ result, request_config = self.run_commit(commit_upto, brd,
+ work_dir, do_config, self.mrproper,
+ self.builder.config_only,
+ force_build or self.builder.force_build,
+ self.builder.force_build_failures,
+ job.work_in_output, job.adjust_cfg)
+ failed = result.return_code or result.stderr
+ did_config = do_config
+ if failed and not do_config and not self.mrproper:
+ # If our incremental build failed, try building again
+ # with a reconfig.
+ if self.builder.force_config_on_failure:
+ result, request_config = self.run_commit(commit_upto,
+ brd, work_dir, True,
+ self.mrproper or self.builder.fallback_mrproper,
+ False, True, False, job.work_in_output,
+ job.adjust_cfg)
+ did_config = True
+ if not self.builder.force_reconfig:
+ do_config = request_config
+
+ # If we built that commit, then config is done. But if we got
+ # an warning, reconfig next time to force it to build the same
+ # files that created warnings this time. Otherwise an
+ # incremental build may not build the same file, and we will
+ # think that the warning has gone away.
+ # We could avoid this by using -Werror everywhere...
+ # For errors, the problem doesn't happen, since presumably
+ # the build stopped and didn't generate output, so will retry
+ # that file next time. So we could detect warnings and deal
+ # with them specially here. For now, we just reconfigure if
+ # anything goes work.
+ # Of course this is substantially slower if there are build
+ # errors/warnings (e.g. 2-3x slower even if only 10% of builds
+ # have problems).
+ if (failed and not result.already_done and not did_config and
+ self.builder.force_config_on_failure):
+ # If this build failed, try the next one with a
+ # reconfigure.
+ # Sometimes if the board_config.h file changes it can mess
+ # with dependencies, and we get:
+ # make: *** No rule to make target `include/autoconf.mk',
+ # needed by `depend'.
+ do_config = True
+ force_build = True
+ else:
+ force_build = False
+ if self.builder.force_config_on_failure:
+ if failed:
+ do_config = True
+ result.commit_upto = commit_upto
+ if result.return_code < 0:
+ raise ValueError('Interrupt')
+
+ # We have the build results, so output the result
+ self._write_result(result, job.keep_outputs, job.work_in_output)
+ self._send_result(result)
+ else:
+ # Just build the currently checked-out build
+ result, request_config = self.run_commit(None, brd, work_dir, True,
+ self.mrproper, self.builder.config_only, True,
+ self.builder.force_build_failures, job.work_in_output,
+ job.adjust_cfg)
+ failed = result.return_code or result.stderr
+ if failed and not self.mrproper:
+ result, request_config = self.run_commit(None, brd, work_dir,
+ True, self.builder.fallback_mrproper,
+ self.builder.config_only, True,
+ self.builder.force_build_failures,
+ job.work_in_output, job.adjust_cfg)
+
+ result.commit_upto = 0
+ self._write_result(result, job.keep_outputs, job.work_in_output)
+ self._send_result(result)
+
+ def run(self):
+ """Our thread's run function
+
+ This thread picks a job from the queue, runs it, and then goes to the
+ next job.
+ """
+ while True:
+ job = self.builder.queue.get()
+ try:
+ self.run_job(job)
+ except Exception as exc:
+ print('Thread exception (use -T0 to run without threads):',
+ exc)
+ self.builder.thread_exceptions.append(exc)
+ self.builder.queue.task_done()