blob: be8a8fa13a68e9f87f40b678fe156fc793424197 [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glassc05694f2013-04-03 11:07:16 +00002# Copyright (c) 2013 The Chromium OS Authors.
3#
4# Bloat-o-meter code used here Copyright 2004 Matt Mackall <mpm@selenic.com>
5#
Simon Glassc05694f2013-04-03 11:07:16 +00006
7import collections
Simon Glassc05694f2013-04-03 11:07:16 +00008from datetime import datetime, timedelta
9import glob
10import os
11import re
Simon Glassc78ed662019-10-31 07:42:53 -060012import queue
Simon Glassc05694f2013-04-03 11:07:16 +000013import shutil
Simon Glass205ac042016-09-18 16:48:37 -060014import signal
Simon Glassc05694f2013-04-03 11:07:16 +000015import string
16import sys
Simon Glassd26e1442016-09-18 16:48:35 -060017import threading
Simon Glassc05694f2013-04-03 11:07:16 +000018import time
19
Simon Glassf0d9c102020-04-17 18:09:02 -060020from buildman import builderthread
21from buildman import toolchain
Simon Glassa997ea52020-04-17 18:09:04 -060022from patman import command
23from patman import gitutil
24from patman import terminal
25from patman.terminal import Print
Simon Glassc05694f2013-04-03 11:07:16 +000026
Simon Glassc05694f2013-04-03 11:07:16 +000027"""
28Theory of Operation
29
30Please see README for user documentation, and you should be familiar with
31that before trying to make sense of this.
32
33Buildman works by keeping the machine as busy as possible, building different
34commits for different boards on multiple CPUs at once.
35
36The source repo (self.git_dir) contains all the commits to be built. Each
37thread works on a single board at a time. It checks out the first commit,
38configures it for that board, then builds it. Then it checks out the next
39commit and builds it (typically without re-configuring). When it runs out
40of commits, it gets another job from the builder and starts again with that
41board.
42
43Clearly the builder threads could work either way - they could check out a
44commit and then built it for all boards. Using separate directories for each
45commit/board pair they could leave their build product around afterwards
46also.
47
48The intent behind building a single board for multiple commits, is to make
49use of incremental builds. Since each commit is built incrementally from
50the previous one, builds are faster. Reconfiguring for a different board
51removes all intermediate object files.
52
53Many threads can be working at once, but each has its own working directory.
54When a thread finishes a build, it puts the output files into a result
55directory.
56
57The base directory used by buildman is normally '../<branch>', i.e.
58a directory higher than the source repository and named after the branch
59being built.
60
61Within the base directory, we have one subdirectory for each commit. Within
62that is one subdirectory for each board. Within that is the build output for
63that commit/board combination.
64
65Buildman also create working directories for each thread, in a .bm-work/
66subdirectory in the base dir.
67
68As an example, say we are building branch 'us-net' for boards 'sandbox' and
69'seaboard', and say that us-net has two commits. We will have directories
70like this:
71
72us-net/ base directory
Ovidiu Panaitee8e9cb2020-05-15 09:30:12 +030073 01_g4ed4ebc_net--Add-tftp-speed-/
Simon Glassc05694f2013-04-03 11:07:16 +000074 sandbox/
75 u-boot.bin
76 seaboard/
77 u-boot.bin
Ovidiu Panaitee8e9cb2020-05-15 09:30:12 +030078 02_g4ed4ebc_net--Check-tftp-comp/
Simon Glassc05694f2013-04-03 11:07:16 +000079 sandbox/
80 u-boot.bin
81 seaboard/
82 u-boot.bin
83 .bm-work/
84 00/ working directory for thread 0 (contains source checkout)
85 build/ build output
86 01/ working directory for thread 1
87 build/ build output
88 ...
89u-boot/ source directory
90 .git/ repository
91"""
92
Simon Glassde0fefc2020-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 Glassc05694f2013-04-03 11:07:16 +0000102# Possible build outcomes
Simon Glassc78ed662019-10-31 07:42:53 -0600103OUTCOME_OK, OUTCOME_WARNING, OUTCOME_ERROR, OUTCOME_UNKNOWN = list(range(4))
Simon Glassc05694f2013-04-03 11:07:16 +0000104
Simon Glassd214bef2017-04-12 18:23:26 -0600105# Translate a commit subject into a valid filename (and handle unicode)
Simon Glassc78ed662019-10-31 07:42:53 -0600106trans_valid_chars = str.maketrans('/: ', '---')
Simon Glassc05694f2013-04-03 11:07:16 +0000107
Simon Glasscde5c302016-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 Glassdb17fb82015-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 Glassdb17fb82015-02-05 22:06:15 -0700116]
117
Simon Glasscad8abf2015-08-25 21:52:14 -0600118class Config:
119 """Holds information about configuration settings for a board."""
Simon Glasscde5c302016-11-13 14:25:53 -0700120 def __init__(self, config_filename, target):
Simon Glasscad8abf2015-08-25 21:52:14 -0600121 self.target = target
122 self.config = {}
Simon Glasscde5c302016-11-13 14:25:53 -0700123 for fname in config_filename:
Simon Glasscad8abf2015-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 Glassc78ed662019-10-31 07:42:53 -0600132 for key, value in self.config[fname].items():
133 print(key, value)
Simon Glasscad8abf2015-08-25 21:52:14 -0600134 val = val ^ hash(key) & hash(value)
135 return val
Simon Glassc05694f2013-04-03 11:07:16 +0000136
Alex Kiernan4059e302018-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 Glassc05694f2013-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 Glassc05694f2013-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 Glass7041c392014-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 Glassc05694f2013-04-03 11:07:16 +0000164 git_dir: Git directory containing source repository
Simon Glassc05694f2013-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 Glassf3018b7a2014-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 Glass38df2e22014-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 Glassb6eb8cf2020-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 Glassc05694f2013-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 Glass03749d42014-08-28 09:43:44 -0600189 _base_warn_lines: Last-summarised list of warnings
Simon Glassc05694f2013-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 Glass726ae812020-04-09 15:08:47 -0600195 _start_time: Start time for the build
Simon Glassc05694f2013-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
Simon Glassc635d892021-01-30 22:17:46 -0700200 _single_builder: BuilderThread object for the singer builder, if
201 threading is not being used
Simon Glassc05694f2013-04-03 11:07:16 +0000202 """
203 class Outcome:
204 """Records a build outcome for a single make invocation
205
206 Public Members:
207 rc: Outcome value (OUTCOME_...)
208 err_lines: List of error lines or [] if none
209 sizes: Dictionary of image size information, keyed by filename
210 - Each value is itself a dictionary containing
211 values for 'text', 'data' and 'bss', being the integer
212 size in bytes of each section.
213 func_sizes: Dictionary keyed by filename - e.g. 'u-boot'. Each
214 value is itself a dictionary:
215 key: function name
216 value: Size of function in bytes
Simon Glassdb17fb82015-02-05 22:06:15 -0700217 config: Dictionary keyed by filename - e.g. '.config'. Each
218 value is itself a dictionary:
219 key: config name
220 value: config value
Alex Kiernan4059e302018-05-31 04:48:34 +0000221 environment: Dictionary keyed by environment variable, Each
222 value is the value of environment variable.
Simon Glassc05694f2013-04-03 11:07:16 +0000223 """
Alex Kiernan4059e302018-05-31 04:48:34 +0000224 def __init__(self, rc, err_lines, sizes, func_sizes, config,
225 environment):
Simon Glassc05694f2013-04-03 11:07:16 +0000226 self.rc = rc
227 self.err_lines = err_lines
228 self.sizes = sizes
229 self.func_sizes = func_sizes
Simon Glassdb17fb82015-02-05 22:06:15 -0700230 self.config = config
Alex Kiernan4059e302018-05-31 04:48:34 +0000231 self.environment = environment
Simon Glassc05694f2013-04-03 11:07:16 +0000232
233 def __init__(self, toolchains, base_dir, git_dir, num_threads, num_jobs,
Simon Glasse87bde12014-12-01 17:33:55 -0700234 gnu_make='make', checkout=True, show_unknown=True, step=1,
Stephen Warren97c96902016-04-11 10:48:44 -0600235 no_subdirs=False, full_path=False, verbose_build=False,
Simon Glass6029af12020-04-09 15:08:51 -0600236 mrproper=False, per_board_out_dir=False,
Daniel Schwierzeck20e2ea92018-01-26 16:31:05 +0100237 config_only=False, squash_config_y=False,
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600238 warnings_as_errors=False, work_in_output=False):
Simon Glassc05694f2013-04-03 11:07:16 +0000239 """Create a new Builder object
240
241 Args:
242 toolchains: Toolchains object to use for building
243 base_dir: Base directory to use for builder
244 git_dir: Git directory containing source repository
245 num_threads: Number of builder threads to run
246 num_jobs: Number of jobs to run at once (passed to make as -j)
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900247 gnu_make: the command name of GNU Make.
Simon Glassc05694f2013-04-03 11:07:16 +0000248 checkout: True to check out source, False to skip that step.
249 This is used for testing.
250 show_unknown: Show unknown boards (those not built) in summary
251 step: 1 to process every commit, n to process every nth commit
Simon Glassd48a46c2014-12-01 17:34:00 -0700252 no_subdirs: Don't create subdirectories when building current
253 source for a single board
254 full_path: Return the full path in CROSS_COMPILE and don't set
255 PATH
Simon Glass655b6102014-12-01 17:34:07 -0700256 verbose_build: Run build with V=1 and don't use 'make -s'
Simon Glass6029af12020-04-09 15:08:51 -0600257 mrproper: Always run 'make mrproper' when configuring
Stephen Warren97c96902016-04-11 10:48:44 -0600258 per_board_out_dir: Build in a separate persistent directory per
259 board rather than a thread-specific directory
Simon Glass739e8512016-11-13 14:25:51 -0700260 config_only: Only configure each build, don't build it
Simon Glasscde5c302016-11-13 14:25:53 -0700261 squash_config_y: Convert CONFIG options with the value 'y' to '1'
Daniel Schwierzeck20e2ea92018-01-26 16:31:05 +0100262 warnings_as_errors: Treat all compiler warnings as errors
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600263 work_in_output: Use the output directory as the work directory and
264 don't write to a separate output directory.
Simon Glassc05694f2013-04-03 11:07:16 +0000265 """
266 self.toolchains = toolchains
267 self.base_dir = base_dir
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600268 if work_in_output:
269 self._working_dir = base_dir
270 else:
271 self._working_dir = os.path.join(base_dir, '.bm-work')
Simon Glassc05694f2013-04-03 11:07:16 +0000272 self.threads = []
Simon Glassc05694f2013-04-03 11:07:16 +0000273 self.do_make = self.Make
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900274 self.gnu_make = gnu_make
Simon Glassc05694f2013-04-03 11:07:16 +0000275 self.checkout = checkout
276 self.num_threads = num_threads
277 self.num_jobs = num_jobs
278 self.already_done = 0
279 self.force_build = False
280 self.git_dir = git_dir
281 self._show_unknown = show_unknown
282 self._timestamp_count = 10
283 self._build_period_us = None
284 self._complete_delay = None
285 self._next_delay_update = datetime.now()
Simon Glass726ae812020-04-09 15:08:47 -0600286 self._start_time = datetime.now()
Simon Glassc05694f2013-04-03 11:07:16 +0000287 self.force_config_on_failure = True
Simon Glass7041c392014-07-13 12:22:31 -0600288 self.force_build_failures = False
Simon Glassf3018b7a2014-07-14 17:51:02 -0600289 self.force_reconfig = False
Simon Glassc05694f2013-04-03 11:07:16 +0000290 self._step = step
Simon Glass38df2e22014-07-14 17:51:03 -0600291 self.in_tree = False
Simon Glassbb4dffb2014-08-09 15:33:06 -0600292 self._error_lines = 0
Simon Glasse87bde12014-12-01 17:33:55 -0700293 self.no_subdirs = no_subdirs
Simon Glassd48a46c2014-12-01 17:34:00 -0700294 self.full_path = full_path
Simon Glass655b6102014-12-01 17:34:07 -0700295 self.verbose_build = verbose_build
Simon Glass739e8512016-11-13 14:25:51 -0700296 self.config_only = config_only
Simon Glasscde5c302016-11-13 14:25:53 -0700297 self.squash_config_y = squash_config_y
298 self.config_filenames = BASE_CONFIG_FILENAMES
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600299 self.work_in_output = work_in_output
Simon Glasscde5c302016-11-13 14:25:53 -0700300 if not self.squash_config_y:
301 self.config_filenames += EXTRA_CONFIG_FILENAMES
Simon Glassc05694f2013-04-03 11:07:16 +0000302
Daniel Schwierzeck20e2ea92018-01-26 16:31:05 +0100303 self.warnings_as_errors = warnings_as_errors
Simon Glassc05694f2013-04-03 11:07:16 +0000304 self.col = terminal.Color()
305
Simon Glass03749d42014-08-28 09:43:44 -0600306 self._re_function = re.compile('(.*): In function.*')
307 self._re_files = re.compile('In file included from.*')
308 self._re_warning = re.compile('(.*):(\d*):(\d*): warning: .*')
Simon Glass0db94432018-11-06 16:02:11 -0700309 self._re_dtb_warning = re.compile('(.*): Warning .*')
Simon Glass03749d42014-08-28 09:43:44 -0600310 self._re_note = re.compile('(.*):(\d*):(\d*): note: this is the location of the previous.*')
Simon Glassf4ebfba2020-04-09 15:08:53 -0600311 self._re_migration_warning = re.compile(r'^={21} WARNING ={22}\n.*\n=+\n',
312 re.MULTILINE | re.DOTALL)
Simon Glass03749d42014-08-28 09:43:44 -0600313
Simon Glassc635d892021-01-30 22:17:46 -0700314 if self.num_threads:
315 self._single_builder = None
316 self.queue = queue.Queue()
317 self.out_queue = queue.Queue()
318 for i in range(self.num_threads):
319 t = builderthread.BuilderThread(self, i, mrproper,
320 per_board_out_dir)
321 t.setDaemon(True)
322 t.start()
323 self.threads.append(t)
324
325 t = builderthread.ResultThread(self)
Simon Glassc05694f2013-04-03 11:07:16 +0000326 t.setDaemon(True)
327 t.start()
328 self.threads.append(t)
Simon Glassc635d892021-01-30 22:17:46 -0700329 else:
330 self._single_builder = builderthread.BuilderThread(
331 self, -1, mrproper, per_board_out_dir)
Simon Glassc05694f2013-04-03 11:07:16 +0000332
333 ignore_lines = ['(make.*Waiting for unfinished)', '(Segmentation fault)']
334 self.re_make_err = re.compile('|'.join(ignore_lines))
335
Simon Glass205ac042016-09-18 16:48:37 -0600336 # Handle existing graceful with SIGINT / Ctrl-C
337 signal.signal(signal.SIGINT, self.signal_handler)
338
Simon Glassc05694f2013-04-03 11:07:16 +0000339 def __del__(self):
340 """Get rid of all threads created by the builder"""
341 for t in self.threads:
342 del t
343
Simon Glass205ac042016-09-18 16:48:37 -0600344 def signal_handler(self, signal, frame):
345 sys.exit(1)
346
Simon Glasseb48bbc2014-08-09 15:33:02 -0600347 def SetDisplayOptions(self, show_errors=False, show_sizes=False,
Simon Glass3394c9f2014-08-28 09:43:43 -0600348 show_detail=False, show_bloat=False,
Alex Kiernan4059e302018-05-31 04:48:34 +0000349 list_error_boards=False, show_config=False,
Simon Glassf4ebfba2020-04-09 15:08:53 -0600350 show_environment=False, filter_dtb_warnings=False,
351 filter_migration_warnings=False):
Simon Glasseb48bbc2014-08-09 15:33:02 -0600352 """Setup display options for the builder.
353
Simon Glass9ea93812020-04-09 15:08:52 -0600354 Args:
355 show_errors: True to show summarised error/warning info
356 show_sizes: Show size deltas
357 show_detail: Show size delta detail for each board if show_sizes
358 show_bloat: Show detail for each function
359 list_error_boards: Show the boards which caused each error/warning
360 show_config: Show config deltas
361 show_environment: Show environment deltas
362 filter_dtb_warnings: Filter out any warnings from the device-tree
363 compiler
Simon Glassf4ebfba2020-04-09 15:08:53 -0600364 filter_migration_warnings: Filter out any warnings about migrating
365 a board to driver model
Simon Glasseb48bbc2014-08-09 15:33:02 -0600366 """
367 self._show_errors = show_errors
368 self._show_sizes = show_sizes
369 self._show_detail = show_detail
370 self._show_bloat = show_bloat
Simon Glass3394c9f2014-08-28 09:43:43 -0600371 self._list_error_boards = list_error_boards
Simon Glassdb17fb82015-02-05 22:06:15 -0700372 self._show_config = show_config
Alex Kiernan4059e302018-05-31 04:48:34 +0000373 self._show_environment = show_environment
Simon Glass9ea93812020-04-09 15:08:52 -0600374 self._filter_dtb_warnings = filter_dtb_warnings
Simon Glassf4ebfba2020-04-09 15:08:53 -0600375 self._filter_migration_warnings = filter_migration_warnings
Simon Glasseb48bbc2014-08-09 15:33:02 -0600376
Simon Glassc05694f2013-04-03 11:07:16 +0000377 def _AddTimestamp(self):
378 """Add a new timestamp to the list and record the build period.
379
380 The build period is the length of time taken to perform a single
381 build (one board, one commit).
382 """
383 now = datetime.now()
384 self._timestamps.append(now)
385 count = len(self._timestamps)
386 delta = self._timestamps[-1] - self._timestamps[0]
387 seconds = delta.total_seconds()
388
389 # If we have enough data, estimate build period (time taken for a
390 # single build) and therefore completion time.
391 if count > 1 and self._next_delay_update < now:
392 self._next_delay_update = now + timedelta(seconds=2)
393 if seconds > 0:
394 self._build_period = float(seconds) / count
395 todo = self.count - self.upto
396 self._complete_delay = timedelta(microseconds=
397 self._build_period * todo * 1000000)
398 # Round it
399 self._complete_delay -= timedelta(
400 microseconds=self._complete_delay.microseconds)
401
402 if seconds > 60:
403 self._timestamps.popleft()
404 count -= 1
405
Simon Glassc05694f2013-04-03 11:07:16 +0000406 def SelectCommit(self, commit, checkout=True):
407 """Checkout the selected commit for this build
408 """
409 self.commit = commit
410 if checkout and self.checkout:
411 gitutil.Checkout(commit.hash)
412
413 def Make(self, commit, brd, stage, cwd, *args, **kwargs):
414 """Run make
415
416 Args:
417 commit: Commit object that is being built
418 brd: Board object that is being built
Roger Meiere0a0e552014-08-20 22:10:29 +0200419 stage: Stage that we are at (mrproper, config, build)
Simon Glassc05694f2013-04-03 11:07:16 +0000420 cwd: Directory where make should be run
421 args: Arguments to pass to make
422 kwargs: Arguments to pass to command.RunPipe()
423 """
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900424 cmd = [self.gnu_make] + list(args)
Simon Glassc05694f2013-04-03 11:07:16 +0000425 result = command.RunPipe([cmd], capture=True, capture_stderr=True,
Simon Glassaa09b362018-09-17 23:55:42 -0600426 cwd=cwd, raise_on_error=False, infile='/dev/null', **kwargs)
Simon Glass413f91a2015-02-05 22:06:12 -0700427 if self.verbose_build:
428 result.stdout = '%s\n' % (' '.join(cmd)) + result.stdout
429 result.combined = '%s\n' % (' '.join(cmd)) + result.combined
Simon Glassc05694f2013-04-03 11:07:16 +0000430 return result
431
432 def ProcessResult(self, result):
433 """Process the result of a build, showing progress information
434
435 Args:
Simon Glass78e418e2014-08-09 15:33:03 -0600436 result: A CommandResult object, which indicates the result for
437 a single build
Simon Glassc05694f2013-04-03 11:07:16 +0000438 """
439 col = terminal.Color()
440 if result:
441 target = result.brd.target
442
Simon Glassc05694f2013-04-03 11:07:16 +0000443 self.upto += 1
444 if result.return_code != 0:
445 self.fail += 1
446 elif result.stderr:
447 self.warned += 1
448 if result.already_done:
449 self.already_done += 1
Simon Glass78e418e2014-08-09 15:33:03 -0600450 if self._verbose:
Simon Glassdd95b0b2020-04-09 15:08:42 -0600451 terminal.PrintClear()
Simon Glass78e418e2014-08-09 15:33:03 -0600452 boards_selected = {target : result.brd}
453 self.ResetResultSummary(boards_selected)
454 self.ProduceResultSummary(result.commit_upto, self.commits,
455 boards_selected)
Simon Glassc05694f2013-04-03 11:07:16 +0000456 else:
457 target = '(starting)'
458
459 # Display separate counts for ok, warned and fail
460 ok = self.upto - self.warned - self.fail
461 line = '\r' + self.col.Color(self.col.GREEN, '%5d' % ok)
462 line += self.col.Color(self.col.YELLOW, '%5d' % self.warned)
463 line += self.col.Color(self.col.RED, '%5d' % self.fail)
464
Simon Glass69c3a8a2020-04-09 15:08:45 -0600465 line += ' /%-5d ' % self.count
466 remaining = self.count - self.upto
467 if remaining:
468 line += self.col.Color(self.col.MAGENTA, ' -%-5d ' % remaining)
469 else:
470 line += ' ' * 8
Simon Glassc05694f2013-04-03 11:07:16 +0000471
472 # Add our current completion time estimate
473 self._AddTimestamp()
474 if self._complete_delay:
Simon Glass69c3a8a2020-04-09 15:08:45 -0600475 line += '%s : ' % self._complete_delay
Simon Glassc05694f2013-04-03 11:07:16 +0000476
Simon Glass69c3a8a2020-04-09 15:08:45 -0600477 line += target
Simon Glassdd95b0b2020-04-09 15:08:42 -0600478 terminal.PrintClear()
Simon Glassc7ddae12020-04-09 15:08:46 -0600479 Print(line, newline=False, limit_to_line=True)
Simon Glassc05694f2013-04-03 11:07:16 +0000480
481 def _GetOutputDir(self, commit_upto):
482 """Get the name of the output directory for a commit number
483
484 The output directory is typically .../<branch>/<commit>.
485
486 Args:
487 commit_upto: Commit number to use (0..self.count-1)
488 """
Simon Glasse3c85ab2020-04-17 17:51:34 -0600489 if self.work_in_output:
490 return self._working_dir
491
Simon Glasse87bde12014-12-01 17:33:55 -0700492 commit_dir = None
Simon Glassd326ad72014-08-09 15:32:59 -0600493 if self.commits:
494 commit = self.commits[commit_upto]
495 subject = commit.subject.translate(trans_valid_chars)
Simon Glass5dc1ca72020-03-18 09:42:45 -0600496 # See _GetOutputSpaceRemovals() which parses this name
Ovidiu Panaitee8e9cb2020-05-15 09:30:12 +0300497 commit_dir = ('%02d_g%s_%s' % (commit_upto + 1,
498 commit.hash, subject[:20]))
Simon Glasse87bde12014-12-01 17:33:55 -0700499 elif not self.no_subdirs:
Simon Glassd326ad72014-08-09 15:32:59 -0600500 commit_dir = 'current'
Simon Glasse87bde12014-12-01 17:33:55 -0700501 if not commit_dir:
502 return self.base_dir
503 return os.path.join(self.base_dir, commit_dir)
Simon Glassc05694f2013-04-03 11:07:16 +0000504
505 def GetBuildDir(self, commit_upto, target):
506 """Get the name of the build directory for a commit number
507
508 The build directory is typically .../<branch>/<commit>/<target>.
509
510 Args:
511 commit_upto: Commit number to use (0..self.count-1)
512 target: Target name
513 """
514 output_dir = self._GetOutputDir(commit_upto)
Simon Glasse3c85ab2020-04-17 17:51:34 -0600515 if self.work_in_output:
516 return output_dir
Simon Glassc05694f2013-04-03 11:07:16 +0000517 return os.path.join(output_dir, target)
518
519 def GetDoneFile(self, commit_upto, target):
520 """Get the name of the done file for a commit number
521
522 Args:
523 commit_upto: Commit number to use (0..self.count-1)
524 target: Target name
525 """
526 return os.path.join(self.GetBuildDir(commit_upto, target), 'done')
527
528 def GetSizesFile(self, commit_upto, target):
529 """Get the name of the sizes file for a commit number
530
531 Args:
532 commit_upto: Commit number to use (0..self.count-1)
533 target: Target name
534 """
535 return os.path.join(self.GetBuildDir(commit_upto, target), 'sizes')
536
537 def GetFuncSizesFile(self, commit_upto, target, elf_fname):
538 """Get the name of the funcsizes file for a commit number and ELF file
539
540 Args:
541 commit_upto: Commit number to use (0..self.count-1)
542 target: Target name
543 elf_fname: Filename of elf image
544 """
545 return os.path.join(self.GetBuildDir(commit_upto, target),
546 '%s.sizes' % elf_fname.replace('/', '-'))
547
548 def GetObjdumpFile(self, commit_upto, target, elf_fname):
549 """Get the name of the objdump file for a commit number and ELF file
550
551 Args:
552 commit_upto: Commit number to use (0..self.count-1)
553 target: Target name
554 elf_fname: Filename of elf image
555 """
556 return os.path.join(self.GetBuildDir(commit_upto, target),
557 '%s.objdump' % elf_fname.replace('/', '-'))
558
559 def GetErrFile(self, commit_upto, target):
560 """Get the name of the err file for a commit number
561
562 Args:
563 commit_upto: Commit number to use (0..self.count-1)
564 target: Target name
565 """
566 output_dir = self.GetBuildDir(commit_upto, target)
567 return os.path.join(output_dir, 'err')
568
569 def FilterErrors(self, lines):
570 """Filter out errors in which we have no interest
571
572 We should probably use map().
573
574 Args:
575 lines: List of error lines, each a string
576 Returns:
577 New list with only interesting lines included
578 """
579 out_lines = []
Simon Glassf4ebfba2020-04-09 15:08:53 -0600580 if self._filter_migration_warnings:
581 text = '\n'.join(lines)
582 text = self._re_migration_warning.sub('', text)
583 lines = text.splitlines()
Simon Glassc05694f2013-04-03 11:07:16 +0000584 for line in lines:
Simon Glass9ea93812020-04-09 15:08:52 -0600585 if self.re_make_err.search(line):
586 continue
587 if self._filter_dtb_warnings and self._re_dtb_warning.search(line):
588 continue
589 out_lines.append(line)
Simon Glassc05694f2013-04-03 11:07:16 +0000590 return out_lines
591
592 def ReadFuncSizes(self, fname, fd):
593 """Read function sizes from the output of 'nm'
594
595 Args:
596 fd: File containing data to read
597 fname: Filename we are reading from (just for errors)
598
599 Returns:
600 Dictionary containing size of each function in bytes, indexed by
601 function name.
602 """
603 sym = {}
604 for line in fd.readlines():
605 try:
Tom Rini08d34fc2019-12-06 15:31:31 -0500606 if line.strip():
607 size, type, name = line[:-1].split()
Simon Glassc05694f2013-04-03 11:07:16 +0000608 except:
Simon Glass4433aa92014-09-05 19:00:07 -0600609 Print("Invalid line in file '%s': '%s'" % (fname, line[:-1]))
Simon Glassc05694f2013-04-03 11:07:16 +0000610 continue
611 if type in 'tTdDbB':
612 # function names begin with '.' on 64-bit powerpc
613 if '.' in name[1:]:
614 name = 'static.' + name.split('.')[0]
615 sym[name] = sym.get(name, 0) + int(size, 16)
616 return sym
617
Simon Glassdb17fb82015-02-05 22:06:15 -0700618 def _ProcessConfig(self, fname):
619 """Read in a .config, autoconf.mk or autoconf.h file
620
621 This function handles all config file types. It ignores comments and
622 any #defines which don't start with CONFIG_.
623
624 Args:
625 fname: Filename to read
626
627 Returns:
628 Dictionary:
629 key: Config name (e.g. CONFIG_DM)
630 value: Config value (e.g. 1)
631 """
632 config = {}
633 if os.path.exists(fname):
634 with open(fname) as fd:
635 for line in fd:
636 line = line.strip()
637 if line.startswith('#define'):
638 values = line[8:].split(' ', 1)
639 if len(values) > 1:
640 key, value = values
641 else:
642 key = values[0]
Simon Glasscde5c302016-11-13 14:25:53 -0700643 value = '1' if self.squash_config_y else ''
Simon Glassdb17fb82015-02-05 22:06:15 -0700644 if not key.startswith('CONFIG_'):
645 continue
646 elif not line or line[0] in ['#', '*', '/']:
647 continue
648 else:
649 key, value = line.split('=', 1)
Simon Glasscde5c302016-11-13 14:25:53 -0700650 if self.squash_config_y and value == 'y':
651 value = '1'
Simon Glassdb17fb82015-02-05 22:06:15 -0700652 config[key] = value
653 return config
654
Alex Kiernan4059e302018-05-31 04:48:34 +0000655 def _ProcessEnvironment(self, fname):
656 """Read in a uboot.env file
657
658 This function reads in environment variables from a file.
659
660 Args:
661 fname: Filename to read
662
663 Returns:
664 Dictionary:
665 key: environment variable (e.g. bootlimit)
666 value: value of environment variable (e.g. 1)
667 """
668 environment = {}
669 if os.path.exists(fname):
670 with open(fname) as fd:
671 for line in fd.read().split('\0'):
672 try:
673 key, value = line.split('=', 1)
674 environment[key] = value
675 except ValueError:
676 # ignore lines we can't parse
677 pass
678 return environment
679
Simon Glassdb17fb82015-02-05 22:06:15 -0700680 def GetBuildOutcome(self, commit_upto, target, read_func_sizes,
Alex Kiernan4059e302018-05-31 04:48:34 +0000681 read_config, read_environment):
Simon Glassc05694f2013-04-03 11:07:16 +0000682 """Work out the outcome of a build.
683
684 Args:
685 commit_upto: Commit number to check (0..n-1)
686 target: Target board to check
687 read_func_sizes: True to read function size information
Simon Glassdb17fb82015-02-05 22:06:15 -0700688 read_config: True to read .config and autoconf.h files
Alex Kiernan4059e302018-05-31 04:48:34 +0000689 read_environment: True to read uboot.env files
Simon Glassc05694f2013-04-03 11:07:16 +0000690
691 Returns:
692 Outcome object
693 """
694 done_file = self.GetDoneFile(commit_upto, target)
695 sizes_file = self.GetSizesFile(commit_upto, target)
696 sizes = {}
697 func_sizes = {}
Simon Glassdb17fb82015-02-05 22:06:15 -0700698 config = {}
Alex Kiernan4059e302018-05-31 04:48:34 +0000699 environment = {}
Simon Glassc05694f2013-04-03 11:07:16 +0000700 if os.path.exists(done_file):
701 with open(done_file, 'r') as fd:
Simon Glass91c54a42019-04-26 19:02:23 -0600702 try:
703 return_code = int(fd.readline())
704 except ValueError:
705 # The file may be empty due to running out of disk space.
706 # Try a rebuild
707 return_code = 1
Simon Glassc05694f2013-04-03 11:07:16 +0000708 err_lines = []
709 err_file = self.GetErrFile(commit_upto, target)
710 if os.path.exists(err_file):
711 with open(err_file, 'r') as fd:
712 err_lines = self.FilterErrors(fd.readlines())
713
714 # Decide whether the build was ok, failed or created warnings
715 if return_code:
716 rc = OUTCOME_ERROR
717 elif len(err_lines):
718 rc = OUTCOME_WARNING
719 else:
720 rc = OUTCOME_OK
721
722 # Convert size information to our simple format
723 if os.path.exists(sizes_file):
724 with open(sizes_file, 'r') as fd:
725 for line in fd.readlines():
726 values = line.split()
727 rodata = 0
728 if len(values) > 6:
729 rodata = int(values[6], 16)
730 size_dict = {
731 'all' : int(values[0]) + int(values[1]) +
732 int(values[2]),
733 'text' : int(values[0]) - rodata,
734 'data' : int(values[1]),
735 'bss' : int(values[2]),
736 'rodata' : rodata,
737 }
738 sizes[values[5]] = size_dict
739
740 if read_func_sizes:
741 pattern = self.GetFuncSizesFile(commit_upto, target, '*')
742 for fname in glob.glob(pattern):
743 with open(fname, 'r') as fd:
744 dict_name = os.path.basename(fname).replace('.sizes',
745 '')
746 func_sizes[dict_name] = self.ReadFuncSizes(fname, fd)
747
Simon Glassdb17fb82015-02-05 22:06:15 -0700748 if read_config:
749 output_dir = self.GetBuildDir(commit_upto, target)
Simon Glasscde5c302016-11-13 14:25:53 -0700750 for name in self.config_filenames:
Simon Glassdb17fb82015-02-05 22:06:15 -0700751 fname = os.path.join(output_dir, name)
752 config[name] = self._ProcessConfig(fname)
753
Alex Kiernan4059e302018-05-31 04:48:34 +0000754 if read_environment:
755 output_dir = self.GetBuildDir(commit_upto, target)
756 fname = os.path.join(output_dir, 'uboot.env')
757 environment = self._ProcessEnvironment(fname)
758
759 return Builder.Outcome(rc, err_lines, sizes, func_sizes, config,
760 environment)
Simon Glassc05694f2013-04-03 11:07:16 +0000761
Alex Kiernan4059e302018-05-31 04:48:34 +0000762 return Builder.Outcome(OUTCOME_UNKNOWN, [], {}, {}, {}, {})
Simon Glassc05694f2013-04-03 11:07:16 +0000763
Simon Glassdb17fb82015-02-05 22:06:15 -0700764 def GetResultSummary(self, boards_selected, commit_upto, read_func_sizes,
Alex Kiernan4059e302018-05-31 04:48:34 +0000765 read_config, read_environment):
Simon Glassc05694f2013-04-03 11:07:16 +0000766 """Calculate a summary of the results of building a commit.
767
768 Args:
769 board_selected: Dict containing boards to summarise
770 commit_upto: Commit number to summarize (0..self.count-1)
771 read_func_sizes: True to read function size information
Simon Glassdb17fb82015-02-05 22:06:15 -0700772 read_config: True to read .config and autoconf.h files
Alex Kiernan4059e302018-05-31 04:48:34 +0000773 read_environment: True to read uboot.env files
Simon Glassc05694f2013-04-03 11:07:16 +0000774
775 Returns:
776 Tuple:
777 Dict containing boards which passed building this commit.
778 keyed by board.target
Simon Glass03749d42014-08-28 09:43:44 -0600779 List containing a summary of error lines
Simon Glass3394c9f2014-08-28 09:43:43 -0600780 Dict keyed by error line, containing a list of the Board
781 objects with that error
Simon Glass03749d42014-08-28 09:43:44 -0600782 List containing a summary of warning lines
783 Dict keyed by error line, containing a list of the Board
784 objects with that warning
Simon Glasscad8abf2015-08-25 21:52:14 -0600785 Dictionary keyed by board.target. Each value is a dictionary:
786 key: filename - e.g. '.config'
Simon Glassdb17fb82015-02-05 22:06:15 -0700787 value is itself a dictionary:
788 key: config name
789 value: config value
Alex Kiernan4059e302018-05-31 04:48:34 +0000790 Dictionary keyed by board.target. Each value is a dictionary:
791 key: environment variable
792 value: value of environment variable
Simon Glassc05694f2013-04-03 11:07:16 +0000793 """
Simon Glass03749d42014-08-28 09:43:44 -0600794 def AddLine(lines_summary, lines_boards, line, board):
795 line = line.rstrip()
796 if line in lines_boards:
797 lines_boards[line].append(board)
798 else:
799 lines_boards[line] = [board]
800 lines_summary.append(line)
801
Simon Glassc05694f2013-04-03 11:07:16 +0000802 board_dict = {}
803 err_lines_summary = []
Simon Glass3394c9f2014-08-28 09:43:43 -0600804 err_lines_boards = {}
Simon Glass03749d42014-08-28 09:43:44 -0600805 warn_lines_summary = []
806 warn_lines_boards = {}
Simon Glassdb17fb82015-02-05 22:06:15 -0700807 config = {}
Alex Kiernan4059e302018-05-31 04:48:34 +0000808 environment = {}
Simon Glassc05694f2013-04-03 11:07:16 +0000809
Simon Glassc78ed662019-10-31 07:42:53 -0600810 for board in boards_selected.values():
Simon Glassc05694f2013-04-03 11:07:16 +0000811 outcome = self.GetBuildOutcome(commit_upto, board.target,
Alex Kiernan4059e302018-05-31 04:48:34 +0000812 read_func_sizes, read_config,
813 read_environment)
Simon Glassc05694f2013-04-03 11:07:16 +0000814 board_dict[board.target] = outcome
Simon Glass03749d42014-08-28 09:43:44 -0600815 last_func = None
816 last_was_warning = False
817 for line in outcome.err_lines:
818 if line:
819 if (self._re_function.match(line) or
820 self._re_files.match(line)):
821 last_func = line
Simon Glass3394c9f2014-08-28 09:43:43 -0600822 else:
Simon Glass0db94432018-11-06 16:02:11 -0700823 is_warning = (self._re_warning.match(line) or
824 self._re_dtb_warning.match(line))
Simon Glass03749d42014-08-28 09:43:44 -0600825 is_note = self._re_note.match(line)
826 if is_warning or (last_was_warning and is_note):
827 if last_func:
828 AddLine(warn_lines_summary, warn_lines_boards,
829 last_func, board)
830 AddLine(warn_lines_summary, warn_lines_boards,
831 line, board)
832 else:
833 if last_func:
834 AddLine(err_lines_summary, err_lines_boards,
835 last_func, board)
836 AddLine(err_lines_summary, err_lines_boards,
837 line, board)
838 last_was_warning = is_warning
839 last_func = None
Simon Glasscde5c302016-11-13 14:25:53 -0700840 tconfig = Config(self.config_filenames, board.target)
841 for fname in self.config_filenames:
Simon Glassdb17fb82015-02-05 22:06:15 -0700842 if outcome.config:
Simon Glassc78ed662019-10-31 07:42:53 -0600843 for key, value in outcome.config[fname].items():
Simon Glasscad8abf2015-08-25 21:52:14 -0600844 tconfig.Add(fname, key, value)
845 config[board.target] = tconfig
Simon Glassdb17fb82015-02-05 22:06:15 -0700846
Alex Kiernan4059e302018-05-31 04:48:34 +0000847 tenvironment = Environment(board.target)
848 if outcome.environment:
Simon Glassc78ed662019-10-31 07:42:53 -0600849 for key, value in outcome.environment.items():
Alex Kiernan4059e302018-05-31 04:48:34 +0000850 tenvironment.Add(key, value)
851 environment[board.target] = tenvironment
852
Simon Glass03749d42014-08-28 09:43:44 -0600853 return (board_dict, err_lines_summary, err_lines_boards,
Alex Kiernan4059e302018-05-31 04:48:34 +0000854 warn_lines_summary, warn_lines_boards, config, environment)
Simon Glassc05694f2013-04-03 11:07:16 +0000855
856 def AddOutcome(self, board_dict, arch_list, changes, char, color):
857 """Add an output to our list of outcomes for each architecture
858
859 This simple function adds failing boards (changes) to the
860 relevant architecture string, so we can print the results out
861 sorted by architecture.
862
863 Args:
864 board_dict: Dict containing all boards
865 arch_list: Dict keyed by arch name. Value is a string containing
866 a list of board names which failed for that arch.
867 changes: List of boards to add to arch_list
868 color: terminal.Colour object
869 """
870 done_arch = {}
871 for target in changes:
872 if target in board_dict:
873 arch = board_dict[target].arch
874 else:
875 arch = 'unknown'
876 str = self.col.Color(color, ' ' + target)
877 if not arch in done_arch:
Simon Glass26f72372015-02-05 22:06:11 -0700878 str = ' %s %s' % (self.col.Color(color, char), str)
Simon Glassc05694f2013-04-03 11:07:16 +0000879 done_arch[arch] = True
880 if not arch in arch_list:
881 arch_list[arch] = str
882 else:
883 arch_list[arch] += str
884
885
886 def ColourNum(self, num):
887 color = self.col.RED if num > 0 else self.col.GREEN
888 if num == 0:
889 return '0'
890 return self.col.Color(color, str(num))
891
892 def ResetResultSummary(self, board_selected):
893 """Reset the results summary ready for use.
894
895 Set up the base board list to be all those selected, and set the
896 error lines to empty.
897
898 Following this, calls to PrintResultSummary() will use this
899 information to work out what has changed.
900
901 Args:
902 board_selected: Dict containing boards to summarise, keyed by
903 board.target
904 """
905 self._base_board_dict = {}
906 for board in board_selected:
Alex Kiernan4059e302018-05-31 04:48:34 +0000907 self._base_board_dict[board] = Builder.Outcome(0, [], [], {}, {},
908 {})
Simon Glassc05694f2013-04-03 11:07:16 +0000909 self._base_err_lines = []
Simon Glass03749d42014-08-28 09:43:44 -0600910 self._base_warn_lines = []
911 self._base_err_line_boards = {}
912 self._base_warn_line_boards = {}
Simon Glasscad8abf2015-08-25 21:52:14 -0600913 self._base_config = None
Alex Kiernan4059e302018-05-31 04:48:34 +0000914 self._base_environment = None
Simon Glassc05694f2013-04-03 11:07:16 +0000915
916 def PrintFuncSizeDetail(self, fname, old, new):
917 grow, shrink, add, remove, up, down = 0, 0, 0, 0, 0, 0
918 delta, common = [], {}
919
920 for a in old:
921 if a in new:
922 common[a] = 1
923
924 for name in old:
925 if name not in common:
926 remove += 1
927 down += old[name]
928 delta.append([-old[name], name])
929
930 for name in new:
931 if name not in common:
932 add += 1
933 up += new[name]
934 delta.append([new[name], name])
935
936 for name in common:
937 diff = new.get(name, 0) - old.get(name, 0)
938 if diff > 0:
939 grow, up = grow + 1, up + diff
940 elif diff < 0:
941 shrink, down = shrink + 1, down - diff
942 delta.append([diff, name])
943
944 delta.sort()
945 delta.reverse()
946
947 args = [add, -remove, grow, -shrink, up, -down, up - down]
Tom Rini0b48cd62017-05-22 13:48:52 -0400948 if max(args) == 0 and min(args) == 0:
Simon Glassc05694f2013-04-03 11:07:16 +0000949 return
950 args = [self.ColourNum(x) for x in args]
951 indent = ' ' * 15
Simon Glass4433aa92014-09-05 19:00:07 -0600952 Print('%s%s: add: %s/%s, grow: %s/%s bytes: %s/%s (%s)' %
953 tuple([indent, self.col.Color(self.col.YELLOW, fname)] + args))
954 Print('%s %-38s %7s %7s %+7s' % (indent, 'function', 'old', 'new',
955 'delta'))
Simon Glassc05694f2013-04-03 11:07:16 +0000956 for diff, name in delta:
957 if diff:
958 color = self.col.RED if diff > 0 else self.col.GREEN
959 msg = '%s %-38s %7s %7s %+7d' % (indent, name,
960 old.get(name, '-'), new.get(name,'-'), diff)
Simon Glass4433aa92014-09-05 19:00:07 -0600961 Print(msg, colour=color)
Simon Glassc05694f2013-04-03 11:07:16 +0000962
963
964 def PrintSizeDetail(self, target_list, show_bloat):
965 """Show details size information for each board
966
967 Args:
968 target_list: List of targets, each a dict containing:
969 'target': Target name
970 'total_diff': Total difference in bytes across all areas
971 <part_name>: Difference for that part
972 show_bloat: Show detail for each function
973 """
974 targets_by_diff = sorted(target_list, reverse=True,
975 key=lambda x: x['_total_diff'])
976 for result in targets_by_diff:
977 printed_target = False
978 for name in sorted(result):
979 diff = result[name]
980 if name.startswith('_'):
981 continue
982 if diff != 0:
983 color = self.col.RED if diff > 0 else self.col.GREEN
984 msg = ' %s %+d' % (name, diff)
985 if not printed_target:
Simon Glass4433aa92014-09-05 19:00:07 -0600986 Print('%10s %-15s:' % ('', result['_target']),
987 newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +0000988 printed_target = True
Simon Glass4433aa92014-09-05 19:00:07 -0600989 Print(msg, colour=color, newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +0000990 if printed_target:
Simon Glass4433aa92014-09-05 19:00:07 -0600991 Print()
Simon Glassc05694f2013-04-03 11:07:16 +0000992 if show_bloat:
993 target = result['_target']
994 outcome = result['_outcome']
995 base_outcome = self._base_board_dict[target]
996 for fname in outcome.func_sizes:
997 self.PrintFuncSizeDetail(fname,
998 base_outcome.func_sizes[fname],
999 outcome.func_sizes[fname])
1000
1001
1002 def PrintSizeSummary(self, board_selected, board_dict, show_detail,
1003 show_bloat):
1004 """Print a summary of image sizes broken down by section.
1005
1006 The summary takes the form of one line per architecture. The
1007 line contains deltas for each of the sections (+ means the section
Flavio Suligoie5e3c112020-01-29 09:56:05 +01001008 got bigger, - means smaller). The numbers are the average number
Simon Glassc05694f2013-04-03 11:07:16 +00001009 of bytes that a board in this section increased by.
1010
1011 For example:
1012 powerpc: (622 boards) text -0.0
1013 arm: (285 boards) text -0.0
1014 nds32: (3 boards) text -8.0
1015
1016 Args:
1017 board_selected: Dict containing boards to summarise, keyed by
1018 board.target
1019 board_dict: Dict containing boards for which we built this
1020 commit, keyed by board.target. The value is an Outcome object.
Simon Glassb4002462020-03-18 09:42:43 -06001021 show_detail: Show size delta detail for each board
Simon Glassc05694f2013-04-03 11:07:16 +00001022 show_bloat: Show detail for each function
1023 """
1024 arch_list = {}
1025 arch_count = {}
1026
1027 # Calculate changes in size for different image parts
1028 # The previous sizes are in Board.sizes, for each board
1029 for target in board_dict:
1030 if target not in board_selected:
1031 continue
1032 base_sizes = self._base_board_dict[target].sizes
1033 outcome = board_dict[target]
1034 sizes = outcome.sizes
1035
1036 # Loop through the list of images, creating a dict of size
1037 # changes for each image/part. We end up with something like
1038 # {'target' : 'snapper9g45, 'data' : 5, 'u-boot-spl:text' : -4}
1039 # which means that U-Boot data increased by 5 bytes and SPL
1040 # text decreased by 4.
1041 err = {'_target' : target}
1042 for image in sizes:
1043 if image in base_sizes:
1044 base_image = base_sizes[image]
1045 # Loop through the text, data, bss parts
1046 for part in sorted(sizes[image]):
1047 diff = sizes[image][part] - base_image[part]
1048 col = None
1049 if diff:
1050 if image == 'u-boot':
1051 name = part
1052 else:
1053 name = image + ':' + part
1054 err[name] = diff
1055 arch = board_selected[target].arch
1056 if not arch in arch_count:
1057 arch_count[arch] = 1
1058 else:
1059 arch_count[arch] += 1
1060 if not sizes:
1061 pass # Only add to our list when we have some stats
1062 elif not arch in arch_list:
1063 arch_list[arch] = [err]
1064 else:
1065 arch_list[arch].append(err)
1066
1067 # We now have a list of image size changes sorted by arch
1068 # Print out a summary of these
Simon Glassc78ed662019-10-31 07:42:53 -06001069 for arch, target_list in arch_list.items():
Simon Glassc05694f2013-04-03 11:07:16 +00001070 # Get total difference for each type
1071 totals = {}
1072 for result in target_list:
1073 total = 0
Simon Glassc78ed662019-10-31 07:42:53 -06001074 for name, diff in result.items():
Simon Glassc05694f2013-04-03 11:07:16 +00001075 if name.startswith('_'):
1076 continue
1077 total += diff
1078 if name in totals:
1079 totals[name] += diff
1080 else:
1081 totals[name] = diff
1082 result['_total_diff'] = total
1083 result['_outcome'] = board_dict[result['_target']]
1084
1085 count = len(target_list)
1086 printed_arch = False
1087 for name in sorted(totals):
1088 diff = totals[name]
1089 if diff:
1090 # Display the average difference in this name for this
1091 # architecture
1092 avg_diff = float(diff) / count
1093 color = self.col.RED if avg_diff > 0 else self.col.GREEN
1094 msg = ' %s %+1.1f' % (name, avg_diff)
1095 if not printed_arch:
Simon Glass4433aa92014-09-05 19:00:07 -06001096 Print('%10s: (for %d/%d boards)' % (arch, count,
1097 arch_count[arch]), newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001098 printed_arch = True
Simon Glass4433aa92014-09-05 19:00:07 -06001099 Print(msg, colour=color, newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001100
1101 if printed_arch:
Simon Glass4433aa92014-09-05 19:00:07 -06001102 Print()
Simon Glassc05694f2013-04-03 11:07:16 +00001103 if show_detail:
1104 self.PrintSizeDetail(target_list, show_bloat)
1105
1106
1107 def PrintResultSummary(self, board_selected, board_dict, err_lines,
Simon Glass03749d42014-08-28 09:43:44 -06001108 err_line_boards, warn_lines, warn_line_boards,
Alex Kiernan4059e302018-05-31 04:48:34 +00001109 config, environment, show_sizes, show_detail,
1110 show_bloat, show_config, show_environment):
Simon Glassc05694f2013-04-03 11:07:16 +00001111 """Compare results with the base results and display delta.
1112
1113 Only boards mentioned in board_selected will be considered. This
1114 function is intended to be called repeatedly with the results of
1115 each commit. It therefore shows a 'diff' between what it saw in
1116 the last call and what it sees now.
1117
1118 Args:
1119 board_selected: Dict containing boards to summarise, keyed by
1120 board.target
1121 board_dict: Dict containing boards for which we built this
1122 commit, keyed by board.target. The value is an Outcome object.
1123 err_lines: A list of errors for this commit, or [] if there is
1124 none, or we don't want to print errors
Simon Glass3394c9f2014-08-28 09:43:43 -06001125 err_line_boards: Dict keyed by error line, containing a list of
1126 the Board objects with that error
Simon Glass03749d42014-08-28 09:43:44 -06001127 warn_lines: A list of warnings for this commit, or [] if there is
1128 none, or we don't want to print errors
1129 warn_line_boards: Dict keyed by warning line, containing a list of
1130 the Board objects with that warning
Simon Glassdb17fb82015-02-05 22:06:15 -07001131 config: Dictionary keyed by filename - e.g. '.config'. Each
1132 value is itself a dictionary:
1133 key: config name
1134 value: config value
Alex Kiernan4059e302018-05-31 04:48:34 +00001135 environment: Dictionary keyed by environment variable, Each
1136 value is the value of environment variable.
Simon Glassc05694f2013-04-03 11:07:16 +00001137 show_sizes: Show image size deltas
Simon Glassb4002462020-03-18 09:42:43 -06001138 show_detail: Show size delta detail for each board if show_sizes
Simon Glassc05694f2013-04-03 11:07:16 +00001139 show_bloat: Show detail for each function
Simon Glassdb17fb82015-02-05 22:06:15 -07001140 show_config: Show config changes
Alex Kiernan4059e302018-05-31 04:48:34 +00001141 show_environment: Show environment changes
Simon Glassc05694f2013-04-03 11:07:16 +00001142 """
Simon Glass03749d42014-08-28 09:43:44 -06001143 def _BoardList(line, line_boards):
Simon Glass3394c9f2014-08-28 09:43:43 -06001144 """Helper function to get a line of boards containing a line
1145
1146 Args:
1147 line: Error line to search for
Simon Glassde0fefc2020-04-09 15:08:36 -06001148 line_boards: boards to search, each a Board
Simon Glass3394c9f2014-08-28 09:43:43 -06001149 Return:
Simon Glassde0fefc2020-04-09 15:08:36 -06001150 List of boards with that error line, or [] if the user has not
1151 requested such a list
Simon Glass3394c9f2014-08-28 09:43:43 -06001152 """
Simon Glassde0fefc2020-04-09 15:08:36 -06001153 boards = []
1154 board_set = set()
Simon Glass3394c9f2014-08-28 09:43:43 -06001155 if self._list_error_boards:
Simon Glass03749d42014-08-28 09:43:44 -06001156 for board in line_boards[line]:
Simon Glassde0fefc2020-04-09 15:08:36 -06001157 if not board in board_set:
1158 boards.append(board)
1159 board_set.add(board)
1160 return boards
Simon Glass3394c9f2014-08-28 09:43:43 -06001161
Simon Glass03749d42014-08-28 09:43:44 -06001162 def _CalcErrorDelta(base_lines, base_line_boards, lines, line_boards,
1163 char):
Simon Glassde0fefc2020-04-09 15:08:36 -06001164 """Calculate the required output based on changes in errors
1165
1166 Args:
1167 base_lines: List of errors/warnings for previous commit
1168 base_line_boards: Dict keyed by error line, containing a list
1169 of the Board objects with that error in the previous commit
1170 lines: List of errors/warning for this commit, each a str
1171 line_boards: Dict keyed by error line, containing a list
1172 of the Board objects with that error in this commit
1173 char: Character representing error ('') or warning ('w'). The
1174 broken ('+') or fixed ('-') characters are added in this
1175 function
1176
1177 Returns:
1178 Tuple
1179 List of ErrLine objects for 'better' lines
1180 List of ErrLine objects for 'worse' lines
1181 """
Simon Glass03749d42014-08-28 09:43:44 -06001182 better_lines = []
1183 worse_lines = []
1184 for line in lines:
1185 if line not in base_lines:
Simon Glassde0fefc2020-04-09 15:08:36 -06001186 errline = ErrLine(char + '+', _BoardList(line, line_boards),
1187 line)
1188 worse_lines.append(errline)
Simon Glass03749d42014-08-28 09:43:44 -06001189 for line in base_lines:
1190 if line not in lines:
Simon Glassde0fefc2020-04-09 15:08:36 -06001191 errline = ErrLine(char + '-',
1192 _BoardList(line, base_line_boards), line)
1193 better_lines.append(errline)
Simon Glass03749d42014-08-28 09:43:44 -06001194 return better_lines, worse_lines
1195
Simon Glassdb17fb82015-02-05 22:06:15 -07001196 def _CalcConfig(delta, name, config):
1197 """Calculate configuration changes
1198
1199 Args:
1200 delta: Type of the delta, e.g. '+'
1201 name: name of the file which changed (e.g. .config)
1202 config: configuration change dictionary
1203 key: config name
1204 value: config value
1205 Returns:
1206 String containing the configuration changes which can be
1207 printed
1208 """
1209 out = ''
1210 for key in sorted(config.keys()):
1211 out += '%s=%s ' % (key, config[key])
Simon Glasscad8abf2015-08-25 21:52:14 -06001212 return '%s %s: %s' % (delta, name, out)
Simon Glassdb17fb82015-02-05 22:06:15 -07001213
Simon Glasscad8abf2015-08-25 21:52:14 -06001214 def _AddConfig(lines, name, config_plus, config_minus, config_change):
1215 """Add changes in configuration to a list
Simon Glassdb17fb82015-02-05 22:06:15 -07001216
1217 Args:
Simon Glasscad8abf2015-08-25 21:52:14 -06001218 lines: list to add to
1219 name: config file name
Simon Glassdb17fb82015-02-05 22:06:15 -07001220 config_plus: configurations added, dictionary
1221 key: config name
1222 value: config value
1223 config_minus: configurations removed, dictionary
1224 key: config name
1225 value: config value
1226 config_change: configurations changed, dictionary
1227 key: config name
1228 value: config value
1229 """
1230 if config_plus:
Simon Glasscad8abf2015-08-25 21:52:14 -06001231 lines.append(_CalcConfig('+', name, config_plus))
Simon Glassdb17fb82015-02-05 22:06:15 -07001232 if config_minus:
Simon Glasscad8abf2015-08-25 21:52:14 -06001233 lines.append(_CalcConfig('-', name, config_minus))
Simon Glassdb17fb82015-02-05 22:06:15 -07001234 if config_change:
Simon Glasscad8abf2015-08-25 21:52:14 -06001235 lines.append(_CalcConfig('c', name, config_change))
1236
1237 def _OutputConfigInfo(lines):
1238 for line in lines:
1239 if not line:
1240 continue
1241 if line[0] == '+':
1242 col = self.col.GREEN
1243 elif line[0] == '-':
1244 col = self.col.RED
1245 elif line[0] == 'c':
1246 col = self.col.YELLOW
1247 Print(' ' + line, newline=True, colour=col)
1248
Simon Glassac500222020-04-09 15:08:28 -06001249 def _OutputErrLines(err_lines, colour):
1250 """Output the line of error/warning lines, if not empty
1251
1252 Also increments self._error_lines if err_lines not empty
1253
1254 Args:
Simon Glassde0fefc2020-04-09 15:08:36 -06001255 err_lines: List of ErrLine objects, each an error or warning
1256 line, possibly including a list of boards with that
1257 error/warning
Simon Glassac500222020-04-09 15:08:28 -06001258 colour: Colour to use for output
1259 """
1260 if err_lines:
Simon Glassea49f9b2020-04-09 15:08:37 -06001261 out_list = []
Simon Glassde0fefc2020-04-09 15:08:36 -06001262 for line in err_lines:
1263 boards = ''
1264 names = [board.target for board in line.boards]
Simon Glass070589b2020-04-09 15:08:38 -06001265 board_str = ' '.join(names) if names else ''
Simon Glassea49f9b2020-04-09 15:08:37 -06001266 if board_str:
1267 out = self.col.Color(colour, line.char + '(')
1268 out += self.col.Color(self.col.MAGENTA, board_str,
1269 bright=False)
1270 out += self.col.Color(colour, ') %s' % line.errline)
1271 else:
1272 out = self.col.Color(colour, line.char + line.errline)
1273 out_list.append(out)
1274 Print('\n'.join(out_list))
Simon Glassac500222020-04-09 15:08:28 -06001275 self._error_lines += 1
1276
Simon Glassdb17fb82015-02-05 22:06:15 -07001277
Simon Glass454507f2018-11-06 16:02:12 -07001278 ok_boards = [] # List of boards fixed since last commit
Simon Glass071a1782018-11-06 16:02:13 -07001279 warn_boards = [] # List of boards with warnings since last commit
Simon Glass454507f2018-11-06 16:02:12 -07001280 err_boards = [] # List of new broken boards since last commit
1281 new_boards = [] # List of boards that didn't exist last time
1282 unknown_boards = [] # List of boards that were not built
Simon Glassc05694f2013-04-03 11:07:16 +00001283
1284 for target in board_dict:
1285 if target not in board_selected:
1286 continue
1287
1288 # If the board was built last time, add its outcome to a list
1289 if target in self._base_board_dict:
1290 base_outcome = self._base_board_dict[target].rc
1291 outcome = board_dict[target]
1292 if outcome.rc == OUTCOME_UNKNOWN:
Simon Glass454507f2018-11-06 16:02:12 -07001293 unknown_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001294 elif outcome.rc < base_outcome:
Simon Glass071a1782018-11-06 16:02:13 -07001295 if outcome.rc == OUTCOME_WARNING:
1296 warn_boards.append(target)
1297 else:
1298 ok_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001299 elif outcome.rc > base_outcome:
Simon Glass071a1782018-11-06 16:02:13 -07001300 if outcome.rc == OUTCOME_WARNING:
1301 warn_boards.append(target)
1302 else:
1303 err_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001304 else:
Simon Glass454507f2018-11-06 16:02:12 -07001305 new_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001306
Simon Glassac500222020-04-09 15:08:28 -06001307 # Get a list of errors and warnings that have appeared, and disappeared
Simon Glass03749d42014-08-28 09:43:44 -06001308 better_err, worse_err = _CalcErrorDelta(self._base_err_lines,
1309 self._base_err_line_boards, err_lines, err_line_boards, '')
1310 better_warn, worse_warn = _CalcErrorDelta(self._base_warn_lines,
1311 self._base_warn_line_boards, warn_lines, warn_line_boards, 'w')
Simon Glassc05694f2013-04-03 11:07:16 +00001312
1313 # Display results by arch
Simon Glass071a1782018-11-06 16:02:13 -07001314 if any((ok_boards, warn_boards, err_boards, unknown_boards, new_boards,
1315 worse_err, better_err, worse_warn, better_warn)):
Simon Glassc05694f2013-04-03 11:07:16 +00001316 arch_list = {}
Simon Glass454507f2018-11-06 16:02:12 -07001317 self.AddOutcome(board_selected, arch_list, ok_boards, '',
Simon Glassc05694f2013-04-03 11:07:16 +00001318 self.col.GREEN)
Simon Glass071a1782018-11-06 16:02:13 -07001319 self.AddOutcome(board_selected, arch_list, warn_boards, 'w+',
1320 self.col.YELLOW)
Simon Glass454507f2018-11-06 16:02:12 -07001321 self.AddOutcome(board_selected, arch_list, err_boards, '+',
Simon Glassc05694f2013-04-03 11:07:16 +00001322 self.col.RED)
Simon Glass454507f2018-11-06 16:02:12 -07001323 self.AddOutcome(board_selected, arch_list, new_boards, '*', self.col.BLUE)
Simon Glassc05694f2013-04-03 11:07:16 +00001324 if self._show_unknown:
Simon Glass454507f2018-11-06 16:02:12 -07001325 self.AddOutcome(board_selected, arch_list, unknown_boards, '?',
Simon Glassc05694f2013-04-03 11:07:16 +00001326 self.col.MAGENTA)
Simon Glassc78ed662019-10-31 07:42:53 -06001327 for arch, target_list in arch_list.items():
Simon Glass4433aa92014-09-05 19:00:07 -06001328 Print('%10s: %s' % (arch, target_list))
Simon Glassbb4dffb2014-08-09 15:33:06 -06001329 self._error_lines += 1
Simon Glassac500222020-04-09 15:08:28 -06001330 _OutputErrLines(better_err, colour=self.col.GREEN)
1331 _OutputErrLines(worse_err, colour=self.col.RED)
1332 _OutputErrLines(better_warn, colour=self.col.CYAN)
Simon Glass564ddac2020-04-09 15:08:35 -06001333 _OutputErrLines(worse_warn, colour=self.col.YELLOW)
Simon Glassc05694f2013-04-03 11:07:16 +00001334
1335 if show_sizes:
1336 self.PrintSizeSummary(board_selected, board_dict, show_detail,
1337 show_bloat)
1338
Alex Kiernan4059e302018-05-31 04:48:34 +00001339 if show_environment and self._base_environment:
1340 lines = []
1341
1342 for target in board_dict:
1343 if target not in board_selected:
1344 continue
1345
1346 tbase = self._base_environment[target]
1347 tenvironment = environment[target]
1348 environment_plus = {}
1349 environment_minus = {}
1350 environment_change = {}
1351 base = tbase.environment
Simon Glassc78ed662019-10-31 07:42:53 -06001352 for key, value in tenvironment.environment.items():
Alex Kiernan4059e302018-05-31 04:48:34 +00001353 if key not in base:
1354 environment_plus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001355 for key, value in base.items():
Alex Kiernan4059e302018-05-31 04:48:34 +00001356 if key not in tenvironment.environment:
1357 environment_minus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001358 for key, value in base.items():
Alex Kiernan4059e302018-05-31 04:48:34 +00001359 new_value = tenvironment.environment.get(key)
1360 if new_value and value != new_value:
1361 desc = '%s -> %s' % (value, new_value)
1362 environment_change[key] = desc
1363
1364 _AddConfig(lines, target, environment_plus, environment_minus,
1365 environment_change)
1366
1367 _OutputConfigInfo(lines)
1368
Simon Glasscad8abf2015-08-25 21:52:14 -06001369 if show_config and self._base_config:
1370 summary = {}
1371 arch_config_plus = {}
1372 arch_config_minus = {}
1373 arch_config_change = {}
1374 arch_list = []
1375
1376 for target in board_dict:
1377 if target not in board_selected:
1378 continue
1379 arch = board_selected[target].arch
1380 if arch not in arch_list:
1381 arch_list.append(arch)
1382
1383 for arch in arch_list:
1384 arch_config_plus[arch] = {}
1385 arch_config_minus[arch] = {}
1386 arch_config_change[arch] = {}
Simon Glasscde5c302016-11-13 14:25:53 -07001387 for name in self.config_filenames:
Simon Glasscad8abf2015-08-25 21:52:14 -06001388 arch_config_plus[arch][name] = {}
1389 arch_config_minus[arch][name] = {}
1390 arch_config_change[arch][name] = {}
1391
1392 for target in board_dict:
1393 if target not in board_selected:
Simon Glassdb17fb82015-02-05 22:06:15 -07001394 continue
Simon Glasscad8abf2015-08-25 21:52:14 -06001395
1396 arch = board_selected[target].arch
1397
1398 all_config_plus = {}
1399 all_config_minus = {}
1400 all_config_change = {}
1401 tbase = self._base_config[target]
1402 tconfig = config[target]
1403 lines = []
Simon Glasscde5c302016-11-13 14:25:53 -07001404 for name in self.config_filenames:
Simon Glasscad8abf2015-08-25 21:52:14 -06001405 if not tconfig.config[name]:
1406 continue
1407 config_plus = {}
1408 config_minus = {}
1409 config_change = {}
1410 base = tbase.config[name]
Simon Glassc78ed662019-10-31 07:42:53 -06001411 for key, value in tconfig.config[name].items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001412 if key not in base:
1413 config_plus[key] = value
1414 all_config_plus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001415 for key, value in base.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001416 if key not in tconfig.config[name]:
1417 config_minus[key] = value
1418 all_config_minus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001419 for key, value in base.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001420 new_value = tconfig.config.get(key)
1421 if new_value and value != new_value:
1422 desc = '%s -> %s' % (value, new_value)
1423 config_change[key] = desc
1424 all_config_change[key] = desc
1425
1426 arch_config_plus[arch][name].update(config_plus)
1427 arch_config_minus[arch][name].update(config_minus)
1428 arch_config_change[arch][name].update(config_change)
1429
1430 _AddConfig(lines, name, config_plus, config_minus,
1431 config_change)
1432 _AddConfig(lines, 'all', all_config_plus, all_config_minus,
1433 all_config_change)
1434 summary[target] = '\n'.join(lines)
1435
1436 lines_by_target = {}
Simon Glassc78ed662019-10-31 07:42:53 -06001437 for target, lines in summary.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001438 if lines in lines_by_target:
1439 lines_by_target[lines].append(target)
1440 else:
1441 lines_by_target[lines] = [target]
1442
1443 for arch in arch_list:
1444 lines = []
1445 all_plus = {}
1446 all_minus = {}
1447 all_change = {}
Simon Glasscde5c302016-11-13 14:25:53 -07001448 for name in self.config_filenames:
Simon Glasscad8abf2015-08-25 21:52:14 -06001449 all_plus.update(arch_config_plus[arch][name])
1450 all_minus.update(arch_config_minus[arch][name])
1451 all_change.update(arch_config_change[arch][name])
1452 _AddConfig(lines, name, arch_config_plus[arch][name],
1453 arch_config_minus[arch][name],
1454 arch_config_change[arch][name])
1455 _AddConfig(lines, 'all', all_plus, all_minus, all_change)
1456 #arch_summary[target] = '\n'.join(lines)
1457 if lines:
1458 Print('%s:' % arch)
1459 _OutputConfigInfo(lines)
1460
Simon Glassc78ed662019-10-31 07:42:53 -06001461 for lines, targets in lines_by_target.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001462 if not lines:
1463 continue
1464 Print('%s :' % ' '.join(sorted(targets)))
1465 _OutputConfigInfo(lines.split('\n'))
1466
Simon Glassdb17fb82015-02-05 22:06:15 -07001467
Simon Glassc05694f2013-04-03 11:07:16 +00001468 # Save our updated information for the next call to this function
1469 self._base_board_dict = board_dict
1470 self._base_err_lines = err_lines
Simon Glass03749d42014-08-28 09:43:44 -06001471 self._base_warn_lines = warn_lines
1472 self._base_err_line_boards = err_line_boards
1473 self._base_warn_line_boards = warn_line_boards
Simon Glassdb17fb82015-02-05 22:06:15 -07001474 self._base_config = config
Alex Kiernan4059e302018-05-31 04:48:34 +00001475 self._base_environment = environment
Simon Glassc05694f2013-04-03 11:07:16 +00001476
1477 # Get a list of boards that did not get built, if needed
1478 not_built = []
1479 for board in board_selected:
1480 if not board in board_dict:
1481 not_built.append(board)
1482 if not_built:
Simon Glass4433aa92014-09-05 19:00:07 -06001483 Print("Boards not built (%d): %s" % (len(not_built),
1484 ', '.join(not_built)))
Simon Glassc05694f2013-04-03 11:07:16 +00001485
Simon Glasseb48bbc2014-08-09 15:33:02 -06001486 def ProduceResultSummary(self, commit_upto, commits, board_selected):
Simon Glass03749d42014-08-28 09:43:44 -06001487 (board_dict, err_lines, err_line_boards, warn_lines,
Alex Kiernan4059e302018-05-31 04:48:34 +00001488 warn_line_boards, config, environment) = self.GetResultSummary(
Simon Glass3394c9f2014-08-28 09:43:43 -06001489 board_selected, commit_upto,
Simon Glassdb17fb82015-02-05 22:06:15 -07001490 read_func_sizes=self._show_bloat,
Alex Kiernan4059e302018-05-31 04:48:34 +00001491 read_config=self._show_config,
1492 read_environment=self._show_environment)
Simon Glasseb48bbc2014-08-09 15:33:02 -06001493 if commits:
1494 msg = '%02d: %s' % (commit_upto + 1,
1495 commits[commit_upto].subject)
Simon Glass4433aa92014-09-05 19:00:07 -06001496 Print(msg, colour=self.col.BLUE)
Simon Glasseb48bbc2014-08-09 15:33:02 -06001497 self.PrintResultSummary(board_selected, board_dict,
Simon Glass3394c9f2014-08-28 09:43:43 -06001498 err_lines if self._show_errors else [], err_line_boards,
Simon Glass03749d42014-08-28 09:43:44 -06001499 warn_lines if self._show_errors else [], warn_line_boards,
Alex Kiernan4059e302018-05-31 04:48:34 +00001500 config, environment, self._show_sizes, self._show_detail,
1501 self._show_bloat, self._show_config, self._show_environment)
Simon Glassc05694f2013-04-03 11:07:16 +00001502
Simon Glasseb48bbc2014-08-09 15:33:02 -06001503 def ShowSummary(self, commits, board_selected):
Simon Glassc05694f2013-04-03 11:07:16 +00001504 """Show a build summary for U-Boot for a given board list.
1505
1506 Reset the result summary, then repeatedly call GetResultSummary on
1507 each commit's results, then display the differences we see.
1508
1509 Args:
1510 commit: Commit objects to summarise
1511 board_selected: Dict containing boards to summarise
Simon Glassc05694f2013-04-03 11:07:16 +00001512 """
Simon Glassd326ad72014-08-09 15:32:59 -06001513 self.commit_count = len(commits) if commits else 1
Simon Glassc05694f2013-04-03 11:07:16 +00001514 self.commits = commits
1515 self.ResetResultSummary(board_selected)
Simon Glassbb4dffb2014-08-09 15:33:06 -06001516 self._error_lines = 0
Simon Glassc05694f2013-04-03 11:07:16 +00001517
1518 for commit_upto in range(0, self.commit_count, self._step):
Simon Glasseb48bbc2014-08-09 15:33:02 -06001519 self.ProduceResultSummary(commit_upto, commits, board_selected)
Simon Glassbb4dffb2014-08-09 15:33:06 -06001520 if not self._error_lines:
Simon Glass4433aa92014-09-05 19:00:07 -06001521 Print('(no errors to report)', colour=self.col.GREEN)
Simon Glassc05694f2013-04-03 11:07:16 +00001522
1523
1524 def SetupBuild(self, board_selected, commits):
1525 """Set up ready to start a build.
1526
1527 Args:
1528 board_selected: Selected boards to build
1529 commits: Selected commits to build
1530 """
1531 # First work out how many commits we will build
Simon Glassc78ed662019-10-31 07:42:53 -06001532 count = (self.commit_count + self._step - 1) // self._step
Simon Glassc05694f2013-04-03 11:07:16 +00001533 self.count = len(board_selected) * count
1534 self.upto = self.warned = self.fail = 0
1535 self._timestamps = collections.deque()
1536
Simon Glassc05694f2013-04-03 11:07:16 +00001537 def GetThreadDir(self, thread_num):
1538 """Get the directory path to the working dir for a thread.
1539
1540 Args:
Simon Glassc635d892021-01-30 22:17:46 -07001541 thread_num: Number of thread to check (-1 for main process, which
1542 is treated as 0)
Simon Glassc05694f2013-04-03 11:07:16 +00001543 """
Simon Glassb6eb8cf2020-03-18 09:42:42 -06001544 if self.work_in_output:
1545 return self._working_dir
Simon Glassc635d892021-01-30 22:17:46 -07001546 return os.path.join(self._working_dir, '%02d' % max(thread_num, 0))
Simon Glassc05694f2013-04-03 11:07:16 +00001547
Simon Glassd326ad72014-08-09 15:32:59 -06001548 def _PrepareThread(self, thread_num, setup_git):
Simon Glassc05694f2013-04-03 11:07:16 +00001549 """Prepare the working directory for a thread.
1550
1551 This clones or fetches the repo into the thread's work directory.
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001552 Optionally, it can create a linked working tree of the repo in the
1553 thread's work directory instead.
Simon Glassc05694f2013-04-03 11:07:16 +00001554
1555 Args:
1556 thread_num: Thread number (0, 1, ...)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001557 setup_git:
1558 'clone' to set up a git clone
1559 'worktree' to set up a git worktree
Simon Glassc05694f2013-04-03 11:07:16 +00001560 """
1561 thread_dir = self.GetThreadDir(thread_num)
Simon Glass4a1e88b2014-08-09 15:33:00 -06001562 builderthread.Mkdir(thread_dir)
Simon Glassc05694f2013-04-03 11:07:16 +00001563 git_dir = os.path.join(thread_dir, '.git')
1564
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001565 # Create a worktree or a git repo clone for this thread if it
1566 # doesn't already exist
Simon Glassd326ad72014-08-09 15:32:59 -06001567 if setup_git and self.git_dir:
Simon Glassc05694f2013-04-03 11:07:16 +00001568 src_dir = os.path.abspath(self.git_dir)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001569 if os.path.isdir(git_dir):
1570 # This is a clone of the src_dir repo, we can keep using
1571 # it but need to fetch from src_dir.
Simon Glass43054932020-04-09 15:08:43 -06001572 Print('\rFetching repo for thread %d' % thread_num,
1573 newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001574 gitutil.Fetch(git_dir, thread_dir)
Simon Glass43054932020-04-09 15:08:43 -06001575 terminal.PrintClear()
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001576 elif os.path.isfile(git_dir):
1577 # This is a worktree of the src_dir repo, we don't need to
1578 # create it again or update it in any way.
1579 pass
1580 elif os.path.exists(git_dir):
1581 # Don't know what could trigger this, but we probably
1582 # can't create a git worktree/clone here.
1583 raise ValueError('Git dir %s exists, but is not a file '
1584 'or a directory.' % git_dir)
1585 elif setup_git == 'worktree':
1586 Print('\rChecking out worktree for thread %d' % thread_num,
1587 newline=False)
1588 gitutil.AddWorktree(src_dir, thread_dir)
1589 terminal.PrintClear()
1590 elif setup_git == 'clone' or setup_git == True:
Simon Glass2e2a6e62016-09-18 16:48:31 -06001591 Print('\rCloning repo for thread %d' % thread_num,
1592 newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001593 gitutil.Clone(src_dir, thread_dir)
Simon Glassdd95b0b2020-04-09 15:08:42 -06001594 terminal.PrintClear()
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001595 else:
1596 raise ValueError("Can't setup git repo with %s." % setup_git)
Simon Glassc05694f2013-04-03 11:07:16 +00001597
Simon Glassd326ad72014-08-09 15:32:59 -06001598 def _PrepareWorkingSpace(self, max_threads, setup_git):
Simon Glassc05694f2013-04-03 11:07:16 +00001599 """Prepare the working directory for use.
1600
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001601 Set up the git repo for each thread. Creates a linked working tree
1602 if git-worktree is available, or clones the repo if it isn't.
Simon Glassc05694f2013-04-03 11:07:16 +00001603
1604 Args:
Simon Glassc635d892021-01-30 22:17:46 -07001605 max_threads: Maximum number of threads we expect to need. If 0 then
1606 1 is set up, since the main process still needs somewhere to
1607 work
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001608 setup_git: True to set up a git worktree or a git clone
Simon Glassc05694f2013-04-03 11:07:16 +00001609 """
Simon Glass4a1e88b2014-08-09 15:33:00 -06001610 builderthread.Mkdir(self._working_dir)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001611 if setup_git and self.git_dir:
1612 src_dir = os.path.abspath(self.git_dir)
1613 if gitutil.CheckWorktreeIsAvailable(src_dir):
1614 setup_git = 'worktree'
1615 # If we previously added a worktree but the directory for it
1616 # got deleted, we need to prune its files from the repo so
1617 # that we can check out another in its place.
1618 gitutil.PruneWorktrees(src_dir)
1619 else:
1620 setup_git = 'clone'
Simon Glassc635d892021-01-30 22:17:46 -07001621
1622 # Always do at least one thread
1623 for thread in range(max(max_threads, 1)):
Simon Glassd326ad72014-08-09 15:32:59 -06001624 self._PrepareThread(thread, setup_git)
Simon Glassc05694f2013-04-03 11:07:16 +00001625
Simon Glass5dc1ca72020-03-18 09:42:45 -06001626 def _GetOutputSpaceRemovals(self):
Simon Glassc05694f2013-04-03 11:07:16 +00001627 """Get the output directories ready to receive files.
1628
Simon Glass5dc1ca72020-03-18 09:42:45 -06001629 Figure out what needs to be deleted in the output directory before it
1630 can be used. We only delete old buildman directories which have the
1631 expected name pattern. See _GetOutputDir().
1632
1633 Returns:
1634 List of full paths of directories to remove
Simon Glassc05694f2013-04-03 11:07:16 +00001635 """
Simon Glassb9a9b7c2014-12-01 17:33:53 -07001636 if not self.commits:
1637 return
Simon Glassc05694f2013-04-03 11:07:16 +00001638 dir_list = []
1639 for commit_upto in range(self.commit_count):
1640 dir_list.append(self._GetOutputDir(commit_upto))
1641
Simon Glass83cb6cc2016-09-18 16:48:32 -06001642 to_remove = []
Simon Glassc05694f2013-04-03 11:07:16 +00001643 for dirname in glob.glob(os.path.join(self.base_dir, '*')):
1644 if dirname not in dir_list:
Simon Glass5dc1ca72020-03-18 09:42:45 -06001645 leaf = dirname[len(self.base_dir) + 1:]
Ovidiu Panaitee8e9cb2020-05-15 09:30:12 +03001646 m = re.match('[0-9]+_g[0-9a-f]+_.*', leaf)
Simon Glass5dc1ca72020-03-18 09:42:45 -06001647 if m:
1648 to_remove.append(dirname)
1649 return to_remove
1650
1651 def _PrepareOutputSpace(self):
1652 """Get the output directories ready to receive files.
1653
1654 We delete any output directories which look like ones we need to
1655 create. Having left over directories is confusing when the user wants
1656 to check the output manually.
1657 """
1658 to_remove = self._GetOutputSpaceRemovals()
Simon Glass83cb6cc2016-09-18 16:48:32 -06001659 if to_remove:
Simon Glass44028272020-03-18 09:42:46 -06001660 Print('Removing %d old build directories...' % len(to_remove),
Simon Glass83cb6cc2016-09-18 16:48:32 -06001661 newline=False)
1662 for dirname in to_remove:
Simon Glassc05694f2013-04-03 11:07:16 +00001663 shutil.rmtree(dirname)
Simon Glass43054932020-04-09 15:08:43 -06001664 terminal.PrintClear()
Simon Glassc05694f2013-04-03 11:07:16 +00001665
Simon Glass78e418e2014-08-09 15:33:03 -06001666 def BuildBoards(self, commits, board_selected, keep_outputs, verbose):
Simon Glassc05694f2013-04-03 11:07:16 +00001667 """Build all commits for a list of boards
1668
1669 Args:
1670 commits: List of commits to be build, each a Commit object
1671 boards_selected: Dict of selected boards, key is target name,
1672 value is Board object
Simon Glassc05694f2013-04-03 11:07:16 +00001673 keep_outputs: True to save build output files
Simon Glass78e418e2014-08-09 15:33:03 -06001674 verbose: Display build results as they are completed
Simon Glassc2f91072014-08-28 09:43:39 -06001675 Returns:
1676 Tuple containing:
1677 - number of boards that failed to build
1678 - number of boards that issued warnings
Simon Glassc05694f2013-04-03 11:07:16 +00001679 """
Simon Glassd326ad72014-08-09 15:32:59 -06001680 self.commit_count = len(commits) if commits else 1
Simon Glassc05694f2013-04-03 11:07:16 +00001681 self.commits = commits
Simon Glass78e418e2014-08-09 15:33:03 -06001682 self._verbose = verbose
Simon Glassc05694f2013-04-03 11:07:16 +00001683
1684 self.ResetResultSummary(board_selected)
Thierry Reding336d5bd2014-08-19 10:22:39 +02001685 builderthread.Mkdir(self.base_dir, parents = True)
Simon Glassd326ad72014-08-09 15:32:59 -06001686 self._PrepareWorkingSpace(min(self.num_threads, len(board_selected)),
1687 commits is not None)
Simon Glassc05694f2013-04-03 11:07:16 +00001688 self._PrepareOutputSpace()
Simon Glasse0f23602016-09-18 16:48:33 -06001689 Print('\rStarting build...', newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001690 self.SetupBuild(board_selected, commits)
1691 self.ProcessResult(None)
1692
1693 # Create jobs to build all commits for each board
Simon Glassc78ed662019-10-31 07:42:53 -06001694 for brd in board_selected.values():
Simon Glass4a1e88b2014-08-09 15:33:00 -06001695 job = builderthread.BuilderJob()
Simon Glassc05694f2013-04-03 11:07:16 +00001696 job.board = brd
1697 job.commits = commits
1698 job.keep_outputs = keep_outputs
Simon Glassb6eb8cf2020-03-18 09:42:42 -06001699 job.work_in_output = self.work_in_output
Simon Glassc05694f2013-04-03 11:07:16 +00001700 job.step = self._step
Simon Glassc635d892021-01-30 22:17:46 -07001701 if self.num_threads:
1702 self.queue.put(job)
1703 else:
1704 results = self._single_builder.RunJob(job)
Simon Glassc05694f2013-04-03 11:07:16 +00001705
Simon Glassc635d892021-01-30 22:17:46 -07001706 if self.num_threads:
1707 term = threading.Thread(target=self.queue.join)
1708 term.setDaemon(True)
1709 term.start()
1710 while term.is_alive():
1711 term.join(100)
Simon Glassc05694f2013-04-03 11:07:16 +00001712
Simon Glassc635d892021-01-30 22:17:46 -07001713 # Wait until we have processed all output
1714 self.out_queue.join()
Simon Glass4433aa92014-09-05 19:00:07 -06001715 Print()
Simon Glass726ae812020-04-09 15:08:47 -06001716
1717 msg = 'Completed: %d total built' % self.count
1718 if self.already_done:
1719 msg += ' (%d previously' % self.already_done
1720 if self.already_done != self.count:
1721 msg += ', %d newly' % (self.count - self.already_done)
1722 msg += ')'
1723 duration = datetime.now() - self._start_time
1724 if duration > timedelta(microseconds=1000000):
1725 if duration.microseconds >= 500000:
1726 duration = duration + timedelta(seconds=1)
1727 duration = duration - timedelta(microseconds=duration.microseconds)
Simon Glassa6793662020-07-19 12:40:26 -06001728 rate = float(self.count) / duration.total_seconds()
1729 msg += ', duration %s, rate %1.2f' % (duration, rate)
Simon Glass726ae812020-04-09 15:08:47 -06001730 Print(msg)
1731
Simon Glassc2f91072014-08-28 09:43:39 -06001732 return (self.fail, self.warned)