blob: b4cb66397bb777562b4b5218a8534a7839c7e3cc [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
Simon Glass51f55182025-02-03 09:26:45 -0700182 command.run_one()
Simon Glass4a1e88b2014-08-09 15:33:00 -0600183
184 Returns:
185 CommandResult object
186 """
Simon Glass51f55182025-02-03 09:26:45 -0700187 return self.builder.do_make(commit, brd, stage, cwd, *args, **kwargs)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600188
Simon Glass926c11b2023-07-19 17:49:15 -0600189 def _build_args(self, brd, out_dir, out_rel_dir, work_dir, commit_upto):
Simon Glass8d0b6b42023-07-19 17:49:13 -0600190 """Set up arguments to the args list based on the settings
191
192 Args:
Simon Glassec7132b2023-07-19 17:49:14 -0600193 brd (Board): Board to create arguments for
Simon Glass926c11b2023-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 Glass8d0b6b42023-07-19 17:49:13 -0600204 """
Simon Glass926c11b2023-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 Glass8d0b6b42023-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 Glassec7132b2023-07-19 17:49:14 -0600238 args.extend(self.builder.toolchains.GetMakeArguments(brd))
239 args.extend(self.toolchain.MakeArgs())
Simon Glass926c11b2023-07-19 17:49:15 -0600240 return args, cwd, src_dir
Simon Glass8d0b6b42023-07-19 17:49:13 -0600241
Simon Glass147f3492023-07-19 17:49:17 -0600242 def _reconfigure(self, commit, brd, cwd, args, env, config_args, config_out,
Simon Glass0850c0d2024-06-23 11:55:09 -0600243 cmd_list, mrproper):
Simon Glass147f3492023-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 Glass0850c0d2024-06-23 11:55:09 -0600254 mrproper (bool): True to run mrproper first
Simon Glass147f3492023-07-19 17:49:17 -0600255
256 Returns:
257 CommandResult object
258 """
Simon Glass0850c0d2024-06-23 11:55:09 -0600259 if mrproper:
Simon Glass147f3492023-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 Glass3b4f50e2023-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')
Simon Glassb8ec3002024-12-17 06:26:16 -0700288 elif self.builder.build_target:
289 args.append(self.builder.build_target)
Simon Glass3b4f50e2023-07-19 17:49:18 -0600290 result = self.make(commit, brd, 'build', cwd, *args, env=env)
291 cmd_list.append([self.builder.gnu_make] + args)
292 if (result.return_code == 2 and
293 ('Some images are invalid' in result.stderr)):
294 # This is handled later by the check for output in stderr
295 result.return_code = 0
296 return result
297
Simon Glassb9a1b772023-07-19 17:49:24 -0600298 def _read_done_file(self, commit_upto, brd, force_build,
Simon Glassfdd7be22023-07-19 17:49:19 -0600299 force_build_failures):
300 """Check the 'done' file and see if this commit should be built
301
302 Args:
303 commit (Commit): Commit only being built
304 brd (Board): Board being built
Simon Glassfdd7be22023-07-19 17:49:19 -0600305 force_build (bool): Force a build even if one was previously done
306 force_build_failures (bool): Force a bulid if the previous result
307 showed failure
308
309 Returns:
Simon Glassb9a1b772023-07-19 17:49:24 -0600310 tuple:
311 bool: True if build should be built
312 CommandResult: if there was a previous run:
313 - already_done set to True
314 - return_code set to return code
315 - result.stderr set to 'bad' if stderr output was recorded
Simon Glassfdd7be22023-07-19 17:49:19 -0600316 """
Simon Glassb9a1b772023-07-19 17:49:24 -0600317 result = command.CommandResult()
Simon Glassfdd7be22023-07-19 17:49:19 -0600318 done_file = self.builder.get_done_file(commit_upto, brd.target)
319 result.already_done = os.path.exists(done_file)
320 will_build = (force_build or force_build_failures or
321 not result.already_done)
322 if result.already_done:
323 with open(done_file, 'r', encoding='utf-8') as outf:
324 try:
325 result.return_code = int(outf.readline())
326 except ValueError:
327 # The file may be empty due to running out of disk space.
328 # Try a rebuild
329 result.return_code = RETURN_CODE_RETRY
330
331 # Check the signal that the build needs to be retried
332 if result.return_code == RETURN_CODE_RETRY:
333 will_build = True
334 elif will_build:
335 err_file = self.builder.get_err_file(commit_upto, brd.target)
336 if os.path.exists(err_file) and os.stat(err_file).st_size:
337 result.stderr = 'bad'
338 elif not force_build:
339 # The build passed, so no need to build it again
340 will_build = False
Simon Glassb9a1b772023-07-19 17:49:24 -0600341 return will_build, result
Simon Glassfdd7be22023-07-19 17:49:19 -0600342
Simon Glass83a0b862023-07-19 17:49:21 -0600343 def _decide_dirs(self, brd, work_dir, work_in_output):
344 """Decide the output directory to use
345
346 Args:
347 work_dir (str): Directory to which the source will be checked out
348 work_in_output (bool): Use the output directory as the work
349 directory and don't write to a separate output directory.
350
351 Returns:
352 tuple:
353 out_dir (str): Output directory for the build
354 out_rel_dir (str): Output directory relatie to the current dir
355 """
356 if work_in_output or self.builder.in_tree:
357 out_rel_dir = None
358 out_dir = work_dir
359 else:
360 if self.per_board_out_dir:
361 out_rel_dir = os.path.join('..', brd.target)
362 else:
363 out_rel_dir = 'build'
364 out_dir = os.path.join(work_dir, out_rel_dir)
365 return out_dir, out_rel_dir
366
Simon Glass7e989052023-07-19 17:49:22 -0600367 def _checkout(self, commit_upto, work_dir):
368 """Checkout the right commit
369
370 Args:
371 commit_upto (int): Commit number to build (0...n-1)
372 work_dir (str): Directory to which the source will be checked out
373
374 Returns:
375 Commit: Commit being built, or 'current' for current source
376 """
377 if self.builder.commits:
378 commit = self.builder.commits[commit_upto]
379 if self.builder.checkout:
380 git_dir = os.path.join(work_dir, '.git')
381 gitutil.checkout(commit.hash, git_dir, work_dir, force=True)
382 else:
383 commit = 'current'
384 return commit
385
Simon Glass5d05de52024-06-23 11:55:10 -0600386 def _config_and_build(self, commit_upto, brd, work_dir, do_config, mrproper,
Simon Glass2df95fe2023-07-19 17:49:23 -0600387 config_only, adjust_cfg, commit, out_dir, out_rel_dir,
388 result):
389 """Do the build, configuring first if necessary
390
391 Args:
392 commit_upto (int): Commit number to build (0...n-1)
393 brd (Board): Board to create arguments for
394 work_dir (str): Directory to which the source will be checked out
395 do_config (bool): True to run a make <board>_defconfig on the source
Simon Glass5d05de52024-06-23 11:55:10 -0600396 mrproper (bool): True to run mrproper first
Simon Glass2df95fe2023-07-19 17:49:23 -0600397 config_only (bool): Only configure the source, do not build it
398 adjust_cfg (list of str): See the cfgutil module and run_commit()
399 commit (Commit): Commit only being built
400 out_dir (str): Output directory for the build
401 out_rel_dir (str): Output directory relatie to the current dir
402 result (CommandResult): Previous result
403
404 Returns:
405 tuple:
406 result (CommandResult): Result of the build
407 do_config (bool): indicates whether 'make config' is needed on
408 the next incremental build
409 """
410 # Set up the environment and command line
Simon Glass600ede92024-08-15 13:57:45 -0600411 env = self.builder.make_environment(self.toolchain)
Simon Glass2df95fe2023-07-19 17:49:23 -0600412 mkdir(out_dir)
413
414 args, cwd, src_dir = self._build_args(brd, out_dir, out_rel_dir,
415 work_dir, commit_upto)
416 config_args = [f'{brd.target}_defconfig']
417 config_out = io.StringIO()
418
419 _remove_old_outputs(out_dir)
420
421 # If we need to reconfigure, do that now
422 cfg_file = os.path.join(out_dir, '.config')
423 cmd_list = []
424 if do_config or adjust_cfg:
425 result = self._reconfigure(
Simon Glass0850c0d2024-06-23 11:55:09 -0600426 commit, brd, cwd, args, env, config_args, config_out, cmd_list,
Simon Glass5d05de52024-06-23 11:55:10 -0600427 mrproper)
Simon Glass2df95fe2023-07-19 17:49:23 -0600428 do_config = False # No need to configure next time
429 if adjust_cfg:
430 cfgutil.adjust_cfg_file(cfg_file, adjust_cfg)
431
432 # Now do the build, if everything looks OK
433 if result.return_code == 0:
Simon Glassd6c1ec82023-10-26 14:31:10 -0400434 if adjust_cfg:
435 oldc_args = list(args) + ['oldconfig']
436 oldc_result = self.make(commit, brd, 'oldconfig', cwd,
437 *oldc_args, env=env)
438 if oldc_result.return_code:
439 return oldc_result
Simon Glass2df95fe2023-07-19 17:49:23 -0600440 result = self._build(commit, brd, cwd, args, env, cmd_list,
441 config_only)
442 if adjust_cfg:
443 errs = cfgutil.check_cfg_file(cfg_file, adjust_cfg)
444 if errs:
445 result.stderr += errs
446 result.return_code = 1
447 result.stderr = result.stderr.replace(src_dir + '/', '')
448 if self.builder.verbose_build:
449 result.stdout = config_out.getvalue() + result.stdout
450 result.cmd_list = cmd_list
451 return result, do_config
Simon Glass7e989052023-07-19 17:49:22 -0600452
Simon Glassa7b944f2024-06-23 11:55:11 -0600453 def run_commit(self, commit_upto, brd, work_dir, do_config, mrproper,
454 config_only, force_build, force_build_failures,
455 work_in_output, adjust_cfg):
Simon Glass4a1e88b2014-08-09 15:33:00 -0600456 """Build a particular commit.
457
458 If the build is already done, and we are not forcing a build, we skip
459 the build and just return the previously-saved results.
460
461 Args:
Simon Glass465567f2023-07-19 17:49:26 -0600462 commit_upto (int): Commit number to build (0...n-1)
463 brd (Board): Board to build
464 work_dir (str): Directory to which the source will be checked out
465 do_config (bool): True to run a make <board>_defconfig on the source
Simon Glassa7b944f2024-06-23 11:55:11 -0600466 mrproper (bool): True to run mrproper first
Simon Glass465567f2023-07-19 17:49:26 -0600467 config_only (bool): Only configure the source, do not build it
468 force_build (bool): Force a build even if one was previously done
469 force_build_failures (bool): Force a bulid if the previous result
470 showed failure
471 work_in_output (bool) : Use the output directory as the work
472 directory and don't write to a separate output directory.
Simon Glasse5650a82022-01-22 05:07:33 -0700473 adjust_cfg (list of str): List of changes to make to .config file
474 before building. Each is one of (where C is either CONFIG_xxx
475 or just xxx):
476 C to enable C
477 ~C to disable C
478 C=val to set the value of C (val must have quotes if C is
479 a string Kconfig
Simon Glass4a1e88b2014-08-09 15:33:00 -0600480
481 Returns:
482 tuple containing:
483 - CommandResult object containing the results of the build
484 - boolean indicating whether 'make config' is still needed
485 """
486 # Create a default result - it will be overwritte by the call to
Simon Glassc5077c32023-07-19 17:49:08 -0600487 # self.make() below, in the event that we do a build.
Simon Glass83a0b862023-07-19 17:49:21 -0600488 out_dir, out_rel_dir = self._decide_dirs(brd, work_dir, work_in_output)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600489
490 # Check if the job was already completed last time
Simon Glassb9a1b772023-07-19 17:49:24 -0600491 will_build, result = self._read_done_file(commit_upto, brd, force_build,
492 force_build_failures)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600493
494 if will_build:
495 # We are going to have to build it. First, get a toolchain
496 if not self.toolchain:
497 try:
498 self.toolchain = self.builder.toolchains.Select(brd.arch)
499 except ValueError as err:
500 result.return_code = 10
501 result.stdout = ''
Simon Glassbf353b82023-07-19 17:49:25 -0600502 result.stderr = f'Tool chain error for {brd.arch}: {str(err)}'
Simon Glass4a1e88b2014-08-09 15:33:00 -0600503
504 if self.toolchain:
Simon Glass7e989052023-07-19 17:49:22 -0600505 commit = self._checkout(commit_upto, work_dir)
Simon Glass2df95fe2023-07-19 17:49:23 -0600506 result, do_config = self._config_and_build(
Simon Glassa7b944f2024-06-23 11:55:11 -0600507 commit_upto, brd, work_dir, do_config, mrproper,
Simon Glass5d05de52024-06-23 11:55:10 -0600508 config_only, adjust_cfg, commit, out_dir, out_rel_dir,
509 result)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600510 result.already_done = False
511
512 result.toolchain = self.toolchain
513 result.brd = brd
514 result.commit_upto = commit_upto
515 result.out_dir = out_dir
516 return result, do_config
517
Simon Glassc5077c32023-07-19 17:49:08 -0600518 def _write_result(self, result, keep_outputs, work_in_output):
Simon Glass4a1e88b2014-08-09 15:33:00 -0600519 """Write a built result to the output directory.
520
521 Args:
Simon Glass465567f2023-07-19 17:49:26 -0600522 result (CommandResult): result to write
523 keep_outputs (bool): True to store the output binaries, False
Simon Glass4a1e88b2014-08-09 15:33:00 -0600524 to delete them
Simon Glass465567f2023-07-19 17:49:26 -0600525 work_in_output (bool): Use the output directory as the work
526 directory and don't write to a separate output directory.
Simon Glass4a1e88b2014-08-09 15:33:00 -0600527 """
Simon Glassfd3eea12015-02-05 22:06:13 -0700528 # If we think this might have been aborted with Ctrl-C, record the
529 # failure but not that we are 'done' with this board. A retry may fix
530 # it.
Simon Glass2e737452021-10-19 21:43:23 -0600531 maybe_aborted = result.stderr and 'No child processes' in result.stderr
Simon Glass4a1e88b2014-08-09 15:33:00 -0600532
Simon Glass2e737452021-10-19 21:43:23 -0600533 if result.return_code >= 0 and result.already_done:
Simon Glass4a1e88b2014-08-09 15:33:00 -0600534 return
535
536 # Write the output and stderr
Simon Glass4cb54682023-07-19 17:49:10 -0600537 output_dir = self.builder.get_output_dir(result.commit_upto)
Simon Glassc5077c32023-07-19 17:49:08 -0600538 mkdir(output_dir)
Simon Glassbc74d942023-07-19 17:49:06 -0600539 build_dir = self.builder.get_build_dir(result.commit_upto,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600540 result.brd.target)
Simon Glassc5077c32023-07-19 17:49:08 -0600541 mkdir(build_dir)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600542
543 outfile = os.path.join(build_dir, 'log')
Simon Glassd07e5532023-07-19 17:49:09 -0600544 with open(outfile, 'w', encoding='utf-8') as outf:
Simon Glass4a1e88b2014-08-09 15:33:00 -0600545 if result.stdout:
Simon Glassd07e5532023-07-19 17:49:09 -0600546 outf.write(result.stdout)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600547
Simon Glassbc74d942023-07-19 17:49:06 -0600548 errfile = self.builder.get_err_file(result.commit_upto,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600549 result.brd.target)
550 if result.stderr:
Simon Glassd07e5532023-07-19 17:49:09 -0600551 with open(errfile, 'w', encoding='utf-8') as outf:
552 outf.write(result.stderr)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600553 elif os.path.exists(errfile):
554 os.remove(errfile)
555
Simon Glass2e737452021-10-19 21:43:23 -0600556 # Fatal error
557 if result.return_code < 0:
558 return
559
Simon Glassdc311842024-12-14 11:20:23 -0700560 done_file = self.builder.get_done_file(result.commit_upto,
561 result.brd.target)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600562 if result.toolchain:
563 # Write the build result and toolchain information.
Simon Glassd07e5532023-07-19 17:49:09 -0600564 with open(done_file, 'w', encoding='utf-8') as outf:
Simon Glassfd3eea12015-02-05 22:06:13 -0700565 if maybe_aborted:
566 # Special code to indicate we need to retry
Simon Glassd07e5532023-07-19 17:49:09 -0600567 outf.write(f'{RETURN_CODE_RETRY}')
Simon Glassfd3eea12015-02-05 22:06:13 -0700568 else:
Simon Glassd07e5532023-07-19 17:49:09 -0600569 outf.write(f'{result.return_code}')
570 with open(os.path.join(build_dir, 'toolchain'), 'w',
571 encoding='utf-8') as outf:
572 print('gcc', result.toolchain.gcc, file=outf)
573 print('path', result.toolchain.path, file=outf)
574 print('cross', result.toolchain.cross, file=outf)
575 print('arch', result.toolchain.arch, file=outf)
576 outf.write(f'{result.return_code}')
Simon Glass4a1e88b2014-08-09 15:33:00 -0600577
Simon Glass4a1e88b2014-08-09 15:33:00 -0600578 # Write out the image and function size information and an objdump
Simon Glass600ede92024-08-15 13:57:45 -0600579 env = self.builder.make_environment(self.toolchain)
Simon Glassd07e5532023-07-19 17:49:09 -0600580 with open(os.path.join(build_dir, 'out-env'), 'wb') as outf:
Simon Glass9e90d312019-01-07 16:44:23 -0700581 for var in sorted(env.keys()):
Simon Glassd07e5532023-07-19 17:49:09 -0600582 outf.write(b'%s="%s"' % (var, env[var]))
Simon Glass1382b1d2023-02-21 12:40:27 -0700583
584 with open(os.path.join(build_dir, 'out-cmd'), 'w',
Simon Glassd07e5532023-07-19 17:49:09 -0600585 encoding='utf-8') as outf:
Simon Glass1382b1d2023-02-21 12:40:27 -0700586 for cmd in result.cmd_list:
Simon Glassd07e5532023-07-19 17:49:09 -0600587 print(' '.join(cmd), file=outf)
Simon Glass1382b1d2023-02-21 12:40:27 -0700588
Simon Glass4a1e88b2014-08-09 15:33:00 -0600589 lines = []
Simon Glasse0f19712020-12-16 17:24:17 -0700590 for fname in BASE_ELF_FILENAMES:
Simon Glassd07e5532023-07-19 17:49:09 -0600591 cmd = [f'{self.toolchain.cross}nm', '--size-sort', fname]
Simon Glass51f55182025-02-03 09:26:45 -0700592 nm_result = command.run_one(*cmd, capture=True,
593 capture_stderr=True,
594 cwd=result.out_dir,
595 raise_on_error=False, env=env)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600596 if nm_result.stdout:
Simon Glassd07e5532023-07-19 17:49:09 -0600597 nm_fname = self.builder.get_func_sizes_file(
598 result.commit_upto, result.brd.target, fname)
599 with open(nm_fname, 'w', encoding='utf-8') as outf:
600 print(nm_result.stdout, end=' ', file=outf)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600601
Simon Glassd07e5532023-07-19 17:49:09 -0600602 cmd = [f'{self.toolchain.cross}objdump', '-h', fname]
Simon Glass51f55182025-02-03 09:26:45 -0700603 dump_result = command.run_one(*cmd, capture=True,
604 capture_stderr=True,
605 cwd=result.out_dir,
606 raise_on_error=False, env=env)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600607 rodata_size = ''
608 if dump_result.stdout:
Simon Glassbc74d942023-07-19 17:49:06 -0600609 objdump = self.builder.get_objdump_file(result.commit_upto,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600610 result.brd.target, fname)
Simon Glassd07e5532023-07-19 17:49:09 -0600611 with open(objdump, 'w', encoding='utf-8') as outf:
612 print(dump_result.stdout, end=' ', file=outf)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600613 for line in dump_result.stdout.splitlines():
614 fields = line.split()
615 if len(fields) > 5 and fields[1] == '.rodata':
616 rodata_size = fields[2]
617
Simon Glassd07e5532023-07-19 17:49:09 -0600618 cmd = [f'{self.toolchain.cross}size', fname]
Simon Glass51f55182025-02-03 09:26:45 -0700619 size_result = command.run_one(*cmd, capture=True,
620 capture_stderr=True,
621 cwd=result.out_dir,
622 raise_on_error=False, env=env)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600623 if size_result.stdout:
624 lines.append(size_result.stdout.splitlines()[1] + ' ' +
625 rodata_size)
626
Alex Kiernanf07ed232018-05-31 04:48:33 +0000627 # Extract the environment from U-Boot and dump it out
Simon Glassd07e5532023-07-19 17:49:09 -0600628 cmd = [f'{self.toolchain.cross}objcopy', '-O', 'binary',
Alex Kiernanf07ed232018-05-31 04:48:33 +0000629 '-j', '.rodata.default_environment',
630 'env/built-in.o', 'uboot.env']
Simon Glass51f55182025-02-03 09:26:45 -0700631 command.run_one(*cmd, capture=True, capture_stderr=True,
632 cwd=result.out_dir, raise_on_error=False, env=env)
Simon Glasse3c85ab2020-04-17 17:51:34 -0600633 if not work_in_output:
Simon Glass9d29f952023-07-19 17:49:27 -0600634 copy_files(result.out_dir, build_dir, '', ['uboot.env'])
Alex Kiernanf07ed232018-05-31 04:48:33 +0000635
Simon Glass4a1e88b2014-08-09 15:33:00 -0600636 # Write out the image sizes file. This is similar to the output
637 # of binutil's 'size' utility, but it omits the header line and
638 # adds an additional hex value at the end of each line for the
639 # rodata size
Simon Glassd07e5532023-07-19 17:49:09 -0600640 if lines:
Simon Glassbc74d942023-07-19 17:49:06 -0600641 sizes = self.builder.get_sizes_file(result.commit_upto,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600642 result.brd.target)
Simon Glassd07e5532023-07-19 17:49:09 -0600643 with open(sizes, 'w', encoding='utf-8') as outf:
644 print('\n'.join(lines), file=outf)
Simon Glassdc311842024-12-14 11:20:23 -0700645 else:
646 # Indicate that the build failure due to lack of toolchain
647 tools.write_file(done_file, '2\n', binary=False)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600648
Simon Glasse3c85ab2020-04-17 17:51:34 -0600649 if not work_in_output:
650 # Write out the configuration files, with a special case for SPL
651 for dirname in ['', 'spl', 'tpl']:
Simon Glass9d29f952023-07-19 17:49:27 -0600652 copy_files(
Simon Glasse3c85ab2020-04-17 17:51:34 -0600653 result.out_dir, build_dir, dirname,
654 ['u-boot.cfg', 'spl/u-boot-spl.cfg', 'tpl/u-boot-tpl.cfg',
655 '.config', 'include/autoconf.mk',
656 'include/generated/autoconf.h'])
Simon Glass4b14c532015-02-05 22:06:14 -0700657
Simon Glasse3c85ab2020-04-17 17:51:34 -0600658 # Now write the actual build output
659 if keep_outputs:
Simon Glass31837da2023-09-07 10:00:17 -0600660 to_copy = ['u-boot*', '*.map', 'MLO', 'SPL',
661 'include/autoconf.mk', 'spl/u-boot-spl*',
662 'tpl/u-boot-tpl*', 'vpl/u-boot-vpl*']
663 to_copy += [f'*{ext}' for ext in COMMON_EXTS]
664 copy_files(result.out_dir, build_dir, '', to_copy)
Simon Glass4b14c532015-02-05 22:06:14 -0700665
Simon Glassc5077c32023-07-19 17:49:08 -0600666 def _send_result(self, result):
Simon Glassa3d01442021-04-11 16:27:26 +1200667 """Send a result to the builder for processing
668
669 Args:
Simon Glass465567f2023-07-19 17:49:26 -0600670 result (CommandResult): results of the build
Simon Glass9bf9a722021-04-11 16:27:27 +1200671
672 Raises:
Simon Glass465567f2023-07-19 17:49:26 -0600673 ValueError: self.test_exception is true (for testing)
Simon Glassa3d01442021-04-11 16:27:26 +1200674 """
Simon Glass9bf9a722021-04-11 16:27:27 +1200675 if self.test_exception:
676 raise ValueError('test exception')
Simon Glassa3d01442021-04-11 16:27:26 +1200677 if self.thread_num != -1:
678 self.builder.out_queue.put(result)
679 else:
Simon Glassbc74d942023-07-19 17:49:06 -0600680 self.builder.process_result(result)
Simon Glassa3d01442021-04-11 16:27:26 +1200681
Simon Glassc5077c32023-07-19 17:49:08 -0600682 def run_job(self, job):
Simon Glass4a1e88b2014-08-09 15:33:00 -0600683 """Run a single job
684
685 A job consists of a building a list of commits for a particular board.
686
687 Args:
Simon Glass465567f2023-07-19 17:49:26 -0600688 job (Job): Job to build
Simon Glassc635d892021-01-30 22:17:46 -0700689
Simon Glass465567f2023-07-19 17:49:26 -0600690 Raises:
691 ValueError: Thread was interrupted
Simon Glass4a1e88b2014-08-09 15:33:00 -0600692 """
Simon Glass8132f982022-07-11 19:03:57 -0600693 brd = job.brd
Simon Glassbc74d942023-07-19 17:49:06 -0600694 work_dir = self.builder.get_thread_dir(self.thread_num)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600695 self.toolchain = None
696 if job.commits:
697 # Run 'make board_defconfig' on the first commit
698 do_config = True
699 commit_upto = 0
700 force_build = False
701 for commit_upto in range(0, len(job.commits), job.step):
Simon Glassc5077c32023-07-19 17:49:08 -0600702 result, request_config = self.run_commit(commit_upto, brd,
Simon Glassa7b944f2024-06-23 11:55:11 -0600703 work_dir, do_config, self.mrproper,
704 self.builder.config_only,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600705 force_build or self.builder.force_build,
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600706 self.builder.force_build_failures,
Simon Glasse5650a82022-01-22 05:07:33 -0700707 job.work_in_output, job.adjust_cfg)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600708 failed = result.return_code or result.stderr
709 did_config = do_config
Simon Glasscf76d0a2024-06-23 11:55:12 -0600710 if failed and not do_config and not self.mrproper:
Simon Glass4a1e88b2014-08-09 15:33:00 -0600711 # If our incremental build failed, try building again
712 # with a reconfig.
713 if self.builder.force_config_on_failure:
Simon Glassc5077c32023-07-19 17:49:08 -0600714 result, request_config = self.run_commit(commit_upto,
Simon Glass222825b2024-06-23 11:55:13 -0600715 brd, work_dir, True,
716 self.mrproper or self.builder.fallback_mrproper,
717 False, True, False, job.work_in_output,
718 job.adjust_cfg)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600719 did_config = True
720 if not self.builder.force_reconfig:
721 do_config = request_config
722
723 # If we built that commit, then config is done. But if we got
724 # an warning, reconfig next time to force it to build the same
725 # files that created warnings this time. Otherwise an
726 # incremental build may not build the same file, and we will
727 # think that the warning has gone away.
728 # We could avoid this by using -Werror everywhere...
729 # For errors, the problem doesn't happen, since presumably
730 # the build stopped and didn't generate output, so will retry
731 # that file next time. So we could detect warnings and deal
732 # with them specially here. For now, we just reconfigure if
733 # anything goes work.
734 # Of course this is substantially slower if there are build
735 # errors/warnings (e.g. 2-3x slower even if only 10% of builds
736 # have problems).
737 if (failed and not result.already_done and not did_config and
738 self.builder.force_config_on_failure):
739 # If this build failed, try the next one with a
740 # reconfigure.
741 # Sometimes if the board_config.h file changes it can mess
742 # with dependencies, and we get:
743 # make: *** No rule to make target `include/autoconf.mk',
744 # needed by `depend'.
745 do_config = True
746 force_build = True
747 else:
748 force_build = False
749 if self.builder.force_config_on_failure:
750 if failed:
751 do_config = True
752 result.commit_upto = commit_upto
753 if result.return_code < 0:
754 raise ValueError('Interrupt')
755
756 # We have the build results, so output the result
Simon Glassc5077c32023-07-19 17:49:08 -0600757 self._write_result(result, job.keep_outputs, job.work_in_output)
758 self._send_result(result)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600759 else:
760 # Just build the currently checked-out build
Simon Glassc5077c32023-07-19 17:49:08 -0600761 result, request_config = self.run_commit(None, brd, work_dir, True,
Simon Glassa7b944f2024-06-23 11:55:11 -0600762 self.mrproper, self.builder.config_only, True,
Simon Glasse5650a82022-01-22 05:07:33 -0700763 self.builder.force_build_failures, job.work_in_output,
764 job.adjust_cfg)
Simon Glass2c508342024-06-23 11:55:14 -0600765 failed = result.return_code or result.stderr
766 if failed and not self.mrproper:
767 result, request_config = self.run_commit(None, brd, work_dir,
768 True, self.builder.fallback_mrproper,
769 self.builder.config_only, True,
770 self.builder.force_build_failures,
771 job.work_in_output, job.adjust_cfg)
772
Simon Glass4a1e88b2014-08-09 15:33:00 -0600773 result.commit_upto = 0
Simon Glassc5077c32023-07-19 17:49:08 -0600774 self._write_result(result, job.keep_outputs, job.work_in_output)
775 self._send_result(result)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600776
777 def run(self):
778 """Our thread's run function
779
780 This thread picks a job from the queue, runs it, and then goes to the
781 next job.
782 """
Simon Glass4a1e88b2014-08-09 15:33:00 -0600783 while True:
784 job = self.builder.queue.get()
Simon Glass9bf9a722021-04-11 16:27:27 +1200785 try:
Simon Glassc5077c32023-07-19 17:49:08 -0600786 self.run_job(job)
Simon Glassd07e5532023-07-19 17:49:09 -0600787 except Exception as exc:
788 print('Thread exception (use -T0 to run without threads):',
789 exc)
790 self.builder.thread_exceptions.append(exc)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600791 self.builder.queue.task_done()