mirror of
				https://github.com/smaeul/u-boot.git
				synced 2025-11-04 14:00:19 +00:00 
			
		
		
		
	For most boards, the device-tree compiler is built in-tree, ignoring the system version. Add a special option to skip this build. This can be useful when the system dtc is up-to-date, as it speeds up the build. Signed-off-by: Simon Glass <sjg@chromium.org>
		
			
				
	
	
		
			785 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			785 lines
		
	
	
		
			32 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, 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')
 | 
						|
        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
 | 
						|
            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)
 | 
						|
        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,
 | 
						|
                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
 | 
						|
 | 
						|
        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 = 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_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.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()
 |