blob: ed84a0f6baa35e290a066692ba5e7e409c6f4f99 [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 Glass236f9592023-07-19 17:49:26 -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:
Simon Glass236f9592023-07-19 17:49:26 -060030 dirname (str): Directory to create
31 parents (bool): True to also make parent directories
32
33 Raises:
34 OSError: File already exists
Simon Glass190064b2014-08-09 15:33:00 -060035 """
36 try:
Thierry Redingf3d015c2014-08-19 10:22:39 +020037 if parents:
38 os.makedirs(dirname)
39 else:
40 os.mkdir(dirname)
Simon Glass190064b2014-08-09 15:33:00 -060041 except OSError as err:
42 if err.errno == errno.EEXIST:
Lothar Waßmann409fc022018-04-08 05:14:11 -060043 if os.path.realpath('.') == os.path.realpath(dirname):
Simon Glass606e5432023-07-19 17:49:09 -060044 print(f"Cannot create the current working directory '{dirname}'!")
Lothar Waßmann409fc022018-04-08 05:14:11 -060045 sys.exit(1)
Simon Glass190064b2014-08-09 15:33:00 -060046 else:
47 raise
48
Simon Glasse5490b72023-07-19 17:49:20 -060049
50def _remove_old_outputs(out_dir):
51 """Remove any old output-target files
52
53 Args:
54 out_dir (str): Output directory for the build
55
56 Since we use a build directory that was previously used by another
57 board, it may have produced an SPL image. If we don't remove it (i.e.
58 see do_config and self.mrproper below) then it will appear to be the
59 output of this build, even if it does not produce SPL images.
60 """
61 for elf in BASE_ELF_FILENAMES:
62 fname = os.path.join(out_dir, elf)
63 if os.path.exists(fname):
64 os.remove(fname)
65
66
Simon Glass606e5432023-07-19 17:49:09 -060067# pylint: disable=R0903
Simon Glass190064b2014-08-09 15:33:00 -060068class BuilderJob:
69 """Holds information about a job to be performed by a thread
70
71 Members:
Simon Glassf4ed4702022-07-11 19:03:57 -060072 brd: Board object to build
Simon Glasse9fbbf62020-03-18 09:42:41 -060073 commits: List of Commit objects to build
74 keep_outputs: True to save build output files
75 step: 1 to process every commit, n to process every nth commit
Simon Glassd829f122020-03-18 09:42:42 -060076 work_in_output: Use the output directory as the work directory and
77 don't write to a separate output directory.
Simon Glass190064b2014-08-09 15:33:00 -060078 """
79 def __init__(self):
Simon Glassf4ed4702022-07-11 19:03:57 -060080 self.brd = None
Simon Glass190064b2014-08-09 15:33:00 -060081 self.commits = []
Simon Glasse9fbbf62020-03-18 09:42:41 -060082 self.keep_outputs = False
83 self.step = 1
Simon Glassd829f122020-03-18 09:42:42 -060084 self.work_in_output = False
Simon Glass190064b2014-08-09 15:33:00 -060085
86
87class ResultThread(threading.Thread):
88 """This thread processes results from builder threads.
89
90 It simply passes the results on to the builder. There is only one
91 result thread, and this helps to serialise the build output.
92 """
93 def __init__(self, builder):
94 """Set up a new result thread
95
96 Args:
97 builder: Builder which will be sent each result
98 """
99 threading.Thread.__init__(self)
100 self.builder = builder
101
102 def run(self):
103 """Called to start up the result thread.
104
105 We collect the next result job and pass it on to the build.
106 """
107 while True:
108 result = self.builder.out_queue.get()
Simon Glass37edf5f2023-07-19 17:49:06 -0600109 self.builder.process_result(result)
Simon Glass190064b2014-08-09 15:33:00 -0600110 self.builder.out_queue.task_done()
111
112
113class BuilderThread(threading.Thread):
114 """This thread builds U-Boot for a particular board.
115
116 An input queue provides each new job. We run 'make' to build U-Boot
117 and then pass the results on to the output queue.
118
119 Members:
120 builder: The builder which contains information we might need
121 thread_num: Our thread number (0-n-1), used to decide on a
Simon Glass24993312021-04-11 16:27:25 +1200122 temporary directory. If this is -1 then there are no threads
123 and we are the (only) main process
124 mrproper: Use 'make mrproper' before each reconfigure
125 per_board_out_dir: True to build in a separate persistent directory per
126 board rather than a thread-specific directory
127 test_exception: Used for testing; True to raise an exception instead of
128 reporting the build result
Simon Glass190064b2014-08-09 15:33:00 -0600129 """
Simon Glass8116c782021-04-11 16:27:27 +1200130 def __init__(self, builder, thread_num, mrproper, per_board_out_dir,
131 test_exception=False):
Simon Glass190064b2014-08-09 15:33:00 -0600132 """Set up a new builder thread"""
133 threading.Thread.__init__(self)
134 self.builder = builder
135 self.thread_num = thread_num
Simon Glasseb70a2c2020-04-09 15:08:51 -0600136 self.mrproper = mrproper
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600137 self.per_board_out_dir = per_board_out_dir
Simon Glass8116c782021-04-11 16:27:27 +1200138 self.test_exception = test_exception
Simon Glass606e5432023-07-19 17:49:09 -0600139 self.toolchain = None
Simon Glass190064b2014-08-09 15:33:00 -0600140
Simon Glassf06d3332023-07-19 17:49:08 -0600141 def make(self, commit, brd, stage, cwd, *args, **kwargs):
Simon Glass190064b2014-08-09 15:33:00 -0600142 """Run 'make' on a particular commit and board.
143
144 The source code will already be checked out, so the 'commit'
145 argument is only for information.
146
147 Args:
Simon Glass236f9592023-07-19 17:49:26 -0600148 commit (Commit): Commit that is being built
149 brd (Board): Board that is being built
150 stage (str): Stage of the build. Valid stages are:
Roger Meierfd18a892014-08-20 22:10:29 +0200151 mrproper - can be called to clean source
Simon Glass190064b2014-08-09 15:33:00 -0600152 config - called to configure for a board
153 build - the main make invocation - it does the build
Simon Glass236f9592023-07-19 17:49:26 -0600154 cwd (str): Working directory to set, or None to leave it alone
155 *args (list of str): Arguments to pass to 'make'
156 **kwargs (dict): A list of keyword arguments to pass to
157 command.run_pipe()
Simon Glass190064b2014-08-09 15:33:00 -0600158
159 Returns:
160 CommandResult object
161 """
162 return self.builder.do_make(commit, brd, stage, cwd, *args,
163 **kwargs)
164
Simon Glassed007bf2023-07-19 17:49:15 -0600165 def _build_args(self, brd, out_dir, out_rel_dir, work_dir, commit_upto):
Simon Glass75247002023-07-19 17:49:13 -0600166 """Set up arguments to the args list based on the settings
167
168 Args:
Simon Glassa06ed7f2023-07-19 17:49:14 -0600169 brd (Board): Board to create arguments for
Simon Glassed007bf2023-07-19 17:49:15 -0600170 out_dir (str): Path to output directory containing the files
171 out_rel_dir (str): Output directory relative to the current dir
172 work_dir (str): Directory to which the source will be checked out
173 commit_upto (int): Commit number to build (0...n-1)
174
175 Returns:
176 tuple:
177 list of str: Arguments to pass to make
178 str: Current working directory, or None if no commit
179 str: Source directory (typically the work directory)
Simon Glass75247002023-07-19 17:49:13 -0600180 """
Simon Glassed007bf2023-07-19 17:49:15 -0600181 args = []
182 cwd = work_dir
183 src_dir = os.path.realpath(work_dir)
184 if not self.builder.in_tree:
185 if commit_upto is None:
186 # In this case we are building in the original source directory
187 # (i.e. the current directory where buildman is invoked. The
188 # output directory is set to this thread's selected work
189 # directory.
190 #
191 # Symlinks can confuse U-Boot's Makefile since we may use '..'
192 # in our path, so remove them.
193 real_dir = os.path.realpath(out_dir)
194 args.append(f'O={real_dir}')
195 cwd = None
196 src_dir = os.getcwd()
197 else:
198 args.append(f'O={out_rel_dir}')
Simon Glass75247002023-07-19 17:49:13 -0600199 if self.builder.verbose_build:
200 args.append('V=1')
201 else:
202 args.append('-s')
203 if self.builder.num_jobs is not None:
204 args.extend(['-j', str(self.builder.num_jobs)])
205 if self.builder.warnings_as_errors:
206 args.append('KCFLAGS=-Werror')
207 args.append('HOSTCFLAGS=-Werror')
208 if self.builder.allow_missing:
209 args.append('BINMAN_ALLOW_MISSING=1')
210 if self.builder.no_lto:
211 args.append('NO_LTO=1')
212 if self.builder.reproducible_builds:
213 args.append('SOURCE_DATE_EPOCH=0')
Simon Glassa06ed7f2023-07-19 17:49:14 -0600214 args.extend(self.builder.toolchains.GetMakeArguments(brd))
215 args.extend(self.toolchain.MakeArgs())
Simon Glassed007bf2023-07-19 17:49:15 -0600216 return args, cwd, src_dir
Simon Glass75247002023-07-19 17:49:13 -0600217
Simon Glassec2f4922023-07-19 17:49:17 -0600218 def _reconfigure(self, commit, brd, cwd, args, env, config_args, config_out,
219 cmd_list):
220 """Reconfigure the build
221
222 Args:
223 commit (Commit): Commit only being built
224 brd (Board): Board being built
225 cwd (str): Current working directory
226 args (list of str): Arguments to pass to make
227 env (dict): Environment strings
228 config_args (list of str): defconfig arg for this board
229 cmd_list (list of str): List to add the commands to, for logging
230
231 Returns:
232 CommandResult object
233 """
234 if self.mrproper:
235 result = self.make(commit, brd, 'mrproper', cwd, 'mrproper', *args,
236 env=env)
237 config_out.write(result.combined)
238 cmd_list.append([self.builder.gnu_make, 'mrproper', *args])
239 result = self.make(commit, brd, 'config', cwd, *(args + config_args),
240 env=env)
241 cmd_list.append([self.builder.gnu_make] + args + config_args)
242 config_out.write(result.combined)
243 return result
244
Simon Glass14c15232023-07-19 17:49:18 -0600245 def _build(self, commit, brd, cwd, args, env, cmd_list, config_only):
246 """Perform the build
247
248 Args:
249 commit (Commit): Commit only being built
250 brd (Board): Board being built
251 cwd (str): Current working directory
252 args (list of str): Arguments to pass to make
253 env (dict): Environment strings
254 cmd_list (list of str): List to add the commands to, for logging
255 config_only (bool): True if this is a config-only build (using the
256 'make cfg' target)
257
258 Returns:
259 CommandResult object
260 """
261 if config_only:
262 args.append('cfg')
263 result = self.make(commit, brd, 'build', cwd, *args, env=env)
264 cmd_list.append([self.builder.gnu_make] + args)
265 if (result.return_code == 2 and
266 ('Some images are invalid' in result.stderr)):
267 # This is handled later by the check for output in stderr
268 result.return_code = 0
269 return result
270
Simon Glass5e5044b2023-07-19 17:49:24 -0600271 def _read_done_file(self, commit_upto, brd, force_build,
Simon Glass4981bd32023-07-19 17:49:19 -0600272 force_build_failures):
273 """Check the 'done' file and see if this commit should be built
274
275 Args:
276 commit (Commit): Commit only being built
277 brd (Board): Board being built
Simon Glass4981bd32023-07-19 17:49:19 -0600278 force_build (bool): Force a build even if one was previously done
279 force_build_failures (bool): Force a bulid if the previous result
280 showed failure
281
282 Returns:
Simon Glass5e5044b2023-07-19 17:49:24 -0600283 tuple:
284 bool: True if build should be built
285 CommandResult: if there was a previous run:
286 - already_done set to True
287 - return_code set to return code
288 - result.stderr set to 'bad' if stderr output was recorded
Simon Glass4981bd32023-07-19 17:49:19 -0600289 """
Simon Glass5e5044b2023-07-19 17:49:24 -0600290 result = command.CommandResult()
Simon Glass4981bd32023-07-19 17:49:19 -0600291 done_file = self.builder.get_done_file(commit_upto, brd.target)
292 result.already_done = os.path.exists(done_file)
293 will_build = (force_build or force_build_failures or
294 not result.already_done)
295 if result.already_done:
296 with open(done_file, 'r', encoding='utf-8') as outf:
297 try:
298 result.return_code = int(outf.readline())
299 except ValueError:
300 # The file may be empty due to running out of disk space.
301 # Try a rebuild
302 result.return_code = RETURN_CODE_RETRY
303
304 # Check the signal that the build needs to be retried
305 if result.return_code == RETURN_CODE_RETRY:
306 will_build = True
307 elif will_build:
308 err_file = self.builder.get_err_file(commit_upto, brd.target)
309 if os.path.exists(err_file) and os.stat(err_file).st_size:
310 result.stderr = 'bad'
311 elif not force_build:
312 # The build passed, so no need to build it again
313 will_build = False
Simon Glass5e5044b2023-07-19 17:49:24 -0600314 return will_build, result
Simon Glass4981bd32023-07-19 17:49:19 -0600315
Simon Glass9bdf0232023-07-19 17:49:21 -0600316 def _decide_dirs(self, brd, work_dir, work_in_output):
317 """Decide the output directory to use
318
319 Args:
320 work_dir (str): Directory to which the source will be checked out
321 work_in_output (bool): Use the output directory as the work
322 directory and don't write to a separate output directory.
323
324 Returns:
325 tuple:
326 out_dir (str): Output directory for the build
327 out_rel_dir (str): Output directory relatie to the current dir
328 """
329 if work_in_output or self.builder.in_tree:
330 out_rel_dir = None
331 out_dir = work_dir
332 else:
333 if self.per_board_out_dir:
334 out_rel_dir = os.path.join('..', brd.target)
335 else:
336 out_rel_dir = 'build'
337 out_dir = os.path.join(work_dir, out_rel_dir)
338 return out_dir, out_rel_dir
339
Simon Glassad7181c2023-07-19 17:49:22 -0600340 def _checkout(self, commit_upto, work_dir):
341 """Checkout the right commit
342
343 Args:
344 commit_upto (int): Commit number to build (0...n-1)
345 work_dir (str): Directory to which the source will be checked out
346
347 Returns:
348 Commit: Commit being built, or 'current' for current source
349 """
350 if self.builder.commits:
351 commit = self.builder.commits[commit_upto]
352 if self.builder.checkout:
353 git_dir = os.path.join(work_dir, '.git')
354 gitutil.checkout(commit.hash, git_dir, work_dir, force=True)
355 else:
356 commit = 'current'
357 return commit
358
Simon Glass335c1b92023-07-19 17:49:23 -0600359 def _config_and_build(self, commit_upto, brd, work_dir, do_config,
360 config_only, adjust_cfg, commit, out_dir, out_rel_dir,
361 result):
362 """Do the build, configuring first if necessary
363
364 Args:
365 commit_upto (int): Commit number to build (0...n-1)
366 brd (Board): Board to create arguments for
367 work_dir (str): Directory to which the source will be checked out
368 do_config (bool): True to run a make <board>_defconfig on the source
369 config_only (bool): Only configure the source, do not build it
370 adjust_cfg (list of str): See the cfgutil module and run_commit()
371 commit (Commit): Commit only being built
372 out_dir (str): Output directory for the build
373 out_rel_dir (str): Output directory relatie to the current dir
374 result (CommandResult): Previous result
375
376 Returns:
377 tuple:
378 result (CommandResult): Result of the build
379 do_config (bool): indicates whether 'make config' is needed on
380 the next incremental build
381 """
382 # Set up the environment and command line
383 env = self.toolchain.MakeEnvironment(self.builder.full_path)
384 mkdir(out_dir)
385
386 args, cwd, src_dir = self._build_args(brd, out_dir, out_rel_dir,
387 work_dir, commit_upto)
388 config_args = [f'{brd.target}_defconfig']
389 config_out = io.StringIO()
390
391 _remove_old_outputs(out_dir)
392
393 # If we need to reconfigure, do that now
394 cfg_file = os.path.join(out_dir, '.config')
395 cmd_list = []
396 if do_config or adjust_cfg:
397 result = self._reconfigure(
398 commit, brd, cwd, args, env, config_args, config_out, cmd_list)
399 do_config = False # No need to configure next time
400 if adjust_cfg:
401 cfgutil.adjust_cfg_file(cfg_file, adjust_cfg)
402
403 # Now do the build, if everything looks OK
404 if result.return_code == 0:
405 result = self._build(commit, brd, cwd, args, env, cmd_list,
406 config_only)
407 if adjust_cfg:
408 errs = cfgutil.check_cfg_file(cfg_file, adjust_cfg)
409 if errs:
410 result.stderr += errs
411 result.return_code = 1
412 result.stderr = result.stderr.replace(src_dir + '/', '')
413 if self.builder.verbose_build:
414 result.stdout = config_out.getvalue() + result.stdout
415 result.cmd_list = cmd_list
416 return result, do_config
Simon Glassad7181c2023-07-19 17:49:22 -0600417
Simon Glassf06d3332023-07-19 17:49:08 -0600418 def run_commit(self, commit_upto, brd, work_dir, do_config, config_only,
Simon Glass2b4806e2022-01-22 05:07:33 -0700419 force_build, force_build_failures, work_in_output,
420 adjust_cfg):
Simon Glass190064b2014-08-09 15:33:00 -0600421 """Build a particular commit.
422
423 If the build is already done, and we are not forcing a build, we skip
424 the build and just return the previously-saved results.
425
426 Args:
Simon Glass236f9592023-07-19 17:49:26 -0600427 commit_upto (int): Commit number to build (0...n-1)
428 brd (Board): Board to build
429 work_dir (str): Directory to which the source will be checked out
430 do_config (bool): True to run a make <board>_defconfig on the source
431 config_only (bool): Only configure the source, do not build it
432 force_build (bool): Force a build even if one was previously done
433 force_build_failures (bool): Force a bulid if the previous result
434 showed failure
435 work_in_output (bool) : Use the output directory as the work
436 directory and don't write to a separate output directory.
Simon Glass2b4806e2022-01-22 05:07:33 -0700437 adjust_cfg (list of str): List of changes to make to .config file
438 before building. Each is one of (where C is either CONFIG_xxx
439 or just xxx):
440 C to enable C
441 ~C to disable C
442 C=val to set the value of C (val must have quotes if C is
443 a string Kconfig
Simon Glass190064b2014-08-09 15:33:00 -0600444
445 Returns:
446 tuple containing:
447 - CommandResult object containing the results of the build
448 - boolean indicating whether 'make config' is still needed
449 """
450 # Create a default result - it will be overwritte by the call to
Simon Glassf06d3332023-07-19 17:49:08 -0600451 # self.make() below, in the event that we do a build.
Simon Glass9bdf0232023-07-19 17:49:21 -0600452 out_dir, out_rel_dir = self._decide_dirs(brd, work_dir, work_in_output)
Simon Glass190064b2014-08-09 15:33:00 -0600453
454 # Check if the job was already completed last time
Simon Glass5e5044b2023-07-19 17:49:24 -0600455 will_build, result = self._read_done_file(commit_upto, brd, force_build,
456 force_build_failures)
Simon Glass190064b2014-08-09 15:33:00 -0600457
458 if will_build:
459 # We are going to have to build it. First, get a toolchain
460 if not self.toolchain:
461 try:
462 self.toolchain = self.builder.toolchains.Select(brd.arch)
463 except ValueError as err:
464 result.return_code = 10
465 result.stdout = ''
Simon Glass57686d32023-07-19 17:49:25 -0600466 result.stderr = f'Tool chain error for {brd.arch}: {str(err)}'
Simon Glass190064b2014-08-09 15:33:00 -0600467
468 if self.toolchain:
Simon Glassad7181c2023-07-19 17:49:22 -0600469 commit = self._checkout(commit_upto, work_dir)
Simon Glass335c1b92023-07-19 17:49:23 -0600470 result, do_config = self._config_and_build(
471 commit_upto, brd, work_dir, do_config, config_only,
472 adjust_cfg, commit, out_dir, out_rel_dir, result)
Simon Glass190064b2014-08-09 15:33:00 -0600473 result.already_done = False
474
475 result.toolchain = self.toolchain
476 result.brd = brd
477 result.commit_upto = commit_upto
478 result.out_dir = out_dir
479 return result, do_config
480
Simon Glassf06d3332023-07-19 17:49:08 -0600481 def _write_result(self, result, keep_outputs, work_in_output):
Simon Glass190064b2014-08-09 15:33:00 -0600482 """Write a built result to the output directory.
483
484 Args:
Simon Glass236f9592023-07-19 17:49:26 -0600485 result (CommandResult): result to write
486 keep_outputs (bool): True to store the output binaries, False
Simon Glass190064b2014-08-09 15:33:00 -0600487 to delete them
Simon Glass236f9592023-07-19 17:49:26 -0600488 work_in_output (bool): Use the output directory as the work
489 directory and don't write to a separate output directory.
Simon Glass190064b2014-08-09 15:33:00 -0600490 """
Simon Glass88c8dcf2015-02-05 22:06:13 -0700491 # If we think this might have been aborted with Ctrl-C, record the
492 # failure but not that we are 'done' with this board. A retry may fix
493 # it.
Simon Glassbafdeb42021-10-19 21:43:23 -0600494 maybe_aborted = result.stderr and 'No child processes' in result.stderr
Simon Glass190064b2014-08-09 15:33:00 -0600495
Simon Glassbafdeb42021-10-19 21:43:23 -0600496 if result.return_code >= 0 and result.already_done:
Simon Glass190064b2014-08-09 15:33:00 -0600497 return
498
499 # Write the output and stderr
Simon Glass4a7419b2023-07-19 17:49:10 -0600500 output_dir = self.builder.get_output_dir(result.commit_upto)
Simon Glassf06d3332023-07-19 17:49:08 -0600501 mkdir(output_dir)
Simon Glass37edf5f2023-07-19 17:49:06 -0600502 build_dir = self.builder.get_build_dir(result.commit_upto,
Simon Glass190064b2014-08-09 15:33:00 -0600503 result.brd.target)
Simon Glassf06d3332023-07-19 17:49:08 -0600504 mkdir(build_dir)
Simon Glass190064b2014-08-09 15:33:00 -0600505
506 outfile = os.path.join(build_dir, 'log')
Simon Glass606e5432023-07-19 17:49:09 -0600507 with open(outfile, 'w', encoding='utf-8') as outf:
Simon Glass190064b2014-08-09 15:33:00 -0600508 if result.stdout:
Simon Glass606e5432023-07-19 17:49:09 -0600509 outf.write(result.stdout)
Simon Glass190064b2014-08-09 15:33:00 -0600510
Simon Glass37edf5f2023-07-19 17:49:06 -0600511 errfile = self.builder.get_err_file(result.commit_upto,
Simon Glass190064b2014-08-09 15:33:00 -0600512 result.brd.target)
513 if result.stderr:
Simon Glass606e5432023-07-19 17:49:09 -0600514 with open(errfile, 'w', encoding='utf-8') as outf:
515 outf.write(result.stderr)
Simon Glass190064b2014-08-09 15:33:00 -0600516 elif os.path.exists(errfile):
517 os.remove(errfile)
518
Simon Glassbafdeb42021-10-19 21:43:23 -0600519 # Fatal error
520 if result.return_code < 0:
521 return
522
Simon Glass190064b2014-08-09 15:33:00 -0600523 if result.toolchain:
524 # Write the build result and toolchain information.
Simon Glass37edf5f2023-07-19 17:49:06 -0600525 done_file = self.builder.get_done_file(result.commit_upto,
Simon Glass190064b2014-08-09 15:33:00 -0600526 result.brd.target)
Simon Glass606e5432023-07-19 17:49:09 -0600527 with open(done_file, 'w', encoding='utf-8') as outf:
Simon Glass88c8dcf2015-02-05 22:06:13 -0700528 if maybe_aborted:
529 # Special code to indicate we need to retry
Simon Glass606e5432023-07-19 17:49:09 -0600530 outf.write(f'{RETURN_CODE_RETRY}')
Simon Glass88c8dcf2015-02-05 22:06:13 -0700531 else:
Simon Glass606e5432023-07-19 17:49:09 -0600532 outf.write(f'{result.return_code}')
533 with open(os.path.join(build_dir, 'toolchain'), 'w',
534 encoding='utf-8') as outf:
535 print('gcc', result.toolchain.gcc, file=outf)
536 print('path', result.toolchain.path, file=outf)
537 print('cross', result.toolchain.cross, file=outf)
538 print('arch', result.toolchain.arch, file=outf)
539 outf.write(f'{result.return_code}')
Simon Glass190064b2014-08-09 15:33:00 -0600540
Simon Glass190064b2014-08-09 15:33:00 -0600541 # Write out the image and function size information and an objdump
Simon Glassbb1501f2014-12-01 17:34:00 -0700542 env = result.toolchain.MakeEnvironment(self.builder.full_path)
Simon Glass606e5432023-07-19 17:49:09 -0600543 with open(os.path.join(build_dir, 'out-env'), 'wb') as outf:
Simon Glasse5fc79e2019-01-07 16:44:23 -0700544 for var in sorted(env.keys()):
Simon Glass606e5432023-07-19 17:49:09 -0600545 outf.write(b'%s="%s"' % (var, env[var]))
Simon Glasscd37d5b2023-02-21 12:40:27 -0700546
547 with open(os.path.join(build_dir, 'out-cmd'), 'w',
Simon Glass606e5432023-07-19 17:49:09 -0600548 encoding='utf-8') as outf:
Simon Glasscd37d5b2023-02-21 12:40:27 -0700549 for cmd in result.cmd_list:
Simon Glass606e5432023-07-19 17:49:09 -0600550 print(' '.join(cmd), file=outf)
Simon Glasscd37d5b2023-02-21 12:40:27 -0700551
Simon Glass190064b2014-08-09 15:33:00 -0600552 lines = []
Simon Glass73da3d22020-12-16 17:24:17 -0700553 for fname in BASE_ELF_FILENAMES:
Simon Glass606e5432023-07-19 17:49:09 -0600554 cmd = [f'{self.toolchain.cross}nm', '--size-sort', fname]
Simon Glassd9800692022-01-29 14:14:05 -0700555 nm_result = command.run_pipe([cmd], capture=True,
Simon Glass190064b2014-08-09 15:33:00 -0600556 capture_stderr=True, cwd=result.out_dir,
557 raise_on_error=False, env=env)
558 if nm_result.stdout:
Simon Glass606e5432023-07-19 17:49:09 -0600559 nm_fname = self.builder.get_func_sizes_file(
560 result.commit_upto, result.brd.target, fname)
561 with open(nm_fname, 'w', encoding='utf-8') as outf:
562 print(nm_result.stdout, end=' ', file=outf)
Simon Glass190064b2014-08-09 15:33:00 -0600563
Simon Glass606e5432023-07-19 17:49:09 -0600564 cmd = [f'{self.toolchain.cross}objdump', '-h', fname]
Simon Glassd9800692022-01-29 14:14:05 -0700565 dump_result = command.run_pipe([cmd], capture=True,
Simon Glass190064b2014-08-09 15:33:00 -0600566 capture_stderr=True, cwd=result.out_dir,
567 raise_on_error=False, env=env)
568 rodata_size = ''
569 if dump_result.stdout:
Simon Glass37edf5f2023-07-19 17:49:06 -0600570 objdump = self.builder.get_objdump_file(result.commit_upto,
Simon Glass190064b2014-08-09 15:33:00 -0600571 result.brd.target, fname)
Simon Glass606e5432023-07-19 17:49:09 -0600572 with open(objdump, 'w', encoding='utf-8') as outf:
573 print(dump_result.stdout, end=' ', file=outf)
Simon Glass190064b2014-08-09 15:33:00 -0600574 for line in dump_result.stdout.splitlines():
575 fields = line.split()
576 if len(fields) > 5 and fields[1] == '.rodata':
577 rodata_size = fields[2]
578
Simon Glass606e5432023-07-19 17:49:09 -0600579 cmd = [f'{self.toolchain.cross}size', fname]
Simon Glassd9800692022-01-29 14:14:05 -0700580 size_result = command.run_pipe([cmd], capture=True,
Simon Glass190064b2014-08-09 15:33:00 -0600581 capture_stderr=True, cwd=result.out_dir,
582 raise_on_error=False, env=env)
583 if size_result.stdout:
584 lines.append(size_result.stdout.splitlines()[1] + ' ' +
585 rodata_size)
586
Alex Kiernan0ddc5102018-05-31 04:48:33 +0000587 # Extract the environment from U-Boot and dump it out
Simon Glass606e5432023-07-19 17:49:09 -0600588 cmd = [f'{self.toolchain.cross}objcopy', '-O', 'binary',
Alex Kiernan0ddc5102018-05-31 04:48:33 +0000589 '-j', '.rodata.default_environment',
590 'env/built-in.o', 'uboot.env']
Simon Glassd9800692022-01-29 14:14:05 -0700591 command.run_pipe([cmd], capture=True,
Alex Kiernan0ddc5102018-05-31 04:48:33 +0000592 capture_stderr=True, cwd=result.out_dir,
593 raise_on_error=False, env=env)
Simon Glass60b285f2020-04-17 17:51:34 -0600594 if not work_in_output:
Simon Glassf06d3332023-07-19 17:49:08 -0600595 self.copy_files(result.out_dir, build_dir, '', ['uboot.env'])
Alex Kiernan0ddc5102018-05-31 04:48:33 +0000596
Simon Glass190064b2014-08-09 15:33:00 -0600597 # Write out the image sizes file. This is similar to the output
598 # of binutil's 'size' utility, but it omits the header line and
599 # adds an additional hex value at the end of each line for the
600 # rodata size
Simon Glass606e5432023-07-19 17:49:09 -0600601 if lines:
Simon Glass37edf5f2023-07-19 17:49:06 -0600602 sizes = self.builder.get_sizes_file(result.commit_upto,
Simon Glass190064b2014-08-09 15:33:00 -0600603 result.brd.target)
Simon Glass606e5432023-07-19 17:49:09 -0600604 with open(sizes, 'w', encoding='utf-8') as outf:
605 print('\n'.join(lines), file=outf)
Simon Glass190064b2014-08-09 15:33:00 -0600606
Simon Glass60b285f2020-04-17 17:51:34 -0600607 if not work_in_output:
608 # Write out the configuration files, with a special case for SPL
609 for dirname in ['', 'spl', 'tpl']:
Simon Glassf06d3332023-07-19 17:49:08 -0600610 self.copy_files(
Simon Glass60b285f2020-04-17 17:51:34 -0600611 result.out_dir, build_dir, dirname,
612 ['u-boot.cfg', 'spl/u-boot-spl.cfg', 'tpl/u-boot-tpl.cfg',
613 '.config', 'include/autoconf.mk',
614 'include/generated/autoconf.h'])
Simon Glass970f9322015-02-05 22:06:14 -0700615
Simon Glass60b285f2020-04-17 17:51:34 -0600616 # Now write the actual build output
617 if keep_outputs:
Simon Glassf06d3332023-07-19 17:49:08 -0600618 self.copy_files(
Simon Glass60b285f2020-04-17 17:51:34 -0600619 result.out_dir, build_dir, '',
620 ['u-boot*', '*.bin', '*.map', '*.img', 'MLO', 'SPL',
621 'include/autoconf.mk', 'spl/u-boot-spl*'])
Simon Glass190064b2014-08-09 15:33:00 -0600622
Simon Glassf06d3332023-07-19 17:49:08 -0600623 def copy_files(self, out_dir, build_dir, dirname, patterns):
Simon Glass970f9322015-02-05 22:06:14 -0700624 """Copy files from the build directory to the output.
625
626 Args:
Simon Glass236f9592023-07-19 17:49:26 -0600627 out_dir (str): Path to output directory containing the files
628 build_dir (str): Place to copy the files
629 dirname (str): Source directory, '' for normal U-Boot, 'spl' for SPL
630 patterns (list of str): A list of filenames to copy, each relative
Simon Glass970f9322015-02-05 22:06:14 -0700631 to the build directory
632 """
633 for pattern in patterns:
634 file_list = glob.glob(os.path.join(out_dir, dirname, pattern))
635 for fname in file_list:
636 target = os.path.basename(fname)
637 if dirname:
638 base, ext = os.path.splitext(target)
639 if ext:
Simon Glass606e5432023-07-19 17:49:09 -0600640 target = f'{base}-{dirname}{ext}'
Simon Glass970f9322015-02-05 22:06:14 -0700641 shutil.copy(fname, os.path.join(build_dir, target))
Simon Glass190064b2014-08-09 15:33:00 -0600642
Simon Glassf06d3332023-07-19 17:49:08 -0600643 def _send_result(self, result):
Simon Glassab9b4f32021-04-11 16:27:26 +1200644 """Send a result to the builder for processing
645
646 Args:
Simon Glass236f9592023-07-19 17:49:26 -0600647 result (CommandResult): results of the build
Simon Glass8116c782021-04-11 16:27:27 +1200648
649 Raises:
Simon Glass236f9592023-07-19 17:49:26 -0600650 ValueError: self.test_exception is true (for testing)
Simon Glassab9b4f32021-04-11 16:27:26 +1200651 """
Simon Glass8116c782021-04-11 16:27:27 +1200652 if self.test_exception:
653 raise ValueError('test exception')
Simon Glassab9b4f32021-04-11 16:27:26 +1200654 if self.thread_num != -1:
655 self.builder.out_queue.put(result)
656 else:
Simon Glass37edf5f2023-07-19 17:49:06 -0600657 self.builder.process_result(result)
Simon Glassab9b4f32021-04-11 16:27:26 +1200658
Simon Glassf06d3332023-07-19 17:49:08 -0600659 def run_job(self, job):
Simon Glass190064b2014-08-09 15:33:00 -0600660 """Run a single job
661
662 A job consists of a building a list of commits for a particular board.
663
664 Args:
Simon Glass236f9592023-07-19 17:49:26 -0600665 job (Job): Job to build
Simon Glassb82492b2021-01-30 22:17:46 -0700666
Simon Glass236f9592023-07-19 17:49:26 -0600667 Raises:
668 ValueError: Thread was interrupted
Simon Glass190064b2014-08-09 15:33:00 -0600669 """
Simon Glassf4ed4702022-07-11 19:03:57 -0600670 brd = job.brd
Simon Glass37edf5f2023-07-19 17:49:06 -0600671 work_dir = self.builder.get_thread_dir(self.thread_num)
Simon Glass190064b2014-08-09 15:33:00 -0600672 self.toolchain = None
673 if job.commits:
674 # Run 'make board_defconfig' on the first commit
675 do_config = True
676 commit_upto = 0
677 force_build = False
678 for commit_upto in range(0, len(job.commits), job.step):
Simon Glassf06d3332023-07-19 17:49:08 -0600679 result, request_config = self.run_commit(commit_upto, brd,
Simon Glassa9401b22016-11-16 14:09:25 -0700680 work_dir, do_config, self.builder.config_only,
Simon Glass190064b2014-08-09 15:33:00 -0600681 force_build or self.builder.force_build,
Simon Glassd829f122020-03-18 09:42:42 -0600682 self.builder.force_build_failures,
Simon Glass2b4806e2022-01-22 05:07:33 -0700683 job.work_in_output, job.adjust_cfg)
Simon Glass190064b2014-08-09 15:33:00 -0600684 failed = result.return_code or result.stderr
685 did_config = do_config
686 if failed and not do_config:
687 # If our incremental build failed, try building again
688 # with a reconfig.
689 if self.builder.force_config_on_failure:
Simon Glassf06d3332023-07-19 17:49:08 -0600690 result, request_config = self.run_commit(commit_upto,
Simon Glassd829f122020-03-18 09:42:42 -0600691 brd, work_dir, True, False, True, False,
Simon Glass2b4806e2022-01-22 05:07:33 -0700692 job.work_in_output, job.adjust_cfg)
Simon Glass190064b2014-08-09 15:33:00 -0600693 did_config = True
694 if not self.builder.force_reconfig:
695 do_config = request_config
696
697 # If we built that commit, then config is done. But if we got
698 # an warning, reconfig next time to force it to build the same
699 # files that created warnings this time. Otherwise an
700 # incremental build may not build the same file, and we will
701 # think that the warning has gone away.
702 # We could avoid this by using -Werror everywhere...
703 # For errors, the problem doesn't happen, since presumably
704 # the build stopped and didn't generate output, so will retry
705 # that file next time. So we could detect warnings and deal
706 # with them specially here. For now, we just reconfigure if
707 # anything goes work.
708 # Of course this is substantially slower if there are build
709 # errors/warnings (e.g. 2-3x slower even if only 10% of builds
710 # have problems).
711 if (failed and not result.already_done and not did_config and
712 self.builder.force_config_on_failure):
713 # If this build failed, try the next one with a
714 # reconfigure.
715 # Sometimes if the board_config.h file changes it can mess
716 # with dependencies, and we get:
717 # make: *** No rule to make target `include/autoconf.mk',
718 # needed by `depend'.
719 do_config = True
720 force_build = True
721 else:
722 force_build = False
723 if self.builder.force_config_on_failure:
724 if failed:
725 do_config = True
726 result.commit_upto = commit_upto
727 if result.return_code < 0:
728 raise ValueError('Interrupt')
729
730 # We have the build results, so output the result
Simon Glassf06d3332023-07-19 17:49:08 -0600731 self._write_result(result, job.keep_outputs, job.work_in_output)
732 self._send_result(result)
Simon Glass190064b2014-08-09 15:33:00 -0600733 else:
734 # Just build the currently checked-out build
Simon Glassf06d3332023-07-19 17:49:08 -0600735 result, request_config = self.run_commit(None, brd, work_dir, True,
Simon Glassa9401b22016-11-16 14:09:25 -0700736 self.builder.config_only, True,
Simon Glass2b4806e2022-01-22 05:07:33 -0700737 self.builder.force_build_failures, job.work_in_output,
738 job.adjust_cfg)
Simon Glass190064b2014-08-09 15:33:00 -0600739 result.commit_upto = 0
Simon Glassf06d3332023-07-19 17:49:08 -0600740 self._write_result(result, job.keep_outputs, job.work_in_output)
741 self._send_result(result)
Simon Glass190064b2014-08-09 15:33:00 -0600742
743 def run(self):
744 """Our thread's run function
745
746 This thread picks a job from the queue, runs it, and then goes to the
747 next job.
748 """
Simon Glass190064b2014-08-09 15:33:00 -0600749 while True:
750 job = self.builder.queue.get()
Simon Glass8116c782021-04-11 16:27:27 +1200751 try:
Simon Glassf06d3332023-07-19 17:49:08 -0600752 self.run_job(job)
Simon Glass606e5432023-07-19 17:49:09 -0600753 except Exception as exc:
754 print('Thread exception (use -T0 to run without threads):',
755 exc)
756 self.builder.thread_exceptions.append(exc)
Simon Glass190064b2014-08-09 15:33:00 -0600757 self.builder.queue.task_done()