blob: 25f460c207db1f4a188e02ddf52b58c47eee2990 [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 Glass465567f2023-07-19 17:49:26 -060026def mkdir(dirname, parents=False):
Simon Glass4a1e88b2014-08-09 15:33:00 -060027 """Make a directory if it doesn't already exist.
28
29 Args:
Simon Glass465567f2023-07-19 17:49:26 -060030 dirname (str): Directory to create
31 parents (bool): True to also make parent directories
32
33 Raises:
34 OSError: File already exists
Simon Glass4a1e88b2014-08-09 15:33:00 -060035 """
36 try:
Thierry Reding336d5bd2014-08-19 10:22:39 +020037 if parents:
38 os.makedirs(dirname)
39 else:
40 os.mkdir(dirname)
Simon Glass4a1e88b2014-08-09 15:33:00 -060041 except OSError as err:
42 if err.errno == errno.EEXIST:
Lothar Waßmannce6df922018-04-08 05:14:11 -060043 if os.path.realpath('.') == os.path.realpath(dirname):
Simon Glassd07e5532023-07-19 17:49:09 -060044 print(f"Cannot create the current working directory '{dirname}'!")
Lothar Waßmannce6df922018-04-08 05:14:11 -060045 sys.exit(1)
Simon Glass4a1e88b2014-08-09 15:33:00 -060046 else:
47 raise
48
Simon Glasscb2de022023-07-19 17:49:20 -060049
50def _remove_old_outputs(out_dir):
51 """Remove any old output-target files
52
53 Args:
54 out_dir (str): Output directory for the build
55
56 Since we use a build directory that was previously used by another
57 board, it may have produced an SPL image. If we don't remove it (i.e.
58 see do_config and self.mrproper below) then it will appear to be the
59 output of this build, even if it does not produce SPL images.
60 """
61 for elf in BASE_ELF_FILENAMES:
62 fname = os.path.join(out_dir, elf)
63 if os.path.exists(fname):
64 os.remove(fname)
65
66
Simon Glass9d29f952023-07-19 17:49:27 -060067def copy_files(out_dir, build_dir, dirname, patterns):
68 """Copy files from the build directory to the output.
69
70 Args:
71 out_dir (str): Path to output directory containing the files
72 build_dir (str): Place to copy the files
73 dirname (str): Source directory, '' for normal U-Boot, 'spl' for SPL
74 patterns (list of str): A list of filenames to copy, each relative
75 to the build directory
76 """
77 for pattern in patterns:
78 file_list = glob.glob(os.path.join(out_dir, dirname, pattern))
79 for fname in file_list:
80 target = os.path.basename(fname)
81 if dirname:
82 base, ext = os.path.splitext(target)
83 if ext:
84 target = f'{base}-{dirname}{ext}'
85 shutil.copy(fname, os.path.join(build_dir, target))
86
87
Simon Glassd07e5532023-07-19 17:49:09 -060088# pylint: disable=R0903
Simon Glass4a1e88b2014-08-09 15:33:00 -060089class BuilderJob:
90 """Holds information about a job to be performed by a thread
91
92 Members:
Simon Glass8132f982022-07-11 19:03:57 -060093 brd: Board object to build
Simon Glassdf890152020-03-18 09:42:41 -060094 commits: List of Commit objects to build
95 keep_outputs: True to save build output files
96 step: 1 to process every commit, n to process every nth commit
Simon Glassb6eb8cf2020-03-18 09:42:42 -060097 work_in_output: Use the output directory as the work directory and
98 don't write to a separate output directory.
Simon Glass4a1e88b2014-08-09 15:33:00 -060099 """
100 def __init__(self):
Simon Glass8132f982022-07-11 19:03:57 -0600101 self.brd = None
Simon Glass4a1e88b2014-08-09 15:33:00 -0600102 self.commits = []
Simon Glassdf890152020-03-18 09:42:41 -0600103 self.keep_outputs = False
104 self.step = 1
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600105 self.work_in_output = False
Simon Glass4a1e88b2014-08-09 15:33:00 -0600106
107
108class ResultThread(threading.Thread):
109 """This thread processes results from builder threads.
110
111 It simply passes the results on to the builder. There is only one
112 result thread, and this helps to serialise the build output.
113 """
114 def __init__(self, builder):
115 """Set up a new result thread
116
117 Args:
118 builder: Builder which will be sent each result
119 """
120 threading.Thread.__init__(self)
121 self.builder = builder
122
123 def run(self):
124 """Called to start up the result thread.
125
126 We collect the next result job and pass it on to the build.
127 """
128 while True:
129 result = self.builder.out_queue.get()
Simon Glassbc74d942023-07-19 17:49:06 -0600130 self.builder.process_result(result)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600131 self.builder.out_queue.task_done()
132
133
134class BuilderThread(threading.Thread):
135 """This thread builds U-Boot for a particular board.
136
137 An input queue provides each new job. We run 'make' to build U-Boot
138 and then pass the results on to the output queue.
139
140 Members:
141 builder: The builder which contains information we might need
142 thread_num: Our thread number (0-n-1), used to decide on a
Simon Glassa29b3ea2021-04-11 16:27:25 +1200143 temporary directory. If this is -1 then there are no threads
144 and we are the (only) main process
145 mrproper: Use 'make mrproper' before each reconfigure
146 per_board_out_dir: True to build in a separate persistent directory per
147 board rather than a thread-specific directory
148 test_exception: Used for testing; True to raise an exception instead of
149 reporting the build result
Simon Glass4a1e88b2014-08-09 15:33:00 -0600150 """
Simon Glass9bf9a722021-04-11 16:27:27 +1200151 def __init__(self, builder, thread_num, mrproper, per_board_out_dir,
152 test_exception=False):
Simon Glass4a1e88b2014-08-09 15:33:00 -0600153 """Set up a new builder thread"""
154 threading.Thread.__init__(self)
155 self.builder = builder
156 self.thread_num = thread_num
Simon Glass6029af12020-04-09 15:08:51 -0600157 self.mrproper = mrproper
Stephen Warren97c96902016-04-11 10:48:44 -0600158 self.per_board_out_dir = per_board_out_dir
Simon Glass9bf9a722021-04-11 16:27:27 +1200159 self.test_exception = test_exception
Simon Glassd07e5532023-07-19 17:49:09 -0600160 self.toolchain = None
Simon Glass4a1e88b2014-08-09 15:33:00 -0600161
Simon Glassc5077c32023-07-19 17:49:08 -0600162 def make(self, commit, brd, stage, cwd, *args, **kwargs):
Simon Glass4a1e88b2014-08-09 15:33:00 -0600163 """Run 'make' on a particular commit and board.
164
165 The source code will already be checked out, so the 'commit'
166 argument is only for information.
167
168 Args:
Simon Glass465567f2023-07-19 17:49:26 -0600169 commit (Commit): Commit that is being built
170 brd (Board): Board that is being built
171 stage (str): Stage of the build. Valid stages are:
Roger Meiere0a0e552014-08-20 22:10:29 +0200172 mrproper - can be called to clean source
Simon Glass4a1e88b2014-08-09 15:33:00 -0600173 config - called to configure for a board
174 build - the main make invocation - it does the build
Simon Glass465567f2023-07-19 17:49:26 -0600175 cwd (str): Working directory to set, or None to leave it alone
176 *args (list of str): Arguments to pass to 'make'
177 **kwargs (dict): A list of keyword arguments to pass to
178 command.run_pipe()
Simon Glass4a1e88b2014-08-09 15:33:00 -0600179
180 Returns:
181 CommandResult object
182 """
183 return self.builder.do_make(commit, brd, stage, cwd, *args,
184 **kwargs)
185
Simon Glass926c11b2023-07-19 17:49:15 -0600186 def _build_args(self, brd, out_dir, out_rel_dir, work_dir, commit_upto):
Simon Glass8d0b6b42023-07-19 17:49:13 -0600187 """Set up arguments to the args list based on the settings
188
189 Args:
Simon Glassec7132b2023-07-19 17:49:14 -0600190 brd (Board): Board to create arguments for
Simon Glass926c11b2023-07-19 17:49:15 -0600191 out_dir (str): Path to output directory containing the files
192 out_rel_dir (str): Output directory relative to the current dir
193 work_dir (str): Directory to which the source will be checked out
194 commit_upto (int): Commit number to build (0...n-1)
195
196 Returns:
197 tuple:
198 list of str: Arguments to pass to make
199 str: Current working directory, or None if no commit
200 str: Source directory (typically the work directory)
Simon Glass8d0b6b42023-07-19 17:49:13 -0600201 """
Simon Glass926c11b2023-07-19 17:49:15 -0600202 args = []
203 cwd = work_dir
204 src_dir = os.path.realpath(work_dir)
205 if not self.builder.in_tree:
206 if commit_upto is None:
207 # In this case we are building in the original source directory
208 # (i.e. the current directory where buildman is invoked. The
209 # output directory is set to this thread's selected work
210 # directory.
211 #
212 # Symlinks can confuse U-Boot's Makefile since we may use '..'
213 # in our path, so remove them.
214 real_dir = os.path.realpath(out_dir)
215 args.append(f'O={real_dir}')
216 cwd = None
217 src_dir = os.getcwd()
218 else:
219 args.append(f'O={out_rel_dir}')
Simon Glass8d0b6b42023-07-19 17:49:13 -0600220 if self.builder.verbose_build:
221 args.append('V=1')
222 else:
223 args.append('-s')
224 if self.builder.num_jobs is not None:
225 args.extend(['-j', str(self.builder.num_jobs)])
226 if self.builder.warnings_as_errors:
227 args.append('KCFLAGS=-Werror')
228 args.append('HOSTCFLAGS=-Werror')
229 if self.builder.allow_missing:
230 args.append('BINMAN_ALLOW_MISSING=1')
231 if self.builder.no_lto:
232 args.append('NO_LTO=1')
233 if self.builder.reproducible_builds:
234 args.append('SOURCE_DATE_EPOCH=0')
Simon Glassec7132b2023-07-19 17:49:14 -0600235 args.extend(self.builder.toolchains.GetMakeArguments(brd))
236 args.extend(self.toolchain.MakeArgs())
Simon Glass926c11b2023-07-19 17:49:15 -0600237 return args, cwd, src_dir
Simon Glass8d0b6b42023-07-19 17:49:13 -0600238
Simon Glass147f3492023-07-19 17:49:17 -0600239 def _reconfigure(self, commit, brd, cwd, args, env, config_args, config_out,
240 cmd_list):
241 """Reconfigure the build
242
243 Args:
244 commit (Commit): Commit only being built
245 brd (Board): Board being built
246 cwd (str): Current working directory
247 args (list of str): Arguments to pass to make
248 env (dict): Environment strings
249 config_args (list of str): defconfig arg for this board
250 cmd_list (list of str): List to add the commands to, for logging
251
252 Returns:
253 CommandResult object
254 """
255 if self.mrproper:
256 result = self.make(commit, brd, 'mrproper', cwd, 'mrproper', *args,
257 env=env)
258 config_out.write(result.combined)
259 cmd_list.append([self.builder.gnu_make, 'mrproper', *args])
260 result = self.make(commit, brd, 'config', cwd, *(args + config_args),
261 env=env)
262 cmd_list.append([self.builder.gnu_make] + args + config_args)
263 config_out.write(result.combined)
264 return result
265
Simon Glass3b4f50e2023-07-19 17:49:18 -0600266 def _build(self, commit, brd, cwd, args, env, cmd_list, config_only):
267 """Perform the build
268
269 Args:
270 commit (Commit): Commit only being built
271 brd (Board): Board being built
272 cwd (str): Current working directory
273 args (list of str): Arguments to pass to make
274 env (dict): Environment strings
275 cmd_list (list of str): List to add the commands to, for logging
276 config_only (bool): True if this is a config-only build (using the
277 'make cfg' target)
278
279 Returns:
280 CommandResult object
281 """
282 if config_only:
283 args.append('cfg')
284 result = self.make(commit, brd, 'build', cwd, *args, env=env)
285 cmd_list.append([self.builder.gnu_make] + args)
286 if (result.return_code == 2 and
287 ('Some images are invalid' in result.stderr)):
288 # This is handled later by the check for output in stderr
289 result.return_code = 0
290 return result
291
Simon Glassb9a1b772023-07-19 17:49:24 -0600292 def _read_done_file(self, commit_upto, brd, force_build,
Simon Glassfdd7be22023-07-19 17:49:19 -0600293 force_build_failures):
294 """Check the 'done' file and see if this commit should be built
295
296 Args:
297 commit (Commit): Commit only being built
298 brd (Board): Board being built
Simon Glassfdd7be22023-07-19 17:49:19 -0600299 force_build (bool): Force a build even if one was previously done
300 force_build_failures (bool): Force a bulid if the previous result
301 showed failure
302
303 Returns:
Simon Glassb9a1b772023-07-19 17:49:24 -0600304 tuple:
305 bool: True if build should be built
306 CommandResult: if there was a previous run:
307 - already_done set to True
308 - return_code set to return code
309 - result.stderr set to 'bad' if stderr output was recorded
Simon Glassfdd7be22023-07-19 17:49:19 -0600310 """
Simon Glassb9a1b772023-07-19 17:49:24 -0600311 result = command.CommandResult()
Simon Glassfdd7be22023-07-19 17:49:19 -0600312 done_file = self.builder.get_done_file(commit_upto, brd.target)
313 result.already_done = os.path.exists(done_file)
314 will_build = (force_build or force_build_failures or
315 not result.already_done)
316 if result.already_done:
317 with open(done_file, 'r', encoding='utf-8') as outf:
318 try:
319 result.return_code = int(outf.readline())
320 except ValueError:
321 # The file may be empty due to running out of disk space.
322 # Try a rebuild
323 result.return_code = RETURN_CODE_RETRY
324
325 # Check the signal that the build needs to be retried
326 if result.return_code == RETURN_CODE_RETRY:
327 will_build = True
328 elif will_build:
329 err_file = self.builder.get_err_file(commit_upto, brd.target)
330 if os.path.exists(err_file) and os.stat(err_file).st_size:
331 result.stderr = 'bad'
332 elif not force_build:
333 # The build passed, so no need to build it again
334 will_build = False
Simon Glassb9a1b772023-07-19 17:49:24 -0600335 return will_build, result
Simon Glassfdd7be22023-07-19 17:49:19 -0600336
Simon Glass83a0b862023-07-19 17:49:21 -0600337 def _decide_dirs(self, brd, work_dir, work_in_output):
338 """Decide the output directory to use
339
340 Args:
341 work_dir (str): Directory to which the source will be checked out
342 work_in_output (bool): Use the output directory as the work
343 directory and don't write to a separate output directory.
344
345 Returns:
346 tuple:
347 out_dir (str): Output directory for the build
348 out_rel_dir (str): Output directory relatie to the current dir
349 """
350 if work_in_output or self.builder.in_tree:
351 out_rel_dir = None
352 out_dir = work_dir
353 else:
354 if self.per_board_out_dir:
355 out_rel_dir = os.path.join('..', brd.target)
356 else:
357 out_rel_dir = 'build'
358 out_dir = os.path.join(work_dir, out_rel_dir)
359 return out_dir, out_rel_dir
360
Simon Glass7e989052023-07-19 17:49:22 -0600361 def _checkout(self, commit_upto, work_dir):
362 """Checkout the right commit
363
364 Args:
365 commit_upto (int): Commit number to build (0...n-1)
366 work_dir (str): Directory to which the source will be checked out
367
368 Returns:
369 Commit: Commit being built, or 'current' for current source
370 """
371 if self.builder.commits:
372 commit = self.builder.commits[commit_upto]
373 if self.builder.checkout:
374 git_dir = os.path.join(work_dir, '.git')
375 gitutil.checkout(commit.hash, git_dir, work_dir, force=True)
376 else:
377 commit = 'current'
378 return commit
379
Simon Glass2df95fe2023-07-19 17:49:23 -0600380 def _config_and_build(self, commit_upto, brd, work_dir, do_config,
381 config_only, adjust_cfg, commit, out_dir, out_rel_dir,
382 result):
383 """Do the build, configuring first if necessary
384
385 Args:
386 commit_upto (int): Commit number to build (0...n-1)
387 brd (Board): Board to create arguments for
388 work_dir (str): Directory to which the source will be checked out
389 do_config (bool): True to run a make <board>_defconfig on the source
390 config_only (bool): Only configure the source, do not build it
391 adjust_cfg (list of str): See the cfgutil module and run_commit()
392 commit (Commit): Commit only being built
393 out_dir (str): Output directory for the build
394 out_rel_dir (str): Output directory relatie to the current dir
395 result (CommandResult): Previous result
396
397 Returns:
398 tuple:
399 result (CommandResult): Result of the build
400 do_config (bool): indicates whether 'make config' is needed on
401 the next incremental build
402 """
403 # Set up the environment and command line
404 env = self.toolchain.MakeEnvironment(self.builder.full_path)
405 mkdir(out_dir)
406
407 args, cwd, src_dir = self._build_args(brd, out_dir, out_rel_dir,
408 work_dir, commit_upto)
409 config_args = [f'{brd.target}_defconfig']
410 config_out = io.StringIO()
411
412 _remove_old_outputs(out_dir)
413
414 # If we need to reconfigure, do that now
415 cfg_file = os.path.join(out_dir, '.config')
416 cmd_list = []
417 if do_config or adjust_cfg:
418 result = self._reconfigure(
419 commit, brd, cwd, args, env, config_args, config_out, cmd_list)
420 do_config = False # No need to configure next time
421 if adjust_cfg:
422 cfgutil.adjust_cfg_file(cfg_file, adjust_cfg)
423
424 # Now do the build, if everything looks OK
425 if result.return_code == 0:
426 result = self._build(commit, brd, cwd, args, env, cmd_list,
427 config_only)
428 if adjust_cfg:
429 errs = cfgutil.check_cfg_file(cfg_file, adjust_cfg)
430 if errs:
431 result.stderr += errs
432 result.return_code = 1
433 result.stderr = result.stderr.replace(src_dir + '/', '')
434 if self.builder.verbose_build:
435 result.stdout = config_out.getvalue() + result.stdout
436 result.cmd_list = cmd_list
437 return result, do_config
Simon Glass7e989052023-07-19 17:49:22 -0600438
Simon Glassc5077c32023-07-19 17:49:08 -0600439 def run_commit(self, commit_upto, brd, work_dir, do_config, config_only,
Simon Glasse5650a82022-01-22 05:07:33 -0700440 force_build, force_build_failures, work_in_output,
441 adjust_cfg):
Simon Glass4a1e88b2014-08-09 15:33:00 -0600442 """Build a particular commit.
443
444 If the build is already done, and we are not forcing a build, we skip
445 the build and just return the previously-saved results.
446
447 Args:
Simon Glass465567f2023-07-19 17:49:26 -0600448 commit_upto (int): Commit number to build (0...n-1)
449 brd (Board): Board to build
450 work_dir (str): Directory to which the source will be checked out
451 do_config (bool): True to run a make <board>_defconfig on the source
452 config_only (bool): Only configure the source, do not build it
453 force_build (bool): Force a build even if one was previously done
454 force_build_failures (bool): Force a bulid if the previous result
455 showed failure
456 work_in_output (bool) : Use the output directory as the work
457 directory and don't write to a separate output directory.
Simon Glasse5650a82022-01-22 05:07:33 -0700458 adjust_cfg (list of str): List of changes to make to .config file
459 before building. Each is one of (where C is either CONFIG_xxx
460 or just xxx):
461 C to enable C
462 ~C to disable C
463 C=val to set the value of C (val must have quotes if C is
464 a string Kconfig
Simon Glass4a1e88b2014-08-09 15:33:00 -0600465
466 Returns:
467 tuple containing:
468 - CommandResult object containing the results of the build
469 - boolean indicating whether 'make config' is still needed
470 """
471 # Create a default result - it will be overwritte by the call to
Simon Glassc5077c32023-07-19 17:49:08 -0600472 # self.make() below, in the event that we do a build.
Simon Glass83a0b862023-07-19 17:49:21 -0600473 out_dir, out_rel_dir = self._decide_dirs(brd, work_dir, work_in_output)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600474
475 # Check if the job was already completed last time
Simon Glassb9a1b772023-07-19 17:49:24 -0600476 will_build, result = self._read_done_file(commit_upto, brd, force_build,
477 force_build_failures)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600478
479 if will_build:
480 # We are going to have to build it. First, get a toolchain
481 if not self.toolchain:
482 try:
483 self.toolchain = self.builder.toolchains.Select(brd.arch)
484 except ValueError as err:
485 result.return_code = 10
486 result.stdout = ''
Simon Glassbf353b82023-07-19 17:49:25 -0600487 result.stderr = f'Tool chain error for {brd.arch}: {str(err)}'
Simon Glass4a1e88b2014-08-09 15:33:00 -0600488
489 if self.toolchain:
Simon Glass7e989052023-07-19 17:49:22 -0600490 commit = self._checkout(commit_upto, work_dir)
Simon Glass2df95fe2023-07-19 17:49:23 -0600491 result, do_config = self._config_and_build(
492 commit_upto, brd, work_dir, do_config, config_only,
493 adjust_cfg, commit, out_dir, out_rel_dir, result)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600494 result.already_done = False
495
496 result.toolchain = self.toolchain
497 result.brd = brd
498 result.commit_upto = commit_upto
499 result.out_dir = out_dir
500 return result, do_config
501
Simon Glassc5077c32023-07-19 17:49:08 -0600502 def _write_result(self, result, keep_outputs, work_in_output):
Simon Glass4a1e88b2014-08-09 15:33:00 -0600503 """Write a built result to the output directory.
504
505 Args:
Simon Glass465567f2023-07-19 17:49:26 -0600506 result (CommandResult): result to write
507 keep_outputs (bool): True to store the output binaries, False
Simon Glass4a1e88b2014-08-09 15:33:00 -0600508 to delete them
Simon Glass465567f2023-07-19 17:49:26 -0600509 work_in_output (bool): Use the output directory as the work
510 directory and don't write to a separate output directory.
Simon Glass4a1e88b2014-08-09 15:33:00 -0600511 """
Simon Glassfd3eea12015-02-05 22:06:13 -0700512 # If we think this might have been aborted with Ctrl-C, record the
513 # failure but not that we are 'done' with this board. A retry may fix
514 # it.
Simon Glass2e737452021-10-19 21:43:23 -0600515 maybe_aborted = result.stderr and 'No child processes' in result.stderr
Simon Glass4a1e88b2014-08-09 15:33:00 -0600516
Simon Glass2e737452021-10-19 21:43:23 -0600517 if result.return_code >= 0 and result.already_done:
Simon Glass4a1e88b2014-08-09 15:33:00 -0600518 return
519
520 # Write the output and stderr
Simon Glass4cb54682023-07-19 17:49:10 -0600521 output_dir = self.builder.get_output_dir(result.commit_upto)
Simon Glassc5077c32023-07-19 17:49:08 -0600522 mkdir(output_dir)
Simon Glassbc74d942023-07-19 17:49:06 -0600523 build_dir = self.builder.get_build_dir(result.commit_upto,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600524 result.brd.target)
Simon Glassc5077c32023-07-19 17:49:08 -0600525 mkdir(build_dir)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600526
527 outfile = os.path.join(build_dir, 'log')
Simon Glassd07e5532023-07-19 17:49:09 -0600528 with open(outfile, 'w', encoding='utf-8') as outf:
Simon Glass4a1e88b2014-08-09 15:33:00 -0600529 if result.stdout:
Simon Glassd07e5532023-07-19 17:49:09 -0600530 outf.write(result.stdout)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600531
Simon Glassbc74d942023-07-19 17:49:06 -0600532 errfile = self.builder.get_err_file(result.commit_upto,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600533 result.brd.target)
534 if result.stderr:
Simon Glassd07e5532023-07-19 17:49:09 -0600535 with open(errfile, 'w', encoding='utf-8') as outf:
536 outf.write(result.stderr)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600537 elif os.path.exists(errfile):
538 os.remove(errfile)
539
Simon Glass2e737452021-10-19 21:43:23 -0600540 # Fatal error
541 if result.return_code < 0:
542 return
543
Simon Glass4a1e88b2014-08-09 15:33:00 -0600544 if result.toolchain:
545 # Write the build result and toolchain information.
Simon Glassbc74d942023-07-19 17:49:06 -0600546 done_file = self.builder.get_done_file(result.commit_upto,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600547 result.brd.target)
Simon Glassd07e5532023-07-19 17:49:09 -0600548 with open(done_file, 'w', encoding='utf-8') as outf:
Simon Glassfd3eea12015-02-05 22:06:13 -0700549 if maybe_aborted:
550 # Special code to indicate we need to retry
Simon Glassd07e5532023-07-19 17:49:09 -0600551 outf.write(f'{RETURN_CODE_RETRY}')
Simon Glassfd3eea12015-02-05 22:06:13 -0700552 else:
Simon Glassd07e5532023-07-19 17:49:09 -0600553 outf.write(f'{result.return_code}')
554 with open(os.path.join(build_dir, 'toolchain'), 'w',
555 encoding='utf-8') as outf:
556 print('gcc', result.toolchain.gcc, file=outf)
557 print('path', result.toolchain.path, file=outf)
558 print('cross', result.toolchain.cross, file=outf)
559 print('arch', result.toolchain.arch, file=outf)
560 outf.write(f'{result.return_code}')
Simon Glass4a1e88b2014-08-09 15:33:00 -0600561
Simon Glass4a1e88b2014-08-09 15:33:00 -0600562 # Write out the image and function size information and an objdump
Simon Glassd48a46c2014-12-01 17:34:00 -0700563 env = result.toolchain.MakeEnvironment(self.builder.full_path)
Simon Glassd07e5532023-07-19 17:49:09 -0600564 with open(os.path.join(build_dir, 'out-env'), 'wb') as outf:
Simon Glass9e90d312019-01-07 16:44:23 -0700565 for var in sorted(env.keys()):
Simon Glassd07e5532023-07-19 17:49:09 -0600566 outf.write(b'%s="%s"' % (var, env[var]))
Simon Glass1382b1d2023-02-21 12:40:27 -0700567
568 with open(os.path.join(build_dir, 'out-cmd'), 'w',
Simon Glassd07e5532023-07-19 17:49:09 -0600569 encoding='utf-8') as outf:
Simon Glass1382b1d2023-02-21 12:40:27 -0700570 for cmd in result.cmd_list:
Simon Glassd07e5532023-07-19 17:49:09 -0600571 print(' '.join(cmd), file=outf)
Simon Glass1382b1d2023-02-21 12:40:27 -0700572
Simon Glass4a1e88b2014-08-09 15:33:00 -0600573 lines = []
Simon Glasse0f19712020-12-16 17:24:17 -0700574 for fname in BASE_ELF_FILENAMES:
Simon Glassd07e5532023-07-19 17:49:09 -0600575 cmd = [f'{self.toolchain.cross}nm', '--size-sort', fname]
Simon Glass840be732022-01-29 14:14:05 -0700576 nm_result = command.run_pipe([cmd], capture=True,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600577 capture_stderr=True, cwd=result.out_dir,
578 raise_on_error=False, env=env)
579 if nm_result.stdout:
Simon Glassd07e5532023-07-19 17:49:09 -0600580 nm_fname = self.builder.get_func_sizes_file(
581 result.commit_upto, result.brd.target, fname)
582 with open(nm_fname, 'w', encoding='utf-8') as outf:
583 print(nm_result.stdout, end=' ', file=outf)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600584
Simon Glassd07e5532023-07-19 17:49:09 -0600585 cmd = [f'{self.toolchain.cross}objdump', '-h', fname]
Simon Glass840be732022-01-29 14:14:05 -0700586 dump_result = command.run_pipe([cmd], capture=True,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600587 capture_stderr=True, cwd=result.out_dir,
588 raise_on_error=False, env=env)
589 rodata_size = ''
590 if dump_result.stdout:
Simon Glassbc74d942023-07-19 17:49:06 -0600591 objdump = self.builder.get_objdump_file(result.commit_upto,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600592 result.brd.target, fname)
Simon Glassd07e5532023-07-19 17:49:09 -0600593 with open(objdump, 'w', encoding='utf-8') as outf:
594 print(dump_result.stdout, end=' ', file=outf)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600595 for line in dump_result.stdout.splitlines():
596 fields = line.split()
597 if len(fields) > 5 and fields[1] == '.rodata':
598 rodata_size = fields[2]
599
Simon Glassd07e5532023-07-19 17:49:09 -0600600 cmd = [f'{self.toolchain.cross}size', fname]
Simon Glass840be732022-01-29 14:14:05 -0700601 size_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 if size_result.stdout:
605 lines.append(size_result.stdout.splitlines()[1] + ' ' +
606 rodata_size)
607
Alex Kiernanf07ed232018-05-31 04:48:33 +0000608 # Extract the environment from U-Boot and dump it out
Simon Glassd07e5532023-07-19 17:49:09 -0600609 cmd = [f'{self.toolchain.cross}objcopy', '-O', 'binary',
Alex Kiernanf07ed232018-05-31 04:48:33 +0000610 '-j', '.rodata.default_environment',
611 'env/built-in.o', 'uboot.env']
Simon Glass840be732022-01-29 14:14:05 -0700612 command.run_pipe([cmd], capture=True,
Alex Kiernanf07ed232018-05-31 04:48:33 +0000613 capture_stderr=True, cwd=result.out_dir,
614 raise_on_error=False, env=env)
Simon Glasse3c85ab2020-04-17 17:51:34 -0600615 if not work_in_output:
Simon Glass9d29f952023-07-19 17:49:27 -0600616 copy_files(result.out_dir, build_dir, '', ['uboot.env'])
Alex Kiernanf07ed232018-05-31 04:48:33 +0000617
Simon Glass4a1e88b2014-08-09 15:33:00 -0600618 # Write out the image sizes file. This is similar to the output
619 # of binutil's 'size' utility, but it omits the header line and
620 # adds an additional hex value at the end of each line for the
621 # rodata size
Simon Glassd07e5532023-07-19 17:49:09 -0600622 if lines:
Simon Glassbc74d942023-07-19 17:49:06 -0600623 sizes = self.builder.get_sizes_file(result.commit_upto,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600624 result.brd.target)
Simon Glassd07e5532023-07-19 17:49:09 -0600625 with open(sizes, 'w', encoding='utf-8') as outf:
626 print('\n'.join(lines), file=outf)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600627
Simon Glasse3c85ab2020-04-17 17:51:34 -0600628 if not work_in_output:
629 # Write out the configuration files, with a special case for SPL
630 for dirname in ['', 'spl', 'tpl']:
Simon Glass9d29f952023-07-19 17:49:27 -0600631 copy_files(
Simon Glasse3c85ab2020-04-17 17:51:34 -0600632 result.out_dir, build_dir, dirname,
633 ['u-boot.cfg', 'spl/u-boot-spl.cfg', 'tpl/u-boot-tpl.cfg',
634 '.config', 'include/autoconf.mk',
635 'include/generated/autoconf.h'])
Simon Glass4b14c532015-02-05 22:06:14 -0700636
Simon Glasse3c85ab2020-04-17 17:51:34 -0600637 # Now write the actual build output
638 if keep_outputs:
Simon Glass9d29f952023-07-19 17:49:27 -0600639 copy_files(
Simon Glasse3c85ab2020-04-17 17:51:34 -0600640 result.out_dir, build_dir, '',
641 ['u-boot*', '*.bin', '*.map', '*.img', 'MLO', 'SPL',
642 'include/autoconf.mk', 'spl/u-boot-spl*'])
Simon Glass4b14c532015-02-05 22:06:14 -0700643
Simon Glassc5077c32023-07-19 17:49:08 -0600644 def _send_result(self, result):
Simon Glassa3d01442021-04-11 16:27:26 +1200645 """Send a result to the builder for processing
646
647 Args:
Simon Glass465567f2023-07-19 17:49:26 -0600648 result (CommandResult): results of the build
Simon Glass9bf9a722021-04-11 16:27:27 +1200649
650 Raises:
Simon Glass465567f2023-07-19 17:49:26 -0600651 ValueError: self.test_exception is true (for testing)
Simon Glassa3d01442021-04-11 16:27:26 +1200652 """
Simon Glass9bf9a722021-04-11 16:27:27 +1200653 if self.test_exception:
654 raise ValueError('test exception')
Simon Glassa3d01442021-04-11 16:27:26 +1200655 if self.thread_num != -1:
656 self.builder.out_queue.put(result)
657 else:
Simon Glassbc74d942023-07-19 17:49:06 -0600658 self.builder.process_result(result)
Simon Glassa3d01442021-04-11 16:27:26 +1200659
Simon Glassc5077c32023-07-19 17:49:08 -0600660 def run_job(self, job):
Simon Glass4a1e88b2014-08-09 15:33:00 -0600661 """Run a single job
662
663 A job consists of a building a list of commits for a particular board.
664
665 Args:
Simon Glass465567f2023-07-19 17:49:26 -0600666 job (Job): Job to build
Simon Glassc635d892021-01-30 22:17:46 -0700667
Simon Glass465567f2023-07-19 17:49:26 -0600668 Raises:
669 ValueError: Thread was interrupted
Simon Glass4a1e88b2014-08-09 15:33:00 -0600670 """
Simon Glass8132f982022-07-11 19:03:57 -0600671 brd = job.brd
Simon Glassbc74d942023-07-19 17:49:06 -0600672 work_dir = self.builder.get_thread_dir(self.thread_num)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600673 self.toolchain = None
674 if job.commits:
675 # Run 'make board_defconfig' on the first commit
676 do_config = True
677 commit_upto = 0
678 force_build = False
679 for commit_upto in range(0, len(job.commits), job.step):
Simon Glassc5077c32023-07-19 17:49:08 -0600680 result, request_config = self.run_commit(commit_upto, brd,
Simon Glassb55b57d2016-11-16 14:09:25 -0700681 work_dir, do_config, self.builder.config_only,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600682 force_build or self.builder.force_build,
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600683 self.builder.force_build_failures,
Simon Glasse5650a82022-01-22 05:07:33 -0700684 job.work_in_output, job.adjust_cfg)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600685 failed = result.return_code or result.stderr
686 did_config = do_config
687 if failed and not do_config:
688 # If our incremental build failed, try building again
689 # with a reconfig.
690 if self.builder.force_config_on_failure:
Simon Glassc5077c32023-07-19 17:49:08 -0600691 result, request_config = self.run_commit(commit_upto,
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600692 brd, work_dir, True, False, True, False,
Simon Glasse5650a82022-01-22 05:07:33 -0700693 job.work_in_output, job.adjust_cfg)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600694 did_config = True
695 if not self.builder.force_reconfig:
696 do_config = request_config
697
698 # If we built that commit, then config is done. But if we got
699 # an warning, reconfig next time to force it to build the same
700 # files that created warnings this time. Otherwise an
701 # incremental build may not build the same file, and we will
702 # think that the warning has gone away.
703 # We could avoid this by using -Werror everywhere...
704 # For errors, the problem doesn't happen, since presumably
705 # the build stopped and didn't generate output, so will retry
706 # that file next time. So we could detect warnings and deal
707 # with them specially here. For now, we just reconfigure if
708 # anything goes work.
709 # Of course this is substantially slower if there are build
710 # errors/warnings (e.g. 2-3x slower even if only 10% of builds
711 # have problems).
712 if (failed and not result.already_done and not did_config and
713 self.builder.force_config_on_failure):
714 # If this build failed, try the next one with a
715 # reconfigure.
716 # Sometimes if the board_config.h file changes it can mess
717 # with dependencies, and we get:
718 # make: *** No rule to make target `include/autoconf.mk',
719 # needed by `depend'.
720 do_config = True
721 force_build = True
722 else:
723 force_build = False
724 if self.builder.force_config_on_failure:
725 if failed:
726 do_config = True
727 result.commit_upto = commit_upto
728 if result.return_code < 0:
729 raise ValueError('Interrupt')
730
731 # We have the build results, so output the result
Simon Glassc5077c32023-07-19 17:49:08 -0600732 self._write_result(result, job.keep_outputs, job.work_in_output)
733 self._send_result(result)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600734 else:
735 # Just build the currently checked-out build
Simon Glassc5077c32023-07-19 17:49:08 -0600736 result, request_config = self.run_commit(None, brd, work_dir, True,
Simon Glassb55b57d2016-11-16 14:09:25 -0700737 self.builder.config_only, True,
Simon Glasse5650a82022-01-22 05:07:33 -0700738 self.builder.force_build_failures, job.work_in_output,
739 job.adjust_cfg)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600740 result.commit_upto = 0
Simon Glassc5077c32023-07-19 17:49:08 -0600741 self._write_result(result, job.keep_outputs, job.work_in_output)
742 self._send_result(result)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600743
744 def run(self):
745 """Our thread's run function
746
747 This thread picks a job from the queue, runs it, and then goes to the
748 next job.
749 """
Simon Glass4a1e88b2014-08-09 15:33:00 -0600750 while True:
751 job = self.builder.queue.get()
Simon Glass9bf9a722021-04-11 16:27:27 +1200752 try:
Simon Glassc5077c32023-07-19 17:49:08 -0600753 self.run_job(job)
Simon Glassd07e5532023-07-19 17:49:09 -0600754 except Exception as exc:
755 print('Thread exception (use -T0 to run without threads):',
756 exc)
757 self.builder.thread_exceptions.append(exc)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600758 self.builder.queue.task_done()