blob: a8599c0bb2a8bde66433aa64878be7af1dfb81c8 [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 Glassa997ea52020-04-17 18:09:04 -060020from patman import gitutil
Simon Glass131444f2023-02-23 18:18:04 -070021from u_boot_pylib import command
Simon Glass4a1e88b2014-08-09 15:33:00 -060022
Simon Glassfd3eea12015-02-05 22:06:13 -070023RETURN_CODE_RETRY = -1
Simon Glasse0f19712020-12-16 17:24:17 -070024BASE_ELF_FILENAMES = ['u-boot', 'spl/u-boot-spl', 'tpl/u-boot-tpl']
Simon Glassfd3eea12015-02-05 22:06:13 -070025
Simon Glass31837da2023-09-07 10:00:17 -060026# Common extensions for images
27COMMON_EXTS = ['.bin', '.rom', '.itb', '.img']
28
Simon Glass465567f2023-07-19 17:49:26 -060029def mkdir(dirname, parents=False):
Simon Glass4a1e88b2014-08-09 15:33:00 -060030 """Make a directory if it doesn't already exist.
31
32 Args:
Simon Glass465567f2023-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 Glass4a1e88b2014-08-09 15:33:00 -060038 """
39 try:
Thierry Reding336d5bd2014-08-19 10:22:39 +020040 if parents:
41 os.makedirs(dirname)
42 else:
43 os.mkdir(dirname)
Simon Glass4a1e88b2014-08-09 15:33:00 -060044 except OSError as err:
45 if err.errno == errno.EEXIST:
Lothar Waßmannce6df922018-04-08 05:14:11 -060046 if os.path.realpath('.') == os.path.realpath(dirname):
Simon Glassd07e5532023-07-19 17:49:09 -060047 print(f"Cannot create the current working directory '{dirname}'!")
Lothar Waßmannce6df922018-04-08 05:14:11 -060048 sys.exit(1)
Simon Glass4a1e88b2014-08-09 15:33:00 -060049 else:
50 raise
51
Simon Glasscb2de022023-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 Glass9d29f952023-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 Glassd07e5532023-07-19 17:49:09 -060091# pylint: disable=R0903
Simon Glass4a1e88b2014-08-09 15:33:00 -060092class BuilderJob:
93 """Holds information about a job to be performed by a thread
94
95 Members:
Simon Glass8132f982022-07-11 19:03:57 -060096 brd: Board object to build
Simon Glassdf890152020-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 Glassb6eb8cf2020-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 Glass4a1e88b2014-08-09 15:33:00 -0600102 """
103 def __init__(self):
Simon Glass8132f982022-07-11 19:03:57 -0600104 self.brd = None
Simon Glass4a1e88b2014-08-09 15:33:00 -0600105 self.commits = []
Simon Glassdf890152020-03-18 09:42:41 -0600106 self.keep_outputs = False
107 self.step = 1
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600108 self.work_in_output = False
Simon Glass4a1e88b2014-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 Glassbc74d942023-07-19 17:49:06 -0600133 self.builder.process_result(result)
Simon Glass4a1e88b2014-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 Glassa29b3ea2021-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 Glass4a1e88b2014-08-09 15:33:00 -0600153 """
Simon Glass9bf9a722021-04-11 16:27:27 +1200154 def __init__(self, builder, thread_num, mrproper, per_board_out_dir,
155 test_exception=False):
Simon Glass4a1e88b2014-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 Glass6029af12020-04-09 15:08:51 -0600160 self.mrproper = mrproper
Stephen Warren97c96902016-04-11 10:48:44 -0600161 self.per_board_out_dir = per_board_out_dir
Simon Glass9bf9a722021-04-11 16:27:27 +1200162 self.test_exception = test_exception
Simon Glassd07e5532023-07-19 17:49:09 -0600163 self.toolchain = None
Simon Glass4a1e88b2014-08-09 15:33:00 -0600164
Simon Glassc5077c32023-07-19 17:49:08 -0600165 def make(self, commit, brd, stage, cwd, *args, **kwargs):
Simon Glass4a1e88b2014-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 Glass465567f2023-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 Meiere0a0e552014-08-20 22:10:29 +0200175 mrproper - can be called to clean source
Simon Glass4a1e88b2014-08-09 15:33:00 -0600176 config - called to configure for a board
177 build - the main make invocation - it does the build
Simon Glass465567f2023-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 Glass4a1e88b2014-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 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,
243 cmd_list):
244 """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
254
255 Returns:
256 CommandResult object
257 """
258 if self.mrproper:
259 result = self.make(commit, brd, 'mrproper', cwd, 'mrproper', *args,
260 env=env)
261 config_out.write(result.combined)
262 cmd_list.append([self.builder.gnu_make, 'mrproper', *args])
263 result = self.make(commit, brd, 'config', cwd, *(args + config_args),
264 env=env)
265 cmd_list.append([self.builder.gnu_make] + args + config_args)
266 config_out.write(result.combined)
267 return result
268
Simon Glass3b4f50e2023-07-19 17:49:18 -0600269 def _build(self, commit, brd, cwd, args, env, cmd_list, config_only):
270 """Perform the build
271
272 Args:
273 commit (Commit): Commit only being built
274 brd (Board): Board being built
275 cwd (str): Current working directory
276 args (list of str): Arguments to pass to make
277 env (dict): Environment strings
278 cmd_list (list of str): List to add the commands to, for logging
279 config_only (bool): True if this is a config-only build (using the
280 'make cfg' target)
281
282 Returns:
283 CommandResult object
284 """
285 if config_only:
286 args.append('cfg')
287 result = self.make(commit, brd, 'build', cwd, *args, env=env)
288 cmd_list.append([self.builder.gnu_make] + args)
289 if (result.return_code == 2 and
290 ('Some images are invalid' in result.stderr)):
291 # This is handled later by the check for output in stderr
292 result.return_code = 0
293 return result
294
Simon Glassb9a1b772023-07-19 17:49:24 -0600295 def _read_done_file(self, commit_upto, brd, force_build,
Simon Glassfdd7be22023-07-19 17:49:19 -0600296 force_build_failures):
297 """Check the 'done' file and see if this commit should be built
298
299 Args:
300 commit (Commit): Commit only being built
301 brd (Board): Board being built
Simon Glassfdd7be22023-07-19 17:49:19 -0600302 force_build (bool): Force a build even if one was previously done
303 force_build_failures (bool): Force a bulid if the previous result
304 showed failure
305
306 Returns:
Simon Glassb9a1b772023-07-19 17:49:24 -0600307 tuple:
308 bool: True if build should be built
309 CommandResult: if there was a previous run:
310 - already_done set to True
311 - return_code set to return code
312 - result.stderr set to 'bad' if stderr output was recorded
Simon Glassfdd7be22023-07-19 17:49:19 -0600313 """
Simon Glassb9a1b772023-07-19 17:49:24 -0600314 result = command.CommandResult()
Simon Glassfdd7be22023-07-19 17:49:19 -0600315 done_file = self.builder.get_done_file(commit_upto, brd.target)
316 result.already_done = os.path.exists(done_file)
317 will_build = (force_build or force_build_failures or
318 not result.already_done)
319 if result.already_done:
320 with open(done_file, 'r', encoding='utf-8') as outf:
321 try:
322 result.return_code = int(outf.readline())
323 except ValueError:
324 # The file may be empty due to running out of disk space.
325 # Try a rebuild
326 result.return_code = RETURN_CODE_RETRY
327
328 # Check the signal that the build needs to be retried
329 if result.return_code == RETURN_CODE_RETRY:
330 will_build = True
331 elif will_build:
332 err_file = self.builder.get_err_file(commit_upto, brd.target)
333 if os.path.exists(err_file) and os.stat(err_file).st_size:
334 result.stderr = 'bad'
335 elif not force_build:
336 # The build passed, so no need to build it again
337 will_build = False
Simon Glassb9a1b772023-07-19 17:49:24 -0600338 return will_build, result
Simon Glassfdd7be22023-07-19 17:49:19 -0600339
Simon Glass83a0b862023-07-19 17:49:21 -0600340 def _decide_dirs(self, brd, work_dir, work_in_output):
341 """Decide the output directory to use
342
343 Args:
344 work_dir (str): Directory to which the source will be checked out
345 work_in_output (bool): Use the output directory as the work
346 directory and don't write to a separate output directory.
347
348 Returns:
349 tuple:
350 out_dir (str): Output directory for the build
351 out_rel_dir (str): Output directory relatie to the current dir
352 """
353 if work_in_output or self.builder.in_tree:
354 out_rel_dir = None
355 out_dir = work_dir
356 else:
357 if self.per_board_out_dir:
358 out_rel_dir = os.path.join('..', brd.target)
359 else:
360 out_rel_dir = 'build'
361 out_dir = os.path.join(work_dir, out_rel_dir)
362 return out_dir, out_rel_dir
363
Simon Glass7e989052023-07-19 17:49:22 -0600364 def _checkout(self, commit_upto, work_dir):
365 """Checkout the right commit
366
367 Args:
368 commit_upto (int): Commit number to build (0...n-1)
369 work_dir (str): Directory to which the source will be checked out
370
371 Returns:
372 Commit: Commit being built, or 'current' for current source
373 """
374 if self.builder.commits:
375 commit = self.builder.commits[commit_upto]
376 if self.builder.checkout:
377 git_dir = os.path.join(work_dir, '.git')
378 gitutil.checkout(commit.hash, git_dir, work_dir, force=True)
379 else:
380 commit = 'current'
381 return commit
382
Simon Glass2df95fe2023-07-19 17:49:23 -0600383 def _config_and_build(self, commit_upto, brd, work_dir, do_config,
384 config_only, adjust_cfg, commit, out_dir, out_rel_dir,
385 result):
386 """Do the build, configuring first if necessary
387
388 Args:
389 commit_upto (int): Commit number to build (0...n-1)
390 brd (Board): Board to create arguments for
391 work_dir (str): Directory to which the source will be checked out
392 do_config (bool): True to run a make <board>_defconfig on the source
393 config_only (bool): Only configure the source, do not build it
394 adjust_cfg (list of str): See the cfgutil module and run_commit()
395 commit (Commit): Commit only being built
396 out_dir (str): Output directory for the build
397 out_rel_dir (str): Output directory relatie to the current dir
398 result (CommandResult): Previous result
399
400 Returns:
401 tuple:
402 result (CommandResult): Result of the build
403 do_config (bool): indicates whether 'make config' is needed on
404 the next incremental build
405 """
406 # Set up the environment and command line
407 env = self.toolchain.MakeEnvironment(self.builder.full_path)
408 mkdir(out_dir)
409
410 args, cwd, src_dir = self._build_args(brd, out_dir, out_rel_dir,
411 work_dir, commit_upto)
412 config_args = [f'{brd.target}_defconfig']
413 config_out = io.StringIO()
414
415 _remove_old_outputs(out_dir)
416
417 # If we need to reconfigure, do that now
418 cfg_file = os.path.join(out_dir, '.config')
419 cmd_list = []
420 if do_config or adjust_cfg:
421 result = self._reconfigure(
422 commit, brd, cwd, args, env, config_args, config_out, cmd_list)
423 do_config = False # No need to configure next time
424 if adjust_cfg:
425 cfgutil.adjust_cfg_file(cfg_file, adjust_cfg)
426
427 # Now do the build, if everything looks OK
428 if result.return_code == 0:
Simon Glassd6c1ec82023-10-26 14:31:10 -0400429 if adjust_cfg:
430 oldc_args = list(args) + ['oldconfig']
431 oldc_result = self.make(commit, brd, 'oldconfig', cwd,
432 *oldc_args, env=env)
433 if oldc_result.return_code:
434 return oldc_result
Simon Glass2df95fe2023-07-19 17:49:23 -0600435 result = self._build(commit, brd, cwd, args, env, cmd_list,
436 config_only)
437 if adjust_cfg:
438 errs = cfgutil.check_cfg_file(cfg_file, adjust_cfg)
439 if errs:
440 result.stderr += errs
441 result.return_code = 1
442 result.stderr = result.stderr.replace(src_dir + '/', '')
443 if self.builder.verbose_build:
444 result.stdout = config_out.getvalue() + result.stdout
445 result.cmd_list = cmd_list
446 return result, do_config
Simon Glass7e989052023-07-19 17:49:22 -0600447
Simon Glassc5077c32023-07-19 17:49:08 -0600448 def run_commit(self, commit_upto, brd, work_dir, do_config, config_only,
Simon Glasse5650a82022-01-22 05:07:33 -0700449 force_build, force_build_failures, work_in_output,
450 adjust_cfg):
Simon Glass4a1e88b2014-08-09 15:33:00 -0600451 """Build a particular commit.
452
453 If the build is already done, and we are not forcing a build, we skip
454 the build and just return the previously-saved results.
455
456 Args:
Simon Glass465567f2023-07-19 17:49:26 -0600457 commit_upto (int): Commit number to build (0...n-1)
458 brd (Board): Board to build
459 work_dir (str): Directory to which the source will be checked out
460 do_config (bool): True to run a make <board>_defconfig on the source
461 config_only (bool): Only configure the source, do not build it
462 force_build (bool): Force a build even if one was previously done
463 force_build_failures (bool): Force a bulid if the previous result
464 showed failure
465 work_in_output (bool) : Use the output directory as the work
466 directory and don't write to a separate output directory.
Simon Glasse5650a82022-01-22 05:07:33 -0700467 adjust_cfg (list of str): List of changes to make to .config file
468 before building. Each is one of (where C is either CONFIG_xxx
469 or just xxx):
470 C to enable C
471 ~C to disable C
472 C=val to set the value of C (val must have quotes if C is
473 a string Kconfig
Simon Glass4a1e88b2014-08-09 15:33:00 -0600474
475 Returns:
476 tuple containing:
477 - CommandResult object containing the results of the build
478 - boolean indicating whether 'make config' is still needed
479 """
480 # Create a default result - it will be overwritte by the call to
Simon Glassc5077c32023-07-19 17:49:08 -0600481 # self.make() below, in the event that we do a build.
Simon Glass83a0b862023-07-19 17:49:21 -0600482 out_dir, out_rel_dir = self._decide_dirs(brd, work_dir, work_in_output)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600483
484 # Check if the job was already completed last time
Simon Glassb9a1b772023-07-19 17:49:24 -0600485 will_build, result = self._read_done_file(commit_upto, brd, force_build,
486 force_build_failures)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600487
488 if will_build:
489 # We are going to have to build it. First, get a toolchain
490 if not self.toolchain:
491 try:
492 self.toolchain = self.builder.toolchains.Select(brd.arch)
493 except ValueError as err:
494 result.return_code = 10
495 result.stdout = ''
Simon Glassbf353b82023-07-19 17:49:25 -0600496 result.stderr = f'Tool chain error for {brd.arch}: {str(err)}'
Simon Glass4a1e88b2014-08-09 15:33:00 -0600497
498 if self.toolchain:
Simon Glass7e989052023-07-19 17:49:22 -0600499 commit = self._checkout(commit_upto, work_dir)
Simon Glass2df95fe2023-07-19 17:49:23 -0600500 result, do_config = self._config_and_build(
501 commit_upto, brd, work_dir, do_config, config_only,
502 adjust_cfg, commit, out_dir, out_rel_dir, result)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600503 result.already_done = False
504
505 result.toolchain = self.toolchain
506 result.brd = brd
507 result.commit_upto = commit_upto
508 result.out_dir = out_dir
509 return result, do_config
510
Simon Glassc5077c32023-07-19 17:49:08 -0600511 def _write_result(self, result, keep_outputs, work_in_output):
Simon Glass4a1e88b2014-08-09 15:33:00 -0600512 """Write a built result to the output directory.
513
514 Args:
Simon Glass465567f2023-07-19 17:49:26 -0600515 result (CommandResult): result to write
516 keep_outputs (bool): True to store the output binaries, False
Simon Glass4a1e88b2014-08-09 15:33:00 -0600517 to delete them
Simon Glass465567f2023-07-19 17:49:26 -0600518 work_in_output (bool): Use the output directory as the work
519 directory and don't write to a separate output directory.
Simon Glass4a1e88b2014-08-09 15:33:00 -0600520 """
Simon Glassfd3eea12015-02-05 22:06:13 -0700521 # If we think this might have been aborted with Ctrl-C, record the
522 # failure but not that we are 'done' with this board. A retry may fix
523 # it.
Simon Glass2e737452021-10-19 21:43:23 -0600524 maybe_aborted = result.stderr and 'No child processes' in result.stderr
Simon Glass4a1e88b2014-08-09 15:33:00 -0600525
Simon Glass2e737452021-10-19 21:43:23 -0600526 if result.return_code >= 0 and result.already_done:
Simon Glass4a1e88b2014-08-09 15:33:00 -0600527 return
528
529 # Write the output and stderr
Simon Glass4cb54682023-07-19 17:49:10 -0600530 output_dir = self.builder.get_output_dir(result.commit_upto)
Simon Glassc5077c32023-07-19 17:49:08 -0600531 mkdir(output_dir)
Simon Glassbc74d942023-07-19 17:49:06 -0600532 build_dir = self.builder.get_build_dir(result.commit_upto,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600533 result.brd.target)
Simon Glassc5077c32023-07-19 17:49:08 -0600534 mkdir(build_dir)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600535
536 outfile = os.path.join(build_dir, 'log')
Simon Glassd07e5532023-07-19 17:49:09 -0600537 with open(outfile, 'w', encoding='utf-8') as outf:
Simon Glass4a1e88b2014-08-09 15:33:00 -0600538 if result.stdout:
Simon Glassd07e5532023-07-19 17:49:09 -0600539 outf.write(result.stdout)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600540
Simon Glassbc74d942023-07-19 17:49:06 -0600541 errfile = self.builder.get_err_file(result.commit_upto,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600542 result.brd.target)
543 if result.stderr:
Simon Glassd07e5532023-07-19 17:49:09 -0600544 with open(errfile, 'w', encoding='utf-8') as outf:
545 outf.write(result.stderr)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600546 elif os.path.exists(errfile):
547 os.remove(errfile)
548
Simon Glass2e737452021-10-19 21:43:23 -0600549 # Fatal error
550 if result.return_code < 0:
551 return
552
Simon Glass4a1e88b2014-08-09 15:33:00 -0600553 if result.toolchain:
554 # Write the build result and toolchain information.
Simon Glassbc74d942023-07-19 17:49:06 -0600555 done_file = self.builder.get_done_file(result.commit_upto,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600556 result.brd.target)
Simon Glassd07e5532023-07-19 17:49:09 -0600557 with open(done_file, 'w', encoding='utf-8') as outf:
Simon Glassfd3eea12015-02-05 22:06:13 -0700558 if maybe_aborted:
559 # Special code to indicate we need to retry
Simon Glassd07e5532023-07-19 17:49:09 -0600560 outf.write(f'{RETURN_CODE_RETRY}')
Simon Glassfd3eea12015-02-05 22:06:13 -0700561 else:
Simon Glassd07e5532023-07-19 17:49:09 -0600562 outf.write(f'{result.return_code}')
563 with open(os.path.join(build_dir, 'toolchain'), 'w',
564 encoding='utf-8') as outf:
565 print('gcc', result.toolchain.gcc, file=outf)
566 print('path', result.toolchain.path, file=outf)
567 print('cross', result.toolchain.cross, file=outf)
568 print('arch', result.toolchain.arch, file=outf)
569 outf.write(f'{result.return_code}')
Simon Glass4a1e88b2014-08-09 15:33:00 -0600570
Simon Glass4a1e88b2014-08-09 15:33:00 -0600571 # Write out the image and function size information and an objdump
Simon Glassd48a46c2014-12-01 17:34:00 -0700572 env = result.toolchain.MakeEnvironment(self.builder.full_path)
Simon Glassd07e5532023-07-19 17:49:09 -0600573 with open(os.path.join(build_dir, 'out-env'), 'wb') as outf:
Simon Glass9e90d312019-01-07 16:44:23 -0700574 for var in sorted(env.keys()):
Simon Glassd07e5532023-07-19 17:49:09 -0600575 outf.write(b'%s="%s"' % (var, env[var]))
Simon Glass1382b1d2023-02-21 12:40:27 -0700576
577 with open(os.path.join(build_dir, 'out-cmd'), 'w',
Simon Glassd07e5532023-07-19 17:49:09 -0600578 encoding='utf-8') as outf:
Simon Glass1382b1d2023-02-21 12:40:27 -0700579 for cmd in result.cmd_list:
Simon Glassd07e5532023-07-19 17:49:09 -0600580 print(' '.join(cmd), file=outf)
Simon Glass1382b1d2023-02-21 12:40:27 -0700581
Simon Glass4a1e88b2014-08-09 15:33:00 -0600582 lines = []
Simon Glasse0f19712020-12-16 17:24:17 -0700583 for fname in BASE_ELF_FILENAMES:
Simon Glassd07e5532023-07-19 17:49:09 -0600584 cmd = [f'{self.toolchain.cross}nm', '--size-sort', fname]
Simon Glass840be732022-01-29 14:14:05 -0700585 nm_result = command.run_pipe([cmd], capture=True,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600586 capture_stderr=True, cwd=result.out_dir,
587 raise_on_error=False, env=env)
588 if nm_result.stdout:
Simon Glassd07e5532023-07-19 17:49:09 -0600589 nm_fname = self.builder.get_func_sizes_file(
590 result.commit_upto, result.brd.target, fname)
591 with open(nm_fname, 'w', encoding='utf-8') as outf:
592 print(nm_result.stdout, end=' ', file=outf)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600593
Simon Glassd07e5532023-07-19 17:49:09 -0600594 cmd = [f'{self.toolchain.cross}objdump', '-h', fname]
Simon Glass840be732022-01-29 14:14:05 -0700595 dump_result = command.run_pipe([cmd], capture=True,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600596 capture_stderr=True, cwd=result.out_dir,
597 raise_on_error=False, env=env)
598 rodata_size = ''
599 if dump_result.stdout:
Simon Glassbc74d942023-07-19 17:49:06 -0600600 objdump = self.builder.get_objdump_file(result.commit_upto,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600601 result.brd.target, fname)
Simon Glassd07e5532023-07-19 17:49:09 -0600602 with open(objdump, 'w', encoding='utf-8') as outf:
603 print(dump_result.stdout, end=' ', file=outf)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600604 for line in dump_result.stdout.splitlines():
605 fields = line.split()
606 if len(fields) > 5 and fields[1] == '.rodata':
607 rodata_size = fields[2]
608
Simon Glassd07e5532023-07-19 17:49:09 -0600609 cmd = [f'{self.toolchain.cross}size', fname]
Simon Glass840be732022-01-29 14:14:05 -0700610 size_result = command.run_pipe([cmd], capture=True,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600611 capture_stderr=True, cwd=result.out_dir,
612 raise_on_error=False, env=env)
613 if size_result.stdout:
614 lines.append(size_result.stdout.splitlines()[1] + ' ' +
615 rodata_size)
616
Alex Kiernanf07ed232018-05-31 04:48:33 +0000617 # Extract the environment from U-Boot and dump it out
Simon Glassd07e5532023-07-19 17:49:09 -0600618 cmd = [f'{self.toolchain.cross}objcopy', '-O', 'binary',
Alex Kiernanf07ed232018-05-31 04:48:33 +0000619 '-j', '.rodata.default_environment',
620 'env/built-in.o', 'uboot.env']
Simon Glass840be732022-01-29 14:14:05 -0700621 command.run_pipe([cmd], capture=True,
Alex Kiernanf07ed232018-05-31 04:48:33 +0000622 capture_stderr=True, cwd=result.out_dir,
623 raise_on_error=False, env=env)
Simon Glasse3c85ab2020-04-17 17:51:34 -0600624 if not work_in_output:
Simon Glass9d29f952023-07-19 17:49:27 -0600625 copy_files(result.out_dir, build_dir, '', ['uboot.env'])
Alex Kiernanf07ed232018-05-31 04:48:33 +0000626
Simon Glass4a1e88b2014-08-09 15:33:00 -0600627 # Write out the image sizes file. This is similar to the output
628 # of binutil's 'size' utility, but it omits the header line and
629 # adds an additional hex value at the end of each line for the
630 # rodata size
Simon Glassd07e5532023-07-19 17:49:09 -0600631 if lines:
Simon Glassbc74d942023-07-19 17:49:06 -0600632 sizes = self.builder.get_sizes_file(result.commit_upto,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600633 result.brd.target)
Simon Glassd07e5532023-07-19 17:49:09 -0600634 with open(sizes, 'w', encoding='utf-8') as outf:
635 print('\n'.join(lines), file=outf)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600636
Simon Glasse3c85ab2020-04-17 17:51:34 -0600637 if not work_in_output:
638 # Write out the configuration files, with a special case for SPL
639 for dirname in ['', 'spl', 'tpl']:
Simon Glass9d29f952023-07-19 17:49:27 -0600640 copy_files(
Simon Glasse3c85ab2020-04-17 17:51:34 -0600641 result.out_dir, build_dir, dirname,
642 ['u-boot.cfg', 'spl/u-boot-spl.cfg', 'tpl/u-boot-tpl.cfg',
643 '.config', 'include/autoconf.mk',
644 'include/generated/autoconf.h'])
Simon Glass4b14c532015-02-05 22:06:14 -0700645
Simon Glasse3c85ab2020-04-17 17:51:34 -0600646 # Now write the actual build output
647 if keep_outputs:
Simon Glass31837da2023-09-07 10:00:17 -0600648 to_copy = ['u-boot*', '*.map', 'MLO', 'SPL',
649 'include/autoconf.mk', 'spl/u-boot-spl*',
650 'tpl/u-boot-tpl*', 'vpl/u-boot-vpl*']
651 to_copy += [f'*{ext}' for ext in COMMON_EXTS]
652 copy_files(result.out_dir, build_dir, '', to_copy)
Simon Glass4b14c532015-02-05 22:06:14 -0700653
Simon Glassc5077c32023-07-19 17:49:08 -0600654 def _send_result(self, result):
Simon Glassa3d01442021-04-11 16:27:26 +1200655 """Send a result to the builder for processing
656
657 Args:
Simon Glass465567f2023-07-19 17:49:26 -0600658 result (CommandResult): results of the build
Simon Glass9bf9a722021-04-11 16:27:27 +1200659
660 Raises:
Simon Glass465567f2023-07-19 17:49:26 -0600661 ValueError: self.test_exception is true (for testing)
Simon Glassa3d01442021-04-11 16:27:26 +1200662 """
Simon Glass9bf9a722021-04-11 16:27:27 +1200663 if self.test_exception:
664 raise ValueError('test exception')
Simon Glassa3d01442021-04-11 16:27:26 +1200665 if self.thread_num != -1:
666 self.builder.out_queue.put(result)
667 else:
Simon Glassbc74d942023-07-19 17:49:06 -0600668 self.builder.process_result(result)
Simon Glassa3d01442021-04-11 16:27:26 +1200669
Simon Glassc5077c32023-07-19 17:49:08 -0600670 def run_job(self, job):
Simon Glass4a1e88b2014-08-09 15:33:00 -0600671 """Run a single job
672
673 A job consists of a building a list of commits for a particular board.
674
675 Args:
Simon Glass465567f2023-07-19 17:49:26 -0600676 job (Job): Job to build
Simon Glassc635d892021-01-30 22:17:46 -0700677
Simon Glass465567f2023-07-19 17:49:26 -0600678 Raises:
679 ValueError: Thread was interrupted
Simon Glass4a1e88b2014-08-09 15:33:00 -0600680 """
Simon Glass8132f982022-07-11 19:03:57 -0600681 brd = job.brd
Simon Glassbc74d942023-07-19 17:49:06 -0600682 work_dir = self.builder.get_thread_dir(self.thread_num)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600683 self.toolchain = None
684 if job.commits:
685 # Run 'make board_defconfig' on the first commit
686 do_config = True
687 commit_upto = 0
688 force_build = False
689 for commit_upto in range(0, len(job.commits), job.step):
Simon Glassc5077c32023-07-19 17:49:08 -0600690 result, request_config = self.run_commit(commit_upto, brd,
Simon Glassb55b57d2016-11-16 14:09:25 -0700691 work_dir, do_config, self.builder.config_only,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600692 force_build or self.builder.force_build,
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600693 self.builder.force_build_failures,
Simon Glasse5650a82022-01-22 05:07:33 -0700694 job.work_in_output, job.adjust_cfg)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600695 failed = result.return_code or result.stderr
696 did_config = do_config
697 if failed and not do_config:
698 # If our incremental build failed, try building again
699 # with a reconfig.
700 if self.builder.force_config_on_failure:
Simon Glassc5077c32023-07-19 17:49:08 -0600701 result, request_config = self.run_commit(commit_upto,
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600702 brd, work_dir, True, False, True, False,
Simon Glasse5650a82022-01-22 05:07:33 -0700703 job.work_in_output, job.adjust_cfg)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600704 did_config = True
705 if not self.builder.force_reconfig:
706 do_config = request_config
707
708 # If we built that commit, then config is done. But if we got
709 # an warning, reconfig next time to force it to build the same
710 # files that created warnings this time. Otherwise an
711 # incremental build may not build the same file, and we will
712 # think that the warning has gone away.
713 # We could avoid this by using -Werror everywhere...
714 # For errors, the problem doesn't happen, since presumably
715 # the build stopped and didn't generate output, so will retry
716 # that file next time. So we could detect warnings and deal
717 # with them specially here. For now, we just reconfigure if
718 # anything goes work.
719 # Of course this is substantially slower if there are build
720 # errors/warnings (e.g. 2-3x slower even if only 10% of builds
721 # have problems).
722 if (failed and not result.already_done and not did_config and
723 self.builder.force_config_on_failure):
724 # If this build failed, try the next one with a
725 # reconfigure.
726 # Sometimes if the board_config.h file changes it can mess
727 # with dependencies, and we get:
728 # make: *** No rule to make target `include/autoconf.mk',
729 # needed by `depend'.
730 do_config = True
731 force_build = True
732 else:
733 force_build = False
734 if self.builder.force_config_on_failure:
735 if failed:
736 do_config = True
737 result.commit_upto = commit_upto
738 if result.return_code < 0:
739 raise ValueError('Interrupt')
740
741 # We have the build results, so output the result
Simon Glassc5077c32023-07-19 17:49:08 -0600742 self._write_result(result, job.keep_outputs, job.work_in_output)
743 self._send_result(result)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600744 else:
745 # Just build the currently checked-out build
Simon Glassc5077c32023-07-19 17:49:08 -0600746 result, request_config = self.run_commit(None, brd, work_dir, True,
Simon Glassb55b57d2016-11-16 14:09:25 -0700747 self.builder.config_only, True,
Simon Glasse5650a82022-01-22 05:07:33 -0700748 self.builder.force_build_failures, job.work_in_output,
749 job.adjust_cfg)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600750 result.commit_upto = 0
Simon Glassc5077c32023-07-19 17:49:08 -0600751 self._write_result(result, job.keep_outputs, job.work_in_output)
752 self._send_result(result)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600753
754 def run(self):
755 """Our thread's run function
756
757 This thread picks a job from the queue, runs it, and then goes to the
758 next job.
759 """
Simon Glass4a1e88b2014-08-09 15:33:00 -0600760 while True:
761 job = self.builder.queue.get()
Simon Glass9bf9a722021-04-11 16:27:27 +1200762 try:
Simon Glassc5077c32023-07-19 17:49:08 -0600763 self.run_job(job)
Simon Glassd07e5532023-07-19 17:49:09 -0600764 except Exception as exc:
765 print('Thread exception (use -T0 to run without threads):',
766 exc)
767 self.builder.thread_exceptions.append(exc)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600768 self.builder.queue.task_done()