blob: 30ebe1d820a7fba8b8358e490df33994fbb6ff43 [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 Glass190064b2014-08-09 15:33:00 -060020import builderthread
Simon Glassfc3fe1c2013-04-03 11:07:16 +000021import command
22import gitutil
23import terminal
Simon Glass4653a882014-09-05 19:00:07 -060024from terminal import Print
Simon Glassfc3fe1c2013-04-03 11:07:16 +000025import toolchain
26
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
73 01_of_02_g4ed4ebc_net--Add-tftp-speed-/
74 sandbox/
75 u-boot.bin
76 seaboard/
77 u-boot.bin
78 02_of_02_g4ed4ebc_net--Check-tftp-comp/
79 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 Glassfc3fe1c2013-04-03 11:07:16 +0000185
186 Private members:
187 _base_board_dict: Last-summarised Dict of boards
188 _base_err_lines: Last-summarised list of errors
Simon Glasse30965d2014-08-28 09:43:44 -0600189 _base_warn_lines: Last-summarised list of warnings
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000190 _build_period_us: Time taken for a single build (float object).
191 _complete_delay: Expected delay until completion (timedelta)
192 _next_delay_update: Next time we plan to display a progress update
193 (datatime)
194 _show_unknown: Show unknown boards (those not built) in summary
Simon Glass7b33f212020-04-09 15:08:47 -0600195 _start_time: Start time for the build
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000196 _timestamps: List of timestamps for the completion of the last
197 last _timestamp_count builds. Each is a datetime object.
198 _timestamp_count: Number of timestamps to keep in our list.
199 _working_dir: Base working directory containing all threads
200 """
201 class Outcome:
202 """Records a build outcome for a single make invocation
203
204 Public Members:
205 rc: Outcome value (OUTCOME_...)
206 err_lines: List of error lines or [] if none
207 sizes: Dictionary of image size information, keyed by filename
208 - Each value is itself a dictionary containing
209 values for 'text', 'data' and 'bss', being the integer
210 size in bytes of each section.
211 func_sizes: Dictionary keyed by filename - e.g. 'u-boot'. Each
212 value is itself a dictionary:
213 key: function name
214 value: Size of function in bytes
Simon Glass843312d2015-02-05 22:06:15 -0700215 config: Dictionary keyed by filename - e.g. '.config'. Each
216 value is itself a dictionary:
217 key: config name
218 value: config value
Alex Kiernan48ae4122018-05-31 04:48:34 +0000219 environment: Dictionary keyed by environment variable, Each
220 value is the value of environment variable.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000221 """
Alex Kiernan48ae4122018-05-31 04:48:34 +0000222 def __init__(self, rc, err_lines, sizes, func_sizes, config,
223 environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000224 self.rc = rc
225 self.err_lines = err_lines
226 self.sizes = sizes
227 self.func_sizes = func_sizes
Simon Glass843312d2015-02-05 22:06:15 -0700228 self.config = config
Alex Kiernan48ae4122018-05-31 04:48:34 +0000229 self.environment = environment
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000230
231 def __init__(self, toolchains, base_dir, git_dir, num_threads, num_jobs,
Simon Glass5971ab52014-12-01 17:33:55 -0700232 gnu_make='make', checkout=True, show_unknown=True, step=1,
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600233 no_subdirs=False, full_path=False, verbose_build=False,
Simon Glasseb70a2c2020-04-09 15:08:51 -0600234 mrproper=False, per_board_out_dir=False,
Daniel Schwierzeck2371d1b2018-01-26 16:31:05 +0100235 config_only=False, squash_config_y=False,
Simon Glassd829f122020-03-18 09:42:42 -0600236 warnings_as_errors=False, work_in_output=False):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000237 """Create a new Builder object
238
239 Args:
240 toolchains: Toolchains object to use for building
241 base_dir: Base directory to use for builder
242 git_dir: Git directory containing source repository
243 num_threads: Number of builder threads to run
244 num_jobs: Number of jobs to run at once (passed to make as -j)
Masahiro Yamada99796922014-07-22 11:19:09 +0900245 gnu_make: the command name of GNU Make.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000246 checkout: True to check out source, False to skip that step.
247 This is used for testing.
248 show_unknown: Show unknown boards (those not built) in summary
249 step: 1 to process every commit, n to process every nth commit
Simon Glassbb1501f2014-12-01 17:34:00 -0700250 no_subdirs: Don't create subdirectories when building current
251 source for a single board
252 full_path: Return the full path in CROSS_COMPILE and don't set
253 PATH
Simon Glassd2ce6582014-12-01 17:34:07 -0700254 verbose_build: Run build with V=1 and don't use 'make -s'
Simon Glasseb70a2c2020-04-09 15:08:51 -0600255 mrproper: Always run 'make mrproper' when configuring
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600256 per_board_out_dir: Build in a separate persistent directory per
257 board rather than a thread-specific directory
Simon Glassb50113f2016-11-13 14:25:51 -0700258 config_only: Only configure each build, don't build it
Simon Glassb464f8e2016-11-13 14:25:53 -0700259 squash_config_y: Convert CONFIG options with the value 'y' to '1'
Daniel Schwierzeck2371d1b2018-01-26 16:31:05 +0100260 warnings_as_errors: Treat all compiler warnings as errors
Simon Glassd829f122020-03-18 09:42:42 -0600261 work_in_output: Use the output directory as the work directory and
262 don't write to a separate output directory.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000263 """
264 self.toolchains = toolchains
265 self.base_dir = base_dir
Simon Glassd829f122020-03-18 09:42:42 -0600266 if work_in_output:
267 self._working_dir = base_dir
268 else:
269 self._working_dir = os.path.join(base_dir, '.bm-work')
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000270 self.threads = []
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000271 self.do_make = self.Make
Masahiro Yamada99796922014-07-22 11:19:09 +0900272 self.gnu_make = gnu_make
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000273 self.checkout = checkout
274 self.num_threads = num_threads
275 self.num_jobs = num_jobs
276 self.already_done = 0
277 self.force_build = False
278 self.git_dir = git_dir
279 self._show_unknown = show_unknown
280 self._timestamp_count = 10
281 self._build_period_us = None
282 self._complete_delay = None
283 self._next_delay_update = datetime.now()
Simon Glass7b33f212020-04-09 15:08:47 -0600284 self._start_time = datetime.now()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000285 self.force_config_on_failure = True
Simon Glass4266dc22014-07-13 12:22:31 -0600286 self.force_build_failures = False
Simon Glass97e91522014-07-14 17:51:02 -0600287 self.force_reconfig = False
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000288 self._step = step
Simon Glass189a4962014-07-14 17:51:03 -0600289 self.in_tree = False
Simon Glass28370c12014-08-09 15:33:06 -0600290 self._error_lines = 0
Simon Glass5971ab52014-12-01 17:33:55 -0700291 self.no_subdirs = no_subdirs
Simon Glassbb1501f2014-12-01 17:34:00 -0700292 self.full_path = full_path
Simon Glassd2ce6582014-12-01 17:34:07 -0700293 self.verbose_build = verbose_build
Simon Glassb50113f2016-11-13 14:25:51 -0700294 self.config_only = config_only
Simon Glassb464f8e2016-11-13 14:25:53 -0700295 self.squash_config_y = squash_config_y
296 self.config_filenames = BASE_CONFIG_FILENAMES
Simon Glassd829f122020-03-18 09:42:42 -0600297 self.work_in_output = work_in_output
Simon Glassb464f8e2016-11-13 14:25:53 -0700298 if not self.squash_config_y:
299 self.config_filenames += EXTRA_CONFIG_FILENAMES
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000300
Daniel Schwierzeck2371d1b2018-01-26 16:31:05 +0100301 self.warnings_as_errors = warnings_as_errors
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000302 self.col = terminal.Color()
303
Simon Glasse30965d2014-08-28 09:43:44 -0600304 self._re_function = re.compile('(.*): In function.*')
305 self._re_files = re.compile('In file included from.*')
306 self._re_warning = re.compile('(.*):(\d*):(\d*): warning: .*')
Simon Glass2d483332018-11-06 16:02:11 -0700307 self._re_dtb_warning = re.compile('(.*): Warning .*')
Simon Glasse30965d2014-08-28 09:43:44 -0600308 self._re_note = re.compile('(.*):(\d*):(\d*): note: this is the location of the previous.*')
Simon Glass113a8a52020-04-09 15:08:53 -0600309 self._re_migration_warning = re.compile(r'^={21} WARNING ={22}\n.*\n=+\n',
310 re.MULTILINE | re.DOTALL)
Simon Glasse30965d2014-08-28 09:43:44 -0600311
Simon Glassc05aa032019-10-31 07:42:53 -0600312 self.queue = queue.Queue()
313 self.out_queue = queue.Queue()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000314 for i in range(self.num_threads):
Simon Glasseb70a2c2020-04-09 15:08:51 -0600315 t = builderthread.BuilderThread(self, i, mrproper,
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600316 per_board_out_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000317 t.setDaemon(True)
318 t.start()
319 self.threads.append(t)
320
Simon Glass190064b2014-08-09 15:33:00 -0600321 t = builderthread.ResultThread(self)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000322 t.setDaemon(True)
323 t.start()
324 self.threads.append(t)
325
326 ignore_lines = ['(make.*Waiting for unfinished)', '(Segmentation fault)']
327 self.re_make_err = re.compile('|'.join(ignore_lines))
328
Simon Glass2f256642016-09-18 16:48:37 -0600329 # Handle existing graceful with SIGINT / Ctrl-C
330 signal.signal(signal.SIGINT, self.signal_handler)
331
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000332 def __del__(self):
333 """Get rid of all threads created by the builder"""
334 for t in self.threads:
335 del t
336
Simon Glass2f256642016-09-18 16:48:37 -0600337 def signal_handler(self, signal, frame):
338 sys.exit(1)
339
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600340 def SetDisplayOptions(self, show_errors=False, show_sizes=False,
Simon Glassed966652014-08-28 09:43:43 -0600341 show_detail=False, show_bloat=False,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000342 list_error_boards=False, show_config=False,
Simon Glass113a8a52020-04-09 15:08:53 -0600343 show_environment=False, filter_dtb_warnings=False,
344 filter_migration_warnings=False):
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600345 """Setup display options for the builder.
346
Simon Glass174592b2020-04-09 15:08:52 -0600347 Args:
348 show_errors: True to show summarised error/warning info
349 show_sizes: Show size deltas
350 show_detail: Show size delta detail for each board if show_sizes
351 show_bloat: Show detail for each function
352 list_error_boards: Show the boards which caused each error/warning
353 show_config: Show config deltas
354 show_environment: Show environment deltas
355 filter_dtb_warnings: Filter out any warnings from the device-tree
356 compiler
Simon Glass113a8a52020-04-09 15:08:53 -0600357 filter_migration_warnings: Filter out any warnings about migrating
358 a board to driver model
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600359 """
360 self._show_errors = show_errors
361 self._show_sizes = show_sizes
362 self._show_detail = show_detail
363 self._show_bloat = show_bloat
Simon Glassed966652014-08-28 09:43:43 -0600364 self._list_error_boards = list_error_boards
Simon Glass843312d2015-02-05 22:06:15 -0700365 self._show_config = show_config
Alex Kiernan48ae4122018-05-31 04:48:34 +0000366 self._show_environment = show_environment
Simon Glass174592b2020-04-09 15:08:52 -0600367 self._filter_dtb_warnings = filter_dtb_warnings
Simon Glass113a8a52020-04-09 15:08:53 -0600368 self._filter_migration_warnings = filter_migration_warnings
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600369
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000370 def _AddTimestamp(self):
371 """Add a new timestamp to the list and record the build period.
372
373 The build period is the length of time taken to perform a single
374 build (one board, one commit).
375 """
376 now = datetime.now()
377 self._timestamps.append(now)
378 count = len(self._timestamps)
379 delta = self._timestamps[-1] - self._timestamps[0]
380 seconds = delta.total_seconds()
381
382 # If we have enough data, estimate build period (time taken for a
383 # single build) and therefore completion time.
384 if count > 1 and self._next_delay_update < now:
385 self._next_delay_update = now + timedelta(seconds=2)
386 if seconds > 0:
387 self._build_period = float(seconds) / count
388 todo = self.count - self.upto
389 self._complete_delay = timedelta(microseconds=
390 self._build_period * todo * 1000000)
391 # Round it
392 self._complete_delay -= timedelta(
393 microseconds=self._complete_delay.microseconds)
394
395 if seconds > 60:
396 self._timestamps.popleft()
397 count -= 1
398
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000399 def SelectCommit(self, commit, checkout=True):
400 """Checkout the selected commit for this build
401 """
402 self.commit = commit
403 if checkout and self.checkout:
404 gitutil.Checkout(commit.hash)
405
406 def Make(self, commit, brd, stage, cwd, *args, **kwargs):
407 """Run make
408
409 Args:
410 commit: Commit object that is being built
411 brd: Board object that is being built
Roger Meierfd18a892014-08-20 22:10:29 +0200412 stage: Stage that we are at (mrproper, config, build)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000413 cwd: Directory where make should be run
414 args: Arguments to pass to make
415 kwargs: Arguments to pass to command.RunPipe()
416 """
Masahiro Yamada99796922014-07-22 11:19:09 +0900417 cmd = [self.gnu_make] + list(args)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000418 result = command.RunPipe([cmd], capture=True, capture_stderr=True,
Simon Glasse62a24c2018-09-17 23:55:42 -0600419 cwd=cwd, raise_on_error=False, infile='/dev/null', **kwargs)
Simon Glass40f11fc2015-02-05 22:06:12 -0700420 if self.verbose_build:
421 result.stdout = '%s\n' % (' '.join(cmd)) + result.stdout
422 result.combined = '%s\n' % (' '.join(cmd)) + result.combined
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000423 return result
424
425 def ProcessResult(self, result):
426 """Process the result of a build, showing progress information
427
428 Args:
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600429 result: A CommandResult object, which indicates the result for
430 a single build
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000431 """
432 col = terminal.Color()
433 if result:
434 target = result.brd.target
435
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000436 self.upto += 1
437 if result.return_code != 0:
438 self.fail += 1
439 elif result.stderr:
440 self.warned += 1
441 if result.already_done:
442 self.already_done += 1
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600443 if self._verbose:
Simon Glass102969b2020-04-09 15:08:42 -0600444 terminal.PrintClear()
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600445 boards_selected = {target : result.brd}
446 self.ResetResultSummary(boards_selected)
447 self.ProduceResultSummary(result.commit_upto, self.commits,
448 boards_selected)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000449 else:
450 target = '(starting)'
451
452 # Display separate counts for ok, warned and fail
453 ok = self.upto - self.warned - self.fail
454 line = '\r' + self.col.Color(self.col.GREEN, '%5d' % ok)
455 line += self.col.Color(self.col.YELLOW, '%5d' % self.warned)
456 line += self.col.Color(self.col.RED, '%5d' % self.fail)
457
Simon Glass6eb76ca2020-04-09 15:08:45 -0600458 line += ' /%-5d ' % self.count
459 remaining = self.count - self.upto
460 if remaining:
461 line += self.col.Color(self.col.MAGENTA, ' -%-5d ' % remaining)
462 else:
463 line += ' ' * 8
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000464
465 # Add our current completion time estimate
466 self._AddTimestamp()
467 if self._complete_delay:
Simon Glass6eb76ca2020-04-09 15:08:45 -0600468 line += '%s : ' % self._complete_delay
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000469
Simon Glass6eb76ca2020-04-09 15:08:45 -0600470 line += target
Simon Glass102969b2020-04-09 15:08:42 -0600471 terminal.PrintClear()
Simon Glass95ed0a22020-04-09 15:08:46 -0600472 Print(line, newline=False, limit_to_line=True)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000473
474 def _GetOutputDir(self, commit_upto):
475 """Get the name of the output directory for a commit number
476
477 The output directory is typically .../<branch>/<commit>.
478
479 Args:
480 commit_upto: Commit number to use (0..self.count-1)
481 """
Simon Glass5971ab52014-12-01 17:33:55 -0700482 commit_dir = None
Simon Glassfea58582014-08-09 15:32:59 -0600483 if self.commits:
484 commit = self.commits[commit_upto]
485 subject = commit.subject.translate(trans_valid_chars)
Simon Glass925f6ad2020-03-18 09:42:45 -0600486 # See _GetOutputSpaceRemovals() which parses this name
Simon Glassfea58582014-08-09 15:32:59 -0600487 commit_dir = ('%02d_of_%02d_g%s_%s' % (commit_upto + 1,
488 self.commit_count, commit.hash, subject[:20]))
Simon Glass5971ab52014-12-01 17:33:55 -0700489 elif not self.no_subdirs:
Simon Glassfea58582014-08-09 15:32:59 -0600490 commit_dir = 'current'
Simon Glass5971ab52014-12-01 17:33:55 -0700491 if not commit_dir:
492 return self.base_dir
493 return os.path.join(self.base_dir, commit_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000494
495 def GetBuildDir(self, commit_upto, target):
496 """Get the name of the build directory for a commit number
497
498 The build directory is typically .../<branch>/<commit>/<target>.
499
500 Args:
501 commit_upto: Commit number to use (0..self.count-1)
502 target: Target name
503 """
504 output_dir = self._GetOutputDir(commit_upto)
505 return os.path.join(output_dir, target)
506
507 def GetDoneFile(self, commit_upto, target):
508 """Get the name of the done file for a commit number
509
510 Args:
511 commit_upto: Commit number to use (0..self.count-1)
512 target: Target name
513 """
514 return os.path.join(self.GetBuildDir(commit_upto, target), 'done')
515
516 def GetSizesFile(self, commit_upto, target):
517 """Get the name of the sizes file for a commit number
518
519 Args:
520 commit_upto: Commit number to use (0..self.count-1)
521 target: Target name
522 """
523 return os.path.join(self.GetBuildDir(commit_upto, target), 'sizes')
524
525 def GetFuncSizesFile(self, commit_upto, target, elf_fname):
526 """Get the name of the funcsizes file for a commit number and ELF file
527
528 Args:
529 commit_upto: Commit number to use (0..self.count-1)
530 target: Target name
531 elf_fname: Filename of elf image
532 """
533 return os.path.join(self.GetBuildDir(commit_upto, target),
534 '%s.sizes' % elf_fname.replace('/', '-'))
535
536 def GetObjdumpFile(self, commit_upto, target, elf_fname):
537 """Get the name of the objdump file for a commit number and ELF file
538
539 Args:
540 commit_upto: Commit number to use (0..self.count-1)
541 target: Target name
542 elf_fname: Filename of elf image
543 """
544 return os.path.join(self.GetBuildDir(commit_upto, target),
545 '%s.objdump' % elf_fname.replace('/', '-'))
546
547 def GetErrFile(self, commit_upto, target):
548 """Get the name of the err file for a commit number
549
550 Args:
551 commit_upto: Commit number to use (0..self.count-1)
552 target: Target name
553 """
554 output_dir = self.GetBuildDir(commit_upto, target)
555 return os.path.join(output_dir, 'err')
556
557 def FilterErrors(self, lines):
558 """Filter out errors in which we have no interest
559
560 We should probably use map().
561
562 Args:
563 lines: List of error lines, each a string
564 Returns:
565 New list with only interesting lines included
566 """
567 out_lines = []
Simon Glass113a8a52020-04-09 15:08:53 -0600568 if self._filter_migration_warnings:
569 text = '\n'.join(lines)
570 text = self._re_migration_warning.sub('', text)
571 lines = text.splitlines()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000572 for line in lines:
Simon Glass174592b2020-04-09 15:08:52 -0600573 if self.re_make_err.search(line):
574 continue
575 if self._filter_dtb_warnings and self._re_dtb_warning.search(line):
576 continue
577 out_lines.append(line)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000578 return out_lines
579
580 def ReadFuncSizes(self, fname, fd):
581 """Read function sizes from the output of 'nm'
582
583 Args:
584 fd: File containing data to read
585 fname: Filename we are reading from (just for errors)
586
587 Returns:
588 Dictionary containing size of each function in bytes, indexed by
589 function name.
590 """
591 sym = {}
592 for line in fd.readlines():
593 try:
Tom Rinid08c38c2019-12-06 15:31:31 -0500594 if line.strip():
595 size, type, name = line[:-1].split()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000596 except:
Simon Glass4653a882014-09-05 19:00:07 -0600597 Print("Invalid line in file '%s': '%s'" % (fname, line[:-1]))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000598 continue
599 if type in 'tTdDbB':
600 # function names begin with '.' on 64-bit powerpc
601 if '.' in name[1:]:
602 name = 'static.' + name.split('.')[0]
603 sym[name] = sym.get(name, 0) + int(size, 16)
604 return sym
605
Simon Glass843312d2015-02-05 22:06:15 -0700606 def _ProcessConfig(self, fname):
607 """Read in a .config, autoconf.mk or autoconf.h file
608
609 This function handles all config file types. It ignores comments and
610 any #defines which don't start with CONFIG_.
611
612 Args:
613 fname: Filename to read
614
615 Returns:
616 Dictionary:
617 key: Config name (e.g. CONFIG_DM)
618 value: Config value (e.g. 1)
619 """
620 config = {}
621 if os.path.exists(fname):
622 with open(fname) as fd:
623 for line in fd:
624 line = line.strip()
625 if line.startswith('#define'):
626 values = line[8:].split(' ', 1)
627 if len(values) > 1:
628 key, value = values
629 else:
630 key = values[0]
Simon Glassb464f8e2016-11-13 14:25:53 -0700631 value = '1' if self.squash_config_y else ''
Simon Glass843312d2015-02-05 22:06:15 -0700632 if not key.startswith('CONFIG_'):
633 continue
634 elif not line or line[0] in ['#', '*', '/']:
635 continue
636 else:
637 key, value = line.split('=', 1)
Simon Glassb464f8e2016-11-13 14:25:53 -0700638 if self.squash_config_y and value == 'y':
639 value = '1'
Simon Glass843312d2015-02-05 22:06:15 -0700640 config[key] = value
641 return config
642
Alex Kiernan48ae4122018-05-31 04:48:34 +0000643 def _ProcessEnvironment(self, fname):
644 """Read in a uboot.env file
645
646 This function reads in environment variables from a file.
647
648 Args:
649 fname: Filename to read
650
651 Returns:
652 Dictionary:
653 key: environment variable (e.g. bootlimit)
654 value: value of environment variable (e.g. 1)
655 """
656 environment = {}
657 if os.path.exists(fname):
658 with open(fname) as fd:
659 for line in fd.read().split('\0'):
660 try:
661 key, value = line.split('=', 1)
662 environment[key] = value
663 except ValueError:
664 # ignore lines we can't parse
665 pass
666 return environment
667
Simon Glass843312d2015-02-05 22:06:15 -0700668 def GetBuildOutcome(self, commit_upto, target, read_func_sizes,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000669 read_config, read_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000670 """Work out the outcome of a build.
671
672 Args:
673 commit_upto: Commit number to check (0..n-1)
674 target: Target board to check
675 read_func_sizes: True to read function size information
Simon Glass843312d2015-02-05 22:06:15 -0700676 read_config: True to read .config and autoconf.h files
Alex Kiernan48ae4122018-05-31 04:48:34 +0000677 read_environment: True to read uboot.env files
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000678
679 Returns:
680 Outcome object
681 """
682 done_file = self.GetDoneFile(commit_upto, target)
683 sizes_file = self.GetSizesFile(commit_upto, target)
684 sizes = {}
685 func_sizes = {}
Simon Glass843312d2015-02-05 22:06:15 -0700686 config = {}
Alex Kiernan48ae4122018-05-31 04:48:34 +0000687 environment = {}
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000688 if os.path.exists(done_file):
689 with open(done_file, 'r') as fd:
Simon Glass347ea0b2019-04-26 19:02:23 -0600690 try:
691 return_code = int(fd.readline())
692 except ValueError:
693 # The file may be empty due to running out of disk space.
694 # Try a rebuild
695 return_code = 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000696 err_lines = []
697 err_file = self.GetErrFile(commit_upto, target)
698 if os.path.exists(err_file):
699 with open(err_file, 'r') as fd:
700 err_lines = self.FilterErrors(fd.readlines())
701
702 # Decide whether the build was ok, failed or created warnings
703 if return_code:
704 rc = OUTCOME_ERROR
705 elif len(err_lines):
706 rc = OUTCOME_WARNING
707 else:
708 rc = OUTCOME_OK
709
710 # Convert size information to our simple format
711 if os.path.exists(sizes_file):
712 with open(sizes_file, 'r') as fd:
713 for line in fd.readlines():
714 values = line.split()
715 rodata = 0
716 if len(values) > 6:
717 rodata = int(values[6], 16)
718 size_dict = {
719 'all' : int(values[0]) + int(values[1]) +
720 int(values[2]),
721 'text' : int(values[0]) - rodata,
722 'data' : int(values[1]),
723 'bss' : int(values[2]),
724 'rodata' : rodata,
725 }
726 sizes[values[5]] = size_dict
727
728 if read_func_sizes:
729 pattern = self.GetFuncSizesFile(commit_upto, target, '*')
730 for fname in glob.glob(pattern):
731 with open(fname, 'r') as fd:
732 dict_name = os.path.basename(fname).replace('.sizes',
733 '')
734 func_sizes[dict_name] = self.ReadFuncSizes(fname, fd)
735
Simon Glass843312d2015-02-05 22:06:15 -0700736 if read_config:
737 output_dir = self.GetBuildDir(commit_upto, target)
Simon Glassb464f8e2016-11-13 14:25:53 -0700738 for name in self.config_filenames:
Simon Glass843312d2015-02-05 22:06:15 -0700739 fname = os.path.join(output_dir, name)
740 config[name] = self._ProcessConfig(fname)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000741
Alex Kiernan48ae4122018-05-31 04:48:34 +0000742 if read_environment:
743 output_dir = self.GetBuildDir(commit_upto, target)
744 fname = os.path.join(output_dir, 'uboot.env')
745 environment = self._ProcessEnvironment(fname)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000746
Alex Kiernan48ae4122018-05-31 04:48:34 +0000747 return Builder.Outcome(rc, err_lines, sizes, func_sizes, config,
748 environment)
749
750 return Builder.Outcome(OUTCOME_UNKNOWN, [], {}, {}, {}, {})
Simon Glass843312d2015-02-05 22:06:15 -0700751
752 def GetResultSummary(self, boards_selected, commit_upto, read_func_sizes,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000753 read_config, read_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000754 """Calculate a summary of the results of building a commit.
755
756 Args:
757 board_selected: Dict containing boards to summarise
758 commit_upto: Commit number to summarize (0..self.count-1)
759 read_func_sizes: True to read function size information
Simon Glass843312d2015-02-05 22:06:15 -0700760 read_config: True to read .config and autoconf.h files
Alex Kiernan48ae4122018-05-31 04:48:34 +0000761 read_environment: True to read uboot.env files
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000762
763 Returns:
764 Tuple:
765 Dict containing boards which passed building this commit.
766 keyed by board.target
Simon Glasse30965d2014-08-28 09:43:44 -0600767 List containing a summary of error lines
Simon Glassed966652014-08-28 09:43:43 -0600768 Dict keyed by error line, containing a list of the Board
769 objects with that error
Simon Glasse30965d2014-08-28 09:43:44 -0600770 List containing a summary of warning lines
771 Dict keyed by error line, containing a list of the Board
772 objects with that warning
Simon Glass8270e3c2015-08-25 21:52:14 -0600773 Dictionary keyed by board.target. Each value is a dictionary:
774 key: filename - e.g. '.config'
Simon Glass843312d2015-02-05 22:06:15 -0700775 value is itself a dictionary:
776 key: config name
777 value: config value
Alex Kiernan48ae4122018-05-31 04:48:34 +0000778 Dictionary keyed by board.target. Each value is a dictionary:
779 key: environment variable
780 value: value of environment variable
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000781 """
Simon Glasse30965d2014-08-28 09:43:44 -0600782 def AddLine(lines_summary, lines_boards, line, board):
783 line = line.rstrip()
784 if line in lines_boards:
785 lines_boards[line].append(board)
786 else:
787 lines_boards[line] = [board]
788 lines_summary.append(line)
789
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000790 board_dict = {}
791 err_lines_summary = []
Simon Glassed966652014-08-28 09:43:43 -0600792 err_lines_boards = {}
Simon Glasse30965d2014-08-28 09:43:44 -0600793 warn_lines_summary = []
794 warn_lines_boards = {}
Simon Glass843312d2015-02-05 22:06:15 -0700795 config = {}
Alex Kiernan48ae4122018-05-31 04:48:34 +0000796 environment = {}
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000797
Simon Glassc05aa032019-10-31 07:42:53 -0600798 for board in boards_selected.values():
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000799 outcome = self.GetBuildOutcome(commit_upto, board.target,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000800 read_func_sizes, read_config,
801 read_environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000802 board_dict[board.target] = outcome
Simon Glasse30965d2014-08-28 09:43:44 -0600803 last_func = None
804 last_was_warning = False
805 for line in outcome.err_lines:
806 if line:
807 if (self._re_function.match(line) or
808 self._re_files.match(line)):
809 last_func = line
Simon Glassed966652014-08-28 09:43:43 -0600810 else:
Simon Glass2d483332018-11-06 16:02:11 -0700811 is_warning = (self._re_warning.match(line) or
812 self._re_dtb_warning.match(line))
Simon Glasse30965d2014-08-28 09:43:44 -0600813 is_note = self._re_note.match(line)
814 if is_warning or (last_was_warning and is_note):
815 if last_func:
816 AddLine(warn_lines_summary, warn_lines_boards,
817 last_func, board)
818 AddLine(warn_lines_summary, warn_lines_boards,
819 line, board)
820 else:
821 if last_func:
822 AddLine(err_lines_summary, err_lines_boards,
823 last_func, board)
824 AddLine(err_lines_summary, err_lines_boards,
825 line, board)
826 last_was_warning = is_warning
827 last_func = None
Simon Glassb464f8e2016-11-13 14:25:53 -0700828 tconfig = Config(self.config_filenames, board.target)
829 for fname in self.config_filenames:
Simon Glass843312d2015-02-05 22:06:15 -0700830 if outcome.config:
Simon Glassc05aa032019-10-31 07:42:53 -0600831 for key, value in outcome.config[fname].items():
Simon Glass8270e3c2015-08-25 21:52:14 -0600832 tconfig.Add(fname, key, value)
833 config[board.target] = tconfig
Simon Glass843312d2015-02-05 22:06:15 -0700834
Alex Kiernan48ae4122018-05-31 04:48:34 +0000835 tenvironment = Environment(board.target)
836 if outcome.environment:
Simon Glassc05aa032019-10-31 07:42:53 -0600837 for key, value in outcome.environment.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +0000838 tenvironment.Add(key, value)
839 environment[board.target] = tenvironment
840
Simon Glasse30965d2014-08-28 09:43:44 -0600841 return (board_dict, err_lines_summary, err_lines_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000842 warn_lines_summary, warn_lines_boards, config, environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000843
844 def AddOutcome(self, board_dict, arch_list, changes, char, color):
845 """Add an output to our list of outcomes for each architecture
846
847 This simple function adds failing boards (changes) to the
848 relevant architecture string, so we can print the results out
849 sorted by architecture.
850
851 Args:
852 board_dict: Dict containing all boards
853 arch_list: Dict keyed by arch name. Value is a string containing
854 a list of board names which failed for that arch.
855 changes: List of boards to add to arch_list
856 color: terminal.Colour object
857 """
858 done_arch = {}
859 for target in changes:
860 if target in board_dict:
861 arch = board_dict[target].arch
862 else:
863 arch = 'unknown'
864 str = self.col.Color(color, ' ' + target)
865 if not arch in done_arch:
Simon Glass63c619e2015-02-05 22:06:11 -0700866 str = ' %s %s' % (self.col.Color(color, char), str)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000867 done_arch[arch] = True
868 if not arch in arch_list:
869 arch_list[arch] = str
870 else:
871 arch_list[arch] += str
872
873
874 def ColourNum(self, num):
875 color = self.col.RED if num > 0 else self.col.GREEN
876 if num == 0:
877 return '0'
878 return self.col.Color(color, str(num))
879
880 def ResetResultSummary(self, board_selected):
881 """Reset the results summary ready for use.
882
883 Set up the base board list to be all those selected, and set the
884 error lines to empty.
885
886 Following this, calls to PrintResultSummary() will use this
887 information to work out what has changed.
888
889 Args:
890 board_selected: Dict containing boards to summarise, keyed by
891 board.target
892 """
893 self._base_board_dict = {}
894 for board in board_selected:
Alex Kiernan48ae4122018-05-31 04:48:34 +0000895 self._base_board_dict[board] = Builder.Outcome(0, [], [], {}, {},
896 {})
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000897 self._base_err_lines = []
Simon Glasse30965d2014-08-28 09:43:44 -0600898 self._base_warn_lines = []
899 self._base_err_line_boards = {}
900 self._base_warn_line_boards = {}
Simon Glass8270e3c2015-08-25 21:52:14 -0600901 self._base_config = None
Alex Kiernan48ae4122018-05-31 04:48:34 +0000902 self._base_environment = None
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000903
904 def PrintFuncSizeDetail(self, fname, old, new):
905 grow, shrink, add, remove, up, down = 0, 0, 0, 0, 0, 0
906 delta, common = [], {}
907
908 for a in old:
909 if a in new:
910 common[a] = 1
911
912 for name in old:
913 if name not in common:
914 remove += 1
915 down += old[name]
916 delta.append([-old[name], name])
917
918 for name in new:
919 if name not in common:
920 add += 1
921 up += new[name]
922 delta.append([new[name], name])
923
924 for name in common:
925 diff = new.get(name, 0) - old.get(name, 0)
926 if diff > 0:
927 grow, up = grow + 1, up + diff
928 elif diff < 0:
929 shrink, down = shrink + 1, down - diff
930 delta.append([diff, name])
931
932 delta.sort()
933 delta.reverse()
934
935 args = [add, -remove, grow, -shrink, up, -down, up - down]
Tom Rinid5686a62017-05-22 13:48:52 -0400936 if max(args) == 0 and min(args) == 0:
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000937 return
938 args = [self.ColourNum(x) for x in args]
939 indent = ' ' * 15
Simon Glass4653a882014-09-05 19:00:07 -0600940 Print('%s%s: add: %s/%s, grow: %s/%s bytes: %s/%s (%s)' %
941 tuple([indent, self.col.Color(self.col.YELLOW, fname)] + args))
942 Print('%s %-38s %7s %7s %+7s' % (indent, 'function', 'old', 'new',
943 'delta'))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000944 for diff, name in delta:
945 if diff:
946 color = self.col.RED if diff > 0 else self.col.GREEN
947 msg = '%s %-38s %7s %7s %+7d' % (indent, name,
948 old.get(name, '-'), new.get(name,'-'), diff)
Simon Glass4653a882014-09-05 19:00:07 -0600949 Print(msg, colour=color)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000950
951
952 def PrintSizeDetail(self, target_list, show_bloat):
953 """Show details size information for each board
954
955 Args:
956 target_list: List of targets, each a dict containing:
957 'target': Target name
958 'total_diff': Total difference in bytes across all areas
959 <part_name>: Difference for that part
960 show_bloat: Show detail for each function
961 """
962 targets_by_diff = sorted(target_list, reverse=True,
963 key=lambda x: x['_total_diff'])
964 for result in targets_by_diff:
965 printed_target = False
966 for name in sorted(result):
967 diff = result[name]
968 if name.startswith('_'):
969 continue
970 if diff != 0:
971 color = self.col.RED if diff > 0 else self.col.GREEN
972 msg = ' %s %+d' % (name, diff)
973 if not printed_target:
Simon Glass4653a882014-09-05 19:00:07 -0600974 Print('%10s %-15s:' % ('', result['_target']),
975 newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000976 printed_target = True
Simon Glass4653a882014-09-05 19:00:07 -0600977 Print(msg, colour=color, newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000978 if printed_target:
Simon Glass4653a882014-09-05 19:00:07 -0600979 Print()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000980 if show_bloat:
981 target = result['_target']
982 outcome = result['_outcome']
983 base_outcome = self._base_board_dict[target]
984 for fname in outcome.func_sizes:
985 self.PrintFuncSizeDetail(fname,
986 base_outcome.func_sizes[fname],
987 outcome.func_sizes[fname])
988
989
990 def PrintSizeSummary(self, board_selected, board_dict, show_detail,
991 show_bloat):
992 """Print a summary of image sizes broken down by section.
993
994 The summary takes the form of one line per architecture. The
995 line contains deltas for each of the sections (+ means the section
Flavio Suligoi9de5c392020-01-29 09:56:05 +0100996 got bigger, - means smaller). The numbers are the average number
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000997 of bytes that a board in this section increased by.
998
999 For example:
1000 powerpc: (622 boards) text -0.0
1001 arm: (285 boards) text -0.0
1002 nds32: (3 boards) text -8.0
1003
1004 Args:
1005 board_selected: Dict containing boards to summarise, keyed by
1006 board.target
1007 board_dict: Dict containing boards for which we built this
1008 commit, keyed by board.target. The value is an Outcome object.
Simon Glassf9c094b2020-03-18 09:42:43 -06001009 show_detail: Show size delta detail for each board
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001010 show_bloat: Show detail for each function
1011 """
1012 arch_list = {}
1013 arch_count = {}
1014
1015 # Calculate changes in size for different image parts
1016 # The previous sizes are in Board.sizes, for each board
1017 for target in board_dict:
1018 if target not in board_selected:
1019 continue
1020 base_sizes = self._base_board_dict[target].sizes
1021 outcome = board_dict[target]
1022 sizes = outcome.sizes
1023
1024 # Loop through the list of images, creating a dict of size
1025 # changes for each image/part. We end up with something like
1026 # {'target' : 'snapper9g45, 'data' : 5, 'u-boot-spl:text' : -4}
1027 # which means that U-Boot data increased by 5 bytes and SPL
1028 # text decreased by 4.
1029 err = {'_target' : target}
1030 for image in sizes:
1031 if image in base_sizes:
1032 base_image = base_sizes[image]
1033 # Loop through the text, data, bss parts
1034 for part in sorted(sizes[image]):
1035 diff = sizes[image][part] - base_image[part]
1036 col = None
1037 if diff:
1038 if image == 'u-boot':
1039 name = part
1040 else:
1041 name = image + ':' + part
1042 err[name] = diff
1043 arch = board_selected[target].arch
1044 if not arch in arch_count:
1045 arch_count[arch] = 1
1046 else:
1047 arch_count[arch] += 1
1048 if not sizes:
1049 pass # Only add to our list when we have some stats
1050 elif not arch in arch_list:
1051 arch_list[arch] = [err]
1052 else:
1053 arch_list[arch].append(err)
1054
1055 # We now have a list of image size changes sorted by arch
1056 # Print out a summary of these
Simon Glassc05aa032019-10-31 07:42:53 -06001057 for arch, target_list in arch_list.items():
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001058 # Get total difference for each type
1059 totals = {}
1060 for result in target_list:
1061 total = 0
Simon Glassc05aa032019-10-31 07:42:53 -06001062 for name, diff in result.items():
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001063 if name.startswith('_'):
1064 continue
1065 total += diff
1066 if name in totals:
1067 totals[name] += diff
1068 else:
1069 totals[name] = diff
1070 result['_total_diff'] = total
1071 result['_outcome'] = board_dict[result['_target']]
1072
1073 count = len(target_list)
1074 printed_arch = False
1075 for name in sorted(totals):
1076 diff = totals[name]
1077 if diff:
1078 # Display the average difference in this name for this
1079 # architecture
1080 avg_diff = float(diff) / count
1081 color = self.col.RED if avg_diff > 0 else self.col.GREEN
1082 msg = ' %s %+1.1f' % (name, avg_diff)
1083 if not printed_arch:
Simon Glass4653a882014-09-05 19:00:07 -06001084 Print('%10s: (for %d/%d boards)' % (arch, count,
1085 arch_count[arch]), newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001086 printed_arch = True
Simon Glass4653a882014-09-05 19:00:07 -06001087 Print(msg, colour=color, newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001088
1089 if printed_arch:
Simon Glass4653a882014-09-05 19:00:07 -06001090 Print()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001091 if show_detail:
1092 self.PrintSizeDetail(target_list, show_bloat)
1093
1094
1095 def PrintResultSummary(self, board_selected, board_dict, err_lines,
Simon Glasse30965d2014-08-28 09:43:44 -06001096 err_line_boards, warn_lines, warn_line_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001097 config, environment, show_sizes, show_detail,
1098 show_bloat, show_config, show_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001099 """Compare results with the base results and display delta.
1100
1101 Only boards mentioned in board_selected will be considered. This
1102 function is intended to be called repeatedly with the results of
1103 each commit. It therefore shows a 'diff' between what it saw in
1104 the last call and what it sees now.
1105
1106 Args:
1107 board_selected: Dict containing boards to summarise, keyed by
1108 board.target
1109 board_dict: Dict containing boards for which we built this
1110 commit, keyed by board.target. The value is an Outcome object.
1111 err_lines: A list of errors for this commit, or [] if there is
1112 none, or we don't want to print errors
Simon Glassed966652014-08-28 09:43:43 -06001113 err_line_boards: Dict keyed by error line, containing a list of
1114 the Board objects with that error
Simon Glasse30965d2014-08-28 09:43:44 -06001115 warn_lines: A list of warnings for this commit, or [] if there is
1116 none, or we don't want to print errors
1117 warn_line_boards: Dict keyed by warning line, containing a list of
1118 the Board objects with that warning
Simon Glass843312d2015-02-05 22:06:15 -07001119 config: Dictionary keyed by filename - e.g. '.config'. Each
1120 value is itself a dictionary:
1121 key: config name
1122 value: config value
Alex Kiernan48ae4122018-05-31 04:48:34 +00001123 environment: Dictionary keyed by environment variable, Each
1124 value is the value of environment variable.
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001125 show_sizes: Show image size deltas
Simon Glassf9c094b2020-03-18 09:42:43 -06001126 show_detail: Show size delta detail for each board if show_sizes
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001127 show_bloat: Show detail for each function
Simon Glass843312d2015-02-05 22:06:15 -07001128 show_config: Show config changes
Alex Kiernan48ae4122018-05-31 04:48:34 +00001129 show_environment: Show environment changes
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001130 """
Simon Glasse30965d2014-08-28 09:43:44 -06001131 def _BoardList(line, line_boards):
Simon Glassed966652014-08-28 09:43:43 -06001132 """Helper function to get a line of boards containing a line
1133
1134 Args:
1135 line: Error line to search for
Simon Glass35d696d2020-04-09 15:08:36 -06001136 line_boards: boards to search, each a Board
Simon Glassed966652014-08-28 09:43:43 -06001137 Return:
Simon Glass35d696d2020-04-09 15:08:36 -06001138 List of boards with that error line, or [] if the user has not
1139 requested such a list
Simon Glassed966652014-08-28 09:43:43 -06001140 """
Simon Glass35d696d2020-04-09 15:08:36 -06001141 boards = []
1142 board_set = set()
Simon Glassed966652014-08-28 09:43:43 -06001143 if self._list_error_boards:
Simon Glasse30965d2014-08-28 09:43:44 -06001144 for board in line_boards[line]:
Simon Glass35d696d2020-04-09 15:08:36 -06001145 if not board in board_set:
1146 boards.append(board)
1147 board_set.add(board)
1148 return boards
Simon Glassed966652014-08-28 09:43:43 -06001149
Simon Glasse30965d2014-08-28 09:43:44 -06001150 def _CalcErrorDelta(base_lines, base_line_boards, lines, line_boards,
1151 char):
Simon Glass35d696d2020-04-09 15:08:36 -06001152 """Calculate the required output based on changes in errors
1153
1154 Args:
1155 base_lines: List of errors/warnings for previous commit
1156 base_line_boards: Dict keyed by error line, containing a list
1157 of the Board objects with that error in the previous commit
1158 lines: List of errors/warning for this commit, each a str
1159 line_boards: Dict keyed by error line, containing a list
1160 of the Board objects with that error in this commit
1161 char: Character representing error ('') or warning ('w'). The
1162 broken ('+') or fixed ('-') characters are added in this
1163 function
1164
1165 Returns:
1166 Tuple
1167 List of ErrLine objects for 'better' lines
1168 List of ErrLine objects for 'worse' lines
1169 """
Simon Glasse30965d2014-08-28 09:43:44 -06001170 better_lines = []
1171 worse_lines = []
1172 for line in lines:
1173 if line not in base_lines:
Simon Glass35d696d2020-04-09 15:08:36 -06001174 errline = ErrLine(char + '+', _BoardList(line, line_boards),
1175 line)
1176 worse_lines.append(errline)
Simon Glasse30965d2014-08-28 09:43:44 -06001177 for line in base_lines:
1178 if line not in lines:
Simon Glass35d696d2020-04-09 15:08:36 -06001179 errline = ErrLine(char + '-',
1180 _BoardList(line, base_line_boards), line)
1181 better_lines.append(errline)
Simon Glasse30965d2014-08-28 09:43:44 -06001182 return better_lines, worse_lines
1183
Simon Glass843312d2015-02-05 22:06:15 -07001184 def _CalcConfig(delta, name, config):
1185 """Calculate configuration changes
1186
1187 Args:
1188 delta: Type of the delta, e.g. '+'
1189 name: name of the file which changed (e.g. .config)
1190 config: configuration change dictionary
1191 key: config name
1192 value: config value
1193 Returns:
1194 String containing the configuration changes which can be
1195 printed
1196 """
1197 out = ''
1198 for key in sorted(config.keys()):
1199 out += '%s=%s ' % (key, config[key])
Simon Glass8270e3c2015-08-25 21:52:14 -06001200 return '%s %s: %s' % (delta, name, out)
Simon Glass843312d2015-02-05 22:06:15 -07001201
Simon Glass8270e3c2015-08-25 21:52:14 -06001202 def _AddConfig(lines, name, config_plus, config_minus, config_change):
1203 """Add changes in configuration to a list
Simon Glass843312d2015-02-05 22:06:15 -07001204
1205 Args:
Simon Glass8270e3c2015-08-25 21:52:14 -06001206 lines: list to add to
1207 name: config file name
Simon Glass843312d2015-02-05 22:06:15 -07001208 config_plus: configurations added, dictionary
1209 key: config name
1210 value: config value
1211 config_minus: configurations removed, dictionary
1212 key: config name
1213 value: config value
1214 config_change: configurations changed, dictionary
1215 key: config name
1216 value: config value
1217 """
1218 if config_plus:
Simon Glass8270e3c2015-08-25 21:52:14 -06001219 lines.append(_CalcConfig('+', name, config_plus))
Simon Glass843312d2015-02-05 22:06:15 -07001220 if config_minus:
Simon Glass8270e3c2015-08-25 21:52:14 -06001221 lines.append(_CalcConfig('-', name, config_minus))
Simon Glass843312d2015-02-05 22:06:15 -07001222 if config_change:
Simon Glass8270e3c2015-08-25 21:52:14 -06001223 lines.append(_CalcConfig('c', name, config_change))
1224
1225 def _OutputConfigInfo(lines):
1226 for line in lines:
1227 if not line:
1228 continue
1229 if line[0] == '+':
1230 col = self.col.GREEN
1231 elif line[0] == '-':
1232 col = self.col.RED
1233 elif line[0] == 'c':
1234 col = self.col.YELLOW
1235 Print(' ' + line, newline=True, colour=col)
1236
Simon Glassb206d872020-04-09 15:08:28 -06001237 def _OutputErrLines(err_lines, colour):
1238 """Output the line of error/warning lines, if not empty
1239
1240 Also increments self._error_lines if err_lines not empty
1241
1242 Args:
Simon Glass35d696d2020-04-09 15:08:36 -06001243 err_lines: List of ErrLine objects, each an error or warning
1244 line, possibly including a list of boards with that
1245 error/warning
Simon Glassb206d872020-04-09 15:08:28 -06001246 colour: Colour to use for output
1247 """
1248 if err_lines:
Simon Glass8c9a2672020-04-09 15:08:37 -06001249 out_list = []
Simon Glass35d696d2020-04-09 15:08:36 -06001250 for line in err_lines:
1251 boards = ''
1252 names = [board.target for board in line.boards]
Simon Glass9ef0ceb2020-04-09 15:08:38 -06001253 board_str = ' '.join(names) if names else ''
Simon Glass8c9a2672020-04-09 15:08:37 -06001254 if board_str:
1255 out = self.col.Color(colour, line.char + '(')
1256 out += self.col.Color(self.col.MAGENTA, board_str,
1257 bright=False)
1258 out += self.col.Color(colour, ') %s' % line.errline)
1259 else:
1260 out = self.col.Color(colour, line.char + line.errline)
1261 out_list.append(out)
1262 Print('\n'.join(out_list))
Simon Glassb206d872020-04-09 15:08:28 -06001263 self._error_lines += 1
1264
Simon Glass843312d2015-02-05 22:06:15 -07001265
Simon Glass4cf2b222018-11-06 16:02:12 -07001266 ok_boards = [] # List of boards fixed since last commit
Simon Glass6af71012018-11-06 16:02:13 -07001267 warn_boards = [] # List of boards with warnings since last commit
Simon Glass4cf2b222018-11-06 16:02:12 -07001268 err_boards = [] # List of new broken boards since last commit
1269 new_boards = [] # List of boards that didn't exist last time
1270 unknown_boards = [] # List of boards that were not built
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001271
1272 for target in board_dict:
1273 if target not in board_selected:
1274 continue
1275
1276 # If the board was built last time, add its outcome to a list
1277 if target in self._base_board_dict:
1278 base_outcome = self._base_board_dict[target].rc
1279 outcome = board_dict[target]
1280 if outcome.rc == OUTCOME_UNKNOWN:
Simon Glass4cf2b222018-11-06 16:02:12 -07001281 unknown_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001282 elif outcome.rc < base_outcome:
Simon Glass6af71012018-11-06 16:02:13 -07001283 if outcome.rc == OUTCOME_WARNING:
1284 warn_boards.append(target)
1285 else:
1286 ok_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001287 elif outcome.rc > base_outcome:
Simon Glass6af71012018-11-06 16:02:13 -07001288 if outcome.rc == OUTCOME_WARNING:
1289 warn_boards.append(target)
1290 else:
1291 err_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001292 else:
Simon Glass4cf2b222018-11-06 16:02:12 -07001293 new_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001294
Simon Glassb206d872020-04-09 15:08:28 -06001295 # Get a list of errors and warnings that have appeared, and disappeared
Simon Glasse30965d2014-08-28 09:43:44 -06001296 better_err, worse_err = _CalcErrorDelta(self._base_err_lines,
1297 self._base_err_line_boards, err_lines, err_line_boards, '')
1298 better_warn, worse_warn = _CalcErrorDelta(self._base_warn_lines,
1299 self._base_warn_line_boards, warn_lines, warn_line_boards, 'w')
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001300
1301 # Display results by arch
Simon Glass6af71012018-11-06 16:02:13 -07001302 if any((ok_boards, warn_boards, err_boards, unknown_boards, new_boards,
1303 worse_err, better_err, worse_warn, better_warn)):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001304 arch_list = {}
Simon Glass4cf2b222018-11-06 16:02:12 -07001305 self.AddOutcome(board_selected, arch_list, ok_boards, '',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001306 self.col.GREEN)
Simon Glass6af71012018-11-06 16:02:13 -07001307 self.AddOutcome(board_selected, arch_list, warn_boards, 'w+',
1308 self.col.YELLOW)
Simon Glass4cf2b222018-11-06 16:02:12 -07001309 self.AddOutcome(board_selected, arch_list, err_boards, '+',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001310 self.col.RED)
Simon Glass4cf2b222018-11-06 16:02:12 -07001311 self.AddOutcome(board_selected, arch_list, new_boards, '*', self.col.BLUE)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001312 if self._show_unknown:
Simon Glass4cf2b222018-11-06 16:02:12 -07001313 self.AddOutcome(board_selected, arch_list, unknown_boards, '?',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001314 self.col.MAGENTA)
Simon Glassc05aa032019-10-31 07:42:53 -06001315 for arch, target_list in arch_list.items():
Simon Glass4653a882014-09-05 19:00:07 -06001316 Print('%10s: %s' % (arch, target_list))
Simon Glass28370c12014-08-09 15:33:06 -06001317 self._error_lines += 1
Simon Glassb206d872020-04-09 15:08:28 -06001318 _OutputErrLines(better_err, colour=self.col.GREEN)
1319 _OutputErrLines(worse_err, colour=self.col.RED)
1320 _OutputErrLines(better_warn, colour=self.col.CYAN)
Simon Glass5627bd92020-04-09 15:08:35 -06001321 _OutputErrLines(worse_warn, colour=self.col.YELLOW)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001322
1323 if show_sizes:
1324 self.PrintSizeSummary(board_selected, board_dict, show_detail,
1325 show_bloat)
1326
Alex Kiernan48ae4122018-05-31 04:48:34 +00001327 if show_environment and self._base_environment:
1328 lines = []
1329
1330 for target in board_dict:
1331 if target not in board_selected:
1332 continue
1333
1334 tbase = self._base_environment[target]
1335 tenvironment = environment[target]
1336 environment_plus = {}
1337 environment_minus = {}
1338 environment_change = {}
1339 base = tbase.environment
Simon Glassc05aa032019-10-31 07:42:53 -06001340 for key, value in tenvironment.environment.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001341 if key not in base:
1342 environment_plus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001343 for key, value in base.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001344 if key not in tenvironment.environment:
1345 environment_minus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001346 for key, value in base.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001347 new_value = tenvironment.environment.get(key)
1348 if new_value and value != new_value:
1349 desc = '%s -> %s' % (value, new_value)
1350 environment_change[key] = desc
1351
1352 _AddConfig(lines, target, environment_plus, environment_minus,
1353 environment_change)
1354
1355 _OutputConfigInfo(lines)
1356
Simon Glass8270e3c2015-08-25 21:52:14 -06001357 if show_config and self._base_config:
1358 summary = {}
1359 arch_config_plus = {}
1360 arch_config_minus = {}
1361 arch_config_change = {}
1362 arch_list = []
1363
1364 for target in board_dict:
1365 if target not in board_selected:
Simon Glass843312d2015-02-05 22:06:15 -07001366 continue
Simon Glass8270e3c2015-08-25 21:52:14 -06001367 arch = board_selected[target].arch
1368 if arch not in arch_list:
1369 arch_list.append(arch)
1370
1371 for arch in arch_list:
1372 arch_config_plus[arch] = {}
1373 arch_config_minus[arch] = {}
1374 arch_config_change[arch] = {}
Simon Glassb464f8e2016-11-13 14:25:53 -07001375 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001376 arch_config_plus[arch][name] = {}
1377 arch_config_minus[arch][name] = {}
1378 arch_config_change[arch][name] = {}
1379
1380 for target in board_dict:
1381 if target not in board_selected:
1382 continue
1383
1384 arch = board_selected[target].arch
1385
1386 all_config_plus = {}
1387 all_config_minus = {}
1388 all_config_change = {}
1389 tbase = self._base_config[target]
1390 tconfig = config[target]
1391 lines = []
Simon Glassb464f8e2016-11-13 14:25:53 -07001392 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001393 if not tconfig.config[name]:
1394 continue
1395 config_plus = {}
1396 config_minus = {}
1397 config_change = {}
1398 base = tbase.config[name]
Simon Glassc05aa032019-10-31 07:42:53 -06001399 for key, value in tconfig.config[name].items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001400 if key not in base:
1401 config_plus[key] = value
1402 all_config_plus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001403 for key, value in base.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001404 if key not in tconfig.config[name]:
1405 config_minus[key] = value
1406 all_config_minus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001407 for key, value in base.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001408 new_value = tconfig.config.get(key)
1409 if new_value and value != new_value:
1410 desc = '%s -> %s' % (value, new_value)
1411 config_change[key] = desc
1412 all_config_change[key] = desc
1413
1414 arch_config_plus[arch][name].update(config_plus)
1415 arch_config_minus[arch][name].update(config_minus)
1416 arch_config_change[arch][name].update(config_change)
1417
1418 _AddConfig(lines, name, config_plus, config_minus,
1419 config_change)
1420 _AddConfig(lines, 'all', all_config_plus, all_config_minus,
1421 all_config_change)
1422 summary[target] = '\n'.join(lines)
1423
1424 lines_by_target = {}
Simon Glassc05aa032019-10-31 07:42:53 -06001425 for target, lines in summary.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001426 if lines in lines_by_target:
1427 lines_by_target[lines].append(target)
1428 else:
1429 lines_by_target[lines] = [target]
1430
1431 for arch in arch_list:
1432 lines = []
1433 all_plus = {}
1434 all_minus = {}
1435 all_change = {}
Simon Glassb464f8e2016-11-13 14:25:53 -07001436 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001437 all_plus.update(arch_config_plus[arch][name])
1438 all_minus.update(arch_config_minus[arch][name])
1439 all_change.update(arch_config_change[arch][name])
1440 _AddConfig(lines, name, arch_config_plus[arch][name],
1441 arch_config_minus[arch][name],
1442 arch_config_change[arch][name])
1443 _AddConfig(lines, 'all', all_plus, all_minus, all_change)
1444 #arch_summary[target] = '\n'.join(lines)
1445 if lines:
1446 Print('%s:' % arch)
1447 _OutputConfigInfo(lines)
1448
Simon Glassc05aa032019-10-31 07:42:53 -06001449 for lines, targets in lines_by_target.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001450 if not lines:
1451 continue
1452 Print('%s :' % ' '.join(sorted(targets)))
1453 _OutputConfigInfo(lines.split('\n'))
1454
Simon Glass843312d2015-02-05 22:06:15 -07001455
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001456 # Save our updated information for the next call to this function
1457 self._base_board_dict = board_dict
1458 self._base_err_lines = err_lines
Simon Glasse30965d2014-08-28 09:43:44 -06001459 self._base_warn_lines = warn_lines
1460 self._base_err_line_boards = err_line_boards
1461 self._base_warn_line_boards = warn_line_boards
Simon Glass843312d2015-02-05 22:06:15 -07001462 self._base_config = config
Alex Kiernan48ae4122018-05-31 04:48:34 +00001463 self._base_environment = environment
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001464
1465 # Get a list of boards that did not get built, if needed
1466 not_built = []
1467 for board in board_selected:
1468 if not board in board_dict:
1469 not_built.append(board)
1470 if not_built:
Simon Glass4653a882014-09-05 19:00:07 -06001471 Print("Boards not built (%d): %s" % (len(not_built),
1472 ', '.join(not_built)))
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001473
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001474 def ProduceResultSummary(self, commit_upto, commits, board_selected):
Simon Glasse30965d2014-08-28 09:43:44 -06001475 (board_dict, err_lines, err_line_boards, warn_lines,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001476 warn_line_boards, config, environment) = self.GetResultSummary(
Simon Glassed966652014-08-28 09:43:43 -06001477 board_selected, commit_upto,
Simon Glass843312d2015-02-05 22:06:15 -07001478 read_func_sizes=self._show_bloat,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001479 read_config=self._show_config,
1480 read_environment=self._show_environment)
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001481 if commits:
1482 msg = '%02d: %s' % (commit_upto + 1,
1483 commits[commit_upto].subject)
Simon Glass4653a882014-09-05 19:00:07 -06001484 Print(msg, colour=self.col.BLUE)
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001485 self.PrintResultSummary(board_selected, board_dict,
Simon Glassed966652014-08-28 09:43:43 -06001486 err_lines if self._show_errors else [], err_line_boards,
Simon Glasse30965d2014-08-28 09:43:44 -06001487 warn_lines if self._show_errors else [], warn_line_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001488 config, environment, self._show_sizes, self._show_detail,
1489 self._show_bloat, self._show_config, self._show_environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001490
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001491 def ShowSummary(self, commits, board_selected):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001492 """Show a build summary for U-Boot for a given board list.
1493
1494 Reset the result summary, then repeatedly call GetResultSummary on
1495 each commit's results, then display the differences we see.
1496
1497 Args:
1498 commit: Commit objects to summarise
1499 board_selected: Dict containing boards to summarise
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001500 """
Simon Glassfea58582014-08-09 15:32:59 -06001501 self.commit_count = len(commits) if commits else 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001502 self.commits = commits
1503 self.ResetResultSummary(board_selected)
Simon Glass28370c12014-08-09 15:33:06 -06001504 self._error_lines = 0
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001505
1506 for commit_upto in range(0, self.commit_count, self._step):
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001507 self.ProduceResultSummary(commit_upto, commits, board_selected)
Simon Glass28370c12014-08-09 15:33:06 -06001508 if not self._error_lines:
Simon Glass4653a882014-09-05 19:00:07 -06001509 Print('(no errors to report)', colour=self.col.GREEN)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001510
1511
1512 def SetupBuild(self, board_selected, commits):
1513 """Set up ready to start a build.
1514
1515 Args:
1516 board_selected: Selected boards to build
1517 commits: Selected commits to build
1518 """
1519 # First work out how many commits we will build
Simon Glassc05aa032019-10-31 07:42:53 -06001520 count = (self.commit_count + self._step - 1) // self._step
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001521 self.count = len(board_selected) * count
1522 self.upto = self.warned = self.fail = 0
1523 self._timestamps = collections.deque()
1524
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001525 def GetThreadDir(self, thread_num):
1526 """Get the directory path to the working dir for a thread.
1527
1528 Args:
1529 thread_num: Number of thread to check.
1530 """
Simon Glassd829f122020-03-18 09:42:42 -06001531 if self.work_in_output:
1532 return self._working_dir
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001533 return os.path.join(self._working_dir, '%02d' % thread_num)
1534
Simon Glassfea58582014-08-09 15:32:59 -06001535 def _PrepareThread(self, thread_num, setup_git):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001536 """Prepare the working directory for a thread.
1537
1538 This clones or fetches the repo into the thread's work directory.
1539
1540 Args:
1541 thread_num: Thread number (0, 1, ...)
Simon Glassfea58582014-08-09 15:32:59 -06001542 setup_git: True to set up a git repo clone
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001543 """
1544 thread_dir = self.GetThreadDir(thread_num)
Simon Glass190064b2014-08-09 15:33:00 -06001545 builderthread.Mkdir(thread_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001546 git_dir = os.path.join(thread_dir, '.git')
1547
1548 # Clone the repo if it doesn't already exist
1549 # TODO(sjg@chromium): Perhaps some git hackery to symlink instead, so
1550 # we have a private index but uses the origin repo's contents?
Simon Glassfea58582014-08-09 15:32:59 -06001551 if setup_git and self.git_dir:
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001552 src_dir = os.path.abspath(self.git_dir)
1553 if os.path.exists(git_dir):
Simon Glass212c0b82020-04-09 15:08:43 -06001554 Print('\rFetching repo for thread %d' % thread_num,
1555 newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001556 gitutil.Fetch(git_dir, thread_dir)
Simon Glass212c0b82020-04-09 15:08:43 -06001557 terminal.PrintClear()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001558 else:
Simon Glass21f0eb32016-09-18 16:48:31 -06001559 Print('\rCloning repo for thread %d' % thread_num,
1560 newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001561 gitutil.Clone(src_dir, thread_dir)
Simon Glass102969b2020-04-09 15:08:42 -06001562 terminal.PrintClear()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001563
Simon Glassfea58582014-08-09 15:32:59 -06001564 def _PrepareWorkingSpace(self, max_threads, setup_git):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001565 """Prepare the working directory for use.
1566
1567 Set up the git repo for each thread.
1568
1569 Args:
1570 max_threads: Maximum number of threads we expect to need.
Simon Glassfea58582014-08-09 15:32:59 -06001571 setup_git: True to set up a git repo clone
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001572 """
Simon Glass190064b2014-08-09 15:33:00 -06001573 builderthread.Mkdir(self._working_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001574 for thread in range(max_threads):
Simon Glassfea58582014-08-09 15:32:59 -06001575 self._PrepareThread(thread, setup_git)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001576
Simon Glass925f6ad2020-03-18 09:42:45 -06001577 def _GetOutputSpaceRemovals(self):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001578 """Get the output directories ready to receive files.
1579
Simon Glass925f6ad2020-03-18 09:42:45 -06001580 Figure out what needs to be deleted in the output directory before it
1581 can be used. We only delete old buildman directories which have the
1582 expected name pattern. See _GetOutputDir().
1583
1584 Returns:
1585 List of full paths of directories to remove
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001586 """
Simon Glass1a915672014-12-01 17:33:53 -07001587 if not self.commits:
1588 return
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001589 dir_list = []
1590 for commit_upto in range(self.commit_count):
1591 dir_list.append(self._GetOutputDir(commit_upto))
1592
Simon Glassb222abe2016-09-18 16:48:32 -06001593 to_remove = []
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001594 for dirname in glob.glob(os.path.join(self.base_dir, '*')):
1595 if dirname not in dir_list:
Simon Glass925f6ad2020-03-18 09:42:45 -06001596 leaf = dirname[len(self.base_dir) + 1:]
1597 m = re.match('[0-9]+_of_[0-9]+_g[0-9a-f]+_.*', leaf)
1598 if m:
1599 to_remove.append(dirname)
1600 return to_remove
1601
1602 def _PrepareOutputSpace(self):
1603 """Get the output directories ready to receive files.
1604
1605 We delete any output directories which look like ones we need to
1606 create. Having left over directories is confusing when the user wants
1607 to check the output manually.
1608 """
1609 to_remove = self._GetOutputSpaceRemovals()
Simon Glassb222abe2016-09-18 16:48:32 -06001610 if to_remove:
Simon Glassb2d89bc2020-03-18 09:42:46 -06001611 Print('Removing %d old build directories...' % len(to_remove),
Simon Glassb222abe2016-09-18 16:48:32 -06001612 newline=False)
1613 for dirname in to_remove:
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001614 shutil.rmtree(dirname)
Simon Glass212c0b82020-04-09 15:08:43 -06001615 terminal.PrintClear()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001616
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001617 def BuildBoards(self, commits, board_selected, keep_outputs, verbose):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001618 """Build all commits for a list of boards
1619
1620 Args:
1621 commits: List of commits to be build, each a Commit object
1622 boards_selected: Dict of selected boards, key is target name,
1623 value is Board object
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001624 keep_outputs: True to save build output files
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001625 verbose: Display build results as they are completed
Simon Glass2c3deb92014-08-28 09:43:39 -06001626 Returns:
1627 Tuple containing:
1628 - number of boards that failed to build
1629 - number of boards that issued warnings
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001630 """
Simon Glassfea58582014-08-09 15:32:59 -06001631 self.commit_count = len(commits) if commits else 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001632 self.commits = commits
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001633 self._verbose = verbose
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001634
1635 self.ResetResultSummary(board_selected)
Thierry Redingf3d015c2014-08-19 10:22:39 +02001636 builderthread.Mkdir(self.base_dir, parents = True)
Simon Glassfea58582014-08-09 15:32:59 -06001637 self._PrepareWorkingSpace(min(self.num_threads, len(board_selected)),
1638 commits is not None)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001639 self._PrepareOutputSpace()
Simon Glass745b3952016-09-18 16:48:33 -06001640 Print('\rStarting build...', newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001641 self.SetupBuild(board_selected, commits)
1642 self.ProcessResult(None)
1643
1644 # Create jobs to build all commits for each board
Simon Glassc05aa032019-10-31 07:42:53 -06001645 for brd in board_selected.values():
Simon Glass190064b2014-08-09 15:33:00 -06001646 job = builderthread.BuilderJob()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001647 job.board = brd
1648 job.commits = commits
1649 job.keep_outputs = keep_outputs
Simon Glassd829f122020-03-18 09:42:42 -06001650 job.work_in_output = self.work_in_output
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001651 job.step = self._step
1652 self.queue.put(job)
1653
Simon Glassd436e382016-09-18 16:48:35 -06001654 term = threading.Thread(target=self.queue.join)
1655 term.setDaemon(True)
1656 term.start()
1657 while term.isAlive():
1658 term.join(100)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001659
1660 # Wait until we have processed all output
1661 self.out_queue.join()
Simon Glass4653a882014-09-05 19:00:07 -06001662 Print()
Simon Glass7b33f212020-04-09 15:08:47 -06001663
1664 msg = 'Completed: %d total built' % self.count
1665 if self.already_done:
1666 msg += ' (%d previously' % self.already_done
1667 if self.already_done != self.count:
1668 msg += ', %d newly' % (self.count - self.already_done)
1669 msg += ')'
1670 duration = datetime.now() - self._start_time
1671 if duration > timedelta(microseconds=1000000):
1672 if duration.microseconds >= 500000:
1673 duration = duration + timedelta(seconds=1)
1674 duration = duration - timedelta(microseconds=duration.microseconds)
1675 msg += ', duration %s' % duration
1676 Print(msg)
1677
Simon Glass2c3deb92014-08-28 09:43:39 -06001678 return (self.fail, self.warned)