blob: cb3628a8a085726e92eaeac0af617f5dbad0000a [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 Glasscf91d312023-07-19 17:48:52 -0600258 allow_missing=False, no_lto=False, reproducible_builds=False,
259 force_build=False, force_build_failures=False,
260 force_reconfig=False, in_tree=False,
261 force_config_on_failure=False, make_func=None):
Simon Glassc05694f2013-04-03 11:07:16 +0000262 """Create a new Builder object
263
264 Args:
265 toolchains: Toolchains object to use for building
266 base_dir: Base directory to use for builder
267 git_dir: Git directory containing source repository
268 num_threads: Number of builder threads to run
269 num_jobs: Number of jobs to run at once (passed to make as -j)
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900270 gnu_make: the command name of GNU Make.
Simon Glassc05694f2013-04-03 11:07:16 +0000271 checkout: True to check out source, False to skip that step.
272 This is used for testing.
273 show_unknown: Show unknown boards (those not built) in summary
274 step: 1 to process every commit, n to process every nth commit
Simon Glassd48a46c2014-12-01 17:34:00 -0700275 no_subdirs: Don't create subdirectories when building current
276 source for a single board
277 full_path: Return the full path in CROSS_COMPILE and don't set
278 PATH
Simon Glass655b6102014-12-01 17:34:07 -0700279 verbose_build: Run build with V=1 and don't use 'make -s'
Simon Glass6029af12020-04-09 15:08:51 -0600280 mrproper: Always run 'make mrproper' when configuring
Stephen Warren97c96902016-04-11 10:48:44 -0600281 per_board_out_dir: Build in a separate persistent directory per
282 board rather than a thread-specific directory
Simon Glass739e8512016-11-13 14:25:51 -0700283 config_only: Only configure each build, don't build it
Simon Glasscde5c302016-11-13 14:25:53 -0700284 squash_config_y: Convert CONFIG options with the value 'y' to '1'
Daniel Schwierzeck20e2ea92018-01-26 16:31:05 +0100285 warnings_as_errors: Treat all compiler warnings as errors
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600286 work_in_output: Use the output directory as the work directory and
287 don't write to a separate output directory.
Simon Glass9bf9a722021-04-11 16:27:27 +1200288 test_thread_exceptions: Uses for tests only, True to make the
289 threads raise an exception instead of reporting their result.
290 This simulates a failure in the code somewhere
Simon Glasse5650a82022-01-22 05:07:33 -0700291 adjust_cfg_list (list of str): List of changes to make to .config
292 file before building. Each is one of (where C is the config
293 option with or without the CONFIG_ prefix)
294
295 C to enable C
296 ~C to disable C
297 C=val to set the value of C (val must have quotes if C is
298 a string Kconfig
Tom Rini93ebd462022-11-09 19:14:53 -0700299 allow_missing: Run build with BINMAN_ALLOW_MISSING=1
Simon Glassf6bfcca2023-02-21 12:40:28 -0700300 no_lto (bool): True to set the NO_LTO flag when building
Simon Glasscf91d312023-07-19 17:48:52 -0600301 force_build (bool): Rebuild even commits that are already built
302 force_build_failures (bool): Rebuild commits that have not been
303 built, or failed to build
304 force_reconfig (bool): Reconfigure on each commit
305 in_tree (bool): Bulid in tree instead of out-of-tree
306 force_config_on_failure (bool): Reconfigure the build before
307 retrying a failed build
308 make_func (function): Function to call to run 'make'
Simon Glassc05694f2013-04-03 11:07:16 +0000309 """
310 self.toolchains = toolchains
311 self.base_dir = base_dir
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600312 if work_in_output:
313 self._working_dir = base_dir
314 else:
315 self._working_dir = os.path.join(base_dir, '.bm-work')
Simon Glassc05694f2013-04-03 11:07:16 +0000316 self.threads = []
Simon Glasscf91d312023-07-19 17:48:52 -0600317 self.do_make = make_func or self.Make
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900318 self.gnu_make = gnu_make
Simon Glassc05694f2013-04-03 11:07:16 +0000319 self.checkout = checkout
320 self.num_threads = num_threads
321 self.num_jobs = num_jobs
322 self.already_done = 0
323 self.force_build = False
324 self.git_dir = git_dir
325 self._show_unknown = show_unknown
326 self._timestamp_count = 10
327 self._build_period_us = None
328 self._complete_delay = None
329 self._next_delay_update = datetime.now()
Simon Glass726ae812020-04-09 15:08:47 -0600330 self._start_time = datetime.now()
Simon Glassc05694f2013-04-03 11:07:16 +0000331 self._step = step
Simon Glassbb4dffb2014-08-09 15:33:06 -0600332 self._error_lines = 0
Simon Glasse87bde12014-12-01 17:33:55 -0700333 self.no_subdirs = no_subdirs
Simon Glassd48a46c2014-12-01 17:34:00 -0700334 self.full_path = full_path
Simon Glass655b6102014-12-01 17:34:07 -0700335 self.verbose_build = verbose_build
Simon Glass739e8512016-11-13 14:25:51 -0700336 self.config_only = config_only
Simon Glasscde5c302016-11-13 14:25:53 -0700337 self.squash_config_y = squash_config_y
338 self.config_filenames = BASE_CONFIG_FILENAMES
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600339 self.work_in_output = work_in_output
Simon Glasse5650a82022-01-22 05:07:33 -0700340 self.adjust_cfg = adjust_cfg
Tom Rini93ebd462022-11-09 19:14:53 -0700341 self.allow_missing = allow_missing
Simon Glass6c435622022-07-11 19:03:56 -0600342 self._ide = False
Simon Glassf6bfcca2023-02-21 12:40:28 -0700343 self.no_lto = no_lto
Simon Glass828d70d2023-02-21 12:40:29 -0700344 self.reproducible_builds = reproducible_builds
Simon Glasscf91d312023-07-19 17:48:52 -0600345 self.force_build = force_build
346 self.force_build_failures = force_build_failures
347 self.force_reconfig = force_reconfig
348 self.in_tree = in_tree
349 self.force_config_on_failure = force_config_on_failure
Simon Glasse5650a82022-01-22 05:07:33 -0700350
Simon Glasscde5c302016-11-13 14:25:53 -0700351 if not self.squash_config_y:
352 self.config_filenames += EXTRA_CONFIG_FILENAMES
Simon Glass146b6022021-10-19 21:43:24 -0600353 self._terminated = False
354 self._restarting_config = False
Simon Glassc05694f2013-04-03 11:07:16 +0000355
Daniel Schwierzeck20e2ea92018-01-26 16:31:05 +0100356 self.warnings_as_errors = warnings_as_errors
Simon Glassc05694f2013-04-03 11:07:16 +0000357 self.col = terminal.Color()
358
Simon Glass03749d42014-08-28 09:43:44 -0600359 self._re_function = re.compile('(.*): In function.*')
360 self._re_files = re.compile('In file included from.*')
361 self._re_warning = re.compile('(.*):(\d*):(\d*): warning: .*')
Simon Glass0db94432018-11-06 16:02:11 -0700362 self._re_dtb_warning = re.compile('(.*): Warning .*')
Simon Glass03749d42014-08-28 09:43:44 -0600363 self._re_note = re.compile('(.*):(\d*):(\d*): note: this is the location of the previous.*')
Simon Glassf4ebfba2020-04-09 15:08:53 -0600364 self._re_migration_warning = re.compile(r'^={21} WARNING ={22}\n.*\n=+\n',
365 re.MULTILINE | re.DOTALL)
Simon Glass03749d42014-08-28 09:43:44 -0600366
Simon Glass9bf9a722021-04-11 16:27:27 +1200367 self.thread_exceptions = []
368 self.test_thread_exceptions = test_thread_exceptions
Simon Glassc635d892021-01-30 22:17:46 -0700369 if self.num_threads:
370 self._single_builder = None
371 self.queue = queue.Queue()
372 self.out_queue = queue.Queue()
373 for i in range(self.num_threads):
Simon Glass9bf9a722021-04-11 16:27:27 +1200374 t = builderthread.BuilderThread(
375 self, i, mrproper, per_board_out_dir,
376 test_exception=test_thread_exceptions)
Simon Glassc635d892021-01-30 22:17:46 -0700377 t.setDaemon(True)
378 t.start()
379 self.threads.append(t)
380
381 t = builderthread.ResultThread(self)
Simon Glassc05694f2013-04-03 11:07:16 +0000382 t.setDaemon(True)
383 t.start()
384 self.threads.append(t)
Simon Glassc635d892021-01-30 22:17:46 -0700385 else:
386 self._single_builder = builderthread.BuilderThread(
387 self, -1, mrproper, per_board_out_dir)
Simon Glassc05694f2013-04-03 11:07:16 +0000388
389 ignore_lines = ['(make.*Waiting for unfinished)', '(Segmentation fault)']
390 self.re_make_err = re.compile('|'.join(ignore_lines))
391
Simon Glass205ac042016-09-18 16:48:37 -0600392 # Handle existing graceful with SIGINT / Ctrl-C
393 signal.signal(signal.SIGINT, self.signal_handler)
394
Simon Glassc05694f2013-04-03 11:07:16 +0000395 def __del__(self):
396 """Get rid of all threads created by the builder"""
397 for t in self.threads:
398 del t
399
Simon Glass205ac042016-09-18 16:48:37 -0600400 def signal_handler(self, signal, frame):
401 sys.exit(1)
402
Simon Glasseb48bbc2014-08-09 15:33:02 -0600403 def SetDisplayOptions(self, show_errors=False, show_sizes=False,
Simon Glass3394c9f2014-08-28 09:43:43 -0600404 show_detail=False, show_bloat=False,
Alex Kiernan4059e302018-05-31 04:48:34 +0000405 list_error_boards=False, show_config=False,
Simon Glassf4ebfba2020-04-09 15:08:53 -0600406 show_environment=False, filter_dtb_warnings=False,
Simon Glass6c435622022-07-11 19:03:56 -0600407 filter_migration_warnings=False, ide=False):
Simon Glasseb48bbc2014-08-09 15:33:02 -0600408 """Setup display options for the builder.
409
Simon Glass9ea93812020-04-09 15:08:52 -0600410 Args:
411 show_errors: True to show summarised error/warning info
412 show_sizes: Show size deltas
413 show_detail: Show size delta detail for each board if show_sizes
414 show_bloat: Show detail for each function
415 list_error_boards: Show the boards which caused each error/warning
416 show_config: Show config deltas
417 show_environment: Show environment deltas
418 filter_dtb_warnings: Filter out any warnings from the device-tree
419 compiler
Simon Glassf4ebfba2020-04-09 15:08:53 -0600420 filter_migration_warnings: Filter out any warnings about migrating
421 a board to driver model
Simon Glass6c435622022-07-11 19:03:56 -0600422 ide: Create output that can be parsed by an IDE. There is no '+' prefix on
423 error lines and output on stderr stays on stderr.
Simon Glasseb48bbc2014-08-09 15:33:02 -0600424 """
425 self._show_errors = show_errors
426 self._show_sizes = show_sizes
427 self._show_detail = show_detail
428 self._show_bloat = show_bloat
Simon Glass3394c9f2014-08-28 09:43:43 -0600429 self._list_error_boards = list_error_boards
Simon Glassdb17fb82015-02-05 22:06:15 -0700430 self._show_config = show_config
Alex Kiernan4059e302018-05-31 04:48:34 +0000431 self._show_environment = show_environment
Simon Glass9ea93812020-04-09 15:08:52 -0600432 self._filter_dtb_warnings = filter_dtb_warnings
Simon Glassf4ebfba2020-04-09 15:08:53 -0600433 self._filter_migration_warnings = filter_migration_warnings
Simon Glass6c435622022-07-11 19:03:56 -0600434 self._ide = ide
Simon Glasseb48bbc2014-08-09 15:33:02 -0600435
Simon Glassc05694f2013-04-03 11:07:16 +0000436 def _AddTimestamp(self):
437 """Add a new timestamp to the list and record the build period.
438
439 The build period is the length of time taken to perform a single
440 build (one board, one commit).
441 """
442 now = datetime.now()
443 self._timestamps.append(now)
444 count = len(self._timestamps)
445 delta = self._timestamps[-1] - self._timestamps[0]
446 seconds = delta.total_seconds()
447
448 # If we have enough data, estimate build period (time taken for a
449 # single build) and therefore completion time.
450 if count > 1 and self._next_delay_update < now:
451 self._next_delay_update = now + timedelta(seconds=2)
452 if seconds > 0:
453 self._build_period = float(seconds) / count
454 todo = self.count - self.upto
455 self._complete_delay = timedelta(microseconds=
456 self._build_period * todo * 1000000)
457 # Round it
458 self._complete_delay -= timedelta(
459 microseconds=self._complete_delay.microseconds)
460
461 if seconds > 60:
462 self._timestamps.popleft()
463 count -= 1
464
Simon Glassc05694f2013-04-03 11:07:16 +0000465 def SelectCommit(self, commit, checkout=True):
466 """Checkout the selected commit for this build
467 """
468 self.commit = commit
469 if checkout and self.checkout:
Simon Glass761648b2022-01-29 14:14:11 -0700470 gitutil.checkout(commit.hash)
Simon Glassc05694f2013-04-03 11:07:16 +0000471
472 def Make(self, commit, brd, stage, cwd, *args, **kwargs):
473 """Run make
474
475 Args:
476 commit: Commit object that is being built
477 brd: Board object that is being built
Roger Meiere0a0e552014-08-20 22:10:29 +0200478 stage: Stage that we are at (mrproper, config, build)
Simon Glassc05694f2013-04-03 11:07:16 +0000479 cwd: Directory where make should be run
480 args: Arguments to pass to make
Simon Glass840be732022-01-29 14:14:05 -0700481 kwargs: Arguments to pass to command.run_pipe()
Simon Glassc05694f2013-04-03 11:07:16 +0000482 """
Simon Glass146b6022021-10-19 21:43:24 -0600483
484 def check_output(stream, data):
485 if b'Restart config' in data:
486 self._restarting_config = True
487
488 # If we see 'Restart config' following by multiple errors
489 if self._restarting_config:
490 m = RE_NO_DEFAULT.findall(data)
491
492 # Number of occurences of each Kconfig item
493 multiple = [m.count(val) for val in set(m)]
494
495 # If any of them occur more than once, we have a loop
496 if [val for val in multiple if val > 1]:
497 self._terminated = True
498 return True
499 return False
500
501 self._restarting_config = False
502 self._terminated = False
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900503 cmd = [self.gnu_make] + list(args)
Simon Glass840be732022-01-29 14:14:05 -0700504 result = command.run_pipe([cmd], capture=True, capture_stderr=True,
Simon Glass146b6022021-10-19 21:43:24 -0600505 cwd=cwd, raise_on_error=False, infile='/dev/null',
506 output_func=check_output, **kwargs)
507
508 if self._terminated:
509 # Try to be helpful
510 result.stderr += '(** did you define an int/hex Kconfig with no default? **)'
511
Simon Glass413f91a2015-02-05 22:06:12 -0700512 if self.verbose_build:
513 result.stdout = '%s\n' % (' '.join(cmd)) + result.stdout
514 result.combined = '%s\n' % (' '.join(cmd)) + result.combined
Simon Glassc05694f2013-04-03 11:07:16 +0000515 return result
516
517 def ProcessResult(self, result):
518 """Process the result of a build, showing progress information
519
520 Args:
Simon Glass78e418e2014-08-09 15:33:03 -0600521 result: A CommandResult object, which indicates the result for
522 a single build
Simon Glassc05694f2013-04-03 11:07:16 +0000523 """
524 col = terminal.Color()
525 if result:
526 target = result.brd.target
527
Simon Glassc05694f2013-04-03 11:07:16 +0000528 self.upto += 1
529 if result.return_code != 0:
530 self.fail += 1
531 elif result.stderr:
532 self.warned += 1
533 if result.already_done:
534 self.already_done += 1
Simon Glass78e418e2014-08-09 15:33:03 -0600535 if self._verbose:
Simon Glass02811582022-01-29 14:14:18 -0700536 terminal.print_clear()
Simon Glass78e418e2014-08-09 15:33:03 -0600537 boards_selected = {target : result.brd}
538 self.ResetResultSummary(boards_selected)
539 self.ProduceResultSummary(result.commit_upto, self.commits,
540 boards_selected)
Simon Glassc05694f2013-04-03 11:07:16 +0000541 else:
542 target = '(starting)'
543
544 # Display separate counts for ok, warned and fail
545 ok = self.upto - self.warned - self.fail
Simon Glassf45d3742022-01-29 14:14:17 -0700546 line = '\r' + self.col.build(self.col.GREEN, '%5d' % ok)
547 line += self.col.build(self.col.YELLOW, '%5d' % self.warned)
548 line += self.col.build(self.col.RED, '%5d' % self.fail)
Simon Glassc05694f2013-04-03 11:07:16 +0000549
Simon Glass69c3a8a2020-04-09 15:08:45 -0600550 line += ' /%-5d ' % self.count
551 remaining = self.count - self.upto
552 if remaining:
Simon Glassf45d3742022-01-29 14:14:17 -0700553 line += self.col.build(self.col.MAGENTA, ' -%-5d ' % remaining)
Simon Glass69c3a8a2020-04-09 15:08:45 -0600554 else:
555 line += ' ' * 8
Simon Glassc05694f2013-04-03 11:07:16 +0000556
557 # Add our current completion time estimate
558 self._AddTimestamp()
559 if self._complete_delay:
Simon Glass69c3a8a2020-04-09 15:08:45 -0600560 line += '%s : ' % self._complete_delay
Simon Glassc05694f2013-04-03 11:07:16 +0000561
Simon Glass69c3a8a2020-04-09 15:08:45 -0600562 line += target
Simon Glass6c435622022-07-11 19:03:56 -0600563 if not self._ide:
564 terminal.print_clear()
565 tprint(line, newline=False, limit_to_line=True)
Simon Glassc05694f2013-04-03 11:07:16 +0000566
567 def _GetOutputDir(self, commit_upto):
568 """Get the name of the output directory for a commit number
569
570 The output directory is typically .../<branch>/<commit>.
571
572 Args:
573 commit_upto: Commit number to use (0..self.count-1)
574 """
Simon Glasse3c85ab2020-04-17 17:51:34 -0600575 if self.work_in_output:
576 return self._working_dir
577
Simon Glasse87bde12014-12-01 17:33:55 -0700578 commit_dir = None
Simon Glassd326ad72014-08-09 15:32:59 -0600579 if self.commits:
580 commit = self.commits[commit_upto]
581 subject = commit.subject.translate(trans_valid_chars)
Simon Glass5dc1ca72020-03-18 09:42:45 -0600582 # See _GetOutputSpaceRemovals() which parses this name
Ovidiu Panaitee8e9cb2020-05-15 09:30:12 +0300583 commit_dir = ('%02d_g%s_%s' % (commit_upto + 1,
584 commit.hash, subject[:20]))
Simon Glasse87bde12014-12-01 17:33:55 -0700585 elif not self.no_subdirs:
Simon Glassd326ad72014-08-09 15:32:59 -0600586 commit_dir = 'current'
Simon Glasse87bde12014-12-01 17:33:55 -0700587 if not commit_dir:
588 return self.base_dir
589 return os.path.join(self.base_dir, commit_dir)
Simon Glassc05694f2013-04-03 11:07:16 +0000590
591 def GetBuildDir(self, commit_upto, target):
592 """Get the name of the build directory for a commit number
593
594 The build directory is typically .../<branch>/<commit>/<target>.
595
596 Args:
597 commit_upto: Commit number to use (0..self.count-1)
598 target: Target name
599 """
600 output_dir = self._GetOutputDir(commit_upto)
Simon Glasse3c85ab2020-04-17 17:51:34 -0600601 if self.work_in_output:
602 return output_dir
Simon Glassc05694f2013-04-03 11:07:16 +0000603 return os.path.join(output_dir, target)
604
605 def GetDoneFile(self, commit_upto, target):
606 """Get the name of the done file for a commit number
607
608 Args:
609 commit_upto: Commit number to use (0..self.count-1)
610 target: Target name
611 """
612 return os.path.join(self.GetBuildDir(commit_upto, target), 'done')
613
614 def GetSizesFile(self, commit_upto, target):
615 """Get the name of the sizes file for a commit number
616
617 Args:
618 commit_upto: Commit number to use (0..self.count-1)
619 target: Target name
620 """
621 return os.path.join(self.GetBuildDir(commit_upto, target), 'sizes')
622
623 def GetFuncSizesFile(self, commit_upto, target, elf_fname):
624 """Get the name of the funcsizes 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.sizes' % elf_fname.replace('/', '-'))
633
634 def GetObjdumpFile(self, commit_upto, target, elf_fname):
635 """Get the name of the objdump file for a commit number and ELF file
636
637 Args:
638 commit_upto: Commit number to use (0..self.count-1)
639 target: Target name
640 elf_fname: Filename of elf image
641 """
642 return os.path.join(self.GetBuildDir(commit_upto, target),
643 '%s.objdump' % elf_fname.replace('/', '-'))
644
645 def GetErrFile(self, commit_upto, target):
646 """Get the name of the err file for a commit number
647
648 Args:
649 commit_upto: Commit number to use (0..self.count-1)
650 target: Target name
651 """
652 output_dir = self.GetBuildDir(commit_upto, target)
653 return os.path.join(output_dir, 'err')
654
655 def FilterErrors(self, lines):
656 """Filter out errors in which we have no interest
657
658 We should probably use map().
659
660 Args:
661 lines: List of error lines, each a string
662 Returns:
663 New list with only interesting lines included
664 """
665 out_lines = []
Simon Glassf4ebfba2020-04-09 15:08:53 -0600666 if self._filter_migration_warnings:
667 text = '\n'.join(lines)
668 text = self._re_migration_warning.sub('', text)
669 lines = text.splitlines()
Simon Glassc05694f2013-04-03 11:07:16 +0000670 for line in lines:
Simon Glass9ea93812020-04-09 15:08:52 -0600671 if self.re_make_err.search(line):
672 continue
673 if self._filter_dtb_warnings and self._re_dtb_warning.search(line):
674 continue
675 out_lines.append(line)
Simon Glassc05694f2013-04-03 11:07:16 +0000676 return out_lines
677
678 def ReadFuncSizes(self, fname, fd):
679 """Read function sizes from the output of 'nm'
680
681 Args:
682 fd: File containing data to read
683 fname: Filename we are reading from (just for errors)
684
685 Returns:
686 Dictionary containing size of each function in bytes, indexed by
687 function name.
688 """
689 sym = {}
690 for line in fd.readlines():
Simon Glass86a2afe2022-07-11 19:04:11 -0600691 line = line.strip()
692 parts = line.split()
693 if line and len(parts) == 3:
694 size, type, name = line.split()
695 if type in 'tTdDbB':
696 # function names begin with '.' on 64-bit powerpc
697 if '.' in name[1:]:
698 name = 'static.' + name.split('.')[0]
699 sym[name] = sym.get(name, 0) + int(size, 16)
Simon Glassc05694f2013-04-03 11:07:16 +0000700 return sym
701
Simon Glassdb17fb82015-02-05 22:06:15 -0700702 def _ProcessConfig(self, fname):
703 """Read in a .config, autoconf.mk or autoconf.h file
704
705 This function handles all config file types. It ignores comments and
706 any #defines which don't start with CONFIG_.
707
708 Args:
709 fname: Filename to read
710
711 Returns:
712 Dictionary:
713 key: Config name (e.g. CONFIG_DM)
714 value: Config value (e.g. 1)
715 """
716 config = {}
717 if os.path.exists(fname):
718 with open(fname) as fd:
719 for line in fd:
720 line = line.strip()
721 if line.startswith('#define'):
722 values = line[8:].split(' ', 1)
723 if len(values) > 1:
724 key, value = values
725 else:
726 key = values[0]
Simon Glasscde5c302016-11-13 14:25:53 -0700727 value = '1' if self.squash_config_y else ''
Simon Glassdb17fb82015-02-05 22:06:15 -0700728 if not key.startswith('CONFIG_'):
729 continue
730 elif not line or line[0] in ['#', '*', '/']:
731 continue
732 else:
733 key, value = line.split('=', 1)
Simon Glasscde5c302016-11-13 14:25:53 -0700734 if self.squash_config_y and value == 'y':
735 value = '1'
Simon Glassdb17fb82015-02-05 22:06:15 -0700736 config[key] = value
737 return config
738
Alex Kiernan4059e302018-05-31 04:48:34 +0000739 def _ProcessEnvironment(self, fname):
740 """Read in a uboot.env file
741
742 This function reads in environment variables from a file.
743
744 Args:
745 fname: Filename to read
746
747 Returns:
748 Dictionary:
749 key: environment variable (e.g. bootlimit)
750 value: value of environment variable (e.g. 1)
751 """
752 environment = {}
753 if os.path.exists(fname):
754 with open(fname) as fd:
755 for line in fd.read().split('\0'):
756 try:
757 key, value = line.split('=', 1)
758 environment[key] = value
759 except ValueError:
760 # ignore lines we can't parse
761 pass
762 return environment
763
Simon Glassdb17fb82015-02-05 22:06:15 -0700764 def GetBuildOutcome(self, commit_upto, target, read_func_sizes,
Alex Kiernan4059e302018-05-31 04:48:34 +0000765 read_config, read_environment):
Simon Glassc05694f2013-04-03 11:07:16 +0000766 """Work out the outcome of a build.
767
768 Args:
769 commit_upto: Commit number to check (0..n-1)
770 target: Target board to check
771 read_func_sizes: True to read function size information
Simon Glassdb17fb82015-02-05 22:06:15 -0700772 read_config: True to read .config and autoconf.h files
Alex Kiernan4059e302018-05-31 04:48:34 +0000773 read_environment: True to read uboot.env files
Simon Glassc05694f2013-04-03 11:07:16 +0000774
775 Returns:
776 Outcome object
777 """
778 done_file = self.GetDoneFile(commit_upto, target)
779 sizes_file = self.GetSizesFile(commit_upto, target)
780 sizes = {}
781 func_sizes = {}
Simon Glassdb17fb82015-02-05 22:06:15 -0700782 config = {}
Alex Kiernan4059e302018-05-31 04:48:34 +0000783 environment = {}
Simon Glassc05694f2013-04-03 11:07:16 +0000784 if os.path.exists(done_file):
785 with open(done_file, 'r') as fd:
Simon Glass91c54a42019-04-26 19:02:23 -0600786 try:
787 return_code = int(fd.readline())
788 except ValueError:
789 # The file may be empty due to running out of disk space.
790 # Try a rebuild
791 return_code = 1
Simon Glassc05694f2013-04-03 11:07:16 +0000792 err_lines = []
793 err_file = self.GetErrFile(commit_upto, target)
794 if os.path.exists(err_file):
795 with open(err_file, 'r') as fd:
796 err_lines = self.FilterErrors(fd.readlines())
797
798 # Decide whether the build was ok, failed or created warnings
799 if return_code:
800 rc = OUTCOME_ERROR
801 elif len(err_lines):
802 rc = OUTCOME_WARNING
803 else:
804 rc = OUTCOME_OK
805
806 # Convert size information to our simple format
807 if os.path.exists(sizes_file):
808 with open(sizes_file, 'r') as fd:
809 for line in fd.readlines():
810 values = line.split()
811 rodata = 0
812 if len(values) > 6:
813 rodata = int(values[6], 16)
814 size_dict = {
815 'all' : int(values[0]) + int(values[1]) +
816 int(values[2]),
817 'text' : int(values[0]) - rodata,
818 'data' : int(values[1]),
819 'bss' : int(values[2]),
820 'rodata' : rodata,
821 }
822 sizes[values[5]] = size_dict
823
824 if read_func_sizes:
825 pattern = self.GetFuncSizesFile(commit_upto, target, '*')
826 for fname in glob.glob(pattern):
827 with open(fname, 'r') as fd:
828 dict_name = os.path.basename(fname).replace('.sizes',
829 '')
830 func_sizes[dict_name] = self.ReadFuncSizes(fname, fd)
831
Simon Glassdb17fb82015-02-05 22:06:15 -0700832 if read_config:
833 output_dir = self.GetBuildDir(commit_upto, target)
Simon Glasscde5c302016-11-13 14:25:53 -0700834 for name in self.config_filenames:
Simon Glassdb17fb82015-02-05 22:06:15 -0700835 fname = os.path.join(output_dir, name)
836 config[name] = self._ProcessConfig(fname)
837
Alex Kiernan4059e302018-05-31 04:48:34 +0000838 if read_environment:
839 output_dir = self.GetBuildDir(commit_upto, target)
840 fname = os.path.join(output_dir, 'uboot.env')
841 environment = self._ProcessEnvironment(fname)
842
843 return Builder.Outcome(rc, err_lines, sizes, func_sizes, config,
844 environment)
Simon Glassc05694f2013-04-03 11:07:16 +0000845
Alex Kiernan4059e302018-05-31 04:48:34 +0000846 return Builder.Outcome(OUTCOME_UNKNOWN, [], {}, {}, {}, {})
Simon Glassc05694f2013-04-03 11:07:16 +0000847
Simon Glassdb17fb82015-02-05 22:06:15 -0700848 def GetResultSummary(self, boards_selected, commit_upto, read_func_sizes,
Alex Kiernan4059e302018-05-31 04:48:34 +0000849 read_config, read_environment):
Simon Glassc05694f2013-04-03 11:07:16 +0000850 """Calculate a summary of the results of building a commit.
851
852 Args:
853 board_selected: Dict containing boards to summarise
854 commit_upto: Commit number to summarize (0..self.count-1)
855 read_func_sizes: True to read function size information
Simon Glassdb17fb82015-02-05 22:06:15 -0700856 read_config: True to read .config and autoconf.h files
Alex Kiernan4059e302018-05-31 04:48:34 +0000857 read_environment: True to read uboot.env files
Simon Glassc05694f2013-04-03 11:07:16 +0000858
859 Returns:
860 Tuple:
Simon Glass6c435622022-07-11 19:03:56 -0600861 Dict containing boards which built this commit:
862 key: board.target
863 value: Builder.Outcome object
Simon Glass03749d42014-08-28 09:43:44 -0600864 List containing a summary of error lines
Simon Glass3394c9f2014-08-28 09:43:43 -0600865 Dict keyed by error line, containing a list of the Board
866 objects with that error
Simon Glass03749d42014-08-28 09:43:44 -0600867 List containing a summary of warning lines
868 Dict keyed by error line, containing a list of the Board
869 objects with that warning
Simon Glasscad8abf2015-08-25 21:52:14 -0600870 Dictionary keyed by board.target. Each value is a dictionary:
871 key: filename - e.g. '.config'
Simon Glassdb17fb82015-02-05 22:06:15 -0700872 value is itself a dictionary:
873 key: config name
874 value: config value
Alex Kiernan4059e302018-05-31 04:48:34 +0000875 Dictionary keyed by board.target. Each value is a dictionary:
876 key: environment variable
877 value: value of environment variable
Simon Glassc05694f2013-04-03 11:07:16 +0000878 """
Simon Glass03749d42014-08-28 09:43:44 -0600879 def AddLine(lines_summary, lines_boards, line, board):
880 line = line.rstrip()
881 if line in lines_boards:
882 lines_boards[line].append(board)
883 else:
884 lines_boards[line] = [board]
885 lines_summary.append(line)
886
Simon Glassc05694f2013-04-03 11:07:16 +0000887 board_dict = {}
888 err_lines_summary = []
Simon Glass3394c9f2014-08-28 09:43:43 -0600889 err_lines_boards = {}
Simon Glass03749d42014-08-28 09:43:44 -0600890 warn_lines_summary = []
891 warn_lines_boards = {}
Simon Glassdb17fb82015-02-05 22:06:15 -0700892 config = {}
Alex Kiernan4059e302018-05-31 04:48:34 +0000893 environment = {}
Simon Glassc05694f2013-04-03 11:07:16 +0000894
Simon Glass8132f982022-07-11 19:03:57 -0600895 for brd in boards_selected.values():
896 outcome = self.GetBuildOutcome(commit_upto, brd.target,
Alex Kiernan4059e302018-05-31 04:48:34 +0000897 read_func_sizes, read_config,
898 read_environment)
Simon Glass8132f982022-07-11 19:03:57 -0600899 board_dict[brd.target] = outcome
Simon Glass03749d42014-08-28 09:43:44 -0600900 last_func = None
901 last_was_warning = False
902 for line in outcome.err_lines:
903 if line:
904 if (self._re_function.match(line) or
905 self._re_files.match(line)):
906 last_func = line
Simon Glass3394c9f2014-08-28 09:43:43 -0600907 else:
Simon Glass0db94432018-11-06 16:02:11 -0700908 is_warning = (self._re_warning.match(line) or
909 self._re_dtb_warning.match(line))
Simon Glass03749d42014-08-28 09:43:44 -0600910 is_note = self._re_note.match(line)
911 if is_warning or (last_was_warning and is_note):
912 if last_func:
913 AddLine(warn_lines_summary, warn_lines_boards,
Simon Glass8132f982022-07-11 19:03:57 -0600914 last_func, brd)
Simon Glass03749d42014-08-28 09:43:44 -0600915 AddLine(warn_lines_summary, warn_lines_boards,
Simon Glass8132f982022-07-11 19:03:57 -0600916 line, brd)
Simon Glass03749d42014-08-28 09:43:44 -0600917 else:
918 if last_func:
919 AddLine(err_lines_summary, err_lines_boards,
Simon Glass8132f982022-07-11 19:03:57 -0600920 last_func, brd)
Simon Glass03749d42014-08-28 09:43:44 -0600921 AddLine(err_lines_summary, err_lines_boards,
Simon Glass8132f982022-07-11 19:03:57 -0600922 line, brd)
Simon Glass03749d42014-08-28 09:43:44 -0600923 last_was_warning = is_warning
924 last_func = None
Simon Glass8132f982022-07-11 19:03:57 -0600925 tconfig = Config(self.config_filenames, brd.target)
Simon Glasscde5c302016-11-13 14:25:53 -0700926 for fname in self.config_filenames:
Simon Glassdb17fb82015-02-05 22:06:15 -0700927 if outcome.config:
Simon Glassc78ed662019-10-31 07:42:53 -0600928 for key, value in outcome.config[fname].items():
Simon Glasscad8abf2015-08-25 21:52:14 -0600929 tconfig.Add(fname, key, value)
Simon Glass8132f982022-07-11 19:03:57 -0600930 config[brd.target] = tconfig
Simon Glassdb17fb82015-02-05 22:06:15 -0700931
Simon Glass8132f982022-07-11 19:03:57 -0600932 tenvironment = Environment(brd.target)
Alex Kiernan4059e302018-05-31 04:48:34 +0000933 if outcome.environment:
Simon Glassc78ed662019-10-31 07:42:53 -0600934 for key, value in outcome.environment.items():
Alex Kiernan4059e302018-05-31 04:48:34 +0000935 tenvironment.Add(key, value)
Simon Glass8132f982022-07-11 19:03:57 -0600936 environment[brd.target] = tenvironment
Alex Kiernan4059e302018-05-31 04:48:34 +0000937
Simon Glass03749d42014-08-28 09:43:44 -0600938 return (board_dict, err_lines_summary, err_lines_boards,
Alex Kiernan4059e302018-05-31 04:48:34 +0000939 warn_lines_summary, warn_lines_boards, config, environment)
Simon Glassc05694f2013-04-03 11:07:16 +0000940
941 def AddOutcome(self, board_dict, arch_list, changes, char, color):
942 """Add an output to our list of outcomes for each architecture
943
944 This simple function adds failing boards (changes) to the
945 relevant architecture string, so we can print the results out
946 sorted by architecture.
947
948 Args:
949 board_dict: Dict containing all boards
950 arch_list: Dict keyed by arch name. Value is a string containing
951 a list of board names which failed for that arch.
952 changes: List of boards to add to arch_list
953 color: terminal.Colour object
954 """
955 done_arch = {}
956 for target in changes:
957 if target in board_dict:
958 arch = board_dict[target].arch
959 else:
960 arch = 'unknown'
Simon Glassf45d3742022-01-29 14:14:17 -0700961 str = self.col.build(color, ' ' + target)
Simon Glassc05694f2013-04-03 11:07:16 +0000962 if not arch in done_arch:
Simon Glassf45d3742022-01-29 14:14:17 -0700963 str = ' %s %s' % (self.col.build(color, char), str)
Simon Glassc05694f2013-04-03 11:07:16 +0000964 done_arch[arch] = True
965 if not arch in arch_list:
966 arch_list[arch] = str
967 else:
968 arch_list[arch] += str
969
970
971 def ColourNum(self, num):
972 color = self.col.RED if num > 0 else self.col.GREEN
973 if num == 0:
974 return '0'
Simon Glassf45d3742022-01-29 14:14:17 -0700975 return self.col.build(color, str(num))
Simon Glassc05694f2013-04-03 11:07:16 +0000976
977 def ResetResultSummary(self, board_selected):
978 """Reset the results summary ready for use.
979
980 Set up the base board list to be all those selected, and set the
981 error lines to empty.
982
983 Following this, calls to PrintResultSummary() will use this
984 information to work out what has changed.
985
986 Args:
987 board_selected: Dict containing boards to summarise, keyed by
988 board.target
989 """
990 self._base_board_dict = {}
Simon Glass8132f982022-07-11 19:03:57 -0600991 for brd in board_selected:
992 self._base_board_dict[brd] = Builder.Outcome(0, [], [], {}, {}, {})
Simon Glassc05694f2013-04-03 11:07:16 +0000993 self._base_err_lines = []
Simon Glass03749d42014-08-28 09:43:44 -0600994 self._base_warn_lines = []
995 self._base_err_line_boards = {}
996 self._base_warn_line_boards = {}
Simon Glasscad8abf2015-08-25 21:52:14 -0600997 self._base_config = None
Alex Kiernan4059e302018-05-31 04:48:34 +0000998 self._base_environment = None
Simon Glassc05694f2013-04-03 11:07:16 +0000999
1000 def PrintFuncSizeDetail(self, fname, old, new):
1001 grow, shrink, add, remove, up, down = 0, 0, 0, 0, 0, 0
1002 delta, common = [], {}
1003
1004 for a in old:
1005 if a in new:
1006 common[a] = 1
1007
1008 for name in old:
1009 if name not in common:
1010 remove += 1
1011 down += old[name]
1012 delta.append([-old[name], name])
1013
1014 for name in new:
1015 if name not in common:
1016 add += 1
1017 up += new[name]
1018 delta.append([new[name], name])
1019
1020 for name in common:
1021 diff = new.get(name, 0) - old.get(name, 0)
1022 if diff > 0:
1023 grow, up = grow + 1, up + diff
1024 elif diff < 0:
1025 shrink, down = shrink + 1, down - diff
1026 delta.append([diff, name])
1027
1028 delta.sort()
1029 delta.reverse()
1030
1031 args = [add, -remove, grow, -shrink, up, -down, up - down]
Tom Rini0b48cd62017-05-22 13:48:52 -04001032 if max(args) == 0 and min(args) == 0:
Simon Glassc05694f2013-04-03 11:07:16 +00001033 return
1034 args = [self.ColourNum(x) for x in args]
1035 indent = ' ' * 15
Simon Glass02811582022-01-29 14:14:18 -07001036 tprint('%s%s: add: %s/%s, grow: %s/%s bytes: %s/%s (%s)' %
Simon Glassf45d3742022-01-29 14:14:17 -07001037 tuple([indent, self.col.build(self.col.YELLOW, fname)] + args))
Simon Glass02811582022-01-29 14:14:18 -07001038 tprint('%s %-38s %7s %7s %+7s' % (indent, 'function', 'old', 'new',
Simon Glass4433aa92014-09-05 19:00:07 -06001039 'delta'))
Simon Glassc05694f2013-04-03 11:07:16 +00001040 for diff, name in delta:
1041 if diff:
1042 color = self.col.RED if diff > 0 else self.col.GREEN
1043 msg = '%s %-38s %7s %7s %+7d' % (indent, name,
1044 old.get(name, '-'), new.get(name,'-'), diff)
Simon Glass02811582022-01-29 14:14:18 -07001045 tprint(msg, colour=color)
Simon Glassc05694f2013-04-03 11:07:16 +00001046
1047
1048 def PrintSizeDetail(self, target_list, show_bloat):
1049 """Show details size information for each board
1050
1051 Args:
1052 target_list: List of targets, each a dict containing:
1053 'target': Target name
1054 'total_diff': Total difference in bytes across all areas
1055 <part_name>: Difference for that part
1056 show_bloat: Show detail for each function
1057 """
1058 targets_by_diff = sorted(target_list, reverse=True,
1059 key=lambda x: x['_total_diff'])
1060 for result in targets_by_diff:
1061 printed_target = False
1062 for name in sorted(result):
1063 diff = result[name]
1064 if name.startswith('_'):
1065 continue
1066 if diff != 0:
1067 color = self.col.RED if diff > 0 else self.col.GREEN
1068 msg = ' %s %+d' % (name, diff)
1069 if not printed_target:
Simon Glass02811582022-01-29 14:14:18 -07001070 tprint('%10s %-15s:' % ('', result['_target']),
Simon Glass4433aa92014-09-05 19:00:07 -06001071 newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001072 printed_target = True
Simon Glass02811582022-01-29 14:14:18 -07001073 tprint(msg, colour=color, newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001074 if printed_target:
Simon Glass02811582022-01-29 14:14:18 -07001075 tprint()
Simon Glassc05694f2013-04-03 11:07:16 +00001076 if show_bloat:
1077 target = result['_target']
1078 outcome = result['_outcome']
1079 base_outcome = self._base_board_dict[target]
1080 for fname in outcome.func_sizes:
1081 self.PrintFuncSizeDetail(fname,
1082 base_outcome.func_sizes[fname],
1083 outcome.func_sizes[fname])
1084
1085
1086 def PrintSizeSummary(self, board_selected, board_dict, show_detail,
1087 show_bloat):
1088 """Print a summary of image sizes broken down by section.
1089
1090 The summary takes the form of one line per architecture. The
1091 line contains deltas for each of the sections (+ means the section
Flavio Suligoie5e3c112020-01-29 09:56:05 +01001092 got bigger, - means smaller). The numbers are the average number
Simon Glassc05694f2013-04-03 11:07:16 +00001093 of bytes that a board in this section increased by.
1094
1095 For example:
1096 powerpc: (622 boards) text -0.0
1097 arm: (285 boards) text -0.0
Simon Glassc05694f2013-04-03 11:07:16 +00001098
1099 Args:
1100 board_selected: Dict containing boards to summarise, keyed by
1101 board.target
1102 board_dict: Dict containing boards for which we built this
1103 commit, keyed by board.target. The value is an Outcome object.
Simon Glassb4002462020-03-18 09:42:43 -06001104 show_detail: Show size delta detail for each board
Simon Glassc05694f2013-04-03 11:07:16 +00001105 show_bloat: Show detail for each function
1106 """
1107 arch_list = {}
1108 arch_count = {}
1109
1110 # Calculate changes in size for different image parts
1111 # The previous sizes are in Board.sizes, for each board
1112 for target in board_dict:
1113 if target not in board_selected:
1114 continue
1115 base_sizes = self._base_board_dict[target].sizes
1116 outcome = board_dict[target]
1117 sizes = outcome.sizes
1118
1119 # Loop through the list of images, creating a dict of size
1120 # changes for each image/part. We end up with something like
1121 # {'target' : 'snapper9g45, 'data' : 5, 'u-boot-spl:text' : -4}
1122 # which means that U-Boot data increased by 5 bytes and SPL
1123 # text decreased by 4.
1124 err = {'_target' : target}
1125 for image in sizes:
1126 if image in base_sizes:
1127 base_image = base_sizes[image]
1128 # Loop through the text, data, bss parts
1129 for part in sorted(sizes[image]):
1130 diff = sizes[image][part] - base_image[part]
1131 col = None
1132 if diff:
1133 if image == 'u-boot':
1134 name = part
1135 else:
1136 name = image + ':' + part
1137 err[name] = diff
1138 arch = board_selected[target].arch
1139 if not arch in arch_count:
1140 arch_count[arch] = 1
1141 else:
1142 arch_count[arch] += 1
1143 if not sizes:
1144 pass # Only add to our list when we have some stats
1145 elif not arch in arch_list:
1146 arch_list[arch] = [err]
1147 else:
1148 arch_list[arch].append(err)
1149
1150 # We now have a list of image size changes sorted by arch
1151 # Print out a summary of these
Simon Glassc78ed662019-10-31 07:42:53 -06001152 for arch, target_list in arch_list.items():
Simon Glassc05694f2013-04-03 11:07:16 +00001153 # Get total difference for each type
1154 totals = {}
1155 for result in target_list:
1156 total = 0
Simon Glassc78ed662019-10-31 07:42:53 -06001157 for name, diff in result.items():
Simon Glassc05694f2013-04-03 11:07:16 +00001158 if name.startswith('_'):
1159 continue
1160 total += diff
1161 if name in totals:
1162 totals[name] += diff
1163 else:
1164 totals[name] = diff
1165 result['_total_diff'] = total
1166 result['_outcome'] = board_dict[result['_target']]
1167
1168 count = len(target_list)
1169 printed_arch = False
1170 for name in sorted(totals):
1171 diff = totals[name]
1172 if diff:
1173 # Display the average difference in this name for this
1174 # architecture
1175 avg_diff = float(diff) / count
1176 color = self.col.RED if avg_diff > 0 else self.col.GREEN
1177 msg = ' %s %+1.1f' % (name, avg_diff)
1178 if not printed_arch:
Simon Glass02811582022-01-29 14:14:18 -07001179 tprint('%10s: (for %d/%d boards)' % (arch, count,
Simon Glass4433aa92014-09-05 19:00:07 -06001180 arch_count[arch]), newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001181 printed_arch = True
Simon Glass02811582022-01-29 14:14:18 -07001182 tprint(msg, colour=color, newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001183
1184 if printed_arch:
Simon Glass02811582022-01-29 14:14:18 -07001185 tprint()
Simon Glassc05694f2013-04-03 11:07:16 +00001186 if show_detail:
1187 self.PrintSizeDetail(target_list, show_bloat)
1188
1189
1190 def PrintResultSummary(self, board_selected, board_dict, err_lines,
Simon Glass03749d42014-08-28 09:43:44 -06001191 err_line_boards, warn_lines, warn_line_boards,
Alex Kiernan4059e302018-05-31 04:48:34 +00001192 config, environment, show_sizes, show_detail,
1193 show_bloat, show_config, show_environment):
Simon Glassc05694f2013-04-03 11:07:16 +00001194 """Compare results with the base results and display delta.
1195
1196 Only boards mentioned in board_selected will be considered. This
1197 function is intended to be called repeatedly with the results of
1198 each commit. It therefore shows a 'diff' between what it saw in
1199 the last call and what it sees now.
1200
1201 Args:
1202 board_selected: Dict containing boards to summarise, keyed by
1203 board.target
1204 board_dict: Dict containing boards for which we built this
1205 commit, keyed by board.target. The value is an Outcome object.
1206 err_lines: A list of errors for this commit, or [] if there is
1207 none, or we don't want to print errors
Simon Glass3394c9f2014-08-28 09:43:43 -06001208 err_line_boards: Dict keyed by error line, containing a list of
1209 the Board objects with that error
Simon Glass03749d42014-08-28 09:43:44 -06001210 warn_lines: A list of warnings for this commit, or [] if there is
1211 none, or we don't want to print errors
1212 warn_line_boards: Dict keyed by warning line, containing a list of
1213 the Board objects with that warning
Simon Glassdb17fb82015-02-05 22:06:15 -07001214 config: Dictionary keyed by filename - e.g. '.config'. Each
1215 value is itself a dictionary:
1216 key: config name
1217 value: config value
Alex Kiernan4059e302018-05-31 04:48:34 +00001218 environment: Dictionary keyed by environment variable, Each
1219 value is the value of environment variable.
Simon Glassc05694f2013-04-03 11:07:16 +00001220 show_sizes: Show image size deltas
Simon Glassb4002462020-03-18 09:42:43 -06001221 show_detail: Show size delta detail for each board if show_sizes
Simon Glassc05694f2013-04-03 11:07:16 +00001222 show_bloat: Show detail for each function
Simon Glassdb17fb82015-02-05 22:06:15 -07001223 show_config: Show config changes
Alex Kiernan4059e302018-05-31 04:48:34 +00001224 show_environment: Show environment changes
Simon Glassc05694f2013-04-03 11:07:16 +00001225 """
Simon Glass03749d42014-08-28 09:43:44 -06001226 def _BoardList(line, line_boards):
Simon Glass3394c9f2014-08-28 09:43:43 -06001227 """Helper function to get a line of boards containing a line
1228
1229 Args:
1230 line: Error line to search for
Simon Glassde0fefc2020-04-09 15:08:36 -06001231 line_boards: boards to search, each a Board
Simon Glass3394c9f2014-08-28 09:43:43 -06001232 Return:
Simon Glassde0fefc2020-04-09 15:08:36 -06001233 List of boards with that error line, or [] if the user has not
1234 requested such a list
Simon Glass3394c9f2014-08-28 09:43:43 -06001235 """
Simon Glass5df45222022-07-11 19:04:00 -06001236 brds = []
Simon Glassde0fefc2020-04-09 15:08:36 -06001237 board_set = set()
Simon Glass3394c9f2014-08-28 09:43:43 -06001238 if self._list_error_boards:
Simon Glass8132f982022-07-11 19:03:57 -06001239 for brd in line_boards[line]:
1240 if not brd in board_set:
Simon Glass5df45222022-07-11 19:04:00 -06001241 brds.append(brd)
Simon Glass8132f982022-07-11 19:03:57 -06001242 board_set.add(brd)
Simon Glass5df45222022-07-11 19:04:00 -06001243 return brds
Simon Glass3394c9f2014-08-28 09:43:43 -06001244
Simon Glass03749d42014-08-28 09:43:44 -06001245 def _CalcErrorDelta(base_lines, base_line_boards, lines, line_boards,
1246 char):
Simon Glassde0fefc2020-04-09 15:08:36 -06001247 """Calculate the required output based on changes in errors
1248
1249 Args:
1250 base_lines: List of errors/warnings for previous commit
1251 base_line_boards: Dict keyed by error line, containing a list
1252 of the Board objects with that error in the previous commit
1253 lines: List of errors/warning for this commit, each a str
1254 line_boards: Dict keyed by error line, containing a list
1255 of the Board objects with that error in this commit
1256 char: Character representing error ('') or warning ('w'). The
1257 broken ('+') or fixed ('-') characters are added in this
1258 function
1259
1260 Returns:
1261 Tuple
1262 List of ErrLine objects for 'better' lines
1263 List of ErrLine objects for 'worse' lines
1264 """
Simon Glass03749d42014-08-28 09:43:44 -06001265 better_lines = []
1266 worse_lines = []
1267 for line in lines:
1268 if line not in base_lines:
Simon Glassde0fefc2020-04-09 15:08:36 -06001269 errline = ErrLine(char + '+', _BoardList(line, line_boards),
1270 line)
1271 worse_lines.append(errline)
Simon Glass03749d42014-08-28 09:43:44 -06001272 for line in base_lines:
1273 if line not in lines:
Simon Glassde0fefc2020-04-09 15:08:36 -06001274 errline = ErrLine(char + '-',
1275 _BoardList(line, base_line_boards), line)
1276 better_lines.append(errline)
Simon Glass03749d42014-08-28 09:43:44 -06001277 return better_lines, worse_lines
1278
Simon Glassdb17fb82015-02-05 22:06:15 -07001279 def _CalcConfig(delta, name, config):
1280 """Calculate configuration changes
1281
1282 Args:
1283 delta: Type of the delta, e.g. '+'
1284 name: name of the file which changed (e.g. .config)
1285 config: configuration change dictionary
1286 key: config name
1287 value: config value
1288 Returns:
1289 String containing the configuration changes which can be
1290 printed
1291 """
1292 out = ''
1293 for key in sorted(config.keys()):
1294 out += '%s=%s ' % (key, config[key])
Simon Glasscad8abf2015-08-25 21:52:14 -06001295 return '%s %s: %s' % (delta, name, out)
Simon Glassdb17fb82015-02-05 22:06:15 -07001296
Simon Glasscad8abf2015-08-25 21:52:14 -06001297 def _AddConfig(lines, name, config_plus, config_minus, config_change):
1298 """Add changes in configuration to a list
Simon Glassdb17fb82015-02-05 22:06:15 -07001299
1300 Args:
Simon Glasscad8abf2015-08-25 21:52:14 -06001301 lines: list to add to
1302 name: config file name
Simon Glassdb17fb82015-02-05 22:06:15 -07001303 config_plus: configurations added, dictionary
1304 key: config name
1305 value: config value
1306 config_minus: configurations removed, dictionary
1307 key: config name
1308 value: config value
1309 config_change: configurations changed, dictionary
1310 key: config name
1311 value: config value
1312 """
1313 if config_plus:
Simon Glasscad8abf2015-08-25 21:52:14 -06001314 lines.append(_CalcConfig('+', name, config_plus))
Simon Glassdb17fb82015-02-05 22:06:15 -07001315 if config_minus:
Simon Glasscad8abf2015-08-25 21:52:14 -06001316 lines.append(_CalcConfig('-', name, config_minus))
Simon Glassdb17fb82015-02-05 22:06:15 -07001317 if config_change:
Simon Glasscad8abf2015-08-25 21:52:14 -06001318 lines.append(_CalcConfig('c', name, config_change))
1319
1320 def _OutputConfigInfo(lines):
1321 for line in lines:
1322 if not line:
1323 continue
1324 if line[0] == '+':
1325 col = self.col.GREEN
1326 elif line[0] == '-':
1327 col = self.col.RED
1328 elif line[0] == 'c':
1329 col = self.col.YELLOW
Simon Glass02811582022-01-29 14:14:18 -07001330 tprint(' ' + line, newline=True, colour=col)
Simon Glasscad8abf2015-08-25 21:52:14 -06001331
Simon Glassac500222020-04-09 15:08:28 -06001332 def _OutputErrLines(err_lines, colour):
1333 """Output the line of error/warning lines, if not empty
1334
1335 Also increments self._error_lines if err_lines not empty
1336
1337 Args:
Simon Glassde0fefc2020-04-09 15:08:36 -06001338 err_lines: List of ErrLine objects, each an error or warning
1339 line, possibly including a list of boards with that
1340 error/warning
Simon Glassac500222020-04-09 15:08:28 -06001341 colour: Colour to use for output
1342 """
1343 if err_lines:
Simon Glassea49f9b2020-04-09 15:08:37 -06001344 out_list = []
Simon Glassde0fefc2020-04-09 15:08:36 -06001345 for line in err_lines:
Simon Glass5df45222022-07-11 19:04:00 -06001346 names = [brd.target for brd in line.brds]
Simon Glass070589b2020-04-09 15:08:38 -06001347 board_str = ' '.join(names) if names else ''
Simon Glassea49f9b2020-04-09 15:08:37 -06001348 if board_str:
Simon Glassf45d3742022-01-29 14:14:17 -07001349 out = self.col.build(colour, line.char + '(')
1350 out += self.col.build(self.col.MAGENTA, board_str,
Simon Glassea49f9b2020-04-09 15:08:37 -06001351 bright=False)
Simon Glassf45d3742022-01-29 14:14:17 -07001352 out += self.col.build(colour, ') %s' % line.errline)
Simon Glassea49f9b2020-04-09 15:08:37 -06001353 else:
Simon Glassf45d3742022-01-29 14:14:17 -07001354 out = self.col.build(colour, line.char + line.errline)
Simon Glassea49f9b2020-04-09 15:08:37 -06001355 out_list.append(out)
Simon Glass02811582022-01-29 14:14:18 -07001356 tprint('\n'.join(out_list))
Simon Glassac500222020-04-09 15:08:28 -06001357 self._error_lines += 1
1358
Simon Glassdb17fb82015-02-05 22:06:15 -07001359
Simon Glass454507f2018-11-06 16:02:12 -07001360 ok_boards = [] # List of boards fixed since last commit
Simon Glass071a1782018-11-06 16:02:13 -07001361 warn_boards = [] # List of boards with warnings since last commit
Simon Glass454507f2018-11-06 16:02:12 -07001362 err_boards = [] # List of new broken boards since last commit
1363 new_boards = [] # List of boards that didn't exist last time
1364 unknown_boards = [] # List of boards that were not built
Simon Glassc05694f2013-04-03 11:07:16 +00001365
1366 for target in board_dict:
1367 if target not in board_selected:
1368 continue
1369
1370 # If the board was built last time, add its outcome to a list
1371 if target in self._base_board_dict:
1372 base_outcome = self._base_board_dict[target].rc
1373 outcome = board_dict[target]
1374 if outcome.rc == OUTCOME_UNKNOWN:
Simon Glass454507f2018-11-06 16:02:12 -07001375 unknown_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001376 elif outcome.rc < base_outcome:
Simon Glass071a1782018-11-06 16:02:13 -07001377 if outcome.rc == OUTCOME_WARNING:
1378 warn_boards.append(target)
1379 else:
1380 ok_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001381 elif outcome.rc > base_outcome:
Simon Glass071a1782018-11-06 16:02:13 -07001382 if outcome.rc == OUTCOME_WARNING:
1383 warn_boards.append(target)
1384 else:
1385 err_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001386 else:
Simon Glass454507f2018-11-06 16:02:12 -07001387 new_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001388
Simon Glassac500222020-04-09 15:08:28 -06001389 # Get a list of errors and warnings that have appeared, and disappeared
Simon Glass03749d42014-08-28 09:43:44 -06001390 better_err, worse_err = _CalcErrorDelta(self._base_err_lines,
1391 self._base_err_line_boards, err_lines, err_line_boards, '')
1392 better_warn, worse_warn = _CalcErrorDelta(self._base_warn_lines,
1393 self._base_warn_line_boards, warn_lines, warn_line_boards, 'w')
Simon Glassc05694f2013-04-03 11:07:16 +00001394
Simon Glass6c435622022-07-11 19:03:56 -06001395 # For the IDE mode, print out all the output
1396 if self._ide:
1397 outcome = board_dict[target]
1398 for line in outcome.err_lines:
1399 sys.stderr.write(line)
1400
Simon Glassc05694f2013-04-03 11:07:16 +00001401 # Display results by arch
Simon Glass6c435622022-07-11 19:03:56 -06001402 elif any((ok_boards, warn_boards, err_boards, unknown_boards, new_boards,
Simon Glass071a1782018-11-06 16:02:13 -07001403 worse_err, better_err, worse_warn, better_warn)):
Simon Glassc05694f2013-04-03 11:07:16 +00001404 arch_list = {}
Simon Glass454507f2018-11-06 16:02:12 -07001405 self.AddOutcome(board_selected, arch_list, ok_boards, '',
Simon Glassc05694f2013-04-03 11:07:16 +00001406 self.col.GREEN)
Simon Glass071a1782018-11-06 16:02:13 -07001407 self.AddOutcome(board_selected, arch_list, warn_boards, 'w+',
1408 self.col.YELLOW)
Simon Glass454507f2018-11-06 16:02:12 -07001409 self.AddOutcome(board_selected, arch_list, err_boards, '+',
Simon Glassc05694f2013-04-03 11:07:16 +00001410 self.col.RED)
Simon Glass454507f2018-11-06 16:02:12 -07001411 self.AddOutcome(board_selected, arch_list, new_boards, '*', self.col.BLUE)
Simon Glassc05694f2013-04-03 11:07:16 +00001412 if self._show_unknown:
Simon Glass454507f2018-11-06 16:02:12 -07001413 self.AddOutcome(board_selected, arch_list, unknown_boards, '?',
Simon Glassc05694f2013-04-03 11:07:16 +00001414 self.col.MAGENTA)
Simon Glassc78ed662019-10-31 07:42:53 -06001415 for arch, target_list in arch_list.items():
Simon Glass02811582022-01-29 14:14:18 -07001416 tprint('%10s: %s' % (arch, target_list))
Simon Glassbb4dffb2014-08-09 15:33:06 -06001417 self._error_lines += 1
Simon Glassac500222020-04-09 15:08:28 -06001418 _OutputErrLines(better_err, colour=self.col.GREEN)
1419 _OutputErrLines(worse_err, colour=self.col.RED)
1420 _OutputErrLines(better_warn, colour=self.col.CYAN)
Simon Glass564ddac2020-04-09 15:08:35 -06001421 _OutputErrLines(worse_warn, colour=self.col.YELLOW)
Simon Glassc05694f2013-04-03 11:07:16 +00001422
1423 if show_sizes:
1424 self.PrintSizeSummary(board_selected, board_dict, show_detail,
1425 show_bloat)
1426
Alex Kiernan4059e302018-05-31 04:48:34 +00001427 if show_environment and self._base_environment:
1428 lines = []
1429
1430 for target in board_dict:
1431 if target not in board_selected:
1432 continue
1433
1434 tbase = self._base_environment[target]
1435 tenvironment = environment[target]
1436 environment_plus = {}
1437 environment_minus = {}
1438 environment_change = {}
1439 base = tbase.environment
Simon Glassc78ed662019-10-31 07:42:53 -06001440 for key, value in tenvironment.environment.items():
Alex Kiernan4059e302018-05-31 04:48:34 +00001441 if key not in base:
1442 environment_plus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001443 for key, value in base.items():
Alex Kiernan4059e302018-05-31 04:48:34 +00001444 if key not in tenvironment.environment:
1445 environment_minus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001446 for key, value in base.items():
Alex Kiernan4059e302018-05-31 04:48:34 +00001447 new_value = tenvironment.environment.get(key)
1448 if new_value and value != new_value:
1449 desc = '%s -> %s' % (value, new_value)
1450 environment_change[key] = desc
1451
1452 _AddConfig(lines, target, environment_plus, environment_minus,
1453 environment_change)
1454
1455 _OutputConfigInfo(lines)
1456
Simon Glasscad8abf2015-08-25 21:52:14 -06001457 if show_config and self._base_config:
1458 summary = {}
1459 arch_config_plus = {}
1460 arch_config_minus = {}
1461 arch_config_change = {}
1462 arch_list = []
1463
1464 for target in board_dict:
1465 if target not in board_selected:
1466 continue
1467 arch = board_selected[target].arch
1468 if arch not in arch_list:
1469 arch_list.append(arch)
1470
1471 for arch in arch_list:
1472 arch_config_plus[arch] = {}
1473 arch_config_minus[arch] = {}
1474 arch_config_change[arch] = {}
Simon Glasscde5c302016-11-13 14:25:53 -07001475 for name in self.config_filenames:
Simon Glasscad8abf2015-08-25 21:52:14 -06001476 arch_config_plus[arch][name] = {}
1477 arch_config_minus[arch][name] = {}
1478 arch_config_change[arch][name] = {}
1479
1480 for target in board_dict:
1481 if target not in board_selected:
Simon Glassdb17fb82015-02-05 22:06:15 -07001482 continue
Simon Glasscad8abf2015-08-25 21:52:14 -06001483
1484 arch = board_selected[target].arch
1485
1486 all_config_plus = {}
1487 all_config_minus = {}
1488 all_config_change = {}
1489 tbase = self._base_config[target]
1490 tconfig = config[target]
1491 lines = []
Simon Glasscde5c302016-11-13 14:25:53 -07001492 for name in self.config_filenames:
Simon Glasscad8abf2015-08-25 21:52:14 -06001493 if not tconfig.config[name]:
1494 continue
1495 config_plus = {}
1496 config_minus = {}
1497 config_change = {}
1498 base = tbase.config[name]
Simon Glassc78ed662019-10-31 07:42:53 -06001499 for key, value in tconfig.config[name].items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001500 if key not in base:
1501 config_plus[key] = value
1502 all_config_plus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001503 for key, value in base.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001504 if key not in tconfig.config[name]:
1505 config_minus[key] = value
1506 all_config_minus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001507 for key, value in base.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001508 new_value = tconfig.config.get(key)
1509 if new_value and value != new_value:
1510 desc = '%s -> %s' % (value, new_value)
1511 config_change[key] = desc
1512 all_config_change[key] = desc
1513
1514 arch_config_plus[arch][name].update(config_plus)
1515 arch_config_minus[arch][name].update(config_minus)
1516 arch_config_change[arch][name].update(config_change)
1517
1518 _AddConfig(lines, name, config_plus, config_minus,
1519 config_change)
1520 _AddConfig(lines, 'all', all_config_plus, all_config_minus,
1521 all_config_change)
1522 summary[target] = '\n'.join(lines)
1523
1524 lines_by_target = {}
Simon Glassc78ed662019-10-31 07:42:53 -06001525 for target, lines in summary.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001526 if lines in lines_by_target:
1527 lines_by_target[lines].append(target)
1528 else:
1529 lines_by_target[lines] = [target]
1530
1531 for arch in arch_list:
1532 lines = []
1533 all_plus = {}
1534 all_minus = {}
1535 all_change = {}
Simon Glasscde5c302016-11-13 14:25:53 -07001536 for name in self.config_filenames:
Simon Glasscad8abf2015-08-25 21:52:14 -06001537 all_plus.update(arch_config_plus[arch][name])
1538 all_minus.update(arch_config_minus[arch][name])
1539 all_change.update(arch_config_change[arch][name])
1540 _AddConfig(lines, name, arch_config_plus[arch][name],
1541 arch_config_minus[arch][name],
1542 arch_config_change[arch][name])
1543 _AddConfig(lines, 'all', all_plus, all_minus, all_change)
1544 #arch_summary[target] = '\n'.join(lines)
1545 if lines:
Simon Glass02811582022-01-29 14:14:18 -07001546 tprint('%s:' % arch)
Simon Glasscad8abf2015-08-25 21:52:14 -06001547 _OutputConfigInfo(lines)
1548
Simon Glassc78ed662019-10-31 07:42:53 -06001549 for lines, targets in lines_by_target.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001550 if not lines:
1551 continue
Simon Glass02811582022-01-29 14:14:18 -07001552 tprint('%s :' % ' '.join(sorted(targets)))
Simon Glasscad8abf2015-08-25 21:52:14 -06001553 _OutputConfigInfo(lines.split('\n'))
1554
Simon Glassdb17fb82015-02-05 22:06:15 -07001555
Simon Glassc05694f2013-04-03 11:07:16 +00001556 # Save our updated information for the next call to this function
1557 self._base_board_dict = board_dict
1558 self._base_err_lines = err_lines
Simon Glass03749d42014-08-28 09:43:44 -06001559 self._base_warn_lines = warn_lines
1560 self._base_err_line_boards = err_line_boards
1561 self._base_warn_line_boards = warn_line_boards
Simon Glassdb17fb82015-02-05 22:06:15 -07001562 self._base_config = config
Alex Kiernan4059e302018-05-31 04:48:34 +00001563 self._base_environment = environment
Simon Glassc05694f2013-04-03 11:07:16 +00001564
1565 # Get a list of boards that did not get built, if needed
1566 not_built = []
Simon Glass8132f982022-07-11 19:03:57 -06001567 for brd in board_selected:
1568 if not brd in board_dict:
1569 not_built.append(brd)
Simon Glassc05694f2013-04-03 11:07:16 +00001570 if not_built:
Simon Glass02811582022-01-29 14:14:18 -07001571 tprint("Boards not built (%d): %s" % (len(not_built),
Simon Glass4433aa92014-09-05 19:00:07 -06001572 ', '.join(not_built)))
Simon Glassc05694f2013-04-03 11:07:16 +00001573
Simon Glasseb48bbc2014-08-09 15:33:02 -06001574 def ProduceResultSummary(self, commit_upto, commits, board_selected):
Simon Glass03749d42014-08-28 09:43:44 -06001575 (board_dict, err_lines, err_line_boards, warn_lines,
Alex Kiernan4059e302018-05-31 04:48:34 +00001576 warn_line_boards, config, environment) = self.GetResultSummary(
Simon Glass3394c9f2014-08-28 09:43:43 -06001577 board_selected, commit_upto,
Simon Glassdb17fb82015-02-05 22:06:15 -07001578 read_func_sizes=self._show_bloat,
Alex Kiernan4059e302018-05-31 04:48:34 +00001579 read_config=self._show_config,
1580 read_environment=self._show_environment)
Simon Glasseb48bbc2014-08-09 15:33:02 -06001581 if commits:
1582 msg = '%02d: %s' % (commit_upto + 1,
1583 commits[commit_upto].subject)
Simon Glass02811582022-01-29 14:14:18 -07001584 tprint(msg, colour=self.col.BLUE)
Simon Glasseb48bbc2014-08-09 15:33:02 -06001585 self.PrintResultSummary(board_selected, board_dict,
Simon Glass3394c9f2014-08-28 09:43:43 -06001586 err_lines if self._show_errors else [], err_line_boards,
Simon Glass03749d42014-08-28 09:43:44 -06001587 warn_lines if self._show_errors else [], warn_line_boards,
Alex Kiernan4059e302018-05-31 04:48:34 +00001588 config, environment, self._show_sizes, self._show_detail,
1589 self._show_bloat, self._show_config, self._show_environment)
Simon Glassc05694f2013-04-03 11:07:16 +00001590
Simon Glasseb48bbc2014-08-09 15:33:02 -06001591 def ShowSummary(self, commits, board_selected):
Simon Glassc05694f2013-04-03 11:07:16 +00001592 """Show a build summary for U-Boot for a given board list.
1593
1594 Reset the result summary, then repeatedly call GetResultSummary on
1595 each commit's results, then display the differences we see.
1596
1597 Args:
1598 commit: Commit objects to summarise
1599 board_selected: Dict containing boards to summarise
Simon Glassc05694f2013-04-03 11:07:16 +00001600 """
Simon Glassd326ad72014-08-09 15:32:59 -06001601 self.commit_count = len(commits) if commits else 1
Simon Glassc05694f2013-04-03 11:07:16 +00001602 self.commits = commits
1603 self.ResetResultSummary(board_selected)
Simon Glassbb4dffb2014-08-09 15:33:06 -06001604 self._error_lines = 0
Simon Glassc05694f2013-04-03 11:07:16 +00001605
1606 for commit_upto in range(0, self.commit_count, self._step):
Simon Glasseb48bbc2014-08-09 15:33:02 -06001607 self.ProduceResultSummary(commit_upto, commits, board_selected)
Simon Glassbb4dffb2014-08-09 15:33:06 -06001608 if not self._error_lines:
Simon Glass02811582022-01-29 14:14:18 -07001609 tprint('(no errors to report)', colour=self.col.GREEN)
Simon Glassc05694f2013-04-03 11:07:16 +00001610
1611
1612 def SetupBuild(self, board_selected, commits):
1613 """Set up ready to start a build.
1614
1615 Args:
1616 board_selected: Selected boards to build
1617 commits: Selected commits to build
1618 """
1619 # First work out how many commits we will build
Simon Glassc78ed662019-10-31 07:42:53 -06001620 count = (self.commit_count + self._step - 1) // self._step
Simon Glassc05694f2013-04-03 11:07:16 +00001621 self.count = len(board_selected) * count
1622 self.upto = self.warned = self.fail = 0
1623 self._timestamps = collections.deque()
1624
Simon Glassc05694f2013-04-03 11:07:16 +00001625 def GetThreadDir(self, thread_num):
1626 """Get the directory path to the working dir for a thread.
1627
1628 Args:
Simon Glassc635d892021-01-30 22:17:46 -07001629 thread_num: Number of thread to check (-1 for main process, which
1630 is treated as 0)
Simon Glassc05694f2013-04-03 11:07:16 +00001631 """
Simon Glassb6eb8cf2020-03-18 09:42:42 -06001632 if self.work_in_output:
1633 return self._working_dir
Simon Glassc635d892021-01-30 22:17:46 -07001634 return os.path.join(self._working_dir, '%02d' % max(thread_num, 0))
Simon Glassc05694f2013-04-03 11:07:16 +00001635
Simon Glassd326ad72014-08-09 15:32:59 -06001636 def _PrepareThread(self, thread_num, setup_git):
Simon Glassc05694f2013-04-03 11:07:16 +00001637 """Prepare the working directory for a thread.
1638
1639 This clones or fetches the repo into the thread's work directory.
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001640 Optionally, it can create a linked working tree of the repo in the
1641 thread's work directory instead.
Simon Glassc05694f2013-04-03 11:07:16 +00001642
1643 Args:
1644 thread_num: Thread number (0, 1, ...)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001645 setup_git:
1646 'clone' to set up a git clone
1647 'worktree' to set up a git worktree
Simon Glassc05694f2013-04-03 11:07:16 +00001648 """
1649 thread_dir = self.GetThreadDir(thread_num)
Simon Glass4a1e88b2014-08-09 15:33:00 -06001650 builderthread.Mkdir(thread_dir)
Simon Glassc05694f2013-04-03 11:07:16 +00001651 git_dir = os.path.join(thread_dir, '.git')
1652
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001653 # Create a worktree or a git repo clone for this thread if it
1654 # doesn't already exist
Simon Glassd326ad72014-08-09 15:32:59 -06001655 if setup_git and self.git_dir:
Simon Glassc05694f2013-04-03 11:07:16 +00001656 src_dir = os.path.abspath(self.git_dir)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001657 if os.path.isdir(git_dir):
1658 # This is a clone of the src_dir repo, we can keep using
1659 # it but need to fetch from src_dir.
Simon Glass02811582022-01-29 14:14:18 -07001660 tprint('\rFetching repo for thread %d' % thread_num,
Simon Glass43054932020-04-09 15:08:43 -06001661 newline=False)
Simon Glass761648b2022-01-29 14:14:11 -07001662 gitutil.fetch(git_dir, thread_dir)
Simon Glass02811582022-01-29 14:14:18 -07001663 terminal.print_clear()
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001664 elif os.path.isfile(git_dir):
1665 # This is a worktree of the src_dir repo, we don't need to
1666 # create it again or update it in any way.
1667 pass
1668 elif os.path.exists(git_dir):
1669 # Don't know what could trigger this, but we probably
1670 # can't create a git worktree/clone here.
1671 raise ValueError('Git dir %s exists, but is not a file '
1672 'or a directory.' % git_dir)
1673 elif setup_git == 'worktree':
Simon Glass02811582022-01-29 14:14:18 -07001674 tprint('\rChecking out worktree for thread %d' % thread_num,
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001675 newline=False)
Simon Glass761648b2022-01-29 14:14:11 -07001676 gitutil.add_worktree(src_dir, thread_dir)
Simon Glass02811582022-01-29 14:14:18 -07001677 terminal.print_clear()
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001678 elif setup_git == 'clone' or setup_git == True:
Simon Glass02811582022-01-29 14:14:18 -07001679 tprint('\rCloning repo for thread %d' % thread_num,
Simon Glass2e2a6e62016-09-18 16:48:31 -06001680 newline=False)
Simon Glass761648b2022-01-29 14:14:11 -07001681 gitutil.clone(src_dir, thread_dir)
Simon Glass02811582022-01-29 14:14:18 -07001682 terminal.print_clear()
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001683 else:
1684 raise ValueError("Can't setup git repo with %s." % setup_git)
Simon Glassc05694f2013-04-03 11:07:16 +00001685
Simon Glassd326ad72014-08-09 15:32:59 -06001686 def _PrepareWorkingSpace(self, max_threads, setup_git):
Simon Glassc05694f2013-04-03 11:07:16 +00001687 """Prepare the working directory for use.
1688
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001689 Set up the git repo for each thread. Creates a linked working tree
1690 if git-worktree is available, or clones the repo if it isn't.
Simon Glassc05694f2013-04-03 11:07:16 +00001691
1692 Args:
Simon Glassc635d892021-01-30 22:17:46 -07001693 max_threads: Maximum number of threads we expect to need. If 0 then
1694 1 is set up, since the main process still needs somewhere to
1695 work
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001696 setup_git: True to set up a git worktree or a git clone
Simon Glassc05694f2013-04-03 11:07:16 +00001697 """
Simon Glass4a1e88b2014-08-09 15:33:00 -06001698 builderthread.Mkdir(self._working_dir)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001699 if setup_git and self.git_dir:
1700 src_dir = os.path.abspath(self.git_dir)
Simon Glass761648b2022-01-29 14:14:11 -07001701 if gitutil.check_worktree_is_available(src_dir):
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001702 setup_git = 'worktree'
1703 # If we previously added a worktree but the directory for it
1704 # got deleted, we need to prune its files from the repo so
1705 # that we can check out another in its place.
Simon Glass761648b2022-01-29 14:14:11 -07001706 gitutil.prune_worktrees(src_dir)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001707 else:
1708 setup_git = 'clone'
Simon Glassc635d892021-01-30 22:17:46 -07001709
1710 # Always do at least one thread
1711 for thread in range(max(max_threads, 1)):
Simon Glassd326ad72014-08-09 15:32:59 -06001712 self._PrepareThread(thread, setup_git)
Simon Glassc05694f2013-04-03 11:07:16 +00001713
Simon Glass5dc1ca72020-03-18 09:42:45 -06001714 def _GetOutputSpaceRemovals(self):
Simon Glassc05694f2013-04-03 11:07:16 +00001715 """Get the output directories ready to receive files.
1716
Simon Glass5dc1ca72020-03-18 09:42:45 -06001717 Figure out what needs to be deleted in the output directory before it
1718 can be used. We only delete old buildman directories which have the
1719 expected name pattern. See _GetOutputDir().
1720
1721 Returns:
1722 List of full paths of directories to remove
Simon Glassc05694f2013-04-03 11:07:16 +00001723 """
Simon Glassb9a9b7c2014-12-01 17:33:53 -07001724 if not self.commits:
1725 return
Simon Glassc05694f2013-04-03 11:07:16 +00001726 dir_list = []
1727 for commit_upto in range(self.commit_count):
1728 dir_list.append(self._GetOutputDir(commit_upto))
1729
Simon Glass83cb6cc2016-09-18 16:48:32 -06001730 to_remove = []
Simon Glassc05694f2013-04-03 11:07:16 +00001731 for dirname in glob.glob(os.path.join(self.base_dir, '*')):
1732 if dirname not in dir_list:
Simon Glass5dc1ca72020-03-18 09:42:45 -06001733 leaf = dirname[len(self.base_dir) + 1:]
Ovidiu Panaitee8e9cb2020-05-15 09:30:12 +03001734 m = re.match('[0-9]+_g[0-9a-f]+_.*', leaf)
Simon Glass5dc1ca72020-03-18 09:42:45 -06001735 if m:
1736 to_remove.append(dirname)
1737 return to_remove
1738
1739 def _PrepareOutputSpace(self):
1740 """Get the output directories ready to receive files.
1741
1742 We delete any output directories which look like ones we need to
1743 create. Having left over directories is confusing when the user wants
1744 to check the output manually.
1745 """
1746 to_remove = self._GetOutputSpaceRemovals()
Simon Glass83cb6cc2016-09-18 16:48:32 -06001747 if to_remove:
Simon Glass02811582022-01-29 14:14:18 -07001748 tprint('Removing %d old build directories...' % len(to_remove),
Simon Glass83cb6cc2016-09-18 16:48:32 -06001749 newline=False)
1750 for dirname in to_remove:
Simon Glassc05694f2013-04-03 11:07:16 +00001751 shutil.rmtree(dirname)
Simon Glass02811582022-01-29 14:14:18 -07001752 terminal.print_clear()
Simon Glassc05694f2013-04-03 11:07:16 +00001753
Simon Glass78e418e2014-08-09 15:33:03 -06001754 def BuildBoards(self, commits, board_selected, keep_outputs, verbose):
Simon Glassc05694f2013-04-03 11:07:16 +00001755 """Build all commits for a list of boards
1756
1757 Args:
1758 commits: List of commits to be build, each a Commit object
1759 boards_selected: Dict of selected boards, key is target name,
1760 value is Board object
Simon Glassc05694f2013-04-03 11:07:16 +00001761 keep_outputs: True to save build output files
Simon Glass78e418e2014-08-09 15:33:03 -06001762 verbose: Display build results as they are completed
Simon Glassc2f91072014-08-28 09:43:39 -06001763 Returns:
1764 Tuple containing:
1765 - number of boards that failed to build
1766 - number of boards that issued warnings
Simon Glass9bf9a722021-04-11 16:27:27 +12001767 - list of thread exceptions raised
Simon Glassc05694f2013-04-03 11:07:16 +00001768 """
Simon Glassd326ad72014-08-09 15:32:59 -06001769 self.commit_count = len(commits) if commits else 1
Simon Glassc05694f2013-04-03 11:07:16 +00001770 self.commits = commits
Simon Glass78e418e2014-08-09 15:33:03 -06001771 self._verbose = verbose
Simon Glassc05694f2013-04-03 11:07:16 +00001772
1773 self.ResetResultSummary(board_selected)
Thierry Reding336d5bd2014-08-19 10:22:39 +02001774 builderthread.Mkdir(self.base_dir, parents = True)
Simon Glassd326ad72014-08-09 15:32:59 -06001775 self._PrepareWorkingSpace(min(self.num_threads, len(board_selected)),
1776 commits is not None)
Simon Glassc05694f2013-04-03 11:07:16 +00001777 self._PrepareOutputSpace()
Simon Glass6c435622022-07-11 19:03:56 -06001778 if not self._ide:
1779 tprint('\rStarting build...', newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001780 self.SetupBuild(board_selected, commits)
1781 self.ProcessResult(None)
Simon Glass9bf9a722021-04-11 16:27:27 +12001782 self.thread_exceptions = []
Simon Glassc05694f2013-04-03 11:07:16 +00001783 # Create jobs to build all commits for each board
Simon Glassc78ed662019-10-31 07:42:53 -06001784 for brd in board_selected.values():
Simon Glass4a1e88b2014-08-09 15:33:00 -06001785 job = builderthread.BuilderJob()
Simon Glass8132f982022-07-11 19:03:57 -06001786 job.brd = brd
Simon Glassc05694f2013-04-03 11:07:16 +00001787 job.commits = commits
1788 job.keep_outputs = keep_outputs
Simon Glassb6eb8cf2020-03-18 09:42:42 -06001789 job.work_in_output = self.work_in_output
Simon Glasse5650a82022-01-22 05:07:33 -07001790 job.adjust_cfg = self.adjust_cfg
Simon Glassc05694f2013-04-03 11:07:16 +00001791 job.step = self._step
Simon Glassc635d892021-01-30 22:17:46 -07001792 if self.num_threads:
1793 self.queue.put(job)
1794 else:
Simon Glasse36fe012022-02-11 13:23:19 -07001795 self._single_builder.RunJob(job)
Simon Glassc05694f2013-04-03 11:07:16 +00001796
Simon Glassc635d892021-01-30 22:17:46 -07001797 if self.num_threads:
1798 term = threading.Thread(target=self.queue.join)
1799 term.setDaemon(True)
1800 term.start()
1801 while term.is_alive():
1802 term.join(100)
Simon Glassc05694f2013-04-03 11:07:16 +00001803
Simon Glassc635d892021-01-30 22:17:46 -07001804 # Wait until we have processed all output
1805 self.out_queue.join()
Simon Glass6c435622022-07-11 19:03:56 -06001806 if not self._ide:
1807 tprint()
Simon Glass726ae812020-04-09 15:08:47 -06001808
Simon Glass6c435622022-07-11 19:03:56 -06001809 msg = 'Completed: %d total built' % self.count
1810 if self.already_done:
1811 msg += ' (%d previously' % self.already_done
1812 if self.already_done != self.count:
1813 msg += ', %d newly' % (self.count - self.already_done)
1814 msg += ')'
1815 duration = datetime.now() - self._start_time
1816 if duration > timedelta(microseconds=1000000):
1817 if duration.microseconds >= 500000:
1818 duration = duration + timedelta(seconds=1)
1819 duration = duration - timedelta(microseconds=duration.microseconds)
1820 rate = float(self.count) / duration.total_seconds()
1821 msg += ', duration %s, rate %1.2f' % (duration, rate)
1822 tprint(msg)
1823 if self.thread_exceptions:
1824 tprint('Failed: %d thread exceptions' % len(self.thread_exceptions),
1825 colour=self.col.RED)
Simon Glass726ae812020-04-09 15:08:47 -06001826
Simon Glass9bf9a722021-04-11 16:27:27 +12001827 return (self.fail, self.warned, self.thread_exceptions)