blob: ff63f9397e6e77030ef0d743b0e210ce8e865f5e [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 Glass124a0da2023-09-07 10:00:17 -060026# Common extensions for images
27COMMON_EXTS = ['.bin', '.rom', '.itb', '.img']
28
Simon Glass236f9592023-07-19 17:49:26 -060029def mkdir(dirname, parents=False):
Simon Glass190064b2014-08-09 15:33:00 -060030 """Make a directory if it doesn't already exist.
31
32 Args:
Simon Glass236f9592023-07-19 17:49:26 -060033 dirname (str): Directory to create
34 parents (bool): True to also make parent directories
35
36 Raises:
37 OSError: File already exists
Simon Glass190064b2014-08-09 15:33:00 -060038 """
39 try:
Thierry Redingf3d015c2014-08-19 10:22:39 +020040 if parents:
41 os.makedirs(dirname)
42 else:
43 os.mkdir(dirname)
Simon Glass190064b2014-08-09 15:33:00 -060044 except OSError as err:
45 if err.errno == errno.EEXIST:
Lothar Waßmann409fc022018-04-08 05:14:11 -060046 if os.path.realpath('.') == os.path.realpath(dirname):
Simon Glass606e5432023-07-19 17:49:09 -060047 print(f"Cannot create the current working directory '{dirname}'!")
Lothar Waßmann409fc022018-04-08 05:14:11 -060048 sys.exit(1)
Simon Glass190064b2014-08-09 15:33:00 -060049 else:
50 raise
51
Simon Glasse5490b72023-07-19 17:49:20 -060052
53def _remove_old_outputs(out_dir):
54 """Remove any old output-target files
55
56 Args:
57 out_dir (str): Output directory for the build
58
59 Since we use a build directory that was previously used by another
60 board, it may have produced an SPL image. If we don't remove it (i.e.
61 see do_config and self.mrproper below) then it will appear to be the
62 output of this build, even if it does not produce SPL images.
63 """
64 for elf in BASE_ELF_FILENAMES:
65 fname = os.path.join(out_dir, elf)
66 if os.path.exists(fname):
67 os.remove(fname)
68
69
Simon Glass2ecc5802023-07-19 17:49:27 -060070def copy_files(out_dir, build_dir, dirname, patterns):
71 """Copy files from the build directory to the output.
72
73 Args:
74 out_dir (str): Path to output directory containing the files
75 build_dir (str): Place to copy the files
76 dirname (str): Source directory, '' for normal U-Boot, 'spl' for SPL
77 patterns (list of str): A list of filenames to copy, each relative
78 to the build directory
79 """
80 for pattern in patterns:
81 file_list = glob.glob(os.path.join(out_dir, dirname, pattern))
82 for fname in file_list:
83 target = os.path.basename(fname)
84 if dirname:
85 base, ext = os.path.splitext(target)
86 if ext:
87 target = f'{base}-{dirname}{ext}'
88 shutil.copy(fname, os.path.join(build_dir, target))
89
90
Simon Glass606e5432023-07-19 17:49:09 -060091# pylint: disable=R0903
Simon Glass190064b2014-08-09 15:33:00 -060092class BuilderJob:
93 """Holds information about a job to be performed by a thread
94
95 Members:
Simon Glassf4ed4702022-07-11 19:03:57 -060096 brd: Board object to build
Simon Glasse9fbbf62020-03-18 09:42:41 -060097 commits: List of Commit objects to build
98 keep_outputs: True to save build output files
99 step: 1 to process every commit, n to process every nth commit
Simon Glassd829f122020-03-18 09:42:42 -0600100 work_in_output: Use the output directory as the work directory and
101 don't write to a separate output directory.
Simon Glass190064b2014-08-09 15:33:00 -0600102 """
103 def __init__(self):
Simon Glassf4ed4702022-07-11 19:03:57 -0600104 self.brd = None
Simon Glass190064b2014-08-09 15:33:00 -0600105 self.commits = []
Simon Glasse9fbbf62020-03-18 09:42:41 -0600106 self.keep_outputs = False
107 self.step = 1
Simon Glassd829f122020-03-18 09:42:42 -0600108 self.work_in_output = False
Simon Glass190064b2014-08-09 15:33:00 -0600109
110
111class ResultThread(threading.Thread):
112 """This thread processes results from builder threads.
113
114 It simply passes the results on to the builder. There is only one
115 result thread, and this helps to serialise the build output.
116 """
117 def __init__(self, builder):
118 """Set up a new result thread
119
120 Args:
121 builder: Builder which will be sent each result
122 """
123 threading.Thread.__init__(self)
124 self.builder = builder
125
126 def run(self):
127 """Called to start up the result thread.
128
129 We collect the next result job and pass it on to the build.
130 """
131 while True:
132 result = self.builder.out_queue.get()
Simon Glass37edf5f2023-07-19 17:49:06 -0600133 self.builder.process_result(result)
Simon Glass190064b2014-08-09 15:33:00 -0600134 self.builder.out_queue.task_done()
135
136
137class BuilderThread(threading.Thread):
138 """This thread builds U-Boot for a particular board.
139
140 An input queue provides each new job. We run 'make' to build U-Boot
141 and then pass the results on to the output queue.
142
143 Members:
144 builder: The builder which contains information we might need
145 thread_num: Our thread number (0-n-1), used to decide on a
Simon Glass24993312021-04-11 16:27:25 +1200146 temporary directory. If this is -1 then there are no threads
147 and we are the (only) main process
148 mrproper: Use 'make mrproper' before each reconfigure
149 per_board_out_dir: True to build in a separate persistent directory per
150 board rather than a thread-specific directory
151 test_exception: Used for testing; True to raise an exception instead of
152 reporting the build result
Simon Glass190064b2014-08-09 15:33:00 -0600153 """
Simon Glass8116c782021-04-11 16:27:27 +1200154 def __init__(self, builder, thread_num, mrproper, per_board_out_dir,
155 test_exception=False):
Simon Glass190064b2014-08-09 15:33:00 -0600156 """Set up a new builder thread"""
157 threading.Thread.__init__(self)
158 self.builder = builder
159 self.thread_num = thread_num
Simon Glasseb70a2c2020-04-09 15:08:51 -0600160 self.mrproper = mrproper
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600161 self.per_board_out_dir = per_board_out_dir
Simon Glass8116c782021-04-11 16:27:27 +1200162 self.test_exception = test_exception
Simon Glass606e5432023-07-19 17:49:09 -0600163 self.toolchain = None
Simon Glass190064b2014-08-09 15:33:00 -0600164
Simon Glassf06d3332023-07-19 17:49:08 -0600165 def make(self, commit, brd, stage, cwd, *args, **kwargs):
Simon Glass190064b2014-08-09 15:33:00 -0600166 """Run 'make' on a particular commit and board.
167
168 The source code will already be checked out, so the 'commit'
169 argument is only for information.
170
171 Args:
Simon Glass236f9592023-07-19 17:49:26 -0600172 commit (Commit): Commit that is being built
173 brd (Board): Board that is being built
174 stage (str): Stage of the build. Valid stages are:
Roger Meierfd18a892014-08-20 22:10:29 +0200175 mrproper - can be called to clean source
Simon Glass190064b2014-08-09 15:33:00 -0600176 config - called to configure for a board
177 build - the main make invocation - it does the build
Simon Glass236f9592023-07-19 17:49:26 -0600178 cwd (str): Working directory to set, or None to leave it alone
179 *args (list of str): Arguments to pass to 'make'
180 **kwargs (dict): A list of keyword arguments to pass to
181 command.run_pipe()
Simon Glass190064b2014-08-09 15:33:00 -0600182
183 Returns:
184 CommandResult object
185 """
186 return self.builder.do_make(commit, brd, stage, cwd, *args,
187 **kwargs)
188
Simon Glassed007bf2023-07-19 17:49:15 -0600189 def _build_args(self, brd, out_dir, out_rel_dir, work_dir, commit_upto):
Simon Glass75247002023-07-19 17:49:13 -0600190 """Set up arguments to the args list based on the settings
191
192 Args:
Simon Glassa06ed7f2023-07-19 17:49:14 -0600193 brd (Board): Board to create arguments for
Simon Glassed007bf2023-07-19 17:49:15 -0600194 out_dir (str): Path to output directory containing the files
195 out_rel_dir (str): Output directory relative to the current dir
196 work_dir (str): Directory to which the source will be checked out
197 commit_upto (int): Commit number to build (0...n-1)
198
199 Returns:
200 tuple:
201 list of str: Arguments to pass to make
202 str: Current working directory, or None if no commit
203 str: Source directory (typically the work directory)
Simon Glass75247002023-07-19 17:49:13 -0600204 """
Simon Glassed007bf2023-07-19 17:49:15 -0600205 args = []
206 cwd = work_dir
207 src_dir = os.path.realpath(work_dir)
208 if not self.builder.in_tree:
209 if commit_upto is None:
210 # In this case we are building in the original source directory
211 # (i.e. the current directory where buildman is invoked. The
212 # output directory is set to this thread's selected work
213 # directory.
214 #
215 # Symlinks can confuse U-Boot's Makefile since we may use '..'
216 # in our path, so remove them.
217 real_dir = os.path.realpath(out_dir)
218 args.append(f'O={real_dir}')
219 cwd = None
220 src_dir = os.getcwd()
221 else:
222 args.append(f'O={out_rel_dir}')
Simon Glass75247002023-07-19 17:49:13 -0600223 if self.builder.verbose_build:
224 args.append('V=1')
225 else:
226 args.append('-s')
227 if self.builder.num_jobs is not None:
228 args.extend(['-j', str(self.builder.num_jobs)])
229 if self.builder.warnings_as_errors:
230 args.append('KCFLAGS=-Werror')
231 args.append('HOSTCFLAGS=-Werror')
232 if self.builder.allow_missing:
233 args.append('BINMAN_ALLOW_MISSING=1')
234 if self.builder.no_lto:
235 args.append('NO_LTO=1')
236 if self.builder.reproducible_builds:
237 args.append('SOURCE_DATE_EPOCH=0')
Simon Glassa06ed7f2023-07-19 17:49:14 -0600238 args.extend(self.builder.toolchains.GetMakeArguments(brd))
239 args.extend(self.toolchain.MakeArgs())
Simon Glassed007bf2023-07-19 17:49:15 -0600240 return args, cwd, src_dir
Simon Glass75247002023-07-19 17:49:13 -0600241
Simon Glassec2f4922023-07-19 17:49:17 -0600242 def _reconfigure(self, commit, brd, cwd, args, env, config_args, config_out,
Simon Glass49b158a2024-06-23 11:55:09 -0600243 cmd_list, mrproper):
Simon Glassec2f4922023-07-19 17:49:17 -0600244 """Reconfigure the build
245
246 Args:
247 commit (Commit): Commit only being built
248 brd (Board): Board being built
249 cwd (str): Current working directory
250 args (list of str): Arguments to pass to make
251 env (dict): Environment strings
252 config_args (list of str): defconfig arg for this board
253 cmd_list (list of str): List to add the commands to, for logging
Simon Glass49b158a2024-06-23 11:55:09 -0600254 mrproper (bool): True to run mrproper first
Simon Glassec2f4922023-07-19 17:49:17 -0600255
256 Returns:
257 CommandResult object
258 """
Simon Glass49b158a2024-06-23 11:55:09 -0600259 if mrproper:
Simon Glassec2f4922023-07-19 17:49:17 -0600260 result = self.make(commit, brd, 'mrproper', cwd, 'mrproper', *args,
261 env=env)
262 config_out.write(result.combined)
263 cmd_list.append([self.builder.gnu_make, 'mrproper', *args])
264 result = self.make(commit, brd, 'config', cwd, *(args + config_args),
265 env=env)
266 cmd_list.append([self.builder.gnu_make] + args + config_args)
267 config_out.write(result.combined)
268 return result
269
Simon Glass14c15232023-07-19 17:49:18 -0600270 def _build(self, commit, brd, cwd, args, env, cmd_list, config_only):
271 """Perform the build
272
273 Args:
274 commit (Commit): Commit only being built
275 brd (Board): Board being built
276 cwd (str): Current working directory
277 args (list of str): Arguments to pass to make
278 env (dict): Environment strings
279 cmd_list (list of str): List to add the commands to, for logging
280 config_only (bool): True if this is a config-only build (using the
281 'make cfg' target)
282
283 Returns:
284 CommandResult object
285 """
286 if config_only:
287 args.append('cfg')
288 result = self.make(commit, brd, 'build', cwd, *args, env=env)
289 cmd_list.append([self.builder.gnu_make] + args)
290 if (result.return_code == 2 and
291 ('Some images are invalid' in result.stderr)):
292 # This is handled later by the check for output in stderr
293 result.return_code = 0
294 return result
295
Simon Glass5e5044b2023-07-19 17:49:24 -0600296 def _read_done_file(self, commit_upto, brd, force_build,
Simon Glass4981bd32023-07-19 17:49:19 -0600297 force_build_failures):
298 """Check the 'done' file and see if this commit should be built
299
300 Args:
301 commit (Commit): Commit only being built
302 brd (Board): Board being built
Simon Glass4981bd32023-07-19 17:49:19 -0600303 force_build (bool): Force a build even if one was previously done
304 force_build_failures (bool): Force a bulid if the previous result
305 showed failure
306
307 Returns:
Simon Glass5e5044b2023-07-19 17:49:24 -0600308 tuple:
309 bool: True if build should be built
310 CommandResult: if there was a previous run:
311 - already_done set to True
312 - return_code set to return code
313 - result.stderr set to 'bad' if stderr output was recorded
Simon Glass4981bd32023-07-19 17:49:19 -0600314 """
Simon Glass5e5044b2023-07-19 17:49:24 -0600315 result = command.CommandResult()
Simon Glass4981bd32023-07-19 17:49:19 -0600316 done_file = self.builder.get_done_file(commit_upto, brd.target)
317 result.already_done = os.path.exists(done_file)
318 will_build = (force_build or force_build_failures or
319 not result.already_done)
320 if result.already_done:
321 with open(done_file, 'r', encoding='utf-8') as outf:
322 try:
323 result.return_code = int(outf.readline())
324 except ValueError:
325 # The file may be empty due to running out of disk space.
326 # Try a rebuild
327 result.return_code = RETURN_CODE_RETRY
328
329 # Check the signal that the build needs to be retried
330 if result.return_code == RETURN_CODE_RETRY:
331 will_build = True
332 elif will_build:
333 err_file = self.builder.get_err_file(commit_upto, brd.target)
334 if os.path.exists(err_file) and os.stat(err_file).st_size:
335 result.stderr = 'bad'
336 elif not force_build:
337 # The build passed, so no need to build it again
338 will_build = False
Simon Glass5e5044b2023-07-19 17:49:24 -0600339 return will_build, result
Simon Glass4981bd32023-07-19 17:49:19 -0600340
Simon Glass9bdf0232023-07-19 17:49:21 -0600341 def _decide_dirs(self, brd, work_dir, work_in_output):
342 """Decide the output directory to use
343
344 Args:
345 work_dir (str): Directory to which the source will be checked out
346 work_in_output (bool): Use the output directory as the work
347 directory and don't write to a separate output directory.
348
349 Returns:
350 tuple:
351 out_dir (str): Output directory for the build
352 out_rel_dir (str): Output directory relatie to the current dir
353 """
354 if work_in_output or self.builder.in_tree:
355 out_rel_dir = None
356 out_dir = work_dir
357 else:
358 if self.per_board_out_dir:
359 out_rel_dir = os.path.join('..', brd.target)
360 else:
361 out_rel_dir = 'build'
362 out_dir = os.path.join(work_dir, out_rel_dir)
363 return out_dir, out_rel_dir
364
Simon Glassad7181c2023-07-19 17:49:22 -0600365 def _checkout(self, commit_upto, work_dir):
366 """Checkout the right commit
367
368 Args:
369 commit_upto (int): Commit number to build (0...n-1)
370 work_dir (str): Directory to which the source will be checked out
371
372 Returns:
373 Commit: Commit being built, or 'current' for current source
374 """
375 if self.builder.commits:
376 commit = self.builder.commits[commit_upto]
377 if self.builder.checkout:
378 git_dir = os.path.join(work_dir, '.git')
379 gitutil.checkout(commit.hash, git_dir, work_dir, force=True)
380 else:
381 commit = 'current'
382 return commit
383
Simon Glass3f972a42024-06-23 11:55:10 -0600384 def _config_and_build(self, commit_upto, brd, work_dir, do_config, mrproper,
Simon Glass335c1b92023-07-19 17:49:23 -0600385 config_only, adjust_cfg, commit, out_dir, out_rel_dir,
386 result):
387 """Do the build, configuring first if necessary
388
389 Args:
390 commit_upto (int): Commit number to build (0...n-1)
391 brd (Board): Board to create arguments for
392 work_dir (str): Directory to which the source will be checked out
393 do_config (bool): True to run a make <board>_defconfig on the source
Simon Glass3f972a42024-06-23 11:55:10 -0600394 mrproper (bool): True to run mrproper first
Simon Glass335c1b92023-07-19 17:49:23 -0600395 config_only (bool): Only configure the source, do not build it
396 adjust_cfg (list of str): See the cfgutil module and run_commit()
397 commit (Commit): Commit only being built
398 out_dir (str): Output directory for the build
399 out_rel_dir (str): Output directory relatie to the current dir
400 result (CommandResult): Previous result
401
402 Returns:
403 tuple:
404 result (CommandResult): Result of the build
405 do_config (bool): indicates whether 'make config' is needed on
406 the next incremental build
407 """
408 # Set up the environment and command line
409 env = self.toolchain.MakeEnvironment(self.builder.full_path)
410 mkdir(out_dir)
411
412 args, cwd, src_dir = self._build_args(brd, out_dir, out_rel_dir,
413 work_dir, commit_upto)
414 config_args = [f'{brd.target}_defconfig']
415 config_out = io.StringIO()
416
417 _remove_old_outputs(out_dir)
418
419 # If we need to reconfigure, do that now
420 cfg_file = os.path.join(out_dir, '.config')
421 cmd_list = []
422 if do_config or adjust_cfg:
423 result = self._reconfigure(
Simon Glass49b158a2024-06-23 11:55:09 -0600424 commit, brd, cwd, args, env, config_args, config_out, cmd_list,
Simon Glass3f972a42024-06-23 11:55:10 -0600425 mrproper)
Simon Glass335c1b92023-07-19 17:49:23 -0600426 do_config = False # No need to configure next time
427 if adjust_cfg:
428 cfgutil.adjust_cfg_file(cfg_file, adjust_cfg)
429
430 # Now do the build, if everything looks OK
431 if result.return_code == 0:
Simon Glassdc314182023-10-26 14:31:10 -0400432 if adjust_cfg:
433 oldc_args = list(args) + ['oldconfig']
434 oldc_result = self.make(commit, brd, 'oldconfig', cwd,
435 *oldc_args, env=env)
436 if oldc_result.return_code:
437 return oldc_result
Simon Glass335c1b92023-07-19 17:49:23 -0600438 result = self._build(commit, brd, cwd, args, env, cmd_list,
439 config_only)
440 if adjust_cfg:
441 errs = cfgutil.check_cfg_file(cfg_file, adjust_cfg)
442 if errs:
443 result.stderr += errs
444 result.return_code = 1
445 result.stderr = result.stderr.replace(src_dir + '/', '')
446 if self.builder.verbose_build:
447 result.stdout = config_out.getvalue() + result.stdout
448 result.cmd_list = cmd_list
449 return result, do_config
Simon Glassad7181c2023-07-19 17:49:22 -0600450
Simon Glassf06d3332023-07-19 17:49:08 -0600451 def run_commit(self, commit_upto, brd, work_dir, do_config, config_only,
Simon Glass2b4806e2022-01-22 05:07:33 -0700452 force_build, force_build_failures, work_in_output,
453 adjust_cfg):
Simon Glass190064b2014-08-09 15:33:00 -0600454 """Build a particular commit.
455
456 If the build is already done, and we are not forcing a build, we skip
457 the build and just return the previously-saved results.
458
459 Args:
Simon Glass236f9592023-07-19 17:49:26 -0600460 commit_upto (int): Commit number to build (0...n-1)
461 brd (Board): Board to build
462 work_dir (str): Directory to which the source will be checked out
463 do_config (bool): True to run a make <board>_defconfig on the source
464 config_only (bool): Only configure the source, do not build it
465 force_build (bool): Force a build even if one was previously done
466 force_build_failures (bool): Force a bulid if the previous result
467 showed failure
468 work_in_output (bool) : Use the output directory as the work
469 directory and don't write to a separate output directory.
Simon Glass2b4806e2022-01-22 05:07:33 -0700470 adjust_cfg (list of str): List of changes to make to .config file
471 before building. Each is one of (where C is either CONFIG_xxx
472 or just xxx):
473 C to enable C
474 ~C to disable C
475 C=val to set the value of C (val must have quotes if C is
476 a string Kconfig
Simon Glass190064b2014-08-09 15:33:00 -0600477
478 Returns:
479 tuple containing:
480 - CommandResult object containing the results of the build
481 - boolean indicating whether 'make config' is still needed
482 """
483 # Create a default result - it will be overwritte by the call to
Simon Glassf06d3332023-07-19 17:49:08 -0600484 # self.make() below, in the event that we do a build.
Simon Glass9bdf0232023-07-19 17:49:21 -0600485 out_dir, out_rel_dir = self._decide_dirs(brd, work_dir, work_in_output)
Simon Glass190064b2014-08-09 15:33:00 -0600486
487 # Check if the job was already completed last time
Simon Glass5e5044b2023-07-19 17:49:24 -0600488 will_build, result = self._read_done_file(commit_upto, brd, force_build,
489 force_build_failures)
Simon Glass190064b2014-08-09 15:33:00 -0600490
491 if will_build:
492 # We are going to have to build it. First, get a toolchain
493 if not self.toolchain:
494 try:
495 self.toolchain = self.builder.toolchains.Select(brd.arch)
496 except ValueError as err:
497 result.return_code = 10
498 result.stdout = ''
Simon Glass57686d32023-07-19 17:49:25 -0600499 result.stderr = f'Tool chain error for {brd.arch}: {str(err)}'
Simon Glass190064b2014-08-09 15:33:00 -0600500
501 if self.toolchain:
Simon Glassad7181c2023-07-19 17:49:22 -0600502 commit = self._checkout(commit_upto, work_dir)
Simon Glass335c1b92023-07-19 17:49:23 -0600503 result, do_config = self._config_and_build(
Simon Glass3f972a42024-06-23 11:55:10 -0600504 commit_upto, brd, work_dir, do_config, self.mrproper,
505 config_only, adjust_cfg, commit, out_dir, out_rel_dir,
506 result)
Simon Glass190064b2014-08-09 15:33:00 -0600507 result.already_done = False
508
509 result.toolchain = self.toolchain
510 result.brd = brd
511 result.commit_upto = commit_upto
512 result.out_dir = out_dir
513 return result, do_config
514
Simon Glassf06d3332023-07-19 17:49:08 -0600515 def _write_result(self, result, keep_outputs, work_in_output):
Simon Glass190064b2014-08-09 15:33:00 -0600516 """Write a built result to the output directory.
517
518 Args:
Simon Glass236f9592023-07-19 17:49:26 -0600519 result (CommandResult): result to write
520 keep_outputs (bool): True to store the output binaries, False
Simon Glass190064b2014-08-09 15:33:00 -0600521 to delete them
Simon Glass236f9592023-07-19 17:49:26 -0600522 work_in_output (bool): Use the output directory as the work
523 directory and don't write to a separate output directory.
Simon Glass190064b2014-08-09 15:33:00 -0600524 """
Simon Glass88c8dcf2015-02-05 22:06:13 -0700525 # If we think this might have been aborted with Ctrl-C, record the
526 # failure but not that we are 'done' with this board. A retry may fix
527 # it.
Simon Glassbafdeb42021-10-19 21:43:23 -0600528 maybe_aborted = result.stderr and 'No child processes' in result.stderr
Simon Glass190064b2014-08-09 15:33:00 -0600529
Simon Glassbafdeb42021-10-19 21:43:23 -0600530 if result.return_code >= 0 and result.already_done:
Simon Glass190064b2014-08-09 15:33:00 -0600531 return
532
533 # Write the output and stderr
Simon Glass4a7419b2023-07-19 17:49:10 -0600534 output_dir = self.builder.get_output_dir(result.commit_upto)
Simon Glassf06d3332023-07-19 17:49:08 -0600535 mkdir(output_dir)
Simon Glass37edf5f2023-07-19 17:49:06 -0600536 build_dir = self.builder.get_build_dir(result.commit_upto,
Simon Glass190064b2014-08-09 15:33:00 -0600537 result.brd.target)
Simon Glassf06d3332023-07-19 17:49:08 -0600538 mkdir(build_dir)
Simon Glass190064b2014-08-09 15:33:00 -0600539
540 outfile = os.path.join(build_dir, 'log')
Simon Glass606e5432023-07-19 17:49:09 -0600541 with open(outfile, 'w', encoding='utf-8') as outf:
Simon Glass190064b2014-08-09 15:33:00 -0600542 if result.stdout:
Simon Glass606e5432023-07-19 17:49:09 -0600543 outf.write(result.stdout)
Simon Glass190064b2014-08-09 15:33:00 -0600544
Simon Glass37edf5f2023-07-19 17:49:06 -0600545 errfile = self.builder.get_err_file(result.commit_upto,
Simon Glass190064b2014-08-09 15:33:00 -0600546 result.brd.target)
547 if result.stderr:
Simon Glass606e5432023-07-19 17:49:09 -0600548 with open(errfile, 'w', encoding='utf-8') as outf:
549 outf.write(result.stderr)
Simon Glass190064b2014-08-09 15:33:00 -0600550 elif os.path.exists(errfile):
551 os.remove(errfile)
552
Simon Glassbafdeb42021-10-19 21:43:23 -0600553 # Fatal error
554 if result.return_code < 0:
555 return
556
Simon Glass190064b2014-08-09 15:33:00 -0600557 if result.toolchain:
558 # Write the build result and toolchain information.
Simon Glass37edf5f2023-07-19 17:49:06 -0600559 done_file = self.builder.get_done_file(result.commit_upto,
Simon Glass190064b2014-08-09 15:33:00 -0600560 result.brd.target)
Simon Glass606e5432023-07-19 17:49:09 -0600561 with open(done_file, 'w', encoding='utf-8') as outf:
Simon Glass88c8dcf2015-02-05 22:06:13 -0700562 if maybe_aborted:
563 # Special code to indicate we need to retry
Simon Glass606e5432023-07-19 17:49:09 -0600564 outf.write(f'{RETURN_CODE_RETRY}')
Simon Glass88c8dcf2015-02-05 22:06:13 -0700565 else:
Simon Glass606e5432023-07-19 17:49:09 -0600566 outf.write(f'{result.return_code}')
567 with open(os.path.join(build_dir, 'toolchain'), 'w',
568 encoding='utf-8') as outf:
569 print('gcc', result.toolchain.gcc, file=outf)
570 print('path', result.toolchain.path, file=outf)
571 print('cross', result.toolchain.cross, file=outf)
572 print('arch', result.toolchain.arch, file=outf)
573 outf.write(f'{result.return_code}')
Simon Glass190064b2014-08-09 15:33:00 -0600574
Simon Glass190064b2014-08-09 15:33:00 -0600575 # Write out the image and function size information and an objdump
Simon Glassbb1501f2014-12-01 17:34:00 -0700576 env = result.toolchain.MakeEnvironment(self.builder.full_path)
Simon Glass606e5432023-07-19 17:49:09 -0600577 with open(os.path.join(build_dir, 'out-env'), 'wb') as outf:
Simon Glasse5fc79e2019-01-07 16:44:23 -0700578 for var in sorted(env.keys()):
Simon Glass606e5432023-07-19 17:49:09 -0600579 outf.write(b'%s="%s"' % (var, env[var]))
Simon Glasscd37d5b2023-02-21 12:40:27 -0700580
581 with open(os.path.join(build_dir, 'out-cmd'), 'w',
Simon Glass606e5432023-07-19 17:49:09 -0600582 encoding='utf-8') as outf:
Simon Glasscd37d5b2023-02-21 12:40:27 -0700583 for cmd in result.cmd_list:
Simon Glass606e5432023-07-19 17:49:09 -0600584 print(' '.join(cmd), file=outf)
Simon Glasscd37d5b2023-02-21 12:40:27 -0700585
Simon Glass190064b2014-08-09 15:33:00 -0600586 lines = []
Simon Glass73da3d22020-12-16 17:24:17 -0700587 for fname in BASE_ELF_FILENAMES:
Simon Glass606e5432023-07-19 17:49:09 -0600588 cmd = [f'{self.toolchain.cross}nm', '--size-sort', fname]
Simon Glassd9800692022-01-29 14:14:05 -0700589 nm_result = command.run_pipe([cmd], capture=True,
Simon Glass190064b2014-08-09 15:33:00 -0600590 capture_stderr=True, cwd=result.out_dir,
591 raise_on_error=False, env=env)
592 if nm_result.stdout:
Simon Glass606e5432023-07-19 17:49:09 -0600593 nm_fname = self.builder.get_func_sizes_file(
594 result.commit_upto, result.brd.target, fname)
595 with open(nm_fname, 'w', encoding='utf-8') as outf:
596 print(nm_result.stdout, end=' ', file=outf)
Simon Glass190064b2014-08-09 15:33:00 -0600597
Simon Glass606e5432023-07-19 17:49:09 -0600598 cmd = [f'{self.toolchain.cross}objdump', '-h', fname]
Simon Glassd9800692022-01-29 14:14:05 -0700599 dump_result = command.run_pipe([cmd], capture=True,
Simon Glass190064b2014-08-09 15:33:00 -0600600 capture_stderr=True, cwd=result.out_dir,
601 raise_on_error=False, env=env)
602 rodata_size = ''
603 if dump_result.stdout:
Simon Glass37edf5f2023-07-19 17:49:06 -0600604 objdump = self.builder.get_objdump_file(result.commit_upto,
Simon Glass190064b2014-08-09 15:33:00 -0600605 result.brd.target, fname)
Simon Glass606e5432023-07-19 17:49:09 -0600606 with open(objdump, 'w', encoding='utf-8') as outf:
607 print(dump_result.stdout, end=' ', file=outf)
Simon Glass190064b2014-08-09 15:33:00 -0600608 for line in dump_result.stdout.splitlines():
609 fields = line.split()
610 if len(fields) > 5 and fields[1] == '.rodata':
611 rodata_size = fields[2]
612
Simon Glass606e5432023-07-19 17:49:09 -0600613 cmd = [f'{self.toolchain.cross}size', fname]
Simon Glassd9800692022-01-29 14:14:05 -0700614 size_result = command.run_pipe([cmd], capture=True,
Simon Glass190064b2014-08-09 15:33:00 -0600615 capture_stderr=True, cwd=result.out_dir,
616 raise_on_error=False, env=env)
617 if size_result.stdout:
618 lines.append(size_result.stdout.splitlines()[1] + ' ' +
619 rodata_size)
620
Alex Kiernan0ddc5102018-05-31 04:48:33 +0000621 # Extract the environment from U-Boot and dump it out
Simon Glass606e5432023-07-19 17:49:09 -0600622 cmd = [f'{self.toolchain.cross}objcopy', '-O', 'binary',
Alex Kiernan0ddc5102018-05-31 04:48:33 +0000623 '-j', '.rodata.default_environment',
624 'env/built-in.o', 'uboot.env']
Simon Glassd9800692022-01-29 14:14:05 -0700625 command.run_pipe([cmd], capture=True,
Alex Kiernan0ddc5102018-05-31 04:48:33 +0000626 capture_stderr=True, cwd=result.out_dir,
627 raise_on_error=False, env=env)
Simon Glass60b285f2020-04-17 17:51:34 -0600628 if not work_in_output:
Simon Glass2ecc5802023-07-19 17:49:27 -0600629 copy_files(result.out_dir, build_dir, '', ['uboot.env'])
Alex Kiernan0ddc5102018-05-31 04:48:33 +0000630
Simon Glass190064b2014-08-09 15:33:00 -0600631 # Write out the image sizes file. This is similar to the output
632 # of binutil's 'size' utility, but it omits the header line and
633 # adds an additional hex value at the end of each line for the
634 # rodata size
Simon Glass606e5432023-07-19 17:49:09 -0600635 if lines:
Simon Glass37edf5f2023-07-19 17:49:06 -0600636 sizes = self.builder.get_sizes_file(result.commit_upto,
Simon Glass190064b2014-08-09 15:33:00 -0600637 result.brd.target)
Simon Glass606e5432023-07-19 17:49:09 -0600638 with open(sizes, 'w', encoding='utf-8') as outf:
639 print('\n'.join(lines), file=outf)
Simon Glass190064b2014-08-09 15:33:00 -0600640
Simon Glass60b285f2020-04-17 17:51:34 -0600641 if not work_in_output:
642 # Write out the configuration files, with a special case for SPL
643 for dirname in ['', 'spl', 'tpl']:
Simon Glass2ecc5802023-07-19 17:49:27 -0600644 copy_files(
Simon Glass60b285f2020-04-17 17:51:34 -0600645 result.out_dir, build_dir, dirname,
646 ['u-boot.cfg', 'spl/u-boot-spl.cfg', 'tpl/u-boot-tpl.cfg',
647 '.config', 'include/autoconf.mk',
648 'include/generated/autoconf.h'])
Simon Glass970f9322015-02-05 22:06:14 -0700649
Simon Glass60b285f2020-04-17 17:51:34 -0600650 # Now write the actual build output
651 if keep_outputs:
Simon Glass124a0da2023-09-07 10:00:17 -0600652 to_copy = ['u-boot*', '*.map', 'MLO', 'SPL',
653 'include/autoconf.mk', 'spl/u-boot-spl*',
654 'tpl/u-boot-tpl*', 'vpl/u-boot-vpl*']
655 to_copy += [f'*{ext}' for ext in COMMON_EXTS]
656 copy_files(result.out_dir, build_dir, '', to_copy)
Simon Glass190064b2014-08-09 15:33:00 -0600657
Simon Glassf06d3332023-07-19 17:49:08 -0600658 def _send_result(self, result):
Simon Glassab9b4f32021-04-11 16:27:26 +1200659 """Send a result to the builder for processing
660
661 Args:
Simon Glass236f9592023-07-19 17:49:26 -0600662 result (CommandResult): results of the build
Simon Glass8116c782021-04-11 16:27:27 +1200663
664 Raises:
Simon Glass236f9592023-07-19 17:49:26 -0600665 ValueError: self.test_exception is true (for testing)
Simon Glassab9b4f32021-04-11 16:27:26 +1200666 """
Simon Glass8116c782021-04-11 16:27:27 +1200667 if self.test_exception:
668 raise ValueError('test exception')
Simon Glassab9b4f32021-04-11 16:27:26 +1200669 if self.thread_num != -1:
670 self.builder.out_queue.put(result)
671 else:
Simon Glass37edf5f2023-07-19 17:49:06 -0600672 self.builder.process_result(result)
Simon Glassab9b4f32021-04-11 16:27:26 +1200673
Simon Glassf06d3332023-07-19 17:49:08 -0600674 def run_job(self, job):
Simon Glass190064b2014-08-09 15:33:00 -0600675 """Run a single job
676
677 A job consists of a building a list of commits for a particular board.
678
679 Args:
Simon Glass236f9592023-07-19 17:49:26 -0600680 job (Job): Job to build
Simon Glassb82492b2021-01-30 22:17:46 -0700681
Simon Glass236f9592023-07-19 17:49:26 -0600682 Raises:
683 ValueError: Thread was interrupted
Simon Glass190064b2014-08-09 15:33:00 -0600684 """
Simon Glassf4ed4702022-07-11 19:03:57 -0600685 brd = job.brd
Simon Glass37edf5f2023-07-19 17:49:06 -0600686 work_dir = self.builder.get_thread_dir(self.thread_num)
Simon Glass190064b2014-08-09 15:33:00 -0600687 self.toolchain = None
688 if job.commits:
689 # Run 'make board_defconfig' on the first commit
690 do_config = True
691 commit_upto = 0
692 force_build = False
693 for commit_upto in range(0, len(job.commits), job.step):
Simon Glassf06d3332023-07-19 17:49:08 -0600694 result, request_config = self.run_commit(commit_upto, brd,
Simon Glassa9401b22016-11-16 14:09:25 -0700695 work_dir, do_config, self.builder.config_only,
Simon Glass190064b2014-08-09 15:33:00 -0600696 force_build or self.builder.force_build,
Simon Glassd829f122020-03-18 09:42:42 -0600697 self.builder.force_build_failures,
Simon Glass2b4806e2022-01-22 05:07:33 -0700698 job.work_in_output, job.adjust_cfg)
Simon Glass190064b2014-08-09 15:33:00 -0600699 failed = result.return_code or result.stderr
700 did_config = do_config
701 if failed and not do_config:
702 # If our incremental build failed, try building again
703 # with a reconfig.
704 if self.builder.force_config_on_failure:
Simon Glassf06d3332023-07-19 17:49:08 -0600705 result, request_config = self.run_commit(commit_upto,
Simon Glassd829f122020-03-18 09:42:42 -0600706 brd, work_dir, True, False, True, False,
Simon Glass2b4806e2022-01-22 05:07:33 -0700707 job.work_in_output, job.adjust_cfg)
Simon Glass190064b2014-08-09 15:33:00 -0600708 did_config = True
709 if not self.builder.force_reconfig:
710 do_config = request_config
711
712 # If we built that commit, then config is done. But if we got
713 # an warning, reconfig next time to force it to build the same
714 # files that created warnings this time. Otherwise an
715 # incremental build may not build the same file, and we will
716 # think that the warning has gone away.
717 # We could avoid this by using -Werror everywhere...
718 # For errors, the problem doesn't happen, since presumably
719 # the build stopped and didn't generate output, so will retry
720 # that file next time. So we could detect warnings and deal
721 # with them specially here. For now, we just reconfigure if
722 # anything goes work.
723 # Of course this is substantially slower if there are build
724 # errors/warnings (e.g. 2-3x slower even if only 10% of builds
725 # have problems).
726 if (failed and not result.already_done and not did_config and
727 self.builder.force_config_on_failure):
728 # If this build failed, try the next one with a
729 # reconfigure.
730 # Sometimes if the board_config.h file changes it can mess
731 # with dependencies, and we get:
732 # make: *** No rule to make target `include/autoconf.mk',
733 # needed by `depend'.
734 do_config = True
735 force_build = True
736 else:
737 force_build = False
738 if self.builder.force_config_on_failure:
739 if failed:
740 do_config = True
741 result.commit_upto = commit_upto
742 if result.return_code < 0:
743 raise ValueError('Interrupt')
744
745 # We have the build results, so output the result
Simon Glassf06d3332023-07-19 17:49:08 -0600746 self._write_result(result, job.keep_outputs, job.work_in_output)
747 self._send_result(result)
Simon Glass190064b2014-08-09 15:33:00 -0600748 else:
749 # Just build the currently checked-out build
Simon Glassf06d3332023-07-19 17:49:08 -0600750 result, request_config = self.run_commit(None, brd, work_dir, True,
Simon Glassa9401b22016-11-16 14:09:25 -0700751 self.builder.config_only, True,
Simon Glass2b4806e2022-01-22 05:07:33 -0700752 self.builder.force_build_failures, job.work_in_output,
753 job.adjust_cfg)
Simon Glass190064b2014-08-09 15:33:00 -0600754 result.commit_upto = 0
Simon Glassf06d3332023-07-19 17:49:08 -0600755 self._write_result(result, job.keep_outputs, job.work_in_output)
756 self._send_result(result)
Simon Glass190064b2014-08-09 15:33:00 -0600757
758 def run(self):
759 """Our thread's run function
760
761 This thread picks a job from the queue, runs it, and then goes to the
762 next job.
763 """
Simon Glass190064b2014-08-09 15:33:00 -0600764 while True:
765 job = self.builder.queue.get()
Simon Glass8116c782021-04-11 16:27:27 +1200766 try:
Simon Glassf06d3332023-07-19 17:49:08 -0600767 self.run_job(job)
Simon Glass606e5432023-07-19 17:49:09 -0600768 except Exception as exc:
769 print('Thread exception (use -T0 to run without threads):',
770 exc)
771 self.builder.thread_exceptions.append(exc)
Simon Glass190064b2014-08-09 15:33:00 -0600772 self.builder.queue.task_done()