mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-12-22 11:13:07 +00:00
124a0da554
Make a list of common output extensions and use it to ensure that the -k option preserves all of these. Signed-off-by: Simon Glass <sjg@chromium.org> Suggested-by: Tom Rini <trini@konsulko.com> Reviewed-by: Tom Rini <trini@konsulko.com>
762 lines
31 KiB
Python
762 lines
31 KiB
Python
# 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 patman import gitutil
|
|
from u_boot_pylib import command
|
|
|
|
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
|
|
parents (bool): True to also make parent directories
|
|
|
|
Raises:
|
|
OSError: File already exists
|
|
"""
|
|
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):
|
|
print(f"Cannot create the current working directory '{dirname}'!")
|
|
sys.exit(1)
|
|
else:
|
|
raise
|
|
|
|
|
|
def _remove_old_outputs(out_dir):
|
|
"""Remove any old output-target files
|
|
|
|
Args:
|
|
out_dir (str): Output directory for the build
|
|
|
|
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, 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_pipe()
|
|
|
|
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
|
|
out_rel_dir (str): Output directory relative to the current dir
|
|
work_dir (str): Directory to which the source will be checked out
|
|
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 not self.builder.in_tree:
|
|
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.
|
|
real_dir = os.path.realpath(out_dir)
|
|
args.append(f'O={real_dir}')
|
|
cwd = None
|
|
src_dir = os.getcwd()
|
|
else:
|
|
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):
|
|
"""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
|
|
|
|
Returns:
|
|
CommandResult object
|
|
"""
|
|
if self.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')
|
|
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,
|
|
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
|
|
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
|
|
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.toolchain.MakeEnvironment(self.builder.full_path)
|
|
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, '.config')
|
|
cmd_list = []
|
|
if do_config or adjust_cfg:
|
|
result = self._reconfigure(
|
|
commit, brd, cwd, args, env, config_args, config_out, cmd_list)
|
|
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:
|
|
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, 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
|
|
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, 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
|
|
|
|
if result.toolchain:
|
|
# Write the build result and toolchain information.
|
|
done_file = self.builder.get_done_file(result.commit_upto,
|
|
result.brd.target)
|
|
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 = result.toolchain.MakeEnvironment(self.builder.full_path)
|
|
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_pipe([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_pipe([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_pipe([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.o', 'uboot.env']
|
|
command.run_pipe([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)
|
|
|
|
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.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:
|
|
# 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, 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.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()
|