blob: 78c95a67095c84fd90b1d6f99209f2d272ad1765 [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glass4a1e88b2014-08-09 15:33:00 -06002# Copyright (c) 2014 Google, Inc
3#
Simon Glass4a1e88b2014-08-09 15:33:00 -06004
Simon Glassd07e5532023-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 Glass4a1e88b2014-08-09 15:33:00 -060011import errno
12import glob
Simon Glass65cfdd12023-07-19 17:49:16 -060013import io
Simon Glass4a1e88b2014-08-09 15:33:00 -060014import os
15import shutil
Lothar Waßmannce6df922018-04-08 05:14:11 -060016import sys
Simon Glass4a1e88b2014-08-09 15:33:00 -060017import threading
18
Simon Glasse5650a82022-01-22 05:07:33 -070019from buildman import cfgutil
Simon Glass131444f2023-02-23 18:18:04 -070020from u_boot_pylib import command
Simon Glassba1b3b92025-02-09 14:26:00 -070021from u_boot_pylib import gitutil
Simon Glassdc311842024-12-14 11:20:23 -070022from u_boot_pylib import tools
Simon Glass4a1e88b2014-08-09 15:33:00 -060023
Simon Glassfd3eea12015-02-05 22:06:13 -070024RETURN_CODE_RETRY = -1
Simon Glasse0f19712020-12-16 17:24:17 -070025BASE_ELF_FILENAMES = ['u-boot', 'spl/u-boot-spl', 'tpl/u-boot-tpl']
Simon Glassfd3eea12015-02-05 22:06:13 -070026
Simon Glass31837da2023-09-07 10:00:17 -060027# Common extensions for images
28COMMON_EXTS = ['.bin', '.rom', '.itb', '.img']
29
Simon Glass465567f2023-07-19 17:49:26 -060030def mkdir(dirname, parents=False):
Simon Glass4a1e88b2014-08-09 15:33:00 -060031 """Make a directory if it doesn't already exist.
32
33 Args:
Simon Glass465567f2023-07-19 17:49:26 -060034 dirname (str): Directory to create
35 parents (bool): True to also make parent directories
36
37 Raises:
38 OSError: File already exists
Simon Glass4a1e88b2014-08-09 15:33:00 -060039 """
40 try:
Thierry Reding336d5bd2014-08-19 10:22:39 +020041 if parents:
42 os.makedirs(dirname)
43 else:
44 os.mkdir(dirname)
Simon Glass4a1e88b2014-08-09 15:33:00 -060045 except OSError as err:
46 if err.errno == errno.EEXIST:
Lothar Waßmannce6df922018-04-08 05:14:11 -060047 if os.path.realpath('.') == os.path.realpath(dirname):
Simon Glassd07e5532023-07-19 17:49:09 -060048 print(f"Cannot create the current working directory '{dirname}'!")
Lothar Waßmannce6df922018-04-08 05:14:11 -060049 sys.exit(1)
Simon Glass4a1e88b2014-08-09 15:33:00 -060050 else:
51 raise
52
Simon Glasscb2de022023-07-19 17:49:20 -060053
54def _remove_old_outputs(out_dir):
55 """Remove any old output-target files
56
57 Args:
58 out_dir (str): Output directory for the build
59
60 Since we use a build directory that was previously used by another
61 board, it may have produced an SPL image. If we don't remove it (i.e.
62 see do_config and self.mrproper below) then it will appear to be the
63 output of this build, even if it does not produce SPL images.
64 """
65 for elf in BASE_ELF_FILENAMES:
66 fname = os.path.join(out_dir, elf)
67 if os.path.exists(fname):
68 os.remove(fname)
69
70
Simon Glass9d29f952023-07-19 17:49:27 -060071def copy_files(out_dir, build_dir, dirname, patterns):
72 """Copy files from the build directory to the output.
73
74 Args:
75 out_dir (str): Path to output directory containing the files
76 build_dir (str): Place to copy the files
77 dirname (str): Source directory, '' for normal U-Boot, 'spl' for SPL
78 patterns (list of str): A list of filenames to copy, each relative
79 to the build directory
80 """
81 for pattern in patterns:
82 file_list = glob.glob(os.path.join(out_dir, dirname, pattern))
83 for fname in file_list:
84 target = os.path.basename(fname)
85 if dirname:
86 base, ext = os.path.splitext(target)
87 if ext:
88 target = f'{base}-{dirname}{ext}'
89 shutil.copy(fname, os.path.join(build_dir, target))
90
91
Simon Glassd07e5532023-07-19 17:49:09 -060092# pylint: disable=R0903
Simon Glass4a1e88b2014-08-09 15:33:00 -060093class BuilderJob:
94 """Holds information about a job to be performed by a thread
95
96 Members:
Simon Glass8132f982022-07-11 19:03:57 -060097 brd: Board object to build
Simon Glassdf890152020-03-18 09:42:41 -060098 commits: List of Commit objects to build
99 keep_outputs: True to save build output files
100 step: 1 to process every commit, n to process every nth commit
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600101 work_in_output: Use the output directory as the work directory and
102 don't write to a separate output directory.
Simon Glass4a1e88b2014-08-09 15:33:00 -0600103 """
104 def __init__(self):
Simon Glass8132f982022-07-11 19:03:57 -0600105 self.brd = None
Simon Glass4a1e88b2014-08-09 15:33:00 -0600106 self.commits = []
Simon Glassdf890152020-03-18 09:42:41 -0600107 self.keep_outputs = False
108 self.step = 1
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600109 self.work_in_output = False
Simon Glass4a1e88b2014-08-09 15:33:00 -0600110
111
112class ResultThread(threading.Thread):
113 """This thread processes results from builder threads.
114
115 It simply passes the results on to the builder. There is only one
116 result thread, and this helps to serialise the build output.
117 """
118 def __init__(self, builder):
119 """Set up a new result thread
120
121 Args:
122 builder: Builder which will be sent each result
123 """
124 threading.Thread.__init__(self)
125 self.builder = builder
126
127 def run(self):
128 """Called to start up the result thread.
129
130 We collect the next result job and pass it on to the build.
131 """
132 while True:
133 result = self.builder.out_queue.get()
Simon Glassbc74d942023-07-19 17:49:06 -0600134 self.builder.process_result(result)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600135 self.builder.out_queue.task_done()
136
137
138class BuilderThread(threading.Thread):
139 """This thread builds U-Boot for a particular board.
140
141 An input queue provides each new job. We run 'make' to build U-Boot
142 and then pass the results on to the output queue.
143
144 Members:
145 builder: The builder which contains information we might need
146 thread_num: Our thread number (0-n-1), used to decide on a
Simon Glassa29b3ea2021-04-11 16:27:25 +1200147 temporary directory. If this is -1 then there are no threads
148 and we are the (only) main process
149 mrproper: Use 'make mrproper' before each reconfigure
150 per_board_out_dir: True to build in a separate persistent directory per
151 board rather than a thread-specific directory
152 test_exception: Used for testing; True to raise an exception instead of
153 reporting the build result
Simon Glass4a1e88b2014-08-09 15:33:00 -0600154 """
Simon Glass9bf9a722021-04-11 16:27:27 +1200155 def __init__(self, builder, thread_num, mrproper, per_board_out_dir,
156 test_exception=False):
Simon Glass4a1e88b2014-08-09 15:33:00 -0600157 """Set up a new builder thread"""
158 threading.Thread.__init__(self)
159 self.builder = builder
160 self.thread_num = thread_num
Simon Glass6029af12020-04-09 15:08:51 -0600161 self.mrproper = mrproper
Stephen Warren97c96902016-04-11 10:48:44 -0600162 self.per_board_out_dir = per_board_out_dir
Simon Glass9bf9a722021-04-11 16:27:27 +1200163 self.test_exception = test_exception
Simon Glassd07e5532023-07-19 17:49:09 -0600164 self.toolchain = None
Simon Glass4a1e88b2014-08-09 15:33:00 -0600165
Simon Glassc5077c32023-07-19 17:49:08 -0600166 def make(self, commit, brd, stage, cwd, *args, **kwargs):
Simon Glass4a1e88b2014-08-09 15:33:00 -0600167 """Run 'make' on a particular commit and board.
168
169 The source code will already be checked out, so the 'commit'
170 argument is only for information.
171
172 Args:
Simon Glass465567f2023-07-19 17:49:26 -0600173 commit (Commit): Commit that is being built
174 brd (Board): Board that is being built
175 stage (str): Stage of the build. Valid stages are:
Roger Meiere0a0e552014-08-20 22:10:29 +0200176 mrproper - can be called to clean source
Simon Glass4a1e88b2014-08-09 15:33:00 -0600177 config - called to configure for a board
178 build - the main make invocation - it does the build
Simon Glass465567f2023-07-19 17:49:26 -0600179 cwd (str): Working directory to set, or None to leave it alone
180 *args (list of str): Arguments to pass to 'make'
181 **kwargs (dict): A list of keyword arguments to pass to
182 command.run_pipe()
Simon Glass4a1e88b2014-08-09 15:33:00 -0600183
184 Returns:
185 CommandResult object
186 """
187 return self.builder.do_make(commit, brd, stage, cwd, *args,
188 **kwargs)
189
Simon Glass926c11b2023-07-19 17:49:15 -0600190 def _build_args(self, brd, out_dir, out_rel_dir, work_dir, commit_upto):
Simon Glass8d0b6b42023-07-19 17:49:13 -0600191 """Set up arguments to the args list based on the settings
192
193 Args:
Simon Glassec7132b2023-07-19 17:49:14 -0600194 brd (Board): Board to create arguments for
Simon Glass926c11b2023-07-19 17:49:15 -0600195 out_dir (str): Path to output directory containing the files
196 out_rel_dir (str): Output directory relative to the current dir
197 work_dir (str): Directory to which the source will be checked out
198 commit_upto (int): Commit number to build (0...n-1)
199
200 Returns:
201 tuple:
202 list of str: Arguments to pass to make
203 str: Current working directory, or None if no commit
204 str: Source directory (typically the work directory)
Simon Glass8d0b6b42023-07-19 17:49:13 -0600205 """
Simon Glass926c11b2023-07-19 17:49:15 -0600206 args = []
207 cwd = work_dir
208 src_dir = os.path.realpath(work_dir)
209 if not self.builder.in_tree:
210 if commit_upto is None:
211 # In this case we are building in the original source directory
212 # (i.e. the current directory where buildman is invoked. The
213 # output directory is set to this thread's selected work
214 # directory.
215 #
216 # Symlinks can confuse U-Boot's Makefile since we may use '..'
217 # in our path, so remove them.
218 real_dir = os.path.realpath(out_dir)
219 args.append(f'O={real_dir}')
220 cwd = None
221 src_dir = os.getcwd()
222 else:
223 args.append(f'O={out_rel_dir}')
Simon Glass8d0b6b42023-07-19 17:49:13 -0600224 if self.builder.verbose_build:
225 args.append('V=1')
226 else:
227 args.append('-s')
228 if self.builder.num_jobs is not None:
229 args.extend(['-j', str(self.builder.num_jobs)])
230 if self.builder.warnings_as_errors:
231 args.append('KCFLAGS=-Werror')
232 args.append('HOSTCFLAGS=-Werror')
233 if self.builder.allow_missing:
234 args.append('BINMAN_ALLOW_MISSING=1')
235 if self.builder.no_lto:
236 args.append('NO_LTO=1')
237 if self.builder.reproducible_builds:
238 args.append('SOURCE_DATE_EPOCH=0')
Simon Glassec7132b2023-07-19 17:49:14 -0600239 args.extend(self.builder.toolchains.GetMakeArguments(brd))
240 args.extend(self.toolchain.MakeArgs())
Simon Glass926c11b2023-07-19 17:49:15 -0600241 return args, cwd, src_dir
Simon Glass8d0b6b42023-07-19 17:49:13 -0600242
Simon Glass147f3492023-07-19 17:49:17 -0600243 def _reconfigure(self, commit, brd, cwd, args, env, config_args, config_out,
Simon Glass0850c0d2024-06-23 11:55:09 -0600244 cmd_list, mrproper):
Simon Glass147f3492023-07-19 17:49:17 -0600245 """Reconfigure the build
246
247 Args:
248 commit (Commit): Commit only being built
249 brd (Board): Board being built
250 cwd (str): Current working directory
251 args (list of str): Arguments to pass to make
252 env (dict): Environment strings
253 config_args (list of str): defconfig arg for this board
254 cmd_list (list of str): List to add the commands to, for logging
Simon Glass0850c0d2024-06-23 11:55:09 -0600255 mrproper (bool): True to run mrproper first
Simon Glass147f3492023-07-19 17:49:17 -0600256
257 Returns:
258 CommandResult object
259 """
Simon Glass0850c0d2024-06-23 11:55:09 -0600260 if mrproper:
Simon Glass147f3492023-07-19 17:49:17 -0600261 result = self.make(commit, brd, 'mrproper', cwd, 'mrproper', *args,
262 env=env)
263 config_out.write(result.combined)
264 cmd_list.append([self.builder.gnu_make, 'mrproper', *args])
265 result = self.make(commit, brd, 'config', cwd, *(args + config_args),
266 env=env)
267 cmd_list.append([self.builder.gnu_make] + args + config_args)
268 config_out.write(result.combined)
269 return result
270
Simon Glass3b4f50e2023-07-19 17:49:18 -0600271 def _build(self, commit, brd, cwd, args, env, cmd_list, config_only):
272 """Perform the build
273
274 Args:
275 commit (Commit): Commit only being built
276 brd (Board): Board being built
277 cwd (str): Current working directory
278 args (list of str): Arguments to pass to make
279 env (dict): Environment strings
280 cmd_list (list of str): List to add the commands to, for logging
281 config_only (bool): True if this is a config-only build (using the
282 'make cfg' target)
283
284 Returns:
285 CommandResult object
286 """
287 if config_only:
288 args.append('cfg')
289 result = self.make(commit, brd, 'build', cwd, *args, env=env)
290 cmd_list.append([self.builder.gnu_make] + args)
291 if (result.return_code == 2 and
292 ('Some images are invalid' in result.stderr)):
293 # This is handled later by the check for output in stderr
294 result.return_code = 0
295 return result
296
Simon Glassb9a1b772023-07-19 17:49:24 -0600297 def _read_done_file(self, commit_upto, brd, force_build,
Simon Glassfdd7be22023-07-19 17:49:19 -0600298 force_build_failures):
299 """Check the 'done' file and see if this commit should be built
300
301 Args:
302 commit (Commit): Commit only being built
303 brd (Board): Board being built
Simon Glassfdd7be22023-07-19 17:49:19 -0600304 force_build (bool): Force a build even if one was previously done
305 force_build_failures (bool): Force a bulid if the previous result
306 showed failure
307
308 Returns:
Simon Glassb9a1b772023-07-19 17:49:24 -0600309 tuple:
310 bool: True if build should be built
311 CommandResult: if there was a previous run:
312 - already_done set to True
313 - return_code set to return code
314 - result.stderr set to 'bad' if stderr output was recorded
Simon Glassfdd7be22023-07-19 17:49:19 -0600315 """
Simon Glassb9a1b772023-07-19 17:49:24 -0600316 result = command.CommandResult()
Simon Glassfdd7be22023-07-19 17:49:19 -0600317 done_file = self.builder.get_done_file(commit_upto, brd.target)
318 result.already_done = os.path.exists(done_file)
319 will_build = (force_build or force_build_failures or
320 not result.already_done)
321 if result.already_done:
322 with open(done_file, 'r', encoding='utf-8') as outf:
323 try:
324 result.return_code = int(outf.readline())
325 except ValueError:
326 # The file may be empty due to running out of disk space.
327 # Try a rebuild
328 result.return_code = RETURN_CODE_RETRY
329
330 # Check the signal that the build needs to be retried
331 if result.return_code == RETURN_CODE_RETRY:
332 will_build = True
333 elif will_build:
334 err_file = self.builder.get_err_file(commit_upto, brd.target)
335 if os.path.exists(err_file) and os.stat(err_file).st_size:
336 result.stderr = 'bad'
337 elif not force_build:
338 # The build passed, so no need to build it again
339 will_build = False
Simon Glassb9a1b772023-07-19 17:49:24 -0600340 return will_build, result
Simon Glassfdd7be22023-07-19 17:49:19 -0600341
Simon Glass83a0b862023-07-19 17:49:21 -0600342 def _decide_dirs(self, brd, work_dir, work_in_output):
343 """Decide the output directory to use
344
345 Args:
346 work_dir (str): Directory to which the source will be checked out
347 work_in_output (bool): Use the output directory as the work
348 directory and don't write to a separate output directory.
349
350 Returns:
351 tuple:
352 out_dir (str): Output directory for the build
353 out_rel_dir (str): Output directory relatie to the current dir
354 """
355 if work_in_output or self.builder.in_tree:
356 out_rel_dir = None
357 out_dir = work_dir
358 else:
359 if self.per_board_out_dir:
360 out_rel_dir = os.path.join('..', brd.target)
361 else:
362 out_rel_dir = 'build'
363 out_dir = os.path.join(work_dir, out_rel_dir)
364 return out_dir, out_rel_dir
365
Simon Glass7e989052023-07-19 17:49:22 -0600366 def _checkout(self, commit_upto, work_dir):
367 """Checkout the right commit
368
369 Args:
370 commit_upto (int): Commit number to build (0...n-1)
371 work_dir (str): Directory to which the source will be checked out
372
373 Returns:
374 Commit: Commit being built, or 'current' for current source
375 """
376 if self.builder.commits:
377 commit = self.builder.commits[commit_upto]
378 if self.builder.checkout:
379 git_dir = os.path.join(work_dir, '.git')
380 gitutil.checkout(commit.hash, git_dir, work_dir, force=True)
381 else:
382 commit = 'current'
383 return commit
384
Simon Glass5d05de52024-06-23 11:55:10 -0600385 def _config_and_build(self, commit_upto, brd, work_dir, do_config, mrproper,
Simon Glass2df95fe2023-07-19 17:49:23 -0600386 config_only, adjust_cfg, commit, out_dir, out_rel_dir,
387 result):
388 """Do the build, configuring first if necessary
389
390 Args:
391 commit_upto (int): Commit number to build (0...n-1)
392 brd (Board): Board to create arguments for
393 work_dir (str): Directory to which the source will be checked out
394 do_config (bool): True to run a make <board>_defconfig on the source
Simon Glass5d05de52024-06-23 11:55:10 -0600395 mrproper (bool): True to run mrproper first
Simon Glass2df95fe2023-07-19 17:49:23 -0600396 config_only (bool): Only configure the source, do not build it
397 adjust_cfg (list of str): See the cfgutil module and run_commit()
398 commit (Commit): Commit only being built
399 out_dir (str): Output directory for the build
400 out_rel_dir (str): Output directory relatie to the current dir
401 result (CommandResult): Previous result
402
403 Returns:
404 tuple:
405 result (CommandResult): Result of the build
406 do_config (bool): indicates whether 'make config' is needed on
407 the next incremental build
408 """
409 # Set up the environment and command line
Simon Glass600ede92024-08-15 13:57:45 -0600410 env = self.builder.make_environment(self.toolchain)
Simon Glass2df95fe2023-07-19 17:49:23 -0600411 mkdir(out_dir)
412
413 args, cwd, src_dir = self._build_args(brd, out_dir, out_rel_dir,
414 work_dir, commit_upto)
415 config_args = [f'{brd.target}_defconfig']
416 config_out = io.StringIO()
417
418 _remove_old_outputs(out_dir)
419
420 # If we need to reconfigure, do that now
421 cfg_file = os.path.join(out_dir, '.config')
422 cmd_list = []
423 if do_config or adjust_cfg:
424 result = self._reconfigure(
Simon Glass0850c0d2024-06-23 11:55:09 -0600425 commit, brd, cwd, args, env, config_args, config_out, cmd_list,
Simon Glass5d05de52024-06-23 11:55:10 -0600426 mrproper)
Simon Glass2df95fe2023-07-19 17:49:23 -0600427 do_config = False # No need to configure next time
428 if adjust_cfg:
429 cfgutil.adjust_cfg_file(cfg_file, adjust_cfg)
430
431 # Now do the build, if everything looks OK
432 if result.return_code == 0:
Simon Glassd6c1ec82023-10-26 14:31:10 -0400433 if adjust_cfg:
434 oldc_args = list(args) + ['oldconfig']
435 oldc_result = self.make(commit, brd, 'oldconfig', cwd,
436 *oldc_args, env=env)
437 if oldc_result.return_code:
438 return oldc_result
Simon Glass2df95fe2023-07-19 17:49:23 -0600439 result = self._build(commit, brd, cwd, args, env, cmd_list,
440 config_only)
441 if adjust_cfg:
442 errs = cfgutil.check_cfg_file(cfg_file, adjust_cfg)
443 if errs:
444 result.stderr += errs
445 result.return_code = 1
446 result.stderr = result.stderr.replace(src_dir + '/', '')
447 if self.builder.verbose_build:
448 result.stdout = config_out.getvalue() + result.stdout
449 result.cmd_list = cmd_list
450 return result, do_config
Simon Glass7e989052023-07-19 17:49:22 -0600451
Simon Glassa7b944f2024-06-23 11:55:11 -0600452 def run_commit(self, commit_upto, brd, work_dir, do_config, mrproper,
453 config_only, force_build, force_build_failures,
454 work_in_output, adjust_cfg):
Simon Glass4a1e88b2014-08-09 15:33:00 -0600455 """Build a particular commit.
456
457 If the build is already done, and we are not forcing a build, we skip
458 the build and just return the previously-saved results.
459
460 Args:
Simon Glass465567f2023-07-19 17:49:26 -0600461 commit_upto (int): Commit number to build (0...n-1)
462 brd (Board): Board to build
463 work_dir (str): Directory to which the source will be checked out
464 do_config (bool): True to run a make <board>_defconfig on the source
Simon Glassa7b944f2024-06-23 11:55:11 -0600465 mrproper (bool): True to run mrproper first
Simon Glass465567f2023-07-19 17:49:26 -0600466 config_only (bool): Only configure the source, do not build it
467 force_build (bool): Force a build even if one was previously done
468 force_build_failures (bool): Force a bulid if the previous result
469 showed failure
470 work_in_output (bool) : Use the output directory as the work
471 directory and don't write to a separate output directory.
Simon Glasse5650a82022-01-22 05:07:33 -0700472 adjust_cfg (list of str): List of changes to make to .config file
473 before building. Each is one of (where C is either CONFIG_xxx
474 or just xxx):
475 C to enable C
476 ~C to disable C
477 C=val to set the value of C (val must have quotes if C is
478 a string Kconfig
Simon Glass4a1e88b2014-08-09 15:33:00 -0600479
480 Returns:
481 tuple containing:
482 - CommandResult object containing the results of the build
483 - boolean indicating whether 'make config' is still needed
484 """
485 # Create a default result - it will be overwritte by the call to
Simon Glassc5077c32023-07-19 17:49:08 -0600486 # self.make() below, in the event that we do a build.
Simon Glass83a0b862023-07-19 17:49:21 -0600487 out_dir, out_rel_dir = self._decide_dirs(brd, work_dir, work_in_output)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600488
489 # Check if the job was already completed last time
Simon Glassb9a1b772023-07-19 17:49:24 -0600490 will_build, result = self._read_done_file(commit_upto, brd, force_build,
491 force_build_failures)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600492
493 if will_build:
494 # We are going to have to build it. First, get a toolchain
495 if not self.toolchain:
496 try:
497 self.toolchain = self.builder.toolchains.Select(brd.arch)
498 except ValueError as err:
499 result.return_code = 10
500 result.stdout = ''
Simon Glassbf353b82023-07-19 17:49:25 -0600501 result.stderr = f'Tool chain error for {brd.arch}: {str(err)}'
Simon Glass4a1e88b2014-08-09 15:33:00 -0600502
503 if self.toolchain:
Simon Glass7e989052023-07-19 17:49:22 -0600504 commit = self._checkout(commit_upto, work_dir)
Simon Glass2df95fe2023-07-19 17:49:23 -0600505 result, do_config = self._config_and_build(
Simon Glassa7b944f2024-06-23 11:55:11 -0600506 commit_upto, brd, work_dir, do_config, mrproper,
Simon Glass5d05de52024-06-23 11:55:10 -0600507 config_only, adjust_cfg, commit, out_dir, out_rel_dir,
508 result)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600509 result.already_done = False
510
511 result.toolchain = self.toolchain
512 result.brd = brd
513 result.commit_upto = commit_upto
514 result.out_dir = out_dir
515 return result, do_config
516
Simon Glassc5077c32023-07-19 17:49:08 -0600517 def _write_result(self, result, keep_outputs, work_in_output):
Simon Glass4a1e88b2014-08-09 15:33:00 -0600518 """Write a built result to the output directory.
519
520 Args:
Simon Glass465567f2023-07-19 17:49:26 -0600521 result (CommandResult): result to write
522 keep_outputs (bool): True to store the output binaries, False
Simon Glass4a1e88b2014-08-09 15:33:00 -0600523 to delete them
Simon Glass465567f2023-07-19 17:49:26 -0600524 work_in_output (bool): Use the output directory as the work
525 directory and don't write to a separate output directory.
Simon Glass4a1e88b2014-08-09 15:33:00 -0600526 """
Simon Glassfd3eea12015-02-05 22:06:13 -0700527 # If we think this might have been aborted with Ctrl-C, record the
528 # failure but not that we are 'done' with this board. A retry may fix
529 # it.
Simon Glass2e737452021-10-19 21:43:23 -0600530 maybe_aborted = result.stderr and 'No child processes' in result.stderr
Simon Glass4a1e88b2014-08-09 15:33:00 -0600531
Simon Glass2e737452021-10-19 21:43:23 -0600532 if result.return_code >= 0 and result.already_done:
Simon Glass4a1e88b2014-08-09 15:33:00 -0600533 return
534
535 # Write the output and stderr
Simon Glass4cb54682023-07-19 17:49:10 -0600536 output_dir = self.builder.get_output_dir(result.commit_upto)
Simon Glassc5077c32023-07-19 17:49:08 -0600537 mkdir(output_dir)
Simon Glassbc74d942023-07-19 17:49:06 -0600538 build_dir = self.builder.get_build_dir(result.commit_upto,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600539 result.brd.target)
Simon Glassc5077c32023-07-19 17:49:08 -0600540 mkdir(build_dir)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600541
542 outfile = os.path.join(build_dir, 'log')
Simon Glassd07e5532023-07-19 17:49:09 -0600543 with open(outfile, 'w', encoding='utf-8') as outf:
Simon Glass4a1e88b2014-08-09 15:33:00 -0600544 if result.stdout:
Simon Glassd07e5532023-07-19 17:49:09 -0600545 outf.write(result.stdout)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600546
Simon Glassbc74d942023-07-19 17:49:06 -0600547 errfile = self.builder.get_err_file(result.commit_upto,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600548 result.brd.target)
549 if result.stderr:
Simon Glassd07e5532023-07-19 17:49:09 -0600550 with open(errfile, 'w', encoding='utf-8') as outf:
551 outf.write(result.stderr)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600552 elif os.path.exists(errfile):
553 os.remove(errfile)
554
Simon Glass2e737452021-10-19 21:43:23 -0600555 # Fatal error
556 if result.return_code < 0:
557 return
558
Simon Glassdc311842024-12-14 11:20:23 -0700559 done_file = self.builder.get_done_file(result.commit_upto,
560 result.brd.target)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600561 if result.toolchain:
562 # Write the build result and toolchain information.
Simon Glassd07e5532023-07-19 17:49:09 -0600563 with open(done_file, 'w', encoding='utf-8') as outf:
Simon Glassfd3eea12015-02-05 22:06:13 -0700564 if maybe_aborted:
565 # Special code to indicate we need to retry
Simon Glassd07e5532023-07-19 17:49:09 -0600566 outf.write(f'{RETURN_CODE_RETRY}')
Simon Glassfd3eea12015-02-05 22:06:13 -0700567 else:
Simon Glassd07e5532023-07-19 17:49:09 -0600568 outf.write(f'{result.return_code}')
569 with open(os.path.join(build_dir, 'toolchain'), 'w',
570 encoding='utf-8') as outf:
571 print('gcc', result.toolchain.gcc, file=outf)
572 print('path', result.toolchain.path, file=outf)
573 print('cross', result.toolchain.cross, file=outf)
574 print('arch', result.toolchain.arch, file=outf)
575 outf.write(f'{result.return_code}')
Simon Glass4a1e88b2014-08-09 15:33:00 -0600576
Simon Glass4a1e88b2014-08-09 15:33:00 -0600577 # Write out the image and function size information and an objdump
Simon Glass600ede92024-08-15 13:57:45 -0600578 env = self.builder.make_environment(self.toolchain)
Simon Glassd07e5532023-07-19 17:49:09 -0600579 with open(os.path.join(build_dir, 'out-env'), 'wb') as outf:
Simon Glass9e90d312019-01-07 16:44:23 -0700580 for var in sorted(env.keys()):
Simon Glassd07e5532023-07-19 17:49:09 -0600581 outf.write(b'%s="%s"' % (var, env[var]))
Simon Glass1382b1d2023-02-21 12:40:27 -0700582
583 with open(os.path.join(build_dir, 'out-cmd'), 'w',
Simon Glassd07e5532023-07-19 17:49:09 -0600584 encoding='utf-8') as outf:
Simon Glass1382b1d2023-02-21 12:40:27 -0700585 for cmd in result.cmd_list:
Simon Glassd07e5532023-07-19 17:49:09 -0600586 print(' '.join(cmd), file=outf)
Simon Glass1382b1d2023-02-21 12:40:27 -0700587
Simon Glass4a1e88b2014-08-09 15:33:00 -0600588 lines = []
Simon Glasse0f19712020-12-16 17:24:17 -0700589 for fname in BASE_ELF_FILENAMES:
Simon Glassd07e5532023-07-19 17:49:09 -0600590 cmd = [f'{self.toolchain.cross}nm', '--size-sort', fname]
Simon Glass840be732022-01-29 14:14:05 -0700591 nm_result = command.run_pipe([cmd], capture=True,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600592 capture_stderr=True, cwd=result.out_dir,
593 raise_on_error=False, env=env)
594 if nm_result.stdout:
Simon Glassd07e5532023-07-19 17:49:09 -0600595 nm_fname = self.builder.get_func_sizes_file(
596 result.commit_upto, result.brd.target, fname)
597 with open(nm_fname, 'w', encoding='utf-8') as outf:
598 print(nm_result.stdout, end=' ', file=outf)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600599
Simon Glassd07e5532023-07-19 17:49:09 -0600600 cmd = [f'{self.toolchain.cross}objdump', '-h', fname]
Simon Glass840be732022-01-29 14:14:05 -0700601 dump_result = command.run_pipe([cmd], capture=True,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600602 capture_stderr=True, cwd=result.out_dir,
603 raise_on_error=False, env=env)
604 rodata_size = ''
605 if dump_result.stdout:
Simon Glassbc74d942023-07-19 17:49:06 -0600606 objdump = self.builder.get_objdump_file(result.commit_upto,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600607 result.brd.target, fname)
Simon Glassd07e5532023-07-19 17:49:09 -0600608 with open(objdump, 'w', encoding='utf-8') as outf:
609 print(dump_result.stdout, end=' ', file=outf)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600610 for line in dump_result.stdout.splitlines():
611 fields = line.split()
612 if len(fields) > 5 and fields[1] == '.rodata':
613 rodata_size = fields[2]
614
Simon Glassd07e5532023-07-19 17:49:09 -0600615 cmd = [f'{self.toolchain.cross}size', fname]
Simon Glass840be732022-01-29 14:14:05 -0700616 size_result = command.run_pipe([cmd], capture=True,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600617 capture_stderr=True, cwd=result.out_dir,
618 raise_on_error=False, env=env)
619 if size_result.stdout:
620 lines.append(size_result.stdout.splitlines()[1] + ' ' +
621 rodata_size)
622
Alex Kiernanf07ed232018-05-31 04:48:33 +0000623 # Extract the environment from U-Boot and dump it out
Simon Glassd07e5532023-07-19 17:49:09 -0600624 cmd = [f'{self.toolchain.cross}objcopy', '-O', 'binary',
Alex Kiernanf07ed232018-05-31 04:48:33 +0000625 '-j', '.rodata.default_environment',
626 'env/built-in.o', 'uboot.env']
Simon Glass840be732022-01-29 14:14:05 -0700627 command.run_pipe([cmd], capture=True,
Alex Kiernanf07ed232018-05-31 04:48:33 +0000628 capture_stderr=True, cwd=result.out_dir,
629 raise_on_error=False, env=env)
Simon Glasse3c85ab2020-04-17 17:51:34 -0600630 if not work_in_output:
Simon Glass9d29f952023-07-19 17:49:27 -0600631 copy_files(result.out_dir, build_dir, '', ['uboot.env'])
Alex Kiernanf07ed232018-05-31 04:48:33 +0000632
Simon Glass4a1e88b2014-08-09 15:33:00 -0600633 # Write out the image sizes file. This is similar to the output
634 # of binutil's 'size' utility, but it omits the header line and
635 # adds an additional hex value at the end of each line for the
636 # rodata size
Simon Glassd07e5532023-07-19 17:49:09 -0600637 if lines:
Simon Glassbc74d942023-07-19 17:49:06 -0600638 sizes = self.builder.get_sizes_file(result.commit_upto,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600639 result.brd.target)
Simon Glassd07e5532023-07-19 17:49:09 -0600640 with open(sizes, 'w', encoding='utf-8') as outf:
641 print('\n'.join(lines), file=outf)
Simon Glassdc311842024-12-14 11:20:23 -0700642 else:
643 # Indicate that the build failure due to lack of toolchain
644 tools.write_file(done_file, '2\n', binary=False)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600645
Simon Glasse3c85ab2020-04-17 17:51:34 -0600646 if not work_in_output:
647 # Write out the configuration files, with a special case for SPL
648 for dirname in ['', 'spl', 'tpl']:
Simon Glass9d29f952023-07-19 17:49:27 -0600649 copy_files(
Simon Glasse3c85ab2020-04-17 17:51:34 -0600650 result.out_dir, build_dir, dirname,
651 ['u-boot.cfg', 'spl/u-boot-spl.cfg', 'tpl/u-boot-tpl.cfg',
652 '.config', 'include/autoconf.mk',
653 'include/generated/autoconf.h'])
Simon Glass4b14c532015-02-05 22:06:14 -0700654
Simon Glasse3c85ab2020-04-17 17:51:34 -0600655 # Now write the actual build output
656 if keep_outputs:
Simon Glass31837da2023-09-07 10:00:17 -0600657 to_copy = ['u-boot*', '*.map', 'MLO', 'SPL',
658 'include/autoconf.mk', 'spl/u-boot-spl*',
659 'tpl/u-boot-tpl*', 'vpl/u-boot-vpl*']
660 to_copy += [f'*{ext}' for ext in COMMON_EXTS]
661 copy_files(result.out_dir, build_dir, '', to_copy)
Simon Glass4b14c532015-02-05 22:06:14 -0700662
Simon Glassc5077c32023-07-19 17:49:08 -0600663 def _send_result(self, result):
Simon Glassa3d01442021-04-11 16:27:26 +1200664 """Send a result to the builder for processing
665
666 Args:
Simon Glass465567f2023-07-19 17:49:26 -0600667 result (CommandResult): results of the build
Simon Glass9bf9a722021-04-11 16:27:27 +1200668
669 Raises:
Simon Glass465567f2023-07-19 17:49:26 -0600670 ValueError: self.test_exception is true (for testing)
Simon Glassa3d01442021-04-11 16:27:26 +1200671 """
Simon Glass9bf9a722021-04-11 16:27:27 +1200672 if self.test_exception:
673 raise ValueError('test exception')
Simon Glassa3d01442021-04-11 16:27:26 +1200674 if self.thread_num != -1:
675 self.builder.out_queue.put(result)
676 else:
Simon Glassbc74d942023-07-19 17:49:06 -0600677 self.builder.process_result(result)
Simon Glassa3d01442021-04-11 16:27:26 +1200678
Simon Glassc5077c32023-07-19 17:49:08 -0600679 def run_job(self, job):
Simon Glass4a1e88b2014-08-09 15:33:00 -0600680 """Run a single job
681
682 A job consists of a building a list of commits for a particular board.
683
684 Args:
Simon Glass465567f2023-07-19 17:49:26 -0600685 job (Job): Job to build
Simon Glassc635d892021-01-30 22:17:46 -0700686
Simon Glass465567f2023-07-19 17:49:26 -0600687 Raises:
688 ValueError: Thread was interrupted
Simon Glass4a1e88b2014-08-09 15:33:00 -0600689 """
Simon Glass8132f982022-07-11 19:03:57 -0600690 brd = job.brd
Simon Glassbc74d942023-07-19 17:49:06 -0600691 work_dir = self.builder.get_thread_dir(self.thread_num)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600692 self.toolchain = None
693 if job.commits:
694 # Run 'make board_defconfig' on the first commit
695 do_config = True
696 commit_upto = 0
697 force_build = False
698 for commit_upto in range(0, len(job.commits), job.step):
Simon Glassc5077c32023-07-19 17:49:08 -0600699 result, request_config = self.run_commit(commit_upto, brd,
Simon Glassa7b944f2024-06-23 11:55:11 -0600700 work_dir, do_config, self.mrproper,
701 self.builder.config_only,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600702 force_build or self.builder.force_build,
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600703 self.builder.force_build_failures,
Simon Glasse5650a82022-01-22 05:07:33 -0700704 job.work_in_output, job.adjust_cfg)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600705 failed = result.return_code or result.stderr
706 did_config = do_config
Simon Glasscf76d0a2024-06-23 11:55:12 -0600707 if failed and not do_config and not self.mrproper:
Simon Glass4a1e88b2014-08-09 15:33:00 -0600708 # If our incremental build failed, try building again
709 # with a reconfig.
710 if self.builder.force_config_on_failure:
Simon Glassc5077c32023-07-19 17:49:08 -0600711 result, request_config = self.run_commit(commit_upto,
Simon Glass222825b2024-06-23 11:55:13 -0600712 brd, work_dir, True,
713 self.mrproper or self.builder.fallback_mrproper,
714 False, True, False, job.work_in_output,
715 job.adjust_cfg)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600716 did_config = True
717 if not self.builder.force_reconfig:
718 do_config = request_config
719
720 # If we built that commit, then config is done. But if we got
721 # an warning, reconfig next time to force it to build the same
722 # files that created warnings this time. Otherwise an
723 # incremental build may not build the same file, and we will
724 # think that the warning has gone away.
725 # We could avoid this by using -Werror everywhere...
726 # For errors, the problem doesn't happen, since presumably
727 # the build stopped and didn't generate output, so will retry
728 # that file next time. So we could detect warnings and deal
729 # with them specially here. For now, we just reconfigure if
730 # anything goes work.
731 # Of course this is substantially slower if there are build
732 # errors/warnings (e.g. 2-3x slower even if only 10% of builds
733 # have problems).
734 if (failed and not result.already_done and not did_config and
735 self.builder.force_config_on_failure):
736 # If this build failed, try the next one with a
737 # reconfigure.
738 # Sometimes if the board_config.h file changes it can mess
739 # with dependencies, and we get:
740 # make: *** No rule to make target `include/autoconf.mk',
741 # needed by `depend'.
742 do_config = True
743 force_build = True
744 else:
745 force_build = False
746 if self.builder.force_config_on_failure:
747 if failed:
748 do_config = True
749 result.commit_upto = commit_upto
750 if result.return_code < 0:
751 raise ValueError('Interrupt')
752
753 # We have the build results, so output the result
Simon Glassc5077c32023-07-19 17:49:08 -0600754 self._write_result(result, job.keep_outputs, job.work_in_output)
755 self._send_result(result)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600756 else:
757 # Just build the currently checked-out build
Simon Glassc5077c32023-07-19 17:49:08 -0600758 result, request_config = self.run_commit(None, brd, work_dir, True,
Simon Glassa7b944f2024-06-23 11:55:11 -0600759 self.mrproper, self.builder.config_only, True,
Simon Glasse5650a82022-01-22 05:07:33 -0700760 self.builder.force_build_failures, job.work_in_output,
761 job.adjust_cfg)
Simon Glass2c508342024-06-23 11:55:14 -0600762 failed = result.return_code or result.stderr
763 if failed and not self.mrproper:
764 result, request_config = self.run_commit(None, brd, work_dir,
765 True, self.builder.fallback_mrproper,
766 self.builder.config_only, True,
767 self.builder.force_build_failures,
768 job.work_in_output, job.adjust_cfg)
769
Simon Glass4a1e88b2014-08-09 15:33:00 -0600770 result.commit_upto = 0
Simon Glassc5077c32023-07-19 17:49:08 -0600771 self._write_result(result, job.keep_outputs, job.work_in_output)
772 self._send_result(result)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600773
774 def run(self):
775 """Our thread's run function
776
777 This thread picks a job from the queue, runs it, and then goes to the
778 next job.
779 """
Simon Glass4a1e88b2014-08-09 15:33:00 -0600780 while True:
781 job = self.builder.queue.get()
Simon Glass9bf9a722021-04-11 16:27:27 +1200782 try:
Simon Glassc5077c32023-07-19 17:49:08 -0600783 self.run_job(job)
Simon Glassd07e5532023-07-19 17:49:09 -0600784 except Exception as exc:
785 print('Thread exception (use -T0 to run without threads):',
786 exc)
787 self.builder.thread_exceptions.append(exc)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600788 self.builder.queue.task_done()