blob: 4abad86ebc7b8dff85ed4b34b8374aeccc7ff961 [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 Glassc5077c32023-07-19 17:49:08 -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:
30 dirname: Directory to create
31 """
32 try:
Thierry Reding336d5bd2014-08-19 10:22:39 +020033 if parents:
34 os.makedirs(dirname)
35 else:
36 os.mkdir(dirname)
Simon Glass4a1e88b2014-08-09 15:33:00 -060037 except OSError as err:
38 if err.errno == errno.EEXIST:
Lothar Waßmannce6df922018-04-08 05:14:11 -060039 if os.path.realpath('.') == os.path.realpath(dirname):
Simon Glassd07e5532023-07-19 17:49:09 -060040 print(f"Cannot create the current working directory '{dirname}'!")
Lothar Waßmannce6df922018-04-08 05:14:11 -060041 sys.exit(1)
Simon Glass4a1e88b2014-08-09 15:33:00 -060042 else:
43 raise
44
Simon Glasscb2de022023-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 Glassd07e5532023-07-19 17:49:09 -060063# pylint: disable=R0903
Simon Glass4a1e88b2014-08-09 15:33:00 -060064class BuilderJob:
65 """Holds information about a job to be performed by a thread
66
67 Members:
Simon Glass8132f982022-07-11 19:03:57 -060068 brd: Board object to build
Simon Glassdf890152020-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 Glassb6eb8cf2020-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 Glass4a1e88b2014-08-09 15:33:00 -060074 """
75 def __init__(self):
Simon Glass8132f982022-07-11 19:03:57 -060076 self.brd = None
Simon Glass4a1e88b2014-08-09 15:33:00 -060077 self.commits = []
Simon Glassdf890152020-03-18 09:42:41 -060078 self.keep_outputs = False
79 self.step = 1
Simon Glassb6eb8cf2020-03-18 09:42:42 -060080 self.work_in_output = False
Simon Glass4a1e88b2014-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 Glassbc74d942023-07-19 17:49:06 -0600105 self.builder.process_result(result)
Simon Glass4a1e88b2014-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 Glassa29b3ea2021-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 Glass4a1e88b2014-08-09 15:33:00 -0600125 """
Simon Glass9bf9a722021-04-11 16:27:27 +1200126 def __init__(self, builder, thread_num, mrproper, per_board_out_dir,
127 test_exception=False):
Simon Glass4a1e88b2014-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 Glass6029af12020-04-09 15:08:51 -0600132 self.mrproper = mrproper
Stephen Warren97c96902016-04-11 10:48:44 -0600133 self.per_board_out_dir = per_board_out_dir
Simon Glass9bf9a722021-04-11 16:27:27 +1200134 self.test_exception = test_exception
Simon Glassd07e5532023-07-19 17:49:09 -0600135 self.toolchain = None
Simon Glass4a1e88b2014-08-09 15:33:00 -0600136
Simon Glassc5077c32023-07-19 17:49:08 -0600137 def make(self, commit, brd, stage, cwd, *args, **kwargs):
Simon Glass4a1e88b2014-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 Meiere0a0e552014-08-20 22:10:29 +0200147 mrproper - can be called to clean source
Simon Glass4a1e88b2014-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 Glass840be732022-01-29 14:14:05 -0700151 kwargs: A list of keyword arguments to pass to command.run_pipe()
Simon Glass4a1e88b2014-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 Glass926c11b2023-07-19 17:49:15 -0600159 def _build_args(self, brd, out_dir, out_rel_dir, work_dir, commit_upto):
Simon Glass8d0b6b42023-07-19 17:49:13 -0600160 """Set up arguments to the args list based on the settings
161
162 Args:
Simon Glassec7132b2023-07-19 17:49:14 -0600163 brd (Board): Board to create arguments for
Simon Glass926c11b2023-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 Glass8d0b6b42023-07-19 17:49:13 -0600174 """
Simon Glass926c11b2023-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 Glass8d0b6b42023-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 Glassec7132b2023-07-19 17:49:14 -0600208 args.extend(self.builder.toolchains.GetMakeArguments(brd))
209 args.extend(self.toolchain.MakeArgs())
Simon Glass926c11b2023-07-19 17:49:15 -0600210 return args, cwd, src_dir
Simon Glass8d0b6b42023-07-19 17:49:13 -0600211
Simon Glass147f3492023-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 Glass3b4f50e2023-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 Glassfdd7be22023-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 Glassc5077c32023-07-19 17:49:08 -0600305 def run_commit(self, commit_upto, brd, work_dir, do_config, config_only,
Simon Glasse5650a82022-01-22 05:07:33 -0700306 force_build, force_build_failures, work_in_output,
307 adjust_cfg):
Simon Glass4a1e88b2014-08-09 15:33:00 -0600308 """Build a particular commit.
309
310 If the build is already done, and we are not forcing a build, we skip
311 the build and just return the previously-saved results.
312
313 Args:
314 commit_upto: Commit number to build (0...n-1)
315 brd: Board object to build
316 work_dir: Directory to which the source will be checked out
317 do_config: True to run a make <board>_defconfig on the source
Simon Glassb55b57d2016-11-16 14:09:25 -0700318 config_only: Only configure the source, do not build it
Simon Glass4a1e88b2014-08-09 15:33:00 -0600319 force_build: Force a build even if one was previously done
320 force_build_failures: Force a bulid if the previous result showed
321 failure
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600322 work_in_output: Use the output directory as the work directory and
323 don't write to a separate output directory.
Simon Glasse5650a82022-01-22 05:07:33 -0700324 adjust_cfg (list of str): List of changes to make to .config file
325 before building. Each is one of (where C is either CONFIG_xxx
326 or just xxx):
327 C to enable C
328 ~C to disable C
329 C=val to set the value of C (val must have quotes if C is
330 a string Kconfig
Simon Glass4a1e88b2014-08-09 15:33:00 -0600331
332 Returns:
333 tuple containing:
334 - CommandResult object containing the results of the build
335 - boolean indicating whether 'make config' is still needed
336 """
337 # Create a default result - it will be overwritte by the call to
Simon Glassc5077c32023-07-19 17:49:08 -0600338 # self.make() below, in the event that we do a build.
Simon Glass4a1e88b2014-08-09 15:33:00 -0600339 result = command.CommandResult()
340 result.return_code = 0
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600341 if work_in_output or self.builder.in_tree:
Simon Glass926c11b2023-07-19 17:49:15 -0600342 out_rel_dir = None
Simon Glass4a1e88b2014-08-09 15:33:00 -0600343 out_dir = work_dir
344 else:
Stephen Warren97c96902016-04-11 10:48:44 -0600345 if self.per_board_out_dir:
346 out_rel_dir = os.path.join('..', brd.target)
347 else:
348 out_rel_dir = 'build'
349 out_dir = os.path.join(work_dir, out_rel_dir)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600350
351 # Check if the job was already completed last time
Simon Glassfdd7be22023-07-19 17:49:19 -0600352 will_build = self._read_done_file(commit_upto, brd, result, force_build,
353 force_build_failures)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600354
355 if will_build:
356 # We are going to have to build it. First, get a toolchain
357 if not self.toolchain:
358 try:
359 self.toolchain = self.builder.toolchains.Select(brd.arch)
360 except ValueError as err:
361 result.return_code = 10
362 result.stdout = ''
363 result.stderr = str(err)
364 # TODO(sjg@chromium.org): This gets swallowed, but needs
365 # to be reported.
366
367 if self.toolchain:
368 # Checkout the right commit
369 if self.builder.commits:
370 commit = self.builder.commits[commit_upto]
371 if self.builder.checkout:
372 git_dir = os.path.join(work_dir, '.git')
Simon Glass761648b2022-01-29 14:14:11 -0700373 gitutil.checkout(commit.hash, git_dir, work_dir,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600374 force=True)
375 else:
376 commit = 'current'
377
378 # Set up the environment and command line
Simon Glassd48a46c2014-12-01 17:34:00 -0700379 env = self.toolchain.MakeEnvironment(self.builder.full_path)
Simon Glassc5077c32023-07-19 17:49:08 -0600380 mkdir(out_dir)
Simon Glass926c11b2023-07-19 17:49:15 -0600381
382 args, cwd, src_dir = self._build_args(brd, out_dir, out_rel_dir,
383 work_dir, commit_upto)
Simon Glassd07e5532023-07-19 17:49:09 -0600384 config_args = [f'{brd.target}_defconfig']
Simon Glass65cfdd12023-07-19 17:49:16 -0600385 config_out = io.StringIO()
Simon Glass4a1e88b2014-08-09 15:33:00 -0600386
Simon Glasscb2de022023-07-19 17:49:20 -0600387 _remove_old_outputs(out_dir)
Simon Glasse0f19712020-12-16 17:24:17 -0700388
Simon Glass4a1e88b2014-08-09 15:33:00 -0600389 # If we need to reconfigure, do that now
Simon Glasse5650a82022-01-22 05:07:33 -0700390 cfg_file = os.path.join(out_dir, '.config')
Simon Glass1382b1d2023-02-21 12:40:27 -0700391 cmd_list = []
Simon Glasse5650a82022-01-22 05:07:33 -0700392 if do_config or adjust_cfg:
Simon Glass147f3492023-07-19 17:49:17 -0600393 result = self._reconfigure(
394 commit, brd, cwd, args, env, config_args, config_out,
395 cmd_list)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600396 do_config = False # No need to configure next time
Simon Glasse5650a82022-01-22 05:07:33 -0700397 if adjust_cfg:
398 cfgutil.adjust_cfg_file(cfg_file, adjust_cfg)
Simon Glass3b4f50e2023-07-19 17:49:18 -0600399
400 # Now do the build, if everything looks OK
Simon Glass4a1e88b2014-08-09 15:33:00 -0600401 if result.return_code == 0:
Simon Glass3b4f50e2023-07-19 17:49:18 -0600402 result = self._build(commit, brd, cwd, args, env, cmd_list,
403 config_only)
Simon Glasse5650a82022-01-22 05:07:33 -0700404 if adjust_cfg:
405 errs = cfgutil.check_cfg_file(cfg_file, adjust_cfg)
406 if errs:
Simon Glasse5650a82022-01-22 05:07:33 -0700407 result.stderr += errs
408 result.return_code = 1
Simon Glasse3096252014-08-28 09:43:42 -0600409 result.stderr = result.stderr.replace(src_dir + '/', '')
Simon Glass413f91a2015-02-05 22:06:12 -0700410 if self.builder.verbose_build:
Simon Glass65cfdd12023-07-19 17:49:16 -0600411 result.stdout = config_out.getvalue() + result.stdout
Simon Glass1382b1d2023-02-21 12:40:27 -0700412 result.cmd_list = cmd_list
Simon Glass4a1e88b2014-08-09 15:33:00 -0600413 else:
414 result.return_code = 1
Simon Glassd07e5532023-07-19 17:49:09 -0600415 result.stderr = f'No tool chain for {brd.arch}\n'
Simon Glass4a1e88b2014-08-09 15:33:00 -0600416 result.already_done = False
417
418 result.toolchain = self.toolchain
419 result.brd = brd
420 result.commit_upto = commit_upto
421 result.out_dir = out_dir
422 return result, do_config
423
Simon Glassc5077c32023-07-19 17:49:08 -0600424 def _write_result(self, result, keep_outputs, work_in_output):
Simon Glass4a1e88b2014-08-09 15:33:00 -0600425 """Write a built result to the output directory.
426
427 Args:
428 result: CommandResult object containing result to write
429 keep_outputs: True to store the output binaries, False
430 to delete them
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600431 work_in_output: Use the output directory as the work directory and
432 don't write to a separate output directory.
Simon Glass4a1e88b2014-08-09 15:33:00 -0600433 """
Simon Glassfd3eea12015-02-05 22:06:13 -0700434 # If we think this might have been aborted with Ctrl-C, record the
435 # failure but not that we are 'done' with this board. A retry may fix
436 # it.
Simon Glass2e737452021-10-19 21:43:23 -0600437 maybe_aborted = result.stderr and 'No child processes' in result.stderr
Simon Glass4a1e88b2014-08-09 15:33:00 -0600438
Simon Glass2e737452021-10-19 21:43:23 -0600439 if result.return_code >= 0 and result.already_done:
Simon Glass4a1e88b2014-08-09 15:33:00 -0600440 return
441
442 # Write the output and stderr
Simon Glass4cb54682023-07-19 17:49:10 -0600443 output_dir = self.builder.get_output_dir(result.commit_upto)
Simon Glassc5077c32023-07-19 17:49:08 -0600444 mkdir(output_dir)
Simon Glassbc74d942023-07-19 17:49:06 -0600445 build_dir = self.builder.get_build_dir(result.commit_upto,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600446 result.brd.target)
Simon Glassc5077c32023-07-19 17:49:08 -0600447 mkdir(build_dir)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600448
449 outfile = os.path.join(build_dir, 'log')
Simon Glassd07e5532023-07-19 17:49:09 -0600450 with open(outfile, 'w', encoding='utf-8') as outf:
Simon Glass4a1e88b2014-08-09 15:33:00 -0600451 if result.stdout:
Simon Glassd07e5532023-07-19 17:49:09 -0600452 outf.write(result.stdout)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600453
Simon Glassbc74d942023-07-19 17:49:06 -0600454 errfile = self.builder.get_err_file(result.commit_upto,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600455 result.brd.target)
456 if result.stderr:
Simon Glassd07e5532023-07-19 17:49:09 -0600457 with open(errfile, 'w', encoding='utf-8') as outf:
458 outf.write(result.stderr)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600459 elif os.path.exists(errfile):
460 os.remove(errfile)
461
Simon Glass2e737452021-10-19 21:43:23 -0600462 # Fatal error
463 if result.return_code < 0:
464 return
465
Simon Glass4a1e88b2014-08-09 15:33:00 -0600466 if result.toolchain:
467 # Write the build result and toolchain information.
Simon Glassbc74d942023-07-19 17:49:06 -0600468 done_file = self.builder.get_done_file(result.commit_upto,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600469 result.brd.target)
Simon Glassd07e5532023-07-19 17:49:09 -0600470 with open(done_file, 'w', encoding='utf-8') as outf:
Simon Glassfd3eea12015-02-05 22:06:13 -0700471 if maybe_aborted:
472 # Special code to indicate we need to retry
Simon Glassd07e5532023-07-19 17:49:09 -0600473 outf.write(f'{RETURN_CODE_RETRY}')
Simon Glassfd3eea12015-02-05 22:06:13 -0700474 else:
Simon Glassd07e5532023-07-19 17:49:09 -0600475 outf.write(f'{result.return_code}')
476 with open(os.path.join(build_dir, 'toolchain'), 'w',
477 encoding='utf-8') as outf:
478 print('gcc', result.toolchain.gcc, file=outf)
479 print('path', result.toolchain.path, file=outf)
480 print('cross', result.toolchain.cross, file=outf)
481 print('arch', result.toolchain.arch, file=outf)
482 outf.write(f'{result.return_code}')
Simon Glass4a1e88b2014-08-09 15:33:00 -0600483
Simon Glass4a1e88b2014-08-09 15:33:00 -0600484 # Write out the image and function size information and an objdump
Simon Glassd48a46c2014-12-01 17:34:00 -0700485 env = result.toolchain.MakeEnvironment(self.builder.full_path)
Simon Glassd07e5532023-07-19 17:49:09 -0600486 with open(os.path.join(build_dir, 'out-env'), 'wb') as outf:
Simon Glass9e90d312019-01-07 16:44:23 -0700487 for var in sorted(env.keys()):
Simon Glassd07e5532023-07-19 17:49:09 -0600488 outf.write(b'%s="%s"' % (var, env[var]))
Simon Glass1382b1d2023-02-21 12:40:27 -0700489
490 with open(os.path.join(build_dir, 'out-cmd'), 'w',
Simon Glassd07e5532023-07-19 17:49:09 -0600491 encoding='utf-8') as outf:
Simon Glass1382b1d2023-02-21 12:40:27 -0700492 for cmd in result.cmd_list:
Simon Glassd07e5532023-07-19 17:49:09 -0600493 print(' '.join(cmd), file=outf)
Simon Glass1382b1d2023-02-21 12:40:27 -0700494
Simon Glass4a1e88b2014-08-09 15:33:00 -0600495 lines = []
Simon Glasse0f19712020-12-16 17:24:17 -0700496 for fname in BASE_ELF_FILENAMES:
Simon Glassd07e5532023-07-19 17:49:09 -0600497 cmd = [f'{self.toolchain.cross}nm', '--size-sort', fname]
Simon Glass840be732022-01-29 14:14:05 -0700498 nm_result = command.run_pipe([cmd], capture=True,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600499 capture_stderr=True, cwd=result.out_dir,
500 raise_on_error=False, env=env)
501 if nm_result.stdout:
Simon Glassd07e5532023-07-19 17:49:09 -0600502 nm_fname = self.builder.get_func_sizes_file(
503 result.commit_upto, result.brd.target, fname)
504 with open(nm_fname, 'w', encoding='utf-8') as outf:
505 print(nm_result.stdout, end=' ', file=outf)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600506
Simon Glassd07e5532023-07-19 17:49:09 -0600507 cmd = [f'{self.toolchain.cross}objdump', '-h', fname]
Simon Glass840be732022-01-29 14:14:05 -0700508 dump_result = command.run_pipe([cmd], capture=True,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600509 capture_stderr=True, cwd=result.out_dir,
510 raise_on_error=False, env=env)
511 rodata_size = ''
512 if dump_result.stdout:
Simon Glassbc74d942023-07-19 17:49:06 -0600513 objdump = self.builder.get_objdump_file(result.commit_upto,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600514 result.brd.target, fname)
Simon Glassd07e5532023-07-19 17:49:09 -0600515 with open(objdump, 'w', encoding='utf-8') as outf:
516 print(dump_result.stdout, end=' ', file=outf)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600517 for line in dump_result.stdout.splitlines():
518 fields = line.split()
519 if len(fields) > 5 and fields[1] == '.rodata':
520 rodata_size = fields[2]
521
Simon Glassd07e5532023-07-19 17:49:09 -0600522 cmd = [f'{self.toolchain.cross}size', fname]
Simon Glass840be732022-01-29 14:14:05 -0700523 size_result = command.run_pipe([cmd], capture=True,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600524 capture_stderr=True, cwd=result.out_dir,
525 raise_on_error=False, env=env)
526 if size_result.stdout:
527 lines.append(size_result.stdout.splitlines()[1] + ' ' +
528 rodata_size)
529
Alex Kiernanf07ed232018-05-31 04:48:33 +0000530 # Extract the environment from U-Boot and dump it out
Simon Glassd07e5532023-07-19 17:49:09 -0600531 cmd = [f'{self.toolchain.cross}objcopy', '-O', 'binary',
Alex Kiernanf07ed232018-05-31 04:48:33 +0000532 '-j', '.rodata.default_environment',
533 'env/built-in.o', 'uboot.env']
Simon Glass840be732022-01-29 14:14:05 -0700534 command.run_pipe([cmd], capture=True,
Alex Kiernanf07ed232018-05-31 04:48:33 +0000535 capture_stderr=True, cwd=result.out_dir,
536 raise_on_error=False, env=env)
Simon Glasse3c85ab2020-04-17 17:51:34 -0600537 if not work_in_output:
Simon Glassc5077c32023-07-19 17:49:08 -0600538 self.copy_files(result.out_dir, build_dir, '', ['uboot.env'])
Alex Kiernanf07ed232018-05-31 04:48:33 +0000539
Simon Glass4a1e88b2014-08-09 15:33:00 -0600540 # Write out the image sizes file. This is similar to the output
541 # of binutil's 'size' utility, but it omits the header line and
542 # adds an additional hex value at the end of each line for the
543 # rodata size
Simon Glassd07e5532023-07-19 17:49:09 -0600544 if lines:
Simon Glassbc74d942023-07-19 17:49:06 -0600545 sizes = self.builder.get_sizes_file(result.commit_upto,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600546 result.brd.target)
Simon Glassd07e5532023-07-19 17:49:09 -0600547 with open(sizes, 'w', encoding='utf-8') as outf:
548 print('\n'.join(lines), file=outf)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600549
Simon Glasse3c85ab2020-04-17 17:51:34 -0600550 if not work_in_output:
551 # Write out the configuration files, with a special case for SPL
552 for dirname in ['', 'spl', 'tpl']:
Simon Glassc5077c32023-07-19 17:49:08 -0600553 self.copy_files(
Simon Glasse3c85ab2020-04-17 17:51:34 -0600554 result.out_dir, build_dir, dirname,
555 ['u-boot.cfg', 'spl/u-boot-spl.cfg', 'tpl/u-boot-tpl.cfg',
556 '.config', 'include/autoconf.mk',
557 'include/generated/autoconf.h'])
Simon Glass4b14c532015-02-05 22:06:14 -0700558
Simon Glasse3c85ab2020-04-17 17:51:34 -0600559 # Now write the actual build output
560 if keep_outputs:
Simon Glassc5077c32023-07-19 17:49:08 -0600561 self.copy_files(
Simon Glasse3c85ab2020-04-17 17:51:34 -0600562 result.out_dir, build_dir, '',
563 ['u-boot*', '*.bin', '*.map', '*.img', 'MLO', 'SPL',
564 'include/autoconf.mk', 'spl/u-boot-spl*'])
Simon Glass4b14c532015-02-05 22:06:14 -0700565
Simon Glassc5077c32023-07-19 17:49:08 -0600566 def copy_files(self, out_dir, build_dir, dirname, patterns):
Simon Glass4b14c532015-02-05 22:06:14 -0700567 """Copy files from the build directory to the output.
Simon Glass4a1e88b2014-08-09 15:33:00 -0600568
Simon Glass4b14c532015-02-05 22:06:14 -0700569 Args:
570 out_dir: Path to output directory containing the files
571 build_dir: Place to copy the files
572 dirname: Source directory, '' for normal U-Boot, 'spl' for SPL
573 patterns: A list of filenames (strings) to copy, each relative
574 to the build directory
575 """
576 for pattern in patterns:
577 file_list = glob.glob(os.path.join(out_dir, dirname, pattern))
578 for fname in file_list:
579 target = os.path.basename(fname)
580 if dirname:
581 base, ext = os.path.splitext(target)
582 if ext:
Simon Glassd07e5532023-07-19 17:49:09 -0600583 target = f'{base}-{dirname}{ext}'
Simon Glass4b14c532015-02-05 22:06:14 -0700584 shutil.copy(fname, os.path.join(build_dir, target))
Simon Glass4a1e88b2014-08-09 15:33:00 -0600585
Simon Glassc5077c32023-07-19 17:49:08 -0600586 def _send_result(self, result):
Simon Glassa3d01442021-04-11 16:27:26 +1200587 """Send a result to the builder for processing
588
589 Args:
590 result: CommandResult object containing the results of the build
Simon Glass9bf9a722021-04-11 16:27:27 +1200591
592 Raises:
593 ValueError if self.test_exception is true (for testing)
Simon Glassa3d01442021-04-11 16:27:26 +1200594 """
Simon Glass9bf9a722021-04-11 16:27:27 +1200595 if self.test_exception:
596 raise ValueError('test exception')
Simon Glassa3d01442021-04-11 16:27:26 +1200597 if self.thread_num != -1:
598 self.builder.out_queue.put(result)
599 else:
Simon Glassbc74d942023-07-19 17:49:06 -0600600 self.builder.process_result(result)
Simon Glassa3d01442021-04-11 16:27:26 +1200601
Simon Glassc5077c32023-07-19 17:49:08 -0600602 def run_job(self, job):
Simon Glass4a1e88b2014-08-09 15:33:00 -0600603 """Run a single job
604
605 A job consists of a building a list of commits for a particular board.
606
607 Args:
608 job: Job to build
Simon Glassc635d892021-01-30 22:17:46 -0700609
610 Returns:
611 List of Result objects
Simon Glass4a1e88b2014-08-09 15:33:00 -0600612 """
Simon Glass8132f982022-07-11 19:03:57 -0600613 brd = job.brd
Simon Glassbc74d942023-07-19 17:49:06 -0600614 work_dir = self.builder.get_thread_dir(self.thread_num)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600615 self.toolchain = None
616 if job.commits:
617 # Run 'make board_defconfig' on the first commit
618 do_config = True
619 commit_upto = 0
620 force_build = False
621 for commit_upto in range(0, len(job.commits), job.step):
Simon Glassc5077c32023-07-19 17:49:08 -0600622 result, request_config = self.run_commit(commit_upto, brd,
Simon Glassb55b57d2016-11-16 14:09:25 -0700623 work_dir, do_config, self.builder.config_only,
Simon Glass4a1e88b2014-08-09 15:33:00 -0600624 force_build or self.builder.force_build,
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600625 self.builder.force_build_failures,
Simon Glasse5650a82022-01-22 05:07:33 -0700626 job.work_in_output, job.adjust_cfg)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600627 failed = result.return_code or result.stderr
628 did_config = do_config
629 if failed and not do_config:
630 # If our incremental build failed, try building again
631 # with a reconfig.
632 if self.builder.force_config_on_failure:
Simon Glassc5077c32023-07-19 17:49:08 -0600633 result, request_config = self.run_commit(commit_upto,
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600634 brd, work_dir, True, False, True, False,
Simon Glasse5650a82022-01-22 05:07:33 -0700635 job.work_in_output, job.adjust_cfg)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600636 did_config = True
637 if not self.builder.force_reconfig:
638 do_config = request_config
639
640 # If we built that commit, then config is done. But if we got
641 # an warning, reconfig next time to force it to build the same
642 # files that created warnings this time. Otherwise an
643 # incremental build may not build the same file, and we will
644 # think that the warning has gone away.
645 # We could avoid this by using -Werror everywhere...
646 # For errors, the problem doesn't happen, since presumably
647 # the build stopped and didn't generate output, so will retry
648 # that file next time. So we could detect warnings and deal
649 # with them specially here. For now, we just reconfigure if
650 # anything goes work.
651 # Of course this is substantially slower if there are build
652 # errors/warnings (e.g. 2-3x slower even if only 10% of builds
653 # have problems).
654 if (failed and not result.already_done and not did_config and
655 self.builder.force_config_on_failure):
656 # If this build failed, try the next one with a
657 # reconfigure.
658 # Sometimes if the board_config.h file changes it can mess
659 # with dependencies, and we get:
660 # make: *** No rule to make target `include/autoconf.mk',
661 # needed by `depend'.
662 do_config = True
663 force_build = True
664 else:
665 force_build = False
666 if self.builder.force_config_on_failure:
667 if failed:
668 do_config = True
669 result.commit_upto = commit_upto
670 if result.return_code < 0:
671 raise ValueError('Interrupt')
672
673 # We have the build results, so output the result
Simon Glassc5077c32023-07-19 17:49:08 -0600674 self._write_result(result, job.keep_outputs, job.work_in_output)
675 self._send_result(result)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600676 else:
677 # Just build the currently checked-out build
Simon Glassc5077c32023-07-19 17:49:08 -0600678 result, request_config = self.run_commit(None, brd, work_dir, True,
Simon Glassb55b57d2016-11-16 14:09:25 -0700679 self.builder.config_only, True,
Simon Glasse5650a82022-01-22 05:07:33 -0700680 self.builder.force_build_failures, job.work_in_output,
681 job.adjust_cfg)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600682 result.commit_upto = 0
Simon Glassc5077c32023-07-19 17:49:08 -0600683 self._write_result(result, job.keep_outputs, job.work_in_output)
684 self._send_result(result)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600685
686 def run(self):
687 """Our thread's run function
688
689 This thread picks a job from the queue, runs it, and then goes to the
690 next job.
691 """
Simon Glass4a1e88b2014-08-09 15:33:00 -0600692 while True:
693 job = self.builder.queue.get()
Simon Glass9bf9a722021-04-11 16:27:27 +1200694 try:
Simon Glassc5077c32023-07-19 17:49:08 -0600695 self.run_job(job)
Simon Glassd07e5532023-07-19 17:49:09 -0600696 except Exception as exc:
697 print('Thread exception (use -T0 to run without threads):',
698 exc)
699 self.builder.thread_exceptions.append(exc)
Simon Glass4a1e88b2014-08-09 15:33:00 -0600700 self.builder.queue.task_done()