blob: d81752e99438e4400c03da6934b5963e49fe1aaa [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glassc05694f2013-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 Glassc05694f2013-04-03 11:07:16 +00006
7import collections
Simon Glassc05694f2013-04-03 11:07:16 +00008from datetime import datetime, timedelta
9import glob
10import os
11import re
Simon Glassc78ed662019-10-31 07:42:53 -060012import queue
Simon Glassc05694f2013-04-03 11:07:16 +000013import shutil
Simon Glass205ac042016-09-18 16:48:37 -060014import signal
Simon Glassc05694f2013-04-03 11:07:16 +000015import string
16import sys
Simon Glassd26e1442016-09-18 16:48:35 -060017import threading
Simon Glassc05694f2013-04-03 11:07:16 +000018import time
19
Simon Glassf0d9c102020-04-17 18:09:02 -060020from buildman import builderthread
21from buildman import toolchain
Simon Glassa997ea52020-04-17 18:09:04 -060022from patman import gitutil
Simon Glass131444f2023-02-23 18:18:04 -070023from u_boot_pylib import command
24from u_boot_pylib import terminal
25from u_boot_pylib.terminal import tprint
Simon Glassc05694f2013-04-03 11:07:16 +000026
Simon Glass146b6022021-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 Glassc05694f2013-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 Panaitee8e9cb2020-05-15 09:30:12 +030084 01_g4ed4ebc_net--Add-tftp-speed-/
Simon Glassc05694f2013-04-03 11:07:16 +000085 sandbox/
86 u-boot.bin
87 seaboard/
88 u-boot.bin
Ovidiu Panaitee8e9cb2020-05-15 09:30:12 +030089 02_g4ed4ebc_net--Check-tftp-comp/
Simon Glassc05694f2013-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 Glassde0fefc2020-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 Glass5df45222022-07-11 19:04:00 -0600111ErrLine = collections.namedtuple('ErrLine', 'char,brds,errline')
Simon Glassde0fefc2020-04-09 15:08:36 -0600112
Simon Glassc05694f2013-04-03 11:07:16 +0000113# Possible build outcomes
Simon Glassc78ed662019-10-31 07:42:53 -0600114OUTCOME_OK, OUTCOME_WARNING, OUTCOME_ERROR, OUTCOME_UNKNOWN = list(range(4))
Simon Glassc05694f2013-04-03 11:07:16 +0000115
Simon Glassd214bef2017-04-12 18:23:26 -0600116# Translate a commit subject into a valid filename (and handle unicode)
Simon Glassc78ed662019-10-31 07:42:53 -0600117trans_valid_chars = str.maketrans('/: ', '---')
Simon Glassc05694f2013-04-03 11:07:16 +0000118
Simon Glasscde5c302016-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 Glassdb17fb82015-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 Glassdb17fb82015-02-05 22:06:15 -0700127]
128
Simon Glasscad8abf2015-08-25 21:52:14 -0600129class Config:
130 """Holds information about configuration settings for a board."""
Simon Glasscde5c302016-11-13 14:25:53 -0700131 def __init__(self, config_filename, target):
Simon Glasscad8abf2015-08-25 21:52:14 -0600132 self.target = target
133 self.config = {}
Simon Glasscde5c302016-11-13 14:25:53 -0700134 for fname in config_filename:
Simon Glasscad8abf2015-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 Glassc78ed662019-10-31 07:42:53 -0600143 for key, value in self.config[fname].items():
144 print(key, value)
Simon Glasscad8abf2015-08-25 21:52:14 -0600145 val = val ^ hash(key) & hash(value)
146 return val
Simon Glassc05694f2013-04-03 11:07:16 +0000147
Alex Kiernan4059e302018-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 Glassc05694f2013-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 Glassc05694f2013-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 Glass7041c392014-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 Glassc05694f2013-04-03 11:07:16 +0000175 git_dir: Git directory containing source repository
Simon Glassc05694f2013-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 Glassf3018b7a2014-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 Glass38df2e22014-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 Glassb6eb8cf2020-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 Glass9bf9a722021-04-11 16:27:27 +1200196 thread_exceptions: List of exceptions raised by thread jobs
Simon Glassf6bfcca2023-02-21 12:40:28 -0700197 no_lto (bool): True to set the NO_LTO flag when building
Simon Glass828d70d2023-02-21 12:40:29 -0700198 reproducible_builds (bool): True to set SOURCE_DATE_EPOCH=0 for builds
Simon Glassc05694f2013-04-03 11:07:16 +0000199
200 Private members:
201 _base_board_dict: Last-summarised Dict of boards
202 _base_err_lines: Last-summarised list of errors
Simon Glass03749d42014-08-28 09:43:44 -0600203 _base_warn_lines: Last-summarised list of warnings
Simon Glassc05694f2013-04-03 11:07:16 +0000204 _build_period_us: Time taken for a single build (float object).
205 _complete_delay: Expected delay until completion (timedelta)
206 _next_delay_update: Next time we plan to display a progress update
207 (datatime)
208 _show_unknown: Show unknown boards (those not built) in summary
Simon Glass726ae812020-04-09 15:08:47 -0600209 _start_time: Start time for the build
Simon Glassc05694f2013-04-03 11:07:16 +0000210 _timestamps: List of timestamps for the completion of the last
211 last _timestamp_count builds. Each is a datetime object.
212 _timestamp_count: Number of timestamps to keep in our list.
213 _working_dir: Base working directory containing all threads
Simon Glassc635d892021-01-30 22:17:46 -0700214 _single_builder: BuilderThread object for the singer builder, if
215 threading is not being used
Simon Glass146b6022021-10-19 21:43:24 -0600216 _terminated: Thread was terminated due to an error
217 _restarting_config: True if 'Restart config' is detected in output
Simon Glass6c435622022-07-11 19:03:56 -0600218 _ide: Produce output suitable for an Integrated Development Environment,
219 i.e. dont emit progress information and put errors/warnings on stderr
Simon Glassc05694f2013-04-03 11:07:16 +0000220 """
221 class Outcome:
222 """Records a build outcome for a single make invocation
223
224 Public Members:
225 rc: Outcome value (OUTCOME_...)
226 err_lines: List of error lines or [] if none
227 sizes: Dictionary of image size information, keyed by filename
228 - Each value is itself a dictionary containing
229 values for 'text', 'data' and 'bss', being the integer
230 size in bytes of each section.
231 func_sizes: Dictionary keyed by filename - e.g. 'u-boot'. Each
232 value is itself a dictionary:
233 key: function name
234 value: Size of function in bytes
Simon Glassdb17fb82015-02-05 22:06:15 -0700235 config: Dictionary keyed by filename - e.g. '.config'. Each
236 value is itself a dictionary:
237 key: config name
238 value: config value
Alex Kiernan4059e302018-05-31 04:48:34 +0000239 environment: Dictionary keyed by environment variable, Each
240 value is the value of environment variable.
Simon Glassc05694f2013-04-03 11:07:16 +0000241 """
Alex Kiernan4059e302018-05-31 04:48:34 +0000242 def __init__(self, rc, err_lines, sizes, func_sizes, config,
243 environment):
Simon Glassc05694f2013-04-03 11:07:16 +0000244 self.rc = rc
245 self.err_lines = err_lines
246 self.sizes = sizes
247 self.func_sizes = func_sizes
Simon Glassdb17fb82015-02-05 22:06:15 -0700248 self.config = config
Alex Kiernan4059e302018-05-31 04:48:34 +0000249 self.environment = environment
Simon Glassc05694f2013-04-03 11:07:16 +0000250
251 def __init__(self, toolchains, base_dir, git_dir, num_threads, num_jobs,
Simon Glasse87bde12014-12-01 17:33:55 -0700252 gnu_make='make', checkout=True, show_unknown=True, step=1,
Stephen Warren97c96902016-04-11 10:48:44 -0600253 no_subdirs=False, full_path=False, verbose_build=False,
Simon Glass6029af12020-04-09 15:08:51 -0600254 mrproper=False, per_board_out_dir=False,
Daniel Schwierzeck20e2ea92018-01-26 16:31:05 +0100255 config_only=False, squash_config_y=False,
Simon Glass9bf9a722021-04-11 16:27:27 +1200256 warnings_as_errors=False, work_in_output=False,
Tom Rini93ebd462022-11-09 19:14:53 -0700257 test_thread_exceptions=False, adjust_cfg=None,
Simon Glass828d70d2023-02-21 12:40:29 -0700258 allow_missing=False, no_lto=False, reproducible_builds=False):
Simon Glassc05694f2013-04-03 11:07:16 +0000259 """Create a new Builder object
260
261 Args:
262 toolchains: Toolchains object to use for building
263 base_dir: Base directory to use for builder
264 git_dir: Git directory containing source repository
265 num_threads: Number of builder threads to run
266 num_jobs: Number of jobs to run at once (passed to make as -j)
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900267 gnu_make: the command name of GNU Make.
Simon Glassc05694f2013-04-03 11:07:16 +0000268 checkout: True to check out source, False to skip that step.
269 This is used for testing.
270 show_unknown: Show unknown boards (those not built) in summary
271 step: 1 to process every commit, n to process every nth commit
Simon Glassd48a46c2014-12-01 17:34:00 -0700272 no_subdirs: Don't create subdirectories when building current
273 source for a single board
274 full_path: Return the full path in CROSS_COMPILE and don't set
275 PATH
Simon Glass655b6102014-12-01 17:34:07 -0700276 verbose_build: Run build with V=1 and don't use 'make -s'
Simon Glass6029af12020-04-09 15:08:51 -0600277 mrproper: Always run 'make mrproper' when configuring
Stephen Warren97c96902016-04-11 10:48:44 -0600278 per_board_out_dir: Build in a separate persistent directory per
279 board rather than a thread-specific directory
Simon Glass739e8512016-11-13 14:25:51 -0700280 config_only: Only configure each build, don't build it
Simon Glasscde5c302016-11-13 14:25:53 -0700281 squash_config_y: Convert CONFIG options with the value 'y' to '1'
Daniel Schwierzeck20e2ea92018-01-26 16:31:05 +0100282 warnings_as_errors: Treat all compiler warnings as errors
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600283 work_in_output: Use the output directory as the work directory and
284 don't write to a separate output directory.
Simon Glass9bf9a722021-04-11 16:27:27 +1200285 test_thread_exceptions: Uses for tests only, True to make the
286 threads raise an exception instead of reporting their result.
287 This simulates a failure in the code somewhere
Simon Glasse5650a82022-01-22 05:07:33 -0700288 adjust_cfg_list (list of str): List of changes to make to .config
289 file before building. Each is one of (where C is the config
290 option with or without the CONFIG_ prefix)
291
292 C to enable C
293 ~C to disable C
294 C=val to set the value of C (val must have quotes if C is
295 a string Kconfig
Tom Rini93ebd462022-11-09 19:14:53 -0700296 allow_missing: Run build with BINMAN_ALLOW_MISSING=1
Simon Glassf6bfcca2023-02-21 12:40:28 -0700297 no_lto (bool): True to set the NO_LTO flag when building
Simon Glasse5650a82022-01-22 05:07:33 -0700298
Simon Glassc05694f2013-04-03 11:07:16 +0000299 """
300 self.toolchains = toolchains
301 self.base_dir = base_dir
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600302 if work_in_output:
303 self._working_dir = base_dir
304 else:
305 self._working_dir = os.path.join(base_dir, '.bm-work')
Simon Glassc05694f2013-04-03 11:07:16 +0000306 self.threads = []
Simon Glassc05694f2013-04-03 11:07:16 +0000307 self.do_make = self.Make
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900308 self.gnu_make = gnu_make
Simon Glassc05694f2013-04-03 11:07:16 +0000309 self.checkout = checkout
310 self.num_threads = num_threads
311 self.num_jobs = num_jobs
312 self.already_done = 0
313 self.force_build = False
314 self.git_dir = git_dir
315 self._show_unknown = show_unknown
316 self._timestamp_count = 10
317 self._build_period_us = None
318 self._complete_delay = None
319 self._next_delay_update = datetime.now()
Simon Glass726ae812020-04-09 15:08:47 -0600320 self._start_time = datetime.now()
Simon Glassc05694f2013-04-03 11:07:16 +0000321 self.force_config_on_failure = True
Simon Glass7041c392014-07-13 12:22:31 -0600322 self.force_build_failures = False
Simon Glassf3018b7a2014-07-14 17:51:02 -0600323 self.force_reconfig = False
Simon Glassc05694f2013-04-03 11:07:16 +0000324 self._step = step
Simon Glass38df2e22014-07-14 17:51:03 -0600325 self.in_tree = False
Simon Glassbb4dffb2014-08-09 15:33:06 -0600326 self._error_lines = 0
Simon Glasse87bde12014-12-01 17:33:55 -0700327 self.no_subdirs = no_subdirs
Simon Glassd48a46c2014-12-01 17:34:00 -0700328 self.full_path = full_path
Simon Glass655b6102014-12-01 17:34:07 -0700329 self.verbose_build = verbose_build
Simon Glass739e8512016-11-13 14:25:51 -0700330 self.config_only = config_only
Simon Glasscde5c302016-11-13 14:25:53 -0700331 self.squash_config_y = squash_config_y
332 self.config_filenames = BASE_CONFIG_FILENAMES
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600333 self.work_in_output = work_in_output
Simon Glasse5650a82022-01-22 05:07:33 -0700334 self.adjust_cfg = adjust_cfg
Tom Rini93ebd462022-11-09 19:14:53 -0700335 self.allow_missing = allow_missing
Simon Glass6c435622022-07-11 19:03:56 -0600336 self._ide = False
Simon Glassf6bfcca2023-02-21 12:40:28 -0700337 self.no_lto = no_lto
Simon Glass828d70d2023-02-21 12:40:29 -0700338 self.reproducible_builds = reproducible_builds
Simon Glasse5650a82022-01-22 05:07:33 -0700339
Simon Glasscde5c302016-11-13 14:25:53 -0700340 if not self.squash_config_y:
341 self.config_filenames += EXTRA_CONFIG_FILENAMES
Simon Glass146b6022021-10-19 21:43:24 -0600342 self._terminated = False
343 self._restarting_config = False
Simon Glassc05694f2013-04-03 11:07:16 +0000344
Daniel Schwierzeck20e2ea92018-01-26 16:31:05 +0100345 self.warnings_as_errors = warnings_as_errors
Simon Glassc05694f2013-04-03 11:07:16 +0000346 self.col = terminal.Color()
347
Simon Glass03749d42014-08-28 09:43:44 -0600348 self._re_function = re.compile('(.*): In function.*')
349 self._re_files = re.compile('In file included from.*')
350 self._re_warning = re.compile('(.*):(\d*):(\d*): warning: .*')
Simon Glass0db94432018-11-06 16:02:11 -0700351 self._re_dtb_warning = re.compile('(.*): Warning .*')
Simon Glass03749d42014-08-28 09:43:44 -0600352 self._re_note = re.compile('(.*):(\d*):(\d*): note: this is the location of the previous.*')
Simon Glassf4ebfba2020-04-09 15:08:53 -0600353 self._re_migration_warning = re.compile(r'^={21} WARNING ={22}\n.*\n=+\n',
354 re.MULTILINE | re.DOTALL)
Simon Glass03749d42014-08-28 09:43:44 -0600355
Simon Glass9bf9a722021-04-11 16:27:27 +1200356 self.thread_exceptions = []
357 self.test_thread_exceptions = test_thread_exceptions
Simon Glassc635d892021-01-30 22:17:46 -0700358 if self.num_threads:
359 self._single_builder = None
360 self.queue = queue.Queue()
361 self.out_queue = queue.Queue()
362 for i in range(self.num_threads):
Simon Glass9bf9a722021-04-11 16:27:27 +1200363 t = builderthread.BuilderThread(
364 self, i, mrproper, per_board_out_dir,
365 test_exception=test_thread_exceptions)
Simon Glassc635d892021-01-30 22:17:46 -0700366 t.setDaemon(True)
367 t.start()
368 self.threads.append(t)
369
370 t = builderthread.ResultThread(self)
Simon Glassc05694f2013-04-03 11:07:16 +0000371 t.setDaemon(True)
372 t.start()
373 self.threads.append(t)
Simon Glassc635d892021-01-30 22:17:46 -0700374 else:
375 self._single_builder = builderthread.BuilderThread(
376 self, -1, mrproper, per_board_out_dir)
Simon Glassc05694f2013-04-03 11:07:16 +0000377
378 ignore_lines = ['(make.*Waiting for unfinished)', '(Segmentation fault)']
379 self.re_make_err = re.compile('|'.join(ignore_lines))
380
Simon Glass205ac042016-09-18 16:48:37 -0600381 # Handle existing graceful with SIGINT / Ctrl-C
382 signal.signal(signal.SIGINT, self.signal_handler)
383
Simon Glassc05694f2013-04-03 11:07:16 +0000384 def __del__(self):
385 """Get rid of all threads created by the builder"""
386 for t in self.threads:
387 del t
388
Simon Glass205ac042016-09-18 16:48:37 -0600389 def signal_handler(self, signal, frame):
390 sys.exit(1)
391
Simon Glasseb48bbc2014-08-09 15:33:02 -0600392 def SetDisplayOptions(self, show_errors=False, show_sizes=False,
Simon Glass3394c9f2014-08-28 09:43:43 -0600393 show_detail=False, show_bloat=False,
Alex Kiernan4059e302018-05-31 04:48:34 +0000394 list_error_boards=False, show_config=False,
Simon Glassf4ebfba2020-04-09 15:08:53 -0600395 show_environment=False, filter_dtb_warnings=False,
Simon Glass6c435622022-07-11 19:03:56 -0600396 filter_migration_warnings=False, ide=False):
Simon Glasseb48bbc2014-08-09 15:33:02 -0600397 """Setup display options for the builder.
398
Simon Glass9ea93812020-04-09 15:08:52 -0600399 Args:
400 show_errors: True to show summarised error/warning info
401 show_sizes: Show size deltas
402 show_detail: Show size delta detail for each board if show_sizes
403 show_bloat: Show detail for each function
404 list_error_boards: Show the boards which caused each error/warning
405 show_config: Show config deltas
406 show_environment: Show environment deltas
407 filter_dtb_warnings: Filter out any warnings from the device-tree
408 compiler
Simon Glassf4ebfba2020-04-09 15:08:53 -0600409 filter_migration_warnings: Filter out any warnings about migrating
410 a board to driver model
Simon Glass6c435622022-07-11 19:03:56 -0600411 ide: Create output that can be parsed by an IDE. There is no '+' prefix on
412 error lines and output on stderr stays on stderr.
Simon Glasseb48bbc2014-08-09 15:33:02 -0600413 """
414 self._show_errors = show_errors
415 self._show_sizes = show_sizes
416 self._show_detail = show_detail
417 self._show_bloat = show_bloat
Simon Glass3394c9f2014-08-28 09:43:43 -0600418 self._list_error_boards = list_error_boards
Simon Glassdb17fb82015-02-05 22:06:15 -0700419 self._show_config = show_config
Alex Kiernan4059e302018-05-31 04:48:34 +0000420 self._show_environment = show_environment
Simon Glass9ea93812020-04-09 15:08:52 -0600421 self._filter_dtb_warnings = filter_dtb_warnings
Simon Glassf4ebfba2020-04-09 15:08:53 -0600422 self._filter_migration_warnings = filter_migration_warnings
Simon Glass6c435622022-07-11 19:03:56 -0600423 self._ide = ide
Simon Glasseb48bbc2014-08-09 15:33:02 -0600424
Simon Glassc05694f2013-04-03 11:07:16 +0000425 def _AddTimestamp(self):
426 """Add a new timestamp to the list and record the build period.
427
428 The build period is the length of time taken to perform a single
429 build (one board, one commit).
430 """
431 now = datetime.now()
432 self._timestamps.append(now)
433 count = len(self._timestamps)
434 delta = self._timestamps[-1] - self._timestamps[0]
435 seconds = delta.total_seconds()
436
437 # If we have enough data, estimate build period (time taken for a
438 # single build) and therefore completion time.
439 if count > 1 and self._next_delay_update < now:
440 self._next_delay_update = now + timedelta(seconds=2)
441 if seconds > 0:
442 self._build_period = float(seconds) / count
443 todo = self.count - self.upto
444 self._complete_delay = timedelta(microseconds=
445 self._build_period * todo * 1000000)
446 # Round it
447 self._complete_delay -= timedelta(
448 microseconds=self._complete_delay.microseconds)
449
450 if seconds > 60:
451 self._timestamps.popleft()
452 count -= 1
453
Simon Glassc05694f2013-04-03 11:07:16 +0000454 def SelectCommit(self, commit, checkout=True):
455 """Checkout the selected commit for this build
456 """
457 self.commit = commit
458 if checkout and self.checkout:
Simon Glass761648b2022-01-29 14:14:11 -0700459 gitutil.checkout(commit.hash)
Simon Glassc05694f2013-04-03 11:07:16 +0000460
461 def Make(self, commit, brd, stage, cwd, *args, **kwargs):
462 """Run make
463
464 Args:
465 commit: Commit object that is being built
466 brd: Board object that is being built
Roger Meiere0a0e552014-08-20 22:10:29 +0200467 stage: Stage that we are at (mrproper, config, build)
Simon Glassc05694f2013-04-03 11:07:16 +0000468 cwd: Directory where make should be run
469 args: Arguments to pass to make
Simon Glass840be732022-01-29 14:14:05 -0700470 kwargs: Arguments to pass to command.run_pipe()
Simon Glassc05694f2013-04-03 11:07:16 +0000471 """
Simon Glass146b6022021-10-19 21:43:24 -0600472
473 def check_output(stream, data):
474 if b'Restart config' in data:
475 self._restarting_config = True
476
477 # If we see 'Restart config' following by multiple errors
478 if self._restarting_config:
479 m = RE_NO_DEFAULT.findall(data)
480
481 # Number of occurences of each Kconfig item
482 multiple = [m.count(val) for val in set(m)]
483
484 # If any of them occur more than once, we have a loop
485 if [val for val in multiple if val > 1]:
486 self._terminated = True
487 return True
488 return False
489
490 self._restarting_config = False
491 self._terminated = False
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900492 cmd = [self.gnu_make] + list(args)
Simon Glass840be732022-01-29 14:14:05 -0700493 result = command.run_pipe([cmd], capture=True, capture_stderr=True,
Simon Glass146b6022021-10-19 21:43:24 -0600494 cwd=cwd, raise_on_error=False, infile='/dev/null',
495 output_func=check_output, **kwargs)
496
497 if self._terminated:
498 # Try to be helpful
499 result.stderr += '(** did you define an int/hex Kconfig with no default? **)'
500
Simon Glass413f91a2015-02-05 22:06:12 -0700501 if self.verbose_build:
502 result.stdout = '%s\n' % (' '.join(cmd)) + result.stdout
503 result.combined = '%s\n' % (' '.join(cmd)) + result.combined
Simon Glassc05694f2013-04-03 11:07:16 +0000504 return result
505
506 def ProcessResult(self, result):
507 """Process the result of a build, showing progress information
508
509 Args:
Simon Glass78e418e2014-08-09 15:33:03 -0600510 result: A CommandResult object, which indicates the result for
511 a single build
Simon Glassc05694f2013-04-03 11:07:16 +0000512 """
513 col = terminal.Color()
514 if result:
515 target = result.brd.target
516
Simon Glassc05694f2013-04-03 11:07:16 +0000517 self.upto += 1
518 if result.return_code != 0:
519 self.fail += 1
520 elif result.stderr:
521 self.warned += 1
522 if result.already_done:
523 self.already_done += 1
Simon Glass78e418e2014-08-09 15:33:03 -0600524 if self._verbose:
Simon Glass02811582022-01-29 14:14:18 -0700525 terminal.print_clear()
Simon Glass78e418e2014-08-09 15:33:03 -0600526 boards_selected = {target : result.brd}
527 self.ResetResultSummary(boards_selected)
528 self.ProduceResultSummary(result.commit_upto, self.commits,
529 boards_selected)
Simon Glassc05694f2013-04-03 11:07:16 +0000530 else:
531 target = '(starting)'
532
533 # Display separate counts for ok, warned and fail
534 ok = self.upto - self.warned - self.fail
Simon Glassf45d3742022-01-29 14:14:17 -0700535 line = '\r' + self.col.build(self.col.GREEN, '%5d' % ok)
536 line += self.col.build(self.col.YELLOW, '%5d' % self.warned)
537 line += self.col.build(self.col.RED, '%5d' % self.fail)
Simon Glassc05694f2013-04-03 11:07:16 +0000538
Simon Glass69c3a8a2020-04-09 15:08:45 -0600539 line += ' /%-5d ' % self.count
540 remaining = self.count - self.upto
541 if remaining:
Simon Glassf45d3742022-01-29 14:14:17 -0700542 line += self.col.build(self.col.MAGENTA, ' -%-5d ' % remaining)
Simon Glass69c3a8a2020-04-09 15:08:45 -0600543 else:
544 line += ' ' * 8
Simon Glassc05694f2013-04-03 11:07:16 +0000545
546 # Add our current completion time estimate
547 self._AddTimestamp()
548 if self._complete_delay:
Simon Glass69c3a8a2020-04-09 15:08:45 -0600549 line += '%s : ' % self._complete_delay
Simon Glassc05694f2013-04-03 11:07:16 +0000550
Simon Glass69c3a8a2020-04-09 15:08:45 -0600551 line += target
Simon Glass6c435622022-07-11 19:03:56 -0600552 if not self._ide:
553 terminal.print_clear()
554 tprint(line, newline=False, limit_to_line=True)
Simon Glassc05694f2013-04-03 11:07:16 +0000555
556 def _GetOutputDir(self, commit_upto):
557 """Get the name of the output directory for a commit number
558
559 The output directory is typically .../<branch>/<commit>.
560
561 Args:
562 commit_upto: Commit number to use (0..self.count-1)
563 """
Simon Glasse3c85ab2020-04-17 17:51:34 -0600564 if self.work_in_output:
565 return self._working_dir
566
Simon Glasse87bde12014-12-01 17:33:55 -0700567 commit_dir = None
Simon Glassd326ad72014-08-09 15:32:59 -0600568 if self.commits:
569 commit = self.commits[commit_upto]
570 subject = commit.subject.translate(trans_valid_chars)
Simon Glass5dc1ca72020-03-18 09:42:45 -0600571 # See _GetOutputSpaceRemovals() which parses this name
Ovidiu Panaitee8e9cb2020-05-15 09:30:12 +0300572 commit_dir = ('%02d_g%s_%s' % (commit_upto + 1,
573 commit.hash, subject[:20]))
Simon Glasse87bde12014-12-01 17:33:55 -0700574 elif not self.no_subdirs:
Simon Glassd326ad72014-08-09 15:32:59 -0600575 commit_dir = 'current'
Simon Glasse87bde12014-12-01 17:33:55 -0700576 if not commit_dir:
577 return self.base_dir
578 return os.path.join(self.base_dir, commit_dir)
Simon Glassc05694f2013-04-03 11:07:16 +0000579
580 def GetBuildDir(self, commit_upto, target):
581 """Get the name of the build directory for a commit number
582
583 The build directory is typically .../<branch>/<commit>/<target>.
584
585 Args:
586 commit_upto: Commit number to use (0..self.count-1)
587 target: Target name
588 """
589 output_dir = self._GetOutputDir(commit_upto)
Simon Glasse3c85ab2020-04-17 17:51:34 -0600590 if self.work_in_output:
591 return output_dir
Simon Glassc05694f2013-04-03 11:07:16 +0000592 return os.path.join(output_dir, target)
593
594 def GetDoneFile(self, commit_upto, target):
595 """Get the name of the done file for a commit number
596
597 Args:
598 commit_upto: Commit number to use (0..self.count-1)
599 target: Target name
600 """
601 return os.path.join(self.GetBuildDir(commit_upto, target), 'done')
602
603 def GetSizesFile(self, commit_upto, target):
604 """Get the name of the sizes file for a commit number
605
606 Args:
607 commit_upto: Commit number to use (0..self.count-1)
608 target: Target name
609 """
610 return os.path.join(self.GetBuildDir(commit_upto, target), 'sizes')
611
612 def GetFuncSizesFile(self, commit_upto, target, elf_fname):
613 """Get the name of the funcsizes file for a commit number and ELF file
614
615 Args:
616 commit_upto: Commit number to use (0..self.count-1)
617 target: Target name
618 elf_fname: Filename of elf image
619 """
620 return os.path.join(self.GetBuildDir(commit_upto, target),
621 '%s.sizes' % elf_fname.replace('/', '-'))
622
623 def GetObjdumpFile(self, commit_upto, target, elf_fname):
624 """Get the name of the objdump file for a commit number and ELF file
625
626 Args:
627 commit_upto: Commit number to use (0..self.count-1)
628 target: Target name
629 elf_fname: Filename of elf image
630 """
631 return os.path.join(self.GetBuildDir(commit_upto, target),
632 '%s.objdump' % elf_fname.replace('/', '-'))
633
634 def GetErrFile(self, commit_upto, target):
635 """Get the name of the err file for a commit number
636
637 Args:
638 commit_upto: Commit number to use (0..self.count-1)
639 target: Target name
640 """
641 output_dir = self.GetBuildDir(commit_upto, target)
642 return os.path.join(output_dir, 'err')
643
644 def FilterErrors(self, lines):
645 """Filter out errors in which we have no interest
646
647 We should probably use map().
648
649 Args:
650 lines: List of error lines, each a string
651 Returns:
652 New list with only interesting lines included
653 """
654 out_lines = []
Simon Glassf4ebfba2020-04-09 15:08:53 -0600655 if self._filter_migration_warnings:
656 text = '\n'.join(lines)
657 text = self._re_migration_warning.sub('', text)
658 lines = text.splitlines()
Simon Glassc05694f2013-04-03 11:07:16 +0000659 for line in lines:
Simon Glass9ea93812020-04-09 15:08:52 -0600660 if self.re_make_err.search(line):
661 continue
662 if self._filter_dtb_warnings and self._re_dtb_warning.search(line):
663 continue
664 out_lines.append(line)
Simon Glassc05694f2013-04-03 11:07:16 +0000665 return out_lines
666
667 def ReadFuncSizes(self, fname, fd):
668 """Read function sizes from the output of 'nm'
669
670 Args:
671 fd: File containing data to read
672 fname: Filename we are reading from (just for errors)
673
674 Returns:
675 Dictionary containing size of each function in bytes, indexed by
676 function name.
677 """
678 sym = {}
679 for line in fd.readlines():
Simon Glass86a2afe2022-07-11 19:04:11 -0600680 line = line.strip()
681 parts = line.split()
682 if line and len(parts) == 3:
683 size, type, name = line.split()
684 if type in 'tTdDbB':
685 # function names begin with '.' on 64-bit powerpc
686 if '.' in name[1:]:
687 name = 'static.' + name.split('.')[0]
688 sym[name] = sym.get(name, 0) + int(size, 16)
Simon Glassc05694f2013-04-03 11:07:16 +0000689 return sym
690
Simon Glassdb17fb82015-02-05 22:06:15 -0700691 def _ProcessConfig(self, fname):
692 """Read in a .config, autoconf.mk or autoconf.h file
693
694 This function handles all config file types. It ignores comments and
695 any #defines which don't start with CONFIG_.
696
697 Args:
698 fname: Filename to read
699
700 Returns:
701 Dictionary:
702 key: Config name (e.g. CONFIG_DM)
703 value: Config value (e.g. 1)
704 """
705 config = {}
706 if os.path.exists(fname):
707 with open(fname) as fd:
708 for line in fd:
709 line = line.strip()
710 if line.startswith('#define'):
711 values = line[8:].split(' ', 1)
712 if len(values) > 1:
713 key, value = values
714 else:
715 key = values[0]
Simon Glasscde5c302016-11-13 14:25:53 -0700716 value = '1' if self.squash_config_y else ''
Simon Glassdb17fb82015-02-05 22:06:15 -0700717 if not key.startswith('CONFIG_'):
718 continue
719 elif not line or line[0] in ['#', '*', '/']:
720 continue
721 else:
722 key, value = line.split('=', 1)
Simon Glasscde5c302016-11-13 14:25:53 -0700723 if self.squash_config_y and value == 'y':
724 value = '1'
Simon Glassdb17fb82015-02-05 22:06:15 -0700725 config[key] = value
726 return config
727
Alex Kiernan4059e302018-05-31 04:48:34 +0000728 def _ProcessEnvironment(self, fname):
729 """Read in a uboot.env file
730
731 This function reads in environment variables from a file.
732
733 Args:
734 fname: Filename to read
735
736 Returns:
737 Dictionary:
738 key: environment variable (e.g. bootlimit)
739 value: value of environment variable (e.g. 1)
740 """
741 environment = {}
742 if os.path.exists(fname):
743 with open(fname) as fd:
744 for line in fd.read().split('\0'):
745 try:
746 key, value = line.split('=', 1)
747 environment[key] = value
748 except ValueError:
749 # ignore lines we can't parse
750 pass
751 return environment
752
Simon Glassdb17fb82015-02-05 22:06:15 -0700753 def GetBuildOutcome(self, commit_upto, target, read_func_sizes,
Alex Kiernan4059e302018-05-31 04:48:34 +0000754 read_config, read_environment):
Simon Glassc05694f2013-04-03 11:07:16 +0000755 """Work out the outcome of a build.
756
757 Args:
758 commit_upto: Commit number to check (0..n-1)
759 target: Target board to check
760 read_func_sizes: True to read function size information
Simon Glassdb17fb82015-02-05 22:06:15 -0700761 read_config: True to read .config and autoconf.h files
Alex Kiernan4059e302018-05-31 04:48:34 +0000762 read_environment: True to read uboot.env files
Simon Glassc05694f2013-04-03 11:07:16 +0000763
764 Returns:
765 Outcome object
766 """
767 done_file = self.GetDoneFile(commit_upto, target)
768 sizes_file = self.GetSizesFile(commit_upto, target)
769 sizes = {}
770 func_sizes = {}
Simon Glassdb17fb82015-02-05 22:06:15 -0700771 config = {}
Alex Kiernan4059e302018-05-31 04:48:34 +0000772 environment = {}
Simon Glassc05694f2013-04-03 11:07:16 +0000773 if os.path.exists(done_file):
774 with open(done_file, 'r') as fd:
Simon Glass91c54a42019-04-26 19:02:23 -0600775 try:
776 return_code = int(fd.readline())
777 except ValueError:
778 # The file may be empty due to running out of disk space.
779 # Try a rebuild
780 return_code = 1
Simon Glassc05694f2013-04-03 11:07:16 +0000781 err_lines = []
782 err_file = self.GetErrFile(commit_upto, target)
783 if os.path.exists(err_file):
784 with open(err_file, 'r') as fd:
785 err_lines = self.FilterErrors(fd.readlines())
786
787 # Decide whether the build was ok, failed or created warnings
788 if return_code:
789 rc = OUTCOME_ERROR
790 elif len(err_lines):
791 rc = OUTCOME_WARNING
792 else:
793 rc = OUTCOME_OK
794
795 # Convert size information to our simple format
796 if os.path.exists(sizes_file):
797 with open(sizes_file, 'r') as fd:
798 for line in fd.readlines():
799 values = line.split()
800 rodata = 0
801 if len(values) > 6:
802 rodata = int(values[6], 16)
803 size_dict = {
804 'all' : int(values[0]) + int(values[1]) +
805 int(values[2]),
806 'text' : int(values[0]) - rodata,
807 'data' : int(values[1]),
808 'bss' : int(values[2]),
809 'rodata' : rodata,
810 }
811 sizes[values[5]] = size_dict
812
813 if read_func_sizes:
814 pattern = self.GetFuncSizesFile(commit_upto, target, '*')
815 for fname in glob.glob(pattern):
816 with open(fname, 'r') as fd:
817 dict_name = os.path.basename(fname).replace('.sizes',
818 '')
819 func_sizes[dict_name] = self.ReadFuncSizes(fname, fd)
820
Simon Glassdb17fb82015-02-05 22:06:15 -0700821 if read_config:
822 output_dir = self.GetBuildDir(commit_upto, target)
Simon Glasscde5c302016-11-13 14:25:53 -0700823 for name in self.config_filenames:
Simon Glassdb17fb82015-02-05 22:06:15 -0700824 fname = os.path.join(output_dir, name)
825 config[name] = self._ProcessConfig(fname)
826
Alex Kiernan4059e302018-05-31 04:48:34 +0000827 if read_environment:
828 output_dir = self.GetBuildDir(commit_upto, target)
829 fname = os.path.join(output_dir, 'uboot.env')
830 environment = self._ProcessEnvironment(fname)
831
832 return Builder.Outcome(rc, err_lines, sizes, func_sizes, config,
833 environment)
Simon Glassc05694f2013-04-03 11:07:16 +0000834
Alex Kiernan4059e302018-05-31 04:48:34 +0000835 return Builder.Outcome(OUTCOME_UNKNOWN, [], {}, {}, {}, {})
Simon Glassc05694f2013-04-03 11:07:16 +0000836
Simon Glassdb17fb82015-02-05 22:06:15 -0700837 def GetResultSummary(self, boards_selected, commit_upto, read_func_sizes,
Alex Kiernan4059e302018-05-31 04:48:34 +0000838 read_config, read_environment):
Simon Glassc05694f2013-04-03 11:07:16 +0000839 """Calculate a summary of the results of building a commit.
840
841 Args:
842 board_selected: Dict containing boards to summarise
843 commit_upto: Commit number to summarize (0..self.count-1)
844 read_func_sizes: True to read function size information
Simon Glassdb17fb82015-02-05 22:06:15 -0700845 read_config: True to read .config and autoconf.h files
Alex Kiernan4059e302018-05-31 04:48:34 +0000846 read_environment: True to read uboot.env files
Simon Glassc05694f2013-04-03 11:07:16 +0000847
848 Returns:
849 Tuple:
Simon Glass6c435622022-07-11 19:03:56 -0600850 Dict containing boards which built this commit:
851 key: board.target
852 value: Builder.Outcome object
Simon Glass03749d42014-08-28 09:43:44 -0600853 List containing a summary of error lines
Simon Glass3394c9f2014-08-28 09:43:43 -0600854 Dict keyed by error line, containing a list of the Board
855 objects with that error
Simon Glass03749d42014-08-28 09:43:44 -0600856 List containing a summary of warning lines
857 Dict keyed by error line, containing a list of the Board
858 objects with that warning
Simon Glasscad8abf2015-08-25 21:52:14 -0600859 Dictionary keyed by board.target. Each value is a dictionary:
860 key: filename - e.g. '.config'
Simon Glassdb17fb82015-02-05 22:06:15 -0700861 value is itself a dictionary:
862 key: config name
863 value: config value
Alex Kiernan4059e302018-05-31 04:48:34 +0000864 Dictionary keyed by board.target. Each value is a dictionary:
865 key: environment variable
866 value: value of environment variable
Simon Glassc05694f2013-04-03 11:07:16 +0000867 """
Simon Glass03749d42014-08-28 09:43:44 -0600868 def AddLine(lines_summary, lines_boards, line, board):
869 line = line.rstrip()
870 if line in lines_boards:
871 lines_boards[line].append(board)
872 else:
873 lines_boards[line] = [board]
874 lines_summary.append(line)
875
Simon Glassc05694f2013-04-03 11:07:16 +0000876 board_dict = {}
877 err_lines_summary = []
Simon Glass3394c9f2014-08-28 09:43:43 -0600878 err_lines_boards = {}
Simon Glass03749d42014-08-28 09:43:44 -0600879 warn_lines_summary = []
880 warn_lines_boards = {}
Simon Glassdb17fb82015-02-05 22:06:15 -0700881 config = {}
Alex Kiernan4059e302018-05-31 04:48:34 +0000882 environment = {}
Simon Glassc05694f2013-04-03 11:07:16 +0000883
Simon Glass8132f982022-07-11 19:03:57 -0600884 for brd in boards_selected.values():
885 outcome = self.GetBuildOutcome(commit_upto, brd.target,
Alex Kiernan4059e302018-05-31 04:48:34 +0000886 read_func_sizes, read_config,
887 read_environment)
Simon Glass8132f982022-07-11 19:03:57 -0600888 board_dict[brd.target] = outcome
Simon Glass03749d42014-08-28 09:43:44 -0600889 last_func = None
890 last_was_warning = False
891 for line in outcome.err_lines:
892 if line:
893 if (self._re_function.match(line) or
894 self._re_files.match(line)):
895 last_func = line
Simon Glass3394c9f2014-08-28 09:43:43 -0600896 else:
Simon Glass0db94432018-11-06 16:02:11 -0700897 is_warning = (self._re_warning.match(line) or
898 self._re_dtb_warning.match(line))
Simon Glass03749d42014-08-28 09:43:44 -0600899 is_note = self._re_note.match(line)
900 if is_warning or (last_was_warning and is_note):
901 if last_func:
902 AddLine(warn_lines_summary, warn_lines_boards,
Simon Glass8132f982022-07-11 19:03:57 -0600903 last_func, brd)
Simon Glass03749d42014-08-28 09:43:44 -0600904 AddLine(warn_lines_summary, warn_lines_boards,
Simon Glass8132f982022-07-11 19:03:57 -0600905 line, brd)
Simon Glass03749d42014-08-28 09:43:44 -0600906 else:
907 if last_func:
908 AddLine(err_lines_summary, err_lines_boards,
Simon Glass8132f982022-07-11 19:03:57 -0600909 last_func, brd)
Simon Glass03749d42014-08-28 09:43:44 -0600910 AddLine(err_lines_summary, err_lines_boards,
Simon Glass8132f982022-07-11 19:03:57 -0600911 line, brd)
Simon Glass03749d42014-08-28 09:43:44 -0600912 last_was_warning = is_warning
913 last_func = None
Simon Glass8132f982022-07-11 19:03:57 -0600914 tconfig = Config(self.config_filenames, brd.target)
Simon Glasscde5c302016-11-13 14:25:53 -0700915 for fname in self.config_filenames:
Simon Glassdb17fb82015-02-05 22:06:15 -0700916 if outcome.config:
Simon Glassc78ed662019-10-31 07:42:53 -0600917 for key, value in outcome.config[fname].items():
Simon Glasscad8abf2015-08-25 21:52:14 -0600918 tconfig.Add(fname, key, value)
Simon Glass8132f982022-07-11 19:03:57 -0600919 config[brd.target] = tconfig
Simon Glassdb17fb82015-02-05 22:06:15 -0700920
Simon Glass8132f982022-07-11 19:03:57 -0600921 tenvironment = Environment(brd.target)
Alex Kiernan4059e302018-05-31 04:48:34 +0000922 if outcome.environment:
Simon Glassc78ed662019-10-31 07:42:53 -0600923 for key, value in outcome.environment.items():
Alex Kiernan4059e302018-05-31 04:48:34 +0000924 tenvironment.Add(key, value)
Simon Glass8132f982022-07-11 19:03:57 -0600925 environment[brd.target] = tenvironment
Alex Kiernan4059e302018-05-31 04:48:34 +0000926
Simon Glass03749d42014-08-28 09:43:44 -0600927 return (board_dict, err_lines_summary, err_lines_boards,
Alex Kiernan4059e302018-05-31 04:48:34 +0000928 warn_lines_summary, warn_lines_boards, config, environment)
Simon Glassc05694f2013-04-03 11:07:16 +0000929
930 def AddOutcome(self, board_dict, arch_list, changes, char, color):
931 """Add an output to our list of outcomes for each architecture
932
933 This simple function adds failing boards (changes) to the
934 relevant architecture string, so we can print the results out
935 sorted by architecture.
936
937 Args:
938 board_dict: Dict containing all boards
939 arch_list: Dict keyed by arch name. Value is a string containing
940 a list of board names which failed for that arch.
941 changes: List of boards to add to arch_list
942 color: terminal.Colour object
943 """
944 done_arch = {}
945 for target in changes:
946 if target in board_dict:
947 arch = board_dict[target].arch
948 else:
949 arch = 'unknown'
Simon Glassf45d3742022-01-29 14:14:17 -0700950 str = self.col.build(color, ' ' + target)
Simon Glassc05694f2013-04-03 11:07:16 +0000951 if not arch in done_arch:
Simon Glassf45d3742022-01-29 14:14:17 -0700952 str = ' %s %s' % (self.col.build(color, char), str)
Simon Glassc05694f2013-04-03 11:07:16 +0000953 done_arch[arch] = True
954 if not arch in arch_list:
955 arch_list[arch] = str
956 else:
957 arch_list[arch] += str
958
959
960 def ColourNum(self, num):
961 color = self.col.RED if num > 0 else self.col.GREEN
962 if num == 0:
963 return '0'
Simon Glassf45d3742022-01-29 14:14:17 -0700964 return self.col.build(color, str(num))
Simon Glassc05694f2013-04-03 11:07:16 +0000965
966 def ResetResultSummary(self, board_selected):
967 """Reset the results summary ready for use.
968
969 Set up the base board list to be all those selected, and set the
970 error lines to empty.
971
972 Following this, calls to PrintResultSummary() will use this
973 information to work out what has changed.
974
975 Args:
976 board_selected: Dict containing boards to summarise, keyed by
977 board.target
978 """
979 self._base_board_dict = {}
Simon Glass8132f982022-07-11 19:03:57 -0600980 for brd in board_selected:
981 self._base_board_dict[brd] = Builder.Outcome(0, [], [], {}, {}, {})
Simon Glassc05694f2013-04-03 11:07:16 +0000982 self._base_err_lines = []
Simon Glass03749d42014-08-28 09:43:44 -0600983 self._base_warn_lines = []
984 self._base_err_line_boards = {}
985 self._base_warn_line_boards = {}
Simon Glasscad8abf2015-08-25 21:52:14 -0600986 self._base_config = None
Alex Kiernan4059e302018-05-31 04:48:34 +0000987 self._base_environment = None
Simon Glassc05694f2013-04-03 11:07:16 +0000988
989 def PrintFuncSizeDetail(self, fname, old, new):
990 grow, shrink, add, remove, up, down = 0, 0, 0, 0, 0, 0
991 delta, common = [], {}
992
993 for a in old:
994 if a in new:
995 common[a] = 1
996
997 for name in old:
998 if name not in common:
999 remove += 1
1000 down += old[name]
1001 delta.append([-old[name], name])
1002
1003 for name in new:
1004 if name not in common:
1005 add += 1
1006 up += new[name]
1007 delta.append([new[name], name])
1008
1009 for name in common:
1010 diff = new.get(name, 0) - old.get(name, 0)
1011 if diff > 0:
1012 grow, up = grow + 1, up + diff
1013 elif diff < 0:
1014 shrink, down = shrink + 1, down - diff
1015 delta.append([diff, name])
1016
1017 delta.sort()
1018 delta.reverse()
1019
1020 args = [add, -remove, grow, -shrink, up, -down, up - down]
Tom Rini0b48cd62017-05-22 13:48:52 -04001021 if max(args) == 0 and min(args) == 0:
Simon Glassc05694f2013-04-03 11:07:16 +00001022 return
1023 args = [self.ColourNum(x) for x in args]
1024 indent = ' ' * 15
Simon Glass02811582022-01-29 14:14:18 -07001025 tprint('%s%s: add: %s/%s, grow: %s/%s bytes: %s/%s (%s)' %
Simon Glassf45d3742022-01-29 14:14:17 -07001026 tuple([indent, self.col.build(self.col.YELLOW, fname)] + args))
Simon Glass02811582022-01-29 14:14:18 -07001027 tprint('%s %-38s %7s %7s %+7s' % (indent, 'function', 'old', 'new',
Simon Glass4433aa92014-09-05 19:00:07 -06001028 'delta'))
Simon Glassc05694f2013-04-03 11:07:16 +00001029 for diff, name in delta:
1030 if diff:
1031 color = self.col.RED if diff > 0 else self.col.GREEN
1032 msg = '%s %-38s %7s %7s %+7d' % (indent, name,
1033 old.get(name, '-'), new.get(name,'-'), diff)
Simon Glass02811582022-01-29 14:14:18 -07001034 tprint(msg, colour=color)
Simon Glassc05694f2013-04-03 11:07:16 +00001035
1036
1037 def PrintSizeDetail(self, target_list, show_bloat):
1038 """Show details size information for each board
1039
1040 Args:
1041 target_list: List of targets, each a dict containing:
1042 'target': Target name
1043 'total_diff': Total difference in bytes across all areas
1044 <part_name>: Difference for that part
1045 show_bloat: Show detail for each function
1046 """
1047 targets_by_diff = sorted(target_list, reverse=True,
1048 key=lambda x: x['_total_diff'])
1049 for result in targets_by_diff:
1050 printed_target = False
1051 for name in sorted(result):
1052 diff = result[name]
1053 if name.startswith('_'):
1054 continue
1055 if diff != 0:
1056 color = self.col.RED if diff > 0 else self.col.GREEN
1057 msg = ' %s %+d' % (name, diff)
1058 if not printed_target:
Simon Glass02811582022-01-29 14:14:18 -07001059 tprint('%10s %-15s:' % ('', result['_target']),
Simon Glass4433aa92014-09-05 19:00:07 -06001060 newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001061 printed_target = True
Simon Glass02811582022-01-29 14:14:18 -07001062 tprint(msg, colour=color, newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001063 if printed_target:
Simon Glass02811582022-01-29 14:14:18 -07001064 tprint()
Simon Glassc05694f2013-04-03 11:07:16 +00001065 if show_bloat:
1066 target = result['_target']
1067 outcome = result['_outcome']
1068 base_outcome = self._base_board_dict[target]
1069 for fname in outcome.func_sizes:
1070 self.PrintFuncSizeDetail(fname,
1071 base_outcome.func_sizes[fname],
1072 outcome.func_sizes[fname])
1073
1074
1075 def PrintSizeSummary(self, board_selected, board_dict, show_detail,
1076 show_bloat):
1077 """Print a summary of image sizes broken down by section.
1078
1079 The summary takes the form of one line per architecture. The
1080 line contains deltas for each of the sections (+ means the section
Flavio Suligoie5e3c112020-01-29 09:56:05 +01001081 got bigger, - means smaller). The numbers are the average number
Simon Glassc05694f2013-04-03 11:07:16 +00001082 of bytes that a board in this section increased by.
1083
1084 For example:
1085 powerpc: (622 boards) text -0.0
1086 arm: (285 boards) text -0.0
Simon Glassc05694f2013-04-03 11:07:16 +00001087
1088 Args:
1089 board_selected: Dict containing boards to summarise, keyed by
1090 board.target
1091 board_dict: Dict containing boards for which we built this
1092 commit, keyed by board.target. The value is an Outcome object.
Simon Glassb4002462020-03-18 09:42:43 -06001093 show_detail: Show size delta detail for each board
Simon Glassc05694f2013-04-03 11:07:16 +00001094 show_bloat: Show detail for each function
1095 """
1096 arch_list = {}
1097 arch_count = {}
1098
1099 # Calculate changes in size for different image parts
1100 # The previous sizes are in Board.sizes, for each board
1101 for target in board_dict:
1102 if target not in board_selected:
1103 continue
1104 base_sizes = self._base_board_dict[target].sizes
1105 outcome = board_dict[target]
1106 sizes = outcome.sizes
1107
1108 # Loop through the list of images, creating a dict of size
1109 # changes for each image/part. We end up with something like
1110 # {'target' : 'snapper9g45, 'data' : 5, 'u-boot-spl:text' : -4}
1111 # which means that U-Boot data increased by 5 bytes and SPL
1112 # text decreased by 4.
1113 err = {'_target' : target}
1114 for image in sizes:
1115 if image in base_sizes:
1116 base_image = base_sizes[image]
1117 # Loop through the text, data, bss parts
1118 for part in sorted(sizes[image]):
1119 diff = sizes[image][part] - base_image[part]
1120 col = None
1121 if diff:
1122 if image == 'u-boot':
1123 name = part
1124 else:
1125 name = image + ':' + part
1126 err[name] = diff
1127 arch = board_selected[target].arch
1128 if not arch in arch_count:
1129 arch_count[arch] = 1
1130 else:
1131 arch_count[arch] += 1
1132 if not sizes:
1133 pass # Only add to our list when we have some stats
1134 elif not arch in arch_list:
1135 arch_list[arch] = [err]
1136 else:
1137 arch_list[arch].append(err)
1138
1139 # We now have a list of image size changes sorted by arch
1140 # Print out a summary of these
Simon Glassc78ed662019-10-31 07:42:53 -06001141 for arch, target_list in arch_list.items():
Simon Glassc05694f2013-04-03 11:07:16 +00001142 # Get total difference for each type
1143 totals = {}
1144 for result in target_list:
1145 total = 0
Simon Glassc78ed662019-10-31 07:42:53 -06001146 for name, diff in result.items():
Simon Glassc05694f2013-04-03 11:07:16 +00001147 if name.startswith('_'):
1148 continue
1149 total += diff
1150 if name in totals:
1151 totals[name] += diff
1152 else:
1153 totals[name] = diff
1154 result['_total_diff'] = total
1155 result['_outcome'] = board_dict[result['_target']]
1156
1157 count = len(target_list)
1158 printed_arch = False
1159 for name in sorted(totals):
1160 diff = totals[name]
1161 if diff:
1162 # Display the average difference in this name for this
1163 # architecture
1164 avg_diff = float(diff) / count
1165 color = self.col.RED if avg_diff > 0 else self.col.GREEN
1166 msg = ' %s %+1.1f' % (name, avg_diff)
1167 if not printed_arch:
Simon Glass02811582022-01-29 14:14:18 -07001168 tprint('%10s: (for %d/%d boards)' % (arch, count,
Simon Glass4433aa92014-09-05 19:00:07 -06001169 arch_count[arch]), newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001170 printed_arch = True
Simon Glass02811582022-01-29 14:14:18 -07001171 tprint(msg, colour=color, newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001172
1173 if printed_arch:
Simon Glass02811582022-01-29 14:14:18 -07001174 tprint()
Simon Glassc05694f2013-04-03 11:07:16 +00001175 if show_detail:
1176 self.PrintSizeDetail(target_list, show_bloat)
1177
1178
1179 def PrintResultSummary(self, board_selected, board_dict, err_lines,
Simon Glass03749d42014-08-28 09:43:44 -06001180 err_line_boards, warn_lines, warn_line_boards,
Alex Kiernan4059e302018-05-31 04:48:34 +00001181 config, environment, show_sizes, show_detail,
1182 show_bloat, show_config, show_environment):
Simon Glassc05694f2013-04-03 11:07:16 +00001183 """Compare results with the base results and display delta.
1184
1185 Only boards mentioned in board_selected will be considered. This
1186 function is intended to be called repeatedly with the results of
1187 each commit. It therefore shows a 'diff' between what it saw in
1188 the last call and what it sees now.
1189
1190 Args:
1191 board_selected: Dict containing boards to summarise, keyed by
1192 board.target
1193 board_dict: Dict containing boards for which we built this
1194 commit, keyed by board.target. The value is an Outcome object.
1195 err_lines: A list of errors for this commit, or [] if there is
1196 none, or we don't want to print errors
Simon Glass3394c9f2014-08-28 09:43:43 -06001197 err_line_boards: Dict keyed by error line, containing a list of
1198 the Board objects with that error
Simon Glass03749d42014-08-28 09:43:44 -06001199 warn_lines: A list of warnings for this commit, or [] if there is
1200 none, or we don't want to print errors
1201 warn_line_boards: Dict keyed by warning line, containing a list of
1202 the Board objects with that warning
Simon Glassdb17fb82015-02-05 22:06:15 -07001203 config: Dictionary keyed by filename - e.g. '.config'. Each
1204 value is itself a dictionary:
1205 key: config name
1206 value: config value
Alex Kiernan4059e302018-05-31 04:48:34 +00001207 environment: Dictionary keyed by environment variable, Each
1208 value is the value of environment variable.
Simon Glassc05694f2013-04-03 11:07:16 +00001209 show_sizes: Show image size deltas
Simon Glassb4002462020-03-18 09:42:43 -06001210 show_detail: Show size delta detail for each board if show_sizes
Simon Glassc05694f2013-04-03 11:07:16 +00001211 show_bloat: Show detail for each function
Simon Glassdb17fb82015-02-05 22:06:15 -07001212 show_config: Show config changes
Alex Kiernan4059e302018-05-31 04:48:34 +00001213 show_environment: Show environment changes
Simon Glassc05694f2013-04-03 11:07:16 +00001214 """
Simon Glass03749d42014-08-28 09:43:44 -06001215 def _BoardList(line, line_boards):
Simon Glass3394c9f2014-08-28 09:43:43 -06001216 """Helper function to get a line of boards containing a line
1217
1218 Args:
1219 line: Error line to search for
Simon Glassde0fefc2020-04-09 15:08:36 -06001220 line_boards: boards to search, each a Board
Simon Glass3394c9f2014-08-28 09:43:43 -06001221 Return:
Simon Glassde0fefc2020-04-09 15:08:36 -06001222 List of boards with that error line, or [] if the user has not
1223 requested such a list
Simon Glass3394c9f2014-08-28 09:43:43 -06001224 """
Simon Glass5df45222022-07-11 19:04:00 -06001225 brds = []
Simon Glassde0fefc2020-04-09 15:08:36 -06001226 board_set = set()
Simon Glass3394c9f2014-08-28 09:43:43 -06001227 if self._list_error_boards:
Simon Glass8132f982022-07-11 19:03:57 -06001228 for brd in line_boards[line]:
1229 if not brd in board_set:
Simon Glass5df45222022-07-11 19:04:00 -06001230 brds.append(brd)
Simon Glass8132f982022-07-11 19:03:57 -06001231 board_set.add(brd)
Simon Glass5df45222022-07-11 19:04:00 -06001232 return brds
Simon Glass3394c9f2014-08-28 09:43:43 -06001233
Simon Glass03749d42014-08-28 09:43:44 -06001234 def _CalcErrorDelta(base_lines, base_line_boards, lines, line_boards,
1235 char):
Simon Glassde0fefc2020-04-09 15:08:36 -06001236 """Calculate the required output based on changes in errors
1237
1238 Args:
1239 base_lines: List of errors/warnings for previous commit
1240 base_line_boards: Dict keyed by error line, containing a list
1241 of the Board objects with that error in the previous commit
1242 lines: List of errors/warning for this commit, each a str
1243 line_boards: Dict keyed by error line, containing a list
1244 of the Board objects with that error in this commit
1245 char: Character representing error ('') or warning ('w'). The
1246 broken ('+') or fixed ('-') characters are added in this
1247 function
1248
1249 Returns:
1250 Tuple
1251 List of ErrLine objects for 'better' lines
1252 List of ErrLine objects for 'worse' lines
1253 """
Simon Glass03749d42014-08-28 09:43:44 -06001254 better_lines = []
1255 worse_lines = []
1256 for line in lines:
1257 if line not in base_lines:
Simon Glassde0fefc2020-04-09 15:08:36 -06001258 errline = ErrLine(char + '+', _BoardList(line, line_boards),
1259 line)
1260 worse_lines.append(errline)
Simon Glass03749d42014-08-28 09:43:44 -06001261 for line in base_lines:
1262 if line not in lines:
Simon Glassde0fefc2020-04-09 15:08:36 -06001263 errline = ErrLine(char + '-',
1264 _BoardList(line, base_line_boards), line)
1265 better_lines.append(errline)
Simon Glass03749d42014-08-28 09:43:44 -06001266 return better_lines, worse_lines
1267
Simon Glassdb17fb82015-02-05 22:06:15 -07001268 def _CalcConfig(delta, name, config):
1269 """Calculate configuration changes
1270
1271 Args:
1272 delta: Type of the delta, e.g. '+'
1273 name: name of the file which changed (e.g. .config)
1274 config: configuration change dictionary
1275 key: config name
1276 value: config value
1277 Returns:
1278 String containing the configuration changes which can be
1279 printed
1280 """
1281 out = ''
1282 for key in sorted(config.keys()):
1283 out += '%s=%s ' % (key, config[key])
Simon Glasscad8abf2015-08-25 21:52:14 -06001284 return '%s %s: %s' % (delta, name, out)
Simon Glassdb17fb82015-02-05 22:06:15 -07001285
Simon Glasscad8abf2015-08-25 21:52:14 -06001286 def _AddConfig(lines, name, config_plus, config_minus, config_change):
1287 """Add changes in configuration to a list
Simon Glassdb17fb82015-02-05 22:06:15 -07001288
1289 Args:
Simon Glasscad8abf2015-08-25 21:52:14 -06001290 lines: list to add to
1291 name: config file name
Simon Glassdb17fb82015-02-05 22:06:15 -07001292 config_plus: configurations added, dictionary
1293 key: config name
1294 value: config value
1295 config_minus: configurations removed, dictionary
1296 key: config name
1297 value: config value
1298 config_change: configurations changed, dictionary
1299 key: config name
1300 value: config value
1301 """
1302 if config_plus:
Simon Glasscad8abf2015-08-25 21:52:14 -06001303 lines.append(_CalcConfig('+', name, config_plus))
Simon Glassdb17fb82015-02-05 22:06:15 -07001304 if config_minus:
Simon Glasscad8abf2015-08-25 21:52:14 -06001305 lines.append(_CalcConfig('-', name, config_minus))
Simon Glassdb17fb82015-02-05 22:06:15 -07001306 if config_change:
Simon Glasscad8abf2015-08-25 21:52:14 -06001307 lines.append(_CalcConfig('c', name, config_change))
1308
1309 def _OutputConfigInfo(lines):
1310 for line in lines:
1311 if not line:
1312 continue
1313 if line[0] == '+':
1314 col = self.col.GREEN
1315 elif line[0] == '-':
1316 col = self.col.RED
1317 elif line[0] == 'c':
1318 col = self.col.YELLOW
Simon Glass02811582022-01-29 14:14:18 -07001319 tprint(' ' + line, newline=True, colour=col)
Simon Glasscad8abf2015-08-25 21:52:14 -06001320
Simon Glassac500222020-04-09 15:08:28 -06001321 def _OutputErrLines(err_lines, colour):
1322 """Output the line of error/warning lines, if not empty
1323
1324 Also increments self._error_lines if err_lines not empty
1325
1326 Args:
Simon Glassde0fefc2020-04-09 15:08:36 -06001327 err_lines: List of ErrLine objects, each an error or warning
1328 line, possibly including a list of boards with that
1329 error/warning
Simon Glassac500222020-04-09 15:08:28 -06001330 colour: Colour to use for output
1331 """
1332 if err_lines:
Simon Glassea49f9b2020-04-09 15:08:37 -06001333 out_list = []
Simon Glassde0fefc2020-04-09 15:08:36 -06001334 for line in err_lines:
Simon Glass5df45222022-07-11 19:04:00 -06001335 names = [brd.target for brd in line.brds]
Simon Glass070589b2020-04-09 15:08:38 -06001336 board_str = ' '.join(names) if names else ''
Simon Glassea49f9b2020-04-09 15:08:37 -06001337 if board_str:
Simon Glassf45d3742022-01-29 14:14:17 -07001338 out = self.col.build(colour, line.char + '(')
1339 out += self.col.build(self.col.MAGENTA, board_str,
Simon Glassea49f9b2020-04-09 15:08:37 -06001340 bright=False)
Simon Glassf45d3742022-01-29 14:14:17 -07001341 out += self.col.build(colour, ') %s' % line.errline)
Simon Glassea49f9b2020-04-09 15:08:37 -06001342 else:
Simon Glassf45d3742022-01-29 14:14:17 -07001343 out = self.col.build(colour, line.char + line.errline)
Simon Glassea49f9b2020-04-09 15:08:37 -06001344 out_list.append(out)
Simon Glass02811582022-01-29 14:14:18 -07001345 tprint('\n'.join(out_list))
Simon Glassac500222020-04-09 15:08:28 -06001346 self._error_lines += 1
1347
Simon Glassdb17fb82015-02-05 22:06:15 -07001348
Simon Glass454507f2018-11-06 16:02:12 -07001349 ok_boards = [] # List of boards fixed since last commit
Simon Glass071a1782018-11-06 16:02:13 -07001350 warn_boards = [] # List of boards with warnings since last commit
Simon Glass454507f2018-11-06 16:02:12 -07001351 err_boards = [] # List of new broken boards since last commit
1352 new_boards = [] # List of boards that didn't exist last time
1353 unknown_boards = [] # List of boards that were not built
Simon Glassc05694f2013-04-03 11:07:16 +00001354
1355 for target in board_dict:
1356 if target not in board_selected:
1357 continue
1358
1359 # If the board was built last time, add its outcome to a list
1360 if target in self._base_board_dict:
1361 base_outcome = self._base_board_dict[target].rc
1362 outcome = board_dict[target]
1363 if outcome.rc == OUTCOME_UNKNOWN:
Simon Glass454507f2018-11-06 16:02:12 -07001364 unknown_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001365 elif outcome.rc < base_outcome:
Simon Glass071a1782018-11-06 16:02:13 -07001366 if outcome.rc == OUTCOME_WARNING:
1367 warn_boards.append(target)
1368 else:
1369 ok_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001370 elif outcome.rc > base_outcome:
Simon Glass071a1782018-11-06 16:02:13 -07001371 if outcome.rc == OUTCOME_WARNING:
1372 warn_boards.append(target)
1373 else:
1374 err_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001375 else:
Simon Glass454507f2018-11-06 16:02:12 -07001376 new_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001377
Simon Glassac500222020-04-09 15:08:28 -06001378 # Get a list of errors and warnings that have appeared, and disappeared
Simon Glass03749d42014-08-28 09:43:44 -06001379 better_err, worse_err = _CalcErrorDelta(self._base_err_lines,
1380 self._base_err_line_boards, err_lines, err_line_boards, '')
1381 better_warn, worse_warn = _CalcErrorDelta(self._base_warn_lines,
1382 self._base_warn_line_boards, warn_lines, warn_line_boards, 'w')
Simon Glassc05694f2013-04-03 11:07:16 +00001383
Simon Glass6c435622022-07-11 19:03:56 -06001384 # For the IDE mode, print out all the output
1385 if self._ide:
1386 outcome = board_dict[target]
1387 for line in outcome.err_lines:
1388 sys.stderr.write(line)
1389
Simon Glassc05694f2013-04-03 11:07:16 +00001390 # Display results by arch
Simon Glass6c435622022-07-11 19:03:56 -06001391 elif any((ok_boards, warn_boards, err_boards, unknown_boards, new_boards,
Simon Glass071a1782018-11-06 16:02:13 -07001392 worse_err, better_err, worse_warn, better_warn)):
Simon Glassc05694f2013-04-03 11:07:16 +00001393 arch_list = {}
Simon Glass454507f2018-11-06 16:02:12 -07001394 self.AddOutcome(board_selected, arch_list, ok_boards, '',
Simon Glassc05694f2013-04-03 11:07:16 +00001395 self.col.GREEN)
Simon Glass071a1782018-11-06 16:02:13 -07001396 self.AddOutcome(board_selected, arch_list, warn_boards, 'w+',
1397 self.col.YELLOW)
Simon Glass454507f2018-11-06 16:02:12 -07001398 self.AddOutcome(board_selected, arch_list, err_boards, '+',
Simon Glassc05694f2013-04-03 11:07:16 +00001399 self.col.RED)
Simon Glass454507f2018-11-06 16:02:12 -07001400 self.AddOutcome(board_selected, arch_list, new_boards, '*', self.col.BLUE)
Simon Glassc05694f2013-04-03 11:07:16 +00001401 if self._show_unknown:
Simon Glass454507f2018-11-06 16:02:12 -07001402 self.AddOutcome(board_selected, arch_list, unknown_boards, '?',
Simon Glassc05694f2013-04-03 11:07:16 +00001403 self.col.MAGENTA)
Simon Glassc78ed662019-10-31 07:42:53 -06001404 for arch, target_list in arch_list.items():
Simon Glass02811582022-01-29 14:14:18 -07001405 tprint('%10s: %s' % (arch, target_list))
Simon Glassbb4dffb2014-08-09 15:33:06 -06001406 self._error_lines += 1
Simon Glassac500222020-04-09 15:08:28 -06001407 _OutputErrLines(better_err, colour=self.col.GREEN)
1408 _OutputErrLines(worse_err, colour=self.col.RED)
1409 _OutputErrLines(better_warn, colour=self.col.CYAN)
Simon Glass564ddac2020-04-09 15:08:35 -06001410 _OutputErrLines(worse_warn, colour=self.col.YELLOW)
Simon Glassc05694f2013-04-03 11:07:16 +00001411
1412 if show_sizes:
1413 self.PrintSizeSummary(board_selected, board_dict, show_detail,
1414 show_bloat)
1415
Alex Kiernan4059e302018-05-31 04:48:34 +00001416 if show_environment and self._base_environment:
1417 lines = []
1418
1419 for target in board_dict:
1420 if target not in board_selected:
1421 continue
1422
1423 tbase = self._base_environment[target]
1424 tenvironment = environment[target]
1425 environment_plus = {}
1426 environment_minus = {}
1427 environment_change = {}
1428 base = tbase.environment
Simon Glassc78ed662019-10-31 07:42:53 -06001429 for key, value in tenvironment.environment.items():
Alex Kiernan4059e302018-05-31 04:48:34 +00001430 if key not in base:
1431 environment_plus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001432 for key, value in base.items():
Alex Kiernan4059e302018-05-31 04:48:34 +00001433 if key not in tenvironment.environment:
1434 environment_minus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001435 for key, value in base.items():
Alex Kiernan4059e302018-05-31 04:48:34 +00001436 new_value = tenvironment.environment.get(key)
1437 if new_value and value != new_value:
1438 desc = '%s -> %s' % (value, new_value)
1439 environment_change[key] = desc
1440
1441 _AddConfig(lines, target, environment_plus, environment_minus,
1442 environment_change)
1443
1444 _OutputConfigInfo(lines)
1445
Simon Glasscad8abf2015-08-25 21:52:14 -06001446 if show_config and self._base_config:
1447 summary = {}
1448 arch_config_plus = {}
1449 arch_config_minus = {}
1450 arch_config_change = {}
1451 arch_list = []
1452
1453 for target in board_dict:
1454 if target not in board_selected:
1455 continue
1456 arch = board_selected[target].arch
1457 if arch not in arch_list:
1458 arch_list.append(arch)
1459
1460 for arch in arch_list:
1461 arch_config_plus[arch] = {}
1462 arch_config_minus[arch] = {}
1463 arch_config_change[arch] = {}
Simon Glasscde5c302016-11-13 14:25:53 -07001464 for name in self.config_filenames:
Simon Glasscad8abf2015-08-25 21:52:14 -06001465 arch_config_plus[arch][name] = {}
1466 arch_config_minus[arch][name] = {}
1467 arch_config_change[arch][name] = {}
1468
1469 for target in board_dict:
1470 if target not in board_selected:
Simon Glassdb17fb82015-02-05 22:06:15 -07001471 continue
Simon Glasscad8abf2015-08-25 21:52:14 -06001472
1473 arch = board_selected[target].arch
1474
1475 all_config_plus = {}
1476 all_config_minus = {}
1477 all_config_change = {}
1478 tbase = self._base_config[target]
1479 tconfig = config[target]
1480 lines = []
Simon Glasscde5c302016-11-13 14:25:53 -07001481 for name in self.config_filenames:
Simon Glasscad8abf2015-08-25 21:52:14 -06001482 if not tconfig.config[name]:
1483 continue
1484 config_plus = {}
1485 config_minus = {}
1486 config_change = {}
1487 base = tbase.config[name]
Simon Glassc78ed662019-10-31 07:42:53 -06001488 for key, value in tconfig.config[name].items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001489 if key not in base:
1490 config_plus[key] = value
1491 all_config_plus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001492 for key, value in base.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001493 if key not in tconfig.config[name]:
1494 config_minus[key] = value
1495 all_config_minus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001496 for key, value in base.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001497 new_value = tconfig.config.get(key)
1498 if new_value and value != new_value:
1499 desc = '%s -> %s' % (value, new_value)
1500 config_change[key] = desc
1501 all_config_change[key] = desc
1502
1503 arch_config_plus[arch][name].update(config_plus)
1504 arch_config_minus[arch][name].update(config_minus)
1505 arch_config_change[arch][name].update(config_change)
1506
1507 _AddConfig(lines, name, config_plus, config_minus,
1508 config_change)
1509 _AddConfig(lines, 'all', all_config_plus, all_config_minus,
1510 all_config_change)
1511 summary[target] = '\n'.join(lines)
1512
1513 lines_by_target = {}
Simon Glassc78ed662019-10-31 07:42:53 -06001514 for target, lines in summary.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001515 if lines in lines_by_target:
1516 lines_by_target[lines].append(target)
1517 else:
1518 lines_by_target[lines] = [target]
1519
1520 for arch in arch_list:
1521 lines = []
1522 all_plus = {}
1523 all_minus = {}
1524 all_change = {}
Simon Glasscde5c302016-11-13 14:25:53 -07001525 for name in self.config_filenames:
Simon Glasscad8abf2015-08-25 21:52:14 -06001526 all_plus.update(arch_config_plus[arch][name])
1527 all_minus.update(arch_config_minus[arch][name])
1528 all_change.update(arch_config_change[arch][name])
1529 _AddConfig(lines, name, arch_config_plus[arch][name],
1530 arch_config_minus[arch][name],
1531 arch_config_change[arch][name])
1532 _AddConfig(lines, 'all', all_plus, all_minus, all_change)
1533 #arch_summary[target] = '\n'.join(lines)
1534 if lines:
Simon Glass02811582022-01-29 14:14:18 -07001535 tprint('%s:' % arch)
Simon Glasscad8abf2015-08-25 21:52:14 -06001536 _OutputConfigInfo(lines)
1537
Simon Glassc78ed662019-10-31 07:42:53 -06001538 for lines, targets in lines_by_target.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001539 if not lines:
1540 continue
Simon Glass02811582022-01-29 14:14:18 -07001541 tprint('%s :' % ' '.join(sorted(targets)))
Simon Glasscad8abf2015-08-25 21:52:14 -06001542 _OutputConfigInfo(lines.split('\n'))
1543
Simon Glassdb17fb82015-02-05 22:06:15 -07001544
Simon Glassc05694f2013-04-03 11:07:16 +00001545 # Save our updated information for the next call to this function
1546 self._base_board_dict = board_dict
1547 self._base_err_lines = err_lines
Simon Glass03749d42014-08-28 09:43:44 -06001548 self._base_warn_lines = warn_lines
1549 self._base_err_line_boards = err_line_boards
1550 self._base_warn_line_boards = warn_line_boards
Simon Glassdb17fb82015-02-05 22:06:15 -07001551 self._base_config = config
Alex Kiernan4059e302018-05-31 04:48:34 +00001552 self._base_environment = environment
Simon Glassc05694f2013-04-03 11:07:16 +00001553
1554 # Get a list of boards that did not get built, if needed
1555 not_built = []
Simon Glass8132f982022-07-11 19:03:57 -06001556 for brd in board_selected:
1557 if not brd in board_dict:
1558 not_built.append(brd)
Simon Glassc05694f2013-04-03 11:07:16 +00001559 if not_built:
Simon Glass02811582022-01-29 14:14:18 -07001560 tprint("Boards not built (%d): %s" % (len(not_built),
Simon Glass4433aa92014-09-05 19:00:07 -06001561 ', '.join(not_built)))
Simon Glassc05694f2013-04-03 11:07:16 +00001562
Simon Glasseb48bbc2014-08-09 15:33:02 -06001563 def ProduceResultSummary(self, commit_upto, commits, board_selected):
Simon Glass03749d42014-08-28 09:43:44 -06001564 (board_dict, err_lines, err_line_boards, warn_lines,
Alex Kiernan4059e302018-05-31 04:48:34 +00001565 warn_line_boards, config, environment) = self.GetResultSummary(
Simon Glass3394c9f2014-08-28 09:43:43 -06001566 board_selected, commit_upto,
Simon Glassdb17fb82015-02-05 22:06:15 -07001567 read_func_sizes=self._show_bloat,
Alex Kiernan4059e302018-05-31 04:48:34 +00001568 read_config=self._show_config,
1569 read_environment=self._show_environment)
Simon Glasseb48bbc2014-08-09 15:33:02 -06001570 if commits:
1571 msg = '%02d: %s' % (commit_upto + 1,
1572 commits[commit_upto].subject)
Simon Glass02811582022-01-29 14:14:18 -07001573 tprint(msg, colour=self.col.BLUE)
Simon Glasseb48bbc2014-08-09 15:33:02 -06001574 self.PrintResultSummary(board_selected, board_dict,
Simon Glass3394c9f2014-08-28 09:43:43 -06001575 err_lines if self._show_errors else [], err_line_boards,
Simon Glass03749d42014-08-28 09:43:44 -06001576 warn_lines if self._show_errors else [], warn_line_boards,
Alex Kiernan4059e302018-05-31 04:48:34 +00001577 config, environment, self._show_sizes, self._show_detail,
1578 self._show_bloat, self._show_config, self._show_environment)
Simon Glassc05694f2013-04-03 11:07:16 +00001579
Simon Glasseb48bbc2014-08-09 15:33:02 -06001580 def ShowSummary(self, commits, board_selected):
Simon Glassc05694f2013-04-03 11:07:16 +00001581 """Show a build summary for U-Boot for a given board list.
1582
1583 Reset the result summary, then repeatedly call GetResultSummary on
1584 each commit's results, then display the differences we see.
1585
1586 Args:
1587 commit: Commit objects to summarise
1588 board_selected: Dict containing boards to summarise
Simon Glassc05694f2013-04-03 11:07:16 +00001589 """
Simon Glassd326ad72014-08-09 15:32:59 -06001590 self.commit_count = len(commits) if commits else 1
Simon Glassc05694f2013-04-03 11:07:16 +00001591 self.commits = commits
1592 self.ResetResultSummary(board_selected)
Simon Glassbb4dffb2014-08-09 15:33:06 -06001593 self._error_lines = 0
Simon Glassc05694f2013-04-03 11:07:16 +00001594
1595 for commit_upto in range(0, self.commit_count, self._step):
Simon Glasseb48bbc2014-08-09 15:33:02 -06001596 self.ProduceResultSummary(commit_upto, commits, board_selected)
Simon Glassbb4dffb2014-08-09 15:33:06 -06001597 if not self._error_lines:
Simon Glass02811582022-01-29 14:14:18 -07001598 tprint('(no errors to report)', colour=self.col.GREEN)
Simon Glassc05694f2013-04-03 11:07:16 +00001599
1600
1601 def SetupBuild(self, board_selected, commits):
1602 """Set up ready to start a build.
1603
1604 Args:
1605 board_selected: Selected boards to build
1606 commits: Selected commits to build
1607 """
1608 # First work out how many commits we will build
Simon Glassc78ed662019-10-31 07:42:53 -06001609 count = (self.commit_count + self._step - 1) // self._step
Simon Glassc05694f2013-04-03 11:07:16 +00001610 self.count = len(board_selected) * count
1611 self.upto = self.warned = self.fail = 0
1612 self._timestamps = collections.deque()
1613
Simon Glassc05694f2013-04-03 11:07:16 +00001614 def GetThreadDir(self, thread_num):
1615 """Get the directory path to the working dir for a thread.
1616
1617 Args:
Simon Glassc635d892021-01-30 22:17:46 -07001618 thread_num: Number of thread to check (-1 for main process, which
1619 is treated as 0)
Simon Glassc05694f2013-04-03 11:07:16 +00001620 """
Simon Glassb6eb8cf2020-03-18 09:42:42 -06001621 if self.work_in_output:
1622 return self._working_dir
Simon Glassc635d892021-01-30 22:17:46 -07001623 return os.path.join(self._working_dir, '%02d' % max(thread_num, 0))
Simon Glassc05694f2013-04-03 11:07:16 +00001624
Simon Glassd326ad72014-08-09 15:32:59 -06001625 def _PrepareThread(self, thread_num, setup_git):
Simon Glassc05694f2013-04-03 11:07:16 +00001626 """Prepare the working directory for a thread.
1627
1628 This clones or fetches the repo into the thread's work directory.
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001629 Optionally, it can create a linked working tree of the repo in the
1630 thread's work directory instead.
Simon Glassc05694f2013-04-03 11:07:16 +00001631
1632 Args:
1633 thread_num: Thread number (0, 1, ...)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001634 setup_git:
1635 'clone' to set up a git clone
1636 'worktree' to set up a git worktree
Simon Glassc05694f2013-04-03 11:07:16 +00001637 """
1638 thread_dir = self.GetThreadDir(thread_num)
Simon Glass4a1e88b2014-08-09 15:33:00 -06001639 builderthread.Mkdir(thread_dir)
Simon Glassc05694f2013-04-03 11:07:16 +00001640 git_dir = os.path.join(thread_dir, '.git')
1641
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001642 # Create a worktree or a git repo clone for this thread if it
1643 # doesn't already exist
Simon Glassd326ad72014-08-09 15:32:59 -06001644 if setup_git and self.git_dir:
Simon Glassc05694f2013-04-03 11:07:16 +00001645 src_dir = os.path.abspath(self.git_dir)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001646 if os.path.isdir(git_dir):
1647 # This is a clone of the src_dir repo, we can keep using
1648 # it but need to fetch from src_dir.
Simon Glass02811582022-01-29 14:14:18 -07001649 tprint('\rFetching repo for thread %d' % thread_num,
Simon Glass43054932020-04-09 15:08:43 -06001650 newline=False)
Simon Glass761648b2022-01-29 14:14:11 -07001651 gitutil.fetch(git_dir, thread_dir)
Simon Glass02811582022-01-29 14:14:18 -07001652 terminal.print_clear()
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001653 elif os.path.isfile(git_dir):
1654 # This is a worktree of the src_dir repo, we don't need to
1655 # create it again or update it in any way.
1656 pass
1657 elif os.path.exists(git_dir):
1658 # Don't know what could trigger this, but we probably
1659 # can't create a git worktree/clone here.
1660 raise ValueError('Git dir %s exists, but is not a file '
1661 'or a directory.' % git_dir)
1662 elif setup_git == 'worktree':
Simon Glass02811582022-01-29 14:14:18 -07001663 tprint('\rChecking out worktree for thread %d' % thread_num,
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001664 newline=False)
Simon Glass761648b2022-01-29 14:14:11 -07001665 gitutil.add_worktree(src_dir, thread_dir)
Simon Glass02811582022-01-29 14:14:18 -07001666 terminal.print_clear()
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001667 elif setup_git == 'clone' or setup_git == True:
Simon Glass02811582022-01-29 14:14:18 -07001668 tprint('\rCloning repo for thread %d' % thread_num,
Simon Glass2e2a6e62016-09-18 16:48:31 -06001669 newline=False)
Simon Glass761648b2022-01-29 14:14:11 -07001670 gitutil.clone(src_dir, thread_dir)
Simon Glass02811582022-01-29 14:14:18 -07001671 terminal.print_clear()
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001672 else:
1673 raise ValueError("Can't setup git repo with %s." % setup_git)
Simon Glassc05694f2013-04-03 11:07:16 +00001674
Simon Glassd326ad72014-08-09 15:32:59 -06001675 def _PrepareWorkingSpace(self, max_threads, setup_git):
Simon Glassc05694f2013-04-03 11:07:16 +00001676 """Prepare the working directory for use.
1677
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001678 Set up the git repo for each thread. Creates a linked working tree
1679 if git-worktree is available, or clones the repo if it isn't.
Simon Glassc05694f2013-04-03 11:07:16 +00001680
1681 Args:
Simon Glassc635d892021-01-30 22:17:46 -07001682 max_threads: Maximum number of threads we expect to need. If 0 then
1683 1 is set up, since the main process still needs somewhere to
1684 work
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001685 setup_git: True to set up a git worktree or a git clone
Simon Glassc05694f2013-04-03 11:07:16 +00001686 """
Simon Glass4a1e88b2014-08-09 15:33:00 -06001687 builderthread.Mkdir(self._working_dir)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001688 if setup_git and self.git_dir:
1689 src_dir = os.path.abspath(self.git_dir)
Simon Glass761648b2022-01-29 14:14:11 -07001690 if gitutil.check_worktree_is_available(src_dir):
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001691 setup_git = 'worktree'
1692 # If we previously added a worktree but the directory for it
1693 # got deleted, we need to prune its files from the repo so
1694 # that we can check out another in its place.
Simon Glass761648b2022-01-29 14:14:11 -07001695 gitutil.prune_worktrees(src_dir)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001696 else:
1697 setup_git = 'clone'
Simon Glassc635d892021-01-30 22:17:46 -07001698
1699 # Always do at least one thread
1700 for thread in range(max(max_threads, 1)):
Simon Glassd326ad72014-08-09 15:32:59 -06001701 self._PrepareThread(thread, setup_git)
Simon Glassc05694f2013-04-03 11:07:16 +00001702
Simon Glass5dc1ca72020-03-18 09:42:45 -06001703 def _GetOutputSpaceRemovals(self):
Simon Glassc05694f2013-04-03 11:07:16 +00001704 """Get the output directories ready to receive files.
1705
Simon Glass5dc1ca72020-03-18 09:42:45 -06001706 Figure out what needs to be deleted in the output directory before it
1707 can be used. We only delete old buildman directories which have the
1708 expected name pattern. See _GetOutputDir().
1709
1710 Returns:
1711 List of full paths of directories to remove
Simon Glassc05694f2013-04-03 11:07:16 +00001712 """
Simon Glassb9a9b7c2014-12-01 17:33:53 -07001713 if not self.commits:
1714 return
Simon Glassc05694f2013-04-03 11:07:16 +00001715 dir_list = []
1716 for commit_upto in range(self.commit_count):
1717 dir_list.append(self._GetOutputDir(commit_upto))
1718
Simon Glass83cb6cc2016-09-18 16:48:32 -06001719 to_remove = []
Simon Glassc05694f2013-04-03 11:07:16 +00001720 for dirname in glob.glob(os.path.join(self.base_dir, '*')):
1721 if dirname not in dir_list:
Simon Glass5dc1ca72020-03-18 09:42:45 -06001722 leaf = dirname[len(self.base_dir) + 1:]
Ovidiu Panaitee8e9cb2020-05-15 09:30:12 +03001723 m = re.match('[0-9]+_g[0-9a-f]+_.*', leaf)
Simon Glass5dc1ca72020-03-18 09:42:45 -06001724 if m:
1725 to_remove.append(dirname)
1726 return to_remove
1727
1728 def _PrepareOutputSpace(self):
1729 """Get the output directories ready to receive files.
1730
1731 We delete any output directories which look like ones we need to
1732 create. Having left over directories is confusing when the user wants
1733 to check the output manually.
1734 """
1735 to_remove = self._GetOutputSpaceRemovals()
Simon Glass83cb6cc2016-09-18 16:48:32 -06001736 if to_remove:
Simon Glass02811582022-01-29 14:14:18 -07001737 tprint('Removing %d old build directories...' % len(to_remove),
Simon Glass83cb6cc2016-09-18 16:48:32 -06001738 newline=False)
1739 for dirname in to_remove:
Simon Glassc05694f2013-04-03 11:07:16 +00001740 shutil.rmtree(dirname)
Simon Glass02811582022-01-29 14:14:18 -07001741 terminal.print_clear()
Simon Glassc05694f2013-04-03 11:07:16 +00001742
Simon Glass78e418e2014-08-09 15:33:03 -06001743 def BuildBoards(self, commits, board_selected, keep_outputs, verbose):
Simon Glassc05694f2013-04-03 11:07:16 +00001744 """Build all commits for a list of boards
1745
1746 Args:
1747 commits: List of commits to be build, each a Commit object
1748 boards_selected: Dict of selected boards, key is target name,
1749 value is Board object
Simon Glassc05694f2013-04-03 11:07:16 +00001750 keep_outputs: True to save build output files
Simon Glass78e418e2014-08-09 15:33:03 -06001751 verbose: Display build results as they are completed
Simon Glassc2f91072014-08-28 09:43:39 -06001752 Returns:
1753 Tuple containing:
1754 - number of boards that failed to build
1755 - number of boards that issued warnings
Simon Glass9bf9a722021-04-11 16:27:27 +12001756 - list of thread exceptions raised
Simon Glassc05694f2013-04-03 11:07:16 +00001757 """
Simon Glassd326ad72014-08-09 15:32:59 -06001758 self.commit_count = len(commits) if commits else 1
Simon Glassc05694f2013-04-03 11:07:16 +00001759 self.commits = commits
Simon Glass78e418e2014-08-09 15:33:03 -06001760 self._verbose = verbose
Simon Glassc05694f2013-04-03 11:07:16 +00001761
1762 self.ResetResultSummary(board_selected)
Thierry Reding336d5bd2014-08-19 10:22:39 +02001763 builderthread.Mkdir(self.base_dir, parents = True)
Simon Glassd326ad72014-08-09 15:32:59 -06001764 self._PrepareWorkingSpace(min(self.num_threads, len(board_selected)),
1765 commits is not None)
Simon Glassc05694f2013-04-03 11:07:16 +00001766 self._PrepareOutputSpace()
Simon Glass6c435622022-07-11 19:03:56 -06001767 if not self._ide:
1768 tprint('\rStarting build...', newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001769 self.SetupBuild(board_selected, commits)
1770 self.ProcessResult(None)
Simon Glass9bf9a722021-04-11 16:27:27 +12001771 self.thread_exceptions = []
Simon Glassc05694f2013-04-03 11:07:16 +00001772 # Create jobs to build all commits for each board
Simon Glassc78ed662019-10-31 07:42:53 -06001773 for brd in board_selected.values():
Simon Glass4a1e88b2014-08-09 15:33:00 -06001774 job = builderthread.BuilderJob()
Simon Glass8132f982022-07-11 19:03:57 -06001775 job.brd = brd
Simon Glassc05694f2013-04-03 11:07:16 +00001776 job.commits = commits
1777 job.keep_outputs = keep_outputs
Simon Glassb6eb8cf2020-03-18 09:42:42 -06001778 job.work_in_output = self.work_in_output
Simon Glasse5650a82022-01-22 05:07:33 -07001779 job.adjust_cfg = self.adjust_cfg
Simon Glassc05694f2013-04-03 11:07:16 +00001780 job.step = self._step
Simon Glassc635d892021-01-30 22:17:46 -07001781 if self.num_threads:
1782 self.queue.put(job)
1783 else:
Simon Glasse36fe012022-02-11 13:23:19 -07001784 self._single_builder.RunJob(job)
Simon Glassc05694f2013-04-03 11:07:16 +00001785
Simon Glassc635d892021-01-30 22:17:46 -07001786 if self.num_threads:
1787 term = threading.Thread(target=self.queue.join)
1788 term.setDaemon(True)
1789 term.start()
1790 while term.is_alive():
1791 term.join(100)
Simon Glassc05694f2013-04-03 11:07:16 +00001792
Simon Glassc635d892021-01-30 22:17:46 -07001793 # Wait until we have processed all output
1794 self.out_queue.join()
Simon Glass6c435622022-07-11 19:03:56 -06001795 if not self._ide:
1796 tprint()
Simon Glass726ae812020-04-09 15:08:47 -06001797
Simon Glass6c435622022-07-11 19:03:56 -06001798 msg = 'Completed: %d total built' % self.count
1799 if self.already_done:
1800 msg += ' (%d previously' % self.already_done
1801 if self.already_done != self.count:
1802 msg += ', %d newly' % (self.count - self.already_done)
1803 msg += ')'
1804 duration = datetime.now() - self._start_time
1805 if duration > timedelta(microseconds=1000000):
1806 if duration.microseconds >= 500000:
1807 duration = duration + timedelta(seconds=1)
1808 duration = duration - timedelta(microseconds=duration.microseconds)
1809 rate = float(self.count) / duration.total_seconds()
1810 msg += ', duration %s, rate %1.2f' % (duration, rate)
1811 tprint(msg)
1812 if self.thread_exceptions:
1813 tprint('Failed: %d thread exceptions' % len(self.thread_exceptions),
1814 colour=self.col.RED)
Simon Glass726ae812020-04-09 15:08:47 -06001815
Simon Glass9bf9a722021-04-11 16:27:27 +12001816 return (self.fail, self.warned, self.thread_exceptions)