blob: c2a69027f885b74a82d19644fbf0b59f07eebef9 [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,
Tom Rinid7713ad2022-11-09 19:14:53 -0700255 test_thread_exceptions=False, adjust_cfg=None,
256 allow_missing=False):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000257 """Create a new Builder object
258
259 Args:
260 toolchains: Toolchains object to use for building
261 base_dir: Base directory to use for builder
262 git_dir: Git directory containing source repository
263 num_threads: Number of builder threads to run
264 num_jobs: Number of jobs to run at once (passed to make as -j)
Masahiro Yamada99796922014-07-22 11:19:09 +0900265 gnu_make: the command name of GNU Make.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000266 checkout: True to check out source, False to skip that step.
267 This is used for testing.
268 show_unknown: Show unknown boards (those not built) in summary
269 step: 1 to process every commit, n to process every nth commit
Simon Glassbb1501f2014-12-01 17:34:00 -0700270 no_subdirs: Don't create subdirectories when building current
271 source for a single board
272 full_path: Return the full path in CROSS_COMPILE and don't set
273 PATH
Simon Glassd2ce6582014-12-01 17:34:07 -0700274 verbose_build: Run build with V=1 and don't use 'make -s'
Simon Glasseb70a2c2020-04-09 15:08:51 -0600275 mrproper: Always run 'make mrproper' when configuring
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600276 per_board_out_dir: Build in a separate persistent directory per
277 board rather than a thread-specific directory
Simon Glassb50113f2016-11-13 14:25:51 -0700278 config_only: Only configure each build, don't build it
Simon Glassb464f8e2016-11-13 14:25:53 -0700279 squash_config_y: Convert CONFIG options with the value 'y' to '1'
Daniel Schwierzeck2371d1b2018-01-26 16:31:05 +0100280 warnings_as_errors: Treat all compiler warnings as errors
Simon Glassd829f122020-03-18 09:42:42 -0600281 work_in_output: Use the output directory as the work directory and
282 don't write to a separate output directory.
Simon Glass8116c782021-04-11 16:27:27 +1200283 test_thread_exceptions: Uses for tests only, True to make the
284 threads raise an exception instead of reporting their result.
285 This simulates a failure in the code somewhere
Simon Glass2b4806e2022-01-22 05:07:33 -0700286 adjust_cfg_list (list of str): List of changes to make to .config
287 file before building. Each is one of (where C is the config
288 option with or without the CONFIG_ prefix)
289
290 C to enable C
291 ~C to disable C
292 C=val to set the value of C (val must have quotes if C is
293 a string Kconfig
Tom Rinid7713ad2022-11-09 19:14:53 -0700294 allow_missing: Run build with BINMAN_ALLOW_MISSING=1
Simon Glass2b4806e2022-01-22 05:07:33 -0700295
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000296 """
297 self.toolchains = toolchains
298 self.base_dir = base_dir
Simon Glassd829f122020-03-18 09:42:42 -0600299 if work_in_output:
300 self._working_dir = base_dir
301 else:
302 self._working_dir = os.path.join(base_dir, '.bm-work')
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000303 self.threads = []
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000304 self.do_make = self.Make
Masahiro Yamada99796922014-07-22 11:19:09 +0900305 self.gnu_make = gnu_make
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000306 self.checkout = checkout
307 self.num_threads = num_threads
308 self.num_jobs = num_jobs
309 self.already_done = 0
310 self.force_build = False
311 self.git_dir = git_dir
312 self._show_unknown = show_unknown
313 self._timestamp_count = 10
314 self._build_period_us = None
315 self._complete_delay = None
316 self._next_delay_update = datetime.now()
Simon Glass7b33f212020-04-09 15:08:47 -0600317 self._start_time = datetime.now()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000318 self.force_config_on_failure = True
Simon Glass4266dc22014-07-13 12:22:31 -0600319 self.force_build_failures = False
Simon Glass97e91522014-07-14 17:51:02 -0600320 self.force_reconfig = False
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000321 self._step = step
Simon Glass189a4962014-07-14 17:51:03 -0600322 self.in_tree = False
Simon Glass28370c12014-08-09 15:33:06 -0600323 self._error_lines = 0
Simon Glass5971ab52014-12-01 17:33:55 -0700324 self.no_subdirs = no_subdirs
Simon Glassbb1501f2014-12-01 17:34:00 -0700325 self.full_path = full_path
Simon Glassd2ce6582014-12-01 17:34:07 -0700326 self.verbose_build = verbose_build
Simon Glassb50113f2016-11-13 14:25:51 -0700327 self.config_only = config_only
Simon Glassb464f8e2016-11-13 14:25:53 -0700328 self.squash_config_y = squash_config_y
329 self.config_filenames = BASE_CONFIG_FILENAMES
Simon Glassd829f122020-03-18 09:42:42 -0600330 self.work_in_output = work_in_output
Simon Glass2b4806e2022-01-22 05:07:33 -0700331 self.adjust_cfg = adjust_cfg
Tom Rinid7713ad2022-11-09 19:14:53 -0700332 self.allow_missing = allow_missing
Simon Glassae1a09f2022-07-11 19:03:56 -0600333 self._ide = False
Simon Glass2b4806e2022-01-22 05:07:33 -0700334
Simon Glassb464f8e2016-11-13 14:25:53 -0700335 if not self.squash_config_y:
336 self.config_filenames += EXTRA_CONFIG_FILENAMES
Simon Glass7bf83a52021-10-19 21:43:24 -0600337 self._terminated = False
338 self._restarting_config = False
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000339
Daniel Schwierzeck2371d1b2018-01-26 16:31:05 +0100340 self.warnings_as_errors = warnings_as_errors
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000341 self.col = terminal.Color()
342
Simon Glasse30965d2014-08-28 09:43:44 -0600343 self._re_function = re.compile('(.*): In function.*')
344 self._re_files = re.compile('In file included from.*')
345 self._re_warning = re.compile('(.*):(\d*):(\d*): warning: .*')
Simon Glass2d483332018-11-06 16:02:11 -0700346 self._re_dtb_warning = re.compile('(.*): Warning .*')
Simon Glasse30965d2014-08-28 09:43:44 -0600347 self._re_note = re.compile('(.*):(\d*):(\d*): note: this is the location of the previous.*')
Simon Glass113a8a52020-04-09 15:08:53 -0600348 self._re_migration_warning = re.compile(r'^={21} WARNING ={22}\n.*\n=+\n',
349 re.MULTILINE | re.DOTALL)
Simon Glasse30965d2014-08-28 09:43:44 -0600350
Simon Glass8116c782021-04-11 16:27:27 +1200351 self.thread_exceptions = []
352 self.test_thread_exceptions = test_thread_exceptions
Simon Glassb82492b2021-01-30 22:17:46 -0700353 if self.num_threads:
354 self._single_builder = None
355 self.queue = queue.Queue()
356 self.out_queue = queue.Queue()
357 for i in range(self.num_threads):
Simon Glass8116c782021-04-11 16:27:27 +1200358 t = builderthread.BuilderThread(
359 self, i, mrproper, per_board_out_dir,
360 test_exception=test_thread_exceptions)
Simon Glassb82492b2021-01-30 22:17:46 -0700361 t.setDaemon(True)
362 t.start()
363 self.threads.append(t)
364
365 t = builderthread.ResultThread(self)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000366 t.setDaemon(True)
367 t.start()
368 self.threads.append(t)
Simon Glassb82492b2021-01-30 22:17:46 -0700369 else:
370 self._single_builder = builderthread.BuilderThread(
371 self, -1, mrproper, per_board_out_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000372
373 ignore_lines = ['(make.*Waiting for unfinished)', '(Segmentation fault)']
374 self.re_make_err = re.compile('|'.join(ignore_lines))
375
Simon Glass2f256642016-09-18 16:48:37 -0600376 # Handle existing graceful with SIGINT / Ctrl-C
377 signal.signal(signal.SIGINT, self.signal_handler)
378
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000379 def __del__(self):
380 """Get rid of all threads created by the builder"""
381 for t in self.threads:
382 del t
383
Simon Glass2f256642016-09-18 16:48:37 -0600384 def signal_handler(self, signal, frame):
385 sys.exit(1)
386
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600387 def SetDisplayOptions(self, show_errors=False, show_sizes=False,
Simon Glassed966652014-08-28 09:43:43 -0600388 show_detail=False, show_bloat=False,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000389 list_error_boards=False, show_config=False,
Simon Glass113a8a52020-04-09 15:08:53 -0600390 show_environment=False, filter_dtb_warnings=False,
Simon Glassae1a09f2022-07-11 19:03:56 -0600391 filter_migration_warnings=False, ide=False):
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600392 """Setup display options for the builder.
393
Simon Glass174592b2020-04-09 15:08:52 -0600394 Args:
395 show_errors: True to show summarised error/warning info
396 show_sizes: Show size deltas
397 show_detail: Show size delta detail for each board if show_sizes
398 show_bloat: Show detail for each function
399 list_error_boards: Show the boards which caused each error/warning
400 show_config: Show config deltas
401 show_environment: Show environment deltas
402 filter_dtb_warnings: Filter out any warnings from the device-tree
403 compiler
Simon Glass113a8a52020-04-09 15:08:53 -0600404 filter_migration_warnings: Filter out any warnings about migrating
405 a board to driver model
Simon Glassae1a09f2022-07-11 19:03:56 -0600406 ide: Create output that can be parsed by an IDE. There is no '+' prefix on
407 error lines and output on stderr stays on stderr.
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600408 """
409 self._show_errors = show_errors
410 self._show_sizes = show_sizes
411 self._show_detail = show_detail
412 self._show_bloat = show_bloat
Simon Glassed966652014-08-28 09:43:43 -0600413 self._list_error_boards = list_error_boards
Simon Glass843312d2015-02-05 22:06:15 -0700414 self._show_config = show_config
Alex Kiernan48ae4122018-05-31 04:48:34 +0000415 self._show_environment = show_environment
Simon Glass174592b2020-04-09 15:08:52 -0600416 self._filter_dtb_warnings = filter_dtb_warnings
Simon Glass113a8a52020-04-09 15:08:53 -0600417 self._filter_migration_warnings = filter_migration_warnings
Simon Glassae1a09f2022-07-11 19:03:56 -0600418 self._ide = ide
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600419
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000420 def _AddTimestamp(self):
421 """Add a new timestamp to the list and record the build period.
422
423 The build period is the length of time taken to perform a single
424 build (one board, one commit).
425 """
426 now = datetime.now()
427 self._timestamps.append(now)
428 count = len(self._timestamps)
429 delta = self._timestamps[-1] - self._timestamps[0]
430 seconds = delta.total_seconds()
431
432 # If we have enough data, estimate build period (time taken for a
433 # single build) and therefore completion time.
434 if count > 1 and self._next_delay_update < now:
435 self._next_delay_update = now + timedelta(seconds=2)
436 if seconds > 0:
437 self._build_period = float(seconds) / count
438 todo = self.count - self.upto
439 self._complete_delay = timedelta(microseconds=
440 self._build_period * todo * 1000000)
441 # Round it
442 self._complete_delay -= timedelta(
443 microseconds=self._complete_delay.microseconds)
444
445 if seconds > 60:
446 self._timestamps.popleft()
447 count -= 1
448
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000449 def SelectCommit(self, commit, checkout=True):
450 """Checkout the selected commit for this build
451 """
452 self.commit = commit
453 if checkout and self.checkout:
Simon Glass0157b182022-01-29 14:14:11 -0700454 gitutil.checkout(commit.hash)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000455
456 def Make(self, commit, brd, stage, cwd, *args, **kwargs):
457 """Run make
458
459 Args:
460 commit: Commit object that is being built
461 brd: Board object that is being built
Roger Meierfd18a892014-08-20 22:10:29 +0200462 stage: Stage that we are at (mrproper, config, build)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000463 cwd: Directory where make should be run
464 args: Arguments to pass to make
Simon Glassd9800692022-01-29 14:14:05 -0700465 kwargs: Arguments to pass to command.run_pipe()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000466 """
Simon Glass7bf83a52021-10-19 21:43:24 -0600467
468 def check_output(stream, data):
469 if b'Restart config' in data:
470 self._restarting_config = True
471
472 # If we see 'Restart config' following by multiple errors
473 if self._restarting_config:
474 m = RE_NO_DEFAULT.findall(data)
475
476 # Number of occurences of each Kconfig item
477 multiple = [m.count(val) for val in set(m)]
478
479 # If any of them occur more than once, we have a loop
480 if [val for val in multiple if val > 1]:
481 self._terminated = True
482 return True
483 return False
484
485 self._restarting_config = False
486 self._terminated = False
Masahiro Yamada99796922014-07-22 11:19:09 +0900487 cmd = [self.gnu_make] + list(args)
Simon Glassd9800692022-01-29 14:14:05 -0700488 result = command.run_pipe([cmd], capture=True, capture_stderr=True,
Simon Glass7bf83a52021-10-19 21:43:24 -0600489 cwd=cwd, raise_on_error=False, infile='/dev/null',
490 output_func=check_output, **kwargs)
491
492 if self._terminated:
493 # Try to be helpful
494 result.stderr += '(** did you define an int/hex Kconfig with no default? **)'
495
Simon Glass40f11fc2015-02-05 22:06:12 -0700496 if self.verbose_build:
497 result.stdout = '%s\n' % (' '.join(cmd)) + result.stdout
498 result.combined = '%s\n' % (' '.join(cmd)) + result.combined
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000499 return result
500
501 def ProcessResult(self, result):
502 """Process the result of a build, showing progress information
503
504 Args:
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600505 result: A CommandResult object, which indicates the result for
506 a single build
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000507 """
508 col = terminal.Color()
509 if result:
510 target = result.brd.target
511
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000512 self.upto += 1
513 if result.return_code != 0:
514 self.fail += 1
515 elif result.stderr:
516 self.warned += 1
517 if result.already_done:
518 self.already_done += 1
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600519 if self._verbose:
Simon Glass098b10f2022-01-29 14:14:18 -0700520 terminal.print_clear()
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600521 boards_selected = {target : result.brd}
522 self.ResetResultSummary(boards_selected)
523 self.ProduceResultSummary(result.commit_upto, self.commits,
524 boards_selected)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000525 else:
526 target = '(starting)'
527
528 # Display separate counts for ok, warned and fail
529 ok = self.upto - self.warned - self.fail
Simon Glass252ac582022-01-29 14:14:17 -0700530 line = '\r' + self.col.build(self.col.GREEN, '%5d' % ok)
531 line += self.col.build(self.col.YELLOW, '%5d' % self.warned)
532 line += self.col.build(self.col.RED, '%5d' % self.fail)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000533
Simon Glass6eb76ca2020-04-09 15:08:45 -0600534 line += ' /%-5d ' % self.count
535 remaining = self.count - self.upto
536 if remaining:
Simon Glass252ac582022-01-29 14:14:17 -0700537 line += self.col.build(self.col.MAGENTA, ' -%-5d ' % remaining)
Simon Glass6eb76ca2020-04-09 15:08:45 -0600538 else:
539 line += ' ' * 8
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000540
541 # Add our current completion time estimate
542 self._AddTimestamp()
543 if self._complete_delay:
Simon Glass6eb76ca2020-04-09 15:08:45 -0600544 line += '%s : ' % self._complete_delay
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000545
Simon Glass6eb76ca2020-04-09 15:08:45 -0600546 line += target
Simon Glassae1a09f2022-07-11 19:03:56 -0600547 if not self._ide:
548 terminal.print_clear()
549 tprint(line, newline=False, limit_to_line=True)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000550
551 def _GetOutputDir(self, commit_upto):
552 """Get the name of the output directory for a commit number
553
554 The output directory is typically .../<branch>/<commit>.
555
556 Args:
557 commit_upto: Commit number to use (0..self.count-1)
558 """
Simon Glass60b285f2020-04-17 17:51:34 -0600559 if self.work_in_output:
560 return self._working_dir
561
Simon Glass5971ab52014-12-01 17:33:55 -0700562 commit_dir = None
Simon Glassfea58582014-08-09 15:32:59 -0600563 if self.commits:
564 commit = self.commits[commit_upto]
565 subject = commit.subject.translate(trans_valid_chars)
Simon Glass925f6ad2020-03-18 09:42:45 -0600566 # See _GetOutputSpaceRemovals() which parses this name
Ovidiu Panait7664b032020-05-15 09:30:12 +0300567 commit_dir = ('%02d_g%s_%s' % (commit_upto + 1,
568 commit.hash, subject[:20]))
Simon Glass5971ab52014-12-01 17:33:55 -0700569 elif not self.no_subdirs:
Simon Glassfea58582014-08-09 15:32:59 -0600570 commit_dir = 'current'
Simon Glass5971ab52014-12-01 17:33:55 -0700571 if not commit_dir:
572 return self.base_dir
573 return os.path.join(self.base_dir, commit_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000574
575 def GetBuildDir(self, commit_upto, target):
576 """Get the name of the build directory for a commit number
577
578 The build directory is typically .../<branch>/<commit>/<target>.
579
580 Args:
581 commit_upto: Commit number to use (0..self.count-1)
582 target: Target name
583 """
584 output_dir = self._GetOutputDir(commit_upto)
Simon Glass60b285f2020-04-17 17:51:34 -0600585 if self.work_in_output:
586 return output_dir
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000587 return os.path.join(output_dir, target)
588
589 def GetDoneFile(self, commit_upto, target):
590 """Get the name of the done file for a commit number
591
592 Args:
593 commit_upto: Commit number to use (0..self.count-1)
594 target: Target name
595 """
596 return os.path.join(self.GetBuildDir(commit_upto, target), 'done')
597
598 def GetSizesFile(self, commit_upto, target):
599 """Get the name of the sizes file for a commit number
600
601 Args:
602 commit_upto: Commit number to use (0..self.count-1)
603 target: Target name
604 """
605 return os.path.join(self.GetBuildDir(commit_upto, target), 'sizes')
606
607 def GetFuncSizesFile(self, commit_upto, target, elf_fname):
608 """Get the name of the funcsizes file for a commit number and ELF file
609
610 Args:
611 commit_upto: Commit number to use (0..self.count-1)
612 target: Target name
613 elf_fname: Filename of elf image
614 """
615 return os.path.join(self.GetBuildDir(commit_upto, target),
616 '%s.sizes' % elf_fname.replace('/', '-'))
617
618 def GetObjdumpFile(self, commit_upto, target, elf_fname):
619 """Get the name of the objdump file for a commit number and ELF file
620
621 Args:
622 commit_upto: Commit number to use (0..self.count-1)
623 target: Target name
624 elf_fname: Filename of elf image
625 """
626 return os.path.join(self.GetBuildDir(commit_upto, target),
627 '%s.objdump' % elf_fname.replace('/', '-'))
628
629 def GetErrFile(self, commit_upto, target):
630 """Get the name of the err file for a commit number
631
632 Args:
633 commit_upto: Commit number to use (0..self.count-1)
634 target: Target name
635 """
636 output_dir = self.GetBuildDir(commit_upto, target)
637 return os.path.join(output_dir, 'err')
638
639 def FilterErrors(self, lines):
640 """Filter out errors in which we have no interest
641
642 We should probably use map().
643
644 Args:
645 lines: List of error lines, each a string
646 Returns:
647 New list with only interesting lines included
648 """
649 out_lines = []
Simon Glass113a8a52020-04-09 15:08:53 -0600650 if self._filter_migration_warnings:
651 text = '\n'.join(lines)
652 text = self._re_migration_warning.sub('', text)
653 lines = text.splitlines()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000654 for line in lines:
Simon Glass174592b2020-04-09 15:08:52 -0600655 if self.re_make_err.search(line):
656 continue
657 if self._filter_dtb_warnings and self._re_dtb_warning.search(line):
658 continue
659 out_lines.append(line)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000660 return out_lines
661
662 def ReadFuncSizes(self, fname, fd):
663 """Read function sizes from the output of 'nm'
664
665 Args:
666 fd: File containing data to read
667 fname: Filename we are reading from (just for errors)
668
669 Returns:
670 Dictionary containing size of each function in bytes, indexed by
671 function name.
672 """
673 sym = {}
674 for line in fd.readlines():
Simon Glassf2e67752022-07-11 19:04:11 -0600675 line = line.strip()
676 parts = line.split()
677 if line and len(parts) == 3:
678 size, type, name = line.split()
679 if type in 'tTdDbB':
680 # function names begin with '.' on 64-bit powerpc
681 if '.' in name[1:]:
682 name = 'static.' + name.split('.')[0]
683 sym[name] = sym.get(name, 0) + int(size, 16)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000684 return sym
685
Simon Glass843312d2015-02-05 22:06:15 -0700686 def _ProcessConfig(self, fname):
687 """Read in a .config, autoconf.mk or autoconf.h file
688
689 This function handles all config file types. It ignores comments and
690 any #defines which don't start with CONFIG_.
691
692 Args:
693 fname: Filename to read
694
695 Returns:
696 Dictionary:
697 key: Config name (e.g. CONFIG_DM)
698 value: Config value (e.g. 1)
699 """
700 config = {}
701 if os.path.exists(fname):
702 with open(fname) as fd:
703 for line in fd:
704 line = line.strip()
705 if line.startswith('#define'):
706 values = line[8:].split(' ', 1)
707 if len(values) > 1:
708 key, value = values
709 else:
710 key = values[0]
Simon Glassb464f8e2016-11-13 14:25:53 -0700711 value = '1' if self.squash_config_y else ''
Simon Glass843312d2015-02-05 22:06:15 -0700712 if not key.startswith('CONFIG_'):
713 continue
714 elif not line or line[0] in ['#', '*', '/']:
715 continue
716 else:
717 key, value = line.split('=', 1)
Simon Glassb464f8e2016-11-13 14:25:53 -0700718 if self.squash_config_y and value == 'y':
719 value = '1'
Simon Glass843312d2015-02-05 22:06:15 -0700720 config[key] = value
721 return config
722
Alex Kiernan48ae4122018-05-31 04:48:34 +0000723 def _ProcessEnvironment(self, fname):
724 """Read in a uboot.env file
725
726 This function reads in environment variables from a file.
727
728 Args:
729 fname: Filename to read
730
731 Returns:
732 Dictionary:
733 key: environment variable (e.g. bootlimit)
734 value: value of environment variable (e.g. 1)
735 """
736 environment = {}
737 if os.path.exists(fname):
738 with open(fname) as fd:
739 for line in fd.read().split('\0'):
740 try:
741 key, value = line.split('=', 1)
742 environment[key] = value
743 except ValueError:
744 # ignore lines we can't parse
745 pass
746 return environment
747
Simon Glass843312d2015-02-05 22:06:15 -0700748 def GetBuildOutcome(self, commit_upto, target, read_func_sizes,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000749 read_config, read_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000750 """Work out the outcome of a build.
751
752 Args:
753 commit_upto: Commit number to check (0..n-1)
754 target: Target board to check
755 read_func_sizes: True to read function size information
Simon Glass843312d2015-02-05 22:06:15 -0700756 read_config: True to read .config and autoconf.h files
Alex Kiernan48ae4122018-05-31 04:48:34 +0000757 read_environment: True to read uboot.env files
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000758
759 Returns:
760 Outcome object
761 """
762 done_file = self.GetDoneFile(commit_upto, target)
763 sizes_file = self.GetSizesFile(commit_upto, target)
764 sizes = {}
765 func_sizes = {}
Simon Glass843312d2015-02-05 22:06:15 -0700766 config = {}
Alex Kiernan48ae4122018-05-31 04:48:34 +0000767 environment = {}
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000768 if os.path.exists(done_file):
769 with open(done_file, 'r') as fd:
Simon Glass347ea0b2019-04-26 19:02:23 -0600770 try:
771 return_code = int(fd.readline())
772 except ValueError:
773 # The file may be empty due to running out of disk space.
774 # Try a rebuild
775 return_code = 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000776 err_lines = []
777 err_file = self.GetErrFile(commit_upto, target)
778 if os.path.exists(err_file):
779 with open(err_file, 'r') as fd:
780 err_lines = self.FilterErrors(fd.readlines())
781
782 # Decide whether the build was ok, failed or created warnings
783 if return_code:
784 rc = OUTCOME_ERROR
785 elif len(err_lines):
786 rc = OUTCOME_WARNING
787 else:
788 rc = OUTCOME_OK
789
790 # Convert size information to our simple format
791 if os.path.exists(sizes_file):
792 with open(sizes_file, 'r') as fd:
793 for line in fd.readlines():
794 values = line.split()
795 rodata = 0
796 if len(values) > 6:
797 rodata = int(values[6], 16)
798 size_dict = {
799 'all' : int(values[0]) + int(values[1]) +
800 int(values[2]),
801 'text' : int(values[0]) - rodata,
802 'data' : int(values[1]),
803 'bss' : int(values[2]),
804 'rodata' : rodata,
805 }
806 sizes[values[5]] = size_dict
807
808 if read_func_sizes:
809 pattern = self.GetFuncSizesFile(commit_upto, target, '*')
810 for fname in glob.glob(pattern):
811 with open(fname, 'r') as fd:
812 dict_name = os.path.basename(fname).replace('.sizes',
813 '')
814 func_sizes[dict_name] = self.ReadFuncSizes(fname, fd)
815
Simon Glass843312d2015-02-05 22:06:15 -0700816 if read_config:
817 output_dir = self.GetBuildDir(commit_upto, target)
Simon Glassb464f8e2016-11-13 14:25:53 -0700818 for name in self.config_filenames:
Simon Glass843312d2015-02-05 22:06:15 -0700819 fname = os.path.join(output_dir, name)
820 config[name] = self._ProcessConfig(fname)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000821
Alex Kiernan48ae4122018-05-31 04:48:34 +0000822 if read_environment:
823 output_dir = self.GetBuildDir(commit_upto, target)
824 fname = os.path.join(output_dir, 'uboot.env')
825 environment = self._ProcessEnvironment(fname)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000826
Alex Kiernan48ae4122018-05-31 04:48:34 +0000827 return Builder.Outcome(rc, err_lines, sizes, func_sizes, config,
828 environment)
829
830 return Builder.Outcome(OUTCOME_UNKNOWN, [], {}, {}, {}, {})
Simon Glass843312d2015-02-05 22:06:15 -0700831
832 def GetResultSummary(self, boards_selected, commit_upto, read_func_sizes,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000833 read_config, read_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000834 """Calculate a summary of the results of building a commit.
835
836 Args:
837 board_selected: Dict containing boards to summarise
838 commit_upto: Commit number to summarize (0..self.count-1)
839 read_func_sizes: True to read function size information
Simon Glass843312d2015-02-05 22:06:15 -0700840 read_config: True to read .config and autoconf.h files
Alex Kiernan48ae4122018-05-31 04:48:34 +0000841 read_environment: True to read uboot.env files
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000842
843 Returns:
844 Tuple:
Simon Glassae1a09f2022-07-11 19:03:56 -0600845 Dict containing boards which built this commit:
846 key: board.target
847 value: Builder.Outcome object
Simon Glasse30965d2014-08-28 09:43:44 -0600848 List containing a summary of error lines
Simon Glassed966652014-08-28 09:43:43 -0600849 Dict keyed by error line, containing a list of the Board
850 objects with that error
Simon Glasse30965d2014-08-28 09:43:44 -0600851 List containing a summary of warning lines
852 Dict keyed by error line, containing a list of the Board
853 objects with that warning
Simon Glass8270e3c2015-08-25 21:52:14 -0600854 Dictionary keyed by board.target. Each value is a dictionary:
855 key: filename - e.g. '.config'
Simon Glass843312d2015-02-05 22:06:15 -0700856 value is itself a dictionary:
857 key: config name
858 value: config value
Alex Kiernan48ae4122018-05-31 04:48:34 +0000859 Dictionary keyed by board.target. Each value is a dictionary:
860 key: environment variable
861 value: value of environment variable
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000862 """
Simon Glasse30965d2014-08-28 09:43:44 -0600863 def AddLine(lines_summary, lines_boards, line, board):
864 line = line.rstrip()
865 if line in lines_boards:
866 lines_boards[line].append(board)
867 else:
868 lines_boards[line] = [board]
869 lines_summary.append(line)
870
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000871 board_dict = {}
872 err_lines_summary = []
Simon Glassed966652014-08-28 09:43:43 -0600873 err_lines_boards = {}
Simon Glasse30965d2014-08-28 09:43:44 -0600874 warn_lines_summary = []
875 warn_lines_boards = {}
Simon Glass843312d2015-02-05 22:06:15 -0700876 config = {}
Alex Kiernan48ae4122018-05-31 04:48:34 +0000877 environment = {}
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000878
Simon Glassf4ed4702022-07-11 19:03:57 -0600879 for brd in boards_selected.values():
880 outcome = self.GetBuildOutcome(commit_upto, brd.target,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000881 read_func_sizes, read_config,
882 read_environment)
Simon Glassf4ed4702022-07-11 19:03:57 -0600883 board_dict[brd.target] = outcome
Simon Glasse30965d2014-08-28 09:43:44 -0600884 last_func = None
885 last_was_warning = False
886 for line in outcome.err_lines:
887 if line:
888 if (self._re_function.match(line) or
889 self._re_files.match(line)):
890 last_func = line
Simon Glassed966652014-08-28 09:43:43 -0600891 else:
Simon Glass2d483332018-11-06 16:02:11 -0700892 is_warning = (self._re_warning.match(line) or
893 self._re_dtb_warning.match(line))
Simon Glasse30965d2014-08-28 09:43:44 -0600894 is_note = self._re_note.match(line)
895 if is_warning or (last_was_warning and is_note):
896 if last_func:
897 AddLine(warn_lines_summary, warn_lines_boards,
Simon Glassf4ed4702022-07-11 19:03:57 -0600898 last_func, brd)
Simon Glasse30965d2014-08-28 09:43:44 -0600899 AddLine(warn_lines_summary, warn_lines_boards,
Simon Glassf4ed4702022-07-11 19:03:57 -0600900 line, brd)
Simon Glasse30965d2014-08-28 09:43:44 -0600901 else:
902 if last_func:
903 AddLine(err_lines_summary, err_lines_boards,
Simon Glassf4ed4702022-07-11 19:03:57 -0600904 last_func, brd)
Simon Glasse30965d2014-08-28 09:43:44 -0600905 AddLine(err_lines_summary, err_lines_boards,
Simon Glassf4ed4702022-07-11 19:03:57 -0600906 line, brd)
Simon Glasse30965d2014-08-28 09:43:44 -0600907 last_was_warning = is_warning
908 last_func = None
Simon Glassf4ed4702022-07-11 19:03:57 -0600909 tconfig = Config(self.config_filenames, brd.target)
Simon Glassb464f8e2016-11-13 14:25:53 -0700910 for fname in self.config_filenames:
Simon Glass843312d2015-02-05 22:06:15 -0700911 if outcome.config:
Simon Glassc05aa032019-10-31 07:42:53 -0600912 for key, value in outcome.config[fname].items():
Simon Glass8270e3c2015-08-25 21:52:14 -0600913 tconfig.Add(fname, key, value)
Simon Glassf4ed4702022-07-11 19:03:57 -0600914 config[brd.target] = tconfig
Simon Glass843312d2015-02-05 22:06:15 -0700915
Simon Glassf4ed4702022-07-11 19:03:57 -0600916 tenvironment = Environment(brd.target)
Alex Kiernan48ae4122018-05-31 04:48:34 +0000917 if outcome.environment:
Simon Glassc05aa032019-10-31 07:42:53 -0600918 for key, value in outcome.environment.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +0000919 tenvironment.Add(key, value)
Simon Glassf4ed4702022-07-11 19:03:57 -0600920 environment[brd.target] = tenvironment
Alex Kiernan48ae4122018-05-31 04:48:34 +0000921
Simon Glasse30965d2014-08-28 09:43:44 -0600922 return (board_dict, err_lines_summary, err_lines_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000923 warn_lines_summary, warn_lines_boards, config, environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000924
925 def AddOutcome(self, board_dict, arch_list, changes, char, color):
926 """Add an output to our list of outcomes for each architecture
927
928 This simple function adds failing boards (changes) to the
929 relevant architecture string, so we can print the results out
930 sorted by architecture.
931
932 Args:
933 board_dict: Dict containing all boards
934 arch_list: Dict keyed by arch name. Value is a string containing
935 a list of board names which failed for that arch.
936 changes: List of boards to add to arch_list
937 color: terminal.Colour object
938 """
939 done_arch = {}
940 for target in changes:
941 if target in board_dict:
942 arch = board_dict[target].arch
943 else:
944 arch = 'unknown'
Simon Glass252ac582022-01-29 14:14:17 -0700945 str = self.col.build(color, ' ' + target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000946 if not arch in done_arch:
Simon Glass252ac582022-01-29 14:14:17 -0700947 str = ' %s %s' % (self.col.build(color, char), str)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000948 done_arch[arch] = True
949 if not arch in arch_list:
950 arch_list[arch] = str
951 else:
952 arch_list[arch] += str
953
954
955 def ColourNum(self, num):
956 color = self.col.RED if num > 0 else self.col.GREEN
957 if num == 0:
958 return '0'
Simon Glass252ac582022-01-29 14:14:17 -0700959 return self.col.build(color, str(num))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000960
961 def ResetResultSummary(self, board_selected):
962 """Reset the results summary ready for use.
963
964 Set up the base board list to be all those selected, and set the
965 error lines to empty.
966
967 Following this, calls to PrintResultSummary() will use this
968 information to work out what has changed.
969
970 Args:
971 board_selected: Dict containing boards to summarise, keyed by
972 board.target
973 """
974 self._base_board_dict = {}
Simon Glassf4ed4702022-07-11 19:03:57 -0600975 for brd in board_selected:
976 self._base_board_dict[brd] = Builder.Outcome(0, [], [], {}, {}, {})
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000977 self._base_err_lines = []
Simon Glasse30965d2014-08-28 09:43:44 -0600978 self._base_warn_lines = []
979 self._base_err_line_boards = {}
980 self._base_warn_line_boards = {}
Simon Glass8270e3c2015-08-25 21:52:14 -0600981 self._base_config = None
Alex Kiernan48ae4122018-05-31 04:48:34 +0000982 self._base_environment = None
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000983
984 def PrintFuncSizeDetail(self, fname, old, new):
985 grow, shrink, add, remove, up, down = 0, 0, 0, 0, 0, 0
986 delta, common = [], {}
987
988 for a in old:
989 if a in new:
990 common[a] = 1
991
992 for name in old:
993 if name not in common:
994 remove += 1
995 down += old[name]
996 delta.append([-old[name], name])
997
998 for name in new:
999 if name not in common:
1000 add += 1
1001 up += new[name]
1002 delta.append([new[name], name])
1003
1004 for name in common:
1005 diff = new.get(name, 0) - old.get(name, 0)
1006 if diff > 0:
1007 grow, up = grow + 1, up + diff
1008 elif diff < 0:
1009 shrink, down = shrink + 1, down - diff
1010 delta.append([diff, name])
1011
1012 delta.sort()
1013 delta.reverse()
1014
1015 args = [add, -remove, grow, -shrink, up, -down, up - down]
Tom Rinid5686a62017-05-22 13:48:52 -04001016 if max(args) == 0 and min(args) == 0:
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001017 return
1018 args = [self.ColourNum(x) for x in args]
1019 indent = ' ' * 15
Simon Glass098b10f2022-01-29 14:14:18 -07001020 tprint('%s%s: add: %s/%s, grow: %s/%s bytes: %s/%s (%s)' %
Simon Glass252ac582022-01-29 14:14:17 -07001021 tuple([indent, self.col.build(self.col.YELLOW, fname)] + args))
Simon Glass098b10f2022-01-29 14:14:18 -07001022 tprint('%s %-38s %7s %7s %+7s' % (indent, 'function', 'old', 'new',
Simon Glass4653a882014-09-05 19:00:07 -06001023 'delta'))
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001024 for diff, name in delta:
1025 if diff:
1026 color = self.col.RED if diff > 0 else self.col.GREEN
1027 msg = '%s %-38s %7s %7s %+7d' % (indent, name,
1028 old.get(name, '-'), new.get(name,'-'), diff)
Simon Glass098b10f2022-01-29 14:14:18 -07001029 tprint(msg, colour=color)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001030
1031
1032 def PrintSizeDetail(self, target_list, show_bloat):
1033 """Show details size information for each board
1034
1035 Args:
1036 target_list: List of targets, each a dict containing:
1037 'target': Target name
1038 'total_diff': Total difference in bytes across all areas
1039 <part_name>: Difference for that part
1040 show_bloat: Show detail for each function
1041 """
1042 targets_by_diff = sorted(target_list, reverse=True,
1043 key=lambda x: x['_total_diff'])
1044 for result in targets_by_diff:
1045 printed_target = False
1046 for name in sorted(result):
1047 diff = result[name]
1048 if name.startswith('_'):
1049 continue
1050 if diff != 0:
1051 color = self.col.RED if diff > 0 else self.col.GREEN
1052 msg = ' %s %+d' % (name, diff)
1053 if not printed_target:
Simon Glass098b10f2022-01-29 14:14:18 -07001054 tprint('%10s %-15s:' % ('', result['_target']),
Simon Glass4653a882014-09-05 19:00:07 -06001055 newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001056 printed_target = True
Simon Glass098b10f2022-01-29 14:14:18 -07001057 tprint(msg, colour=color, newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001058 if printed_target:
Simon Glass098b10f2022-01-29 14:14:18 -07001059 tprint()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001060 if show_bloat:
1061 target = result['_target']
1062 outcome = result['_outcome']
1063 base_outcome = self._base_board_dict[target]
1064 for fname in outcome.func_sizes:
1065 self.PrintFuncSizeDetail(fname,
1066 base_outcome.func_sizes[fname],
1067 outcome.func_sizes[fname])
1068
1069
1070 def PrintSizeSummary(self, board_selected, board_dict, show_detail,
1071 show_bloat):
1072 """Print a summary of image sizes broken down by section.
1073
1074 The summary takes the form of one line per architecture. The
1075 line contains deltas for each of the sections (+ means the section
Flavio Suligoi9de5c392020-01-29 09:56:05 +01001076 got bigger, - means smaller). The numbers are the average number
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001077 of bytes that a board in this section increased by.
1078
1079 For example:
1080 powerpc: (622 boards) text -0.0
1081 arm: (285 boards) text -0.0
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001082
1083 Args:
1084 board_selected: Dict containing boards to summarise, keyed by
1085 board.target
1086 board_dict: Dict containing boards for which we built this
1087 commit, keyed by board.target. The value is an Outcome object.
Simon Glassf9c094b2020-03-18 09:42:43 -06001088 show_detail: Show size delta detail for each board
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001089 show_bloat: Show detail for each function
1090 """
1091 arch_list = {}
1092 arch_count = {}
1093
1094 # Calculate changes in size for different image parts
1095 # The previous sizes are in Board.sizes, for each board
1096 for target in board_dict:
1097 if target not in board_selected:
1098 continue
1099 base_sizes = self._base_board_dict[target].sizes
1100 outcome = board_dict[target]
1101 sizes = outcome.sizes
1102
1103 # Loop through the list of images, creating a dict of size
1104 # changes for each image/part. We end up with something like
1105 # {'target' : 'snapper9g45, 'data' : 5, 'u-boot-spl:text' : -4}
1106 # which means that U-Boot data increased by 5 bytes and SPL
1107 # text decreased by 4.
1108 err = {'_target' : target}
1109 for image in sizes:
1110 if image in base_sizes:
1111 base_image = base_sizes[image]
1112 # Loop through the text, data, bss parts
1113 for part in sorted(sizes[image]):
1114 diff = sizes[image][part] - base_image[part]
1115 col = None
1116 if diff:
1117 if image == 'u-boot':
1118 name = part
1119 else:
1120 name = image + ':' + part
1121 err[name] = diff
1122 arch = board_selected[target].arch
1123 if not arch in arch_count:
1124 arch_count[arch] = 1
1125 else:
1126 arch_count[arch] += 1
1127 if not sizes:
1128 pass # Only add to our list when we have some stats
1129 elif not arch in arch_list:
1130 arch_list[arch] = [err]
1131 else:
1132 arch_list[arch].append(err)
1133
1134 # We now have a list of image size changes sorted by arch
1135 # Print out a summary of these
Simon Glassc05aa032019-10-31 07:42:53 -06001136 for arch, target_list in arch_list.items():
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001137 # Get total difference for each type
1138 totals = {}
1139 for result in target_list:
1140 total = 0
Simon Glassc05aa032019-10-31 07:42:53 -06001141 for name, diff in result.items():
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001142 if name.startswith('_'):
1143 continue
1144 total += diff
1145 if name in totals:
1146 totals[name] += diff
1147 else:
1148 totals[name] = diff
1149 result['_total_diff'] = total
1150 result['_outcome'] = board_dict[result['_target']]
1151
1152 count = len(target_list)
1153 printed_arch = False
1154 for name in sorted(totals):
1155 diff = totals[name]
1156 if diff:
1157 # Display the average difference in this name for this
1158 # architecture
1159 avg_diff = float(diff) / count
1160 color = self.col.RED if avg_diff > 0 else self.col.GREEN
1161 msg = ' %s %+1.1f' % (name, avg_diff)
1162 if not printed_arch:
Simon Glass098b10f2022-01-29 14:14:18 -07001163 tprint('%10s: (for %d/%d boards)' % (arch, count,
Simon Glass4653a882014-09-05 19:00:07 -06001164 arch_count[arch]), newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001165 printed_arch = True
Simon Glass098b10f2022-01-29 14:14:18 -07001166 tprint(msg, colour=color, newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001167
1168 if printed_arch:
Simon Glass098b10f2022-01-29 14:14:18 -07001169 tprint()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001170 if show_detail:
1171 self.PrintSizeDetail(target_list, show_bloat)
1172
1173
1174 def PrintResultSummary(self, board_selected, board_dict, err_lines,
Simon Glasse30965d2014-08-28 09:43:44 -06001175 err_line_boards, warn_lines, warn_line_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001176 config, environment, show_sizes, show_detail,
1177 show_bloat, show_config, show_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001178 """Compare results with the base results and display delta.
1179
1180 Only boards mentioned in board_selected will be considered. This
1181 function is intended to be called repeatedly with the results of
1182 each commit. It therefore shows a 'diff' between what it saw in
1183 the last call and what it sees now.
1184
1185 Args:
1186 board_selected: Dict containing boards to summarise, keyed by
1187 board.target
1188 board_dict: Dict containing boards for which we built this
1189 commit, keyed by board.target. The value is an Outcome object.
1190 err_lines: A list of errors for this commit, or [] if there is
1191 none, or we don't want to print errors
Simon Glassed966652014-08-28 09:43:43 -06001192 err_line_boards: Dict keyed by error line, containing a list of
1193 the Board objects with that error
Simon Glasse30965d2014-08-28 09:43:44 -06001194 warn_lines: A list of warnings for this commit, or [] if there is
1195 none, or we don't want to print errors
1196 warn_line_boards: Dict keyed by warning line, containing a list of
1197 the Board objects with that warning
Simon Glass843312d2015-02-05 22:06:15 -07001198 config: Dictionary keyed by filename - e.g. '.config'. Each
1199 value is itself a dictionary:
1200 key: config name
1201 value: config value
Alex Kiernan48ae4122018-05-31 04:48:34 +00001202 environment: Dictionary keyed by environment variable, Each
1203 value is the value of environment variable.
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001204 show_sizes: Show image size deltas
Simon Glassf9c094b2020-03-18 09:42:43 -06001205 show_detail: Show size delta detail for each board if show_sizes
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001206 show_bloat: Show detail for each function
Simon Glass843312d2015-02-05 22:06:15 -07001207 show_config: Show config changes
Alex Kiernan48ae4122018-05-31 04:48:34 +00001208 show_environment: Show environment changes
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001209 """
Simon Glasse30965d2014-08-28 09:43:44 -06001210 def _BoardList(line, line_boards):
Simon Glassed966652014-08-28 09:43:43 -06001211 """Helper function to get a line of boards containing a line
1212
1213 Args:
1214 line: Error line to search for
Simon Glass35d696d2020-04-09 15:08:36 -06001215 line_boards: boards to search, each a Board
Simon Glassed966652014-08-28 09:43:43 -06001216 Return:
Simon Glass35d696d2020-04-09 15:08:36 -06001217 List of boards with that error line, or [] if the user has not
1218 requested such a list
Simon Glassed966652014-08-28 09:43:43 -06001219 """
Simon Glasscc2c0d12022-07-11 19:04:00 -06001220 brds = []
Simon Glass35d696d2020-04-09 15:08:36 -06001221 board_set = set()
Simon Glassed966652014-08-28 09:43:43 -06001222 if self._list_error_boards:
Simon Glassf4ed4702022-07-11 19:03:57 -06001223 for brd in line_boards[line]:
1224 if not brd in board_set:
Simon Glasscc2c0d12022-07-11 19:04:00 -06001225 brds.append(brd)
Simon Glassf4ed4702022-07-11 19:03:57 -06001226 board_set.add(brd)
Simon Glasscc2c0d12022-07-11 19:04:00 -06001227 return brds
Simon Glassed966652014-08-28 09:43:43 -06001228
Simon Glasse30965d2014-08-28 09:43:44 -06001229 def _CalcErrorDelta(base_lines, base_line_boards, lines, line_boards,
1230 char):
Simon Glass35d696d2020-04-09 15:08:36 -06001231 """Calculate the required output based on changes in errors
1232
1233 Args:
1234 base_lines: List of errors/warnings for previous commit
1235 base_line_boards: Dict keyed by error line, containing a list
1236 of the Board objects with that error in the previous commit
1237 lines: List of errors/warning for this commit, each a str
1238 line_boards: Dict keyed by error line, containing a list
1239 of the Board objects with that error in this commit
1240 char: Character representing error ('') or warning ('w'). The
1241 broken ('+') or fixed ('-') characters are added in this
1242 function
1243
1244 Returns:
1245 Tuple
1246 List of ErrLine objects for 'better' lines
1247 List of ErrLine objects for 'worse' lines
1248 """
Simon Glasse30965d2014-08-28 09:43:44 -06001249 better_lines = []
1250 worse_lines = []
1251 for line in lines:
1252 if line not in base_lines:
Simon Glass35d696d2020-04-09 15:08:36 -06001253 errline = ErrLine(char + '+', _BoardList(line, line_boards),
1254 line)
1255 worse_lines.append(errline)
Simon Glasse30965d2014-08-28 09:43:44 -06001256 for line in base_lines:
1257 if line not in lines:
Simon Glass35d696d2020-04-09 15:08:36 -06001258 errline = ErrLine(char + '-',
1259 _BoardList(line, base_line_boards), line)
1260 better_lines.append(errline)
Simon Glasse30965d2014-08-28 09:43:44 -06001261 return better_lines, worse_lines
1262
Simon Glass843312d2015-02-05 22:06:15 -07001263 def _CalcConfig(delta, name, config):
1264 """Calculate configuration changes
1265
1266 Args:
1267 delta: Type of the delta, e.g. '+'
1268 name: name of the file which changed (e.g. .config)
1269 config: configuration change dictionary
1270 key: config name
1271 value: config value
1272 Returns:
1273 String containing the configuration changes which can be
1274 printed
1275 """
1276 out = ''
1277 for key in sorted(config.keys()):
1278 out += '%s=%s ' % (key, config[key])
Simon Glass8270e3c2015-08-25 21:52:14 -06001279 return '%s %s: %s' % (delta, name, out)
Simon Glass843312d2015-02-05 22:06:15 -07001280
Simon Glass8270e3c2015-08-25 21:52:14 -06001281 def _AddConfig(lines, name, config_plus, config_minus, config_change):
1282 """Add changes in configuration to a list
Simon Glass843312d2015-02-05 22:06:15 -07001283
1284 Args:
Simon Glass8270e3c2015-08-25 21:52:14 -06001285 lines: list to add to
1286 name: config file name
Simon Glass843312d2015-02-05 22:06:15 -07001287 config_plus: configurations added, dictionary
1288 key: config name
1289 value: config value
1290 config_minus: configurations removed, dictionary
1291 key: config name
1292 value: config value
1293 config_change: configurations changed, dictionary
1294 key: config name
1295 value: config value
1296 """
1297 if config_plus:
Simon Glass8270e3c2015-08-25 21:52:14 -06001298 lines.append(_CalcConfig('+', name, config_plus))
Simon Glass843312d2015-02-05 22:06:15 -07001299 if config_minus:
Simon Glass8270e3c2015-08-25 21:52:14 -06001300 lines.append(_CalcConfig('-', name, config_minus))
Simon Glass843312d2015-02-05 22:06:15 -07001301 if config_change:
Simon Glass8270e3c2015-08-25 21:52:14 -06001302 lines.append(_CalcConfig('c', name, config_change))
1303
1304 def _OutputConfigInfo(lines):
1305 for line in lines:
1306 if not line:
1307 continue
1308 if line[0] == '+':
1309 col = self.col.GREEN
1310 elif line[0] == '-':
1311 col = self.col.RED
1312 elif line[0] == 'c':
1313 col = self.col.YELLOW
Simon Glass098b10f2022-01-29 14:14:18 -07001314 tprint(' ' + line, newline=True, colour=col)
Simon Glass8270e3c2015-08-25 21:52:14 -06001315
Simon Glassb206d872020-04-09 15:08:28 -06001316 def _OutputErrLines(err_lines, colour):
1317 """Output the line of error/warning lines, if not empty
1318
1319 Also increments self._error_lines if err_lines not empty
1320
1321 Args:
Simon Glass35d696d2020-04-09 15:08:36 -06001322 err_lines: List of ErrLine objects, each an error or warning
1323 line, possibly including a list of boards with that
1324 error/warning
Simon Glassb206d872020-04-09 15:08:28 -06001325 colour: Colour to use for output
1326 """
1327 if err_lines:
Simon Glass8c9a2672020-04-09 15:08:37 -06001328 out_list = []
Simon Glass35d696d2020-04-09 15:08:36 -06001329 for line in err_lines:
Simon Glasscc2c0d12022-07-11 19:04:00 -06001330 names = [brd.target for brd in line.brds]
Simon Glass9ef0ceb2020-04-09 15:08:38 -06001331 board_str = ' '.join(names) if names else ''
Simon Glass8c9a2672020-04-09 15:08:37 -06001332 if board_str:
Simon Glass252ac582022-01-29 14:14:17 -07001333 out = self.col.build(colour, line.char + '(')
1334 out += self.col.build(self.col.MAGENTA, board_str,
Simon Glass8c9a2672020-04-09 15:08:37 -06001335 bright=False)
Simon Glass252ac582022-01-29 14:14:17 -07001336 out += self.col.build(colour, ') %s' % line.errline)
Simon Glass8c9a2672020-04-09 15:08:37 -06001337 else:
Simon Glass252ac582022-01-29 14:14:17 -07001338 out = self.col.build(colour, line.char + line.errline)
Simon Glass8c9a2672020-04-09 15:08:37 -06001339 out_list.append(out)
Simon Glass098b10f2022-01-29 14:14:18 -07001340 tprint('\n'.join(out_list))
Simon Glassb206d872020-04-09 15:08:28 -06001341 self._error_lines += 1
1342
Simon Glass843312d2015-02-05 22:06:15 -07001343
Simon Glass4cf2b222018-11-06 16:02:12 -07001344 ok_boards = [] # List of boards fixed since last commit
Simon Glass6af71012018-11-06 16:02:13 -07001345 warn_boards = [] # List of boards with warnings since last commit
Simon Glass4cf2b222018-11-06 16:02:12 -07001346 err_boards = [] # List of new broken boards since last commit
1347 new_boards = [] # List of boards that didn't exist last time
1348 unknown_boards = [] # List of boards that were not built
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001349
1350 for target in board_dict:
1351 if target not in board_selected:
1352 continue
1353
1354 # If the board was built last time, add its outcome to a list
1355 if target in self._base_board_dict:
1356 base_outcome = self._base_board_dict[target].rc
1357 outcome = board_dict[target]
1358 if outcome.rc == OUTCOME_UNKNOWN:
Simon Glass4cf2b222018-11-06 16:02:12 -07001359 unknown_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001360 elif outcome.rc < base_outcome:
Simon Glass6af71012018-11-06 16:02:13 -07001361 if outcome.rc == OUTCOME_WARNING:
1362 warn_boards.append(target)
1363 else:
1364 ok_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001365 elif outcome.rc > base_outcome:
Simon Glass6af71012018-11-06 16:02:13 -07001366 if outcome.rc == OUTCOME_WARNING:
1367 warn_boards.append(target)
1368 else:
1369 err_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001370 else:
Simon Glass4cf2b222018-11-06 16:02:12 -07001371 new_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001372
Simon Glassb206d872020-04-09 15:08:28 -06001373 # Get a list of errors and warnings that have appeared, and disappeared
Simon Glasse30965d2014-08-28 09:43:44 -06001374 better_err, worse_err = _CalcErrorDelta(self._base_err_lines,
1375 self._base_err_line_boards, err_lines, err_line_boards, '')
1376 better_warn, worse_warn = _CalcErrorDelta(self._base_warn_lines,
1377 self._base_warn_line_boards, warn_lines, warn_line_boards, 'w')
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001378
Simon Glassae1a09f2022-07-11 19:03:56 -06001379 # For the IDE mode, print out all the output
1380 if self._ide:
1381 outcome = board_dict[target]
1382 for line in outcome.err_lines:
1383 sys.stderr.write(line)
1384
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001385 # Display results by arch
Simon Glassae1a09f2022-07-11 19:03:56 -06001386 elif any((ok_boards, warn_boards, err_boards, unknown_boards, new_boards,
Simon Glass6af71012018-11-06 16:02:13 -07001387 worse_err, better_err, worse_warn, better_warn)):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001388 arch_list = {}
Simon Glass4cf2b222018-11-06 16:02:12 -07001389 self.AddOutcome(board_selected, arch_list, ok_boards, '',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001390 self.col.GREEN)
Simon Glass6af71012018-11-06 16:02:13 -07001391 self.AddOutcome(board_selected, arch_list, warn_boards, 'w+',
1392 self.col.YELLOW)
Simon Glass4cf2b222018-11-06 16:02:12 -07001393 self.AddOutcome(board_selected, arch_list, err_boards, '+',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001394 self.col.RED)
Simon Glass4cf2b222018-11-06 16:02:12 -07001395 self.AddOutcome(board_selected, arch_list, new_boards, '*', self.col.BLUE)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001396 if self._show_unknown:
Simon Glass4cf2b222018-11-06 16:02:12 -07001397 self.AddOutcome(board_selected, arch_list, unknown_boards, '?',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001398 self.col.MAGENTA)
Simon Glassc05aa032019-10-31 07:42:53 -06001399 for arch, target_list in arch_list.items():
Simon Glass098b10f2022-01-29 14:14:18 -07001400 tprint('%10s: %s' % (arch, target_list))
Simon Glass28370c12014-08-09 15:33:06 -06001401 self._error_lines += 1
Simon Glassb206d872020-04-09 15:08:28 -06001402 _OutputErrLines(better_err, colour=self.col.GREEN)
1403 _OutputErrLines(worse_err, colour=self.col.RED)
1404 _OutputErrLines(better_warn, colour=self.col.CYAN)
Simon Glass5627bd92020-04-09 15:08:35 -06001405 _OutputErrLines(worse_warn, colour=self.col.YELLOW)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001406
1407 if show_sizes:
1408 self.PrintSizeSummary(board_selected, board_dict, show_detail,
1409 show_bloat)
1410
Alex Kiernan48ae4122018-05-31 04:48:34 +00001411 if show_environment and self._base_environment:
1412 lines = []
1413
1414 for target in board_dict:
1415 if target not in board_selected:
1416 continue
1417
1418 tbase = self._base_environment[target]
1419 tenvironment = environment[target]
1420 environment_plus = {}
1421 environment_minus = {}
1422 environment_change = {}
1423 base = tbase.environment
Simon Glassc05aa032019-10-31 07:42:53 -06001424 for key, value in tenvironment.environment.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001425 if key not in base:
1426 environment_plus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001427 for key, value in base.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001428 if key not in tenvironment.environment:
1429 environment_minus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001430 for key, value in base.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001431 new_value = tenvironment.environment.get(key)
1432 if new_value and value != new_value:
1433 desc = '%s -> %s' % (value, new_value)
1434 environment_change[key] = desc
1435
1436 _AddConfig(lines, target, environment_plus, environment_minus,
1437 environment_change)
1438
1439 _OutputConfigInfo(lines)
1440
Simon Glass8270e3c2015-08-25 21:52:14 -06001441 if show_config and self._base_config:
1442 summary = {}
1443 arch_config_plus = {}
1444 arch_config_minus = {}
1445 arch_config_change = {}
1446 arch_list = []
1447
1448 for target in board_dict:
1449 if target not in board_selected:
Simon Glass843312d2015-02-05 22:06:15 -07001450 continue
Simon Glass8270e3c2015-08-25 21:52:14 -06001451 arch = board_selected[target].arch
1452 if arch not in arch_list:
1453 arch_list.append(arch)
1454
1455 for arch in arch_list:
1456 arch_config_plus[arch] = {}
1457 arch_config_minus[arch] = {}
1458 arch_config_change[arch] = {}
Simon Glassb464f8e2016-11-13 14:25:53 -07001459 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001460 arch_config_plus[arch][name] = {}
1461 arch_config_minus[arch][name] = {}
1462 arch_config_change[arch][name] = {}
1463
1464 for target in board_dict:
1465 if target not in board_selected:
1466 continue
1467
1468 arch = board_selected[target].arch
1469
1470 all_config_plus = {}
1471 all_config_minus = {}
1472 all_config_change = {}
1473 tbase = self._base_config[target]
1474 tconfig = config[target]
1475 lines = []
Simon Glassb464f8e2016-11-13 14:25:53 -07001476 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001477 if not tconfig.config[name]:
1478 continue
1479 config_plus = {}
1480 config_minus = {}
1481 config_change = {}
1482 base = tbase.config[name]
Simon Glassc05aa032019-10-31 07:42:53 -06001483 for key, value in tconfig.config[name].items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001484 if key not in base:
1485 config_plus[key] = value
1486 all_config_plus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001487 for key, value in base.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001488 if key not in tconfig.config[name]:
1489 config_minus[key] = value
1490 all_config_minus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001491 for key, value in base.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001492 new_value = tconfig.config.get(key)
1493 if new_value and value != new_value:
1494 desc = '%s -> %s' % (value, new_value)
1495 config_change[key] = desc
1496 all_config_change[key] = desc
1497
1498 arch_config_plus[arch][name].update(config_plus)
1499 arch_config_minus[arch][name].update(config_minus)
1500 arch_config_change[arch][name].update(config_change)
1501
1502 _AddConfig(lines, name, config_plus, config_minus,
1503 config_change)
1504 _AddConfig(lines, 'all', all_config_plus, all_config_minus,
1505 all_config_change)
1506 summary[target] = '\n'.join(lines)
1507
1508 lines_by_target = {}
Simon Glassc05aa032019-10-31 07:42:53 -06001509 for target, lines in summary.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001510 if lines in lines_by_target:
1511 lines_by_target[lines].append(target)
1512 else:
1513 lines_by_target[lines] = [target]
1514
1515 for arch in arch_list:
1516 lines = []
1517 all_plus = {}
1518 all_minus = {}
1519 all_change = {}
Simon Glassb464f8e2016-11-13 14:25:53 -07001520 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001521 all_plus.update(arch_config_plus[arch][name])
1522 all_minus.update(arch_config_minus[arch][name])
1523 all_change.update(arch_config_change[arch][name])
1524 _AddConfig(lines, name, arch_config_plus[arch][name],
1525 arch_config_minus[arch][name],
1526 arch_config_change[arch][name])
1527 _AddConfig(lines, 'all', all_plus, all_minus, all_change)
1528 #arch_summary[target] = '\n'.join(lines)
1529 if lines:
Simon Glass098b10f2022-01-29 14:14:18 -07001530 tprint('%s:' % arch)
Simon Glass8270e3c2015-08-25 21:52:14 -06001531 _OutputConfigInfo(lines)
1532
Simon Glassc05aa032019-10-31 07:42:53 -06001533 for lines, targets in lines_by_target.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001534 if not lines:
1535 continue
Simon Glass098b10f2022-01-29 14:14:18 -07001536 tprint('%s :' % ' '.join(sorted(targets)))
Simon Glass8270e3c2015-08-25 21:52:14 -06001537 _OutputConfigInfo(lines.split('\n'))
1538
Simon Glass843312d2015-02-05 22:06:15 -07001539
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001540 # Save our updated information for the next call to this function
1541 self._base_board_dict = board_dict
1542 self._base_err_lines = err_lines
Simon Glasse30965d2014-08-28 09:43:44 -06001543 self._base_warn_lines = warn_lines
1544 self._base_err_line_boards = err_line_boards
1545 self._base_warn_line_boards = warn_line_boards
Simon Glass843312d2015-02-05 22:06:15 -07001546 self._base_config = config
Alex Kiernan48ae4122018-05-31 04:48:34 +00001547 self._base_environment = environment
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001548
1549 # Get a list of boards that did not get built, if needed
1550 not_built = []
Simon Glassf4ed4702022-07-11 19:03:57 -06001551 for brd in board_selected:
1552 if not brd in board_dict:
1553 not_built.append(brd)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001554 if not_built:
Simon Glass098b10f2022-01-29 14:14:18 -07001555 tprint("Boards not built (%d): %s" % (len(not_built),
Simon Glass4653a882014-09-05 19:00:07 -06001556 ', '.join(not_built)))
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001557
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001558 def ProduceResultSummary(self, commit_upto, commits, board_selected):
Simon Glasse30965d2014-08-28 09:43:44 -06001559 (board_dict, err_lines, err_line_boards, warn_lines,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001560 warn_line_boards, config, environment) = self.GetResultSummary(
Simon Glassed966652014-08-28 09:43:43 -06001561 board_selected, commit_upto,
Simon Glass843312d2015-02-05 22:06:15 -07001562 read_func_sizes=self._show_bloat,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001563 read_config=self._show_config,
1564 read_environment=self._show_environment)
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001565 if commits:
1566 msg = '%02d: %s' % (commit_upto + 1,
1567 commits[commit_upto].subject)
Simon Glass098b10f2022-01-29 14:14:18 -07001568 tprint(msg, colour=self.col.BLUE)
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001569 self.PrintResultSummary(board_selected, board_dict,
Simon Glassed966652014-08-28 09:43:43 -06001570 err_lines if self._show_errors else [], err_line_boards,
Simon Glasse30965d2014-08-28 09:43:44 -06001571 warn_lines if self._show_errors else [], warn_line_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001572 config, environment, self._show_sizes, self._show_detail,
1573 self._show_bloat, self._show_config, self._show_environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001574
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001575 def ShowSummary(self, commits, board_selected):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001576 """Show a build summary for U-Boot for a given board list.
1577
1578 Reset the result summary, then repeatedly call GetResultSummary on
1579 each commit's results, then display the differences we see.
1580
1581 Args:
1582 commit: Commit objects to summarise
1583 board_selected: Dict containing boards to summarise
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001584 """
Simon Glassfea58582014-08-09 15:32:59 -06001585 self.commit_count = len(commits) if commits else 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001586 self.commits = commits
1587 self.ResetResultSummary(board_selected)
Simon Glass28370c12014-08-09 15:33:06 -06001588 self._error_lines = 0
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001589
1590 for commit_upto in range(0, self.commit_count, self._step):
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001591 self.ProduceResultSummary(commit_upto, commits, board_selected)
Simon Glass28370c12014-08-09 15:33:06 -06001592 if not self._error_lines:
Simon Glass098b10f2022-01-29 14:14:18 -07001593 tprint('(no errors to report)', colour=self.col.GREEN)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001594
1595
1596 def SetupBuild(self, board_selected, commits):
1597 """Set up ready to start a build.
1598
1599 Args:
1600 board_selected: Selected boards to build
1601 commits: Selected commits to build
1602 """
1603 # First work out how many commits we will build
Simon Glassc05aa032019-10-31 07:42:53 -06001604 count = (self.commit_count + self._step - 1) // self._step
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001605 self.count = len(board_selected) * count
1606 self.upto = self.warned = self.fail = 0
1607 self._timestamps = collections.deque()
1608
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001609 def GetThreadDir(self, thread_num):
1610 """Get the directory path to the working dir for a thread.
1611
1612 Args:
Simon Glassb82492b2021-01-30 22:17:46 -07001613 thread_num: Number of thread to check (-1 for main process, which
1614 is treated as 0)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001615 """
Simon Glassd829f122020-03-18 09:42:42 -06001616 if self.work_in_output:
1617 return self._working_dir
Simon Glassb82492b2021-01-30 22:17:46 -07001618 return os.path.join(self._working_dir, '%02d' % max(thread_num, 0))
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001619
Simon Glassfea58582014-08-09 15:32:59 -06001620 def _PrepareThread(self, thread_num, setup_git):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001621 """Prepare the working directory for a thread.
1622
1623 This clones or fetches the repo into the thread's work directory.
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001624 Optionally, it can create a linked working tree of the repo in the
1625 thread's work directory instead.
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001626
1627 Args:
1628 thread_num: Thread number (0, 1, ...)
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001629 setup_git:
1630 'clone' to set up a git clone
1631 'worktree' to set up a git worktree
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001632 """
1633 thread_dir = self.GetThreadDir(thread_num)
Simon Glass190064b2014-08-09 15:33:00 -06001634 builderthread.Mkdir(thread_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001635 git_dir = os.path.join(thread_dir, '.git')
1636
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001637 # Create a worktree or a git repo clone for this thread if it
1638 # doesn't already exist
Simon Glassfea58582014-08-09 15:32:59 -06001639 if setup_git and self.git_dir:
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001640 src_dir = os.path.abspath(self.git_dir)
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001641 if os.path.isdir(git_dir):
1642 # This is a clone of the src_dir repo, we can keep using
1643 # it but need to fetch from src_dir.
Simon Glass098b10f2022-01-29 14:14:18 -07001644 tprint('\rFetching repo for thread %d' % thread_num,
Simon Glass212c0b82020-04-09 15:08:43 -06001645 newline=False)
Simon Glass0157b182022-01-29 14:14:11 -07001646 gitutil.fetch(git_dir, thread_dir)
Simon Glass098b10f2022-01-29 14:14:18 -07001647 terminal.print_clear()
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001648 elif os.path.isfile(git_dir):
1649 # This is a worktree of the src_dir repo, we don't need to
1650 # create it again or update it in any way.
1651 pass
1652 elif os.path.exists(git_dir):
1653 # Don't know what could trigger this, but we probably
1654 # can't create a git worktree/clone here.
1655 raise ValueError('Git dir %s exists, but is not a file '
1656 'or a directory.' % git_dir)
1657 elif setup_git == 'worktree':
Simon Glass098b10f2022-01-29 14:14:18 -07001658 tprint('\rChecking out worktree for thread %d' % thread_num,
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001659 newline=False)
Simon Glass0157b182022-01-29 14:14:11 -07001660 gitutil.add_worktree(src_dir, thread_dir)
Simon Glass098b10f2022-01-29 14:14:18 -07001661 terminal.print_clear()
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001662 elif setup_git == 'clone' or setup_git == True:
Simon Glass098b10f2022-01-29 14:14:18 -07001663 tprint('\rCloning repo for thread %d' % thread_num,
Simon Glass21f0eb32016-09-18 16:48:31 -06001664 newline=False)
Simon Glass0157b182022-01-29 14:14:11 -07001665 gitutil.clone(src_dir, thread_dir)
Simon Glass098b10f2022-01-29 14:14:18 -07001666 terminal.print_clear()
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001667 else:
1668 raise ValueError("Can't setup git repo with %s." % setup_git)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001669
Simon Glassfea58582014-08-09 15:32:59 -06001670 def _PrepareWorkingSpace(self, max_threads, setup_git):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001671 """Prepare the working directory for use.
1672
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001673 Set up the git repo for each thread. Creates a linked working tree
1674 if git-worktree is available, or clones the repo if it isn't.
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001675
1676 Args:
Simon Glassb82492b2021-01-30 22:17:46 -07001677 max_threads: Maximum number of threads we expect to need. If 0 then
1678 1 is set up, since the main process still needs somewhere to
1679 work
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001680 setup_git: True to set up a git worktree or a git clone
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001681 """
Simon Glass190064b2014-08-09 15:33:00 -06001682 builderthread.Mkdir(self._working_dir)
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001683 if setup_git and self.git_dir:
1684 src_dir = os.path.abspath(self.git_dir)
Simon Glass0157b182022-01-29 14:14:11 -07001685 if gitutil.check_worktree_is_available(src_dir):
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001686 setup_git = 'worktree'
1687 # If we previously added a worktree but the directory for it
1688 # got deleted, we need to prune its files from the repo so
1689 # that we can check out another in its place.
Simon Glass0157b182022-01-29 14:14:11 -07001690 gitutil.prune_worktrees(src_dir)
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001691 else:
1692 setup_git = 'clone'
Simon Glassb82492b2021-01-30 22:17:46 -07001693
1694 # Always do at least one thread
1695 for thread in range(max(max_threads, 1)):
Simon Glassfea58582014-08-09 15:32:59 -06001696 self._PrepareThread(thread, setup_git)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001697
Simon Glass925f6ad2020-03-18 09:42:45 -06001698 def _GetOutputSpaceRemovals(self):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001699 """Get the output directories ready to receive files.
1700
Simon Glass925f6ad2020-03-18 09:42:45 -06001701 Figure out what needs to be deleted in the output directory before it
1702 can be used. We only delete old buildman directories which have the
1703 expected name pattern. See _GetOutputDir().
1704
1705 Returns:
1706 List of full paths of directories to remove
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001707 """
Simon Glass1a915672014-12-01 17:33:53 -07001708 if not self.commits:
1709 return
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001710 dir_list = []
1711 for commit_upto in range(self.commit_count):
1712 dir_list.append(self._GetOutputDir(commit_upto))
1713
Simon Glassb222abe2016-09-18 16:48:32 -06001714 to_remove = []
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001715 for dirname in glob.glob(os.path.join(self.base_dir, '*')):
1716 if dirname not in dir_list:
Simon Glass925f6ad2020-03-18 09:42:45 -06001717 leaf = dirname[len(self.base_dir) + 1:]
Ovidiu Panait7664b032020-05-15 09:30:12 +03001718 m = re.match('[0-9]+_g[0-9a-f]+_.*', leaf)
Simon Glass925f6ad2020-03-18 09:42:45 -06001719 if m:
1720 to_remove.append(dirname)
1721 return to_remove
1722
1723 def _PrepareOutputSpace(self):
1724 """Get the output directories ready to receive files.
1725
1726 We delete any output directories which look like ones we need to
1727 create. Having left over directories is confusing when the user wants
1728 to check the output manually.
1729 """
1730 to_remove = self._GetOutputSpaceRemovals()
Simon Glassb222abe2016-09-18 16:48:32 -06001731 if to_remove:
Simon Glass098b10f2022-01-29 14:14:18 -07001732 tprint('Removing %d old build directories...' % len(to_remove),
Simon Glassb222abe2016-09-18 16:48:32 -06001733 newline=False)
1734 for dirname in to_remove:
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001735 shutil.rmtree(dirname)
Simon Glass098b10f2022-01-29 14:14:18 -07001736 terminal.print_clear()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001737
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001738 def BuildBoards(self, commits, board_selected, keep_outputs, verbose):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001739 """Build all commits for a list of boards
1740
1741 Args:
1742 commits: List of commits to be build, each a Commit object
1743 boards_selected: Dict of selected boards, key is target name,
1744 value is Board object
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001745 keep_outputs: True to save build output files
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001746 verbose: Display build results as they are completed
Simon Glass2c3deb92014-08-28 09:43:39 -06001747 Returns:
1748 Tuple containing:
1749 - number of boards that failed to build
1750 - number of boards that issued warnings
Simon Glass8116c782021-04-11 16:27:27 +12001751 - list of thread exceptions raised
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001752 """
Simon Glassfea58582014-08-09 15:32:59 -06001753 self.commit_count = len(commits) if commits else 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001754 self.commits = commits
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001755 self._verbose = verbose
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001756
1757 self.ResetResultSummary(board_selected)
Thierry Redingf3d015c2014-08-19 10:22:39 +02001758 builderthread.Mkdir(self.base_dir, parents = True)
Simon Glassfea58582014-08-09 15:32:59 -06001759 self._PrepareWorkingSpace(min(self.num_threads, len(board_selected)),
1760 commits is not None)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001761 self._PrepareOutputSpace()
Simon Glassae1a09f2022-07-11 19:03:56 -06001762 if not self._ide:
1763 tprint('\rStarting build...', newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001764 self.SetupBuild(board_selected, commits)
1765 self.ProcessResult(None)
Simon Glass8116c782021-04-11 16:27:27 +12001766 self.thread_exceptions = []
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001767 # Create jobs to build all commits for each board
Simon Glassc05aa032019-10-31 07:42:53 -06001768 for brd in board_selected.values():
Simon Glass190064b2014-08-09 15:33:00 -06001769 job = builderthread.BuilderJob()
Simon Glassf4ed4702022-07-11 19:03:57 -06001770 job.brd = brd
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001771 job.commits = commits
1772 job.keep_outputs = keep_outputs
Simon Glassd829f122020-03-18 09:42:42 -06001773 job.work_in_output = self.work_in_output
Simon Glass2b4806e2022-01-22 05:07:33 -07001774 job.adjust_cfg = self.adjust_cfg
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001775 job.step = self._step
Simon Glassb82492b2021-01-30 22:17:46 -07001776 if self.num_threads:
1777 self.queue.put(job)
1778 else:
Simon Glassac053352022-02-11 13:23:19 -07001779 self._single_builder.RunJob(job)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001780
Simon Glassb82492b2021-01-30 22:17:46 -07001781 if self.num_threads:
1782 term = threading.Thread(target=self.queue.join)
1783 term.setDaemon(True)
1784 term.start()
1785 while term.is_alive():
1786 term.join(100)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001787
Simon Glassb82492b2021-01-30 22:17:46 -07001788 # Wait until we have processed all output
1789 self.out_queue.join()
Simon Glassae1a09f2022-07-11 19:03:56 -06001790 if not self._ide:
1791 tprint()
Simon Glass7b33f212020-04-09 15:08:47 -06001792
Simon Glassae1a09f2022-07-11 19:03:56 -06001793 msg = 'Completed: %d total built' % self.count
1794 if self.already_done:
1795 msg += ' (%d previously' % self.already_done
1796 if self.already_done != self.count:
1797 msg += ', %d newly' % (self.count - self.already_done)
1798 msg += ')'
1799 duration = datetime.now() - self._start_time
1800 if duration > timedelta(microseconds=1000000):
1801 if duration.microseconds >= 500000:
1802 duration = duration + timedelta(seconds=1)
1803 duration = duration - timedelta(microseconds=duration.microseconds)
1804 rate = float(self.count) / duration.total_seconds()
1805 msg += ', duration %s, rate %1.2f' % (duration, rate)
1806 tprint(msg)
1807 if self.thread_exceptions:
1808 tprint('Failed: %d thread exceptions' % len(self.thread_exceptions),
1809 colour=self.col.RED)
Simon Glass7b33f212020-04-09 15:08:47 -06001810
Simon Glass8116c782021-04-11 16:27:27 +12001811 return (self.fail, self.warned, self.thread_exceptions)