mirror of
				https://xff.cz/git/u-boot/
				synced 2025-10-31 18:35:42 +01:00 
			
		
		
		
	Sometimes it is useful to see the environment that was used to build U-Boot. Write this out to a file in the build directory. Signed-off-by: Simon Glass <sjg@chromium.org>
		
			
				
	
	
		
			503 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			503 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # SPDX-License-Identifier: GPL-2.0+
 | |
| # Copyright (c) 2014 Google, Inc
 | |
| #
 | |
| 
 | |
| import errno
 | |
| import glob
 | |
| import os
 | |
| import shutil
 | |
| import sys
 | |
| import threading
 | |
| 
 | |
| import command
 | |
| import gitutil
 | |
| 
 | |
| RETURN_CODE_RETRY = -1
 | |
| 
 | |
| def Mkdir(dirname, parents = False):
 | |
|     """Make a directory if it doesn't already exist.
 | |
| 
 | |
|     Args:
 | |
|         dirname: Directory to create
 | |
|     """
 | |
|     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 "Cannot create the current working directory '%s'!" % dirname
 | |
|                 sys.exit(1)
 | |
|             pass
 | |
|         else:
 | |
|             raise
 | |
| 
 | |
| class BuilderJob:
 | |
|     """Holds information about a job to be performed by a thread
 | |
| 
 | |
|     Members:
 | |
|         board: Board object to build
 | |
|         commits: List of commit options to build.
 | |
|     """
 | |
|     def __init__(self):
 | |
|         self.board = None
 | |
|         self.commits = []
 | |
| 
 | |
| 
 | |
| 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.ProcessResult(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
 | |
|     """
 | |
|     def __init__(self, builder, thread_num, incremental, per_board_out_dir):
 | |
|         """Set up a new builder thread"""
 | |
|         threading.Thread.__init__(self)
 | |
|         self.builder = builder
 | |
|         self.thread_num = thread_num
 | |
|         self.incremental = incremental
 | |
|         self.per_board_out_dir = per_board_out_dir
 | |
| 
 | |
|     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 object that is being built
 | |
|             brd: Board object that is being built
 | |
|             stage: 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
 | |
|             args: A list of arguments to pass to 'make'
 | |
|             kwargs: A list of keyword arguments to pass to command.RunPipe()
 | |
| 
 | |
|         Returns:
 | |
|             CommandResult object
 | |
|         """
 | |
|         return self.builder.do_make(commit, brd, stage, cwd, *args,
 | |
|                 **kwargs)
 | |
| 
 | |
|     def RunCommit(self, commit_upto, brd, work_dir, do_config, config_only,
 | |
|                   force_build, force_build_failures):
 | |
|         """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: Commit number to build (0...n-1)
 | |
|             brd: Board object to build
 | |
|             work_dir: Directory to which the source will be checked out
 | |
|             do_config: True to run a make <board>_defconfig on the source
 | |
|             config_only: Only configure the source, do not build it
 | |
|             force_build: Force a build even if one was previously done
 | |
|             force_build_failures: Force a bulid if the previous result showed
 | |
|                 failure
 | |
| 
 | |
|         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.
 | |
|         result = command.CommandResult()
 | |
|         result.return_code = 0
 | |
|         if self.builder.in_tree:
 | |
|             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)
 | |
| 
 | |
|         # Check if the job was already completed last time
 | |
|         done_file = self.builder.GetDoneFile(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:
 | |
|             # Get the return code from that build and use it
 | |
|             with open(done_file, 'r') as fd:
 | |
|                 try:
 | |
|                     result.return_code = int(fd.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.GetErrFile(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
 | |
| 
 | |
|         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 = str(err)
 | |
|                     # TODO(sjg@chromium.org): This gets swallowed, but needs
 | |
|                     # to be reported.
 | |
| 
 | |
|             if self.toolchain:
 | |
|                 # Checkout the right commit
 | |
|                 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'
 | |
| 
 | |
|                 # Set up the environment and command line
 | |
|                 env = self.toolchain.MakeEnvironment(self.builder.full_path)
 | |
|                 Mkdir(out_dir)
 | |
|                 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.
 | |
|                         out_dir = os.path.realpath(out_dir)
 | |
|                         args.append('O=%s' % out_dir)
 | |
|                         cwd = None
 | |
|                         src_dir = os.getcwd()
 | |
|                     else:
 | |
|                         args.append('O=%s' % 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')
 | |
|                 config_args = ['%s_defconfig' % brd.target]
 | |
|                 config_out = ''
 | |
|                 args.extend(self.builder.toolchains.GetMakeArguments(brd))
 | |
|                 args.extend(self.toolchain.MakeArgs())
 | |
| 
 | |
|                 # If we need to reconfigure, do that now
 | |
|                 if do_config:
 | |
|                     config_out = ''
 | |
|                     if not self.incremental:
 | |
|                         result = self.Make(commit, brd, 'mrproper', cwd,
 | |
|                                 'mrproper', *args, env=env)
 | |
|                         config_out += result.combined
 | |
|                     result = self.Make(commit, brd, 'config', cwd,
 | |
|                             *(args + config_args), env=env)
 | |
|                     config_out += result.combined
 | |
|                     do_config = False   # No need to configure next time
 | |
|                 if result.return_code == 0:
 | |
|                     if config_only:
 | |
|                         args.append('cfg')
 | |
|                     result = self.Make(commit, brd, 'build', cwd, *args,
 | |
|                             env=env)
 | |
|                 result.stderr = result.stderr.replace(src_dir + '/', '')
 | |
|                 if self.builder.verbose_build:
 | |
|                     result.stdout = config_out + result.stdout
 | |
|             else:
 | |
|                 result.return_code = 1
 | |
|                 result.stderr = 'No tool chain for %s\n' % brd.arch
 | |
|             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 _WriteResult(self, result, keep_outputs):
 | |
|         """Write a built result to the output directory.
 | |
| 
 | |
|         Args:
 | |
|             result: CommandResult object containing result to write
 | |
|             keep_outputs: True to store the output binaries, False
 | |
|                 to delete them
 | |
|         """
 | |
|         # Fatal error
 | |
|         if result.return_code < 0:
 | |
|             return
 | |
| 
 | |
|         # 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.already_done:
 | |
|             return
 | |
| 
 | |
|         # Write the output and stderr
 | |
|         output_dir = self.builder._GetOutputDir(result.commit_upto)
 | |
|         Mkdir(output_dir)
 | |
|         build_dir = self.builder.GetBuildDir(result.commit_upto,
 | |
|                 result.brd.target)
 | |
|         Mkdir(build_dir)
 | |
| 
 | |
|         outfile = os.path.join(build_dir, 'log')
 | |
|         with open(outfile, 'w') as fd:
 | |
|             if result.stdout:
 | |
|                 # We don't want unicode characters in log files
 | |
|                 fd.write(result.stdout.decode('UTF-8').encode('ASCII', 'replace'))
 | |
| 
 | |
|         errfile = self.builder.GetErrFile(result.commit_upto,
 | |
|                 result.brd.target)
 | |
|         if result.stderr:
 | |
|             with open(errfile, 'w') as fd:
 | |
|                 # We don't want unicode characters in log files
 | |
|                 fd.write(result.stderr.decode('UTF-8').encode('ASCII', 'replace'))
 | |
|         elif os.path.exists(errfile):
 | |
|             os.remove(errfile)
 | |
| 
 | |
|         if result.toolchain:
 | |
|             # Write the build result and toolchain information.
 | |
|             done_file = self.builder.GetDoneFile(result.commit_upto,
 | |
|                     result.brd.target)
 | |
|             with open(done_file, 'w') as fd:
 | |
|                 if maybe_aborted:
 | |
|                     # Special code to indicate we need to retry
 | |
|                     fd.write('%s' % RETURN_CODE_RETRY)
 | |
|                 else:
 | |
|                     fd.write('%s' % result.return_code)
 | |
|             with open(os.path.join(build_dir, 'toolchain'), 'w') as fd:
 | |
|                 print >>fd, 'gcc', result.toolchain.gcc
 | |
|                 print >>fd, 'path', result.toolchain.path
 | |
|                 print >>fd, 'cross', result.toolchain.cross
 | |
|                 print >>fd, 'arch', result.toolchain.arch
 | |
|                 fd.write('%s' % 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, 'env'), 'w') as fd:
 | |
|                 for var in sorted(env.keys()):
 | |
|                     print >>fd, '%s="%s"' % (var, env[var])
 | |
|             lines = []
 | |
|             for fname in ['u-boot', 'spl/u-boot-spl']:
 | |
|                 cmd = ['%snm' % self.toolchain.cross, '--size-sort', fname]
 | |
|                 nm_result = command.RunPipe([cmd], capture=True,
 | |
|                         capture_stderr=True, cwd=result.out_dir,
 | |
|                         raise_on_error=False, env=env)
 | |
|                 if nm_result.stdout:
 | |
|                     nm = self.builder.GetFuncSizesFile(result.commit_upto,
 | |
|                                     result.brd.target, fname)
 | |
|                     with open(nm, 'w') as fd:
 | |
|                         print >>fd, nm_result.stdout,
 | |
| 
 | |
|                 cmd = ['%sobjdump' % self.toolchain.cross, '-h', fname]
 | |
|                 dump_result = command.RunPipe([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.GetObjdumpFile(result.commit_upto,
 | |
|                                     result.brd.target, fname)
 | |
|                     with open(objdump, 'w') as fd:
 | |
|                         print >>fd, dump_result.stdout,
 | |
|                     for line in dump_result.stdout.splitlines():
 | |
|                         fields = line.split()
 | |
|                         if len(fields) > 5 and fields[1] == '.rodata':
 | |
|                             rodata_size = fields[2]
 | |
| 
 | |
|                 cmd = ['%ssize' % self.toolchain.cross, fname]
 | |
|                 size_result = command.RunPipe([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 = ['%sobjcopy' % self.toolchain.cross, '-O', 'binary',
 | |
|                    '-j', '.rodata.default_environment',
 | |
|                    'env/built-in.o', 'uboot.env']
 | |
|             command.RunPipe([cmd], capture=True,
 | |
|                             capture_stderr=True, cwd=result.out_dir,
 | |
|                             raise_on_error=False, env=env)
 | |
|             ubootenv = os.path.join(result.out_dir, 'uboot.env')
 | |
|             self.CopyFiles(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 len(lines):
 | |
|                 sizes = self.builder.GetSizesFile(result.commit_upto,
 | |
|                                 result.brd.target)
 | |
|                 with open(sizes, 'w') as fd:
 | |
|                     print >>fd, '\n'.join(lines)
 | |
| 
 | |
|         # Write out the configuration files, with a special case for SPL
 | |
|         for dirname in ['', 'spl', 'tpl']:
 | |
|             self.CopyFiles(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:
 | |
|             self.CopyFiles(result.out_dir, build_dir, '', ['u-boot*', '*.bin',
 | |
|                 '*.map', '*.img', 'MLO', 'SPL', 'include/autoconf.mk',
 | |
|                 'spl/u-boot-spl*'])
 | |
| 
 | |
|     def CopyFiles(self, out_dir, build_dir, dirname, patterns):
 | |
|         """Copy files from the build directory to the output.
 | |
| 
 | |
|         Args:
 | |
|             out_dir: Path to output directory containing the files
 | |
|             build_dir: Place to copy the files
 | |
|             dirname: Source directory, '' for normal U-Boot, 'spl' for SPL
 | |
|             patterns: A list of filenames (strings) 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 = '%s-%s%s' % (base, dirname, ext)
 | |
|                 shutil.copy(fname, os.path.join(build_dir, target))
 | |
| 
 | |
|     def RunJob(self, job):
 | |
|         """Run a single job
 | |
| 
 | |
|         A job consists of a building a list of commits for a particular board.
 | |
| 
 | |
|         Args:
 | |
|             job: Job to build
 | |
|         """
 | |
|         brd = job.board
 | |
|         work_dir = self.builder.GetThreadDir(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.RunCommit(commit_upto, brd,
 | |
|                         work_dir, do_config, self.builder.config_only,
 | |
|                         force_build or self.builder.force_build,
 | |
|                         self.builder.force_build_failures)
 | |
|                 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.RunCommit(commit_upto,
 | |
|                             brd, work_dir, True, False, True, False)
 | |
|                         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._WriteResult(result, job.keep_outputs)
 | |
|                 self.builder.out_queue.put(result)
 | |
|         else:
 | |
|             # Just build the currently checked-out build
 | |
|             result, request_config = self.RunCommit(None, brd, work_dir, True,
 | |
|                         self.builder.config_only, True,
 | |
|                         self.builder.force_build_failures)
 | |
|             result.commit_upto = 0
 | |
|             self._WriteResult(result, job.keep_outputs)
 | |
|             self.builder.out_queue.put(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()
 | |
|             self.RunJob(job)
 | |
|             self.builder.queue.task_done()
 |