blob: aa4a9d9919c6b16e23f8e0ba25bdbb5bc6baa7c8 [file] [log] [blame]
Tom Rini83d290c2018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glass190064b2014-08-09 15:33:00 -06002# Copyright (c) 2014 Google, Inc
3#
Simon Glass190064b2014-08-09 15:33:00 -06004
Simon Glass606e5432023-07-19 17:49:09 -06005"""Implementation the bulider threads
6
7This module provides the BuilderThread class, which handles calling the builder
8based on the jobs provided.
9"""
10
Simon Glass190064b2014-08-09 15:33:00 -060011import errno
12import glob
Simon Glassdab3a4a2023-07-19 17:49:16 -060013import io
Simon Glass190064b2014-08-09 15:33:00 -060014import os
15import shutil
Lothar Waßmann409fc022018-04-08 05:14:11 -060016import sys
Simon Glass190064b2014-08-09 15:33:00 -060017import threading
18
Simon Glass2b4806e2022-01-22 05:07:33 -070019from buildman import cfgutil
Simon Glassbf776672020-04-17 18:09:04 -060020from patman import gitutil
Simon Glass4583c002023-02-23 18:18:04 -070021from u_boot_pylib import command
Simon Glass190064b2014-08-09 15:33:00 -060022
Simon Glass88c8dcf2015-02-05 22:06:13 -070023RETURN_CODE_RETRY = -1
Simon Glass73da3d22020-12-16 17:24:17 -070024BASE_ELF_FILENAMES = ['u-boot', 'spl/u-boot-spl', 'tpl/u-boot-tpl']
Simon Glass88c8dcf2015-02-05 22:06:13 -070025
Simon Glassf06d3332023-07-19 17:49:08 -060026def mkdir(dirname, parents = False):
Simon Glass190064b2014-08-09 15:33:00 -060027 """Make a directory if it doesn't already exist.
28
29 Args:
30 dirname: Directory to create
31 """
32 try:
Thierry Redingf3d015c2014-08-19 10:22:39 +020033 if parents:
34 os.makedirs(dirname)
35 else:
36 os.mkdir(dirname)
Simon Glass190064b2014-08-09 15:33:00 -060037 except OSError as err:
38 if err.errno == errno.EEXIST:
Lothar Waßmann409fc022018-04-08 05:14:11 -060039 if os.path.realpath('.') == os.path.realpath(dirname):
Simon Glass606e5432023-07-19 17:49:09 -060040 print(f"Cannot create the current working directory '{dirname}'!")
Lothar Waßmann409fc022018-04-08 05:14:11 -060041 sys.exit(1)
Simon Glass190064b2014-08-09 15:33:00 -060042 else:
43 raise
44
Simon Glasse5490b72023-07-19 17:49:20 -060045
46def _remove_old_outputs(out_dir):
47 """Remove any old output-target files
48
49 Args:
50 out_dir (str): Output directory for the build
51
52 Since we use a build directory that was previously used by another
53 board, it may have produced an SPL image. If we don't remove it (i.e.
54 see do_config and self.mrproper below) then it will appear to be the
55 output of this build, even if it does not produce SPL images.
56 """
57 for elf in BASE_ELF_FILENAMES:
58 fname = os.path.join(out_dir, elf)
59 if os.path.exists(fname):
60 os.remove(fname)
61
62
Simon Glass606e5432023-07-19 17:49:09 -060063# pylint: disable=R0903
Simon Glass190064b2014-08-09 15:33:00 -060064class BuilderJob:
65 """Holds information about a job to be performed by a thread
66
67 Members:
Simon Glassf4ed4702022-07-11 19:03:57 -060068 brd: Board object to build
Simon Glasse9fbbf62020-03-18 09:42:41 -060069 commits: List of Commit objects to build
70 keep_outputs: True to save build output files
71 step: 1 to process every commit, n to process every nth commit
Simon Glassd829f122020-03-18 09:42:42 -060072 work_in_output: Use the output directory as the work directory and
73 don't write to a separate output directory.
Simon Glass190064b2014-08-09 15:33:00 -060074 """
75 def __init__(self):
Simon Glassf4ed4702022-07-11 19:03:57 -060076 self.brd = None
Simon Glass190064b2014-08-09 15:33:00 -060077 self.commits = []
Simon Glasse9fbbf62020-03-18 09:42:41 -060078 self.keep_outputs = False
79 self.step = 1
Simon Glassd829f122020-03-18 09:42:42 -060080 self.work_in_output = False
Simon Glass190064b2014-08-09 15:33:00 -060081
82
83class ResultThread(threading.Thread):
84 """This thread processes results from builder threads.
85
86 It simply passes the results on to the builder. There is only one
87 result thread, and this helps to serialise the build output.
88 """
89 def __init__(self, builder):
90 """Set up a new result thread
91
92 Args:
93 builder: Builder which will be sent each result
94 """
95 threading.Thread.__init__(self)
96 self.builder = builder
97
98 def run(self):
99 """Called to start up the result thread.
100
101 We collect the next result job and pass it on to the build.
102 """
103 while True:
104 result = self.builder.out_queue.get()
Simon Glass37edf5f2023-07-19 17:49:06 -0600105 self.builder.process_result(result)
Simon Glass190064b2014-08-09 15:33:00 -0600106 self.builder.out_queue.task_done()
107
108
109class BuilderThread(threading.Thread):
110 """This thread builds U-Boot for a particular board.
111
112 An input queue provides each new job. We run 'make' to build U-Boot
113 and then pass the results on to the output queue.
114
115 Members:
116 builder: The builder which contains information we might need
117 thread_num: Our thread number (0-n-1), used to decide on a
Simon Glass24993312021-04-11 16:27:25 +1200118 temporary directory. If this is -1 then there are no threads
119 and we are the (only) main process
120 mrproper: Use 'make mrproper' before each reconfigure
121 per_board_out_dir: True to build in a separate persistent directory per
122 board rather than a thread-specific directory
123 test_exception: Used for testing; True to raise an exception instead of
124 reporting the build result
Simon Glass190064b2014-08-09 15:33:00 -0600125 """
Simon Glass8116c782021-04-11 16:27:27 +1200126 def __init__(self, builder, thread_num, mrproper, per_board_out_dir,
127 test_exception=False):
Simon Glass190064b2014-08-09 15:33:00 -0600128 """Set up a new builder thread"""
129 threading.Thread.__init__(self)
130 self.builder = builder
131 self.thread_num = thread_num
Simon Glasseb70a2c2020-04-09 15:08:51 -0600132 self.mrproper = mrproper
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600133 self.per_board_out_dir = per_board_out_dir
Simon Glass8116c782021-04-11 16:27:27 +1200134 self.test_exception = test_exception
Simon Glass606e5432023-07-19 17:49:09 -0600135 self.toolchain = None
Simon Glass190064b2014-08-09 15:33:00 -0600136
Simon Glassf06d3332023-07-19 17:49:08 -0600137 def make(self, commit, brd, stage, cwd, *args, **kwargs):
Simon Glass190064b2014-08-09 15:33:00 -0600138 """Run 'make' on a particular commit and board.
139
140 The source code will already be checked out, so the 'commit'
141 argument is only for information.
142
143 Args:
144 commit: Commit object that is being built
145 brd: Board object that is being built
146 stage: Stage of the build. Valid stages are:
Roger Meierfd18a892014-08-20 22:10:29 +0200147 mrproper - can be called to clean source
Simon Glass190064b2014-08-09 15:33:00 -0600148 config - called to configure for a board
149 build - the main make invocation - it does the build
150 args: A list of arguments to pass to 'make'
Simon Glassd9800692022-01-29 14:14:05 -0700151 kwargs: A list of keyword arguments to pass to command.run_pipe()
Simon Glass190064b2014-08-09 15:33:00 -0600152
153 Returns:
154 CommandResult object
155 """
156 return self.builder.do_make(commit, brd, stage, cwd, *args,
157 **kwargs)
158
Simon Glassed007bf2023-07-19 17:49:15 -0600159 def _build_args(self, brd, out_dir, out_rel_dir, work_dir, commit_upto):
Simon Glass75247002023-07-19 17:49:13 -0600160 """Set up arguments to the args list based on the settings
161
162 Args:
Simon Glassa06ed7f2023-07-19 17:49:14 -0600163 brd (Board): Board to create arguments for
Simon Glassed007bf2023-07-19 17:49:15 -0600164 out_dir (str): Path to output directory containing the files
165 out_rel_dir (str): Output directory relative to the current dir
166 work_dir (str): Directory to which the source will be checked out
167 commit_upto (int): Commit number to build (0...n-1)
168
169 Returns:
170 tuple:
171 list of str: Arguments to pass to make
172 str: Current working directory, or None if no commit
173 str: Source directory (typically the work directory)
Simon Glass75247002023-07-19 17:49:13 -0600174 """
Simon Glassed007bf2023-07-19 17:49:15 -0600175 args = []
176 cwd = work_dir
177 src_dir = os.path.realpath(work_dir)
178 if not self.builder.in_tree:
179 if commit_upto is None:
180 # In this case we are building in the original source directory
181 # (i.e. the current directory where buildman is invoked. The
182 # output directory is set to this thread's selected work
183 # directory.
184 #
185 # Symlinks can confuse U-Boot's Makefile since we may use '..'
186 # in our path, so remove them.
187 real_dir = os.path.realpath(out_dir)
188 args.append(f'O={real_dir}')
189 cwd = None
190 src_dir = os.getcwd()
191 else:
192 args.append(f'O={out_rel_dir}')
Simon Glass75247002023-07-19 17:49:13 -0600193 if self.builder.verbose_build:
194 args.append('V=1')
195 else:
196 args.append('-s')
197 if self.builder.num_jobs is not None:
198 args.extend(['-j', str(self.builder.num_jobs)])
199 if self.builder.warnings_as_errors:
200 args.append('KCFLAGS=-Werror')
201 args.append('HOSTCFLAGS=-Werror')
202 if self.builder.allow_missing:
203 args.append('BINMAN_ALLOW_MISSING=1')
204 if self.builder.no_lto:
205 args.append('NO_LTO=1')
206 if self.builder.reproducible_builds:
207 args.append('SOURCE_DATE_EPOCH=0')
Simon Glassa06ed7f2023-07-19 17:49:14 -0600208 args.extend(self.builder.toolchains.GetMakeArguments(brd))
209 args.extend(self.toolchain.MakeArgs())
Simon Glassed007bf2023-07-19 17:49:15 -0600210 return args, cwd, src_dir
Simon Glass75247002023-07-19 17:49:13 -0600211
Simon Glassec2f4922023-07-19 17:49:17 -0600212 def _reconfigure(self, commit, brd, cwd, args, env, config_args, config_out,
213 cmd_list):
214 """Reconfigure the build
215
216 Args:
217 commit (Commit): Commit only being built
218 brd (Board): Board being built
219 cwd (str): Current working directory
220 args (list of str): Arguments to pass to make
221 env (dict): Environment strings
222 config_args (list of str): defconfig arg for this board
223 cmd_list (list of str): List to add the commands to, for logging
224
225 Returns:
226 CommandResult object
227 """
228 if self.mrproper:
229 result = self.make(commit, brd, 'mrproper', cwd, 'mrproper', *args,
230 env=env)
231 config_out.write(result.combined)
232 cmd_list.append([self.builder.gnu_make, 'mrproper', *args])
233 result = self.make(commit, brd, 'config', cwd, *(args + config_args),
234 env=env)
235 cmd_list.append([self.builder.gnu_make] + args + config_args)
236 config_out.write(result.combined)
237 return result
238
Simon Glass14c15232023-07-19 17:49:18 -0600239 def _build(self, commit, brd, cwd, args, env, cmd_list, config_only):
240 """Perform the build
241
242 Args:
243 commit (Commit): Commit only being built
244 brd (Board): Board being built
245 cwd (str): Current working directory
246 args (list of str): Arguments to pass to make
247 env (dict): Environment strings
248 cmd_list (list of str): List to add the commands to, for logging
249 config_only (bool): True if this is a config-only build (using the
250 'make cfg' target)
251
252 Returns:
253 CommandResult object
254 """
255 if config_only:
256 args.append('cfg')
257 result = self.make(commit, brd, 'build', cwd, *args, env=env)
258 cmd_list.append([self.builder.gnu_make] + args)
259 if (result.return_code == 2 and
260 ('Some images are invalid' in result.stderr)):
261 # This is handled later by the check for output in stderr
262 result.return_code = 0
263 return result
264
Simon Glass4981bd32023-07-19 17:49:19 -0600265 def _read_done_file(self, commit_upto, brd, result, force_build,
266 force_build_failures):
267 """Check the 'done' file and see if this commit should be built
268
269 Args:
270 commit (Commit): Commit only being built
271 brd (Board): Board being built
272 result (CommandResult): result object to update
273 force_build (bool): Force a build even if one was previously done
274 force_build_failures (bool): Force a bulid if the previous result
275 showed failure
276
277 Returns:
278 bool: True if build should be built
279 """
280 done_file = self.builder.get_done_file(commit_upto, brd.target)
281 result.already_done = os.path.exists(done_file)
282 will_build = (force_build or force_build_failures or
283 not result.already_done)
284 if result.already_done:
285 with open(done_file, 'r', encoding='utf-8') as outf:
286 try:
287 result.return_code = int(outf.readline())
288 except ValueError:
289 # The file may be empty due to running out of disk space.
290 # Try a rebuild
291 result.return_code = RETURN_CODE_RETRY
292
293 # Check the signal that the build needs to be retried
294 if result.return_code == RETURN_CODE_RETRY:
295 will_build = True
296 elif will_build:
297 err_file = self.builder.get_err_file(commit_upto, brd.target)
298 if os.path.exists(err_file) and os.stat(err_file).st_size:
299 result.stderr = 'bad'
300 elif not force_build:
301 # The build passed, so no need to build it again
302 will_build = False
303 return will_build
304
Simon Glass9bdf0232023-07-19 17:49:21 -0600305 def _decide_dirs(self, brd, work_dir, work_in_output):
306 """Decide the output directory to use
307
308 Args:
309 work_dir (str): Directory to which the source will be checked out
310 work_in_output (bool): Use the output directory as the work
311 directory and don't write to a separate output directory.
312
313 Returns:
314 tuple:
315 out_dir (str): Output directory for the build
316 out_rel_dir (str): Output directory relatie to the current dir
317 """
318 if work_in_output or self.builder.in_tree:
319 out_rel_dir = None
320 out_dir = work_dir
321 else:
322 if self.per_board_out_dir:
323 out_rel_dir = os.path.join('..', brd.target)
324 else:
325 out_rel_dir = 'build'
326 out_dir = os.path.join(work_dir, out_rel_dir)
327 return out_dir, out_rel_dir
328
Simon Glassad7181c2023-07-19 17:49:22 -0600329 def _checkout(self, commit_upto, work_dir):
330 """Checkout the right commit
331
332 Args:
333 commit_upto (int): Commit number to build (0...n-1)
334 work_dir (str): Directory to which the source will be checked out
335
336 Returns:
337 Commit: Commit being built, or 'current' for current source
338 """
339 if self.builder.commits:
340 commit = self.builder.commits[commit_upto]
341 if self.builder.checkout:
342 git_dir = os.path.join(work_dir, '.git')
343 gitutil.checkout(commit.hash, git_dir, work_dir, force=True)
344 else:
345 commit = 'current'
346 return commit
347
Simon Glass335c1b92023-07-19 17:49:23 -0600348 def _config_and_build(self, commit_upto, brd, work_dir, do_config,
349 config_only, adjust_cfg, commit, out_dir, out_rel_dir,
350 result):
351 """Do the build, configuring first if necessary
352
353 Args:
354 commit_upto (int): Commit number to build (0...n-1)
355 brd (Board): Board to create arguments for
356 work_dir (str): Directory to which the source will be checked out
357 do_config (bool): True to run a make <board>_defconfig on the source
358 config_only (bool): Only configure the source, do not build it
359 adjust_cfg (list of str): See the cfgutil module and run_commit()
360 commit (Commit): Commit only being built
361 out_dir (str): Output directory for the build
362 out_rel_dir (str): Output directory relatie to the current dir
363 result (CommandResult): Previous result
364
365 Returns:
366 tuple:
367 result (CommandResult): Result of the build
368 do_config (bool): indicates whether 'make config' is needed on
369 the next incremental build
370 """
371 # Set up the environment and command line
372 env = self.toolchain.MakeEnvironment(self.builder.full_path)
373 mkdir(out_dir)
374
375 args, cwd, src_dir = self._build_args(brd, out_dir, out_rel_dir,
376 work_dir, commit_upto)
377 config_args = [f'{brd.target}_defconfig']
378 config_out = io.StringIO()
379
380 _remove_old_outputs(out_dir)
381
382 # If we need to reconfigure, do that now
383 cfg_file = os.path.join(out_dir, '.config')
384 cmd_list = []
385 if do_config or adjust_cfg:
386 result = self._reconfigure(
387 commit, brd, cwd, args, env, config_args, config_out, cmd_list)
388 do_config = False # No need to configure next time
389 if adjust_cfg:
390 cfgutil.adjust_cfg_file(cfg_file, adjust_cfg)
391
392 # Now do the build, if everything looks OK
393 if result.return_code == 0:
394 result = self._build(commit, brd, cwd, args, env, cmd_list,
395 config_only)
396 if adjust_cfg:
397 errs = cfgutil.check_cfg_file(cfg_file, adjust_cfg)
398 if errs:
399 result.stderr += errs
400 result.return_code = 1
401 result.stderr = result.stderr.replace(src_dir + '/', '')
402 if self.builder.verbose_build:
403 result.stdout = config_out.getvalue() + result.stdout
404 result.cmd_list = cmd_list
405 return result, do_config
Simon Glassad7181c2023-07-19 17:49:22 -0600406
Simon Glassf06d3332023-07-19 17:49:08 -0600407 def run_commit(self, commit_upto, brd, work_dir, do_config, config_only,
Simon Glass2b4806e2022-01-22 05:07:33 -0700408 force_build, force_build_failures, work_in_output,
409 adjust_cfg):
Simon Glass190064b2014-08-09 15:33:00 -0600410 """Build a particular commit.
411
412 If the build is already done, and we are not forcing a build, we skip
413 the build and just return the previously-saved results.
414
415 Args:
416 commit_upto: Commit number to build (0...n-1)
417 brd: Board object to build
418 work_dir: Directory to which the source will be checked out
419 do_config: True to run a make <board>_defconfig on the source
Simon Glassa9401b22016-11-16 14:09:25 -0700420 config_only: Only configure the source, do not build it
Simon Glass190064b2014-08-09 15:33:00 -0600421 force_build: Force a build even if one was previously done
422 force_build_failures: Force a bulid if the previous result showed
423 failure
Simon Glassd829f122020-03-18 09:42:42 -0600424 work_in_output: Use the output directory as the work directory and
425 don't write to a separate output directory.
Simon Glass2b4806e2022-01-22 05:07:33 -0700426 adjust_cfg (list of str): List of changes to make to .config file
427 before building. Each is one of (where C is either CONFIG_xxx
428 or just xxx):
429 C to enable C
430 ~C to disable C
431 C=val to set the value of C (val must have quotes if C is
432 a string Kconfig
Simon Glass190064b2014-08-09 15:33:00 -0600433
434 Returns:
435 tuple containing:
436 - CommandResult object containing the results of the build
437 - boolean indicating whether 'make config' is still needed
438 """
439 # Create a default result - it will be overwritte by the call to
Simon Glassf06d3332023-07-19 17:49:08 -0600440 # self.make() below, in the event that we do a build.
Simon Glass190064b2014-08-09 15:33:00 -0600441 result = command.CommandResult()
442 result.return_code = 0
Simon Glass9bdf0232023-07-19 17:49:21 -0600443 out_dir, out_rel_dir = self._decide_dirs(brd, work_dir, work_in_output)
Simon Glass190064b2014-08-09 15:33:00 -0600444
445 # Check if the job was already completed last time
Simon Glass4981bd32023-07-19 17:49:19 -0600446 will_build = self._read_done_file(commit_upto, brd, result, force_build,
447 force_build_failures)
Simon Glass190064b2014-08-09 15:33:00 -0600448
449 if will_build:
450 # We are going to have to build it. First, get a toolchain
451 if not self.toolchain:
452 try:
453 self.toolchain = self.builder.toolchains.Select(brd.arch)
454 except ValueError as err:
455 result.return_code = 10
456 result.stdout = ''
457 result.stderr = str(err)
458 # TODO(sjg@chromium.org): This gets swallowed, but needs
459 # to be reported.
460
461 if self.toolchain:
Simon Glassad7181c2023-07-19 17:49:22 -0600462 commit = self._checkout(commit_upto, work_dir)
Simon Glass335c1b92023-07-19 17:49:23 -0600463 result, do_config = self._config_and_build(
464 commit_upto, brd, work_dir, do_config, config_only,
465 adjust_cfg, commit, out_dir, out_rel_dir, result)
Simon Glass190064b2014-08-09 15:33:00 -0600466 else:
467 result.return_code = 1
Simon Glass606e5432023-07-19 17:49:09 -0600468 result.stderr = f'No tool chain for {brd.arch}\n'
Simon Glass190064b2014-08-09 15:33:00 -0600469 result.already_done = False
470
471 result.toolchain = self.toolchain
472 result.brd = brd
473 result.commit_upto = commit_upto
474 result.out_dir = out_dir
475 return result, do_config
476
Simon Glassf06d3332023-07-19 17:49:08 -0600477 def _write_result(self, result, keep_outputs, work_in_output):
Simon Glass190064b2014-08-09 15:33:00 -0600478 """Write a built result to the output directory.
479
480 Args:
481 result: CommandResult object containing result to write
482 keep_outputs: True to store the output binaries, False
483 to delete them
Simon Glassd829f122020-03-18 09:42:42 -0600484 work_in_output: Use the output directory as the work directory and
485 don't write to a separate output directory.
Simon Glass190064b2014-08-09 15:33:00 -0600486 """
Simon Glass88c8dcf2015-02-05 22:06:13 -0700487 # If we think this might have been aborted with Ctrl-C, record the
488 # failure but not that we are 'done' with this board. A retry may fix
489 # it.
Simon Glassbafdeb42021-10-19 21:43:23 -0600490 maybe_aborted = result.stderr and 'No child processes' in result.stderr
Simon Glass190064b2014-08-09 15:33:00 -0600491
Simon Glassbafdeb42021-10-19 21:43:23 -0600492 if result.return_code >= 0 and result.already_done:
Simon Glass190064b2014-08-09 15:33:00 -0600493 return
494
495 # Write the output and stderr
Simon Glass4a7419b2023-07-19 17:49:10 -0600496 output_dir = self.builder.get_output_dir(result.commit_upto)
Simon Glassf06d3332023-07-19 17:49:08 -0600497 mkdir(output_dir)
Simon Glass37edf5f2023-07-19 17:49:06 -0600498 build_dir = self.builder.get_build_dir(result.commit_upto,
Simon Glass190064b2014-08-09 15:33:00 -0600499 result.brd.target)
Simon Glassf06d3332023-07-19 17:49:08 -0600500 mkdir(build_dir)
Simon Glass190064b2014-08-09 15:33:00 -0600501
502 outfile = os.path.join(build_dir, 'log')
Simon Glass606e5432023-07-19 17:49:09 -0600503 with open(outfile, 'w', encoding='utf-8') as outf:
Simon Glass190064b2014-08-09 15:33:00 -0600504 if result.stdout:
Simon Glass606e5432023-07-19 17:49:09 -0600505 outf.write(result.stdout)
Simon Glass190064b2014-08-09 15:33:00 -0600506
Simon Glass37edf5f2023-07-19 17:49:06 -0600507 errfile = self.builder.get_err_file(result.commit_upto,
Simon Glass190064b2014-08-09 15:33:00 -0600508 result.brd.target)
509 if result.stderr:
Simon Glass606e5432023-07-19 17:49:09 -0600510 with open(errfile, 'w', encoding='utf-8') as outf:
511 outf.write(result.stderr)
Simon Glass190064b2014-08-09 15:33:00 -0600512 elif os.path.exists(errfile):
513 os.remove(errfile)
514
Simon Glassbafdeb42021-10-19 21:43:23 -0600515 # Fatal error
516 if result.return_code < 0:
517 return
518
Simon Glass190064b2014-08-09 15:33:00 -0600519 if result.toolchain:
520 # Write the build result and toolchain information.
Simon Glass37edf5f2023-07-19 17:49:06 -0600521 done_file = self.builder.get_done_file(result.commit_upto,
Simon Glass190064b2014-08-09 15:33:00 -0600522 result.brd.target)
Simon Glass606e5432023-07-19 17:49:09 -0600523 with open(done_file, 'w', encoding='utf-8') as outf:
Simon Glass88c8dcf2015-02-05 22:06:13 -0700524 if maybe_aborted:
525 # Special code to indicate we need to retry
Simon Glass606e5432023-07-19 17:49:09 -0600526 outf.write(f'{RETURN_CODE_RETRY}')
Simon Glass88c8dcf2015-02-05 22:06:13 -0700527 else:
Simon Glass606e5432023-07-19 17:49:09 -0600528 outf.write(f'{result.return_code}')
529 with open(os.path.join(build_dir, 'toolchain'), 'w',
530 encoding='utf-8') as outf:
531 print('gcc', result.toolchain.gcc, file=outf)
532 print('path', result.toolchain.path, file=outf)
533 print('cross', result.toolchain.cross, file=outf)
534 print('arch', result.toolchain.arch, file=outf)
535 outf.write(f'{result.return_code}')
Simon Glass190064b2014-08-09 15:33:00 -0600536
Simon Glass190064b2014-08-09 15:33:00 -0600537 # Write out the image and function size information and an objdump
Simon Glassbb1501f2014-12-01 17:34:00 -0700538 env = result.toolchain.MakeEnvironment(self.builder.full_path)
Simon Glass606e5432023-07-19 17:49:09 -0600539 with open(os.path.join(build_dir, 'out-env'), 'wb') as outf:
Simon Glasse5fc79e2019-01-07 16:44:23 -0700540 for var in sorted(env.keys()):
Simon Glass606e5432023-07-19 17:49:09 -0600541 outf.write(b'%s="%s"' % (var, env[var]))
Simon Glasscd37d5b2023-02-21 12:40:27 -0700542
543 with open(os.path.join(build_dir, 'out-cmd'), 'w',
Simon Glass606e5432023-07-19 17:49:09 -0600544 encoding='utf-8') as outf:
Simon Glasscd37d5b2023-02-21 12:40:27 -0700545 for cmd in result.cmd_list:
Simon Glass606e5432023-07-19 17:49:09 -0600546 print(' '.join(cmd), file=outf)
Simon Glasscd37d5b2023-02-21 12:40:27 -0700547
Simon Glass190064b2014-08-09 15:33:00 -0600548 lines = []
Simon Glass73da3d22020-12-16 17:24:17 -0700549 for fname in BASE_ELF_FILENAMES:
Simon Glass606e5432023-07-19 17:49:09 -0600550 cmd = [f'{self.toolchain.cross}nm', '--size-sort', fname]
Simon Glassd9800692022-01-29 14:14:05 -0700551 nm_result = command.run_pipe([cmd], capture=True,
Simon Glass190064b2014-08-09 15:33:00 -0600552 capture_stderr=True, cwd=result.out_dir,
553 raise_on_error=False, env=env)
554 if nm_result.stdout:
Simon Glass606e5432023-07-19 17:49:09 -0600555 nm_fname = self.builder.get_func_sizes_file(
556 result.commit_upto, result.brd.target, fname)
557 with open(nm_fname, 'w', encoding='utf-8') as outf:
558 print(nm_result.stdout, end=' ', file=outf)
Simon Glass190064b2014-08-09 15:33:00 -0600559
Simon Glass606e5432023-07-19 17:49:09 -0600560 cmd = [f'{self.toolchain.cross}objdump', '-h', fname]
Simon Glassd9800692022-01-29 14:14:05 -0700561 dump_result = command.run_pipe([cmd], capture=True,
Simon Glass190064b2014-08-09 15:33:00 -0600562 capture_stderr=True, cwd=result.out_dir,
563 raise_on_error=False, env=env)
564 rodata_size = ''
565 if dump_result.stdout:
Simon Glass37edf5f2023-07-19 17:49:06 -0600566 objdump = self.builder.get_objdump_file(result.commit_upto,
Simon Glass190064b2014-08-09 15:33:00 -0600567 result.brd.target, fname)
Simon Glass606e5432023-07-19 17:49:09 -0600568 with open(objdump, 'w', encoding='utf-8') as outf:
569 print(dump_result.stdout, end=' ', file=outf)
Simon Glass190064b2014-08-09 15:33:00 -0600570 for line in dump_result.stdout.splitlines():
571 fields = line.split()
572 if len(fields) > 5 and fields[1] == '.rodata':
573 rodata_size = fields[2]
574
Simon Glass606e5432023-07-19 17:49:09 -0600575 cmd = [f'{self.toolchain.cross}size', fname]
Simon Glassd9800692022-01-29 14:14:05 -0700576 size_result = command.run_pipe([cmd], capture=True,
Simon Glass190064b2014-08-09 15:33:00 -0600577 capture_stderr=True, cwd=result.out_dir,
578 raise_on_error=False, env=env)
579 if size_result.stdout:
580 lines.append(size_result.stdout.splitlines()[1] + ' ' +
581 rodata_size)
582
Alex Kiernan0ddc5102018-05-31 04:48:33 +0000583 # Extract the environment from U-Boot and dump it out
Simon Glass606e5432023-07-19 17:49:09 -0600584 cmd = [f'{self.toolchain.cross}objcopy', '-O', 'binary',
Alex Kiernan0ddc5102018-05-31 04:48:33 +0000585 '-j', '.rodata.default_environment',
586 'env/built-in.o', 'uboot.env']
Simon Glassd9800692022-01-29 14:14:05 -0700587 command.run_pipe([cmd], capture=True,
Alex Kiernan0ddc5102018-05-31 04:48:33 +0000588 capture_stderr=True, cwd=result.out_dir,
589 raise_on_error=False, env=env)
Simon Glass60b285f2020-04-17 17:51:34 -0600590 if not work_in_output:
Simon Glassf06d3332023-07-19 17:49:08 -0600591 self.copy_files(result.out_dir, build_dir, '', ['uboot.env'])
Alex Kiernan0ddc5102018-05-31 04:48:33 +0000592
Simon Glass190064b2014-08-09 15:33:00 -0600593 # Write out the image sizes file. This is similar to the output
594 # of binutil's 'size' utility, but it omits the header line and
595 # adds an additional hex value at the end of each line for the
596 # rodata size
Simon Glass606e5432023-07-19 17:49:09 -0600597 if lines:
Simon Glass37edf5f2023-07-19 17:49:06 -0600598 sizes = self.builder.get_sizes_file(result.commit_upto,
Simon Glass190064b2014-08-09 15:33:00 -0600599 result.brd.target)
Simon Glass606e5432023-07-19 17:49:09 -0600600 with open(sizes, 'w', encoding='utf-8') as outf:
601 print('\n'.join(lines), file=outf)
Simon Glass190064b2014-08-09 15:33:00 -0600602
Simon Glass60b285f2020-04-17 17:51:34 -0600603 if not work_in_output:
604 # Write out the configuration files, with a special case for SPL
605 for dirname in ['', 'spl', 'tpl']:
Simon Glassf06d3332023-07-19 17:49:08 -0600606 self.copy_files(
Simon Glass60b285f2020-04-17 17:51:34 -0600607 result.out_dir, build_dir, dirname,
608 ['u-boot.cfg', 'spl/u-boot-spl.cfg', 'tpl/u-boot-tpl.cfg',
609 '.config', 'include/autoconf.mk',
610 'include/generated/autoconf.h'])
Simon Glass970f9322015-02-05 22:06:14 -0700611
Simon Glass60b285f2020-04-17 17:51:34 -0600612 # Now write the actual build output
613 if keep_outputs:
Simon Glassf06d3332023-07-19 17:49:08 -0600614 self.copy_files(
Simon Glass60b285f2020-04-17 17:51:34 -0600615 result.out_dir, build_dir, '',
616 ['u-boot*', '*.bin', '*.map', '*.img', 'MLO', 'SPL',
617 'include/autoconf.mk', 'spl/u-boot-spl*'])
Simon Glass190064b2014-08-09 15:33:00 -0600618
Simon Glassf06d3332023-07-19 17:49:08 -0600619 def copy_files(self, out_dir, build_dir, dirname, patterns):
Simon Glass970f9322015-02-05 22:06:14 -0700620 """Copy files from the build directory to the output.
621
622 Args:
623 out_dir: Path to output directory containing the files
624 build_dir: Place to copy the files
625 dirname: Source directory, '' for normal U-Boot, 'spl' for SPL
626 patterns: A list of filenames (strings) to copy, each relative
627 to the build directory
628 """
629 for pattern in patterns:
630 file_list = glob.glob(os.path.join(out_dir, dirname, pattern))
631 for fname in file_list:
632 target = os.path.basename(fname)
633 if dirname:
634 base, ext = os.path.splitext(target)
635 if ext:
Simon Glass606e5432023-07-19 17:49:09 -0600636 target = f'{base}-{dirname}{ext}'
Simon Glass970f9322015-02-05 22:06:14 -0700637 shutil.copy(fname, os.path.join(build_dir, target))
Simon Glass190064b2014-08-09 15:33:00 -0600638
Simon Glassf06d3332023-07-19 17:49:08 -0600639 def _send_result(self, result):
Simon Glassab9b4f32021-04-11 16:27:26 +1200640 """Send a result to the builder for processing
641
642 Args:
643 result: CommandResult object containing the results of the build
Simon Glass8116c782021-04-11 16:27:27 +1200644
645 Raises:
646 ValueError if self.test_exception is true (for testing)
Simon Glassab9b4f32021-04-11 16:27:26 +1200647 """
Simon Glass8116c782021-04-11 16:27:27 +1200648 if self.test_exception:
649 raise ValueError('test exception')
Simon Glassab9b4f32021-04-11 16:27:26 +1200650 if self.thread_num != -1:
651 self.builder.out_queue.put(result)
652 else:
Simon Glass37edf5f2023-07-19 17:49:06 -0600653 self.builder.process_result(result)
Simon Glassab9b4f32021-04-11 16:27:26 +1200654
Simon Glassf06d3332023-07-19 17:49:08 -0600655 def run_job(self, job):
Simon Glass190064b2014-08-09 15:33:00 -0600656 """Run a single job
657
658 A job consists of a building a list of commits for a particular board.
659
660 Args:
661 job: Job to build
Simon Glassb82492b2021-01-30 22:17:46 -0700662
663 Returns:
664 List of Result objects
Simon Glass190064b2014-08-09 15:33:00 -0600665 """
Simon Glassf4ed4702022-07-11 19:03:57 -0600666 brd = job.brd
Simon Glass37edf5f2023-07-19 17:49:06 -0600667 work_dir = self.builder.get_thread_dir(self.thread_num)
Simon Glass190064b2014-08-09 15:33:00 -0600668 self.toolchain = None
669 if job.commits:
670 # Run 'make board_defconfig' on the first commit
671 do_config = True
672 commit_upto = 0
673 force_build = False
674 for commit_upto in range(0, len(job.commits), job.step):
Simon Glassf06d3332023-07-19 17:49:08 -0600675 result, request_config = self.run_commit(commit_upto, brd,
Simon Glassa9401b22016-11-16 14:09:25 -0700676 work_dir, do_config, self.builder.config_only,
Simon Glass190064b2014-08-09 15:33:00 -0600677 force_build or self.builder.force_build,
Simon Glassd829f122020-03-18 09:42:42 -0600678 self.builder.force_build_failures,
Simon Glass2b4806e2022-01-22 05:07:33 -0700679 job.work_in_output, job.adjust_cfg)
Simon Glass190064b2014-08-09 15:33:00 -0600680 failed = result.return_code or result.stderr
681 did_config = do_config
682 if failed and not do_config:
683 # If our incremental build failed, try building again
684 # with a reconfig.
685 if self.builder.force_config_on_failure:
Simon Glassf06d3332023-07-19 17:49:08 -0600686 result, request_config = self.run_commit(commit_upto,
Simon Glassd829f122020-03-18 09:42:42 -0600687 brd, work_dir, True, False, True, False,
Simon Glass2b4806e2022-01-22 05:07:33 -0700688 job.work_in_output, job.adjust_cfg)
Simon Glass190064b2014-08-09 15:33:00 -0600689 did_config = True
690 if not self.builder.force_reconfig:
691 do_config = request_config
692
693 # If we built that commit, then config is done. But if we got
694 # an warning, reconfig next time to force it to build the same
695 # files that created warnings this time. Otherwise an
696 # incremental build may not build the same file, and we will
697 # think that the warning has gone away.
698 # We could avoid this by using -Werror everywhere...
699 # For errors, the problem doesn't happen, since presumably
700 # the build stopped and didn't generate output, so will retry
701 # that file next time. So we could detect warnings and deal
702 # with them specially here. For now, we just reconfigure if
703 # anything goes work.
704 # Of course this is substantially slower if there are build
705 # errors/warnings (e.g. 2-3x slower even if only 10% of builds
706 # have problems).
707 if (failed and not result.already_done and not did_config and
708 self.builder.force_config_on_failure):
709 # If this build failed, try the next one with a
710 # reconfigure.
711 # Sometimes if the board_config.h file changes it can mess
712 # with dependencies, and we get:
713 # make: *** No rule to make target `include/autoconf.mk',
714 # needed by `depend'.
715 do_config = True
716 force_build = True
717 else:
718 force_build = False
719 if self.builder.force_config_on_failure:
720 if failed:
721 do_config = True
722 result.commit_upto = commit_upto
723 if result.return_code < 0:
724 raise ValueError('Interrupt')
725
726 # We have the build results, so output the result
Simon Glassf06d3332023-07-19 17:49:08 -0600727 self._write_result(result, job.keep_outputs, job.work_in_output)
728 self._send_result(result)
Simon Glass190064b2014-08-09 15:33:00 -0600729 else:
730 # Just build the currently checked-out build
Simon Glassf06d3332023-07-19 17:49:08 -0600731 result, request_config = self.run_commit(None, brd, work_dir, True,
Simon Glassa9401b22016-11-16 14:09:25 -0700732 self.builder.config_only, True,
Simon Glass2b4806e2022-01-22 05:07:33 -0700733 self.builder.force_build_failures, job.work_in_output,
734 job.adjust_cfg)
Simon Glass190064b2014-08-09 15:33:00 -0600735 result.commit_upto = 0
Simon Glassf06d3332023-07-19 17:49:08 -0600736 self._write_result(result, job.keep_outputs, job.work_in_output)
737 self._send_result(result)
Simon Glass190064b2014-08-09 15:33:00 -0600738
739 def run(self):
740 """Our thread's run function
741
742 This thread picks a job from the queue, runs it, and then goes to the
743 next job.
744 """
Simon Glass190064b2014-08-09 15:33:00 -0600745 while True:
746 job = self.builder.queue.get()
Simon Glass8116c782021-04-11 16:27:27 +1200747 try:
Simon Glassf06d3332023-07-19 17:49:08 -0600748 self.run_job(job)
Simon Glass606e5432023-07-19 17:49:09 -0600749 except Exception as exc:
750 print('Thread exception (use -T0 to run without threads):',
751 exc)
752 self.builder.thread_exceptions.append(exc)
Simon Glass190064b2014-08-09 15:33:00 -0600753 self.builder.queue.task_done()