blob: 33f9373b8f8ac6b389a91ac030331528fd9c866e [file] [log] [blame]
Tom Rini83d290c2018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glassfc3fe1c2013-04-03 11:07:16 +00002# Copyright (c) 2013 The Chromium OS Authors.
3#
4# Bloat-o-meter code used here Copyright 2004 Matt Mackall <mpm@selenic.com>
5#
Simon Glassfc3fe1c2013-04-03 11:07:16 +00006
7import collections
Simon Glassfc3fe1c2013-04-03 11:07:16 +00008from datetime import datetime, timedelta
9import glob
10import os
11import re
Simon Glassc05aa032019-10-31 07:42:53 -060012import queue
Simon Glassfc3fe1c2013-04-03 11:07:16 +000013import shutil
Simon Glass2f256642016-09-18 16:48:37 -060014import signal
Simon Glassfc3fe1c2013-04-03 11:07:16 +000015import string
16import sys
Simon Glassd436e382016-09-18 16:48:35 -060017import threading
Simon Glassfc3fe1c2013-04-03 11:07:16 +000018import time
19
Simon Glass0ede00f2020-04-17 18:09:02 -060020from buildman import builderthread
21from buildman import toolchain
Simon Glassbf776672020-04-17 18:09:04 -060022from patman import command
23from patman import gitutil
24from patman import terminal
Simon Glass098b10f2022-01-29 14:14:18 -070025from patman.terminal import tprint
Simon Glassfc3fe1c2013-04-03 11:07:16 +000026
Simon Glass7bf83a52021-10-19 21:43:24 -060027# This indicates an new int or hex Kconfig property with no default
28# It hangs the build since the 'conf' tool cannot proceed without valid input.
29#
30# We get a repeat sequence of something like this:
31# >>
32# Break things (BREAK_ME) [] (NEW)
33# Error in reading or end of file.
34# <<
35# which indicates that BREAK_ME has an empty default
36RE_NO_DEFAULT = re.compile(b'\((\w+)\) \[] \(NEW\)')
37
Simon Glassfc3fe1c2013-04-03 11:07:16 +000038"""
39Theory of Operation
40
41Please see README for user documentation, and you should be familiar with
42that before trying to make sense of this.
43
44Buildman works by keeping the machine as busy as possible, building different
45commits for different boards on multiple CPUs at once.
46
47The source repo (self.git_dir) contains all the commits to be built. Each
48thread works on a single board at a time. It checks out the first commit,
49configures it for that board, then builds it. Then it checks out the next
50commit and builds it (typically without re-configuring). When it runs out
51of commits, it gets another job from the builder and starts again with that
52board.
53
54Clearly the builder threads could work either way - they could check out a
55commit and then built it for all boards. Using separate directories for each
56commit/board pair they could leave their build product around afterwards
57also.
58
59The intent behind building a single board for multiple commits, is to make
60use of incremental builds. Since each commit is built incrementally from
61the previous one, builds are faster. Reconfiguring for a different board
62removes all intermediate object files.
63
64Many threads can be working at once, but each has its own working directory.
65When a thread finishes a build, it puts the output files into a result
66directory.
67
68The base directory used by buildman is normally '../<branch>', i.e.
69a directory higher than the source repository and named after the branch
70being built.
71
72Within the base directory, we have one subdirectory for each commit. Within
73that is one subdirectory for each board. Within that is the build output for
74that commit/board combination.
75
76Buildman also create working directories for each thread, in a .bm-work/
77subdirectory in the base dir.
78
79As an example, say we are building branch 'us-net' for boards 'sandbox' and
80'seaboard', and say that us-net has two commits. We will have directories
81like this:
82
83us-net/ base directory
Ovidiu Panait7664b032020-05-15 09:30:12 +030084 01_g4ed4ebc_net--Add-tftp-speed-/
Simon Glassfc3fe1c2013-04-03 11:07:16 +000085 sandbox/
86 u-boot.bin
87 seaboard/
88 u-boot.bin
Ovidiu Panait7664b032020-05-15 09:30:12 +030089 02_g4ed4ebc_net--Check-tftp-comp/
Simon Glassfc3fe1c2013-04-03 11:07:16 +000090 sandbox/
91 u-boot.bin
92 seaboard/
93 u-boot.bin
94 .bm-work/
95 00/ working directory for thread 0 (contains source checkout)
96 build/ build output
97 01/ working directory for thread 1
98 build/ build output
99 ...
100u-boot/ source directory
101 .git/ repository
102"""
103
Simon Glass35d696d2020-04-09 15:08:36 -0600104"""Holds information about a particular error line we are outputing
105
106 char: Character representation: '+': error, '-': fixed error, 'w+': warning,
107 'w-' = fixed warning
108 boards: List of Board objects which have line in the error/warning output
109 errline: The text of the error line
110"""
Simon Glasscc2c0d12022-07-11 19:04:00 -0600111ErrLine = collections.namedtuple('ErrLine', 'char,brds,errline')
Simon Glass35d696d2020-04-09 15:08:36 -0600112
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000113# Possible build outcomes
Simon Glassc05aa032019-10-31 07:42:53 -0600114OUTCOME_OK, OUTCOME_WARNING, OUTCOME_ERROR, OUTCOME_UNKNOWN = list(range(4))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000115
Simon Glass9a6d2e22017-04-12 18:23:26 -0600116# Translate a commit subject into a valid filename (and handle unicode)
Simon Glassc05aa032019-10-31 07:42:53 -0600117trans_valid_chars = str.maketrans('/: ', '---')
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000118
Simon Glassb464f8e2016-11-13 14:25:53 -0700119BASE_CONFIG_FILENAMES = [
120 'u-boot.cfg', 'u-boot-spl.cfg', 'u-boot-tpl.cfg'
121]
122
123EXTRA_CONFIG_FILENAMES = [
Simon Glass843312d2015-02-05 22:06:15 -0700124 '.config', '.config-spl', '.config-tpl',
125 'autoconf.mk', 'autoconf-spl.mk', 'autoconf-tpl.mk',
126 'autoconf.h', 'autoconf-spl.h','autoconf-tpl.h',
Simon Glass843312d2015-02-05 22:06:15 -0700127]
128
Simon Glass8270e3c2015-08-25 21:52:14 -0600129class Config:
130 """Holds information about configuration settings for a board."""
Simon Glassb464f8e2016-11-13 14:25:53 -0700131 def __init__(self, config_filename, target):
Simon Glass8270e3c2015-08-25 21:52:14 -0600132 self.target = target
133 self.config = {}
Simon Glassb464f8e2016-11-13 14:25:53 -0700134 for fname in config_filename:
Simon Glass8270e3c2015-08-25 21:52:14 -0600135 self.config[fname] = {}
136
137 def Add(self, fname, key, value):
138 self.config[fname][key] = value
139
140 def __hash__(self):
141 val = 0
142 for fname in self.config:
Simon Glassc05aa032019-10-31 07:42:53 -0600143 for key, value in self.config[fname].items():
144 print(key, value)
Simon Glass8270e3c2015-08-25 21:52:14 -0600145 val = val ^ hash(key) & hash(value)
146 return val
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000147
Alex Kiernan48ae4122018-05-31 04:48:34 +0000148class Environment:
149 """Holds information about environment variables for a board."""
150 def __init__(self, target):
151 self.target = target
152 self.environment = {}
153
154 def Add(self, key, value):
155 self.environment[key] = value
156
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000157class Builder:
158 """Class for building U-Boot for a particular commit.
159
160 Public members: (many should ->private)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000161 already_done: Number of builds already completed
162 base_dir: Base directory to use for builder
163 checkout: True to check out source, False to skip that step.
164 This is used for testing.
165 col: terminal.Color() object
166 count: Number of commits to build
167 do_make: Method to call to invoke Make
168 fail: Number of builds that failed due to error
169 force_build: Force building even if a build already exists
170 force_config_on_failure: If a commit fails for a board, disable
171 incremental building for the next commit we build for that
172 board, so that we will see all warnings/errors again.
Simon Glass4266dc22014-07-13 12:22:31 -0600173 force_build_failures: If a previously-built build (i.e. built on
174 a previous run of buildman) is marked as failed, rebuild it.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000175 git_dir: Git directory containing source repository
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000176 num_jobs: Number of jobs to run at once (passed to make as -j)
177 num_threads: Number of builder threads to run
178 out_queue: Queue of results to process
179 re_make_err: Compiled regular expression for ignore_lines
180 queue: Queue of jobs to run
181 threads: List of active threads
182 toolchains: Toolchains object to use for building
183 upto: Current commit number we are building (0.count-1)
184 warned: Number of builds that produced at least one warning
Simon Glass97e91522014-07-14 17:51:02 -0600185 force_reconfig: Reconfigure U-Boot on each comiit. This disables
186 incremental building, where buildman reconfigures on the first
187 commit for a baord, and then just does an incremental build for
188 the following commits. In fact buildman will reconfigure and
189 retry for any failing commits, so generally the only effect of
190 this option is to slow things down.
Simon Glass189a4962014-07-14 17:51:03 -0600191 in_tree: Build U-Boot in-tree instead of specifying an output
192 directory separate from the source code. This option is really
193 only useful for testing in-tree builds.
Simon Glassd829f122020-03-18 09:42:42 -0600194 work_in_output: Use the output directory as the work directory and
195 don't write to a separate output directory.
Simon Glass8116c782021-04-11 16:27:27 +1200196 thread_exceptions: List of exceptions raised by thread jobs
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000197
198 Private members:
199 _base_board_dict: Last-summarised Dict of boards
200 _base_err_lines: Last-summarised list of errors
Simon Glasse30965d2014-08-28 09:43:44 -0600201 _base_warn_lines: Last-summarised list of warnings
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000202 _build_period_us: Time taken for a single build (float object).
203 _complete_delay: Expected delay until completion (timedelta)
204 _next_delay_update: Next time we plan to display a progress update
205 (datatime)
206 _show_unknown: Show unknown boards (those not built) in summary
Simon Glass7b33f212020-04-09 15:08:47 -0600207 _start_time: Start time for the build
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000208 _timestamps: List of timestamps for the completion of the last
209 last _timestamp_count builds. Each is a datetime object.
210 _timestamp_count: Number of timestamps to keep in our list.
211 _working_dir: Base working directory containing all threads
Simon Glassb82492b2021-01-30 22:17:46 -0700212 _single_builder: BuilderThread object for the singer builder, if
213 threading is not being used
Simon Glass7bf83a52021-10-19 21:43:24 -0600214 _terminated: Thread was terminated due to an error
215 _restarting_config: True if 'Restart config' is detected in output
Simon Glassae1a09f2022-07-11 19:03:56 -0600216 _ide: Produce output suitable for an Integrated Development Environment,
217 i.e. dont emit progress information and put errors/warnings on stderr
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000218 """
219 class Outcome:
220 """Records a build outcome for a single make invocation
221
222 Public Members:
223 rc: Outcome value (OUTCOME_...)
224 err_lines: List of error lines or [] if none
225 sizes: Dictionary of image size information, keyed by filename
226 - Each value is itself a dictionary containing
227 values for 'text', 'data' and 'bss', being the integer
228 size in bytes of each section.
229 func_sizes: Dictionary keyed by filename - e.g. 'u-boot'. Each
230 value is itself a dictionary:
231 key: function name
232 value: Size of function in bytes
Simon Glass843312d2015-02-05 22:06:15 -0700233 config: Dictionary keyed by filename - e.g. '.config'. Each
234 value is itself a dictionary:
235 key: config name
236 value: config value
Alex Kiernan48ae4122018-05-31 04:48:34 +0000237 environment: Dictionary keyed by environment variable, Each
238 value is the value of environment variable.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000239 """
Alex Kiernan48ae4122018-05-31 04:48:34 +0000240 def __init__(self, rc, err_lines, sizes, func_sizes, config,
241 environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000242 self.rc = rc
243 self.err_lines = err_lines
244 self.sizes = sizes
245 self.func_sizes = func_sizes
Simon Glass843312d2015-02-05 22:06:15 -0700246 self.config = config
Alex Kiernan48ae4122018-05-31 04:48:34 +0000247 self.environment = environment
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000248
249 def __init__(self, toolchains, base_dir, git_dir, num_threads, num_jobs,
Simon Glass5971ab52014-12-01 17:33:55 -0700250 gnu_make='make', checkout=True, show_unknown=True, step=1,
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600251 no_subdirs=False, full_path=False, verbose_build=False,
Simon Glasseb70a2c2020-04-09 15:08:51 -0600252 mrproper=False, per_board_out_dir=False,
Daniel Schwierzeck2371d1b2018-01-26 16:31:05 +0100253 config_only=False, squash_config_y=False,
Simon Glass8116c782021-04-11 16:27:27 +1200254 warnings_as_errors=False, work_in_output=False,
Simon Glass2b4806e2022-01-22 05:07:33 -0700255 test_thread_exceptions=False, adjust_cfg=None):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000256 """Create a new Builder object
257
258 Args:
259 toolchains: Toolchains object to use for building
260 base_dir: Base directory to use for builder
261 git_dir: Git directory containing source repository
262 num_threads: Number of builder threads to run
263 num_jobs: Number of jobs to run at once (passed to make as -j)
Masahiro Yamada99796922014-07-22 11:19:09 +0900264 gnu_make: the command name of GNU Make.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000265 checkout: True to check out source, False to skip that step.
266 This is used for testing.
267 show_unknown: Show unknown boards (those not built) in summary
268 step: 1 to process every commit, n to process every nth commit
Simon Glassbb1501f2014-12-01 17:34:00 -0700269 no_subdirs: Don't create subdirectories when building current
270 source for a single board
271 full_path: Return the full path in CROSS_COMPILE and don't set
272 PATH
Simon Glassd2ce6582014-12-01 17:34:07 -0700273 verbose_build: Run build with V=1 and don't use 'make -s'
Simon Glasseb70a2c2020-04-09 15:08:51 -0600274 mrproper: Always run 'make mrproper' when configuring
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600275 per_board_out_dir: Build in a separate persistent directory per
276 board rather than a thread-specific directory
Simon Glassb50113f2016-11-13 14:25:51 -0700277 config_only: Only configure each build, don't build it
Simon Glassb464f8e2016-11-13 14:25:53 -0700278 squash_config_y: Convert CONFIG options with the value 'y' to '1'
Daniel Schwierzeck2371d1b2018-01-26 16:31:05 +0100279 warnings_as_errors: Treat all compiler warnings as errors
Simon Glassd829f122020-03-18 09:42:42 -0600280 work_in_output: Use the output directory as the work directory and
281 don't write to a separate output directory.
Simon Glass8116c782021-04-11 16:27:27 +1200282 test_thread_exceptions: Uses for tests only, True to make the
283 threads raise an exception instead of reporting their result.
284 This simulates a failure in the code somewhere
Simon Glass2b4806e2022-01-22 05:07:33 -0700285 adjust_cfg_list (list of str): List of changes to make to .config
286 file before building. Each is one of (where C is the config
287 option with or without the CONFIG_ prefix)
288
289 C to enable C
290 ~C to disable C
291 C=val to set the value of C (val must have quotes if C is
292 a string Kconfig
293
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000294 """
295 self.toolchains = toolchains
296 self.base_dir = base_dir
Simon Glassd829f122020-03-18 09:42:42 -0600297 if work_in_output:
298 self._working_dir = base_dir
299 else:
300 self._working_dir = os.path.join(base_dir, '.bm-work')
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000301 self.threads = []
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000302 self.do_make = self.Make
Masahiro Yamada99796922014-07-22 11:19:09 +0900303 self.gnu_make = gnu_make
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000304 self.checkout = checkout
305 self.num_threads = num_threads
306 self.num_jobs = num_jobs
307 self.already_done = 0
308 self.force_build = False
309 self.git_dir = git_dir
310 self._show_unknown = show_unknown
311 self._timestamp_count = 10
312 self._build_period_us = None
313 self._complete_delay = None
314 self._next_delay_update = datetime.now()
Simon Glass7b33f212020-04-09 15:08:47 -0600315 self._start_time = datetime.now()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000316 self.force_config_on_failure = True
Simon Glass4266dc22014-07-13 12:22:31 -0600317 self.force_build_failures = False
Simon Glass97e91522014-07-14 17:51:02 -0600318 self.force_reconfig = False
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000319 self._step = step
Simon Glass189a4962014-07-14 17:51:03 -0600320 self.in_tree = False
Simon Glass28370c12014-08-09 15:33:06 -0600321 self._error_lines = 0
Simon Glass5971ab52014-12-01 17:33:55 -0700322 self.no_subdirs = no_subdirs
Simon Glassbb1501f2014-12-01 17:34:00 -0700323 self.full_path = full_path
Simon Glassd2ce6582014-12-01 17:34:07 -0700324 self.verbose_build = verbose_build
Simon Glassb50113f2016-11-13 14:25:51 -0700325 self.config_only = config_only
Simon Glassb464f8e2016-11-13 14:25:53 -0700326 self.squash_config_y = squash_config_y
327 self.config_filenames = BASE_CONFIG_FILENAMES
Simon Glassd829f122020-03-18 09:42:42 -0600328 self.work_in_output = work_in_output
Simon Glass2b4806e2022-01-22 05:07:33 -0700329 self.adjust_cfg = adjust_cfg
Simon Glassae1a09f2022-07-11 19:03:56 -0600330 self._ide = False
Simon Glass2b4806e2022-01-22 05:07:33 -0700331
Simon Glassb464f8e2016-11-13 14:25:53 -0700332 if not self.squash_config_y:
333 self.config_filenames += EXTRA_CONFIG_FILENAMES
Simon Glass7bf83a52021-10-19 21:43:24 -0600334 self._terminated = False
335 self._restarting_config = False
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000336
Daniel Schwierzeck2371d1b2018-01-26 16:31:05 +0100337 self.warnings_as_errors = warnings_as_errors
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000338 self.col = terminal.Color()
339
Simon Glasse30965d2014-08-28 09:43:44 -0600340 self._re_function = re.compile('(.*): In function.*')
341 self._re_files = re.compile('In file included from.*')
342 self._re_warning = re.compile('(.*):(\d*):(\d*): warning: .*')
Simon Glass2d483332018-11-06 16:02:11 -0700343 self._re_dtb_warning = re.compile('(.*): Warning .*')
Simon Glasse30965d2014-08-28 09:43:44 -0600344 self._re_note = re.compile('(.*):(\d*):(\d*): note: this is the location of the previous.*')
Simon Glass113a8a52020-04-09 15:08:53 -0600345 self._re_migration_warning = re.compile(r'^={21} WARNING ={22}\n.*\n=+\n',
346 re.MULTILINE | re.DOTALL)
Simon Glasse30965d2014-08-28 09:43:44 -0600347
Simon Glass8116c782021-04-11 16:27:27 +1200348 self.thread_exceptions = []
349 self.test_thread_exceptions = test_thread_exceptions
Simon Glassb82492b2021-01-30 22:17:46 -0700350 if self.num_threads:
351 self._single_builder = None
352 self.queue = queue.Queue()
353 self.out_queue = queue.Queue()
354 for i in range(self.num_threads):
Simon Glass8116c782021-04-11 16:27:27 +1200355 t = builderthread.BuilderThread(
356 self, i, mrproper, per_board_out_dir,
357 test_exception=test_thread_exceptions)
Simon Glassb82492b2021-01-30 22:17:46 -0700358 t.setDaemon(True)
359 t.start()
360 self.threads.append(t)
361
362 t = builderthread.ResultThread(self)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000363 t.setDaemon(True)
364 t.start()
365 self.threads.append(t)
Simon Glassb82492b2021-01-30 22:17:46 -0700366 else:
367 self._single_builder = builderthread.BuilderThread(
368 self, -1, mrproper, per_board_out_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000369
370 ignore_lines = ['(make.*Waiting for unfinished)', '(Segmentation fault)']
371 self.re_make_err = re.compile('|'.join(ignore_lines))
372
Simon Glass2f256642016-09-18 16:48:37 -0600373 # Handle existing graceful with SIGINT / Ctrl-C
374 signal.signal(signal.SIGINT, self.signal_handler)
375
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000376 def __del__(self):
377 """Get rid of all threads created by the builder"""
378 for t in self.threads:
379 del t
380
Simon Glass2f256642016-09-18 16:48:37 -0600381 def signal_handler(self, signal, frame):
382 sys.exit(1)
383
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600384 def SetDisplayOptions(self, show_errors=False, show_sizes=False,
Simon Glassed966652014-08-28 09:43:43 -0600385 show_detail=False, show_bloat=False,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000386 list_error_boards=False, show_config=False,
Simon Glass113a8a52020-04-09 15:08:53 -0600387 show_environment=False, filter_dtb_warnings=False,
Simon Glassae1a09f2022-07-11 19:03:56 -0600388 filter_migration_warnings=False, ide=False):
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600389 """Setup display options for the builder.
390
Simon Glass174592b2020-04-09 15:08:52 -0600391 Args:
392 show_errors: True to show summarised error/warning info
393 show_sizes: Show size deltas
394 show_detail: Show size delta detail for each board if show_sizes
395 show_bloat: Show detail for each function
396 list_error_boards: Show the boards which caused each error/warning
397 show_config: Show config deltas
398 show_environment: Show environment deltas
399 filter_dtb_warnings: Filter out any warnings from the device-tree
400 compiler
Simon Glass113a8a52020-04-09 15:08:53 -0600401 filter_migration_warnings: Filter out any warnings about migrating
402 a board to driver model
Simon Glassae1a09f2022-07-11 19:03:56 -0600403 ide: Create output that can be parsed by an IDE. There is no '+' prefix on
404 error lines and output on stderr stays on stderr.
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600405 """
406 self._show_errors = show_errors
407 self._show_sizes = show_sizes
408 self._show_detail = show_detail
409 self._show_bloat = show_bloat
Simon Glassed966652014-08-28 09:43:43 -0600410 self._list_error_boards = list_error_boards
Simon Glass843312d2015-02-05 22:06:15 -0700411 self._show_config = show_config
Alex Kiernan48ae4122018-05-31 04:48:34 +0000412 self._show_environment = show_environment
Simon Glass174592b2020-04-09 15:08:52 -0600413 self._filter_dtb_warnings = filter_dtb_warnings
Simon Glass113a8a52020-04-09 15:08:53 -0600414 self._filter_migration_warnings = filter_migration_warnings
Simon Glassae1a09f2022-07-11 19:03:56 -0600415 self._ide = ide
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600416
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000417 def _AddTimestamp(self):
418 """Add a new timestamp to the list and record the build period.
419
420 The build period is the length of time taken to perform a single
421 build (one board, one commit).
422 """
423 now = datetime.now()
424 self._timestamps.append(now)
425 count = len(self._timestamps)
426 delta = self._timestamps[-1] - self._timestamps[0]
427 seconds = delta.total_seconds()
428
429 # If we have enough data, estimate build period (time taken for a
430 # single build) and therefore completion time.
431 if count > 1 and self._next_delay_update < now:
432 self._next_delay_update = now + timedelta(seconds=2)
433 if seconds > 0:
434 self._build_period = float(seconds) / count
435 todo = self.count - self.upto
436 self._complete_delay = timedelta(microseconds=
437 self._build_period * todo * 1000000)
438 # Round it
439 self._complete_delay -= timedelta(
440 microseconds=self._complete_delay.microseconds)
441
442 if seconds > 60:
443 self._timestamps.popleft()
444 count -= 1
445
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000446 def SelectCommit(self, commit, checkout=True):
447 """Checkout the selected commit for this build
448 """
449 self.commit = commit
450 if checkout and self.checkout:
Simon Glass0157b182022-01-29 14:14:11 -0700451 gitutil.checkout(commit.hash)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000452
453 def Make(self, commit, brd, stage, cwd, *args, **kwargs):
454 """Run make
455
456 Args:
457 commit: Commit object that is being built
458 brd: Board object that is being built
Roger Meierfd18a892014-08-20 22:10:29 +0200459 stage: Stage that we are at (mrproper, config, build)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000460 cwd: Directory where make should be run
461 args: Arguments to pass to make
Simon Glassd9800692022-01-29 14:14:05 -0700462 kwargs: Arguments to pass to command.run_pipe()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000463 """
Simon Glass7bf83a52021-10-19 21:43:24 -0600464
465 def check_output(stream, data):
466 if b'Restart config' in data:
467 self._restarting_config = True
468
469 # If we see 'Restart config' following by multiple errors
470 if self._restarting_config:
471 m = RE_NO_DEFAULT.findall(data)
472
473 # Number of occurences of each Kconfig item
474 multiple = [m.count(val) for val in set(m)]
475
476 # If any of them occur more than once, we have a loop
477 if [val for val in multiple if val > 1]:
478 self._terminated = True
479 return True
480 return False
481
482 self._restarting_config = False
483 self._terminated = False
Masahiro Yamada99796922014-07-22 11:19:09 +0900484 cmd = [self.gnu_make] + list(args)
Simon Glassd9800692022-01-29 14:14:05 -0700485 result = command.run_pipe([cmd], capture=True, capture_stderr=True,
Simon Glass7bf83a52021-10-19 21:43:24 -0600486 cwd=cwd, raise_on_error=False, infile='/dev/null',
487 output_func=check_output, **kwargs)
488
489 if self._terminated:
490 # Try to be helpful
491 result.stderr += '(** did you define an int/hex Kconfig with no default? **)'
492
Simon Glass40f11fc2015-02-05 22:06:12 -0700493 if self.verbose_build:
494 result.stdout = '%s\n' % (' '.join(cmd)) + result.stdout
495 result.combined = '%s\n' % (' '.join(cmd)) + result.combined
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000496 return result
497
498 def ProcessResult(self, result):
499 """Process the result of a build, showing progress information
500
501 Args:
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600502 result: A CommandResult object, which indicates the result for
503 a single build
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000504 """
505 col = terminal.Color()
506 if result:
507 target = result.brd.target
508
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000509 self.upto += 1
510 if result.return_code != 0:
511 self.fail += 1
512 elif result.stderr:
513 self.warned += 1
514 if result.already_done:
515 self.already_done += 1
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600516 if self._verbose:
Simon Glass098b10f2022-01-29 14:14:18 -0700517 terminal.print_clear()
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600518 boards_selected = {target : result.brd}
519 self.ResetResultSummary(boards_selected)
520 self.ProduceResultSummary(result.commit_upto, self.commits,
521 boards_selected)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000522 else:
523 target = '(starting)'
524
525 # Display separate counts for ok, warned and fail
526 ok = self.upto - self.warned - self.fail
Simon Glass252ac582022-01-29 14:14:17 -0700527 line = '\r' + self.col.build(self.col.GREEN, '%5d' % ok)
528 line += self.col.build(self.col.YELLOW, '%5d' % self.warned)
529 line += self.col.build(self.col.RED, '%5d' % self.fail)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000530
Simon Glass6eb76ca2020-04-09 15:08:45 -0600531 line += ' /%-5d ' % self.count
532 remaining = self.count - self.upto
533 if remaining:
Simon Glass252ac582022-01-29 14:14:17 -0700534 line += self.col.build(self.col.MAGENTA, ' -%-5d ' % remaining)
Simon Glass6eb76ca2020-04-09 15:08:45 -0600535 else:
536 line += ' ' * 8
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000537
538 # Add our current completion time estimate
539 self._AddTimestamp()
540 if self._complete_delay:
Simon Glass6eb76ca2020-04-09 15:08:45 -0600541 line += '%s : ' % self._complete_delay
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000542
Simon Glass6eb76ca2020-04-09 15:08:45 -0600543 line += target
Simon Glassae1a09f2022-07-11 19:03:56 -0600544 if not self._ide:
545 terminal.print_clear()
546 tprint(line, newline=False, limit_to_line=True)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000547
548 def _GetOutputDir(self, commit_upto):
549 """Get the name of the output directory for a commit number
550
551 The output directory is typically .../<branch>/<commit>.
552
553 Args:
554 commit_upto: Commit number to use (0..self.count-1)
555 """
Simon Glass60b285f2020-04-17 17:51:34 -0600556 if self.work_in_output:
557 return self._working_dir
558
Simon Glass5971ab52014-12-01 17:33:55 -0700559 commit_dir = None
Simon Glassfea58582014-08-09 15:32:59 -0600560 if self.commits:
561 commit = self.commits[commit_upto]
562 subject = commit.subject.translate(trans_valid_chars)
Simon Glass925f6ad2020-03-18 09:42:45 -0600563 # See _GetOutputSpaceRemovals() which parses this name
Ovidiu Panait7664b032020-05-15 09:30:12 +0300564 commit_dir = ('%02d_g%s_%s' % (commit_upto + 1,
565 commit.hash, subject[:20]))
Simon Glass5971ab52014-12-01 17:33:55 -0700566 elif not self.no_subdirs:
Simon Glassfea58582014-08-09 15:32:59 -0600567 commit_dir = 'current'
Simon Glass5971ab52014-12-01 17:33:55 -0700568 if not commit_dir:
569 return self.base_dir
570 return os.path.join(self.base_dir, commit_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000571
572 def GetBuildDir(self, commit_upto, target):
573 """Get the name of the build directory for a commit number
574
575 The build directory is typically .../<branch>/<commit>/<target>.
576
577 Args:
578 commit_upto: Commit number to use (0..self.count-1)
579 target: Target name
580 """
581 output_dir = self._GetOutputDir(commit_upto)
Simon Glass60b285f2020-04-17 17:51:34 -0600582 if self.work_in_output:
583 return output_dir
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000584 return os.path.join(output_dir, target)
585
586 def GetDoneFile(self, commit_upto, target):
587 """Get the name of the done file for a commit number
588
589 Args:
590 commit_upto: Commit number to use (0..self.count-1)
591 target: Target name
592 """
593 return os.path.join(self.GetBuildDir(commit_upto, target), 'done')
594
595 def GetSizesFile(self, commit_upto, target):
596 """Get the name of the sizes file for a commit number
597
598 Args:
599 commit_upto: Commit number to use (0..self.count-1)
600 target: Target name
601 """
602 return os.path.join(self.GetBuildDir(commit_upto, target), 'sizes')
603
604 def GetFuncSizesFile(self, commit_upto, target, elf_fname):
605 """Get the name of the funcsizes file for a commit number and ELF file
606
607 Args:
608 commit_upto: Commit number to use (0..self.count-1)
609 target: Target name
610 elf_fname: Filename of elf image
611 """
612 return os.path.join(self.GetBuildDir(commit_upto, target),
613 '%s.sizes' % elf_fname.replace('/', '-'))
614
615 def GetObjdumpFile(self, commit_upto, target, elf_fname):
616 """Get the name of the objdump file for a commit number and ELF file
617
618 Args:
619 commit_upto: Commit number to use (0..self.count-1)
620 target: Target name
621 elf_fname: Filename of elf image
622 """
623 return os.path.join(self.GetBuildDir(commit_upto, target),
624 '%s.objdump' % elf_fname.replace('/', '-'))
625
626 def GetErrFile(self, commit_upto, target):
627 """Get the name of the err file for a commit number
628
629 Args:
630 commit_upto: Commit number to use (0..self.count-1)
631 target: Target name
632 """
633 output_dir = self.GetBuildDir(commit_upto, target)
634 return os.path.join(output_dir, 'err')
635
636 def FilterErrors(self, lines):
637 """Filter out errors in which we have no interest
638
639 We should probably use map().
640
641 Args:
642 lines: List of error lines, each a string
643 Returns:
644 New list with only interesting lines included
645 """
646 out_lines = []
Simon Glass113a8a52020-04-09 15:08:53 -0600647 if self._filter_migration_warnings:
648 text = '\n'.join(lines)
649 text = self._re_migration_warning.sub('', text)
650 lines = text.splitlines()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000651 for line in lines:
Simon Glass174592b2020-04-09 15:08:52 -0600652 if self.re_make_err.search(line):
653 continue
654 if self._filter_dtb_warnings and self._re_dtb_warning.search(line):
655 continue
656 out_lines.append(line)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000657 return out_lines
658
659 def ReadFuncSizes(self, fname, fd):
660 """Read function sizes from the output of 'nm'
661
662 Args:
663 fd: File containing data to read
664 fname: Filename we are reading from (just for errors)
665
666 Returns:
667 Dictionary containing size of each function in bytes, indexed by
668 function name.
669 """
670 sym = {}
671 for line in fd.readlines():
672 try:
Tom Rinid08c38c2019-12-06 15:31:31 -0500673 if line.strip():
674 size, type, name = line[:-1].split()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000675 except:
Simon Glass098b10f2022-01-29 14:14:18 -0700676 tprint("Invalid line in file '%s': '%s'" % (fname, line[:-1]))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000677 continue
678 if type in 'tTdDbB':
679 # function names begin with '.' on 64-bit powerpc
680 if '.' in name[1:]:
681 name = 'static.' + name.split('.')[0]
682 sym[name] = sym.get(name, 0) + int(size, 16)
683 return sym
684
Simon Glass843312d2015-02-05 22:06:15 -0700685 def _ProcessConfig(self, fname):
686 """Read in a .config, autoconf.mk or autoconf.h file
687
688 This function handles all config file types. It ignores comments and
689 any #defines which don't start with CONFIG_.
690
691 Args:
692 fname: Filename to read
693
694 Returns:
695 Dictionary:
696 key: Config name (e.g. CONFIG_DM)
697 value: Config value (e.g. 1)
698 """
699 config = {}
700 if os.path.exists(fname):
701 with open(fname) as fd:
702 for line in fd:
703 line = line.strip()
704 if line.startswith('#define'):
705 values = line[8:].split(' ', 1)
706 if len(values) > 1:
707 key, value = values
708 else:
709 key = values[0]
Simon Glassb464f8e2016-11-13 14:25:53 -0700710 value = '1' if self.squash_config_y else ''
Simon Glass843312d2015-02-05 22:06:15 -0700711 if not key.startswith('CONFIG_'):
712 continue
713 elif not line or line[0] in ['#', '*', '/']:
714 continue
715 else:
716 key, value = line.split('=', 1)
Simon Glassb464f8e2016-11-13 14:25:53 -0700717 if self.squash_config_y and value == 'y':
718 value = '1'
Simon Glass843312d2015-02-05 22:06:15 -0700719 config[key] = value
720 return config
721
Alex Kiernan48ae4122018-05-31 04:48:34 +0000722 def _ProcessEnvironment(self, fname):
723 """Read in a uboot.env file
724
725 This function reads in environment variables from a file.
726
727 Args:
728 fname: Filename to read
729
730 Returns:
731 Dictionary:
732 key: environment variable (e.g. bootlimit)
733 value: value of environment variable (e.g. 1)
734 """
735 environment = {}
736 if os.path.exists(fname):
737 with open(fname) as fd:
738 for line in fd.read().split('\0'):
739 try:
740 key, value = line.split('=', 1)
741 environment[key] = value
742 except ValueError:
743 # ignore lines we can't parse
744 pass
745 return environment
746
Simon Glass843312d2015-02-05 22:06:15 -0700747 def GetBuildOutcome(self, commit_upto, target, read_func_sizes,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000748 read_config, read_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000749 """Work out the outcome of a build.
750
751 Args:
752 commit_upto: Commit number to check (0..n-1)
753 target: Target board to check
754 read_func_sizes: True to read function size information
Simon Glass843312d2015-02-05 22:06:15 -0700755 read_config: True to read .config and autoconf.h files
Alex Kiernan48ae4122018-05-31 04:48:34 +0000756 read_environment: True to read uboot.env files
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000757
758 Returns:
759 Outcome object
760 """
761 done_file = self.GetDoneFile(commit_upto, target)
762 sizes_file = self.GetSizesFile(commit_upto, target)
763 sizes = {}
764 func_sizes = {}
Simon Glass843312d2015-02-05 22:06:15 -0700765 config = {}
Alex Kiernan48ae4122018-05-31 04:48:34 +0000766 environment = {}
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000767 if os.path.exists(done_file):
768 with open(done_file, 'r') as fd:
Simon Glass347ea0b2019-04-26 19:02:23 -0600769 try:
770 return_code = int(fd.readline())
771 except ValueError:
772 # The file may be empty due to running out of disk space.
773 # Try a rebuild
774 return_code = 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000775 err_lines = []
776 err_file = self.GetErrFile(commit_upto, target)
777 if os.path.exists(err_file):
778 with open(err_file, 'r') as fd:
779 err_lines = self.FilterErrors(fd.readlines())
780
781 # Decide whether the build was ok, failed or created warnings
782 if return_code:
783 rc = OUTCOME_ERROR
784 elif len(err_lines):
785 rc = OUTCOME_WARNING
786 else:
787 rc = OUTCOME_OK
788
789 # Convert size information to our simple format
790 if os.path.exists(sizes_file):
791 with open(sizes_file, 'r') as fd:
792 for line in fd.readlines():
793 values = line.split()
794 rodata = 0
795 if len(values) > 6:
796 rodata = int(values[6], 16)
797 size_dict = {
798 'all' : int(values[0]) + int(values[1]) +
799 int(values[2]),
800 'text' : int(values[0]) - rodata,
801 'data' : int(values[1]),
802 'bss' : int(values[2]),
803 'rodata' : rodata,
804 }
805 sizes[values[5]] = size_dict
806
807 if read_func_sizes:
808 pattern = self.GetFuncSizesFile(commit_upto, target, '*')
809 for fname in glob.glob(pattern):
810 with open(fname, 'r') as fd:
811 dict_name = os.path.basename(fname).replace('.sizes',
812 '')
813 func_sizes[dict_name] = self.ReadFuncSizes(fname, fd)
814
Simon Glass843312d2015-02-05 22:06:15 -0700815 if read_config:
816 output_dir = self.GetBuildDir(commit_upto, target)
Simon Glassb464f8e2016-11-13 14:25:53 -0700817 for name in self.config_filenames:
Simon Glass843312d2015-02-05 22:06:15 -0700818 fname = os.path.join(output_dir, name)
819 config[name] = self._ProcessConfig(fname)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000820
Alex Kiernan48ae4122018-05-31 04:48:34 +0000821 if read_environment:
822 output_dir = self.GetBuildDir(commit_upto, target)
823 fname = os.path.join(output_dir, 'uboot.env')
824 environment = self._ProcessEnvironment(fname)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000825
Alex Kiernan48ae4122018-05-31 04:48:34 +0000826 return Builder.Outcome(rc, err_lines, sizes, func_sizes, config,
827 environment)
828
829 return Builder.Outcome(OUTCOME_UNKNOWN, [], {}, {}, {}, {})
Simon Glass843312d2015-02-05 22:06:15 -0700830
831 def GetResultSummary(self, boards_selected, commit_upto, read_func_sizes,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000832 read_config, read_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000833 """Calculate a summary of the results of building a commit.
834
835 Args:
836 board_selected: Dict containing boards to summarise
837 commit_upto: Commit number to summarize (0..self.count-1)
838 read_func_sizes: True to read function size information
Simon Glass843312d2015-02-05 22:06:15 -0700839 read_config: True to read .config and autoconf.h files
Alex Kiernan48ae4122018-05-31 04:48:34 +0000840 read_environment: True to read uboot.env files
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000841
842 Returns:
843 Tuple:
Simon Glassae1a09f2022-07-11 19:03:56 -0600844 Dict containing boards which built this commit:
845 key: board.target
846 value: Builder.Outcome object
Simon Glasse30965d2014-08-28 09:43:44 -0600847 List containing a summary of error lines
Simon Glassed966652014-08-28 09:43:43 -0600848 Dict keyed by error line, containing a list of the Board
849 objects with that error
Simon Glasse30965d2014-08-28 09:43:44 -0600850 List containing a summary of warning lines
851 Dict keyed by error line, containing a list of the Board
852 objects with that warning
Simon Glass8270e3c2015-08-25 21:52:14 -0600853 Dictionary keyed by board.target. Each value is a dictionary:
854 key: filename - e.g. '.config'
Simon Glass843312d2015-02-05 22:06:15 -0700855 value is itself a dictionary:
856 key: config name
857 value: config value
Alex Kiernan48ae4122018-05-31 04:48:34 +0000858 Dictionary keyed by board.target. Each value is a dictionary:
859 key: environment variable
860 value: value of environment variable
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000861 """
Simon Glasse30965d2014-08-28 09:43:44 -0600862 def AddLine(lines_summary, lines_boards, line, board):
863 line = line.rstrip()
864 if line in lines_boards:
865 lines_boards[line].append(board)
866 else:
867 lines_boards[line] = [board]
868 lines_summary.append(line)
869
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000870 board_dict = {}
871 err_lines_summary = []
Simon Glassed966652014-08-28 09:43:43 -0600872 err_lines_boards = {}
Simon Glasse30965d2014-08-28 09:43:44 -0600873 warn_lines_summary = []
874 warn_lines_boards = {}
Simon Glass843312d2015-02-05 22:06:15 -0700875 config = {}
Alex Kiernan48ae4122018-05-31 04:48:34 +0000876 environment = {}
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000877
Simon Glassf4ed4702022-07-11 19:03:57 -0600878 for brd in boards_selected.values():
879 outcome = self.GetBuildOutcome(commit_upto, brd.target,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000880 read_func_sizes, read_config,
881 read_environment)
Simon Glassf4ed4702022-07-11 19:03:57 -0600882 board_dict[brd.target] = outcome
Simon Glasse30965d2014-08-28 09:43:44 -0600883 last_func = None
884 last_was_warning = False
885 for line in outcome.err_lines:
886 if line:
887 if (self._re_function.match(line) or
888 self._re_files.match(line)):
889 last_func = line
Simon Glassed966652014-08-28 09:43:43 -0600890 else:
Simon Glass2d483332018-11-06 16:02:11 -0700891 is_warning = (self._re_warning.match(line) or
892 self._re_dtb_warning.match(line))
Simon Glasse30965d2014-08-28 09:43:44 -0600893 is_note = self._re_note.match(line)
894 if is_warning or (last_was_warning and is_note):
895 if last_func:
896 AddLine(warn_lines_summary, warn_lines_boards,
Simon Glassf4ed4702022-07-11 19:03:57 -0600897 last_func, brd)
Simon Glasse30965d2014-08-28 09:43:44 -0600898 AddLine(warn_lines_summary, warn_lines_boards,
Simon Glassf4ed4702022-07-11 19:03:57 -0600899 line, brd)
Simon Glasse30965d2014-08-28 09:43:44 -0600900 else:
901 if last_func:
902 AddLine(err_lines_summary, err_lines_boards,
Simon Glassf4ed4702022-07-11 19:03:57 -0600903 last_func, brd)
Simon Glasse30965d2014-08-28 09:43:44 -0600904 AddLine(err_lines_summary, err_lines_boards,
Simon Glassf4ed4702022-07-11 19:03:57 -0600905 line, brd)
Simon Glasse30965d2014-08-28 09:43:44 -0600906 last_was_warning = is_warning
907 last_func = None
Simon Glassf4ed4702022-07-11 19:03:57 -0600908 tconfig = Config(self.config_filenames, brd.target)
Simon Glassb464f8e2016-11-13 14:25:53 -0700909 for fname in self.config_filenames:
Simon Glass843312d2015-02-05 22:06:15 -0700910 if outcome.config:
Simon Glassc05aa032019-10-31 07:42:53 -0600911 for key, value in outcome.config[fname].items():
Simon Glass8270e3c2015-08-25 21:52:14 -0600912 tconfig.Add(fname, key, value)
Simon Glassf4ed4702022-07-11 19:03:57 -0600913 config[brd.target] = tconfig
Simon Glass843312d2015-02-05 22:06:15 -0700914
Simon Glassf4ed4702022-07-11 19:03:57 -0600915 tenvironment = Environment(brd.target)
Alex Kiernan48ae4122018-05-31 04:48:34 +0000916 if outcome.environment:
Simon Glassc05aa032019-10-31 07:42:53 -0600917 for key, value in outcome.environment.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +0000918 tenvironment.Add(key, value)
Simon Glassf4ed4702022-07-11 19:03:57 -0600919 environment[brd.target] = tenvironment
Alex Kiernan48ae4122018-05-31 04:48:34 +0000920
Simon Glasse30965d2014-08-28 09:43:44 -0600921 return (board_dict, err_lines_summary, err_lines_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000922 warn_lines_summary, warn_lines_boards, config, environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000923
924 def AddOutcome(self, board_dict, arch_list, changes, char, color):
925 """Add an output to our list of outcomes for each architecture
926
927 This simple function adds failing boards (changes) to the
928 relevant architecture string, so we can print the results out
929 sorted by architecture.
930
931 Args:
932 board_dict: Dict containing all boards
933 arch_list: Dict keyed by arch name. Value is a string containing
934 a list of board names which failed for that arch.
935 changes: List of boards to add to arch_list
936 color: terminal.Colour object
937 """
938 done_arch = {}
939 for target in changes:
940 if target in board_dict:
941 arch = board_dict[target].arch
942 else:
943 arch = 'unknown'
Simon Glass252ac582022-01-29 14:14:17 -0700944 str = self.col.build(color, ' ' + target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000945 if not arch in done_arch:
Simon Glass252ac582022-01-29 14:14:17 -0700946 str = ' %s %s' % (self.col.build(color, char), str)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000947 done_arch[arch] = True
948 if not arch in arch_list:
949 arch_list[arch] = str
950 else:
951 arch_list[arch] += str
952
953
954 def ColourNum(self, num):
955 color = self.col.RED if num > 0 else self.col.GREEN
956 if num == 0:
957 return '0'
Simon Glass252ac582022-01-29 14:14:17 -0700958 return self.col.build(color, str(num))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000959
960 def ResetResultSummary(self, board_selected):
961 """Reset the results summary ready for use.
962
963 Set up the base board list to be all those selected, and set the
964 error lines to empty.
965
966 Following this, calls to PrintResultSummary() will use this
967 information to work out what has changed.
968
969 Args:
970 board_selected: Dict containing boards to summarise, keyed by
971 board.target
972 """
973 self._base_board_dict = {}
Simon Glassf4ed4702022-07-11 19:03:57 -0600974 for brd in board_selected:
975 self._base_board_dict[brd] = Builder.Outcome(0, [], [], {}, {}, {})
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000976 self._base_err_lines = []
Simon Glasse30965d2014-08-28 09:43:44 -0600977 self._base_warn_lines = []
978 self._base_err_line_boards = {}
979 self._base_warn_line_boards = {}
Simon Glass8270e3c2015-08-25 21:52:14 -0600980 self._base_config = None
Alex Kiernan48ae4122018-05-31 04:48:34 +0000981 self._base_environment = None
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000982
983 def PrintFuncSizeDetail(self, fname, old, new):
984 grow, shrink, add, remove, up, down = 0, 0, 0, 0, 0, 0
985 delta, common = [], {}
986
987 for a in old:
988 if a in new:
989 common[a] = 1
990
991 for name in old:
992 if name not in common:
993 remove += 1
994 down += old[name]
995 delta.append([-old[name], name])
996
997 for name in new:
998 if name not in common:
999 add += 1
1000 up += new[name]
1001 delta.append([new[name], name])
1002
1003 for name in common:
1004 diff = new.get(name, 0) - old.get(name, 0)
1005 if diff > 0:
1006 grow, up = grow + 1, up + diff
1007 elif diff < 0:
1008 shrink, down = shrink + 1, down - diff
1009 delta.append([diff, name])
1010
1011 delta.sort()
1012 delta.reverse()
1013
1014 args = [add, -remove, grow, -shrink, up, -down, up - down]
Tom Rinid5686a62017-05-22 13:48:52 -04001015 if max(args) == 0 and min(args) == 0:
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001016 return
1017 args = [self.ColourNum(x) for x in args]
1018 indent = ' ' * 15
Simon Glass098b10f2022-01-29 14:14:18 -07001019 tprint('%s%s: add: %s/%s, grow: %s/%s bytes: %s/%s (%s)' %
Simon Glass252ac582022-01-29 14:14:17 -07001020 tuple([indent, self.col.build(self.col.YELLOW, fname)] + args))
Simon Glass098b10f2022-01-29 14:14:18 -07001021 tprint('%s %-38s %7s %7s %+7s' % (indent, 'function', 'old', 'new',
Simon Glass4653a882014-09-05 19:00:07 -06001022 'delta'))
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001023 for diff, name in delta:
1024 if diff:
1025 color = self.col.RED if diff > 0 else self.col.GREEN
1026 msg = '%s %-38s %7s %7s %+7d' % (indent, name,
1027 old.get(name, '-'), new.get(name,'-'), diff)
Simon Glass098b10f2022-01-29 14:14:18 -07001028 tprint(msg, colour=color)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001029
1030
1031 def PrintSizeDetail(self, target_list, show_bloat):
1032 """Show details size information for each board
1033
1034 Args:
1035 target_list: List of targets, each a dict containing:
1036 'target': Target name
1037 'total_diff': Total difference in bytes across all areas
1038 <part_name>: Difference for that part
1039 show_bloat: Show detail for each function
1040 """
1041 targets_by_diff = sorted(target_list, reverse=True,
1042 key=lambda x: x['_total_diff'])
1043 for result in targets_by_diff:
1044 printed_target = False
1045 for name in sorted(result):
1046 diff = result[name]
1047 if name.startswith('_'):
1048 continue
1049 if diff != 0:
1050 color = self.col.RED if diff > 0 else self.col.GREEN
1051 msg = ' %s %+d' % (name, diff)
1052 if not printed_target:
Simon Glass098b10f2022-01-29 14:14:18 -07001053 tprint('%10s %-15s:' % ('', result['_target']),
Simon Glass4653a882014-09-05 19:00:07 -06001054 newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001055 printed_target = True
Simon Glass098b10f2022-01-29 14:14:18 -07001056 tprint(msg, colour=color, newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001057 if printed_target:
Simon Glass098b10f2022-01-29 14:14:18 -07001058 tprint()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001059 if show_bloat:
1060 target = result['_target']
1061 outcome = result['_outcome']
1062 base_outcome = self._base_board_dict[target]
1063 for fname in outcome.func_sizes:
1064 self.PrintFuncSizeDetail(fname,
1065 base_outcome.func_sizes[fname],
1066 outcome.func_sizes[fname])
1067
1068
1069 def PrintSizeSummary(self, board_selected, board_dict, show_detail,
1070 show_bloat):
1071 """Print a summary of image sizes broken down by section.
1072
1073 The summary takes the form of one line per architecture. The
1074 line contains deltas for each of the sections (+ means the section
Flavio Suligoi9de5c392020-01-29 09:56:05 +01001075 got bigger, - means smaller). The numbers are the average number
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001076 of bytes that a board in this section increased by.
1077
1078 For example:
1079 powerpc: (622 boards) text -0.0
1080 arm: (285 boards) text -0.0
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001081
1082 Args:
1083 board_selected: Dict containing boards to summarise, keyed by
1084 board.target
1085 board_dict: Dict containing boards for which we built this
1086 commit, keyed by board.target. The value is an Outcome object.
Simon Glassf9c094b2020-03-18 09:42:43 -06001087 show_detail: Show size delta detail for each board
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001088 show_bloat: Show detail for each function
1089 """
1090 arch_list = {}
1091 arch_count = {}
1092
1093 # Calculate changes in size for different image parts
1094 # The previous sizes are in Board.sizes, for each board
1095 for target in board_dict:
1096 if target not in board_selected:
1097 continue
1098 base_sizes = self._base_board_dict[target].sizes
1099 outcome = board_dict[target]
1100 sizes = outcome.sizes
1101
1102 # Loop through the list of images, creating a dict of size
1103 # changes for each image/part. We end up with something like
1104 # {'target' : 'snapper9g45, 'data' : 5, 'u-boot-spl:text' : -4}
1105 # which means that U-Boot data increased by 5 bytes and SPL
1106 # text decreased by 4.
1107 err = {'_target' : target}
1108 for image in sizes:
1109 if image in base_sizes:
1110 base_image = base_sizes[image]
1111 # Loop through the text, data, bss parts
1112 for part in sorted(sizes[image]):
1113 diff = sizes[image][part] - base_image[part]
1114 col = None
1115 if diff:
1116 if image == 'u-boot':
1117 name = part
1118 else:
1119 name = image + ':' + part
1120 err[name] = diff
1121 arch = board_selected[target].arch
1122 if not arch in arch_count:
1123 arch_count[arch] = 1
1124 else:
1125 arch_count[arch] += 1
1126 if not sizes:
1127 pass # Only add to our list when we have some stats
1128 elif not arch in arch_list:
1129 arch_list[arch] = [err]
1130 else:
1131 arch_list[arch].append(err)
1132
1133 # We now have a list of image size changes sorted by arch
1134 # Print out a summary of these
Simon Glassc05aa032019-10-31 07:42:53 -06001135 for arch, target_list in arch_list.items():
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001136 # Get total difference for each type
1137 totals = {}
1138 for result in target_list:
1139 total = 0
Simon Glassc05aa032019-10-31 07:42:53 -06001140 for name, diff in result.items():
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001141 if name.startswith('_'):
1142 continue
1143 total += diff
1144 if name in totals:
1145 totals[name] += diff
1146 else:
1147 totals[name] = diff
1148 result['_total_diff'] = total
1149 result['_outcome'] = board_dict[result['_target']]
1150
1151 count = len(target_list)
1152 printed_arch = False
1153 for name in sorted(totals):
1154 diff = totals[name]
1155 if diff:
1156 # Display the average difference in this name for this
1157 # architecture
1158 avg_diff = float(diff) / count
1159 color = self.col.RED if avg_diff > 0 else self.col.GREEN
1160 msg = ' %s %+1.1f' % (name, avg_diff)
1161 if not printed_arch:
Simon Glass098b10f2022-01-29 14:14:18 -07001162 tprint('%10s: (for %d/%d boards)' % (arch, count,
Simon Glass4653a882014-09-05 19:00:07 -06001163 arch_count[arch]), newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001164 printed_arch = True
Simon Glass098b10f2022-01-29 14:14:18 -07001165 tprint(msg, colour=color, newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001166
1167 if printed_arch:
Simon Glass098b10f2022-01-29 14:14:18 -07001168 tprint()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001169 if show_detail:
1170 self.PrintSizeDetail(target_list, show_bloat)
1171
1172
1173 def PrintResultSummary(self, board_selected, board_dict, err_lines,
Simon Glasse30965d2014-08-28 09:43:44 -06001174 err_line_boards, warn_lines, warn_line_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001175 config, environment, show_sizes, show_detail,
1176 show_bloat, show_config, show_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001177 """Compare results with the base results and display delta.
1178
1179 Only boards mentioned in board_selected will be considered. This
1180 function is intended to be called repeatedly with the results of
1181 each commit. It therefore shows a 'diff' between what it saw in
1182 the last call and what it sees now.
1183
1184 Args:
1185 board_selected: Dict containing boards to summarise, keyed by
1186 board.target
1187 board_dict: Dict containing boards for which we built this
1188 commit, keyed by board.target. The value is an Outcome object.
1189 err_lines: A list of errors for this commit, or [] if there is
1190 none, or we don't want to print errors
Simon Glassed966652014-08-28 09:43:43 -06001191 err_line_boards: Dict keyed by error line, containing a list of
1192 the Board objects with that error
Simon Glasse30965d2014-08-28 09:43:44 -06001193 warn_lines: A list of warnings for this commit, or [] if there is
1194 none, or we don't want to print errors
1195 warn_line_boards: Dict keyed by warning line, containing a list of
1196 the Board objects with that warning
Simon Glass843312d2015-02-05 22:06:15 -07001197 config: Dictionary keyed by filename - e.g. '.config'. Each
1198 value is itself a dictionary:
1199 key: config name
1200 value: config value
Alex Kiernan48ae4122018-05-31 04:48:34 +00001201 environment: Dictionary keyed by environment variable, Each
1202 value is the value of environment variable.
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001203 show_sizes: Show image size deltas
Simon Glassf9c094b2020-03-18 09:42:43 -06001204 show_detail: Show size delta detail for each board if show_sizes
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001205 show_bloat: Show detail for each function
Simon Glass843312d2015-02-05 22:06:15 -07001206 show_config: Show config changes
Alex Kiernan48ae4122018-05-31 04:48:34 +00001207 show_environment: Show environment changes
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001208 """
Simon Glasse30965d2014-08-28 09:43:44 -06001209 def _BoardList(line, line_boards):
Simon Glassed966652014-08-28 09:43:43 -06001210 """Helper function to get a line of boards containing a line
1211
1212 Args:
1213 line: Error line to search for
Simon Glass35d696d2020-04-09 15:08:36 -06001214 line_boards: boards to search, each a Board
Simon Glassed966652014-08-28 09:43:43 -06001215 Return:
Simon Glass35d696d2020-04-09 15:08:36 -06001216 List of boards with that error line, or [] if the user has not
1217 requested such a list
Simon Glassed966652014-08-28 09:43:43 -06001218 """
Simon Glasscc2c0d12022-07-11 19:04:00 -06001219 brds = []
Simon Glass35d696d2020-04-09 15:08:36 -06001220 board_set = set()
Simon Glassed966652014-08-28 09:43:43 -06001221 if self._list_error_boards:
Simon Glassf4ed4702022-07-11 19:03:57 -06001222 for brd in line_boards[line]:
1223 if not brd in board_set:
Simon Glasscc2c0d12022-07-11 19:04:00 -06001224 brds.append(brd)
Simon Glassf4ed4702022-07-11 19:03:57 -06001225 board_set.add(brd)
Simon Glasscc2c0d12022-07-11 19:04:00 -06001226 return brds
Simon Glassed966652014-08-28 09:43:43 -06001227
Simon Glasse30965d2014-08-28 09:43:44 -06001228 def _CalcErrorDelta(base_lines, base_line_boards, lines, line_boards,
1229 char):
Simon Glass35d696d2020-04-09 15:08:36 -06001230 """Calculate the required output based on changes in errors
1231
1232 Args:
1233 base_lines: List of errors/warnings for previous commit
1234 base_line_boards: Dict keyed by error line, containing a list
1235 of the Board objects with that error in the previous commit
1236 lines: List of errors/warning for this commit, each a str
1237 line_boards: Dict keyed by error line, containing a list
1238 of the Board objects with that error in this commit
1239 char: Character representing error ('') or warning ('w'). The
1240 broken ('+') or fixed ('-') characters are added in this
1241 function
1242
1243 Returns:
1244 Tuple
1245 List of ErrLine objects for 'better' lines
1246 List of ErrLine objects for 'worse' lines
1247 """
Simon Glasse30965d2014-08-28 09:43:44 -06001248 better_lines = []
1249 worse_lines = []
1250 for line in lines:
1251 if line not in base_lines:
Simon Glass35d696d2020-04-09 15:08:36 -06001252 errline = ErrLine(char + '+', _BoardList(line, line_boards),
1253 line)
1254 worse_lines.append(errline)
Simon Glasse30965d2014-08-28 09:43:44 -06001255 for line in base_lines:
1256 if line not in lines:
Simon Glass35d696d2020-04-09 15:08:36 -06001257 errline = ErrLine(char + '-',
1258 _BoardList(line, base_line_boards), line)
1259 better_lines.append(errline)
Simon Glasse30965d2014-08-28 09:43:44 -06001260 return better_lines, worse_lines
1261
Simon Glass843312d2015-02-05 22:06:15 -07001262 def _CalcConfig(delta, name, config):
1263 """Calculate configuration changes
1264
1265 Args:
1266 delta: Type of the delta, e.g. '+'
1267 name: name of the file which changed (e.g. .config)
1268 config: configuration change dictionary
1269 key: config name
1270 value: config value
1271 Returns:
1272 String containing the configuration changes which can be
1273 printed
1274 """
1275 out = ''
1276 for key in sorted(config.keys()):
1277 out += '%s=%s ' % (key, config[key])
Simon Glass8270e3c2015-08-25 21:52:14 -06001278 return '%s %s: %s' % (delta, name, out)
Simon Glass843312d2015-02-05 22:06:15 -07001279
Simon Glass8270e3c2015-08-25 21:52:14 -06001280 def _AddConfig(lines, name, config_plus, config_minus, config_change):
1281 """Add changes in configuration to a list
Simon Glass843312d2015-02-05 22:06:15 -07001282
1283 Args:
Simon Glass8270e3c2015-08-25 21:52:14 -06001284 lines: list to add to
1285 name: config file name
Simon Glass843312d2015-02-05 22:06:15 -07001286 config_plus: configurations added, dictionary
1287 key: config name
1288 value: config value
1289 config_minus: configurations removed, dictionary
1290 key: config name
1291 value: config value
1292 config_change: configurations changed, dictionary
1293 key: config name
1294 value: config value
1295 """
1296 if config_plus:
Simon Glass8270e3c2015-08-25 21:52:14 -06001297 lines.append(_CalcConfig('+', name, config_plus))
Simon Glass843312d2015-02-05 22:06:15 -07001298 if config_minus:
Simon Glass8270e3c2015-08-25 21:52:14 -06001299 lines.append(_CalcConfig('-', name, config_minus))
Simon Glass843312d2015-02-05 22:06:15 -07001300 if config_change:
Simon Glass8270e3c2015-08-25 21:52:14 -06001301 lines.append(_CalcConfig('c', name, config_change))
1302
1303 def _OutputConfigInfo(lines):
1304 for line in lines:
1305 if not line:
1306 continue
1307 if line[0] == '+':
1308 col = self.col.GREEN
1309 elif line[0] == '-':
1310 col = self.col.RED
1311 elif line[0] == 'c':
1312 col = self.col.YELLOW
Simon Glass098b10f2022-01-29 14:14:18 -07001313 tprint(' ' + line, newline=True, colour=col)
Simon Glass8270e3c2015-08-25 21:52:14 -06001314
Simon Glassb206d872020-04-09 15:08:28 -06001315 def _OutputErrLines(err_lines, colour):
1316 """Output the line of error/warning lines, if not empty
1317
1318 Also increments self._error_lines if err_lines not empty
1319
1320 Args:
Simon Glass35d696d2020-04-09 15:08:36 -06001321 err_lines: List of ErrLine objects, each an error or warning
1322 line, possibly including a list of boards with that
1323 error/warning
Simon Glassb206d872020-04-09 15:08:28 -06001324 colour: Colour to use for output
1325 """
1326 if err_lines:
Simon Glass8c9a2672020-04-09 15:08:37 -06001327 out_list = []
Simon Glass35d696d2020-04-09 15:08:36 -06001328 for line in err_lines:
Simon Glasscc2c0d12022-07-11 19:04:00 -06001329 names = [brd.target for brd in line.brds]
Simon Glass9ef0ceb2020-04-09 15:08:38 -06001330 board_str = ' '.join(names) if names else ''
Simon Glass8c9a2672020-04-09 15:08:37 -06001331 if board_str:
Simon Glass252ac582022-01-29 14:14:17 -07001332 out = self.col.build(colour, line.char + '(')
1333 out += self.col.build(self.col.MAGENTA, board_str,
Simon Glass8c9a2672020-04-09 15:08:37 -06001334 bright=False)
Simon Glass252ac582022-01-29 14:14:17 -07001335 out += self.col.build(colour, ') %s' % line.errline)
Simon Glass8c9a2672020-04-09 15:08:37 -06001336 else:
Simon Glass252ac582022-01-29 14:14:17 -07001337 out = self.col.build(colour, line.char + line.errline)
Simon Glass8c9a2672020-04-09 15:08:37 -06001338 out_list.append(out)
Simon Glass098b10f2022-01-29 14:14:18 -07001339 tprint('\n'.join(out_list))
Simon Glassb206d872020-04-09 15:08:28 -06001340 self._error_lines += 1
1341
Simon Glass843312d2015-02-05 22:06:15 -07001342
Simon Glass4cf2b222018-11-06 16:02:12 -07001343 ok_boards = [] # List of boards fixed since last commit
Simon Glass6af71012018-11-06 16:02:13 -07001344 warn_boards = [] # List of boards with warnings since last commit
Simon Glass4cf2b222018-11-06 16:02:12 -07001345 err_boards = [] # List of new broken boards since last commit
1346 new_boards = [] # List of boards that didn't exist last time
1347 unknown_boards = [] # List of boards that were not built
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001348
1349 for target in board_dict:
1350 if target not in board_selected:
1351 continue
1352
1353 # If the board was built last time, add its outcome to a list
1354 if target in self._base_board_dict:
1355 base_outcome = self._base_board_dict[target].rc
1356 outcome = board_dict[target]
1357 if outcome.rc == OUTCOME_UNKNOWN:
Simon Glass4cf2b222018-11-06 16:02:12 -07001358 unknown_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001359 elif outcome.rc < base_outcome:
Simon Glass6af71012018-11-06 16:02:13 -07001360 if outcome.rc == OUTCOME_WARNING:
1361 warn_boards.append(target)
1362 else:
1363 ok_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001364 elif outcome.rc > base_outcome:
Simon Glass6af71012018-11-06 16:02:13 -07001365 if outcome.rc == OUTCOME_WARNING:
1366 warn_boards.append(target)
1367 else:
1368 err_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001369 else:
Simon Glass4cf2b222018-11-06 16:02:12 -07001370 new_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001371
Simon Glassb206d872020-04-09 15:08:28 -06001372 # Get a list of errors and warnings that have appeared, and disappeared
Simon Glasse30965d2014-08-28 09:43:44 -06001373 better_err, worse_err = _CalcErrorDelta(self._base_err_lines,
1374 self._base_err_line_boards, err_lines, err_line_boards, '')
1375 better_warn, worse_warn = _CalcErrorDelta(self._base_warn_lines,
1376 self._base_warn_line_boards, warn_lines, warn_line_boards, 'w')
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001377
Simon Glassae1a09f2022-07-11 19:03:56 -06001378 # For the IDE mode, print out all the output
1379 if self._ide:
1380 outcome = board_dict[target]
1381 for line in outcome.err_lines:
1382 sys.stderr.write(line)
1383
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001384 # Display results by arch
Simon Glassae1a09f2022-07-11 19:03:56 -06001385 elif any((ok_boards, warn_boards, err_boards, unknown_boards, new_boards,
Simon Glass6af71012018-11-06 16:02:13 -07001386 worse_err, better_err, worse_warn, better_warn)):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001387 arch_list = {}
Simon Glass4cf2b222018-11-06 16:02:12 -07001388 self.AddOutcome(board_selected, arch_list, ok_boards, '',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001389 self.col.GREEN)
Simon Glass6af71012018-11-06 16:02:13 -07001390 self.AddOutcome(board_selected, arch_list, warn_boards, 'w+',
1391 self.col.YELLOW)
Simon Glass4cf2b222018-11-06 16:02:12 -07001392 self.AddOutcome(board_selected, arch_list, err_boards, '+',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001393 self.col.RED)
Simon Glass4cf2b222018-11-06 16:02:12 -07001394 self.AddOutcome(board_selected, arch_list, new_boards, '*', self.col.BLUE)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001395 if self._show_unknown:
Simon Glass4cf2b222018-11-06 16:02:12 -07001396 self.AddOutcome(board_selected, arch_list, unknown_boards, '?',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001397 self.col.MAGENTA)
Simon Glassc05aa032019-10-31 07:42:53 -06001398 for arch, target_list in arch_list.items():
Simon Glass098b10f2022-01-29 14:14:18 -07001399 tprint('%10s: %s' % (arch, target_list))
Simon Glass28370c12014-08-09 15:33:06 -06001400 self._error_lines += 1
Simon Glassb206d872020-04-09 15:08:28 -06001401 _OutputErrLines(better_err, colour=self.col.GREEN)
1402 _OutputErrLines(worse_err, colour=self.col.RED)
1403 _OutputErrLines(better_warn, colour=self.col.CYAN)
Simon Glass5627bd92020-04-09 15:08:35 -06001404 _OutputErrLines(worse_warn, colour=self.col.YELLOW)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001405
1406 if show_sizes:
1407 self.PrintSizeSummary(board_selected, board_dict, show_detail,
1408 show_bloat)
1409
Alex Kiernan48ae4122018-05-31 04:48:34 +00001410 if show_environment and self._base_environment:
1411 lines = []
1412
1413 for target in board_dict:
1414 if target not in board_selected:
1415 continue
1416
1417 tbase = self._base_environment[target]
1418 tenvironment = environment[target]
1419 environment_plus = {}
1420 environment_minus = {}
1421 environment_change = {}
1422 base = tbase.environment
Simon Glassc05aa032019-10-31 07:42:53 -06001423 for key, value in tenvironment.environment.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001424 if key not in base:
1425 environment_plus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001426 for key, value in base.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001427 if key not in tenvironment.environment:
1428 environment_minus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001429 for key, value in base.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001430 new_value = tenvironment.environment.get(key)
1431 if new_value and value != new_value:
1432 desc = '%s -> %s' % (value, new_value)
1433 environment_change[key] = desc
1434
1435 _AddConfig(lines, target, environment_plus, environment_minus,
1436 environment_change)
1437
1438 _OutputConfigInfo(lines)
1439
Simon Glass8270e3c2015-08-25 21:52:14 -06001440 if show_config and self._base_config:
1441 summary = {}
1442 arch_config_plus = {}
1443 arch_config_minus = {}
1444 arch_config_change = {}
1445 arch_list = []
1446
1447 for target in board_dict:
1448 if target not in board_selected:
Simon Glass843312d2015-02-05 22:06:15 -07001449 continue
Simon Glass8270e3c2015-08-25 21:52:14 -06001450 arch = board_selected[target].arch
1451 if arch not in arch_list:
1452 arch_list.append(arch)
1453
1454 for arch in arch_list:
1455 arch_config_plus[arch] = {}
1456 arch_config_minus[arch] = {}
1457 arch_config_change[arch] = {}
Simon Glassb464f8e2016-11-13 14:25:53 -07001458 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001459 arch_config_plus[arch][name] = {}
1460 arch_config_minus[arch][name] = {}
1461 arch_config_change[arch][name] = {}
1462
1463 for target in board_dict:
1464 if target not in board_selected:
1465 continue
1466
1467 arch = board_selected[target].arch
1468
1469 all_config_plus = {}
1470 all_config_minus = {}
1471 all_config_change = {}
1472 tbase = self._base_config[target]
1473 tconfig = config[target]
1474 lines = []
Simon Glassb464f8e2016-11-13 14:25:53 -07001475 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001476 if not tconfig.config[name]:
1477 continue
1478 config_plus = {}
1479 config_minus = {}
1480 config_change = {}
1481 base = tbase.config[name]
Simon Glassc05aa032019-10-31 07:42:53 -06001482 for key, value in tconfig.config[name].items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001483 if key not in base:
1484 config_plus[key] = value
1485 all_config_plus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001486 for key, value in base.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001487 if key not in tconfig.config[name]:
1488 config_minus[key] = value
1489 all_config_minus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001490 for key, value in base.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001491 new_value = tconfig.config.get(key)
1492 if new_value and value != new_value:
1493 desc = '%s -> %s' % (value, new_value)
1494 config_change[key] = desc
1495 all_config_change[key] = desc
1496
1497 arch_config_plus[arch][name].update(config_plus)
1498 arch_config_minus[arch][name].update(config_minus)
1499 arch_config_change[arch][name].update(config_change)
1500
1501 _AddConfig(lines, name, config_plus, config_minus,
1502 config_change)
1503 _AddConfig(lines, 'all', all_config_plus, all_config_minus,
1504 all_config_change)
1505 summary[target] = '\n'.join(lines)
1506
1507 lines_by_target = {}
Simon Glassc05aa032019-10-31 07:42:53 -06001508 for target, lines in summary.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001509 if lines in lines_by_target:
1510 lines_by_target[lines].append(target)
1511 else:
1512 lines_by_target[lines] = [target]
1513
1514 for arch in arch_list:
1515 lines = []
1516 all_plus = {}
1517 all_minus = {}
1518 all_change = {}
Simon Glassb464f8e2016-11-13 14:25:53 -07001519 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001520 all_plus.update(arch_config_plus[arch][name])
1521 all_minus.update(arch_config_minus[arch][name])
1522 all_change.update(arch_config_change[arch][name])
1523 _AddConfig(lines, name, arch_config_plus[arch][name],
1524 arch_config_minus[arch][name],
1525 arch_config_change[arch][name])
1526 _AddConfig(lines, 'all', all_plus, all_minus, all_change)
1527 #arch_summary[target] = '\n'.join(lines)
1528 if lines:
Simon Glass098b10f2022-01-29 14:14:18 -07001529 tprint('%s:' % arch)
Simon Glass8270e3c2015-08-25 21:52:14 -06001530 _OutputConfigInfo(lines)
1531
Simon Glassc05aa032019-10-31 07:42:53 -06001532 for lines, targets in lines_by_target.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001533 if not lines:
1534 continue
Simon Glass098b10f2022-01-29 14:14:18 -07001535 tprint('%s :' % ' '.join(sorted(targets)))
Simon Glass8270e3c2015-08-25 21:52:14 -06001536 _OutputConfigInfo(lines.split('\n'))
1537
Simon Glass843312d2015-02-05 22:06:15 -07001538
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001539 # Save our updated information for the next call to this function
1540 self._base_board_dict = board_dict
1541 self._base_err_lines = err_lines
Simon Glasse30965d2014-08-28 09:43:44 -06001542 self._base_warn_lines = warn_lines
1543 self._base_err_line_boards = err_line_boards
1544 self._base_warn_line_boards = warn_line_boards
Simon Glass843312d2015-02-05 22:06:15 -07001545 self._base_config = config
Alex Kiernan48ae4122018-05-31 04:48:34 +00001546 self._base_environment = environment
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001547
1548 # Get a list of boards that did not get built, if needed
1549 not_built = []
Simon Glassf4ed4702022-07-11 19:03:57 -06001550 for brd in board_selected:
1551 if not brd in board_dict:
1552 not_built.append(brd)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001553 if not_built:
Simon Glass098b10f2022-01-29 14:14:18 -07001554 tprint("Boards not built (%d): %s" % (len(not_built),
Simon Glass4653a882014-09-05 19:00:07 -06001555 ', '.join(not_built)))
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001556
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001557 def ProduceResultSummary(self, commit_upto, commits, board_selected):
Simon Glasse30965d2014-08-28 09:43:44 -06001558 (board_dict, err_lines, err_line_boards, warn_lines,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001559 warn_line_boards, config, environment) = self.GetResultSummary(
Simon Glassed966652014-08-28 09:43:43 -06001560 board_selected, commit_upto,
Simon Glass843312d2015-02-05 22:06:15 -07001561 read_func_sizes=self._show_bloat,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001562 read_config=self._show_config,
1563 read_environment=self._show_environment)
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001564 if commits:
1565 msg = '%02d: %s' % (commit_upto + 1,
1566 commits[commit_upto].subject)
Simon Glass098b10f2022-01-29 14:14:18 -07001567 tprint(msg, colour=self.col.BLUE)
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001568 self.PrintResultSummary(board_selected, board_dict,
Simon Glassed966652014-08-28 09:43:43 -06001569 err_lines if self._show_errors else [], err_line_boards,
Simon Glasse30965d2014-08-28 09:43:44 -06001570 warn_lines if self._show_errors else [], warn_line_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001571 config, environment, self._show_sizes, self._show_detail,
1572 self._show_bloat, self._show_config, self._show_environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001573
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001574 def ShowSummary(self, commits, board_selected):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001575 """Show a build summary for U-Boot for a given board list.
1576
1577 Reset the result summary, then repeatedly call GetResultSummary on
1578 each commit's results, then display the differences we see.
1579
1580 Args:
1581 commit: Commit objects to summarise
1582 board_selected: Dict containing boards to summarise
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001583 """
Simon Glassfea58582014-08-09 15:32:59 -06001584 self.commit_count = len(commits) if commits else 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001585 self.commits = commits
1586 self.ResetResultSummary(board_selected)
Simon Glass28370c12014-08-09 15:33:06 -06001587 self._error_lines = 0
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001588
1589 for commit_upto in range(0, self.commit_count, self._step):
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001590 self.ProduceResultSummary(commit_upto, commits, board_selected)
Simon Glass28370c12014-08-09 15:33:06 -06001591 if not self._error_lines:
Simon Glass098b10f2022-01-29 14:14:18 -07001592 tprint('(no errors to report)', colour=self.col.GREEN)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001593
1594
1595 def SetupBuild(self, board_selected, commits):
1596 """Set up ready to start a build.
1597
1598 Args:
1599 board_selected: Selected boards to build
1600 commits: Selected commits to build
1601 """
1602 # First work out how many commits we will build
Simon Glassc05aa032019-10-31 07:42:53 -06001603 count = (self.commit_count + self._step - 1) // self._step
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001604 self.count = len(board_selected) * count
1605 self.upto = self.warned = self.fail = 0
1606 self._timestamps = collections.deque()
1607
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001608 def GetThreadDir(self, thread_num):
1609 """Get the directory path to the working dir for a thread.
1610
1611 Args:
Simon Glassb82492b2021-01-30 22:17:46 -07001612 thread_num: Number of thread to check (-1 for main process, which
1613 is treated as 0)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001614 """
Simon Glassd829f122020-03-18 09:42:42 -06001615 if self.work_in_output:
1616 return self._working_dir
Simon Glassb82492b2021-01-30 22:17:46 -07001617 return os.path.join(self._working_dir, '%02d' % max(thread_num, 0))
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001618
Simon Glassfea58582014-08-09 15:32:59 -06001619 def _PrepareThread(self, thread_num, setup_git):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001620 """Prepare the working directory for a thread.
1621
1622 This clones or fetches the repo into the thread's work directory.
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001623 Optionally, it can create a linked working tree of the repo in the
1624 thread's work directory instead.
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001625
1626 Args:
1627 thread_num: Thread number (0, 1, ...)
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001628 setup_git:
1629 'clone' to set up a git clone
1630 'worktree' to set up a git worktree
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001631 """
1632 thread_dir = self.GetThreadDir(thread_num)
Simon Glass190064b2014-08-09 15:33:00 -06001633 builderthread.Mkdir(thread_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001634 git_dir = os.path.join(thread_dir, '.git')
1635
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001636 # Create a worktree or a git repo clone for this thread if it
1637 # doesn't already exist
Simon Glassfea58582014-08-09 15:32:59 -06001638 if setup_git and self.git_dir:
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001639 src_dir = os.path.abspath(self.git_dir)
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001640 if os.path.isdir(git_dir):
1641 # This is a clone of the src_dir repo, we can keep using
1642 # it but need to fetch from src_dir.
Simon Glass098b10f2022-01-29 14:14:18 -07001643 tprint('\rFetching repo for thread %d' % thread_num,
Simon Glass212c0b82020-04-09 15:08:43 -06001644 newline=False)
Simon Glass0157b182022-01-29 14:14:11 -07001645 gitutil.fetch(git_dir, thread_dir)
Simon Glass098b10f2022-01-29 14:14:18 -07001646 terminal.print_clear()
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001647 elif os.path.isfile(git_dir):
1648 # This is a worktree of the src_dir repo, we don't need to
1649 # create it again or update it in any way.
1650 pass
1651 elif os.path.exists(git_dir):
1652 # Don't know what could trigger this, but we probably
1653 # can't create a git worktree/clone here.
1654 raise ValueError('Git dir %s exists, but is not a file '
1655 'or a directory.' % git_dir)
1656 elif setup_git == 'worktree':
Simon Glass098b10f2022-01-29 14:14:18 -07001657 tprint('\rChecking out worktree for thread %d' % thread_num,
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001658 newline=False)
Simon Glass0157b182022-01-29 14:14:11 -07001659 gitutil.add_worktree(src_dir, thread_dir)
Simon Glass098b10f2022-01-29 14:14:18 -07001660 terminal.print_clear()
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001661 elif setup_git == 'clone' or setup_git == True:
Simon Glass098b10f2022-01-29 14:14:18 -07001662 tprint('\rCloning repo for thread %d' % thread_num,
Simon Glass21f0eb32016-09-18 16:48:31 -06001663 newline=False)
Simon Glass0157b182022-01-29 14:14:11 -07001664 gitutil.clone(src_dir, thread_dir)
Simon Glass098b10f2022-01-29 14:14:18 -07001665 terminal.print_clear()
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001666 else:
1667 raise ValueError("Can't setup git repo with %s." % setup_git)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001668
Simon Glassfea58582014-08-09 15:32:59 -06001669 def _PrepareWorkingSpace(self, max_threads, setup_git):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001670 """Prepare the working directory for use.
1671
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001672 Set up the git repo for each thread. Creates a linked working tree
1673 if git-worktree is available, or clones the repo if it isn't.
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001674
1675 Args:
Simon Glassb82492b2021-01-30 22:17:46 -07001676 max_threads: Maximum number of threads we expect to need. If 0 then
1677 1 is set up, since the main process still needs somewhere to
1678 work
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001679 setup_git: True to set up a git worktree or a git clone
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001680 """
Simon Glass190064b2014-08-09 15:33:00 -06001681 builderthread.Mkdir(self._working_dir)
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001682 if setup_git and self.git_dir:
1683 src_dir = os.path.abspath(self.git_dir)
Simon Glass0157b182022-01-29 14:14:11 -07001684 if gitutil.check_worktree_is_available(src_dir):
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001685 setup_git = 'worktree'
1686 # If we previously added a worktree but the directory for it
1687 # got deleted, we need to prune its files from the repo so
1688 # that we can check out another in its place.
Simon Glass0157b182022-01-29 14:14:11 -07001689 gitutil.prune_worktrees(src_dir)
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001690 else:
1691 setup_git = 'clone'
Simon Glassb82492b2021-01-30 22:17:46 -07001692
1693 # Always do at least one thread
1694 for thread in range(max(max_threads, 1)):
Simon Glassfea58582014-08-09 15:32:59 -06001695 self._PrepareThread(thread, setup_git)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001696
Simon Glass925f6ad2020-03-18 09:42:45 -06001697 def _GetOutputSpaceRemovals(self):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001698 """Get the output directories ready to receive files.
1699
Simon Glass925f6ad2020-03-18 09:42:45 -06001700 Figure out what needs to be deleted in the output directory before it
1701 can be used. We only delete old buildman directories which have the
1702 expected name pattern. See _GetOutputDir().
1703
1704 Returns:
1705 List of full paths of directories to remove
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001706 """
Simon Glass1a915672014-12-01 17:33:53 -07001707 if not self.commits:
1708 return
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001709 dir_list = []
1710 for commit_upto in range(self.commit_count):
1711 dir_list.append(self._GetOutputDir(commit_upto))
1712
Simon Glassb222abe2016-09-18 16:48:32 -06001713 to_remove = []
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001714 for dirname in glob.glob(os.path.join(self.base_dir, '*')):
1715 if dirname not in dir_list:
Simon Glass925f6ad2020-03-18 09:42:45 -06001716 leaf = dirname[len(self.base_dir) + 1:]
Ovidiu Panait7664b032020-05-15 09:30:12 +03001717 m = re.match('[0-9]+_g[0-9a-f]+_.*', leaf)
Simon Glass925f6ad2020-03-18 09:42:45 -06001718 if m:
1719 to_remove.append(dirname)
1720 return to_remove
1721
1722 def _PrepareOutputSpace(self):
1723 """Get the output directories ready to receive files.
1724
1725 We delete any output directories which look like ones we need to
1726 create. Having left over directories is confusing when the user wants
1727 to check the output manually.
1728 """
1729 to_remove = self._GetOutputSpaceRemovals()
Simon Glassb222abe2016-09-18 16:48:32 -06001730 if to_remove:
Simon Glass098b10f2022-01-29 14:14:18 -07001731 tprint('Removing %d old build directories...' % len(to_remove),
Simon Glassb222abe2016-09-18 16:48:32 -06001732 newline=False)
1733 for dirname in to_remove:
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001734 shutil.rmtree(dirname)
Simon Glass098b10f2022-01-29 14:14:18 -07001735 terminal.print_clear()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001736
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001737 def BuildBoards(self, commits, board_selected, keep_outputs, verbose):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001738 """Build all commits for a list of boards
1739
1740 Args:
1741 commits: List of commits to be build, each a Commit object
1742 boards_selected: Dict of selected boards, key is target name,
1743 value is Board object
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001744 keep_outputs: True to save build output files
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001745 verbose: Display build results as they are completed
Simon Glass2c3deb92014-08-28 09:43:39 -06001746 Returns:
1747 Tuple containing:
1748 - number of boards that failed to build
1749 - number of boards that issued warnings
Simon Glass8116c782021-04-11 16:27:27 +12001750 - list of thread exceptions raised
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001751 """
Simon Glassfea58582014-08-09 15:32:59 -06001752 self.commit_count = len(commits) if commits else 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001753 self.commits = commits
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001754 self._verbose = verbose
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001755
1756 self.ResetResultSummary(board_selected)
Thierry Redingf3d015c2014-08-19 10:22:39 +02001757 builderthread.Mkdir(self.base_dir, parents = True)
Simon Glassfea58582014-08-09 15:32:59 -06001758 self._PrepareWorkingSpace(min(self.num_threads, len(board_selected)),
1759 commits is not None)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001760 self._PrepareOutputSpace()
Simon Glassae1a09f2022-07-11 19:03:56 -06001761 if not self._ide:
1762 tprint('\rStarting build...', newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001763 self.SetupBuild(board_selected, commits)
1764 self.ProcessResult(None)
Simon Glass8116c782021-04-11 16:27:27 +12001765 self.thread_exceptions = []
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001766 # Create jobs to build all commits for each board
Simon Glassc05aa032019-10-31 07:42:53 -06001767 for brd in board_selected.values():
Simon Glass190064b2014-08-09 15:33:00 -06001768 job = builderthread.BuilderJob()
Simon Glassf4ed4702022-07-11 19:03:57 -06001769 job.brd = brd
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001770 job.commits = commits
1771 job.keep_outputs = keep_outputs
Simon Glassd829f122020-03-18 09:42:42 -06001772 job.work_in_output = self.work_in_output
Simon Glass2b4806e2022-01-22 05:07:33 -07001773 job.adjust_cfg = self.adjust_cfg
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001774 job.step = self._step
Simon Glassb82492b2021-01-30 22:17:46 -07001775 if self.num_threads:
1776 self.queue.put(job)
1777 else:
Simon Glassac053352022-02-11 13:23:19 -07001778 self._single_builder.RunJob(job)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001779
Simon Glassb82492b2021-01-30 22:17:46 -07001780 if self.num_threads:
1781 term = threading.Thread(target=self.queue.join)
1782 term.setDaemon(True)
1783 term.start()
1784 while term.is_alive():
1785 term.join(100)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001786
Simon Glassb82492b2021-01-30 22:17:46 -07001787 # Wait until we have processed all output
1788 self.out_queue.join()
Simon Glassae1a09f2022-07-11 19:03:56 -06001789 if not self._ide:
1790 tprint()
Simon Glass7b33f212020-04-09 15:08:47 -06001791
Simon Glassae1a09f2022-07-11 19:03:56 -06001792 msg = 'Completed: %d total built' % self.count
1793 if self.already_done:
1794 msg += ' (%d previously' % self.already_done
1795 if self.already_done != self.count:
1796 msg += ', %d newly' % (self.count - self.already_done)
1797 msg += ')'
1798 duration = datetime.now() - self._start_time
1799 if duration > timedelta(microseconds=1000000):
1800 if duration.microseconds >= 500000:
1801 duration = duration + timedelta(seconds=1)
1802 duration = duration - timedelta(microseconds=duration.microseconds)
1803 rate = float(self.count) / duration.total_seconds()
1804 msg += ', duration %s, rate %1.2f' % (duration, rate)
1805 tprint(msg)
1806 if self.thread_exceptions:
1807 tprint('Failed: %d thread exceptions' % len(self.thread_exceptions),
1808 colour=self.col.RED)
Simon Glass7b33f212020-04-09 15:08:47 -06001809
Simon Glass8116c782021-04-11 16:27:27 +12001810 return (self.fail, self.warned, self.thread_exceptions)