blob: 597a03ffb087a9c5d9a0969a4719a770d96e5b7c [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.*')
309
Simon Glassc05aa032019-10-31 07:42:53 -0600310 self.queue = queue.Queue()
311 self.out_queue = queue.Queue()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000312 for i in range(self.num_threads):
Simon Glasseb70a2c2020-04-09 15:08:51 -0600313 t = builderthread.BuilderThread(self, i, mrproper,
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600314 per_board_out_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000315 t.setDaemon(True)
316 t.start()
317 self.threads.append(t)
318
Simon Glass190064b2014-08-09 15:33:00 -0600319 t = builderthread.ResultThread(self)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000320 t.setDaemon(True)
321 t.start()
322 self.threads.append(t)
323
324 ignore_lines = ['(make.*Waiting for unfinished)', '(Segmentation fault)']
325 self.re_make_err = re.compile('|'.join(ignore_lines))
326
Simon Glass2f256642016-09-18 16:48:37 -0600327 # Handle existing graceful with SIGINT / Ctrl-C
328 signal.signal(signal.SIGINT, self.signal_handler)
329
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000330 def __del__(self):
331 """Get rid of all threads created by the builder"""
332 for t in self.threads:
333 del t
334
Simon Glass2f256642016-09-18 16:48:37 -0600335 def signal_handler(self, signal, frame):
336 sys.exit(1)
337
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600338 def SetDisplayOptions(self, show_errors=False, show_sizes=False,
Simon Glassed966652014-08-28 09:43:43 -0600339 show_detail=False, show_bloat=False,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000340 list_error_boards=False, show_config=False,
341 show_environment=False):
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600342 """Setup display options for the builder.
343
344 show_errors: True to show summarised error/warning info
345 show_sizes: Show size deltas
Simon Glassf9c094b2020-03-18 09:42:43 -0600346 show_detail: Show size delta detail for each board if show_sizes
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600347 show_bloat: Show detail for each function
Simon Glassed966652014-08-28 09:43:43 -0600348 list_error_boards: Show the boards which caused each error/warning
Simon Glass843312d2015-02-05 22:06:15 -0700349 show_config: Show config deltas
Alex Kiernan48ae4122018-05-31 04:48:34 +0000350 show_environment: Show environment deltas
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600351 """
352 self._show_errors = show_errors
353 self._show_sizes = show_sizes
354 self._show_detail = show_detail
355 self._show_bloat = show_bloat
Simon Glassed966652014-08-28 09:43:43 -0600356 self._list_error_boards = list_error_boards
Simon Glass843312d2015-02-05 22:06:15 -0700357 self._show_config = show_config
Alex Kiernan48ae4122018-05-31 04:48:34 +0000358 self._show_environment = show_environment
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600359
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000360 def _AddTimestamp(self):
361 """Add a new timestamp to the list and record the build period.
362
363 The build period is the length of time taken to perform a single
364 build (one board, one commit).
365 """
366 now = datetime.now()
367 self._timestamps.append(now)
368 count = len(self._timestamps)
369 delta = self._timestamps[-1] - self._timestamps[0]
370 seconds = delta.total_seconds()
371
372 # If we have enough data, estimate build period (time taken for a
373 # single build) and therefore completion time.
374 if count > 1 and self._next_delay_update < now:
375 self._next_delay_update = now + timedelta(seconds=2)
376 if seconds > 0:
377 self._build_period = float(seconds) / count
378 todo = self.count - self.upto
379 self._complete_delay = timedelta(microseconds=
380 self._build_period * todo * 1000000)
381 # Round it
382 self._complete_delay -= timedelta(
383 microseconds=self._complete_delay.microseconds)
384
385 if seconds > 60:
386 self._timestamps.popleft()
387 count -= 1
388
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000389 def SelectCommit(self, commit, checkout=True):
390 """Checkout the selected commit for this build
391 """
392 self.commit = commit
393 if checkout and self.checkout:
394 gitutil.Checkout(commit.hash)
395
396 def Make(self, commit, brd, stage, cwd, *args, **kwargs):
397 """Run make
398
399 Args:
400 commit: Commit object that is being built
401 brd: Board object that is being built
Roger Meierfd18a892014-08-20 22:10:29 +0200402 stage: Stage that we are at (mrproper, config, build)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000403 cwd: Directory where make should be run
404 args: Arguments to pass to make
405 kwargs: Arguments to pass to command.RunPipe()
406 """
Masahiro Yamada99796922014-07-22 11:19:09 +0900407 cmd = [self.gnu_make] + list(args)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000408 result = command.RunPipe([cmd], capture=True, capture_stderr=True,
Simon Glasse62a24c2018-09-17 23:55:42 -0600409 cwd=cwd, raise_on_error=False, infile='/dev/null', **kwargs)
Simon Glass40f11fc2015-02-05 22:06:12 -0700410 if self.verbose_build:
411 result.stdout = '%s\n' % (' '.join(cmd)) + result.stdout
412 result.combined = '%s\n' % (' '.join(cmd)) + result.combined
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000413 return result
414
415 def ProcessResult(self, result):
416 """Process the result of a build, showing progress information
417
418 Args:
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600419 result: A CommandResult object, which indicates the result for
420 a single build
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000421 """
422 col = terminal.Color()
423 if result:
424 target = result.brd.target
425
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000426 self.upto += 1
427 if result.return_code != 0:
428 self.fail += 1
429 elif result.stderr:
430 self.warned += 1
431 if result.already_done:
432 self.already_done += 1
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600433 if self._verbose:
Simon Glass102969bb2020-04-09 15:08:42 -0600434 terminal.PrintClear()
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600435 boards_selected = {target : result.brd}
436 self.ResetResultSummary(boards_selected)
437 self.ProduceResultSummary(result.commit_upto, self.commits,
438 boards_selected)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000439 else:
440 target = '(starting)'
441
442 # Display separate counts for ok, warned and fail
443 ok = self.upto - self.warned - self.fail
444 line = '\r' + self.col.Color(self.col.GREEN, '%5d' % ok)
445 line += self.col.Color(self.col.YELLOW, '%5d' % self.warned)
446 line += self.col.Color(self.col.RED, '%5d' % self.fail)
447
Simon Glass6eb76ca2020-04-09 15:08:45 -0600448 line += ' /%-5d ' % self.count
449 remaining = self.count - self.upto
450 if remaining:
451 line += self.col.Color(self.col.MAGENTA, ' -%-5d ' % remaining)
452 else:
453 line += ' ' * 8
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000454
455 # Add our current completion time estimate
456 self._AddTimestamp()
457 if self._complete_delay:
Simon Glass6eb76ca2020-04-09 15:08:45 -0600458 line += '%s : ' % self._complete_delay
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000459
Simon Glass6eb76ca2020-04-09 15:08:45 -0600460 line += target
Simon Glass102969bb2020-04-09 15:08:42 -0600461 terminal.PrintClear()
Simon Glass95ed0a22020-04-09 15:08:46 -0600462 Print(line, newline=False, limit_to_line=True)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000463
464 def _GetOutputDir(self, commit_upto):
465 """Get the name of the output directory for a commit number
466
467 The output directory is typically .../<branch>/<commit>.
468
469 Args:
470 commit_upto: Commit number to use (0..self.count-1)
471 """
Simon Glass5971ab52014-12-01 17:33:55 -0700472 commit_dir = None
Simon Glassfea58582014-08-09 15:32:59 -0600473 if self.commits:
474 commit = self.commits[commit_upto]
475 subject = commit.subject.translate(trans_valid_chars)
Simon Glass925f6ad2020-03-18 09:42:45 -0600476 # See _GetOutputSpaceRemovals() which parses this name
Simon Glassfea58582014-08-09 15:32:59 -0600477 commit_dir = ('%02d_of_%02d_g%s_%s' % (commit_upto + 1,
478 self.commit_count, commit.hash, subject[:20]))
Simon Glass5971ab52014-12-01 17:33:55 -0700479 elif not self.no_subdirs:
Simon Glassfea58582014-08-09 15:32:59 -0600480 commit_dir = 'current'
Simon Glass5971ab52014-12-01 17:33:55 -0700481 if not commit_dir:
482 return self.base_dir
483 return os.path.join(self.base_dir, commit_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000484
485 def GetBuildDir(self, commit_upto, target):
486 """Get the name of the build directory for a commit number
487
488 The build directory is typically .../<branch>/<commit>/<target>.
489
490 Args:
491 commit_upto: Commit number to use (0..self.count-1)
492 target: Target name
493 """
494 output_dir = self._GetOutputDir(commit_upto)
495 return os.path.join(output_dir, target)
496
497 def GetDoneFile(self, commit_upto, target):
498 """Get the name of the done file for a commit number
499
500 Args:
501 commit_upto: Commit number to use (0..self.count-1)
502 target: Target name
503 """
504 return os.path.join(self.GetBuildDir(commit_upto, target), 'done')
505
506 def GetSizesFile(self, commit_upto, target):
507 """Get the name of the sizes file for a commit number
508
509 Args:
510 commit_upto: Commit number to use (0..self.count-1)
511 target: Target name
512 """
513 return os.path.join(self.GetBuildDir(commit_upto, target), 'sizes')
514
515 def GetFuncSizesFile(self, commit_upto, target, elf_fname):
516 """Get the name of the funcsizes file for a commit number and ELF file
517
518 Args:
519 commit_upto: Commit number to use (0..self.count-1)
520 target: Target name
521 elf_fname: Filename of elf image
522 """
523 return os.path.join(self.GetBuildDir(commit_upto, target),
524 '%s.sizes' % elf_fname.replace('/', '-'))
525
526 def GetObjdumpFile(self, commit_upto, target, elf_fname):
527 """Get the name of the objdump file for a commit number and ELF file
528
529 Args:
530 commit_upto: Commit number to use (0..self.count-1)
531 target: Target name
532 elf_fname: Filename of elf image
533 """
534 return os.path.join(self.GetBuildDir(commit_upto, target),
535 '%s.objdump' % elf_fname.replace('/', '-'))
536
537 def GetErrFile(self, commit_upto, target):
538 """Get the name of the err file for a commit number
539
540 Args:
541 commit_upto: Commit number to use (0..self.count-1)
542 target: Target name
543 """
544 output_dir = self.GetBuildDir(commit_upto, target)
545 return os.path.join(output_dir, 'err')
546
547 def FilterErrors(self, lines):
548 """Filter out errors in which we have no interest
549
550 We should probably use map().
551
552 Args:
553 lines: List of error lines, each a string
554 Returns:
555 New list with only interesting lines included
556 """
557 out_lines = []
558 for line in lines:
559 if not self.re_make_err.search(line):
560 out_lines.append(line)
561 return out_lines
562
563 def ReadFuncSizes(self, fname, fd):
564 """Read function sizes from the output of 'nm'
565
566 Args:
567 fd: File containing data to read
568 fname: Filename we are reading from (just for errors)
569
570 Returns:
571 Dictionary containing size of each function in bytes, indexed by
572 function name.
573 """
574 sym = {}
575 for line in fd.readlines():
576 try:
Tom Rinid08c38c2019-12-06 15:31:31 -0500577 if line.strip():
578 size, type, name = line[:-1].split()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000579 except:
Simon Glass4653a882014-09-05 19:00:07 -0600580 Print("Invalid line in file '%s': '%s'" % (fname, line[:-1]))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000581 continue
582 if type in 'tTdDbB':
583 # function names begin with '.' on 64-bit powerpc
584 if '.' in name[1:]:
585 name = 'static.' + name.split('.')[0]
586 sym[name] = sym.get(name, 0) + int(size, 16)
587 return sym
588
Simon Glass843312d2015-02-05 22:06:15 -0700589 def _ProcessConfig(self, fname):
590 """Read in a .config, autoconf.mk or autoconf.h file
591
592 This function handles all config file types. It ignores comments and
593 any #defines which don't start with CONFIG_.
594
595 Args:
596 fname: Filename to read
597
598 Returns:
599 Dictionary:
600 key: Config name (e.g. CONFIG_DM)
601 value: Config value (e.g. 1)
602 """
603 config = {}
604 if os.path.exists(fname):
605 with open(fname) as fd:
606 for line in fd:
607 line = line.strip()
608 if line.startswith('#define'):
609 values = line[8:].split(' ', 1)
610 if len(values) > 1:
611 key, value = values
612 else:
613 key = values[0]
Simon Glassb464f8e2016-11-13 14:25:53 -0700614 value = '1' if self.squash_config_y else ''
Simon Glass843312d2015-02-05 22:06:15 -0700615 if not key.startswith('CONFIG_'):
616 continue
617 elif not line or line[0] in ['#', '*', '/']:
618 continue
619 else:
620 key, value = line.split('=', 1)
Simon Glassb464f8e2016-11-13 14:25:53 -0700621 if self.squash_config_y and value == 'y':
622 value = '1'
Simon Glass843312d2015-02-05 22:06:15 -0700623 config[key] = value
624 return config
625
Alex Kiernan48ae4122018-05-31 04:48:34 +0000626 def _ProcessEnvironment(self, fname):
627 """Read in a uboot.env file
628
629 This function reads in environment variables from a file.
630
631 Args:
632 fname: Filename to read
633
634 Returns:
635 Dictionary:
636 key: environment variable (e.g. bootlimit)
637 value: value of environment variable (e.g. 1)
638 """
639 environment = {}
640 if os.path.exists(fname):
641 with open(fname) as fd:
642 for line in fd.read().split('\0'):
643 try:
644 key, value = line.split('=', 1)
645 environment[key] = value
646 except ValueError:
647 # ignore lines we can't parse
648 pass
649 return environment
650
Simon Glass843312d2015-02-05 22:06:15 -0700651 def GetBuildOutcome(self, commit_upto, target, read_func_sizes,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000652 read_config, read_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000653 """Work out the outcome of a build.
654
655 Args:
656 commit_upto: Commit number to check (0..n-1)
657 target: Target board to check
658 read_func_sizes: True to read function size information
Simon Glass843312d2015-02-05 22:06:15 -0700659 read_config: True to read .config and autoconf.h files
Alex Kiernan48ae4122018-05-31 04:48:34 +0000660 read_environment: True to read uboot.env files
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000661
662 Returns:
663 Outcome object
664 """
665 done_file = self.GetDoneFile(commit_upto, target)
666 sizes_file = self.GetSizesFile(commit_upto, target)
667 sizes = {}
668 func_sizes = {}
Simon Glass843312d2015-02-05 22:06:15 -0700669 config = {}
Alex Kiernan48ae4122018-05-31 04:48:34 +0000670 environment = {}
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000671 if os.path.exists(done_file):
672 with open(done_file, 'r') as fd:
Simon Glass347ea0b2019-04-26 19:02:23 -0600673 try:
674 return_code = int(fd.readline())
675 except ValueError:
676 # The file may be empty due to running out of disk space.
677 # Try a rebuild
678 return_code = 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000679 err_lines = []
680 err_file = self.GetErrFile(commit_upto, target)
681 if os.path.exists(err_file):
682 with open(err_file, 'r') as fd:
683 err_lines = self.FilterErrors(fd.readlines())
684
685 # Decide whether the build was ok, failed or created warnings
686 if return_code:
687 rc = OUTCOME_ERROR
688 elif len(err_lines):
689 rc = OUTCOME_WARNING
690 else:
691 rc = OUTCOME_OK
692
693 # Convert size information to our simple format
694 if os.path.exists(sizes_file):
695 with open(sizes_file, 'r') as fd:
696 for line in fd.readlines():
697 values = line.split()
698 rodata = 0
699 if len(values) > 6:
700 rodata = int(values[6], 16)
701 size_dict = {
702 'all' : int(values[0]) + int(values[1]) +
703 int(values[2]),
704 'text' : int(values[0]) - rodata,
705 'data' : int(values[1]),
706 'bss' : int(values[2]),
707 'rodata' : rodata,
708 }
709 sizes[values[5]] = size_dict
710
711 if read_func_sizes:
712 pattern = self.GetFuncSizesFile(commit_upto, target, '*')
713 for fname in glob.glob(pattern):
714 with open(fname, 'r') as fd:
715 dict_name = os.path.basename(fname).replace('.sizes',
716 '')
717 func_sizes[dict_name] = self.ReadFuncSizes(fname, fd)
718
Simon Glass843312d2015-02-05 22:06:15 -0700719 if read_config:
720 output_dir = self.GetBuildDir(commit_upto, target)
Simon Glassb464f8e2016-11-13 14:25:53 -0700721 for name in self.config_filenames:
Simon Glass843312d2015-02-05 22:06:15 -0700722 fname = os.path.join(output_dir, name)
723 config[name] = self._ProcessConfig(fname)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000724
Alex Kiernan48ae4122018-05-31 04:48:34 +0000725 if read_environment:
726 output_dir = self.GetBuildDir(commit_upto, target)
727 fname = os.path.join(output_dir, 'uboot.env')
728 environment = self._ProcessEnvironment(fname)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000729
Alex Kiernan48ae4122018-05-31 04:48:34 +0000730 return Builder.Outcome(rc, err_lines, sizes, func_sizes, config,
731 environment)
732
733 return Builder.Outcome(OUTCOME_UNKNOWN, [], {}, {}, {}, {})
Simon Glass843312d2015-02-05 22:06:15 -0700734
735 def GetResultSummary(self, boards_selected, commit_upto, read_func_sizes,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000736 read_config, read_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000737 """Calculate a summary of the results of building a commit.
738
739 Args:
740 board_selected: Dict containing boards to summarise
741 commit_upto: Commit number to summarize (0..self.count-1)
742 read_func_sizes: True to read function size information
Simon Glass843312d2015-02-05 22:06:15 -0700743 read_config: True to read .config and autoconf.h files
Alex Kiernan48ae4122018-05-31 04:48:34 +0000744 read_environment: True to read uboot.env files
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000745
746 Returns:
747 Tuple:
748 Dict containing boards which passed building this commit.
749 keyed by board.target
Simon Glasse30965d2014-08-28 09:43:44 -0600750 List containing a summary of error lines
Simon Glassed966652014-08-28 09:43:43 -0600751 Dict keyed by error line, containing a list of the Board
752 objects with that error
Simon Glasse30965d2014-08-28 09:43:44 -0600753 List containing a summary of warning lines
754 Dict keyed by error line, containing a list of the Board
755 objects with that warning
Simon Glass8270e3c2015-08-25 21:52:14 -0600756 Dictionary keyed by board.target. Each value is a dictionary:
757 key: filename - e.g. '.config'
Simon Glass843312d2015-02-05 22:06:15 -0700758 value is itself a dictionary:
759 key: config name
760 value: config value
Alex Kiernan48ae4122018-05-31 04:48:34 +0000761 Dictionary keyed by board.target. Each value is a dictionary:
762 key: environment variable
763 value: value of environment variable
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000764 """
Simon Glasse30965d2014-08-28 09:43:44 -0600765 def AddLine(lines_summary, lines_boards, line, board):
766 line = line.rstrip()
767 if line in lines_boards:
768 lines_boards[line].append(board)
769 else:
770 lines_boards[line] = [board]
771 lines_summary.append(line)
772
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000773 board_dict = {}
774 err_lines_summary = []
Simon Glassed966652014-08-28 09:43:43 -0600775 err_lines_boards = {}
Simon Glasse30965d2014-08-28 09:43:44 -0600776 warn_lines_summary = []
777 warn_lines_boards = {}
Simon Glass843312d2015-02-05 22:06:15 -0700778 config = {}
Alex Kiernan48ae4122018-05-31 04:48:34 +0000779 environment = {}
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000780
Simon Glassc05aa032019-10-31 07:42:53 -0600781 for board in boards_selected.values():
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000782 outcome = self.GetBuildOutcome(commit_upto, board.target,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000783 read_func_sizes, read_config,
784 read_environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000785 board_dict[board.target] = outcome
Simon Glasse30965d2014-08-28 09:43:44 -0600786 last_func = None
787 last_was_warning = False
788 for line in outcome.err_lines:
789 if line:
790 if (self._re_function.match(line) or
791 self._re_files.match(line)):
792 last_func = line
Simon Glassed966652014-08-28 09:43:43 -0600793 else:
Simon Glass2d483332018-11-06 16:02:11 -0700794 is_warning = (self._re_warning.match(line) or
795 self._re_dtb_warning.match(line))
Simon Glasse30965d2014-08-28 09:43:44 -0600796 is_note = self._re_note.match(line)
797 if is_warning or (last_was_warning and is_note):
798 if last_func:
799 AddLine(warn_lines_summary, warn_lines_boards,
800 last_func, board)
801 AddLine(warn_lines_summary, warn_lines_boards,
802 line, board)
803 else:
804 if last_func:
805 AddLine(err_lines_summary, err_lines_boards,
806 last_func, board)
807 AddLine(err_lines_summary, err_lines_boards,
808 line, board)
809 last_was_warning = is_warning
810 last_func = None
Simon Glassb464f8e2016-11-13 14:25:53 -0700811 tconfig = Config(self.config_filenames, board.target)
812 for fname in self.config_filenames:
Simon Glass843312d2015-02-05 22:06:15 -0700813 if outcome.config:
Simon Glassc05aa032019-10-31 07:42:53 -0600814 for key, value in outcome.config[fname].items():
Simon Glass8270e3c2015-08-25 21:52:14 -0600815 tconfig.Add(fname, key, value)
816 config[board.target] = tconfig
Simon Glass843312d2015-02-05 22:06:15 -0700817
Alex Kiernan48ae4122018-05-31 04:48:34 +0000818 tenvironment = Environment(board.target)
819 if outcome.environment:
Simon Glassc05aa032019-10-31 07:42:53 -0600820 for key, value in outcome.environment.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +0000821 tenvironment.Add(key, value)
822 environment[board.target] = tenvironment
823
Simon Glasse30965d2014-08-28 09:43:44 -0600824 return (board_dict, err_lines_summary, err_lines_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000825 warn_lines_summary, warn_lines_boards, config, environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000826
827 def AddOutcome(self, board_dict, arch_list, changes, char, color):
828 """Add an output to our list of outcomes for each architecture
829
830 This simple function adds failing boards (changes) to the
831 relevant architecture string, so we can print the results out
832 sorted by architecture.
833
834 Args:
835 board_dict: Dict containing all boards
836 arch_list: Dict keyed by arch name. Value is a string containing
837 a list of board names which failed for that arch.
838 changes: List of boards to add to arch_list
839 color: terminal.Colour object
840 """
841 done_arch = {}
842 for target in changes:
843 if target in board_dict:
844 arch = board_dict[target].arch
845 else:
846 arch = 'unknown'
847 str = self.col.Color(color, ' ' + target)
848 if not arch in done_arch:
Simon Glass63c619e2015-02-05 22:06:11 -0700849 str = ' %s %s' % (self.col.Color(color, char), str)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000850 done_arch[arch] = True
851 if not arch in arch_list:
852 arch_list[arch] = str
853 else:
854 arch_list[arch] += str
855
856
857 def ColourNum(self, num):
858 color = self.col.RED if num > 0 else self.col.GREEN
859 if num == 0:
860 return '0'
861 return self.col.Color(color, str(num))
862
863 def ResetResultSummary(self, board_selected):
864 """Reset the results summary ready for use.
865
866 Set up the base board list to be all those selected, and set the
867 error lines to empty.
868
869 Following this, calls to PrintResultSummary() will use this
870 information to work out what has changed.
871
872 Args:
873 board_selected: Dict containing boards to summarise, keyed by
874 board.target
875 """
876 self._base_board_dict = {}
877 for board in board_selected:
Alex Kiernan48ae4122018-05-31 04:48:34 +0000878 self._base_board_dict[board] = Builder.Outcome(0, [], [], {}, {},
879 {})
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000880 self._base_err_lines = []
Simon Glasse30965d2014-08-28 09:43:44 -0600881 self._base_warn_lines = []
882 self._base_err_line_boards = {}
883 self._base_warn_line_boards = {}
Simon Glass8270e3c2015-08-25 21:52:14 -0600884 self._base_config = None
Alex Kiernan48ae4122018-05-31 04:48:34 +0000885 self._base_environment = None
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000886
887 def PrintFuncSizeDetail(self, fname, old, new):
888 grow, shrink, add, remove, up, down = 0, 0, 0, 0, 0, 0
889 delta, common = [], {}
890
891 for a in old:
892 if a in new:
893 common[a] = 1
894
895 for name in old:
896 if name not in common:
897 remove += 1
898 down += old[name]
899 delta.append([-old[name], name])
900
901 for name in new:
902 if name not in common:
903 add += 1
904 up += new[name]
905 delta.append([new[name], name])
906
907 for name in common:
908 diff = new.get(name, 0) - old.get(name, 0)
909 if diff > 0:
910 grow, up = grow + 1, up + diff
911 elif diff < 0:
912 shrink, down = shrink + 1, down - diff
913 delta.append([diff, name])
914
915 delta.sort()
916 delta.reverse()
917
918 args = [add, -remove, grow, -shrink, up, -down, up - down]
Tom Rinid5686a62017-05-22 13:48:52 -0400919 if max(args) == 0 and min(args) == 0:
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000920 return
921 args = [self.ColourNum(x) for x in args]
922 indent = ' ' * 15
Simon Glass4653a882014-09-05 19:00:07 -0600923 Print('%s%s: add: %s/%s, grow: %s/%s bytes: %s/%s (%s)' %
924 tuple([indent, self.col.Color(self.col.YELLOW, fname)] + args))
925 Print('%s %-38s %7s %7s %+7s' % (indent, 'function', 'old', 'new',
926 'delta'))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000927 for diff, name in delta:
928 if diff:
929 color = self.col.RED if diff > 0 else self.col.GREEN
930 msg = '%s %-38s %7s %7s %+7d' % (indent, name,
931 old.get(name, '-'), new.get(name,'-'), diff)
Simon Glass4653a882014-09-05 19:00:07 -0600932 Print(msg, colour=color)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000933
934
935 def PrintSizeDetail(self, target_list, show_bloat):
936 """Show details size information for each board
937
938 Args:
939 target_list: List of targets, each a dict containing:
940 'target': Target name
941 'total_diff': Total difference in bytes across all areas
942 <part_name>: Difference for that part
943 show_bloat: Show detail for each function
944 """
945 targets_by_diff = sorted(target_list, reverse=True,
946 key=lambda x: x['_total_diff'])
947 for result in targets_by_diff:
948 printed_target = False
949 for name in sorted(result):
950 diff = result[name]
951 if name.startswith('_'):
952 continue
953 if diff != 0:
954 color = self.col.RED if diff > 0 else self.col.GREEN
955 msg = ' %s %+d' % (name, diff)
956 if not printed_target:
Simon Glass4653a882014-09-05 19:00:07 -0600957 Print('%10s %-15s:' % ('', result['_target']),
958 newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000959 printed_target = True
Simon Glass4653a882014-09-05 19:00:07 -0600960 Print(msg, colour=color, newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000961 if printed_target:
Simon Glass4653a882014-09-05 19:00:07 -0600962 Print()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000963 if show_bloat:
964 target = result['_target']
965 outcome = result['_outcome']
966 base_outcome = self._base_board_dict[target]
967 for fname in outcome.func_sizes:
968 self.PrintFuncSizeDetail(fname,
969 base_outcome.func_sizes[fname],
970 outcome.func_sizes[fname])
971
972
973 def PrintSizeSummary(self, board_selected, board_dict, show_detail,
974 show_bloat):
975 """Print a summary of image sizes broken down by section.
976
977 The summary takes the form of one line per architecture. The
978 line contains deltas for each of the sections (+ means the section
Flavio Suligoi9de5c392020-01-29 09:56:05 +0100979 got bigger, - means smaller). The numbers are the average number
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000980 of bytes that a board in this section increased by.
981
982 For example:
983 powerpc: (622 boards) text -0.0
984 arm: (285 boards) text -0.0
985 nds32: (3 boards) text -8.0
986
987 Args:
988 board_selected: Dict containing boards to summarise, keyed by
989 board.target
990 board_dict: Dict containing boards for which we built this
991 commit, keyed by board.target. The value is an Outcome object.
Simon Glassf9c094b2020-03-18 09:42:43 -0600992 show_detail: Show size delta detail for each board
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000993 show_bloat: Show detail for each function
994 """
995 arch_list = {}
996 arch_count = {}
997
998 # Calculate changes in size for different image parts
999 # The previous sizes are in Board.sizes, for each board
1000 for target in board_dict:
1001 if target not in board_selected:
1002 continue
1003 base_sizes = self._base_board_dict[target].sizes
1004 outcome = board_dict[target]
1005 sizes = outcome.sizes
1006
1007 # Loop through the list of images, creating a dict of size
1008 # changes for each image/part. We end up with something like
1009 # {'target' : 'snapper9g45, 'data' : 5, 'u-boot-spl:text' : -4}
1010 # which means that U-Boot data increased by 5 bytes and SPL
1011 # text decreased by 4.
1012 err = {'_target' : target}
1013 for image in sizes:
1014 if image in base_sizes:
1015 base_image = base_sizes[image]
1016 # Loop through the text, data, bss parts
1017 for part in sorted(sizes[image]):
1018 diff = sizes[image][part] - base_image[part]
1019 col = None
1020 if diff:
1021 if image == 'u-boot':
1022 name = part
1023 else:
1024 name = image + ':' + part
1025 err[name] = diff
1026 arch = board_selected[target].arch
1027 if not arch in arch_count:
1028 arch_count[arch] = 1
1029 else:
1030 arch_count[arch] += 1
1031 if not sizes:
1032 pass # Only add to our list when we have some stats
1033 elif not arch in arch_list:
1034 arch_list[arch] = [err]
1035 else:
1036 arch_list[arch].append(err)
1037
1038 # We now have a list of image size changes sorted by arch
1039 # Print out a summary of these
Simon Glassc05aa032019-10-31 07:42:53 -06001040 for arch, target_list in arch_list.items():
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001041 # Get total difference for each type
1042 totals = {}
1043 for result in target_list:
1044 total = 0
Simon Glassc05aa032019-10-31 07:42:53 -06001045 for name, diff in result.items():
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001046 if name.startswith('_'):
1047 continue
1048 total += diff
1049 if name in totals:
1050 totals[name] += diff
1051 else:
1052 totals[name] = diff
1053 result['_total_diff'] = total
1054 result['_outcome'] = board_dict[result['_target']]
1055
1056 count = len(target_list)
1057 printed_arch = False
1058 for name in sorted(totals):
1059 diff = totals[name]
1060 if diff:
1061 # Display the average difference in this name for this
1062 # architecture
1063 avg_diff = float(diff) / count
1064 color = self.col.RED if avg_diff > 0 else self.col.GREEN
1065 msg = ' %s %+1.1f' % (name, avg_diff)
1066 if not printed_arch:
Simon Glass4653a882014-09-05 19:00:07 -06001067 Print('%10s: (for %d/%d boards)' % (arch, count,
1068 arch_count[arch]), newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001069 printed_arch = True
Simon Glass4653a882014-09-05 19:00:07 -06001070 Print(msg, colour=color, newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001071
1072 if printed_arch:
Simon Glass4653a882014-09-05 19:00:07 -06001073 Print()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001074 if show_detail:
1075 self.PrintSizeDetail(target_list, show_bloat)
1076
1077
1078 def PrintResultSummary(self, board_selected, board_dict, err_lines,
Simon Glasse30965d2014-08-28 09:43:44 -06001079 err_line_boards, warn_lines, warn_line_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001080 config, environment, show_sizes, show_detail,
1081 show_bloat, show_config, show_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001082 """Compare results with the base results and display delta.
1083
1084 Only boards mentioned in board_selected will be considered. This
1085 function is intended to be called repeatedly with the results of
1086 each commit. It therefore shows a 'diff' between what it saw in
1087 the last call and what it sees now.
1088
1089 Args:
1090 board_selected: Dict containing boards to summarise, keyed by
1091 board.target
1092 board_dict: Dict containing boards for which we built this
1093 commit, keyed by board.target. The value is an Outcome object.
1094 err_lines: A list of errors for this commit, or [] if there is
1095 none, or we don't want to print errors
Simon Glassed966652014-08-28 09:43:43 -06001096 err_line_boards: Dict keyed by error line, containing a list of
1097 the Board objects with that error
Simon Glasse30965d2014-08-28 09:43:44 -06001098 warn_lines: A list of warnings for this commit, or [] if there is
1099 none, or we don't want to print errors
1100 warn_line_boards: Dict keyed by warning line, containing a list of
1101 the Board objects with that warning
Simon Glass843312d2015-02-05 22:06:15 -07001102 config: Dictionary keyed by filename - e.g. '.config'. Each
1103 value is itself a dictionary:
1104 key: config name
1105 value: config value
Alex Kiernan48ae4122018-05-31 04:48:34 +00001106 environment: Dictionary keyed by environment variable, Each
1107 value is the value of environment variable.
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001108 show_sizes: Show image size deltas
Simon Glassf9c094b2020-03-18 09:42:43 -06001109 show_detail: Show size delta detail for each board if show_sizes
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001110 show_bloat: Show detail for each function
Simon Glass843312d2015-02-05 22:06:15 -07001111 show_config: Show config changes
Alex Kiernan48ae4122018-05-31 04:48:34 +00001112 show_environment: Show environment changes
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001113 """
Simon Glasse30965d2014-08-28 09:43:44 -06001114 def _BoardList(line, line_boards):
Simon Glassed966652014-08-28 09:43:43 -06001115 """Helper function to get a line of boards containing a line
1116
1117 Args:
1118 line: Error line to search for
Simon Glass35d696d2020-04-09 15:08:36 -06001119 line_boards: boards to search, each a Board
Simon Glassed966652014-08-28 09:43:43 -06001120 Return:
Simon Glass35d696d2020-04-09 15:08:36 -06001121 List of boards with that error line, or [] if the user has not
1122 requested such a list
Simon Glassed966652014-08-28 09:43:43 -06001123 """
Simon Glass35d696d2020-04-09 15:08:36 -06001124 boards = []
1125 board_set = set()
Simon Glassed966652014-08-28 09:43:43 -06001126 if self._list_error_boards:
Simon Glasse30965d2014-08-28 09:43:44 -06001127 for board in line_boards[line]:
Simon Glass35d696d2020-04-09 15:08:36 -06001128 if not board in board_set:
1129 boards.append(board)
1130 board_set.add(board)
1131 return boards
Simon Glassed966652014-08-28 09:43:43 -06001132
Simon Glasse30965d2014-08-28 09:43:44 -06001133 def _CalcErrorDelta(base_lines, base_line_boards, lines, line_boards,
1134 char):
Simon Glass35d696d2020-04-09 15:08:36 -06001135 """Calculate the required output based on changes in errors
1136
1137 Args:
1138 base_lines: List of errors/warnings for previous commit
1139 base_line_boards: Dict keyed by error line, containing a list
1140 of the Board objects with that error in the previous commit
1141 lines: List of errors/warning for this commit, each a str
1142 line_boards: Dict keyed by error line, containing a list
1143 of the Board objects with that error in this commit
1144 char: Character representing error ('') or warning ('w'). The
1145 broken ('+') or fixed ('-') characters are added in this
1146 function
1147
1148 Returns:
1149 Tuple
1150 List of ErrLine objects for 'better' lines
1151 List of ErrLine objects for 'worse' lines
1152 """
Simon Glasse30965d2014-08-28 09:43:44 -06001153 better_lines = []
1154 worse_lines = []
1155 for line in lines:
1156 if line not in base_lines:
Simon Glass35d696d2020-04-09 15:08:36 -06001157 errline = ErrLine(char + '+', _BoardList(line, line_boards),
1158 line)
1159 worse_lines.append(errline)
Simon Glasse30965d2014-08-28 09:43:44 -06001160 for line in base_lines:
1161 if line not in lines:
Simon Glass35d696d2020-04-09 15:08:36 -06001162 errline = ErrLine(char + '-',
1163 _BoardList(line, base_line_boards), line)
1164 better_lines.append(errline)
Simon Glasse30965d2014-08-28 09:43:44 -06001165 return better_lines, worse_lines
1166
Simon Glass843312d2015-02-05 22:06:15 -07001167 def _CalcConfig(delta, name, config):
1168 """Calculate configuration changes
1169
1170 Args:
1171 delta: Type of the delta, e.g. '+'
1172 name: name of the file which changed (e.g. .config)
1173 config: configuration change dictionary
1174 key: config name
1175 value: config value
1176 Returns:
1177 String containing the configuration changes which can be
1178 printed
1179 """
1180 out = ''
1181 for key in sorted(config.keys()):
1182 out += '%s=%s ' % (key, config[key])
Simon Glass8270e3c2015-08-25 21:52:14 -06001183 return '%s %s: %s' % (delta, name, out)
Simon Glass843312d2015-02-05 22:06:15 -07001184
Simon Glass8270e3c2015-08-25 21:52:14 -06001185 def _AddConfig(lines, name, config_plus, config_minus, config_change):
1186 """Add changes in configuration to a list
Simon Glass843312d2015-02-05 22:06:15 -07001187
1188 Args:
Simon Glass8270e3c2015-08-25 21:52:14 -06001189 lines: list to add to
1190 name: config file name
Simon Glass843312d2015-02-05 22:06:15 -07001191 config_plus: configurations added, dictionary
1192 key: config name
1193 value: config value
1194 config_minus: configurations removed, dictionary
1195 key: config name
1196 value: config value
1197 config_change: configurations changed, dictionary
1198 key: config name
1199 value: config value
1200 """
1201 if config_plus:
Simon Glass8270e3c2015-08-25 21:52:14 -06001202 lines.append(_CalcConfig('+', name, config_plus))
Simon Glass843312d2015-02-05 22:06:15 -07001203 if config_minus:
Simon Glass8270e3c2015-08-25 21:52:14 -06001204 lines.append(_CalcConfig('-', name, config_minus))
Simon Glass843312d2015-02-05 22:06:15 -07001205 if config_change:
Simon Glass8270e3c2015-08-25 21:52:14 -06001206 lines.append(_CalcConfig('c', name, config_change))
1207
1208 def _OutputConfigInfo(lines):
1209 for line in lines:
1210 if not line:
1211 continue
1212 if line[0] == '+':
1213 col = self.col.GREEN
1214 elif line[0] == '-':
1215 col = self.col.RED
1216 elif line[0] == 'c':
1217 col = self.col.YELLOW
1218 Print(' ' + line, newline=True, colour=col)
1219
Simon Glassb206d872020-04-09 15:08:28 -06001220 def _OutputErrLines(err_lines, colour):
1221 """Output the line of error/warning lines, if not empty
1222
1223 Also increments self._error_lines if err_lines not empty
1224
1225 Args:
Simon Glass35d696d2020-04-09 15:08:36 -06001226 err_lines: List of ErrLine objects, each an error or warning
1227 line, possibly including a list of boards with that
1228 error/warning
Simon Glassb206d872020-04-09 15:08:28 -06001229 colour: Colour to use for output
1230 """
1231 if err_lines:
Simon Glass8c9a2672020-04-09 15:08:37 -06001232 out_list = []
Simon Glass35d696d2020-04-09 15:08:36 -06001233 for line in err_lines:
1234 boards = ''
1235 names = [board.target for board in line.boards]
Simon Glass9ef0ceb2020-04-09 15:08:38 -06001236 board_str = ' '.join(names) if names else ''
Simon Glass8c9a2672020-04-09 15:08:37 -06001237 if board_str:
1238 out = self.col.Color(colour, line.char + '(')
1239 out += self.col.Color(self.col.MAGENTA, board_str,
1240 bright=False)
1241 out += self.col.Color(colour, ') %s' % line.errline)
1242 else:
1243 out = self.col.Color(colour, line.char + line.errline)
1244 out_list.append(out)
1245 Print('\n'.join(out_list))
Simon Glassb206d872020-04-09 15:08:28 -06001246 self._error_lines += 1
1247
Simon Glass843312d2015-02-05 22:06:15 -07001248
Simon Glass4cf2b222018-11-06 16:02:12 -07001249 ok_boards = [] # List of boards fixed since last commit
Simon Glass6af71012018-11-06 16:02:13 -07001250 warn_boards = [] # List of boards with warnings since last commit
Simon Glass4cf2b222018-11-06 16:02:12 -07001251 err_boards = [] # List of new broken boards since last commit
1252 new_boards = [] # List of boards that didn't exist last time
1253 unknown_boards = [] # List of boards that were not built
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001254
1255 for target in board_dict:
1256 if target not in board_selected:
1257 continue
1258
1259 # If the board was built last time, add its outcome to a list
1260 if target in self._base_board_dict:
1261 base_outcome = self._base_board_dict[target].rc
1262 outcome = board_dict[target]
1263 if outcome.rc == OUTCOME_UNKNOWN:
Simon Glass4cf2b222018-11-06 16:02:12 -07001264 unknown_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001265 elif outcome.rc < base_outcome:
Simon Glass6af71012018-11-06 16:02:13 -07001266 if outcome.rc == OUTCOME_WARNING:
1267 warn_boards.append(target)
1268 else:
1269 ok_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001270 elif outcome.rc > base_outcome:
Simon Glass6af71012018-11-06 16:02:13 -07001271 if outcome.rc == OUTCOME_WARNING:
1272 warn_boards.append(target)
1273 else:
1274 err_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001275 else:
Simon Glass4cf2b222018-11-06 16:02:12 -07001276 new_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001277
Simon Glassb206d872020-04-09 15:08:28 -06001278 # Get a list of errors and warnings that have appeared, and disappeared
Simon Glasse30965d2014-08-28 09:43:44 -06001279 better_err, worse_err = _CalcErrorDelta(self._base_err_lines,
1280 self._base_err_line_boards, err_lines, err_line_boards, '')
1281 better_warn, worse_warn = _CalcErrorDelta(self._base_warn_lines,
1282 self._base_warn_line_boards, warn_lines, warn_line_boards, 'w')
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001283
1284 # Display results by arch
Simon Glass6af71012018-11-06 16:02:13 -07001285 if any((ok_boards, warn_boards, err_boards, unknown_boards, new_boards,
1286 worse_err, better_err, worse_warn, better_warn)):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001287 arch_list = {}
Simon Glass4cf2b222018-11-06 16:02:12 -07001288 self.AddOutcome(board_selected, arch_list, ok_boards, '',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001289 self.col.GREEN)
Simon Glass6af71012018-11-06 16:02:13 -07001290 self.AddOutcome(board_selected, arch_list, warn_boards, 'w+',
1291 self.col.YELLOW)
Simon Glass4cf2b222018-11-06 16:02:12 -07001292 self.AddOutcome(board_selected, arch_list, err_boards, '+',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001293 self.col.RED)
Simon Glass4cf2b222018-11-06 16:02:12 -07001294 self.AddOutcome(board_selected, arch_list, new_boards, '*', self.col.BLUE)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001295 if self._show_unknown:
Simon Glass4cf2b222018-11-06 16:02:12 -07001296 self.AddOutcome(board_selected, arch_list, unknown_boards, '?',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001297 self.col.MAGENTA)
Simon Glassc05aa032019-10-31 07:42:53 -06001298 for arch, target_list in arch_list.items():
Simon Glass4653a882014-09-05 19:00:07 -06001299 Print('%10s: %s' % (arch, target_list))
Simon Glass28370c12014-08-09 15:33:06 -06001300 self._error_lines += 1
Simon Glassb206d872020-04-09 15:08:28 -06001301 _OutputErrLines(better_err, colour=self.col.GREEN)
1302 _OutputErrLines(worse_err, colour=self.col.RED)
1303 _OutputErrLines(better_warn, colour=self.col.CYAN)
Simon Glass5627bd92020-04-09 15:08:35 -06001304 _OutputErrLines(worse_warn, colour=self.col.YELLOW)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001305
1306 if show_sizes:
1307 self.PrintSizeSummary(board_selected, board_dict, show_detail,
1308 show_bloat)
1309
Alex Kiernan48ae4122018-05-31 04:48:34 +00001310 if show_environment and self._base_environment:
1311 lines = []
1312
1313 for target in board_dict:
1314 if target not in board_selected:
1315 continue
1316
1317 tbase = self._base_environment[target]
1318 tenvironment = environment[target]
1319 environment_plus = {}
1320 environment_minus = {}
1321 environment_change = {}
1322 base = tbase.environment
Simon Glassc05aa032019-10-31 07:42:53 -06001323 for key, value in tenvironment.environment.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001324 if key not in base:
1325 environment_plus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001326 for key, value in base.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001327 if key not in tenvironment.environment:
1328 environment_minus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001329 for key, value in base.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001330 new_value = tenvironment.environment.get(key)
1331 if new_value and value != new_value:
1332 desc = '%s -> %s' % (value, new_value)
1333 environment_change[key] = desc
1334
1335 _AddConfig(lines, target, environment_plus, environment_minus,
1336 environment_change)
1337
1338 _OutputConfigInfo(lines)
1339
Simon Glass8270e3c2015-08-25 21:52:14 -06001340 if show_config and self._base_config:
1341 summary = {}
1342 arch_config_plus = {}
1343 arch_config_minus = {}
1344 arch_config_change = {}
1345 arch_list = []
1346
1347 for target in board_dict:
1348 if target not in board_selected:
Simon Glass843312d2015-02-05 22:06:15 -07001349 continue
Simon Glass8270e3c2015-08-25 21:52:14 -06001350 arch = board_selected[target].arch
1351 if arch not in arch_list:
1352 arch_list.append(arch)
1353
1354 for arch in arch_list:
1355 arch_config_plus[arch] = {}
1356 arch_config_minus[arch] = {}
1357 arch_config_change[arch] = {}
Simon Glassb464f8e2016-11-13 14:25:53 -07001358 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001359 arch_config_plus[arch][name] = {}
1360 arch_config_minus[arch][name] = {}
1361 arch_config_change[arch][name] = {}
1362
1363 for target in board_dict:
1364 if target not in board_selected:
1365 continue
1366
1367 arch = board_selected[target].arch
1368
1369 all_config_plus = {}
1370 all_config_minus = {}
1371 all_config_change = {}
1372 tbase = self._base_config[target]
1373 tconfig = config[target]
1374 lines = []
Simon Glassb464f8e2016-11-13 14:25:53 -07001375 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001376 if not tconfig.config[name]:
1377 continue
1378 config_plus = {}
1379 config_minus = {}
1380 config_change = {}
1381 base = tbase.config[name]
Simon Glassc05aa032019-10-31 07:42:53 -06001382 for key, value in tconfig.config[name].items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001383 if key not in base:
1384 config_plus[key] = value
1385 all_config_plus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001386 for key, value in base.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001387 if key not in tconfig.config[name]:
1388 config_minus[key] = value
1389 all_config_minus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001390 for key, value in base.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001391 new_value = tconfig.config.get(key)
1392 if new_value and value != new_value:
1393 desc = '%s -> %s' % (value, new_value)
1394 config_change[key] = desc
1395 all_config_change[key] = desc
1396
1397 arch_config_plus[arch][name].update(config_plus)
1398 arch_config_minus[arch][name].update(config_minus)
1399 arch_config_change[arch][name].update(config_change)
1400
1401 _AddConfig(lines, name, config_plus, config_minus,
1402 config_change)
1403 _AddConfig(lines, 'all', all_config_plus, all_config_minus,
1404 all_config_change)
1405 summary[target] = '\n'.join(lines)
1406
1407 lines_by_target = {}
Simon Glassc05aa032019-10-31 07:42:53 -06001408 for target, lines in summary.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001409 if lines in lines_by_target:
1410 lines_by_target[lines].append(target)
1411 else:
1412 lines_by_target[lines] = [target]
1413
1414 for arch in arch_list:
1415 lines = []
1416 all_plus = {}
1417 all_minus = {}
1418 all_change = {}
Simon Glassb464f8e2016-11-13 14:25:53 -07001419 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001420 all_plus.update(arch_config_plus[arch][name])
1421 all_minus.update(arch_config_minus[arch][name])
1422 all_change.update(arch_config_change[arch][name])
1423 _AddConfig(lines, name, arch_config_plus[arch][name],
1424 arch_config_minus[arch][name],
1425 arch_config_change[arch][name])
1426 _AddConfig(lines, 'all', all_plus, all_minus, all_change)
1427 #arch_summary[target] = '\n'.join(lines)
1428 if lines:
1429 Print('%s:' % arch)
1430 _OutputConfigInfo(lines)
1431
Simon Glassc05aa032019-10-31 07:42:53 -06001432 for lines, targets in lines_by_target.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001433 if not lines:
1434 continue
1435 Print('%s :' % ' '.join(sorted(targets)))
1436 _OutputConfigInfo(lines.split('\n'))
1437
Simon Glass843312d2015-02-05 22:06:15 -07001438
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001439 # Save our updated information for the next call to this function
1440 self._base_board_dict = board_dict
1441 self._base_err_lines = err_lines
Simon Glasse30965d2014-08-28 09:43:44 -06001442 self._base_warn_lines = warn_lines
1443 self._base_err_line_boards = err_line_boards
1444 self._base_warn_line_boards = warn_line_boards
Simon Glass843312d2015-02-05 22:06:15 -07001445 self._base_config = config
Alex Kiernan48ae4122018-05-31 04:48:34 +00001446 self._base_environment = environment
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001447
1448 # Get a list of boards that did not get built, if needed
1449 not_built = []
1450 for board in board_selected:
1451 if not board in board_dict:
1452 not_built.append(board)
1453 if not_built:
Simon Glass4653a882014-09-05 19:00:07 -06001454 Print("Boards not built (%d): %s" % (len(not_built),
1455 ', '.join(not_built)))
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001456
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001457 def ProduceResultSummary(self, commit_upto, commits, board_selected):
Simon Glasse30965d2014-08-28 09:43:44 -06001458 (board_dict, err_lines, err_line_boards, warn_lines,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001459 warn_line_boards, config, environment) = self.GetResultSummary(
Simon Glassed966652014-08-28 09:43:43 -06001460 board_selected, commit_upto,
Simon Glass843312d2015-02-05 22:06:15 -07001461 read_func_sizes=self._show_bloat,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001462 read_config=self._show_config,
1463 read_environment=self._show_environment)
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001464 if commits:
1465 msg = '%02d: %s' % (commit_upto + 1,
1466 commits[commit_upto].subject)
Simon Glass4653a882014-09-05 19:00:07 -06001467 Print(msg, colour=self.col.BLUE)
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001468 self.PrintResultSummary(board_selected, board_dict,
Simon Glassed966652014-08-28 09:43:43 -06001469 err_lines if self._show_errors else [], err_line_boards,
Simon Glasse30965d2014-08-28 09:43:44 -06001470 warn_lines if self._show_errors else [], warn_line_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001471 config, environment, self._show_sizes, self._show_detail,
1472 self._show_bloat, self._show_config, self._show_environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001473
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001474 def ShowSummary(self, commits, board_selected):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001475 """Show a build summary for U-Boot for a given board list.
1476
1477 Reset the result summary, then repeatedly call GetResultSummary on
1478 each commit's results, then display the differences we see.
1479
1480 Args:
1481 commit: Commit objects to summarise
1482 board_selected: Dict containing boards to summarise
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001483 """
Simon Glassfea58582014-08-09 15:32:59 -06001484 self.commit_count = len(commits) if commits else 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001485 self.commits = commits
1486 self.ResetResultSummary(board_selected)
Simon Glass28370c12014-08-09 15:33:06 -06001487 self._error_lines = 0
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001488
1489 for commit_upto in range(0, self.commit_count, self._step):
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001490 self.ProduceResultSummary(commit_upto, commits, board_selected)
Simon Glass28370c12014-08-09 15:33:06 -06001491 if not self._error_lines:
Simon Glass4653a882014-09-05 19:00:07 -06001492 Print('(no errors to report)', colour=self.col.GREEN)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001493
1494
1495 def SetupBuild(self, board_selected, commits):
1496 """Set up ready to start a build.
1497
1498 Args:
1499 board_selected: Selected boards to build
1500 commits: Selected commits to build
1501 """
1502 # First work out how many commits we will build
Simon Glassc05aa032019-10-31 07:42:53 -06001503 count = (self.commit_count + self._step - 1) // self._step
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001504 self.count = len(board_selected) * count
1505 self.upto = self.warned = self.fail = 0
1506 self._timestamps = collections.deque()
1507
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001508 def GetThreadDir(self, thread_num):
1509 """Get the directory path to the working dir for a thread.
1510
1511 Args:
1512 thread_num: Number of thread to check.
1513 """
Simon Glassd829f122020-03-18 09:42:42 -06001514 if self.work_in_output:
1515 return self._working_dir
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001516 return os.path.join(self._working_dir, '%02d' % thread_num)
1517
Simon Glassfea58582014-08-09 15:32:59 -06001518 def _PrepareThread(self, thread_num, setup_git):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001519 """Prepare the working directory for a thread.
1520
1521 This clones or fetches the repo into the thread's work directory.
1522
1523 Args:
1524 thread_num: Thread number (0, 1, ...)
Simon Glassfea58582014-08-09 15:32:59 -06001525 setup_git: True to set up a git repo clone
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001526 """
1527 thread_dir = self.GetThreadDir(thread_num)
Simon Glass190064b2014-08-09 15:33:00 -06001528 builderthread.Mkdir(thread_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001529 git_dir = os.path.join(thread_dir, '.git')
1530
1531 # Clone the repo if it doesn't already exist
1532 # TODO(sjg@chromium): Perhaps some git hackery to symlink instead, so
1533 # we have a private index but uses the origin repo's contents?
Simon Glassfea58582014-08-09 15:32:59 -06001534 if setup_git and self.git_dir:
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001535 src_dir = os.path.abspath(self.git_dir)
1536 if os.path.exists(git_dir):
Simon Glass212c0b82020-04-09 15:08:43 -06001537 Print('\rFetching repo for thread %d' % thread_num,
1538 newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001539 gitutil.Fetch(git_dir, thread_dir)
Simon Glass212c0b82020-04-09 15:08:43 -06001540 terminal.PrintClear()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001541 else:
Simon Glass21f0eb32016-09-18 16:48:31 -06001542 Print('\rCloning repo for thread %d' % thread_num,
1543 newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001544 gitutil.Clone(src_dir, thread_dir)
Simon Glass102969bb2020-04-09 15:08:42 -06001545 terminal.PrintClear()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001546
Simon Glassfea58582014-08-09 15:32:59 -06001547 def _PrepareWorkingSpace(self, max_threads, setup_git):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001548 """Prepare the working directory for use.
1549
1550 Set up the git repo for each thread.
1551
1552 Args:
1553 max_threads: Maximum number of threads we expect to need.
Simon Glassfea58582014-08-09 15:32:59 -06001554 setup_git: True to set up a git repo clone
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001555 """
Simon Glass190064b2014-08-09 15:33:00 -06001556 builderthread.Mkdir(self._working_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001557 for thread in range(max_threads):
Simon Glassfea58582014-08-09 15:32:59 -06001558 self._PrepareThread(thread, setup_git)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001559
Simon Glass925f6ad2020-03-18 09:42:45 -06001560 def _GetOutputSpaceRemovals(self):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001561 """Get the output directories ready to receive files.
1562
Simon Glass925f6ad2020-03-18 09:42:45 -06001563 Figure out what needs to be deleted in the output directory before it
1564 can be used. We only delete old buildman directories which have the
1565 expected name pattern. See _GetOutputDir().
1566
1567 Returns:
1568 List of full paths of directories to remove
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001569 """
Simon Glass1a915672014-12-01 17:33:53 -07001570 if not self.commits:
1571 return
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001572 dir_list = []
1573 for commit_upto in range(self.commit_count):
1574 dir_list.append(self._GetOutputDir(commit_upto))
1575
Simon Glassb222abe2016-09-18 16:48:32 -06001576 to_remove = []
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001577 for dirname in glob.glob(os.path.join(self.base_dir, '*')):
1578 if dirname not in dir_list:
Simon Glass925f6ad2020-03-18 09:42:45 -06001579 leaf = dirname[len(self.base_dir) + 1:]
1580 m = re.match('[0-9]+_of_[0-9]+_g[0-9a-f]+_.*', leaf)
1581 if m:
1582 to_remove.append(dirname)
1583 return to_remove
1584
1585 def _PrepareOutputSpace(self):
1586 """Get the output directories ready to receive files.
1587
1588 We delete any output directories which look like ones we need to
1589 create. Having left over directories is confusing when the user wants
1590 to check the output manually.
1591 """
1592 to_remove = self._GetOutputSpaceRemovals()
Simon Glassb222abe2016-09-18 16:48:32 -06001593 if to_remove:
Simon Glassb2d89bc2020-03-18 09:42:46 -06001594 Print('Removing %d old build directories...' % len(to_remove),
Simon Glassb222abe2016-09-18 16:48:32 -06001595 newline=False)
1596 for dirname in to_remove:
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001597 shutil.rmtree(dirname)
Simon Glass212c0b82020-04-09 15:08:43 -06001598 terminal.PrintClear()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001599
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001600 def BuildBoards(self, commits, board_selected, keep_outputs, verbose):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001601 """Build all commits for a list of boards
1602
1603 Args:
1604 commits: List of commits to be build, each a Commit object
1605 boards_selected: Dict of selected boards, key is target name,
1606 value is Board object
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001607 keep_outputs: True to save build output files
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001608 verbose: Display build results as they are completed
Simon Glass2c3deb92014-08-28 09:43:39 -06001609 Returns:
1610 Tuple containing:
1611 - number of boards that failed to build
1612 - number of boards that issued warnings
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001613 """
Simon Glassfea58582014-08-09 15:32:59 -06001614 self.commit_count = len(commits) if commits else 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001615 self.commits = commits
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001616 self._verbose = verbose
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001617
1618 self.ResetResultSummary(board_selected)
Thierry Redingf3d015c2014-08-19 10:22:39 +02001619 builderthread.Mkdir(self.base_dir, parents = True)
Simon Glassfea58582014-08-09 15:32:59 -06001620 self._PrepareWorkingSpace(min(self.num_threads, len(board_selected)),
1621 commits is not None)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001622 self._PrepareOutputSpace()
Simon Glass745b3952016-09-18 16:48:33 -06001623 Print('\rStarting build...', newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001624 self.SetupBuild(board_selected, commits)
1625 self.ProcessResult(None)
1626
1627 # Create jobs to build all commits for each board
Simon Glassc05aa032019-10-31 07:42:53 -06001628 for brd in board_selected.values():
Simon Glass190064b2014-08-09 15:33:00 -06001629 job = builderthread.BuilderJob()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001630 job.board = brd
1631 job.commits = commits
1632 job.keep_outputs = keep_outputs
Simon Glassd829f122020-03-18 09:42:42 -06001633 job.work_in_output = self.work_in_output
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001634 job.step = self._step
1635 self.queue.put(job)
1636
Simon Glassd436e382016-09-18 16:48:35 -06001637 term = threading.Thread(target=self.queue.join)
1638 term.setDaemon(True)
1639 term.start()
1640 while term.isAlive():
1641 term.join(100)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001642
1643 # Wait until we have processed all output
1644 self.out_queue.join()
Simon Glass4653a882014-09-05 19:00:07 -06001645 Print()
Simon Glass7b33f212020-04-09 15:08:47 -06001646
1647 msg = 'Completed: %d total built' % self.count
1648 if self.already_done:
1649 msg += ' (%d previously' % self.already_done
1650 if self.already_done != self.count:
1651 msg += ', %d newly' % (self.count - self.already_done)
1652 msg += ')'
1653 duration = datetime.now() - self._start_time
1654 if duration > timedelta(microseconds=1000000):
1655 if duration.microseconds >= 500000:
1656 duration = duration + timedelta(seconds=1)
1657 duration = duration - timedelta(microseconds=duration.microseconds)
1658 msg += ', duration %s' % duration
1659 Print(msg)
1660
Simon Glass2c3deb92014-08-28 09:43:39 -06001661 return (self.fail, self.warned)