blob: ce852eb03a38278f3a139800d956b91355aceb19 [file] [log] [blame]
Tom Rini83d290c2018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glassfc3fe1c2013-04-03 11:07:16 +00002# Copyright (c) 2013 The Chromium OS Authors.
3#
4# Bloat-o-meter code used here Copyright 2004 Matt Mackall <mpm@selenic.com>
5#
Simon Glassfc3fe1c2013-04-03 11:07:16 +00006
7import collections
Simon Glassfc3fe1c2013-04-03 11:07:16 +00008from datetime import datetime, timedelta
9import glob
10import os
11import re
Simon Glassc05aa032019-10-31 07:42:53 -060012import queue
Simon Glassfc3fe1c2013-04-03 11:07:16 +000013import shutil
Simon Glass2f256642016-09-18 16:48:37 -060014import signal
Simon Glassfc3fe1c2013-04-03 11:07:16 +000015import string
16import sys
Simon Glassd436e382016-09-18 16:48:35 -060017import threading
Simon Glassfc3fe1c2013-04-03 11:07:16 +000018import time
19
Simon Glass0ede00f2020-04-17 18:09:02 -060020from buildman import builderthread
21from buildman import toolchain
Simon Glassbf776672020-04-17 18:09:04 -060022from patman import command
23from patman import gitutil
24from patman import terminal
25from patman.terminal import Print
Simon Glassfc3fe1c2013-04-03 11:07:16 +000026
Simon Glassfc3fe1c2013-04-03 11:07:16 +000027"""
28Theory of Operation
29
30Please see README for user documentation, and you should be familiar with
31that before trying to make sense of this.
32
33Buildman works by keeping the machine as busy as possible, building different
34commits for different boards on multiple CPUs at once.
35
36The source repo (self.git_dir) contains all the commits to be built. Each
37thread works on a single board at a time. It checks out the first commit,
38configures it for that board, then builds it. Then it checks out the next
39commit and builds it (typically without re-configuring). When it runs out
40of commits, it gets another job from the builder and starts again with that
41board.
42
43Clearly the builder threads could work either way - they could check out a
44commit and then built it for all boards. Using separate directories for each
45commit/board pair they could leave their build product around afterwards
46also.
47
48The intent behind building a single board for multiple commits, is to make
49use of incremental builds. Since each commit is built incrementally from
50the previous one, builds are faster. Reconfiguring for a different board
51removes all intermediate object files.
52
53Many threads can be working at once, but each has its own working directory.
54When a thread finishes a build, it puts the output files into a result
55directory.
56
57The base directory used by buildman is normally '../<branch>', i.e.
58a directory higher than the source repository and named after the branch
59being built.
60
61Within the base directory, we have one subdirectory for each commit. Within
62that is one subdirectory for each board. Within that is the build output for
63that commit/board combination.
64
65Buildman also create working directories for each thread, in a .bm-work/
66subdirectory in the base dir.
67
68As an example, say we are building branch 'us-net' for boards 'sandbox' and
69'seaboard', and say that us-net has two commits. We will have directories
70like this:
71
72us-net/ base directory
Ovidiu Panait7664b032020-05-15 09:30:12 +030073 01_g4ed4ebc_net--Add-tftp-speed-/
Simon Glassfc3fe1c2013-04-03 11:07:16 +000074 sandbox/
75 u-boot.bin
76 seaboard/
77 u-boot.bin
Ovidiu Panait7664b032020-05-15 09:30:12 +030078 02_g4ed4ebc_net--Check-tftp-comp/
Simon Glassfc3fe1c2013-04-03 11:07:16 +000079 sandbox/
80 u-boot.bin
81 seaboard/
82 u-boot.bin
83 .bm-work/
84 00/ working directory for thread 0 (contains source checkout)
85 build/ build output
86 01/ working directory for thread 1
87 build/ build output
88 ...
89u-boot/ source directory
90 .git/ repository
91"""
92
Simon Glass35d696d2020-04-09 15:08:36 -060093"""Holds information about a particular error line we are outputing
94
95 char: Character representation: '+': error, '-': fixed error, 'w+': warning,
96 'w-' = fixed warning
97 boards: List of Board objects which have line in the error/warning output
98 errline: The text of the error line
99"""
100ErrLine = collections.namedtuple('ErrLine', 'char,boards,errline')
101
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000102# Possible build outcomes
Simon Glassc05aa032019-10-31 07:42:53 -0600103OUTCOME_OK, OUTCOME_WARNING, OUTCOME_ERROR, OUTCOME_UNKNOWN = list(range(4))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000104
Simon Glass9a6d2e22017-04-12 18:23:26 -0600105# Translate a commit subject into a valid filename (and handle unicode)
Simon Glassc05aa032019-10-31 07:42:53 -0600106trans_valid_chars = str.maketrans('/: ', '---')
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000107
Simon Glassb464f8e2016-11-13 14:25:53 -0700108BASE_CONFIG_FILENAMES = [
109 'u-boot.cfg', 'u-boot-spl.cfg', 'u-boot-tpl.cfg'
110]
111
112EXTRA_CONFIG_FILENAMES = [
Simon Glass843312d2015-02-05 22:06:15 -0700113 '.config', '.config-spl', '.config-tpl',
114 'autoconf.mk', 'autoconf-spl.mk', 'autoconf-tpl.mk',
115 'autoconf.h', 'autoconf-spl.h','autoconf-tpl.h',
Simon Glass843312d2015-02-05 22:06:15 -0700116]
117
Simon Glass8270e3c2015-08-25 21:52:14 -0600118class Config:
119 """Holds information about configuration settings for a board."""
Simon Glassb464f8e2016-11-13 14:25:53 -0700120 def __init__(self, config_filename, target):
Simon Glass8270e3c2015-08-25 21:52:14 -0600121 self.target = target
122 self.config = {}
Simon Glassb464f8e2016-11-13 14:25:53 -0700123 for fname in config_filename:
Simon Glass8270e3c2015-08-25 21:52:14 -0600124 self.config[fname] = {}
125
126 def Add(self, fname, key, value):
127 self.config[fname][key] = value
128
129 def __hash__(self):
130 val = 0
131 for fname in self.config:
Simon Glassc05aa032019-10-31 07:42:53 -0600132 for key, value in self.config[fname].items():
133 print(key, value)
Simon Glass8270e3c2015-08-25 21:52:14 -0600134 val = val ^ hash(key) & hash(value)
135 return val
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000136
Alex Kiernan48ae4122018-05-31 04:48:34 +0000137class Environment:
138 """Holds information about environment variables for a board."""
139 def __init__(self, target):
140 self.target = target
141 self.environment = {}
142
143 def Add(self, key, value):
144 self.environment[key] = value
145
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000146class Builder:
147 """Class for building U-Boot for a particular commit.
148
149 Public members: (many should ->private)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000150 already_done: Number of builds already completed
151 base_dir: Base directory to use for builder
152 checkout: True to check out source, False to skip that step.
153 This is used for testing.
154 col: terminal.Color() object
155 count: Number of commits to build
156 do_make: Method to call to invoke Make
157 fail: Number of builds that failed due to error
158 force_build: Force building even if a build already exists
159 force_config_on_failure: If a commit fails for a board, disable
160 incremental building for the next commit we build for that
161 board, so that we will see all warnings/errors again.
Simon Glass4266dc22014-07-13 12:22:31 -0600162 force_build_failures: If a previously-built build (i.e. built on
163 a previous run of buildman) is marked as failed, rebuild it.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000164 git_dir: Git directory containing source repository
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000165 num_jobs: Number of jobs to run at once (passed to make as -j)
166 num_threads: Number of builder threads to run
167 out_queue: Queue of results to process
168 re_make_err: Compiled regular expression for ignore_lines
169 queue: Queue of jobs to run
170 threads: List of active threads
171 toolchains: Toolchains object to use for building
172 upto: Current commit number we are building (0.count-1)
173 warned: Number of builds that produced at least one warning
Simon Glass97e91522014-07-14 17:51:02 -0600174 force_reconfig: Reconfigure U-Boot on each comiit. This disables
175 incremental building, where buildman reconfigures on the first
176 commit for a baord, and then just does an incremental build for
177 the following commits. In fact buildman will reconfigure and
178 retry for any failing commits, so generally the only effect of
179 this option is to slow things down.
Simon Glass189a4962014-07-14 17:51:03 -0600180 in_tree: Build U-Boot in-tree instead of specifying an output
181 directory separate from the source code. This option is really
182 only useful for testing in-tree builds.
Simon Glassd829f122020-03-18 09:42:42 -0600183 work_in_output: Use the output directory as the work directory and
184 don't write to a separate output directory.
Simon Glass8116c782021-04-11 16:27:27 +1200185 thread_exceptions: List of exceptions raised by thread jobs
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000186
187 Private members:
188 _base_board_dict: Last-summarised Dict of boards
189 _base_err_lines: Last-summarised list of errors
Simon Glasse30965d2014-08-28 09:43:44 -0600190 _base_warn_lines: Last-summarised list of warnings
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000191 _build_period_us: Time taken for a single build (float object).
192 _complete_delay: Expected delay until completion (timedelta)
193 _next_delay_update: Next time we plan to display a progress update
194 (datatime)
195 _show_unknown: Show unknown boards (those not built) in summary
Simon Glass7b33f212020-04-09 15:08:47 -0600196 _start_time: Start time for the build
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000197 _timestamps: List of timestamps for the completion of the last
198 last _timestamp_count builds. Each is a datetime object.
199 _timestamp_count: Number of timestamps to keep in our list.
200 _working_dir: Base working directory containing all threads
Simon Glassb82492b2021-01-30 22:17:46 -0700201 _single_builder: BuilderThread object for the singer builder, if
202 threading is not being used
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000203 """
204 class Outcome:
205 """Records a build outcome for a single make invocation
206
207 Public Members:
208 rc: Outcome value (OUTCOME_...)
209 err_lines: List of error lines or [] if none
210 sizes: Dictionary of image size information, keyed by filename
211 - Each value is itself a dictionary containing
212 values for 'text', 'data' and 'bss', being the integer
213 size in bytes of each section.
214 func_sizes: Dictionary keyed by filename - e.g. 'u-boot'. Each
215 value is itself a dictionary:
216 key: function name
217 value: Size of function in bytes
Simon Glass843312d2015-02-05 22:06:15 -0700218 config: Dictionary keyed by filename - e.g. '.config'. Each
219 value is itself a dictionary:
220 key: config name
221 value: config value
Alex Kiernan48ae4122018-05-31 04:48:34 +0000222 environment: Dictionary keyed by environment variable, Each
223 value is the value of environment variable.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000224 """
Alex Kiernan48ae4122018-05-31 04:48:34 +0000225 def __init__(self, rc, err_lines, sizes, func_sizes, config,
226 environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000227 self.rc = rc
228 self.err_lines = err_lines
229 self.sizes = sizes
230 self.func_sizes = func_sizes
Simon Glass843312d2015-02-05 22:06:15 -0700231 self.config = config
Alex Kiernan48ae4122018-05-31 04:48:34 +0000232 self.environment = environment
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000233
234 def __init__(self, toolchains, base_dir, git_dir, num_threads, num_jobs,
Simon Glass5971ab52014-12-01 17:33:55 -0700235 gnu_make='make', checkout=True, show_unknown=True, step=1,
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600236 no_subdirs=False, full_path=False, verbose_build=False,
Simon Glasseb70a2c2020-04-09 15:08:51 -0600237 mrproper=False, per_board_out_dir=False,
Daniel Schwierzeck2371d1b2018-01-26 16:31:05 +0100238 config_only=False, squash_config_y=False,
Simon Glass8116c782021-04-11 16:27:27 +1200239 warnings_as_errors=False, work_in_output=False,
240 test_thread_exceptions=False):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000241 """Create a new Builder object
242
243 Args:
244 toolchains: Toolchains object to use for building
245 base_dir: Base directory to use for builder
246 git_dir: Git directory containing source repository
247 num_threads: Number of builder threads to run
248 num_jobs: Number of jobs to run at once (passed to make as -j)
Masahiro Yamada99796922014-07-22 11:19:09 +0900249 gnu_make: the command name of GNU Make.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000250 checkout: True to check out source, False to skip that step.
251 This is used for testing.
252 show_unknown: Show unknown boards (those not built) in summary
253 step: 1 to process every commit, n to process every nth commit
Simon Glassbb1501f2014-12-01 17:34:00 -0700254 no_subdirs: Don't create subdirectories when building current
255 source for a single board
256 full_path: Return the full path in CROSS_COMPILE and don't set
257 PATH
Simon Glassd2ce6582014-12-01 17:34:07 -0700258 verbose_build: Run build with V=1 and don't use 'make -s'
Simon Glasseb70a2c2020-04-09 15:08:51 -0600259 mrproper: Always run 'make mrproper' when configuring
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600260 per_board_out_dir: Build in a separate persistent directory per
261 board rather than a thread-specific directory
Simon Glassb50113f2016-11-13 14:25:51 -0700262 config_only: Only configure each build, don't build it
Simon Glassb464f8e2016-11-13 14:25:53 -0700263 squash_config_y: Convert CONFIG options with the value 'y' to '1'
Daniel Schwierzeck2371d1b2018-01-26 16:31:05 +0100264 warnings_as_errors: Treat all compiler warnings as errors
Simon Glassd829f122020-03-18 09:42:42 -0600265 work_in_output: Use the output directory as the work directory and
266 don't write to a separate output directory.
Simon Glass8116c782021-04-11 16:27:27 +1200267 test_thread_exceptions: Uses for tests only, True to make the
268 threads raise an exception instead of reporting their result.
269 This simulates a failure in the code somewhere
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000270 """
271 self.toolchains = toolchains
272 self.base_dir = base_dir
Simon Glassd829f122020-03-18 09:42:42 -0600273 if work_in_output:
274 self._working_dir = base_dir
275 else:
276 self._working_dir = os.path.join(base_dir, '.bm-work')
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000277 self.threads = []
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000278 self.do_make = self.Make
Masahiro Yamada99796922014-07-22 11:19:09 +0900279 self.gnu_make = gnu_make
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000280 self.checkout = checkout
281 self.num_threads = num_threads
282 self.num_jobs = num_jobs
283 self.already_done = 0
284 self.force_build = False
285 self.git_dir = git_dir
286 self._show_unknown = show_unknown
287 self._timestamp_count = 10
288 self._build_period_us = None
289 self._complete_delay = None
290 self._next_delay_update = datetime.now()
Simon Glass7b33f212020-04-09 15:08:47 -0600291 self._start_time = datetime.now()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000292 self.force_config_on_failure = True
Simon Glass4266dc22014-07-13 12:22:31 -0600293 self.force_build_failures = False
Simon Glass97e91522014-07-14 17:51:02 -0600294 self.force_reconfig = False
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000295 self._step = step
Simon Glass189a4962014-07-14 17:51:03 -0600296 self.in_tree = False
Simon Glass28370c12014-08-09 15:33:06 -0600297 self._error_lines = 0
Simon Glass5971ab52014-12-01 17:33:55 -0700298 self.no_subdirs = no_subdirs
Simon Glassbb1501f2014-12-01 17:34:00 -0700299 self.full_path = full_path
Simon Glassd2ce6582014-12-01 17:34:07 -0700300 self.verbose_build = verbose_build
Simon Glassb50113f2016-11-13 14:25:51 -0700301 self.config_only = config_only
Simon Glassb464f8e2016-11-13 14:25:53 -0700302 self.squash_config_y = squash_config_y
303 self.config_filenames = BASE_CONFIG_FILENAMES
Simon Glassd829f122020-03-18 09:42:42 -0600304 self.work_in_output = work_in_output
Simon Glassb464f8e2016-11-13 14:25:53 -0700305 if not self.squash_config_y:
306 self.config_filenames += EXTRA_CONFIG_FILENAMES
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000307
Daniel Schwierzeck2371d1b2018-01-26 16:31:05 +0100308 self.warnings_as_errors = warnings_as_errors
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000309 self.col = terminal.Color()
310
Simon Glasse30965d2014-08-28 09:43:44 -0600311 self._re_function = re.compile('(.*): In function.*')
312 self._re_files = re.compile('In file included from.*')
313 self._re_warning = re.compile('(.*):(\d*):(\d*): warning: .*')
Simon Glass2d483332018-11-06 16:02:11 -0700314 self._re_dtb_warning = re.compile('(.*): Warning .*')
Simon Glasse30965d2014-08-28 09:43:44 -0600315 self._re_note = re.compile('(.*):(\d*):(\d*): note: this is the location of the previous.*')
Simon Glass113a8a52020-04-09 15:08:53 -0600316 self._re_migration_warning = re.compile(r'^={21} WARNING ={22}\n.*\n=+\n',
317 re.MULTILINE | re.DOTALL)
Simon Glasse30965d2014-08-28 09:43:44 -0600318
Simon Glass8116c782021-04-11 16:27:27 +1200319 self.thread_exceptions = []
320 self.test_thread_exceptions = test_thread_exceptions
Simon Glassb82492b2021-01-30 22:17:46 -0700321 if self.num_threads:
322 self._single_builder = None
323 self.queue = queue.Queue()
324 self.out_queue = queue.Queue()
325 for i in range(self.num_threads):
Simon Glass8116c782021-04-11 16:27:27 +1200326 t = builderthread.BuilderThread(
327 self, i, mrproper, per_board_out_dir,
328 test_exception=test_thread_exceptions)
Simon Glassb82492b2021-01-30 22:17:46 -0700329 t.setDaemon(True)
330 t.start()
331 self.threads.append(t)
332
333 t = builderthread.ResultThread(self)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000334 t.setDaemon(True)
335 t.start()
336 self.threads.append(t)
Simon Glassb82492b2021-01-30 22:17:46 -0700337 else:
338 self._single_builder = builderthread.BuilderThread(
339 self, -1, mrproper, per_board_out_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000340
341 ignore_lines = ['(make.*Waiting for unfinished)', '(Segmentation fault)']
342 self.re_make_err = re.compile('|'.join(ignore_lines))
343
Simon Glass2f256642016-09-18 16:48:37 -0600344 # Handle existing graceful with SIGINT / Ctrl-C
345 signal.signal(signal.SIGINT, self.signal_handler)
346
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000347 def __del__(self):
348 """Get rid of all threads created by the builder"""
349 for t in self.threads:
350 del t
351
Simon Glass2f256642016-09-18 16:48:37 -0600352 def signal_handler(self, signal, frame):
353 sys.exit(1)
354
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600355 def SetDisplayOptions(self, show_errors=False, show_sizes=False,
Simon Glassed966652014-08-28 09:43:43 -0600356 show_detail=False, show_bloat=False,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000357 list_error_boards=False, show_config=False,
Simon Glass113a8a52020-04-09 15:08:53 -0600358 show_environment=False, filter_dtb_warnings=False,
359 filter_migration_warnings=False):
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600360 """Setup display options for the builder.
361
Simon Glass174592b2020-04-09 15:08:52 -0600362 Args:
363 show_errors: True to show summarised error/warning info
364 show_sizes: Show size deltas
365 show_detail: Show size delta detail for each board if show_sizes
366 show_bloat: Show detail for each function
367 list_error_boards: Show the boards which caused each error/warning
368 show_config: Show config deltas
369 show_environment: Show environment deltas
370 filter_dtb_warnings: Filter out any warnings from the device-tree
371 compiler
Simon Glass113a8a52020-04-09 15:08:53 -0600372 filter_migration_warnings: Filter out any warnings about migrating
373 a board to driver model
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600374 """
375 self._show_errors = show_errors
376 self._show_sizes = show_sizes
377 self._show_detail = show_detail
378 self._show_bloat = show_bloat
Simon Glassed966652014-08-28 09:43:43 -0600379 self._list_error_boards = list_error_boards
Simon Glass843312d2015-02-05 22:06:15 -0700380 self._show_config = show_config
Alex Kiernan48ae4122018-05-31 04:48:34 +0000381 self._show_environment = show_environment
Simon Glass174592b2020-04-09 15:08:52 -0600382 self._filter_dtb_warnings = filter_dtb_warnings
Simon Glass113a8a52020-04-09 15:08:53 -0600383 self._filter_migration_warnings = filter_migration_warnings
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600384
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000385 def _AddTimestamp(self):
386 """Add a new timestamp to the list and record the build period.
387
388 The build period is the length of time taken to perform a single
389 build (one board, one commit).
390 """
391 now = datetime.now()
392 self._timestamps.append(now)
393 count = len(self._timestamps)
394 delta = self._timestamps[-1] - self._timestamps[0]
395 seconds = delta.total_seconds()
396
397 # If we have enough data, estimate build period (time taken for a
398 # single build) and therefore completion time.
399 if count > 1 and self._next_delay_update < now:
400 self._next_delay_update = now + timedelta(seconds=2)
401 if seconds > 0:
402 self._build_period = float(seconds) / count
403 todo = self.count - self.upto
404 self._complete_delay = timedelta(microseconds=
405 self._build_period * todo * 1000000)
406 # Round it
407 self._complete_delay -= timedelta(
408 microseconds=self._complete_delay.microseconds)
409
410 if seconds > 60:
411 self._timestamps.popleft()
412 count -= 1
413
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000414 def SelectCommit(self, commit, checkout=True):
415 """Checkout the selected commit for this build
416 """
417 self.commit = commit
418 if checkout and self.checkout:
419 gitutil.Checkout(commit.hash)
420
421 def Make(self, commit, brd, stage, cwd, *args, **kwargs):
422 """Run make
423
424 Args:
425 commit: Commit object that is being built
426 brd: Board object that is being built
Roger Meierfd18a892014-08-20 22:10:29 +0200427 stage: Stage that we are at (mrproper, config, build)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000428 cwd: Directory where make should be run
429 args: Arguments to pass to make
430 kwargs: Arguments to pass to command.RunPipe()
431 """
Masahiro Yamada99796922014-07-22 11:19:09 +0900432 cmd = [self.gnu_make] + list(args)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000433 result = command.RunPipe([cmd], capture=True, capture_stderr=True,
Simon Glasse62a24c2018-09-17 23:55:42 -0600434 cwd=cwd, raise_on_error=False, infile='/dev/null', **kwargs)
Simon Glass40f11fc2015-02-05 22:06:12 -0700435 if self.verbose_build:
436 result.stdout = '%s\n' % (' '.join(cmd)) + result.stdout
437 result.combined = '%s\n' % (' '.join(cmd)) + result.combined
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000438 return result
439
440 def ProcessResult(self, result):
441 """Process the result of a build, showing progress information
442
443 Args:
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600444 result: A CommandResult object, which indicates the result for
445 a single build
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000446 """
447 col = terminal.Color()
448 if result:
449 target = result.brd.target
450
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000451 self.upto += 1
452 if result.return_code != 0:
453 self.fail += 1
454 elif result.stderr:
455 self.warned += 1
456 if result.already_done:
457 self.already_done += 1
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600458 if self._verbose:
Simon Glass102969b2020-04-09 15:08:42 -0600459 terminal.PrintClear()
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600460 boards_selected = {target : result.brd}
461 self.ResetResultSummary(boards_selected)
462 self.ProduceResultSummary(result.commit_upto, self.commits,
463 boards_selected)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000464 else:
465 target = '(starting)'
466
467 # Display separate counts for ok, warned and fail
468 ok = self.upto - self.warned - self.fail
469 line = '\r' + self.col.Color(self.col.GREEN, '%5d' % ok)
470 line += self.col.Color(self.col.YELLOW, '%5d' % self.warned)
471 line += self.col.Color(self.col.RED, '%5d' % self.fail)
472
Simon Glass6eb76ca2020-04-09 15:08:45 -0600473 line += ' /%-5d ' % self.count
474 remaining = self.count - self.upto
475 if remaining:
476 line += self.col.Color(self.col.MAGENTA, ' -%-5d ' % remaining)
477 else:
478 line += ' ' * 8
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000479
480 # Add our current completion time estimate
481 self._AddTimestamp()
482 if self._complete_delay:
Simon Glass6eb76ca2020-04-09 15:08:45 -0600483 line += '%s : ' % self._complete_delay
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000484
Simon Glass6eb76ca2020-04-09 15:08:45 -0600485 line += target
Simon Glass102969b2020-04-09 15:08:42 -0600486 terminal.PrintClear()
Simon Glass95ed0a22020-04-09 15:08:46 -0600487 Print(line, newline=False, limit_to_line=True)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000488
489 def _GetOutputDir(self, commit_upto):
490 """Get the name of the output directory for a commit number
491
492 The output directory is typically .../<branch>/<commit>.
493
494 Args:
495 commit_upto: Commit number to use (0..self.count-1)
496 """
Simon Glass60b285f2020-04-17 17:51:34 -0600497 if self.work_in_output:
498 return self._working_dir
499
Simon Glass5971ab52014-12-01 17:33:55 -0700500 commit_dir = None
Simon Glassfea58582014-08-09 15:32:59 -0600501 if self.commits:
502 commit = self.commits[commit_upto]
503 subject = commit.subject.translate(trans_valid_chars)
Simon Glass925f6ad2020-03-18 09:42:45 -0600504 # See _GetOutputSpaceRemovals() which parses this name
Ovidiu Panait7664b032020-05-15 09:30:12 +0300505 commit_dir = ('%02d_g%s_%s' % (commit_upto + 1,
506 commit.hash, subject[:20]))
Simon Glass5971ab52014-12-01 17:33:55 -0700507 elif not self.no_subdirs:
Simon Glassfea58582014-08-09 15:32:59 -0600508 commit_dir = 'current'
Simon Glass5971ab52014-12-01 17:33:55 -0700509 if not commit_dir:
510 return self.base_dir
511 return os.path.join(self.base_dir, commit_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000512
513 def GetBuildDir(self, commit_upto, target):
514 """Get the name of the build directory for a commit number
515
516 The build directory is typically .../<branch>/<commit>/<target>.
517
518 Args:
519 commit_upto: Commit number to use (0..self.count-1)
520 target: Target name
521 """
522 output_dir = self._GetOutputDir(commit_upto)
Simon Glass60b285f2020-04-17 17:51:34 -0600523 if self.work_in_output:
524 return output_dir
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000525 return os.path.join(output_dir, target)
526
527 def GetDoneFile(self, commit_upto, target):
528 """Get the name of the done file for a commit number
529
530 Args:
531 commit_upto: Commit number to use (0..self.count-1)
532 target: Target name
533 """
534 return os.path.join(self.GetBuildDir(commit_upto, target), 'done')
535
536 def GetSizesFile(self, commit_upto, target):
537 """Get the name of the sizes file for a commit number
538
539 Args:
540 commit_upto: Commit number to use (0..self.count-1)
541 target: Target name
542 """
543 return os.path.join(self.GetBuildDir(commit_upto, target), 'sizes')
544
545 def GetFuncSizesFile(self, commit_upto, target, elf_fname):
546 """Get the name of the funcsizes file for a commit number and ELF file
547
548 Args:
549 commit_upto: Commit number to use (0..self.count-1)
550 target: Target name
551 elf_fname: Filename of elf image
552 """
553 return os.path.join(self.GetBuildDir(commit_upto, target),
554 '%s.sizes' % elf_fname.replace('/', '-'))
555
556 def GetObjdumpFile(self, commit_upto, target, elf_fname):
557 """Get the name of the objdump file for a commit number and ELF file
558
559 Args:
560 commit_upto: Commit number to use (0..self.count-1)
561 target: Target name
562 elf_fname: Filename of elf image
563 """
564 return os.path.join(self.GetBuildDir(commit_upto, target),
565 '%s.objdump' % elf_fname.replace('/', '-'))
566
567 def GetErrFile(self, commit_upto, target):
568 """Get the name of the err file for a commit number
569
570 Args:
571 commit_upto: Commit number to use (0..self.count-1)
572 target: Target name
573 """
574 output_dir = self.GetBuildDir(commit_upto, target)
575 return os.path.join(output_dir, 'err')
576
577 def FilterErrors(self, lines):
578 """Filter out errors in which we have no interest
579
580 We should probably use map().
581
582 Args:
583 lines: List of error lines, each a string
584 Returns:
585 New list with only interesting lines included
586 """
587 out_lines = []
Simon Glass113a8a52020-04-09 15:08:53 -0600588 if self._filter_migration_warnings:
589 text = '\n'.join(lines)
590 text = self._re_migration_warning.sub('', text)
591 lines = text.splitlines()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000592 for line in lines:
Simon Glass174592b2020-04-09 15:08:52 -0600593 if self.re_make_err.search(line):
594 continue
595 if self._filter_dtb_warnings and self._re_dtb_warning.search(line):
596 continue
597 out_lines.append(line)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000598 return out_lines
599
600 def ReadFuncSizes(self, fname, fd):
601 """Read function sizes from the output of 'nm'
602
603 Args:
604 fd: File containing data to read
605 fname: Filename we are reading from (just for errors)
606
607 Returns:
608 Dictionary containing size of each function in bytes, indexed by
609 function name.
610 """
611 sym = {}
612 for line in fd.readlines():
613 try:
Tom Rinid08c38c2019-12-06 15:31:31 -0500614 if line.strip():
615 size, type, name = line[:-1].split()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000616 except:
Simon Glass4653a882014-09-05 19:00:07 -0600617 Print("Invalid line in file '%s': '%s'" % (fname, line[:-1]))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000618 continue
619 if type in 'tTdDbB':
620 # function names begin with '.' on 64-bit powerpc
621 if '.' in name[1:]:
622 name = 'static.' + name.split('.')[0]
623 sym[name] = sym.get(name, 0) + int(size, 16)
624 return sym
625
Simon Glass843312d2015-02-05 22:06:15 -0700626 def _ProcessConfig(self, fname):
627 """Read in a .config, autoconf.mk or autoconf.h file
628
629 This function handles all config file types. It ignores comments and
630 any #defines which don't start with CONFIG_.
631
632 Args:
633 fname: Filename to read
634
635 Returns:
636 Dictionary:
637 key: Config name (e.g. CONFIG_DM)
638 value: Config value (e.g. 1)
639 """
640 config = {}
641 if os.path.exists(fname):
642 with open(fname) as fd:
643 for line in fd:
644 line = line.strip()
645 if line.startswith('#define'):
646 values = line[8:].split(' ', 1)
647 if len(values) > 1:
648 key, value = values
649 else:
650 key = values[0]
Simon Glassb464f8e2016-11-13 14:25:53 -0700651 value = '1' if self.squash_config_y else ''
Simon Glass843312d2015-02-05 22:06:15 -0700652 if not key.startswith('CONFIG_'):
653 continue
654 elif not line or line[0] in ['#', '*', '/']:
655 continue
656 else:
657 key, value = line.split('=', 1)
Simon Glassb464f8e2016-11-13 14:25:53 -0700658 if self.squash_config_y and value == 'y':
659 value = '1'
Simon Glass843312d2015-02-05 22:06:15 -0700660 config[key] = value
661 return config
662
Alex Kiernan48ae4122018-05-31 04:48:34 +0000663 def _ProcessEnvironment(self, fname):
664 """Read in a uboot.env file
665
666 This function reads in environment variables from a file.
667
668 Args:
669 fname: Filename to read
670
671 Returns:
672 Dictionary:
673 key: environment variable (e.g. bootlimit)
674 value: value of environment variable (e.g. 1)
675 """
676 environment = {}
677 if os.path.exists(fname):
678 with open(fname) as fd:
679 for line in fd.read().split('\0'):
680 try:
681 key, value = line.split('=', 1)
682 environment[key] = value
683 except ValueError:
684 # ignore lines we can't parse
685 pass
686 return environment
687
Simon Glass843312d2015-02-05 22:06:15 -0700688 def GetBuildOutcome(self, commit_upto, target, read_func_sizes,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000689 read_config, read_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000690 """Work out the outcome of a build.
691
692 Args:
693 commit_upto: Commit number to check (0..n-1)
694 target: Target board to check
695 read_func_sizes: True to read function size information
Simon Glass843312d2015-02-05 22:06:15 -0700696 read_config: True to read .config and autoconf.h files
Alex Kiernan48ae4122018-05-31 04:48:34 +0000697 read_environment: True to read uboot.env files
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000698
699 Returns:
700 Outcome object
701 """
702 done_file = self.GetDoneFile(commit_upto, target)
703 sizes_file = self.GetSizesFile(commit_upto, target)
704 sizes = {}
705 func_sizes = {}
Simon Glass843312d2015-02-05 22:06:15 -0700706 config = {}
Alex Kiernan48ae4122018-05-31 04:48:34 +0000707 environment = {}
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000708 if os.path.exists(done_file):
709 with open(done_file, 'r') as fd:
Simon Glass347ea0b2019-04-26 19:02:23 -0600710 try:
711 return_code = int(fd.readline())
712 except ValueError:
713 # The file may be empty due to running out of disk space.
714 # Try a rebuild
715 return_code = 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000716 err_lines = []
717 err_file = self.GetErrFile(commit_upto, target)
718 if os.path.exists(err_file):
719 with open(err_file, 'r') as fd:
720 err_lines = self.FilterErrors(fd.readlines())
721
722 # Decide whether the build was ok, failed or created warnings
723 if return_code:
724 rc = OUTCOME_ERROR
725 elif len(err_lines):
726 rc = OUTCOME_WARNING
727 else:
728 rc = OUTCOME_OK
729
730 # Convert size information to our simple format
731 if os.path.exists(sizes_file):
732 with open(sizes_file, 'r') as fd:
733 for line in fd.readlines():
734 values = line.split()
735 rodata = 0
736 if len(values) > 6:
737 rodata = int(values[6], 16)
738 size_dict = {
739 'all' : int(values[0]) + int(values[1]) +
740 int(values[2]),
741 'text' : int(values[0]) - rodata,
742 'data' : int(values[1]),
743 'bss' : int(values[2]),
744 'rodata' : rodata,
745 }
746 sizes[values[5]] = size_dict
747
748 if read_func_sizes:
749 pattern = self.GetFuncSizesFile(commit_upto, target, '*')
750 for fname in glob.glob(pattern):
751 with open(fname, 'r') as fd:
752 dict_name = os.path.basename(fname).replace('.sizes',
753 '')
754 func_sizes[dict_name] = self.ReadFuncSizes(fname, fd)
755
Simon Glass843312d2015-02-05 22:06:15 -0700756 if read_config:
757 output_dir = self.GetBuildDir(commit_upto, target)
Simon Glassb464f8e2016-11-13 14:25:53 -0700758 for name in self.config_filenames:
Simon Glass843312d2015-02-05 22:06:15 -0700759 fname = os.path.join(output_dir, name)
760 config[name] = self._ProcessConfig(fname)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000761
Alex Kiernan48ae4122018-05-31 04:48:34 +0000762 if read_environment:
763 output_dir = self.GetBuildDir(commit_upto, target)
764 fname = os.path.join(output_dir, 'uboot.env')
765 environment = self._ProcessEnvironment(fname)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000766
Alex Kiernan48ae4122018-05-31 04:48:34 +0000767 return Builder.Outcome(rc, err_lines, sizes, func_sizes, config,
768 environment)
769
770 return Builder.Outcome(OUTCOME_UNKNOWN, [], {}, {}, {}, {})
Simon Glass843312d2015-02-05 22:06:15 -0700771
772 def GetResultSummary(self, boards_selected, commit_upto, read_func_sizes,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000773 read_config, read_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000774 """Calculate a summary of the results of building a commit.
775
776 Args:
777 board_selected: Dict containing boards to summarise
778 commit_upto: Commit number to summarize (0..self.count-1)
779 read_func_sizes: True to read function size information
Simon Glass843312d2015-02-05 22:06:15 -0700780 read_config: True to read .config and autoconf.h files
Alex Kiernan48ae4122018-05-31 04:48:34 +0000781 read_environment: True to read uboot.env files
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000782
783 Returns:
784 Tuple:
785 Dict containing boards which passed building this commit.
786 keyed by board.target
Simon Glasse30965d2014-08-28 09:43:44 -0600787 List containing a summary of error lines
Simon Glassed966652014-08-28 09:43:43 -0600788 Dict keyed by error line, containing a list of the Board
789 objects with that error
Simon Glasse30965d2014-08-28 09:43:44 -0600790 List containing a summary of warning lines
791 Dict keyed by error line, containing a list of the Board
792 objects with that warning
Simon Glass8270e3c2015-08-25 21:52:14 -0600793 Dictionary keyed by board.target. Each value is a dictionary:
794 key: filename - e.g. '.config'
Simon Glass843312d2015-02-05 22:06:15 -0700795 value is itself a dictionary:
796 key: config name
797 value: config value
Alex Kiernan48ae4122018-05-31 04:48:34 +0000798 Dictionary keyed by board.target. Each value is a dictionary:
799 key: environment variable
800 value: value of environment variable
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000801 """
Simon Glasse30965d2014-08-28 09:43:44 -0600802 def AddLine(lines_summary, lines_boards, line, board):
803 line = line.rstrip()
804 if line in lines_boards:
805 lines_boards[line].append(board)
806 else:
807 lines_boards[line] = [board]
808 lines_summary.append(line)
809
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000810 board_dict = {}
811 err_lines_summary = []
Simon Glassed966652014-08-28 09:43:43 -0600812 err_lines_boards = {}
Simon Glasse30965d2014-08-28 09:43:44 -0600813 warn_lines_summary = []
814 warn_lines_boards = {}
Simon Glass843312d2015-02-05 22:06:15 -0700815 config = {}
Alex Kiernan48ae4122018-05-31 04:48:34 +0000816 environment = {}
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000817
Simon Glassc05aa032019-10-31 07:42:53 -0600818 for board in boards_selected.values():
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000819 outcome = self.GetBuildOutcome(commit_upto, board.target,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000820 read_func_sizes, read_config,
821 read_environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000822 board_dict[board.target] = outcome
Simon Glasse30965d2014-08-28 09:43:44 -0600823 last_func = None
824 last_was_warning = False
825 for line in outcome.err_lines:
826 if line:
827 if (self._re_function.match(line) or
828 self._re_files.match(line)):
829 last_func = line
Simon Glassed966652014-08-28 09:43:43 -0600830 else:
Simon Glass2d483332018-11-06 16:02:11 -0700831 is_warning = (self._re_warning.match(line) or
832 self._re_dtb_warning.match(line))
Simon Glasse30965d2014-08-28 09:43:44 -0600833 is_note = self._re_note.match(line)
834 if is_warning or (last_was_warning and is_note):
835 if last_func:
836 AddLine(warn_lines_summary, warn_lines_boards,
837 last_func, board)
838 AddLine(warn_lines_summary, warn_lines_boards,
839 line, board)
840 else:
841 if last_func:
842 AddLine(err_lines_summary, err_lines_boards,
843 last_func, board)
844 AddLine(err_lines_summary, err_lines_boards,
845 line, board)
846 last_was_warning = is_warning
847 last_func = None
Simon Glassb464f8e2016-11-13 14:25:53 -0700848 tconfig = Config(self.config_filenames, board.target)
849 for fname in self.config_filenames:
Simon Glass843312d2015-02-05 22:06:15 -0700850 if outcome.config:
Simon Glassc05aa032019-10-31 07:42:53 -0600851 for key, value in outcome.config[fname].items():
Simon Glass8270e3c2015-08-25 21:52:14 -0600852 tconfig.Add(fname, key, value)
853 config[board.target] = tconfig
Simon Glass843312d2015-02-05 22:06:15 -0700854
Alex Kiernan48ae4122018-05-31 04:48:34 +0000855 tenvironment = Environment(board.target)
856 if outcome.environment:
Simon Glassc05aa032019-10-31 07:42:53 -0600857 for key, value in outcome.environment.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +0000858 tenvironment.Add(key, value)
859 environment[board.target] = tenvironment
860
Simon Glasse30965d2014-08-28 09:43:44 -0600861 return (board_dict, err_lines_summary, err_lines_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000862 warn_lines_summary, warn_lines_boards, config, environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000863
864 def AddOutcome(self, board_dict, arch_list, changes, char, color):
865 """Add an output to our list of outcomes for each architecture
866
867 This simple function adds failing boards (changes) to the
868 relevant architecture string, so we can print the results out
869 sorted by architecture.
870
871 Args:
872 board_dict: Dict containing all boards
873 arch_list: Dict keyed by arch name. Value is a string containing
874 a list of board names which failed for that arch.
875 changes: List of boards to add to arch_list
876 color: terminal.Colour object
877 """
878 done_arch = {}
879 for target in changes:
880 if target in board_dict:
881 arch = board_dict[target].arch
882 else:
883 arch = 'unknown'
884 str = self.col.Color(color, ' ' + target)
885 if not arch in done_arch:
Simon Glass63c619e2015-02-05 22:06:11 -0700886 str = ' %s %s' % (self.col.Color(color, char), str)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000887 done_arch[arch] = True
888 if not arch in arch_list:
889 arch_list[arch] = str
890 else:
891 arch_list[arch] += str
892
893
894 def ColourNum(self, num):
895 color = self.col.RED if num > 0 else self.col.GREEN
896 if num == 0:
897 return '0'
898 return self.col.Color(color, str(num))
899
900 def ResetResultSummary(self, board_selected):
901 """Reset the results summary ready for use.
902
903 Set up the base board list to be all those selected, and set the
904 error lines to empty.
905
906 Following this, calls to PrintResultSummary() will use this
907 information to work out what has changed.
908
909 Args:
910 board_selected: Dict containing boards to summarise, keyed by
911 board.target
912 """
913 self._base_board_dict = {}
914 for board in board_selected:
Alex Kiernan48ae4122018-05-31 04:48:34 +0000915 self._base_board_dict[board] = Builder.Outcome(0, [], [], {}, {},
916 {})
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000917 self._base_err_lines = []
Simon Glasse30965d2014-08-28 09:43:44 -0600918 self._base_warn_lines = []
919 self._base_err_line_boards = {}
920 self._base_warn_line_boards = {}
Simon Glass8270e3c2015-08-25 21:52:14 -0600921 self._base_config = None
Alex Kiernan48ae4122018-05-31 04:48:34 +0000922 self._base_environment = None
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000923
924 def PrintFuncSizeDetail(self, fname, old, new):
925 grow, shrink, add, remove, up, down = 0, 0, 0, 0, 0, 0
926 delta, common = [], {}
927
928 for a in old:
929 if a in new:
930 common[a] = 1
931
932 for name in old:
933 if name not in common:
934 remove += 1
935 down += old[name]
936 delta.append([-old[name], name])
937
938 for name in new:
939 if name not in common:
940 add += 1
941 up += new[name]
942 delta.append([new[name], name])
943
944 for name in common:
945 diff = new.get(name, 0) - old.get(name, 0)
946 if diff > 0:
947 grow, up = grow + 1, up + diff
948 elif diff < 0:
949 shrink, down = shrink + 1, down - diff
950 delta.append([diff, name])
951
952 delta.sort()
953 delta.reverse()
954
955 args = [add, -remove, grow, -shrink, up, -down, up - down]
Tom Rinid5686a62017-05-22 13:48:52 -0400956 if max(args) == 0 and min(args) == 0:
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000957 return
958 args = [self.ColourNum(x) for x in args]
959 indent = ' ' * 15
Simon Glass4653a882014-09-05 19:00:07 -0600960 Print('%s%s: add: %s/%s, grow: %s/%s bytes: %s/%s (%s)' %
961 tuple([indent, self.col.Color(self.col.YELLOW, fname)] + args))
962 Print('%s %-38s %7s %7s %+7s' % (indent, 'function', 'old', 'new',
963 'delta'))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000964 for diff, name in delta:
965 if diff:
966 color = self.col.RED if diff > 0 else self.col.GREEN
967 msg = '%s %-38s %7s %7s %+7d' % (indent, name,
968 old.get(name, '-'), new.get(name,'-'), diff)
Simon Glass4653a882014-09-05 19:00:07 -0600969 Print(msg, colour=color)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000970
971
972 def PrintSizeDetail(self, target_list, show_bloat):
973 """Show details size information for each board
974
975 Args:
976 target_list: List of targets, each a dict containing:
977 'target': Target name
978 'total_diff': Total difference in bytes across all areas
979 <part_name>: Difference for that part
980 show_bloat: Show detail for each function
981 """
982 targets_by_diff = sorted(target_list, reverse=True,
983 key=lambda x: x['_total_diff'])
984 for result in targets_by_diff:
985 printed_target = False
986 for name in sorted(result):
987 diff = result[name]
988 if name.startswith('_'):
989 continue
990 if diff != 0:
991 color = self.col.RED if diff > 0 else self.col.GREEN
992 msg = ' %s %+d' % (name, diff)
993 if not printed_target:
Simon Glass4653a882014-09-05 19:00:07 -0600994 Print('%10s %-15s:' % ('', result['_target']),
995 newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000996 printed_target = True
Simon Glass4653a882014-09-05 19:00:07 -0600997 Print(msg, colour=color, newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000998 if printed_target:
Simon Glass4653a882014-09-05 19:00:07 -0600999 Print()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001000 if show_bloat:
1001 target = result['_target']
1002 outcome = result['_outcome']
1003 base_outcome = self._base_board_dict[target]
1004 for fname in outcome.func_sizes:
1005 self.PrintFuncSizeDetail(fname,
1006 base_outcome.func_sizes[fname],
1007 outcome.func_sizes[fname])
1008
1009
1010 def PrintSizeSummary(self, board_selected, board_dict, show_detail,
1011 show_bloat):
1012 """Print a summary of image sizes broken down by section.
1013
1014 The summary takes the form of one line per architecture. The
1015 line contains deltas for each of the sections (+ means the section
Flavio Suligoi9de5c392020-01-29 09:56:05 +01001016 got bigger, - means smaller). The numbers are the average number
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001017 of bytes that a board in this section increased by.
1018
1019 For example:
1020 powerpc: (622 boards) text -0.0
1021 arm: (285 boards) text -0.0
1022 nds32: (3 boards) text -8.0
1023
1024 Args:
1025 board_selected: Dict containing boards to summarise, keyed by
1026 board.target
1027 board_dict: Dict containing boards for which we built this
1028 commit, keyed by board.target. The value is an Outcome object.
Simon Glassf9c094b2020-03-18 09:42:43 -06001029 show_detail: Show size delta detail for each board
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001030 show_bloat: Show detail for each function
1031 """
1032 arch_list = {}
1033 arch_count = {}
1034
1035 # Calculate changes in size for different image parts
1036 # The previous sizes are in Board.sizes, for each board
1037 for target in board_dict:
1038 if target not in board_selected:
1039 continue
1040 base_sizes = self._base_board_dict[target].sizes
1041 outcome = board_dict[target]
1042 sizes = outcome.sizes
1043
1044 # Loop through the list of images, creating a dict of size
1045 # changes for each image/part. We end up with something like
1046 # {'target' : 'snapper9g45, 'data' : 5, 'u-boot-spl:text' : -4}
1047 # which means that U-Boot data increased by 5 bytes and SPL
1048 # text decreased by 4.
1049 err = {'_target' : target}
1050 for image in sizes:
1051 if image in base_sizes:
1052 base_image = base_sizes[image]
1053 # Loop through the text, data, bss parts
1054 for part in sorted(sizes[image]):
1055 diff = sizes[image][part] - base_image[part]
1056 col = None
1057 if diff:
1058 if image == 'u-boot':
1059 name = part
1060 else:
1061 name = image + ':' + part
1062 err[name] = diff
1063 arch = board_selected[target].arch
1064 if not arch in arch_count:
1065 arch_count[arch] = 1
1066 else:
1067 arch_count[arch] += 1
1068 if not sizes:
1069 pass # Only add to our list when we have some stats
1070 elif not arch in arch_list:
1071 arch_list[arch] = [err]
1072 else:
1073 arch_list[arch].append(err)
1074
1075 # We now have a list of image size changes sorted by arch
1076 # Print out a summary of these
Simon Glassc05aa032019-10-31 07:42:53 -06001077 for arch, target_list in arch_list.items():
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001078 # Get total difference for each type
1079 totals = {}
1080 for result in target_list:
1081 total = 0
Simon Glassc05aa032019-10-31 07:42:53 -06001082 for name, diff in result.items():
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001083 if name.startswith('_'):
1084 continue
1085 total += diff
1086 if name in totals:
1087 totals[name] += diff
1088 else:
1089 totals[name] = diff
1090 result['_total_diff'] = total
1091 result['_outcome'] = board_dict[result['_target']]
1092
1093 count = len(target_list)
1094 printed_arch = False
1095 for name in sorted(totals):
1096 diff = totals[name]
1097 if diff:
1098 # Display the average difference in this name for this
1099 # architecture
1100 avg_diff = float(diff) / count
1101 color = self.col.RED if avg_diff > 0 else self.col.GREEN
1102 msg = ' %s %+1.1f' % (name, avg_diff)
1103 if not printed_arch:
Simon Glass4653a882014-09-05 19:00:07 -06001104 Print('%10s: (for %d/%d boards)' % (arch, count,
1105 arch_count[arch]), newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001106 printed_arch = True
Simon Glass4653a882014-09-05 19:00:07 -06001107 Print(msg, colour=color, newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001108
1109 if printed_arch:
Simon Glass4653a882014-09-05 19:00:07 -06001110 Print()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001111 if show_detail:
1112 self.PrintSizeDetail(target_list, show_bloat)
1113
1114
1115 def PrintResultSummary(self, board_selected, board_dict, err_lines,
Simon Glasse30965d2014-08-28 09:43:44 -06001116 err_line_boards, warn_lines, warn_line_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001117 config, environment, show_sizes, show_detail,
1118 show_bloat, show_config, show_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001119 """Compare results with the base results and display delta.
1120
1121 Only boards mentioned in board_selected will be considered. This
1122 function is intended to be called repeatedly with the results of
1123 each commit. It therefore shows a 'diff' between what it saw in
1124 the last call and what it sees now.
1125
1126 Args:
1127 board_selected: Dict containing boards to summarise, keyed by
1128 board.target
1129 board_dict: Dict containing boards for which we built this
1130 commit, keyed by board.target. The value is an Outcome object.
1131 err_lines: A list of errors for this commit, or [] if there is
1132 none, or we don't want to print errors
Simon Glassed966652014-08-28 09:43:43 -06001133 err_line_boards: Dict keyed by error line, containing a list of
1134 the Board objects with that error
Simon Glasse30965d2014-08-28 09:43:44 -06001135 warn_lines: A list of warnings for this commit, or [] if there is
1136 none, or we don't want to print errors
1137 warn_line_boards: Dict keyed by warning line, containing a list of
1138 the Board objects with that warning
Simon Glass843312d2015-02-05 22:06:15 -07001139 config: Dictionary keyed by filename - e.g. '.config'. Each
1140 value is itself a dictionary:
1141 key: config name
1142 value: config value
Alex Kiernan48ae4122018-05-31 04:48:34 +00001143 environment: Dictionary keyed by environment variable, Each
1144 value is the value of environment variable.
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001145 show_sizes: Show image size deltas
Simon Glassf9c094b2020-03-18 09:42:43 -06001146 show_detail: Show size delta detail for each board if show_sizes
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001147 show_bloat: Show detail for each function
Simon Glass843312d2015-02-05 22:06:15 -07001148 show_config: Show config changes
Alex Kiernan48ae4122018-05-31 04:48:34 +00001149 show_environment: Show environment changes
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001150 """
Simon Glasse30965d2014-08-28 09:43:44 -06001151 def _BoardList(line, line_boards):
Simon Glassed966652014-08-28 09:43:43 -06001152 """Helper function to get a line of boards containing a line
1153
1154 Args:
1155 line: Error line to search for
Simon Glass35d696d2020-04-09 15:08:36 -06001156 line_boards: boards to search, each a Board
Simon Glassed966652014-08-28 09:43:43 -06001157 Return:
Simon Glass35d696d2020-04-09 15:08:36 -06001158 List of boards with that error line, or [] if the user has not
1159 requested such a list
Simon Glassed966652014-08-28 09:43:43 -06001160 """
Simon Glass35d696d2020-04-09 15:08:36 -06001161 boards = []
1162 board_set = set()
Simon Glassed966652014-08-28 09:43:43 -06001163 if self._list_error_boards:
Simon Glasse30965d2014-08-28 09:43:44 -06001164 for board in line_boards[line]:
Simon Glass35d696d2020-04-09 15:08:36 -06001165 if not board in board_set:
1166 boards.append(board)
1167 board_set.add(board)
1168 return boards
Simon Glassed966652014-08-28 09:43:43 -06001169
Simon Glasse30965d2014-08-28 09:43:44 -06001170 def _CalcErrorDelta(base_lines, base_line_boards, lines, line_boards,
1171 char):
Simon Glass35d696d2020-04-09 15:08:36 -06001172 """Calculate the required output based on changes in errors
1173
1174 Args:
1175 base_lines: List of errors/warnings for previous commit
1176 base_line_boards: Dict keyed by error line, containing a list
1177 of the Board objects with that error in the previous commit
1178 lines: List of errors/warning for this commit, each a str
1179 line_boards: Dict keyed by error line, containing a list
1180 of the Board objects with that error in this commit
1181 char: Character representing error ('') or warning ('w'). The
1182 broken ('+') or fixed ('-') characters are added in this
1183 function
1184
1185 Returns:
1186 Tuple
1187 List of ErrLine objects for 'better' lines
1188 List of ErrLine objects for 'worse' lines
1189 """
Simon Glasse30965d2014-08-28 09:43:44 -06001190 better_lines = []
1191 worse_lines = []
1192 for line in lines:
1193 if line not in base_lines:
Simon Glass35d696d2020-04-09 15:08:36 -06001194 errline = ErrLine(char + '+', _BoardList(line, line_boards),
1195 line)
1196 worse_lines.append(errline)
Simon Glasse30965d2014-08-28 09:43:44 -06001197 for line in base_lines:
1198 if line not in lines:
Simon Glass35d696d2020-04-09 15:08:36 -06001199 errline = ErrLine(char + '-',
1200 _BoardList(line, base_line_boards), line)
1201 better_lines.append(errline)
Simon Glasse30965d2014-08-28 09:43:44 -06001202 return better_lines, worse_lines
1203
Simon Glass843312d2015-02-05 22:06:15 -07001204 def _CalcConfig(delta, name, config):
1205 """Calculate configuration changes
1206
1207 Args:
1208 delta: Type of the delta, e.g. '+'
1209 name: name of the file which changed (e.g. .config)
1210 config: configuration change dictionary
1211 key: config name
1212 value: config value
1213 Returns:
1214 String containing the configuration changes which can be
1215 printed
1216 """
1217 out = ''
1218 for key in sorted(config.keys()):
1219 out += '%s=%s ' % (key, config[key])
Simon Glass8270e3c2015-08-25 21:52:14 -06001220 return '%s %s: %s' % (delta, name, out)
Simon Glass843312d2015-02-05 22:06:15 -07001221
Simon Glass8270e3c2015-08-25 21:52:14 -06001222 def _AddConfig(lines, name, config_plus, config_minus, config_change):
1223 """Add changes in configuration to a list
Simon Glass843312d2015-02-05 22:06:15 -07001224
1225 Args:
Simon Glass8270e3c2015-08-25 21:52:14 -06001226 lines: list to add to
1227 name: config file name
Simon Glass843312d2015-02-05 22:06:15 -07001228 config_plus: configurations added, dictionary
1229 key: config name
1230 value: config value
1231 config_minus: configurations removed, dictionary
1232 key: config name
1233 value: config value
1234 config_change: configurations changed, dictionary
1235 key: config name
1236 value: config value
1237 """
1238 if config_plus:
Simon Glass8270e3c2015-08-25 21:52:14 -06001239 lines.append(_CalcConfig('+', name, config_plus))
Simon Glass843312d2015-02-05 22:06:15 -07001240 if config_minus:
Simon Glass8270e3c2015-08-25 21:52:14 -06001241 lines.append(_CalcConfig('-', name, config_minus))
Simon Glass843312d2015-02-05 22:06:15 -07001242 if config_change:
Simon Glass8270e3c2015-08-25 21:52:14 -06001243 lines.append(_CalcConfig('c', name, config_change))
1244
1245 def _OutputConfigInfo(lines):
1246 for line in lines:
1247 if not line:
1248 continue
1249 if line[0] == '+':
1250 col = self.col.GREEN
1251 elif line[0] == '-':
1252 col = self.col.RED
1253 elif line[0] == 'c':
1254 col = self.col.YELLOW
1255 Print(' ' + line, newline=True, colour=col)
1256
Simon Glassb206d872020-04-09 15:08:28 -06001257 def _OutputErrLines(err_lines, colour):
1258 """Output the line of error/warning lines, if not empty
1259
1260 Also increments self._error_lines if err_lines not empty
1261
1262 Args:
Simon Glass35d696d2020-04-09 15:08:36 -06001263 err_lines: List of ErrLine objects, each an error or warning
1264 line, possibly including a list of boards with that
1265 error/warning
Simon Glassb206d872020-04-09 15:08:28 -06001266 colour: Colour to use for output
1267 """
1268 if err_lines:
Simon Glass8c9a2672020-04-09 15:08:37 -06001269 out_list = []
Simon Glass35d696d2020-04-09 15:08:36 -06001270 for line in err_lines:
1271 boards = ''
1272 names = [board.target for board in line.boards]
Simon Glass9ef0ceb2020-04-09 15:08:38 -06001273 board_str = ' '.join(names) if names else ''
Simon Glass8c9a2672020-04-09 15:08:37 -06001274 if board_str:
1275 out = self.col.Color(colour, line.char + '(')
1276 out += self.col.Color(self.col.MAGENTA, board_str,
1277 bright=False)
1278 out += self.col.Color(colour, ') %s' % line.errline)
1279 else:
1280 out = self.col.Color(colour, line.char + line.errline)
1281 out_list.append(out)
1282 Print('\n'.join(out_list))
Simon Glassb206d872020-04-09 15:08:28 -06001283 self._error_lines += 1
1284
Simon Glass843312d2015-02-05 22:06:15 -07001285
Simon Glass4cf2b222018-11-06 16:02:12 -07001286 ok_boards = [] # List of boards fixed since last commit
Simon Glass6af71012018-11-06 16:02:13 -07001287 warn_boards = [] # List of boards with warnings since last commit
Simon Glass4cf2b222018-11-06 16:02:12 -07001288 err_boards = [] # List of new broken boards since last commit
1289 new_boards = [] # List of boards that didn't exist last time
1290 unknown_boards = [] # List of boards that were not built
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001291
1292 for target in board_dict:
1293 if target not in board_selected:
1294 continue
1295
1296 # If the board was built last time, add its outcome to a list
1297 if target in self._base_board_dict:
1298 base_outcome = self._base_board_dict[target].rc
1299 outcome = board_dict[target]
1300 if outcome.rc == OUTCOME_UNKNOWN:
Simon Glass4cf2b222018-11-06 16:02:12 -07001301 unknown_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001302 elif outcome.rc < base_outcome:
Simon Glass6af71012018-11-06 16:02:13 -07001303 if outcome.rc == OUTCOME_WARNING:
1304 warn_boards.append(target)
1305 else:
1306 ok_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001307 elif outcome.rc > base_outcome:
Simon Glass6af71012018-11-06 16:02:13 -07001308 if outcome.rc == OUTCOME_WARNING:
1309 warn_boards.append(target)
1310 else:
1311 err_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001312 else:
Simon Glass4cf2b222018-11-06 16:02:12 -07001313 new_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001314
Simon Glassb206d872020-04-09 15:08:28 -06001315 # Get a list of errors and warnings that have appeared, and disappeared
Simon Glasse30965d2014-08-28 09:43:44 -06001316 better_err, worse_err = _CalcErrorDelta(self._base_err_lines,
1317 self._base_err_line_boards, err_lines, err_line_boards, '')
1318 better_warn, worse_warn = _CalcErrorDelta(self._base_warn_lines,
1319 self._base_warn_line_boards, warn_lines, warn_line_boards, 'w')
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001320
1321 # Display results by arch
Simon Glass6af71012018-11-06 16:02:13 -07001322 if any((ok_boards, warn_boards, err_boards, unknown_boards, new_boards,
1323 worse_err, better_err, worse_warn, better_warn)):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001324 arch_list = {}
Simon Glass4cf2b222018-11-06 16:02:12 -07001325 self.AddOutcome(board_selected, arch_list, ok_boards, '',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001326 self.col.GREEN)
Simon Glass6af71012018-11-06 16:02:13 -07001327 self.AddOutcome(board_selected, arch_list, warn_boards, 'w+',
1328 self.col.YELLOW)
Simon Glass4cf2b222018-11-06 16:02:12 -07001329 self.AddOutcome(board_selected, arch_list, err_boards, '+',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001330 self.col.RED)
Simon Glass4cf2b222018-11-06 16:02:12 -07001331 self.AddOutcome(board_selected, arch_list, new_boards, '*', self.col.BLUE)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001332 if self._show_unknown:
Simon Glass4cf2b222018-11-06 16:02:12 -07001333 self.AddOutcome(board_selected, arch_list, unknown_boards, '?',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001334 self.col.MAGENTA)
Simon Glassc05aa032019-10-31 07:42:53 -06001335 for arch, target_list in arch_list.items():
Simon Glass4653a882014-09-05 19:00:07 -06001336 Print('%10s: %s' % (arch, target_list))
Simon Glass28370c12014-08-09 15:33:06 -06001337 self._error_lines += 1
Simon Glassb206d872020-04-09 15:08:28 -06001338 _OutputErrLines(better_err, colour=self.col.GREEN)
1339 _OutputErrLines(worse_err, colour=self.col.RED)
1340 _OutputErrLines(better_warn, colour=self.col.CYAN)
Simon Glass5627bd92020-04-09 15:08:35 -06001341 _OutputErrLines(worse_warn, colour=self.col.YELLOW)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001342
1343 if show_sizes:
1344 self.PrintSizeSummary(board_selected, board_dict, show_detail,
1345 show_bloat)
1346
Alex Kiernan48ae4122018-05-31 04:48:34 +00001347 if show_environment and self._base_environment:
1348 lines = []
1349
1350 for target in board_dict:
1351 if target not in board_selected:
1352 continue
1353
1354 tbase = self._base_environment[target]
1355 tenvironment = environment[target]
1356 environment_plus = {}
1357 environment_minus = {}
1358 environment_change = {}
1359 base = tbase.environment
Simon Glassc05aa032019-10-31 07:42:53 -06001360 for key, value in tenvironment.environment.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001361 if key not in base:
1362 environment_plus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001363 for key, value in base.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001364 if key not in tenvironment.environment:
1365 environment_minus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001366 for key, value in base.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001367 new_value = tenvironment.environment.get(key)
1368 if new_value and value != new_value:
1369 desc = '%s -> %s' % (value, new_value)
1370 environment_change[key] = desc
1371
1372 _AddConfig(lines, target, environment_plus, environment_minus,
1373 environment_change)
1374
1375 _OutputConfigInfo(lines)
1376
Simon Glass8270e3c2015-08-25 21:52:14 -06001377 if show_config and self._base_config:
1378 summary = {}
1379 arch_config_plus = {}
1380 arch_config_minus = {}
1381 arch_config_change = {}
1382 arch_list = []
1383
1384 for target in board_dict:
1385 if target not in board_selected:
Simon Glass843312d2015-02-05 22:06:15 -07001386 continue
Simon Glass8270e3c2015-08-25 21:52:14 -06001387 arch = board_selected[target].arch
1388 if arch not in arch_list:
1389 arch_list.append(arch)
1390
1391 for arch in arch_list:
1392 arch_config_plus[arch] = {}
1393 arch_config_minus[arch] = {}
1394 arch_config_change[arch] = {}
Simon Glassb464f8e2016-11-13 14:25:53 -07001395 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001396 arch_config_plus[arch][name] = {}
1397 arch_config_minus[arch][name] = {}
1398 arch_config_change[arch][name] = {}
1399
1400 for target in board_dict:
1401 if target not in board_selected:
1402 continue
1403
1404 arch = board_selected[target].arch
1405
1406 all_config_plus = {}
1407 all_config_minus = {}
1408 all_config_change = {}
1409 tbase = self._base_config[target]
1410 tconfig = config[target]
1411 lines = []
Simon Glassb464f8e2016-11-13 14:25:53 -07001412 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001413 if not tconfig.config[name]:
1414 continue
1415 config_plus = {}
1416 config_minus = {}
1417 config_change = {}
1418 base = tbase.config[name]
Simon Glassc05aa032019-10-31 07:42:53 -06001419 for key, value in tconfig.config[name].items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001420 if key not in base:
1421 config_plus[key] = value
1422 all_config_plus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001423 for key, value in base.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001424 if key not in tconfig.config[name]:
1425 config_minus[key] = value
1426 all_config_minus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001427 for key, value in base.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001428 new_value = tconfig.config.get(key)
1429 if new_value and value != new_value:
1430 desc = '%s -> %s' % (value, new_value)
1431 config_change[key] = desc
1432 all_config_change[key] = desc
1433
1434 arch_config_plus[arch][name].update(config_plus)
1435 arch_config_minus[arch][name].update(config_minus)
1436 arch_config_change[arch][name].update(config_change)
1437
1438 _AddConfig(lines, name, config_plus, config_minus,
1439 config_change)
1440 _AddConfig(lines, 'all', all_config_plus, all_config_minus,
1441 all_config_change)
1442 summary[target] = '\n'.join(lines)
1443
1444 lines_by_target = {}
Simon Glassc05aa032019-10-31 07:42:53 -06001445 for target, lines in summary.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001446 if lines in lines_by_target:
1447 lines_by_target[lines].append(target)
1448 else:
1449 lines_by_target[lines] = [target]
1450
1451 for arch in arch_list:
1452 lines = []
1453 all_plus = {}
1454 all_minus = {}
1455 all_change = {}
Simon Glassb464f8e2016-11-13 14:25:53 -07001456 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001457 all_plus.update(arch_config_plus[arch][name])
1458 all_minus.update(arch_config_minus[arch][name])
1459 all_change.update(arch_config_change[arch][name])
1460 _AddConfig(lines, name, arch_config_plus[arch][name],
1461 arch_config_minus[arch][name],
1462 arch_config_change[arch][name])
1463 _AddConfig(lines, 'all', all_plus, all_minus, all_change)
1464 #arch_summary[target] = '\n'.join(lines)
1465 if lines:
1466 Print('%s:' % arch)
1467 _OutputConfigInfo(lines)
1468
Simon Glassc05aa032019-10-31 07:42:53 -06001469 for lines, targets in lines_by_target.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001470 if not lines:
1471 continue
1472 Print('%s :' % ' '.join(sorted(targets)))
1473 _OutputConfigInfo(lines.split('\n'))
1474
Simon Glass843312d2015-02-05 22:06:15 -07001475
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001476 # Save our updated information for the next call to this function
1477 self._base_board_dict = board_dict
1478 self._base_err_lines = err_lines
Simon Glasse30965d2014-08-28 09:43:44 -06001479 self._base_warn_lines = warn_lines
1480 self._base_err_line_boards = err_line_boards
1481 self._base_warn_line_boards = warn_line_boards
Simon Glass843312d2015-02-05 22:06:15 -07001482 self._base_config = config
Alex Kiernan48ae4122018-05-31 04:48:34 +00001483 self._base_environment = environment
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001484
1485 # Get a list of boards that did not get built, if needed
1486 not_built = []
1487 for board in board_selected:
1488 if not board in board_dict:
1489 not_built.append(board)
1490 if not_built:
Simon Glass4653a882014-09-05 19:00:07 -06001491 Print("Boards not built (%d): %s" % (len(not_built),
1492 ', '.join(not_built)))
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001493
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001494 def ProduceResultSummary(self, commit_upto, commits, board_selected):
Simon Glasse30965d2014-08-28 09:43:44 -06001495 (board_dict, err_lines, err_line_boards, warn_lines,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001496 warn_line_boards, config, environment) = self.GetResultSummary(
Simon Glassed966652014-08-28 09:43:43 -06001497 board_selected, commit_upto,
Simon Glass843312d2015-02-05 22:06:15 -07001498 read_func_sizes=self._show_bloat,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001499 read_config=self._show_config,
1500 read_environment=self._show_environment)
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001501 if commits:
1502 msg = '%02d: %s' % (commit_upto + 1,
1503 commits[commit_upto].subject)
Simon Glass4653a882014-09-05 19:00:07 -06001504 Print(msg, colour=self.col.BLUE)
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001505 self.PrintResultSummary(board_selected, board_dict,
Simon Glassed966652014-08-28 09:43:43 -06001506 err_lines if self._show_errors else [], err_line_boards,
Simon Glasse30965d2014-08-28 09:43:44 -06001507 warn_lines if self._show_errors else [], warn_line_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001508 config, environment, self._show_sizes, self._show_detail,
1509 self._show_bloat, self._show_config, self._show_environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001510
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001511 def ShowSummary(self, commits, board_selected):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001512 """Show a build summary for U-Boot for a given board list.
1513
1514 Reset the result summary, then repeatedly call GetResultSummary on
1515 each commit's results, then display the differences we see.
1516
1517 Args:
1518 commit: Commit objects to summarise
1519 board_selected: Dict containing boards to summarise
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001520 """
Simon Glassfea58582014-08-09 15:32:59 -06001521 self.commit_count = len(commits) if commits else 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001522 self.commits = commits
1523 self.ResetResultSummary(board_selected)
Simon Glass28370c12014-08-09 15:33:06 -06001524 self._error_lines = 0
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001525
1526 for commit_upto in range(0, self.commit_count, self._step):
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001527 self.ProduceResultSummary(commit_upto, commits, board_selected)
Simon Glass28370c12014-08-09 15:33:06 -06001528 if not self._error_lines:
Simon Glass4653a882014-09-05 19:00:07 -06001529 Print('(no errors to report)', colour=self.col.GREEN)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001530
1531
1532 def SetupBuild(self, board_selected, commits):
1533 """Set up ready to start a build.
1534
1535 Args:
1536 board_selected: Selected boards to build
1537 commits: Selected commits to build
1538 """
1539 # First work out how many commits we will build
Simon Glassc05aa032019-10-31 07:42:53 -06001540 count = (self.commit_count + self._step - 1) // self._step
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001541 self.count = len(board_selected) * count
1542 self.upto = self.warned = self.fail = 0
1543 self._timestamps = collections.deque()
1544
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001545 def GetThreadDir(self, thread_num):
1546 """Get the directory path to the working dir for a thread.
1547
1548 Args:
Simon Glassb82492b2021-01-30 22:17:46 -07001549 thread_num: Number of thread to check (-1 for main process, which
1550 is treated as 0)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001551 """
Simon Glassd829f122020-03-18 09:42:42 -06001552 if self.work_in_output:
1553 return self._working_dir
Simon Glassb82492b2021-01-30 22:17:46 -07001554 return os.path.join(self._working_dir, '%02d' % max(thread_num, 0))
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001555
Simon Glassfea58582014-08-09 15:32:59 -06001556 def _PrepareThread(self, thread_num, setup_git):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001557 """Prepare the working directory for a thread.
1558
1559 This clones or fetches the repo into the thread's work directory.
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001560 Optionally, it can create a linked working tree of the repo in the
1561 thread's work directory instead.
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001562
1563 Args:
1564 thread_num: Thread number (0, 1, ...)
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001565 setup_git:
1566 'clone' to set up a git clone
1567 'worktree' to set up a git worktree
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001568 """
1569 thread_dir = self.GetThreadDir(thread_num)
Simon Glass190064b2014-08-09 15:33:00 -06001570 builderthread.Mkdir(thread_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001571 git_dir = os.path.join(thread_dir, '.git')
1572
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001573 # Create a worktree or a git repo clone for this thread if it
1574 # doesn't already exist
Simon Glassfea58582014-08-09 15:32:59 -06001575 if setup_git and self.git_dir:
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001576 src_dir = os.path.abspath(self.git_dir)
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001577 if os.path.isdir(git_dir):
1578 # This is a clone of the src_dir repo, we can keep using
1579 # it but need to fetch from src_dir.
Simon Glass212c0b82020-04-09 15:08:43 -06001580 Print('\rFetching repo for thread %d' % thread_num,
1581 newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001582 gitutil.Fetch(git_dir, thread_dir)
Simon Glass212c0b82020-04-09 15:08:43 -06001583 terminal.PrintClear()
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001584 elif os.path.isfile(git_dir):
1585 # This is a worktree of the src_dir repo, we don't need to
1586 # create it again or update it in any way.
1587 pass
1588 elif os.path.exists(git_dir):
1589 # Don't know what could trigger this, but we probably
1590 # can't create a git worktree/clone here.
1591 raise ValueError('Git dir %s exists, but is not a file '
1592 'or a directory.' % git_dir)
1593 elif setup_git == 'worktree':
1594 Print('\rChecking out worktree for thread %d' % thread_num,
1595 newline=False)
1596 gitutil.AddWorktree(src_dir, thread_dir)
1597 terminal.PrintClear()
1598 elif setup_git == 'clone' or setup_git == True:
Simon Glass21f0eb32016-09-18 16:48:31 -06001599 Print('\rCloning repo for thread %d' % thread_num,
1600 newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001601 gitutil.Clone(src_dir, thread_dir)
Simon Glass102969b2020-04-09 15:08:42 -06001602 terminal.PrintClear()
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001603 else:
1604 raise ValueError("Can't setup git repo with %s." % setup_git)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001605
Simon Glassfea58582014-08-09 15:32:59 -06001606 def _PrepareWorkingSpace(self, max_threads, setup_git):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001607 """Prepare the working directory for use.
1608
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001609 Set up the git repo for each thread. Creates a linked working tree
1610 if git-worktree is available, or clones the repo if it isn't.
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001611
1612 Args:
Simon Glassb82492b2021-01-30 22:17:46 -07001613 max_threads: Maximum number of threads we expect to need. If 0 then
1614 1 is set up, since the main process still needs somewhere to
1615 work
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001616 setup_git: True to set up a git worktree or a git clone
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001617 """
Simon Glass190064b2014-08-09 15:33:00 -06001618 builderthread.Mkdir(self._working_dir)
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001619 if setup_git and self.git_dir:
1620 src_dir = os.path.abspath(self.git_dir)
1621 if gitutil.CheckWorktreeIsAvailable(src_dir):
1622 setup_git = 'worktree'
1623 # If we previously added a worktree but the directory for it
1624 # got deleted, we need to prune its files from the repo so
1625 # that we can check out another in its place.
1626 gitutil.PruneWorktrees(src_dir)
1627 else:
1628 setup_git = 'clone'
Simon Glassb82492b2021-01-30 22:17:46 -07001629
1630 # Always do at least one thread
1631 for thread in range(max(max_threads, 1)):
Simon Glassfea58582014-08-09 15:32:59 -06001632 self._PrepareThread(thread, setup_git)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001633
Simon Glass925f6ad2020-03-18 09:42:45 -06001634 def _GetOutputSpaceRemovals(self):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001635 """Get the output directories ready to receive files.
1636
Simon Glass925f6ad2020-03-18 09:42:45 -06001637 Figure out what needs to be deleted in the output directory before it
1638 can be used. We only delete old buildman directories which have the
1639 expected name pattern. See _GetOutputDir().
1640
1641 Returns:
1642 List of full paths of directories to remove
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001643 """
Simon Glass1a915672014-12-01 17:33:53 -07001644 if not self.commits:
1645 return
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001646 dir_list = []
1647 for commit_upto in range(self.commit_count):
1648 dir_list.append(self._GetOutputDir(commit_upto))
1649
Simon Glassb222abe2016-09-18 16:48:32 -06001650 to_remove = []
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001651 for dirname in glob.glob(os.path.join(self.base_dir, '*')):
1652 if dirname not in dir_list:
Simon Glass925f6ad2020-03-18 09:42:45 -06001653 leaf = dirname[len(self.base_dir) + 1:]
Ovidiu Panait7664b032020-05-15 09:30:12 +03001654 m = re.match('[0-9]+_g[0-9a-f]+_.*', leaf)
Simon Glass925f6ad2020-03-18 09:42:45 -06001655 if m:
1656 to_remove.append(dirname)
1657 return to_remove
1658
1659 def _PrepareOutputSpace(self):
1660 """Get the output directories ready to receive files.
1661
1662 We delete any output directories which look like ones we need to
1663 create. Having left over directories is confusing when the user wants
1664 to check the output manually.
1665 """
1666 to_remove = self._GetOutputSpaceRemovals()
Simon Glassb222abe2016-09-18 16:48:32 -06001667 if to_remove:
Simon Glassb2d89bc2020-03-18 09:42:46 -06001668 Print('Removing %d old build directories...' % len(to_remove),
Simon Glassb222abe2016-09-18 16:48:32 -06001669 newline=False)
1670 for dirname in to_remove:
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001671 shutil.rmtree(dirname)
Simon Glass212c0b82020-04-09 15:08:43 -06001672 terminal.PrintClear()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001673
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001674 def BuildBoards(self, commits, board_selected, keep_outputs, verbose):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001675 """Build all commits for a list of boards
1676
1677 Args:
1678 commits: List of commits to be build, each a Commit object
1679 boards_selected: Dict of selected boards, key is target name,
1680 value is Board object
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001681 keep_outputs: True to save build output files
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001682 verbose: Display build results as they are completed
Simon Glass2c3deb92014-08-28 09:43:39 -06001683 Returns:
1684 Tuple containing:
1685 - number of boards that failed to build
1686 - number of boards that issued warnings
Simon Glass8116c782021-04-11 16:27:27 +12001687 - list of thread exceptions raised
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001688 """
Simon Glassfea58582014-08-09 15:32:59 -06001689 self.commit_count = len(commits) if commits else 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001690 self.commits = commits
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001691 self._verbose = verbose
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001692
1693 self.ResetResultSummary(board_selected)
Thierry Redingf3d015c2014-08-19 10:22:39 +02001694 builderthread.Mkdir(self.base_dir, parents = True)
Simon Glassfea58582014-08-09 15:32:59 -06001695 self._PrepareWorkingSpace(min(self.num_threads, len(board_selected)),
1696 commits is not None)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001697 self._PrepareOutputSpace()
Simon Glass745b3952016-09-18 16:48:33 -06001698 Print('\rStarting build...', newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001699 self.SetupBuild(board_selected, commits)
1700 self.ProcessResult(None)
Simon Glass8116c782021-04-11 16:27:27 +12001701 self.thread_exceptions = []
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001702 # Create jobs to build all commits for each board
Simon Glassc05aa032019-10-31 07:42:53 -06001703 for brd in board_selected.values():
Simon Glass190064b2014-08-09 15:33:00 -06001704 job = builderthread.BuilderJob()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001705 job.board = brd
1706 job.commits = commits
1707 job.keep_outputs = keep_outputs
Simon Glassd829f122020-03-18 09:42:42 -06001708 job.work_in_output = self.work_in_output
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001709 job.step = self._step
Simon Glassb82492b2021-01-30 22:17:46 -07001710 if self.num_threads:
1711 self.queue.put(job)
1712 else:
1713 results = self._single_builder.RunJob(job)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001714
Simon Glassb82492b2021-01-30 22:17:46 -07001715 if self.num_threads:
1716 term = threading.Thread(target=self.queue.join)
1717 term.setDaemon(True)
1718 term.start()
1719 while term.is_alive():
1720 term.join(100)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001721
Simon Glassb82492b2021-01-30 22:17:46 -07001722 # Wait until we have processed all output
1723 self.out_queue.join()
Simon Glass4653a882014-09-05 19:00:07 -06001724 Print()
Simon Glass7b33f212020-04-09 15:08:47 -06001725
1726 msg = 'Completed: %d total built' % self.count
1727 if self.already_done:
1728 msg += ' (%d previously' % self.already_done
1729 if self.already_done != self.count:
1730 msg += ', %d newly' % (self.count - self.already_done)
1731 msg += ')'
1732 duration = datetime.now() - self._start_time
1733 if duration > timedelta(microseconds=1000000):
1734 if duration.microseconds >= 500000:
1735 duration = duration + timedelta(seconds=1)
1736 duration = duration - timedelta(microseconds=duration.microseconds)
Simon Glass38f159c2020-07-19 12:40:26 -06001737 rate = float(self.count) / duration.total_seconds()
1738 msg += ', duration %s, rate %1.2f' % (duration, rate)
Simon Glass7b33f212020-04-09 15:08:47 -06001739 Print(msg)
Simon Glass8116c782021-04-11 16:27:27 +12001740 if self.thread_exceptions:
1741 Print('Failed: %d thread exceptions' % len(self.thread_exceptions),
1742 colour=self.col.RED)
Simon Glass7b33f212020-04-09 15:08:47 -06001743
Simon Glass8116c782021-04-11 16:27:27 +12001744 return (self.fail, self.warned, self.thread_exceptions)