blob: 2d54e62830234337374f4e5b6f4c4753352cb392 [file] [log] [blame]
Tom Rini83d290c2018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glass190064b2014-08-09 15:33:00 -06002# Copyright (c) 2014 Google, Inc
3#
Simon Glass190064b2014-08-09 15:33:00 -06004
Simon Glass606e5432023-07-19 17:49:09 -06005"""Implementation the bulider threads
6
7This module provides the BuilderThread class, which handles calling the builder
8based on the jobs provided.
9"""
10
Simon Glass190064b2014-08-09 15:33:00 -060011import errno
12import glob
Simon Glassdab3a4a2023-07-19 17:49:16 -060013import io
Simon Glass190064b2014-08-09 15:33:00 -060014import os
15import shutil
Lothar Waßmann409fc022018-04-08 05:14:11 -060016import sys
Simon Glass190064b2014-08-09 15:33:00 -060017import threading
18
Simon Glass2b4806e2022-01-22 05:07:33 -070019from buildman import cfgutil
Simon Glassbf776672020-04-17 18:09:04 -060020from patman import gitutil
Simon Glass4583c002023-02-23 18:18:04 -070021from u_boot_pylib import command
Simon Glass190064b2014-08-09 15:33:00 -060022
Simon Glass88c8dcf2015-02-05 22:06:13 -070023RETURN_CODE_RETRY = -1
Simon Glass73da3d22020-12-16 17:24:17 -070024BASE_ELF_FILENAMES = ['u-boot', 'spl/u-boot-spl', 'tpl/u-boot-tpl']
Simon Glass88c8dcf2015-02-05 22:06:13 -070025
Simon Glassf06d3332023-07-19 17:49:08 -060026def mkdir(dirname, parents = False):
Simon Glass190064b2014-08-09 15:33:00 -060027 """Make a directory if it doesn't already exist.
28
29 Args:
30 dirname: Directory to create
31 """
32 try:
Thierry Redingf3d015c2014-08-19 10:22:39 +020033 if parents:
34 os.makedirs(dirname)
35 else:
36 os.mkdir(dirname)
Simon Glass190064b2014-08-09 15:33:00 -060037 except OSError as err:
38 if err.errno == errno.EEXIST:
Lothar Waßmann409fc022018-04-08 05:14:11 -060039 if os.path.realpath('.') == os.path.realpath(dirname):
Simon Glass606e5432023-07-19 17:49:09 -060040 print(f"Cannot create the current working directory '{dirname}'!")
Lothar Waßmann409fc022018-04-08 05:14:11 -060041 sys.exit(1)
Simon Glass190064b2014-08-09 15:33:00 -060042 else:
43 raise
44
Simon Glass606e5432023-07-19 17:49:09 -060045# pylint: disable=R0903
Simon Glass190064b2014-08-09 15:33:00 -060046class BuilderJob:
47 """Holds information about a job to be performed by a thread
48
49 Members:
Simon Glassf4ed4702022-07-11 19:03:57 -060050 brd: Board object to build
Simon Glasse9fbbf62020-03-18 09:42:41 -060051 commits: List of Commit objects to build
52 keep_outputs: True to save build output files
53 step: 1 to process every commit, n to process every nth commit
Simon Glassd829f122020-03-18 09:42:42 -060054 work_in_output: Use the output directory as the work directory and
55 don't write to a separate output directory.
Simon Glass190064b2014-08-09 15:33:00 -060056 """
57 def __init__(self):
Simon Glassf4ed4702022-07-11 19:03:57 -060058 self.brd = None
Simon Glass190064b2014-08-09 15:33:00 -060059 self.commits = []
Simon Glasse9fbbf62020-03-18 09:42:41 -060060 self.keep_outputs = False
61 self.step = 1
Simon Glassd829f122020-03-18 09:42:42 -060062 self.work_in_output = False
Simon Glass190064b2014-08-09 15:33:00 -060063
64
65class ResultThread(threading.Thread):
66 """This thread processes results from builder threads.
67
68 It simply passes the results on to the builder. There is only one
69 result thread, and this helps to serialise the build output.
70 """
71 def __init__(self, builder):
72 """Set up a new result thread
73
74 Args:
75 builder: Builder which will be sent each result
76 """
77 threading.Thread.__init__(self)
78 self.builder = builder
79
80 def run(self):
81 """Called to start up the result thread.
82
83 We collect the next result job and pass it on to the build.
84 """
85 while True:
86 result = self.builder.out_queue.get()
Simon Glass37edf5f2023-07-19 17:49:06 -060087 self.builder.process_result(result)
Simon Glass190064b2014-08-09 15:33:00 -060088 self.builder.out_queue.task_done()
89
90
91class BuilderThread(threading.Thread):
92 """This thread builds U-Boot for a particular board.
93
94 An input queue provides each new job. We run 'make' to build U-Boot
95 and then pass the results on to the output queue.
96
97 Members:
98 builder: The builder which contains information we might need
99 thread_num: Our thread number (0-n-1), used to decide on a
Simon Glass24993312021-04-11 16:27:25 +1200100 temporary directory. If this is -1 then there are no threads
101 and we are the (only) main process
102 mrproper: Use 'make mrproper' before each reconfigure
103 per_board_out_dir: True to build in a separate persistent directory per
104 board rather than a thread-specific directory
105 test_exception: Used for testing; True to raise an exception instead of
106 reporting the build result
Simon Glass190064b2014-08-09 15:33:00 -0600107 """
Simon Glass8116c782021-04-11 16:27:27 +1200108 def __init__(self, builder, thread_num, mrproper, per_board_out_dir,
109 test_exception=False):
Simon Glass190064b2014-08-09 15:33:00 -0600110 """Set up a new builder thread"""
111 threading.Thread.__init__(self)
112 self.builder = builder
113 self.thread_num = thread_num
Simon Glasseb70a2c2020-04-09 15:08:51 -0600114 self.mrproper = mrproper
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600115 self.per_board_out_dir = per_board_out_dir
Simon Glass8116c782021-04-11 16:27:27 +1200116 self.test_exception = test_exception
Simon Glass606e5432023-07-19 17:49:09 -0600117 self.toolchain = None
Simon Glass190064b2014-08-09 15:33:00 -0600118
Simon Glassf06d3332023-07-19 17:49:08 -0600119 def make(self, commit, brd, stage, cwd, *args, **kwargs):
Simon Glass190064b2014-08-09 15:33:00 -0600120 """Run 'make' on a particular commit and board.
121
122 The source code will already be checked out, so the 'commit'
123 argument is only for information.
124
125 Args:
126 commit: Commit object that is being built
127 brd: Board object that is being built
128 stage: Stage of the build. Valid stages are:
Roger Meierfd18a892014-08-20 22:10:29 +0200129 mrproper - can be called to clean source
Simon Glass190064b2014-08-09 15:33:00 -0600130 config - called to configure for a board
131 build - the main make invocation - it does the build
132 args: A list of arguments to pass to 'make'
Simon Glassd9800692022-01-29 14:14:05 -0700133 kwargs: A list of keyword arguments to pass to command.run_pipe()
Simon Glass190064b2014-08-09 15:33:00 -0600134
135 Returns:
136 CommandResult object
137 """
138 return self.builder.do_make(commit, brd, stage, cwd, *args,
139 **kwargs)
140
Simon Glassed007bf2023-07-19 17:49:15 -0600141 def _build_args(self, brd, out_dir, out_rel_dir, work_dir, commit_upto):
Simon Glass75247002023-07-19 17:49:13 -0600142 """Set up arguments to the args list based on the settings
143
144 Args:
Simon Glassa06ed7f2023-07-19 17:49:14 -0600145 brd (Board): Board to create arguments for
Simon Glassed007bf2023-07-19 17:49:15 -0600146 out_dir (str): Path to output directory containing the files
147 out_rel_dir (str): Output directory relative to the current dir
148 work_dir (str): Directory to which the source will be checked out
149 commit_upto (int): Commit number to build (0...n-1)
150
151 Returns:
152 tuple:
153 list of str: Arguments to pass to make
154 str: Current working directory, or None if no commit
155 str: Source directory (typically the work directory)
Simon Glass75247002023-07-19 17:49:13 -0600156 """
Simon Glassed007bf2023-07-19 17:49:15 -0600157 args = []
158 cwd = work_dir
159 src_dir = os.path.realpath(work_dir)
160 if not self.builder.in_tree:
161 if commit_upto is None:
162 # In this case we are building in the original source directory
163 # (i.e. the current directory where buildman is invoked. The
164 # output directory is set to this thread's selected work
165 # directory.
166 #
167 # Symlinks can confuse U-Boot's Makefile since we may use '..'
168 # in our path, so remove them.
169 real_dir = os.path.realpath(out_dir)
170 args.append(f'O={real_dir}')
171 cwd = None
172 src_dir = os.getcwd()
173 else:
174 args.append(f'O={out_rel_dir}')
Simon Glass75247002023-07-19 17:49:13 -0600175 if self.builder.verbose_build:
176 args.append('V=1')
177 else:
178 args.append('-s')
179 if self.builder.num_jobs is not None:
180 args.extend(['-j', str(self.builder.num_jobs)])
181 if self.builder.warnings_as_errors:
182 args.append('KCFLAGS=-Werror')
183 args.append('HOSTCFLAGS=-Werror')
184 if self.builder.allow_missing:
185 args.append('BINMAN_ALLOW_MISSING=1')
186 if self.builder.no_lto:
187 args.append('NO_LTO=1')
188 if self.builder.reproducible_builds:
189 args.append('SOURCE_DATE_EPOCH=0')
Simon Glassa06ed7f2023-07-19 17:49:14 -0600190 args.extend(self.builder.toolchains.GetMakeArguments(brd))
191 args.extend(self.toolchain.MakeArgs())
Simon Glassed007bf2023-07-19 17:49:15 -0600192 return args, cwd, src_dir
Simon Glass75247002023-07-19 17:49:13 -0600193
Simon Glassec2f4922023-07-19 17:49:17 -0600194 def _reconfigure(self, commit, brd, cwd, args, env, config_args, config_out,
195 cmd_list):
196 """Reconfigure the build
197
198 Args:
199 commit (Commit): Commit only being built
200 brd (Board): Board being built
201 cwd (str): Current working directory
202 args (list of str): Arguments to pass to make
203 env (dict): Environment strings
204 config_args (list of str): defconfig arg for this board
205 cmd_list (list of str): List to add the commands to, for logging
206
207 Returns:
208 CommandResult object
209 """
210 if self.mrproper:
211 result = self.make(commit, brd, 'mrproper', cwd, 'mrproper', *args,
212 env=env)
213 config_out.write(result.combined)
214 cmd_list.append([self.builder.gnu_make, 'mrproper', *args])
215 result = self.make(commit, brd, 'config', cwd, *(args + config_args),
216 env=env)
217 cmd_list.append([self.builder.gnu_make] + args + config_args)
218 config_out.write(result.combined)
219 return result
220
Simon Glass14c15232023-07-19 17:49:18 -0600221 def _build(self, commit, brd, cwd, args, env, cmd_list, config_only):
222 """Perform the build
223
224 Args:
225 commit (Commit): Commit only being built
226 brd (Board): Board being built
227 cwd (str): Current working directory
228 args (list of str): Arguments to pass to make
229 env (dict): Environment strings
230 cmd_list (list of str): List to add the commands to, for logging
231 config_only (bool): True if this is a config-only build (using the
232 'make cfg' target)
233
234 Returns:
235 CommandResult object
236 """
237 if config_only:
238 args.append('cfg')
239 result = self.make(commit, brd, 'build', cwd, *args, env=env)
240 cmd_list.append([self.builder.gnu_make] + args)
241 if (result.return_code == 2 and
242 ('Some images are invalid' in result.stderr)):
243 # This is handled later by the check for output in stderr
244 result.return_code = 0
245 return result
246
Simon Glassf06d3332023-07-19 17:49:08 -0600247 def run_commit(self, commit_upto, brd, work_dir, do_config, config_only,
Simon Glass2b4806e2022-01-22 05:07:33 -0700248 force_build, force_build_failures, work_in_output,
249 adjust_cfg):
Simon Glass190064b2014-08-09 15:33:00 -0600250 """Build a particular commit.
251
252 If the build is already done, and we are not forcing a build, we skip
253 the build and just return the previously-saved results.
254
255 Args:
256 commit_upto: Commit number to build (0...n-1)
257 brd: Board object to build
258 work_dir: Directory to which the source will be checked out
259 do_config: True to run a make <board>_defconfig on the source
Simon Glassa9401b22016-11-16 14:09:25 -0700260 config_only: Only configure the source, do not build it
Simon Glass190064b2014-08-09 15:33:00 -0600261 force_build: Force a build even if one was previously done
262 force_build_failures: Force a bulid if the previous result showed
263 failure
Simon Glassd829f122020-03-18 09:42:42 -0600264 work_in_output: Use the output directory as the work directory and
265 don't write to a separate output directory.
Simon Glass2b4806e2022-01-22 05:07:33 -0700266 adjust_cfg (list of str): List of changes to make to .config file
267 before building. Each is one of (where C is either CONFIG_xxx
268 or just xxx):
269 C to enable C
270 ~C to disable C
271 C=val to set the value of C (val must have quotes if C is
272 a string Kconfig
Simon Glass190064b2014-08-09 15:33:00 -0600273
274 Returns:
275 tuple containing:
276 - CommandResult object containing the results of the build
277 - boolean indicating whether 'make config' is still needed
278 """
279 # Create a default result - it will be overwritte by the call to
Simon Glassf06d3332023-07-19 17:49:08 -0600280 # self.make() below, in the event that we do a build.
Simon Glass190064b2014-08-09 15:33:00 -0600281 result = command.CommandResult()
282 result.return_code = 0
Simon Glassd829f122020-03-18 09:42:42 -0600283 if work_in_output or self.builder.in_tree:
Simon Glassed007bf2023-07-19 17:49:15 -0600284 out_rel_dir = None
Simon Glass190064b2014-08-09 15:33:00 -0600285 out_dir = work_dir
286 else:
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600287 if self.per_board_out_dir:
288 out_rel_dir = os.path.join('..', brd.target)
289 else:
290 out_rel_dir = 'build'
291 out_dir = os.path.join(work_dir, out_rel_dir)
Simon Glass190064b2014-08-09 15:33:00 -0600292
293 # Check if the job was already completed last time
Simon Glass37edf5f2023-07-19 17:49:06 -0600294 done_file = self.builder.get_done_file(commit_upto, brd.target)
Simon Glass190064b2014-08-09 15:33:00 -0600295 result.already_done = os.path.exists(done_file)
296 will_build = (force_build or force_build_failures or
297 not result.already_done)
Simon Glassfb3954f2014-09-05 19:00:17 -0600298 if result.already_done:
Simon Glass190064b2014-08-09 15:33:00 -0600299 # Get the return code from that build and use it
Simon Glass606e5432023-07-19 17:49:09 -0600300 with open(done_file, 'r', encoding='utf-8') as outf:
Simon Glasse74429b2018-12-10 09:05:23 -0700301 try:
Simon Glass606e5432023-07-19 17:49:09 -0600302 result.return_code = int(outf.readline())
Simon Glasse74429b2018-12-10 09:05:23 -0700303 except ValueError:
304 # The file may be empty due to running out of disk space.
305 # Try a rebuild
306 result.return_code = RETURN_CODE_RETRY
Simon Glass88c8dcf2015-02-05 22:06:13 -0700307
308 # Check the signal that the build needs to be retried
309 if result.return_code == RETURN_CODE_RETRY:
310 will_build = True
311 elif will_build:
Simon Glass37edf5f2023-07-19 17:49:06 -0600312 err_file = self.builder.get_err_file(commit_upto, brd.target)
Simon Glassfb3954f2014-09-05 19:00:17 -0600313 if os.path.exists(err_file) and os.stat(err_file).st_size:
314 result.stderr = 'bad'
315 elif not force_build:
316 # The build passed, so no need to build it again
317 will_build = False
Simon Glass190064b2014-08-09 15:33:00 -0600318
319 if will_build:
320 # We are going to have to build it. First, get a toolchain
321 if not self.toolchain:
322 try:
323 self.toolchain = self.builder.toolchains.Select(brd.arch)
324 except ValueError as err:
325 result.return_code = 10
326 result.stdout = ''
327 result.stderr = str(err)
328 # TODO(sjg@chromium.org): This gets swallowed, but needs
329 # to be reported.
330
331 if self.toolchain:
332 # Checkout the right commit
333 if self.builder.commits:
334 commit = self.builder.commits[commit_upto]
335 if self.builder.checkout:
336 git_dir = os.path.join(work_dir, '.git')
Simon Glass0157b182022-01-29 14:14:11 -0700337 gitutil.checkout(commit.hash, git_dir, work_dir,
Simon Glass190064b2014-08-09 15:33:00 -0600338 force=True)
339 else:
340 commit = 'current'
341
342 # Set up the environment and command line
Simon Glassbb1501f2014-12-01 17:34:00 -0700343 env = self.toolchain.MakeEnvironment(self.builder.full_path)
Simon Glassf06d3332023-07-19 17:49:08 -0600344 mkdir(out_dir)
Simon Glassed007bf2023-07-19 17:49:15 -0600345
346 args, cwd, src_dir = self._build_args(brd, out_dir, out_rel_dir,
347 work_dir, commit_upto)
Simon Glass606e5432023-07-19 17:49:09 -0600348 config_args = [f'{brd.target}_defconfig']
Simon Glassdab3a4a2023-07-19 17:49:16 -0600349 config_out = io.StringIO()
Simon Glass190064b2014-08-09 15:33:00 -0600350
Simon Glass73da3d22020-12-16 17:24:17 -0700351 # Remove any output targets. Since we use a build directory that
352 # was previously used by another board, it may have produced an
353 # SPL image. If we don't remove it (i.e. see do_config and
354 # self.mrproper below) then it will appear to be the output of
355 # this build, even if it does not produce SPL images.
Simon Glass73da3d22020-12-16 17:24:17 -0700356 for elf in BASE_ELF_FILENAMES:
357 fname = os.path.join(out_dir, elf)
358 if os.path.exists(fname):
359 os.remove(fname)
360
Simon Glass190064b2014-08-09 15:33:00 -0600361 # If we need to reconfigure, do that now
Simon Glass2b4806e2022-01-22 05:07:33 -0700362 cfg_file = os.path.join(out_dir, '.config')
Simon Glasscd37d5b2023-02-21 12:40:27 -0700363 cmd_list = []
Simon Glass2b4806e2022-01-22 05:07:33 -0700364 if do_config or adjust_cfg:
Simon Glassec2f4922023-07-19 17:49:17 -0600365 result = self._reconfigure(
366 commit, brd, cwd, args, env, config_args, config_out,
367 cmd_list)
Simon Glass190064b2014-08-09 15:33:00 -0600368 do_config = False # No need to configure next time
Simon Glass2b4806e2022-01-22 05:07:33 -0700369 if adjust_cfg:
370 cfgutil.adjust_cfg_file(cfg_file, adjust_cfg)
Simon Glass14c15232023-07-19 17:49:18 -0600371
372 # Now do the build, if everything looks OK
Simon Glass190064b2014-08-09 15:33:00 -0600373 if result.return_code == 0:
Simon Glass14c15232023-07-19 17:49:18 -0600374 result = self._build(commit, brd, cwd, args, env, cmd_list,
375 config_only)
Simon Glass2b4806e2022-01-22 05:07:33 -0700376 if adjust_cfg:
377 errs = cfgutil.check_cfg_file(cfg_file, adjust_cfg)
378 if errs:
Simon Glass2b4806e2022-01-22 05:07:33 -0700379 result.stderr += errs
380 result.return_code = 1
Simon Glass48c1b6a2014-08-28 09:43:42 -0600381 result.stderr = result.stderr.replace(src_dir + '/', '')
Simon Glass40f11fc2015-02-05 22:06:12 -0700382 if self.builder.verbose_build:
Simon Glassdab3a4a2023-07-19 17:49:16 -0600383 result.stdout = config_out.getvalue() + result.stdout
Simon Glasscd37d5b2023-02-21 12:40:27 -0700384 result.cmd_list = cmd_list
Simon Glass190064b2014-08-09 15:33:00 -0600385 else:
386 result.return_code = 1
Simon Glass606e5432023-07-19 17:49:09 -0600387 result.stderr = f'No tool chain for {brd.arch}\n'
Simon Glass190064b2014-08-09 15:33:00 -0600388 result.already_done = False
389
390 result.toolchain = self.toolchain
391 result.brd = brd
392 result.commit_upto = commit_upto
393 result.out_dir = out_dir
394 return result, do_config
395
Simon Glassf06d3332023-07-19 17:49:08 -0600396 def _write_result(self, result, keep_outputs, work_in_output):
Simon Glass190064b2014-08-09 15:33:00 -0600397 """Write a built result to the output directory.
398
399 Args:
400 result: CommandResult object containing result to write
401 keep_outputs: True to store the output binaries, False
402 to delete them
Simon Glassd829f122020-03-18 09:42:42 -0600403 work_in_output: Use the output directory as the work directory and
404 don't write to a separate output directory.
Simon Glass190064b2014-08-09 15:33:00 -0600405 """
Simon Glass88c8dcf2015-02-05 22:06:13 -0700406 # If we think this might have been aborted with Ctrl-C, record the
407 # failure but not that we are 'done' with this board. A retry may fix
408 # it.
Simon Glassbafdeb42021-10-19 21:43:23 -0600409 maybe_aborted = result.stderr and 'No child processes' in result.stderr
Simon Glass190064b2014-08-09 15:33:00 -0600410
Simon Glassbafdeb42021-10-19 21:43:23 -0600411 if result.return_code >= 0 and result.already_done:
Simon Glass190064b2014-08-09 15:33:00 -0600412 return
413
414 # Write the output and stderr
Simon Glass4a7419b2023-07-19 17:49:10 -0600415 output_dir = self.builder.get_output_dir(result.commit_upto)
Simon Glassf06d3332023-07-19 17:49:08 -0600416 mkdir(output_dir)
Simon Glass37edf5f2023-07-19 17:49:06 -0600417 build_dir = self.builder.get_build_dir(result.commit_upto,
Simon Glass190064b2014-08-09 15:33:00 -0600418 result.brd.target)
Simon Glassf06d3332023-07-19 17:49:08 -0600419 mkdir(build_dir)
Simon Glass190064b2014-08-09 15:33:00 -0600420
421 outfile = os.path.join(build_dir, 'log')
Simon Glass606e5432023-07-19 17:49:09 -0600422 with open(outfile, 'w', encoding='utf-8') as outf:
Simon Glass190064b2014-08-09 15:33:00 -0600423 if result.stdout:
Simon Glass606e5432023-07-19 17:49:09 -0600424 outf.write(result.stdout)
Simon Glass190064b2014-08-09 15:33:00 -0600425
Simon Glass37edf5f2023-07-19 17:49:06 -0600426 errfile = self.builder.get_err_file(result.commit_upto,
Simon Glass190064b2014-08-09 15:33:00 -0600427 result.brd.target)
428 if result.stderr:
Simon Glass606e5432023-07-19 17:49:09 -0600429 with open(errfile, 'w', encoding='utf-8') as outf:
430 outf.write(result.stderr)
Simon Glass190064b2014-08-09 15:33:00 -0600431 elif os.path.exists(errfile):
432 os.remove(errfile)
433
Simon Glassbafdeb42021-10-19 21:43:23 -0600434 # Fatal error
435 if result.return_code < 0:
436 return
437
Simon Glass190064b2014-08-09 15:33:00 -0600438 if result.toolchain:
439 # Write the build result and toolchain information.
Simon Glass37edf5f2023-07-19 17:49:06 -0600440 done_file = self.builder.get_done_file(result.commit_upto,
Simon Glass190064b2014-08-09 15:33:00 -0600441 result.brd.target)
Simon Glass606e5432023-07-19 17:49:09 -0600442 with open(done_file, 'w', encoding='utf-8') as outf:
Simon Glass88c8dcf2015-02-05 22:06:13 -0700443 if maybe_aborted:
444 # Special code to indicate we need to retry
Simon Glass606e5432023-07-19 17:49:09 -0600445 outf.write(f'{RETURN_CODE_RETRY}')
Simon Glass88c8dcf2015-02-05 22:06:13 -0700446 else:
Simon Glass606e5432023-07-19 17:49:09 -0600447 outf.write(f'{result.return_code}')
448 with open(os.path.join(build_dir, 'toolchain'), 'w',
449 encoding='utf-8') as outf:
450 print('gcc', result.toolchain.gcc, file=outf)
451 print('path', result.toolchain.path, file=outf)
452 print('cross', result.toolchain.cross, file=outf)
453 print('arch', result.toolchain.arch, file=outf)
454 outf.write(f'{result.return_code}')
Simon Glass190064b2014-08-09 15:33:00 -0600455
Simon Glass190064b2014-08-09 15:33:00 -0600456 # Write out the image and function size information and an objdump
Simon Glassbb1501f2014-12-01 17:34:00 -0700457 env = result.toolchain.MakeEnvironment(self.builder.full_path)
Simon Glass606e5432023-07-19 17:49:09 -0600458 with open(os.path.join(build_dir, 'out-env'), 'wb') as outf:
Simon Glasse5fc79e2019-01-07 16:44:23 -0700459 for var in sorted(env.keys()):
Simon Glass606e5432023-07-19 17:49:09 -0600460 outf.write(b'%s="%s"' % (var, env[var]))
Simon Glasscd37d5b2023-02-21 12:40:27 -0700461
462 with open(os.path.join(build_dir, 'out-cmd'), 'w',
Simon Glass606e5432023-07-19 17:49:09 -0600463 encoding='utf-8') as outf:
Simon Glasscd37d5b2023-02-21 12:40:27 -0700464 for cmd in result.cmd_list:
Simon Glass606e5432023-07-19 17:49:09 -0600465 print(' '.join(cmd), file=outf)
Simon Glasscd37d5b2023-02-21 12:40:27 -0700466
Simon Glass190064b2014-08-09 15:33:00 -0600467 lines = []
Simon Glass73da3d22020-12-16 17:24:17 -0700468 for fname in BASE_ELF_FILENAMES:
Simon Glass606e5432023-07-19 17:49:09 -0600469 cmd = [f'{self.toolchain.cross}nm', '--size-sort', fname]
Simon Glassd9800692022-01-29 14:14:05 -0700470 nm_result = command.run_pipe([cmd], capture=True,
Simon Glass190064b2014-08-09 15:33:00 -0600471 capture_stderr=True, cwd=result.out_dir,
472 raise_on_error=False, env=env)
473 if nm_result.stdout:
Simon Glass606e5432023-07-19 17:49:09 -0600474 nm_fname = self.builder.get_func_sizes_file(
475 result.commit_upto, result.brd.target, fname)
476 with open(nm_fname, 'w', encoding='utf-8') as outf:
477 print(nm_result.stdout, end=' ', file=outf)
Simon Glass190064b2014-08-09 15:33:00 -0600478
Simon Glass606e5432023-07-19 17:49:09 -0600479 cmd = [f'{self.toolchain.cross}objdump', '-h', fname]
Simon Glassd9800692022-01-29 14:14:05 -0700480 dump_result = command.run_pipe([cmd], capture=True,
Simon Glass190064b2014-08-09 15:33:00 -0600481 capture_stderr=True, cwd=result.out_dir,
482 raise_on_error=False, env=env)
483 rodata_size = ''
484 if dump_result.stdout:
Simon Glass37edf5f2023-07-19 17:49:06 -0600485 objdump = self.builder.get_objdump_file(result.commit_upto,
Simon Glass190064b2014-08-09 15:33:00 -0600486 result.brd.target, fname)
Simon Glass606e5432023-07-19 17:49:09 -0600487 with open(objdump, 'w', encoding='utf-8') as outf:
488 print(dump_result.stdout, end=' ', file=outf)
Simon Glass190064b2014-08-09 15:33:00 -0600489 for line in dump_result.stdout.splitlines():
490 fields = line.split()
491 if len(fields) > 5 and fields[1] == '.rodata':
492 rodata_size = fields[2]
493
Simon Glass606e5432023-07-19 17:49:09 -0600494 cmd = [f'{self.toolchain.cross}size', fname]
Simon Glassd9800692022-01-29 14:14:05 -0700495 size_result = command.run_pipe([cmd], capture=True,
Simon Glass190064b2014-08-09 15:33:00 -0600496 capture_stderr=True, cwd=result.out_dir,
497 raise_on_error=False, env=env)
498 if size_result.stdout:
499 lines.append(size_result.stdout.splitlines()[1] + ' ' +
500 rodata_size)
501
Alex Kiernan0ddc5102018-05-31 04:48:33 +0000502 # Extract the environment from U-Boot and dump it out
Simon Glass606e5432023-07-19 17:49:09 -0600503 cmd = [f'{self.toolchain.cross}objcopy', '-O', 'binary',
Alex Kiernan0ddc5102018-05-31 04:48:33 +0000504 '-j', '.rodata.default_environment',
505 'env/built-in.o', 'uboot.env']
Simon Glassd9800692022-01-29 14:14:05 -0700506 command.run_pipe([cmd], capture=True,
Alex Kiernan0ddc5102018-05-31 04:48:33 +0000507 capture_stderr=True, cwd=result.out_dir,
508 raise_on_error=False, env=env)
Simon Glass60b285f2020-04-17 17:51:34 -0600509 if not work_in_output:
Simon Glassf06d3332023-07-19 17:49:08 -0600510 self.copy_files(result.out_dir, build_dir, '', ['uboot.env'])
Alex Kiernan0ddc5102018-05-31 04:48:33 +0000511
Simon Glass190064b2014-08-09 15:33:00 -0600512 # Write out the image sizes file. This is similar to the output
513 # of binutil's 'size' utility, but it omits the header line and
514 # adds an additional hex value at the end of each line for the
515 # rodata size
Simon Glass606e5432023-07-19 17:49:09 -0600516 if lines:
Simon Glass37edf5f2023-07-19 17:49:06 -0600517 sizes = self.builder.get_sizes_file(result.commit_upto,
Simon Glass190064b2014-08-09 15:33:00 -0600518 result.brd.target)
Simon Glass606e5432023-07-19 17:49:09 -0600519 with open(sizes, 'w', encoding='utf-8') as outf:
520 print('\n'.join(lines), file=outf)
Simon Glass190064b2014-08-09 15:33:00 -0600521
Simon Glass60b285f2020-04-17 17:51:34 -0600522 if not work_in_output:
523 # Write out the configuration files, with a special case for SPL
524 for dirname in ['', 'spl', 'tpl']:
Simon Glassf06d3332023-07-19 17:49:08 -0600525 self.copy_files(
Simon Glass60b285f2020-04-17 17:51:34 -0600526 result.out_dir, build_dir, dirname,
527 ['u-boot.cfg', 'spl/u-boot-spl.cfg', 'tpl/u-boot-tpl.cfg',
528 '.config', 'include/autoconf.mk',
529 'include/generated/autoconf.h'])
Simon Glass970f9322015-02-05 22:06:14 -0700530
Simon Glass60b285f2020-04-17 17:51:34 -0600531 # Now write the actual build output
532 if keep_outputs:
Simon Glassf06d3332023-07-19 17:49:08 -0600533 self.copy_files(
Simon Glass60b285f2020-04-17 17:51:34 -0600534 result.out_dir, build_dir, '',
535 ['u-boot*', '*.bin', '*.map', '*.img', 'MLO', 'SPL',
536 'include/autoconf.mk', 'spl/u-boot-spl*'])
Simon Glass190064b2014-08-09 15:33:00 -0600537
Simon Glassf06d3332023-07-19 17:49:08 -0600538 def copy_files(self, out_dir, build_dir, dirname, patterns):
Simon Glass970f9322015-02-05 22:06:14 -0700539 """Copy files from the build directory to the output.
540
541 Args:
542 out_dir: Path to output directory containing the files
543 build_dir: Place to copy the files
544 dirname: Source directory, '' for normal U-Boot, 'spl' for SPL
545 patterns: A list of filenames (strings) to copy, each relative
546 to the build directory
547 """
548 for pattern in patterns:
549 file_list = glob.glob(os.path.join(out_dir, dirname, pattern))
550 for fname in file_list:
551 target = os.path.basename(fname)
552 if dirname:
553 base, ext = os.path.splitext(target)
554 if ext:
Simon Glass606e5432023-07-19 17:49:09 -0600555 target = f'{base}-{dirname}{ext}'
Simon Glass970f9322015-02-05 22:06:14 -0700556 shutil.copy(fname, os.path.join(build_dir, target))
Simon Glass190064b2014-08-09 15:33:00 -0600557
Simon Glassf06d3332023-07-19 17:49:08 -0600558 def _send_result(self, result):
Simon Glassab9b4f32021-04-11 16:27:26 +1200559 """Send a result to the builder for processing
560
561 Args:
562 result: CommandResult object containing the results of the build
Simon Glass8116c782021-04-11 16:27:27 +1200563
564 Raises:
565 ValueError if self.test_exception is true (for testing)
Simon Glassab9b4f32021-04-11 16:27:26 +1200566 """
Simon Glass8116c782021-04-11 16:27:27 +1200567 if self.test_exception:
568 raise ValueError('test exception')
Simon Glassab9b4f32021-04-11 16:27:26 +1200569 if self.thread_num != -1:
570 self.builder.out_queue.put(result)
571 else:
Simon Glass37edf5f2023-07-19 17:49:06 -0600572 self.builder.process_result(result)
Simon Glassab9b4f32021-04-11 16:27:26 +1200573
Simon Glassf06d3332023-07-19 17:49:08 -0600574 def run_job(self, job):
Simon Glass190064b2014-08-09 15:33:00 -0600575 """Run a single job
576
577 A job consists of a building a list of commits for a particular board.
578
579 Args:
580 job: Job to build
Simon Glassb82492b2021-01-30 22:17:46 -0700581
582 Returns:
583 List of Result objects
Simon Glass190064b2014-08-09 15:33:00 -0600584 """
Simon Glassf4ed4702022-07-11 19:03:57 -0600585 brd = job.brd
Simon Glass37edf5f2023-07-19 17:49:06 -0600586 work_dir = self.builder.get_thread_dir(self.thread_num)
Simon Glass190064b2014-08-09 15:33:00 -0600587 self.toolchain = None
588 if job.commits:
589 # Run 'make board_defconfig' on the first commit
590 do_config = True
591 commit_upto = 0
592 force_build = False
593 for commit_upto in range(0, len(job.commits), job.step):
Simon Glassf06d3332023-07-19 17:49:08 -0600594 result, request_config = self.run_commit(commit_upto, brd,
Simon Glassa9401b22016-11-16 14:09:25 -0700595 work_dir, do_config, self.builder.config_only,
Simon Glass190064b2014-08-09 15:33:00 -0600596 force_build or self.builder.force_build,
Simon Glassd829f122020-03-18 09:42:42 -0600597 self.builder.force_build_failures,
Simon Glass2b4806e2022-01-22 05:07:33 -0700598 job.work_in_output, job.adjust_cfg)
Simon Glass190064b2014-08-09 15:33:00 -0600599 failed = result.return_code or result.stderr
600 did_config = do_config
601 if failed and not do_config:
602 # If our incremental build failed, try building again
603 # with a reconfig.
604 if self.builder.force_config_on_failure:
Simon Glassf06d3332023-07-19 17:49:08 -0600605 result, request_config = self.run_commit(commit_upto,
Simon Glassd829f122020-03-18 09:42:42 -0600606 brd, work_dir, True, False, True, False,
Simon Glass2b4806e2022-01-22 05:07:33 -0700607 job.work_in_output, job.adjust_cfg)
Simon Glass190064b2014-08-09 15:33:00 -0600608 did_config = True
609 if not self.builder.force_reconfig:
610 do_config = request_config
611
612 # If we built that commit, then config is done. But if we got
613 # an warning, reconfig next time to force it to build the same
614 # files that created warnings this time. Otherwise an
615 # incremental build may not build the same file, and we will
616 # think that the warning has gone away.
617 # We could avoid this by using -Werror everywhere...
618 # For errors, the problem doesn't happen, since presumably
619 # the build stopped and didn't generate output, so will retry
620 # that file next time. So we could detect warnings and deal
621 # with them specially here. For now, we just reconfigure if
622 # anything goes work.
623 # Of course this is substantially slower if there are build
624 # errors/warnings (e.g. 2-3x slower even if only 10% of builds
625 # have problems).
626 if (failed and not result.already_done and not did_config and
627 self.builder.force_config_on_failure):
628 # If this build failed, try the next one with a
629 # reconfigure.
630 # Sometimes if the board_config.h file changes it can mess
631 # with dependencies, and we get:
632 # make: *** No rule to make target `include/autoconf.mk',
633 # needed by `depend'.
634 do_config = True
635 force_build = True
636 else:
637 force_build = False
638 if self.builder.force_config_on_failure:
639 if failed:
640 do_config = True
641 result.commit_upto = commit_upto
642 if result.return_code < 0:
643 raise ValueError('Interrupt')
644
645 # We have the build results, so output the result
Simon Glassf06d3332023-07-19 17:49:08 -0600646 self._write_result(result, job.keep_outputs, job.work_in_output)
647 self._send_result(result)
Simon Glass190064b2014-08-09 15:33:00 -0600648 else:
649 # Just build the currently checked-out build
Simon Glassf06d3332023-07-19 17:49:08 -0600650 result, request_config = self.run_commit(None, brd, work_dir, True,
Simon Glassa9401b22016-11-16 14:09:25 -0700651 self.builder.config_only, True,
Simon Glass2b4806e2022-01-22 05:07:33 -0700652 self.builder.force_build_failures, job.work_in_output,
653 job.adjust_cfg)
Simon Glass190064b2014-08-09 15:33:00 -0600654 result.commit_upto = 0
Simon Glassf06d3332023-07-19 17:49:08 -0600655 self._write_result(result, job.keep_outputs, job.work_in_output)
656 self._send_result(result)
Simon Glass190064b2014-08-09 15:33:00 -0600657
658 def run(self):
659 """Our thread's run function
660
661 This thread picks a job from the queue, runs it, and then goes to the
662 next job.
663 """
Simon Glass190064b2014-08-09 15:33:00 -0600664 while True:
665 job = self.builder.queue.get()
Simon Glass8116c782021-04-11 16:27:27 +1200666 try:
Simon Glassf06d3332023-07-19 17:49:08 -0600667 self.run_job(job)
Simon Glass606e5432023-07-19 17:49:09 -0600668 except Exception as exc:
669 print('Thread exception (use -T0 to run without threads):',
670 exc)
671 self.builder.thread_exceptions.append(exc)
Simon Glass190064b2014-08-09 15:33:00 -0600672 self.builder.queue.task_done()