blob: 6819e5b40da885718d922d40045c948e3d61a983 [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 Glassb50113f2016-11-13 14:25:51 -0700234 incremental=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'
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600255 incremental: Always perform incremental builds; don't run make
256 mrproper when configuring
257 per_board_out_dir: Build in a separate persistent directory per
258 board rather than a thread-specific directory
Simon Glassb50113f2016-11-13 14:25:51 -0700259 config_only: Only configure each build, don't build it
Simon Glassb464f8e2016-11-13 14:25:53 -0700260 squash_config_y: Convert CONFIG options with the value 'y' to '1'
Daniel Schwierzeck2371d1b2018-01-26 16:31:05 +0100261 warnings_as_errors: Treat all compiler warnings as errors
Simon Glassd829f122020-03-18 09:42:42 -0600262 work_in_output: Use the output directory as the work directory and
263 don't write to a separate output directory.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000264 """
265 self.toolchains = toolchains
266 self.base_dir = base_dir
Simon Glassd829f122020-03-18 09:42:42 -0600267 if work_in_output:
268 self._working_dir = base_dir
269 else:
270 self._working_dir = os.path.join(base_dir, '.bm-work')
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000271 self.threads = []
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000272 self.do_make = self.Make
Masahiro Yamada99796922014-07-22 11:19:09 +0900273 self.gnu_make = gnu_make
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000274 self.checkout = checkout
275 self.num_threads = num_threads
276 self.num_jobs = num_jobs
277 self.already_done = 0
278 self.force_build = False
279 self.git_dir = git_dir
280 self._show_unknown = show_unknown
281 self._timestamp_count = 10
282 self._build_period_us = None
283 self._complete_delay = None
284 self._next_delay_update = datetime.now()
Simon Glass7b33f212020-04-09 15:08:47 -0600285 self._start_time = datetime.now()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000286 self.force_config_on_failure = True
Simon Glass4266dc22014-07-13 12:22:31 -0600287 self.force_build_failures = False
Simon Glass97e91522014-07-14 17:51:02 -0600288 self.force_reconfig = False
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000289 self._step = step
Simon Glass189a4962014-07-14 17:51:03 -0600290 self.in_tree = False
Simon Glass28370c12014-08-09 15:33:06 -0600291 self._error_lines = 0
Simon Glass5971ab52014-12-01 17:33:55 -0700292 self.no_subdirs = no_subdirs
Simon Glassbb1501f2014-12-01 17:34:00 -0700293 self.full_path = full_path
Simon Glassd2ce6582014-12-01 17:34:07 -0700294 self.verbose_build = verbose_build
Simon Glassb50113f2016-11-13 14:25:51 -0700295 self.config_only = config_only
Simon Glassb464f8e2016-11-13 14:25:53 -0700296 self.squash_config_y = squash_config_y
297 self.config_filenames = BASE_CONFIG_FILENAMES
Simon Glassd829f122020-03-18 09:42:42 -0600298 self.work_in_output = work_in_output
Simon Glassb464f8e2016-11-13 14:25:53 -0700299 if not self.squash_config_y:
300 self.config_filenames += EXTRA_CONFIG_FILENAMES
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000301
Daniel Schwierzeck2371d1b2018-01-26 16:31:05 +0100302 self.warnings_as_errors = warnings_as_errors
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000303 self.col = terminal.Color()
304
Simon Glasse30965d2014-08-28 09:43:44 -0600305 self._re_function = re.compile('(.*): In function.*')
306 self._re_files = re.compile('In file included from.*')
307 self._re_warning = re.compile('(.*):(\d*):(\d*): warning: .*')
Simon Glass2d483332018-11-06 16:02:11 -0700308 self._re_dtb_warning = re.compile('(.*): Warning .*')
Simon Glasse30965d2014-08-28 09:43:44 -0600309 self._re_note = re.compile('(.*):(\d*):(\d*): note: this is the location of the previous.*')
310
Simon Glassc05aa032019-10-31 07:42:53 -0600311 self.queue = queue.Queue()
312 self.out_queue = queue.Queue()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000313 for i in range(self.num_threads):
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600314 t = builderthread.BuilderThread(self, i, incremental,
315 per_board_out_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000316 t.setDaemon(True)
317 t.start()
318 self.threads.append(t)
319
Simon Glass190064b2014-08-09 15:33:00 -0600320 t = builderthread.ResultThread(self)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000321 t.setDaemon(True)
322 t.start()
323 self.threads.append(t)
324
325 ignore_lines = ['(make.*Waiting for unfinished)', '(Segmentation fault)']
326 self.re_make_err = re.compile('|'.join(ignore_lines))
327
Simon Glass2f256642016-09-18 16:48:37 -0600328 # Handle existing graceful with SIGINT / Ctrl-C
329 signal.signal(signal.SIGINT, self.signal_handler)
330
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000331 def __del__(self):
332 """Get rid of all threads created by the builder"""
333 for t in self.threads:
334 del t
335
Simon Glass2f256642016-09-18 16:48:37 -0600336 def signal_handler(self, signal, frame):
337 sys.exit(1)
338
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600339 def SetDisplayOptions(self, show_errors=False, show_sizes=False,
Simon Glassed966652014-08-28 09:43:43 -0600340 show_detail=False, show_bloat=False,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000341 list_error_boards=False, show_config=False,
342 show_environment=False):
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600343 """Setup display options for the builder.
344
345 show_errors: True to show summarised error/warning info
346 show_sizes: Show size deltas
Simon Glassf9c094b2020-03-18 09:42:43 -0600347 show_detail: Show size delta detail for each board if show_sizes
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600348 show_bloat: Show detail for each function
Simon Glassed966652014-08-28 09:43:43 -0600349 list_error_boards: Show the boards which caused each error/warning
Simon Glass843312d2015-02-05 22:06:15 -0700350 show_config: Show config deltas
Alex Kiernan48ae4122018-05-31 04:48:34 +0000351 show_environment: Show environment deltas
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600352 """
353 self._show_errors = show_errors
354 self._show_sizes = show_sizes
355 self._show_detail = show_detail
356 self._show_bloat = show_bloat
Simon Glassed966652014-08-28 09:43:43 -0600357 self._list_error_boards = list_error_boards
Simon Glass843312d2015-02-05 22:06:15 -0700358 self._show_config = show_config
Alex Kiernan48ae4122018-05-31 04:48:34 +0000359 self._show_environment = show_environment
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600360
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000361 def _AddTimestamp(self):
362 """Add a new timestamp to the list and record the build period.
363
364 The build period is the length of time taken to perform a single
365 build (one board, one commit).
366 """
367 now = datetime.now()
368 self._timestamps.append(now)
369 count = len(self._timestamps)
370 delta = self._timestamps[-1] - self._timestamps[0]
371 seconds = delta.total_seconds()
372
373 # If we have enough data, estimate build period (time taken for a
374 # single build) and therefore completion time.
375 if count > 1 and self._next_delay_update < now:
376 self._next_delay_update = now + timedelta(seconds=2)
377 if seconds > 0:
378 self._build_period = float(seconds) / count
379 todo = self.count - self.upto
380 self._complete_delay = timedelta(microseconds=
381 self._build_period * todo * 1000000)
382 # Round it
383 self._complete_delay -= timedelta(
384 microseconds=self._complete_delay.microseconds)
385
386 if seconds > 60:
387 self._timestamps.popleft()
388 count -= 1
389
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000390 def SelectCommit(self, commit, checkout=True):
391 """Checkout the selected commit for this build
392 """
393 self.commit = commit
394 if checkout and self.checkout:
395 gitutil.Checkout(commit.hash)
396
397 def Make(self, commit, brd, stage, cwd, *args, **kwargs):
398 """Run make
399
400 Args:
401 commit: Commit object that is being built
402 brd: Board object that is being built
Roger Meierfd18a892014-08-20 22:10:29 +0200403 stage: Stage that we are at (mrproper, config, build)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000404 cwd: Directory where make should be run
405 args: Arguments to pass to make
406 kwargs: Arguments to pass to command.RunPipe()
407 """
Masahiro Yamada99796922014-07-22 11:19:09 +0900408 cmd = [self.gnu_make] + list(args)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000409 result = command.RunPipe([cmd], capture=True, capture_stderr=True,
Simon Glasse62a24c2018-09-17 23:55:42 -0600410 cwd=cwd, raise_on_error=False, infile='/dev/null', **kwargs)
Simon Glass40f11fc2015-02-05 22:06:12 -0700411 if self.verbose_build:
412 result.stdout = '%s\n' % (' '.join(cmd)) + result.stdout
413 result.combined = '%s\n' % (' '.join(cmd)) + result.combined
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000414 return result
415
416 def ProcessResult(self, result):
417 """Process the result of a build, showing progress information
418
419 Args:
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600420 result: A CommandResult object, which indicates the result for
421 a single build
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000422 """
423 col = terminal.Color()
424 if result:
425 target = result.brd.target
426
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000427 self.upto += 1
428 if result.return_code != 0:
429 self.fail += 1
430 elif result.stderr:
431 self.warned += 1
432 if result.already_done:
433 self.already_done += 1
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600434 if self._verbose:
Simon Glass102969b2020-04-09 15:08:42 -0600435 terminal.PrintClear()
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600436 boards_selected = {target : result.brd}
437 self.ResetResultSummary(boards_selected)
438 self.ProduceResultSummary(result.commit_upto, self.commits,
439 boards_selected)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000440 else:
441 target = '(starting)'
442
443 # Display separate counts for ok, warned and fail
444 ok = self.upto - self.warned - self.fail
445 line = '\r' + self.col.Color(self.col.GREEN, '%5d' % ok)
446 line += self.col.Color(self.col.YELLOW, '%5d' % self.warned)
447 line += self.col.Color(self.col.RED, '%5d' % self.fail)
448
Simon Glass6eb76ca2020-04-09 15:08:45 -0600449 line += ' /%-5d ' % self.count
450 remaining = self.count - self.upto
451 if remaining:
452 line += self.col.Color(self.col.MAGENTA, ' -%-5d ' % remaining)
453 else:
454 line += ' ' * 8
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000455
456 # Add our current completion time estimate
457 self._AddTimestamp()
458 if self._complete_delay:
Simon Glass6eb76ca2020-04-09 15:08:45 -0600459 line += '%s : ' % self._complete_delay
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000460
Simon Glass6eb76ca2020-04-09 15:08:45 -0600461 line += target
Simon Glass102969b2020-04-09 15:08:42 -0600462 terminal.PrintClear()
Simon Glass95ed0a22020-04-09 15:08:46 -0600463 Print(line, newline=False, limit_to_line=True)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000464
465 def _GetOutputDir(self, commit_upto):
466 """Get the name of the output directory for a commit number
467
468 The output directory is typically .../<branch>/<commit>.
469
470 Args:
471 commit_upto: Commit number to use (0..self.count-1)
472 """
Simon Glass5971ab52014-12-01 17:33:55 -0700473 commit_dir = None
Simon Glassfea58582014-08-09 15:32:59 -0600474 if self.commits:
475 commit = self.commits[commit_upto]
476 subject = commit.subject.translate(trans_valid_chars)
Simon Glass925f6ad2020-03-18 09:42:45 -0600477 # See _GetOutputSpaceRemovals() which parses this name
Simon Glassfea58582014-08-09 15:32:59 -0600478 commit_dir = ('%02d_of_%02d_g%s_%s' % (commit_upto + 1,
479 self.commit_count, commit.hash, subject[:20]))
Simon Glass5971ab52014-12-01 17:33:55 -0700480 elif not self.no_subdirs:
Simon Glassfea58582014-08-09 15:32:59 -0600481 commit_dir = 'current'
Simon Glass5971ab52014-12-01 17:33:55 -0700482 if not commit_dir:
483 return self.base_dir
484 return os.path.join(self.base_dir, commit_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000485
486 def GetBuildDir(self, commit_upto, target):
487 """Get the name of the build directory for a commit number
488
489 The build directory is typically .../<branch>/<commit>/<target>.
490
491 Args:
492 commit_upto: Commit number to use (0..self.count-1)
493 target: Target name
494 """
495 output_dir = self._GetOutputDir(commit_upto)
496 return os.path.join(output_dir, target)
497
498 def GetDoneFile(self, commit_upto, target):
499 """Get the name of the done file for a commit number
500
501 Args:
502 commit_upto: Commit number to use (0..self.count-1)
503 target: Target name
504 """
505 return os.path.join(self.GetBuildDir(commit_upto, target), 'done')
506
507 def GetSizesFile(self, commit_upto, target):
508 """Get the name of the sizes 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), 'sizes')
515
516 def GetFuncSizesFile(self, commit_upto, target, elf_fname):
517 """Get the name of the funcsizes file for a commit number and ELF file
518
519 Args:
520 commit_upto: Commit number to use (0..self.count-1)
521 target: Target name
522 elf_fname: Filename of elf image
523 """
524 return os.path.join(self.GetBuildDir(commit_upto, target),
525 '%s.sizes' % elf_fname.replace('/', '-'))
526
527 def GetObjdumpFile(self, commit_upto, target, elf_fname):
528 """Get the name of the objdump file for a commit number and ELF file
529
530 Args:
531 commit_upto: Commit number to use (0..self.count-1)
532 target: Target name
533 elf_fname: Filename of elf image
534 """
535 return os.path.join(self.GetBuildDir(commit_upto, target),
536 '%s.objdump' % elf_fname.replace('/', '-'))
537
538 def GetErrFile(self, commit_upto, target):
539 """Get the name of the err file for a commit number
540
541 Args:
542 commit_upto: Commit number to use (0..self.count-1)
543 target: Target name
544 """
545 output_dir = self.GetBuildDir(commit_upto, target)
546 return os.path.join(output_dir, 'err')
547
548 def FilterErrors(self, lines):
549 """Filter out errors in which we have no interest
550
551 We should probably use map().
552
553 Args:
554 lines: List of error lines, each a string
555 Returns:
556 New list with only interesting lines included
557 """
558 out_lines = []
559 for line in lines:
560 if not self.re_make_err.search(line):
561 out_lines.append(line)
562 return out_lines
563
564 def ReadFuncSizes(self, fname, fd):
565 """Read function sizes from the output of 'nm'
566
567 Args:
568 fd: File containing data to read
569 fname: Filename we are reading from (just for errors)
570
571 Returns:
572 Dictionary containing size of each function in bytes, indexed by
573 function name.
574 """
575 sym = {}
576 for line in fd.readlines():
577 try:
Tom Rinid08c38c2019-12-06 15:31:31 -0500578 if line.strip():
579 size, type, name = line[:-1].split()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000580 except:
Simon Glass4653a882014-09-05 19:00:07 -0600581 Print("Invalid line in file '%s': '%s'" % (fname, line[:-1]))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000582 continue
583 if type in 'tTdDbB':
584 # function names begin with '.' on 64-bit powerpc
585 if '.' in name[1:]:
586 name = 'static.' + name.split('.')[0]
587 sym[name] = sym.get(name, 0) + int(size, 16)
588 return sym
589
Simon Glass843312d2015-02-05 22:06:15 -0700590 def _ProcessConfig(self, fname):
591 """Read in a .config, autoconf.mk or autoconf.h file
592
593 This function handles all config file types. It ignores comments and
594 any #defines which don't start with CONFIG_.
595
596 Args:
597 fname: Filename to read
598
599 Returns:
600 Dictionary:
601 key: Config name (e.g. CONFIG_DM)
602 value: Config value (e.g. 1)
603 """
604 config = {}
605 if os.path.exists(fname):
606 with open(fname) as fd:
607 for line in fd:
608 line = line.strip()
609 if line.startswith('#define'):
610 values = line[8:].split(' ', 1)
611 if len(values) > 1:
612 key, value = values
613 else:
614 key = values[0]
Simon Glassb464f8e2016-11-13 14:25:53 -0700615 value = '1' if self.squash_config_y else ''
Simon Glass843312d2015-02-05 22:06:15 -0700616 if not key.startswith('CONFIG_'):
617 continue
618 elif not line or line[0] in ['#', '*', '/']:
619 continue
620 else:
621 key, value = line.split('=', 1)
Simon Glassb464f8e2016-11-13 14:25:53 -0700622 if self.squash_config_y and value == 'y':
623 value = '1'
Simon Glass843312d2015-02-05 22:06:15 -0700624 config[key] = value
625 return config
626
Alex Kiernan48ae4122018-05-31 04:48:34 +0000627 def _ProcessEnvironment(self, fname):
628 """Read in a uboot.env file
629
630 This function reads in environment variables from a file.
631
632 Args:
633 fname: Filename to read
634
635 Returns:
636 Dictionary:
637 key: environment variable (e.g. bootlimit)
638 value: value of environment variable (e.g. 1)
639 """
640 environment = {}
641 if os.path.exists(fname):
642 with open(fname) as fd:
643 for line in fd.read().split('\0'):
644 try:
645 key, value = line.split('=', 1)
646 environment[key] = value
647 except ValueError:
648 # ignore lines we can't parse
649 pass
650 return environment
651
Simon Glass843312d2015-02-05 22:06:15 -0700652 def GetBuildOutcome(self, commit_upto, target, read_func_sizes,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000653 read_config, read_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000654 """Work out the outcome of a build.
655
656 Args:
657 commit_upto: Commit number to check (0..n-1)
658 target: Target board to check
659 read_func_sizes: True to read function size information
Simon Glass843312d2015-02-05 22:06:15 -0700660 read_config: True to read .config and autoconf.h files
Alex Kiernan48ae4122018-05-31 04:48:34 +0000661 read_environment: True to read uboot.env files
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000662
663 Returns:
664 Outcome object
665 """
666 done_file = self.GetDoneFile(commit_upto, target)
667 sizes_file = self.GetSizesFile(commit_upto, target)
668 sizes = {}
669 func_sizes = {}
Simon Glass843312d2015-02-05 22:06:15 -0700670 config = {}
Alex Kiernan48ae4122018-05-31 04:48:34 +0000671 environment = {}
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000672 if os.path.exists(done_file):
673 with open(done_file, 'r') as fd:
Simon Glass347ea0b2019-04-26 19:02:23 -0600674 try:
675 return_code = int(fd.readline())
676 except ValueError:
677 # The file may be empty due to running out of disk space.
678 # Try a rebuild
679 return_code = 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000680 err_lines = []
681 err_file = self.GetErrFile(commit_upto, target)
682 if os.path.exists(err_file):
683 with open(err_file, 'r') as fd:
684 err_lines = self.FilterErrors(fd.readlines())
685
686 # Decide whether the build was ok, failed or created warnings
687 if return_code:
688 rc = OUTCOME_ERROR
689 elif len(err_lines):
690 rc = OUTCOME_WARNING
691 else:
692 rc = OUTCOME_OK
693
694 # Convert size information to our simple format
695 if os.path.exists(sizes_file):
696 with open(sizes_file, 'r') as fd:
697 for line in fd.readlines():
698 values = line.split()
699 rodata = 0
700 if len(values) > 6:
701 rodata = int(values[6], 16)
702 size_dict = {
703 'all' : int(values[0]) + int(values[1]) +
704 int(values[2]),
705 'text' : int(values[0]) - rodata,
706 'data' : int(values[1]),
707 'bss' : int(values[2]),
708 'rodata' : rodata,
709 }
710 sizes[values[5]] = size_dict
711
712 if read_func_sizes:
713 pattern = self.GetFuncSizesFile(commit_upto, target, '*')
714 for fname in glob.glob(pattern):
715 with open(fname, 'r') as fd:
716 dict_name = os.path.basename(fname).replace('.sizes',
717 '')
718 func_sizes[dict_name] = self.ReadFuncSizes(fname, fd)
719
Simon Glass843312d2015-02-05 22:06:15 -0700720 if read_config:
721 output_dir = self.GetBuildDir(commit_upto, target)
Simon Glassb464f8e2016-11-13 14:25:53 -0700722 for name in self.config_filenames:
Simon Glass843312d2015-02-05 22:06:15 -0700723 fname = os.path.join(output_dir, name)
724 config[name] = self._ProcessConfig(fname)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000725
Alex Kiernan48ae4122018-05-31 04:48:34 +0000726 if read_environment:
727 output_dir = self.GetBuildDir(commit_upto, target)
728 fname = os.path.join(output_dir, 'uboot.env')
729 environment = self._ProcessEnvironment(fname)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000730
Alex Kiernan48ae4122018-05-31 04:48:34 +0000731 return Builder.Outcome(rc, err_lines, sizes, func_sizes, config,
732 environment)
733
734 return Builder.Outcome(OUTCOME_UNKNOWN, [], {}, {}, {}, {})
Simon Glass843312d2015-02-05 22:06:15 -0700735
736 def GetResultSummary(self, boards_selected, commit_upto, read_func_sizes,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000737 read_config, read_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000738 """Calculate a summary of the results of building a commit.
739
740 Args:
741 board_selected: Dict containing boards to summarise
742 commit_upto: Commit number to summarize (0..self.count-1)
743 read_func_sizes: True to read function size information
Simon Glass843312d2015-02-05 22:06:15 -0700744 read_config: True to read .config and autoconf.h files
Alex Kiernan48ae4122018-05-31 04:48:34 +0000745 read_environment: True to read uboot.env files
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000746
747 Returns:
748 Tuple:
749 Dict containing boards which passed building this commit.
750 keyed by board.target
Simon Glasse30965d2014-08-28 09:43:44 -0600751 List containing a summary of error lines
Simon Glassed966652014-08-28 09:43:43 -0600752 Dict keyed by error line, containing a list of the Board
753 objects with that error
Simon Glasse30965d2014-08-28 09:43:44 -0600754 List containing a summary of warning lines
755 Dict keyed by error line, containing a list of the Board
756 objects with that warning
Simon Glass8270e3c2015-08-25 21:52:14 -0600757 Dictionary keyed by board.target. Each value is a dictionary:
758 key: filename - e.g. '.config'
Simon Glass843312d2015-02-05 22:06:15 -0700759 value is itself a dictionary:
760 key: config name
761 value: config value
Alex Kiernan48ae4122018-05-31 04:48:34 +0000762 Dictionary keyed by board.target. Each value is a dictionary:
763 key: environment variable
764 value: value of environment variable
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000765 """
Simon Glasse30965d2014-08-28 09:43:44 -0600766 def AddLine(lines_summary, lines_boards, line, board):
767 line = line.rstrip()
768 if line in lines_boards:
769 lines_boards[line].append(board)
770 else:
771 lines_boards[line] = [board]
772 lines_summary.append(line)
773
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000774 board_dict = {}
775 err_lines_summary = []
Simon Glassed966652014-08-28 09:43:43 -0600776 err_lines_boards = {}
Simon Glasse30965d2014-08-28 09:43:44 -0600777 warn_lines_summary = []
778 warn_lines_boards = {}
Simon Glass843312d2015-02-05 22:06:15 -0700779 config = {}
Alex Kiernan48ae4122018-05-31 04:48:34 +0000780 environment = {}
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000781
Simon Glassc05aa032019-10-31 07:42:53 -0600782 for board in boards_selected.values():
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000783 outcome = self.GetBuildOutcome(commit_upto, board.target,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000784 read_func_sizes, read_config,
785 read_environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000786 board_dict[board.target] = outcome
Simon Glasse30965d2014-08-28 09:43:44 -0600787 last_func = None
788 last_was_warning = False
789 for line in outcome.err_lines:
790 if line:
791 if (self._re_function.match(line) or
792 self._re_files.match(line)):
793 last_func = line
Simon Glassed966652014-08-28 09:43:43 -0600794 else:
Simon Glass2d483332018-11-06 16:02:11 -0700795 is_warning = (self._re_warning.match(line) or
796 self._re_dtb_warning.match(line))
Simon Glasse30965d2014-08-28 09:43:44 -0600797 is_note = self._re_note.match(line)
798 if is_warning or (last_was_warning and is_note):
799 if last_func:
800 AddLine(warn_lines_summary, warn_lines_boards,
801 last_func, board)
802 AddLine(warn_lines_summary, warn_lines_boards,
803 line, board)
804 else:
805 if last_func:
806 AddLine(err_lines_summary, err_lines_boards,
807 last_func, board)
808 AddLine(err_lines_summary, err_lines_boards,
809 line, board)
810 last_was_warning = is_warning
811 last_func = None
Simon Glassb464f8e2016-11-13 14:25:53 -0700812 tconfig = Config(self.config_filenames, board.target)
813 for fname in self.config_filenames:
Simon Glass843312d2015-02-05 22:06:15 -0700814 if outcome.config:
Simon Glassc05aa032019-10-31 07:42:53 -0600815 for key, value in outcome.config[fname].items():
Simon Glass8270e3c2015-08-25 21:52:14 -0600816 tconfig.Add(fname, key, value)
817 config[board.target] = tconfig
Simon Glass843312d2015-02-05 22:06:15 -0700818
Alex Kiernan48ae4122018-05-31 04:48:34 +0000819 tenvironment = Environment(board.target)
820 if outcome.environment:
Simon Glassc05aa032019-10-31 07:42:53 -0600821 for key, value in outcome.environment.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +0000822 tenvironment.Add(key, value)
823 environment[board.target] = tenvironment
824
Simon Glasse30965d2014-08-28 09:43:44 -0600825 return (board_dict, err_lines_summary, err_lines_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000826 warn_lines_summary, warn_lines_boards, config, environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000827
828 def AddOutcome(self, board_dict, arch_list, changes, char, color):
829 """Add an output to our list of outcomes for each architecture
830
831 This simple function adds failing boards (changes) to the
832 relevant architecture string, so we can print the results out
833 sorted by architecture.
834
835 Args:
836 board_dict: Dict containing all boards
837 arch_list: Dict keyed by arch name. Value is a string containing
838 a list of board names which failed for that arch.
839 changes: List of boards to add to arch_list
840 color: terminal.Colour object
841 """
842 done_arch = {}
843 for target in changes:
844 if target in board_dict:
845 arch = board_dict[target].arch
846 else:
847 arch = 'unknown'
848 str = self.col.Color(color, ' ' + target)
849 if not arch in done_arch:
Simon Glass63c619e2015-02-05 22:06:11 -0700850 str = ' %s %s' % (self.col.Color(color, char), str)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000851 done_arch[arch] = True
852 if not arch in arch_list:
853 arch_list[arch] = str
854 else:
855 arch_list[arch] += str
856
857
858 def ColourNum(self, num):
859 color = self.col.RED if num > 0 else self.col.GREEN
860 if num == 0:
861 return '0'
862 return self.col.Color(color, str(num))
863
864 def ResetResultSummary(self, board_selected):
865 """Reset the results summary ready for use.
866
867 Set up the base board list to be all those selected, and set the
868 error lines to empty.
869
870 Following this, calls to PrintResultSummary() will use this
871 information to work out what has changed.
872
873 Args:
874 board_selected: Dict containing boards to summarise, keyed by
875 board.target
876 """
877 self._base_board_dict = {}
878 for board in board_selected:
Alex Kiernan48ae4122018-05-31 04:48:34 +0000879 self._base_board_dict[board] = Builder.Outcome(0, [], [], {}, {},
880 {})
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000881 self._base_err_lines = []
Simon Glasse30965d2014-08-28 09:43:44 -0600882 self._base_warn_lines = []
883 self._base_err_line_boards = {}
884 self._base_warn_line_boards = {}
Simon Glass8270e3c2015-08-25 21:52:14 -0600885 self._base_config = None
Alex Kiernan48ae4122018-05-31 04:48:34 +0000886 self._base_environment = None
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000887
888 def PrintFuncSizeDetail(self, fname, old, new):
889 grow, shrink, add, remove, up, down = 0, 0, 0, 0, 0, 0
890 delta, common = [], {}
891
892 for a in old:
893 if a in new:
894 common[a] = 1
895
896 for name in old:
897 if name not in common:
898 remove += 1
899 down += old[name]
900 delta.append([-old[name], name])
901
902 for name in new:
903 if name not in common:
904 add += 1
905 up += new[name]
906 delta.append([new[name], name])
907
908 for name in common:
909 diff = new.get(name, 0) - old.get(name, 0)
910 if diff > 0:
911 grow, up = grow + 1, up + diff
912 elif diff < 0:
913 shrink, down = shrink + 1, down - diff
914 delta.append([diff, name])
915
916 delta.sort()
917 delta.reverse()
918
919 args = [add, -remove, grow, -shrink, up, -down, up - down]
Tom Rinid5686a62017-05-22 13:48:52 -0400920 if max(args) == 0 and min(args) == 0:
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000921 return
922 args = [self.ColourNum(x) for x in args]
923 indent = ' ' * 15
Simon Glass4653a882014-09-05 19:00:07 -0600924 Print('%s%s: add: %s/%s, grow: %s/%s bytes: %s/%s (%s)' %
925 tuple([indent, self.col.Color(self.col.YELLOW, fname)] + args))
926 Print('%s %-38s %7s %7s %+7s' % (indent, 'function', 'old', 'new',
927 'delta'))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000928 for diff, name in delta:
929 if diff:
930 color = self.col.RED if diff > 0 else self.col.GREEN
931 msg = '%s %-38s %7s %7s %+7d' % (indent, name,
932 old.get(name, '-'), new.get(name,'-'), diff)
Simon Glass4653a882014-09-05 19:00:07 -0600933 Print(msg, colour=color)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000934
935
936 def PrintSizeDetail(self, target_list, show_bloat):
937 """Show details size information for each board
938
939 Args:
940 target_list: List of targets, each a dict containing:
941 'target': Target name
942 'total_diff': Total difference in bytes across all areas
943 <part_name>: Difference for that part
944 show_bloat: Show detail for each function
945 """
946 targets_by_diff = sorted(target_list, reverse=True,
947 key=lambda x: x['_total_diff'])
948 for result in targets_by_diff:
949 printed_target = False
950 for name in sorted(result):
951 diff = result[name]
952 if name.startswith('_'):
953 continue
954 if diff != 0:
955 color = self.col.RED if diff > 0 else self.col.GREEN
956 msg = ' %s %+d' % (name, diff)
957 if not printed_target:
Simon Glass4653a882014-09-05 19:00:07 -0600958 Print('%10s %-15s:' % ('', result['_target']),
959 newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000960 printed_target = True
Simon Glass4653a882014-09-05 19:00:07 -0600961 Print(msg, colour=color, newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000962 if printed_target:
Simon Glass4653a882014-09-05 19:00:07 -0600963 Print()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000964 if show_bloat:
965 target = result['_target']
966 outcome = result['_outcome']
967 base_outcome = self._base_board_dict[target]
968 for fname in outcome.func_sizes:
969 self.PrintFuncSizeDetail(fname,
970 base_outcome.func_sizes[fname],
971 outcome.func_sizes[fname])
972
973
974 def PrintSizeSummary(self, board_selected, board_dict, show_detail,
975 show_bloat):
976 """Print a summary of image sizes broken down by section.
977
978 The summary takes the form of one line per architecture. The
979 line contains deltas for each of the sections (+ means the section
Flavio Suligoi9de5c392020-01-29 09:56:05 +0100980 got bigger, - means smaller). The numbers are the average number
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000981 of bytes that a board in this section increased by.
982
983 For example:
984 powerpc: (622 boards) text -0.0
985 arm: (285 boards) text -0.0
986 nds32: (3 boards) text -8.0
987
988 Args:
989 board_selected: Dict containing boards to summarise, keyed by
990 board.target
991 board_dict: Dict containing boards for which we built this
992 commit, keyed by board.target. The value is an Outcome object.
Simon Glassf9c094b2020-03-18 09:42:43 -0600993 show_detail: Show size delta detail for each board
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000994 show_bloat: Show detail for each function
995 """
996 arch_list = {}
997 arch_count = {}
998
999 # Calculate changes in size for different image parts
1000 # The previous sizes are in Board.sizes, for each board
1001 for target in board_dict:
1002 if target not in board_selected:
1003 continue
1004 base_sizes = self._base_board_dict[target].sizes
1005 outcome = board_dict[target]
1006 sizes = outcome.sizes
1007
1008 # Loop through the list of images, creating a dict of size
1009 # changes for each image/part. We end up with something like
1010 # {'target' : 'snapper9g45, 'data' : 5, 'u-boot-spl:text' : -4}
1011 # which means that U-Boot data increased by 5 bytes and SPL
1012 # text decreased by 4.
1013 err = {'_target' : target}
1014 for image in sizes:
1015 if image in base_sizes:
1016 base_image = base_sizes[image]
1017 # Loop through the text, data, bss parts
1018 for part in sorted(sizes[image]):
1019 diff = sizes[image][part] - base_image[part]
1020 col = None
1021 if diff:
1022 if image == 'u-boot':
1023 name = part
1024 else:
1025 name = image + ':' + part
1026 err[name] = diff
1027 arch = board_selected[target].arch
1028 if not arch in arch_count:
1029 arch_count[arch] = 1
1030 else:
1031 arch_count[arch] += 1
1032 if not sizes:
1033 pass # Only add to our list when we have some stats
1034 elif not arch in arch_list:
1035 arch_list[arch] = [err]
1036 else:
1037 arch_list[arch].append(err)
1038
1039 # We now have a list of image size changes sorted by arch
1040 # Print out a summary of these
Simon Glassc05aa032019-10-31 07:42:53 -06001041 for arch, target_list in arch_list.items():
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001042 # Get total difference for each type
1043 totals = {}
1044 for result in target_list:
1045 total = 0
Simon Glassc05aa032019-10-31 07:42:53 -06001046 for name, diff in result.items():
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001047 if name.startswith('_'):
1048 continue
1049 total += diff
1050 if name in totals:
1051 totals[name] += diff
1052 else:
1053 totals[name] = diff
1054 result['_total_diff'] = total
1055 result['_outcome'] = board_dict[result['_target']]
1056
1057 count = len(target_list)
1058 printed_arch = False
1059 for name in sorted(totals):
1060 diff = totals[name]
1061 if diff:
1062 # Display the average difference in this name for this
1063 # architecture
1064 avg_diff = float(diff) / count
1065 color = self.col.RED if avg_diff > 0 else self.col.GREEN
1066 msg = ' %s %+1.1f' % (name, avg_diff)
1067 if not printed_arch:
Simon Glass4653a882014-09-05 19:00:07 -06001068 Print('%10s: (for %d/%d boards)' % (arch, count,
1069 arch_count[arch]), newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001070 printed_arch = True
Simon Glass4653a882014-09-05 19:00:07 -06001071 Print(msg, colour=color, newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001072
1073 if printed_arch:
Simon Glass4653a882014-09-05 19:00:07 -06001074 Print()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001075 if show_detail:
1076 self.PrintSizeDetail(target_list, show_bloat)
1077
1078
1079 def PrintResultSummary(self, board_selected, board_dict, err_lines,
Simon Glasse30965d2014-08-28 09:43:44 -06001080 err_line_boards, warn_lines, warn_line_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001081 config, environment, show_sizes, show_detail,
1082 show_bloat, show_config, show_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001083 """Compare results with the base results and display delta.
1084
1085 Only boards mentioned in board_selected will be considered. This
1086 function is intended to be called repeatedly with the results of
1087 each commit. It therefore shows a 'diff' between what it saw in
1088 the last call and what it sees now.
1089
1090 Args:
1091 board_selected: Dict containing boards to summarise, keyed by
1092 board.target
1093 board_dict: Dict containing boards for which we built this
1094 commit, keyed by board.target. The value is an Outcome object.
1095 err_lines: A list of errors for this commit, or [] if there is
1096 none, or we don't want to print errors
Simon Glassed966652014-08-28 09:43:43 -06001097 err_line_boards: Dict keyed by error line, containing a list of
1098 the Board objects with that error
Simon Glasse30965d2014-08-28 09:43:44 -06001099 warn_lines: A list of warnings for this commit, or [] if there is
1100 none, or we don't want to print errors
1101 warn_line_boards: Dict keyed by warning line, containing a list of
1102 the Board objects with that warning
Simon Glass843312d2015-02-05 22:06:15 -07001103 config: Dictionary keyed by filename - e.g. '.config'. Each
1104 value is itself a dictionary:
1105 key: config name
1106 value: config value
Alex Kiernan48ae4122018-05-31 04:48:34 +00001107 environment: Dictionary keyed by environment variable, Each
1108 value is the value of environment variable.
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001109 show_sizes: Show image size deltas
Simon Glassf9c094b2020-03-18 09:42:43 -06001110 show_detail: Show size delta detail for each board if show_sizes
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001111 show_bloat: Show detail for each function
Simon Glass843312d2015-02-05 22:06:15 -07001112 show_config: Show config changes
Alex Kiernan48ae4122018-05-31 04:48:34 +00001113 show_environment: Show environment changes
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001114 """
Simon Glasse30965d2014-08-28 09:43:44 -06001115 def _BoardList(line, line_boards):
Simon Glassed966652014-08-28 09:43:43 -06001116 """Helper function to get a line of boards containing a line
1117
1118 Args:
1119 line: Error line to search for
Simon Glass35d696d2020-04-09 15:08:36 -06001120 line_boards: boards to search, each a Board
Simon Glassed966652014-08-28 09:43:43 -06001121 Return:
Simon Glass35d696d2020-04-09 15:08:36 -06001122 List of boards with that error line, or [] if the user has not
1123 requested such a list
Simon Glassed966652014-08-28 09:43:43 -06001124 """
Simon Glass35d696d2020-04-09 15:08:36 -06001125 boards = []
1126 board_set = set()
Simon Glassed966652014-08-28 09:43:43 -06001127 if self._list_error_boards:
Simon Glasse30965d2014-08-28 09:43:44 -06001128 for board in line_boards[line]:
Simon Glass35d696d2020-04-09 15:08:36 -06001129 if not board in board_set:
1130 boards.append(board)
1131 board_set.add(board)
1132 return boards
Simon Glassed966652014-08-28 09:43:43 -06001133
Simon Glasse30965d2014-08-28 09:43:44 -06001134 def _CalcErrorDelta(base_lines, base_line_boards, lines, line_boards,
1135 char):
Simon Glass35d696d2020-04-09 15:08:36 -06001136 """Calculate the required output based on changes in errors
1137
1138 Args:
1139 base_lines: List of errors/warnings for previous commit
1140 base_line_boards: Dict keyed by error line, containing a list
1141 of the Board objects with that error in the previous commit
1142 lines: List of errors/warning for this commit, each a str
1143 line_boards: Dict keyed by error line, containing a list
1144 of the Board objects with that error in this commit
1145 char: Character representing error ('') or warning ('w'). The
1146 broken ('+') or fixed ('-') characters are added in this
1147 function
1148
1149 Returns:
1150 Tuple
1151 List of ErrLine objects for 'better' lines
1152 List of ErrLine objects for 'worse' lines
1153 """
Simon Glasse30965d2014-08-28 09:43:44 -06001154 better_lines = []
1155 worse_lines = []
1156 for line in lines:
1157 if line not in base_lines:
Simon Glass35d696d2020-04-09 15:08:36 -06001158 errline = ErrLine(char + '+', _BoardList(line, line_boards),
1159 line)
1160 worse_lines.append(errline)
Simon Glasse30965d2014-08-28 09:43:44 -06001161 for line in base_lines:
1162 if line not in lines:
Simon Glass35d696d2020-04-09 15:08:36 -06001163 errline = ErrLine(char + '-',
1164 _BoardList(line, base_line_boards), line)
1165 better_lines.append(errline)
Simon Glasse30965d2014-08-28 09:43:44 -06001166 return better_lines, worse_lines
1167
Simon Glass843312d2015-02-05 22:06:15 -07001168 def _CalcConfig(delta, name, config):
1169 """Calculate configuration changes
1170
1171 Args:
1172 delta: Type of the delta, e.g. '+'
1173 name: name of the file which changed (e.g. .config)
1174 config: configuration change dictionary
1175 key: config name
1176 value: config value
1177 Returns:
1178 String containing the configuration changes which can be
1179 printed
1180 """
1181 out = ''
1182 for key in sorted(config.keys()):
1183 out += '%s=%s ' % (key, config[key])
Simon Glass8270e3c2015-08-25 21:52:14 -06001184 return '%s %s: %s' % (delta, name, out)
Simon Glass843312d2015-02-05 22:06:15 -07001185
Simon Glass8270e3c2015-08-25 21:52:14 -06001186 def _AddConfig(lines, name, config_plus, config_minus, config_change):
1187 """Add changes in configuration to a list
Simon Glass843312d2015-02-05 22:06:15 -07001188
1189 Args:
Simon Glass8270e3c2015-08-25 21:52:14 -06001190 lines: list to add to
1191 name: config file name
Simon Glass843312d2015-02-05 22:06:15 -07001192 config_plus: configurations added, dictionary
1193 key: config name
1194 value: config value
1195 config_minus: configurations removed, dictionary
1196 key: config name
1197 value: config value
1198 config_change: configurations changed, dictionary
1199 key: config name
1200 value: config value
1201 """
1202 if config_plus:
Simon Glass8270e3c2015-08-25 21:52:14 -06001203 lines.append(_CalcConfig('+', name, config_plus))
Simon Glass843312d2015-02-05 22:06:15 -07001204 if config_minus:
Simon Glass8270e3c2015-08-25 21:52:14 -06001205 lines.append(_CalcConfig('-', name, config_minus))
Simon Glass843312d2015-02-05 22:06:15 -07001206 if config_change:
Simon Glass8270e3c2015-08-25 21:52:14 -06001207 lines.append(_CalcConfig('c', name, config_change))
1208
1209 def _OutputConfigInfo(lines):
1210 for line in lines:
1211 if not line:
1212 continue
1213 if line[0] == '+':
1214 col = self.col.GREEN
1215 elif line[0] == '-':
1216 col = self.col.RED
1217 elif line[0] == 'c':
1218 col = self.col.YELLOW
1219 Print(' ' + line, newline=True, colour=col)
1220
Simon Glassb206d872020-04-09 15:08:28 -06001221 def _OutputErrLines(err_lines, colour):
1222 """Output the line of error/warning lines, if not empty
1223
1224 Also increments self._error_lines if err_lines not empty
1225
1226 Args:
Simon Glass35d696d2020-04-09 15:08:36 -06001227 err_lines: List of ErrLine objects, each an error or warning
1228 line, possibly including a list of boards with that
1229 error/warning
Simon Glassb206d872020-04-09 15:08:28 -06001230 colour: Colour to use for output
1231 """
1232 if err_lines:
Simon Glass8c9a2672020-04-09 15:08:37 -06001233 out_list = []
Simon Glass35d696d2020-04-09 15:08:36 -06001234 for line in err_lines:
1235 boards = ''
1236 names = [board.target for board in line.boards]
Simon Glass9ef0ceb2020-04-09 15:08:38 -06001237 board_str = ' '.join(names) if names else ''
Simon Glass8c9a2672020-04-09 15:08:37 -06001238 if board_str:
1239 out = self.col.Color(colour, line.char + '(')
1240 out += self.col.Color(self.col.MAGENTA, board_str,
1241 bright=False)
1242 out += self.col.Color(colour, ') %s' % line.errline)
1243 else:
1244 out = self.col.Color(colour, line.char + line.errline)
1245 out_list.append(out)
1246 Print('\n'.join(out_list))
Simon Glassb206d872020-04-09 15:08:28 -06001247 self._error_lines += 1
1248
Simon Glass843312d2015-02-05 22:06:15 -07001249
Simon Glass4cf2b222018-11-06 16:02:12 -07001250 ok_boards = [] # List of boards fixed since last commit
Simon Glass6af71012018-11-06 16:02:13 -07001251 warn_boards = [] # List of boards with warnings since last commit
Simon Glass4cf2b222018-11-06 16:02:12 -07001252 err_boards = [] # List of new broken boards since last commit
1253 new_boards = [] # List of boards that didn't exist last time
1254 unknown_boards = [] # List of boards that were not built
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001255
1256 for target in board_dict:
1257 if target not in board_selected:
1258 continue
1259
1260 # If the board was built last time, add its outcome to a list
1261 if target in self._base_board_dict:
1262 base_outcome = self._base_board_dict[target].rc
1263 outcome = board_dict[target]
1264 if outcome.rc == OUTCOME_UNKNOWN:
Simon Glass4cf2b222018-11-06 16:02:12 -07001265 unknown_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001266 elif outcome.rc < base_outcome:
Simon Glass6af71012018-11-06 16:02:13 -07001267 if outcome.rc == OUTCOME_WARNING:
1268 warn_boards.append(target)
1269 else:
1270 ok_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001271 elif outcome.rc > base_outcome:
Simon Glass6af71012018-11-06 16:02:13 -07001272 if outcome.rc == OUTCOME_WARNING:
1273 warn_boards.append(target)
1274 else:
1275 err_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001276 else:
Simon Glass4cf2b222018-11-06 16:02:12 -07001277 new_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001278
Simon Glassb206d872020-04-09 15:08:28 -06001279 # Get a list of errors and warnings that have appeared, and disappeared
Simon Glasse30965d2014-08-28 09:43:44 -06001280 better_err, worse_err = _CalcErrorDelta(self._base_err_lines,
1281 self._base_err_line_boards, err_lines, err_line_boards, '')
1282 better_warn, worse_warn = _CalcErrorDelta(self._base_warn_lines,
1283 self._base_warn_line_boards, warn_lines, warn_line_boards, 'w')
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001284
1285 # Display results by arch
Simon Glass6af71012018-11-06 16:02:13 -07001286 if any((ok_boards, warn_boards, err_boards, unknown_boards, new_boards,
1287 worse_err, better_err, worse_warn, better_warn)):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001288 arch_list = {}
Simon Glass4cf2b222018-11-06 16:02:12 -07001289 self.AddOutcome(board_selected, arch_list, ok_boards, '',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001290 self.col.GREEN)
Simon Glass6af71012018-11-06 16:02:13 -07001291 self.AddOutcome(board_selected, arch_list, warn_boards, 'w+',
1292 self.col.YELLOW)
Simon Glass4cf2b222018-11-06 16:02:12 -07001293 self.AddOutcome(board_selected, arch_list, err_boards, '+',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001294 self.col.RED)
Simon Glass4cf2b222018-11-06 16:02:12 -07001295 self.AddOutcome(board_selected, arch_list, new_boards, '*', self.col.BLUE)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001296 if self._show_unknown:
Simon Glass4cf2b222018-11-06 16:02:12 -07001297 self.AddOutcome(board_selected, arch_list, unknown_boards, '?',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001298 self.col.MAGENTA)
Simon Glassc05aa032019-10-31 07:42:53 -06001299 for arch, target_list in arch_list.items():
Simon Glass4653a882014-09-05 19:00:07 -06001300 Print('%10s: %s' % (arch, target_list))
Simon Glass28370c12014-08-09 15:33:06 -06001301 self._error_lines += 1
Simon Glassb206d872020-04-09 15:08:28 -06001302 _OutputErrLines(better_err, colour=self.col.GREEN)
1303 _OutputErrLines(worse_err, colour=self.col.RED)
1304 _OutputErrLines(better_warn, colour=self.col.CYAN)
Simon Glass5627bd92020-04-09 15:08:35 -06001305 _OutputErrLines(worse_warn, colour=self.col.YELLOW)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001306
1307 if show_sizes:
1308 self.PrintSizeSummary(board_selected, board_dict, show_detail,
1309 show_bloat)
1310
Alex Kiernan48ae4122018-05-31 04:48:34 +00001311 if show_environment and self._base_environment:
1312 lines = []
1313
1314 for target in board_dict:
1315 if target not in board_selected:
1316 continue
1317
1318 tbase = self._base_environment[target]
1319 tenvironment = environment[target]
1320 environment_plus = {}
1321 environment_minus = {}
1322 environment_change = {}
1323 base = tbase.environment
Simon Glassc05aa032019-10-31 07:42:53 -06001324 for key, value in tenvironment.environment.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001325 if key not in base:
1326 environment_plus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001327 for key, value in base.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001328 if key not in tenvironment.environment:
1329 environment_minus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001330 for key, value in base.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001331 new_value = tenvironment.environment.get(key)
1332 if new_value and value != new_value:
1333 desc = '%s -> %s' % (value, new_value)
1334 environment_change[key] = desc
1335
1336 _AddConfig(lines, target, environment_plus, environment_minus,
1337 environment_change)
1338
1339 _OutputConfigInfo(lines)
1340
Simon Glass8270e3c2015-08-25 21:52:14 -06001341 if show_config and self._base_config:
1342 summary = {}
1343 arch_config_plus = {}
1344 arch_config_minus = {}
1345 arch_config_change = {}
1346 arch_list = []
1347
1348 for target in board_dict:
1349 if target not in board_selected:
Simon Glass843312d2015-02-05 22:06:15 -07001350 continue
Simon Glass8270e3c2015-08-25 21:52:14 -06001351 arch = board_selected[target].arch
1352 if arch not in arch_list:
1353 arch_list.append(arch)
1354
1355 for arch in arch_list:
1356 arch_config_plus[arch] = {}
1357 arch_config_minus[arch] = {}
1358 arch_config_change[arch] = {}
Simon Glassb464f8e2016-11-13 14:25:53 -07001359 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001360 arch_config_plus[arch][name] = {}
1361 arch_config_minus[arch][name] = {}
1362 arch_config_change[arch][name] = {}
1363
1364 for target in board_dict:
1365 if target not in board_selected:
1366 continue
1367
1368 arch = board_selected[target].arch
1369
1370 all_config_plus = {}
1371 all_config_minus = {}
1372 all_config_change = {}
1373 tbase = self._base_config[target]
1374 tconfig = config[target]
1375 lines = []
Simon Glassb464f8e2016-11-13 14:25:53 -07001376 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001377 if not tconfig.config[name]:
1378 continue
1379 config_plus = {}
1380 config_minus = {}
1381 config_change = {}
1382 base = tbase.config[name]
Simon Glassc05aa032019-10-31 07:42:53 -06001383 for key, value in tconfig.config[name].items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001384 if key not in base:
1385 config_plus[key] = value
1386 all_config_plus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001387 for key, value in base.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001388 if key not in tconfig.config[name]:
1389 config_minus[key] = value
1390 all_config_minus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001391 for key, value in base.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001392 new_value = tconfig.config.get(key)
1393 if new_value and value != new_value:
1394 desc = '%s -> %s' % (value, new_value)
1395 config_change[key] = desc
1396 all_config_change[key] = desc
1397
1398 arch_config_plus[arch][name].update(config_plus)
1399 arch_config_minus[arch][name].update(config_minus)
1400 arch_config_change[arch][name].update(config_change)
1401
1402 _AddConfig(lines, name, config_plus, config_minus,
1403 config_change)
1404 _AddConfig(lines, 'all', all_config_plus, all_config_minus,
1405 all_config_change)
1406 summary[target] = '\n'.join(lines)
1407
1408 lines_by_target = {}
Simon Glassc05aa032019-10-31 07:42:53 -06001409 for target, lines in summary.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001410 if lines in lines_by_target:
1411 lines_by_target[lines].append(target)
1412 else:
1413 lines_by_target[lines] = [target]
1414
1415 for arch in arch_list:
1416 lines = []
1417 all_plus = {}
1418 all_minus = {}
1419 all_change = {}
Simon Glassb464f8e2016-11-13 14:25:53 -07001420 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001421 all_plus.update(arch_config_plus[arch][name])
1422 all_minus.update(arch_config_minus[arch][name])
1423 all_change.update(arch_config_change[arch][name])
1424 _AddConfig(lines, name, arch_config_plus[arch][name],
1425 arch_config_minus[arch][name],
1426 arch_config_change[arch][name])
1427 _AddConfig(lines, 'all', all_plus, all_minus, all_change)
1428 #arch_summary[target] = '\n'.join(lines)
1429 if lines:
1430 Print('%s:' % arch)
1431 _OutputConfigInfo(lines)
1432
Simon Glassc05aa032019-10-31 07:42:53 -06001433 for lines, targets in lines_by_target.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001434 if not lines:
1435 continue
1436 Print('%s :' % ' '.join(sorted(targets)))
1437 _OutputConfigInfo(lines.split('\n'))
1438
Simon Glass843312d2015-02-05 22:06:15 -07001439
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001440 # Save our updated information for the next call to this function
1441 self._base_board_dict = board_dict
1442 self._base_err_lines = err_lines
Simon Glasse30965d2014-08-28 09:43:44 -06001443 self._base_warn_lines = warn_lines
1444 self._base_err_line_boards = err_line_boards
1445 self._base_warn_line_boards = warn_line_boards
Simon Glass843312d2015-02-05 22:06:15 -07001446 self._base_config = config
Alex Kiernan48ae4122018-05-31 04:48:34 +00001447 self._base_environment = environment
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001448
1449 # Get a list of boards that did not get built, if needed
1450 not_built = []
1451 for board in board_selected:
1452 if not board in board_dict:
1453 not_built.append(board)
1454 if not_built:
Simon Glass4653a882014-09-05 19:00:07 -06001455 Print("Boards not built (%d): %s" % (len(not_built),
1456 ', '.join(not_built)))
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001457
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001458 def ProduceResultSummary(self, commit_upto, commits, board_selected):
Simon Glasse30965d2014-08-28 09:43:44 -06001459 (board_dict, err_lines, err_line_boards, warn_lines,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001460 warn_line_boards, config, environment) = self.GetResultSummary(
Simon Glassed966652014-08-28 09:43:43 -06001461 board_selected, commit_upto,
Simon Glass843312d2015-02-05 22:06:15 -07001462 read_func_sizes=self._show_bloat,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001463 read_config=self._show_config,
1464 read_environment=self._show_environment)
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001465 if commits:
1466 msg = '%02d: %s' % (commit_upto + 1,
1467 commits[commit_upto].subject)
Simon Glass4653a882014-09-05 19:00:07 -06001468 Print(msg, colour=self.col.BLUE)
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001469 self.PrintResultSummary(board_selected, board_dict,
Simon Glassed966652014-08-28 09:43:43 -06001470 err_lines if self._show_errors else [], err_line_boards,
Simon Glasse30965d2014-08-28 09:43:44 -06001471 warn_lines if self._show_errors else [], warn_line_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001472 config, environment, self._show_sizes, self._show_detail,
1473 self._show_bloat, self._show_config, self._show_environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001474
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001475 def ShowSummary(self, commits, board_selected):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001476 """Show a build summary for U-Boot for a given board list.
1477
1478 Reset the result summary, then repeatedly call GetResultSummary on
1479 each commit's results, then display the differences we see.
1480
1481 Args:
1482 commit: Commit objects to summarise
1483 board_selected: Dict containing boards to summarise
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001484 """
Simon Glassfea58582014-08-09 15:32:59 -06001485 self.commit_count = len(commits) if commits else 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001486 self.commits = commits
1487 self.ResetResultSummary(board_selected)
Simon Glass28370c12014-08-09 15:33:06 -06001488 self._error_lines = 0
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001489
1490 for commit_upto in range(0, self.commit_count, self._step):
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001491 self.ProduceResultSummary(commit_upto, commits, board_selected)
Simon Glass28370c12014-08-09 15:33:06 -06001492 if not self._error_lines:
Simon Glass4653a882014-09-05 19:00:07 -06001493 Print('(no errors to report)', colour=self.col.GREEN)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001494
1495
1496 def SetupBuild(self, board_selected, commits):
1497 """Set up ready to start a build.
1498
1499 Args:
1500 board_selected: Selected boards to build
1501 commits: Selected commits to build
1502 """
1503 # First work out how many commits we will build
Simon Glassc05aa032019-10-31 07:42:53 -06001504 count = (self.commit_count + self._step - 1) // self._step
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001505 self.count = len(board_selected) * count
1506 self.upto = self.warned = self.fail = 0
1507 self._timestamps = collections.deque()
1508
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001509 def GetThreadDir(self, thread_num):
1510 """Get the directory path to the working dir for a thread.
1511
1512 Args:
1513 thread_num: Number of thread to check.
1514 """
Simon Glassd829f122020-03-18 09:42:42 -06001515 if self.work_in_output:
1516 return self._working_dir
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001517 return os.path.join(self._working_dir, '%02d' % thread_num)
1518
Simon Glassfea58582014-08-09 15:32:59 -06001519 def _PrepareThread(self, thread_num, setup_git):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001520 """Prepare the working directory for a thread.
1521
1522 This clones or fetches the repo into the thread's work directory.
1523
1524 Args:
1525 thread_num: Thread number (0, 1, ...)
Simon Glassfea58582014-08-09 15:32:59 -06001526 setup_git: True to set up a git repo clone
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001527 """
1528 thread_dir = self.GetThreadDir(thread_num)
Simon Glass190064b2014-08-09 15:33:00 -06001529 builderthread.Mkdir(thread_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001530 git_dir = os.path.join(thread_dir, '.git')
1531
1532 # Clone the repo if it doesn't already exist
1533 # TODO(sjg@chromium): Perhaps some git hackery to symlink instead, so
1534 # we have a private index but uses the origin repo's contents?
Simon Glassfea58582014-08-09 15:32:59 -06001535 if setup_git and self.git_dir:
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001536 src_dir = os.path.abspath(self.git_dir)
1537 if os.path.exists(git_dir):
Simon Glass212c0b82020-04-09 15:08:43 -06001538 Print('\rFetching repo for thread %d' % thread_num,
1539 newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001540 gitutil.Fetch(git_dir, thread_dir)
Simon Glass212c0b82020-04-09 15:08:43 -06001541 terminal.PrintClear()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001542 else:
Simon Glass21f0eb32016-09-18 16:48:31 -06001543 Print('\rCloning repo for thread %d' % thread_num,
1544 newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001545 gitutil.Clone(src_dir, thread_dir)
Simon Glass102969b2020-04-09 15:08:42 -06001546 terminal.PrintClear()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001547
Simon Glassfea58582014-08-09 15:32:59 -06001548 def _PrepareWorkingSpace(self, max_threads, setup_git):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001549 """Prepare the working directory for use.
1550
1551 Set up the git repo for each thread.
1552
1553 Args:
1554 max_threads: Maximum number of threads we expect to need.
Simon Glassfea58582014-08-09 15:32:59 -06001555 setup_git: True to set up a git repo clone
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001556 """
Simon Glass190064b2014-08-09 15:33:00 -06001557 builderthread.Mkdir(self._working_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001558 for thread in range(max_threads):
Simon Glassfea58582014-08-09 15:32:59 -06001559 self._PrepareThread(thread, setup_git)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001560
Simon Glass925f6ad2020-03-18 09:42:45 -06001561 def _GetOutputSpaceRemovals(self):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001562 """Get the output directories ready to receive files.
1563
Simon Glass925f6ad2020-03-18 09:42:45 -06001564 Figure out what needs to be deleted in the output directory before it
1565 can be used. We only delete old buildman directories which have the
1566 expected name pattern. See _GetOutputDir().
1567
1568 Returns:
1569 List of full paths of directories to remove
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001570 """
Simon Glass1a915672014-12-01 17:33:53 -07001571 if not self.commits:
1572 return
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001573 dir_list = []
1574 for commit_upto in range(self.commit_count):
1575 dir_list.append(self._GetOutputDir(commit_upto))
1576
Simon Glassb222abe2016-09-18 16:48:32 -06001577 to_remove = []
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001578 for dirname in glob.glob(os.path.join(self.base_dir, '*')):
1579 if dirname not in dir_list:
Simon Glass925f6ad2020-03-18 09:42:45 -06001580 leaf = dirname[len(self.base_dir) + 1:]
1581 m = re.match('[0-9]+_of_[0-9]+_g[0-9a-f]+_.*', leaf)
1582 if m:
1583 to_remove.append(dirname)
1584 return to_remove
1585
1586 def _PrepareOutputSpace(self):
1587 """Get the output directories ready to receive files.
1588
1589 We delete any output directories which look like ones we need to
1590 create. Having left over directories is confusing when the user wants
1591 to check the output manually.
1592 """
1593 to_remove = self._GetOutputSpaceRemovals()
Simon Glassb222abe2016-09-18 16:48:32 -06001594 if to_remove:
Simon Glassb2d89bc2020-03-18 09:42:46 -06001595 Print('Removing %d old build directories...' % len(to_remove),
Simon Glassb222abe2016-09-18 16:48:32 -06001596 newline=False)
1597 for dirname in to_remove:
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001598 shutil.rmtree(dirname)
Simon Glass212c0b82020-04-09 15:08:43 -06001599 terminal.PrintClear()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001600
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001601 def BuildBoards(self, commits, board_selected, keep_outputs, verbose):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001602 """Build all commits for a list of boards
1603
1604 Args:
1605 commits: List of commits to be build, each a Commit object
1606 boards_selected: Dict of selected boards, key is target name,
1607 value is Board object
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001608 keep_outputs: True to save build output files
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001609 verbose: Display build results as they are completed
Simon Glass2c3deb92014-08-28 09:43:39 -06001610 Returns:
1611 Tuple containing:
1612 - number of boards that failed to build
1613 - number of boards that issued warnings
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001614 """
Simon Glassfea58582014-08-09 15:32:59 -06001615 self.commit_count = len(commits) if commits else 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001616 self.commits = commits
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001617 self._verbose = verbose
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001618
1619 self.ResetResultSummary(board_selected)
Thierry Redingf3d015c2014-08-19 10:22:39 +02001620 builderthread.Mkdir(self.base_dir, parents = True)
Simon Glassfea58582014-08-09 15:32:59 -06001621 self._PrepareWorkingSpace(min(self.num_threads, len(board_selected)),
1622 commits is not None)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001623 self._PrepareOutputSpace()
Simon Glass745b3952016-09-18 16:48:33 -06001624 Print('\rStarting build...', newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001625 self.SetupBuild(board_selected, commits)
1626 self.ProcessResult(None)
1627
1628 # Create jobs to build all commits for each board
Simon Glassc05aa032019-10-31 07:42:53 -06001629 for brd in board_selected.values():
Simon Glass190064b2014-08-09 15:33:00 -06001630 job = builderthread.BuilderJob()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001631 job.board = brd
1632 job.commits = commits
1633 job.keep_outputs = keep_outputs
Simon Glassd829f122020-03-18 09:42:42 -06001634 job.work_in_output = self.work_in_output
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001635 job.step = self._step
1636 self.queue.put(job)
1637
Simon Glassd436e382016-09-18 16:48:35 -06001638 term = threading.Thread(target=self.queue.join)
1639 term.setDaemon(True)
1640 term.start()
1641 while term.isAlive():
1642 term.join(100)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001643
1644 # Wait until we have processed all output
1645 self.out_queue.join()
Simon Glass4653a882014-09-05 19:00:07 -06001646 Print()
Simon Glass7b33f212020-04-09 15:08:47 -06001647
1648 msg = 'Completed: %d total built' % self.count
1649 if self.already_done:
1650 msg += ' (%d previously' % self.already_done
1651 if self.already_done != self.count:
1652 msg += ', %d newly' % (self.count - self.already_done)
1653 msg += ')'
1654 duration = datetime.now() - self._start_time
1655 if duration > timedelta(microseconds=1000000):
1656 if duration.microseconds >= 500000:
1657 duration = duration + timedelta(seconds=1)
1658 duration = duration - timedelta(microseconds=duration.microseconds)
1659 msg += ', duration %s' % duration
1660 Print(msg)
1661
Simon Glass2c3deb92014-08-28 09:43:39 -06001662 return (self.fail, self.warned)