blob: ccf46ac5f6700ac86f29cb3948c2022fa6518a93 [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
195 _timestamps: List of timestamps for the completion of the last
196 last _timestamp_count builds. Each is a datetime object.
197 _timestamp_count: Number of timestamps to keep in our list.
198 _working_dir: Base working directory containing all threads
199 """
200 class Outcome:
201 """Records a build outcome for a single make invocation
202
203 Public Members:
204 rc: Outcome value (OUTCOME_...)
205 err_lines: List of error lines or [] if none
206 sizes: Dictionary of image size information, keyed by filename
207 - Each value is itself a dictionary containing
208 values for 'text', 'data' and 'bss', being the integer
209 size in bytes of each section.
210 func_sizes: Dictionary keyed by filename - e.g. 'u-boot'. Each
211 value is itself a dictionary:
212 key: function name
213 value: Size of function in bytes
Simon Glass843312d2015-02-05 22:06:15 -0700214 config: Dictionary keyed by filename - e.g. '.config'. Each
215 value is itself a dictionary:
216 key: config name
217 value: config value
Alex Kiernan48ae4122018-05-31 04:48:34 +0000218 environment: Dictionary keyed by environment variable, Each
219 value is the value of environment variable.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000220 """
Alex Kiernan48ae4122018-05-31 04:48:34 +0000221 def __init__(self, rc, err_lines, sizes, func_sizes, config,
222 environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000223 self.rc = rc
224 self.err_lines = err_lines
225 self.sizes = sizes
226 self.func_sizes = func_sizes
Simon Glass843312d2015-02-05 22:06:15 -0700227 self.config = config
Alex Kiernan48ae4122018-05-31 04:48:34 +0000228 self.environment = environment
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000229
230 def __init__(self, toolchains, base_dir, git_dir, num_threads, num_jobs,
Simon Glass5971ab52014-12-01 17:33:55 -0700231 gnu_make='make', checkout=True, show_unknown=True, step=1,
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600232 no_subdirs=False, full_path=False, verbose_build=False,
Simon Glassb50113f2016-11-13 14:25:51 -0700233 incremental=False, per_board_out_dir=False,
Daniel Schwierzeck2371d1b2018-01-26 16:31:05 +0100234 config_only=False, squash_config_y=False,
Simon Glassd829f122020-03-18 09:42:42 -0600235 warnings_as_errors=False, work_in_output=False):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000236 """Create a new Builder object
237
238 Args:
239 toolchains: Toolchains object to use for building
240 base_dir: Base directory to use for builder
241 git_dir: Git directory containing source repository
242 num_threads: Number of builder threads to run
243 num_jobs: Number of jobs to run at once (passed to make as -j)
Masahiro Yamada99796922014-07-22 11:19:09 +0900244 gnu_make: the command name of GNU Make.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000245 checkout: True to check out source, False to skip that step.
246 This is used for testing.
247 show_unknown: Show unknown boards (those not built) in summary
248 step: 1 to process every commit, n to process every nth commit
Simon Glassbb1501f2014-12-01 17:34:00 -0700249 no_subdirs: Don't create subdirectories when building current
250 source for a single board
251 full_path: Return the full path in CROSS_COMPILE and don't set
252 PATH
Simon Glassd2ce6582014-12-01 17:34:07 -0700253 verbose_build: Run build with V=1 and don't use 'make -s'
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600254 incremental: Always perform incremental builds; don't run make
255 mrproper when configuring
256 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()
284 self.force_config_on_failure = True
Simon Glass4266dc22014-07-13 12:22:31 -0600285 self.force_build_failures = False
Simon Glass97e91522014-07-14 17:51:02 -0600286 self.force_reconfig = False
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000287 self._step = step
Simon Glass189a4962014-07-14 17:51:03 -0600288 self.in_tree = False
Simon Glass28370c12014-08-09 15:33:06 -0600289 self._error_lines = 0
Simon Glass5971ab52014-12-01 17:33:55 -0700290 self.no_subdirs = no_subdirs
Simon Glassbb1501f2014-12-01 17:34:00 -0700291 self.full_path = full_path
Simon Glassd2ce6582014-12-01 17:34:07 -0700292 self.verbose_build = verbose_build
Simon Glassb50113f2016-11-13 14:25:51 -0700293 self.config_only = config_only
Simon Glassb464f8e2016-11-13 14:25:53 -0700294 self.squash_config_y = squash_config_y
295 self.config_filenames = BASE_CONFIG_FILENAMES
Simon Glassd829f122020-03-18 09:42:42 -0600296 self.work_in_output = work_in_output
Simon Glassb464f8e2016-11-13 14:25:53 -0700297 if not self.squash_config_y:
298 self.config_filenames += EXTRA_CONFIG_FILENAMES
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000299
Daniel Schwierzeck2371d1b2018-01-26 16:31:05 +0100300 self.warnings_as_errors = warnings_as_errors
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000301 self.col = terminal.Color()
302
Simon Glasse30965d2014-08-28 09:43:44 -0600303 self._re_function = re.compile('(.*): In function.*')
304 self._re_files = re.compile('In file included from.*')
305 self._re_warning = re.compile('(.*):(\d*):(\d*): warning: .*')
Simon Glass2d483332018-11-06 16:02:11 -0700306 self._re_dtb_warning = re.compile('(.*): Warning .*')
Simon Glasse30965d2014-08-28 09:43:44 -0600307 self._re_note = re.compile('(.*):(\d*):(\d*): note: this is the location of the previous.*')
308
Simon Glassc05aa032019-10-31 07:42:53 -0600309 self.queue = queue.Queue()
310 self.out_queue = queue.Queue()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000311 for i in range(self.num_threads):
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600312 t = builderthread.BuilderThread(self, i, incremental,
313 per_board_out_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000314 t.setDaemon(True)
315 t.start()
316 self.threads.append(t)
317
Simon Glass190064b2014-08-09 15:33:00 -0600318 t = builderthread.ResultThread(self)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000319 t.setDaemon(True)
320 t.start()
321 self.threads.append(t)
322
323 ignore_lines = ['(make.*Waiting for unfinished)', '(Segmentation fault)']
324 self.re_make_err = re.compile('|'.join(ignore_lines))
325
Simon Glass2f256642016-09-18 16:48:37 -0600326 # Handle existing graceful with SIGINT / Ctrl-C
327 signal.signal(signal.SIGINT, self.signal_handler)
328
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000329 def __del__(self):
330 """Get rid of all threads created by the builder"""
331 for t in self.threads:
332 del t
333
Simon Glass2f256642016-09-18 16:48:37 -0600334 def signal_handler(self, signal, frame):
335 sys.exit(1)
336
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600337 def SetDisplayOptions(self, show_errors=False, show_sizes=False,
Simon Glassed966652014-08-28 09:43:43 -0600338 show_detail=False, show_bloat=False,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000339 list_error_boards=False, show_config=False,
340 show_environment=False):
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600341 """Setup display options for the builder.
342
343 show_errors: True to show summarised error/warning info
344 show_sizes: Show size deltas
Simon Glassf9c094b2020-03-18 09:42:43 -0600345 show_detail: Show size delta detail for each board if show_sizes
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600346 show_bloat: Show detail for each function
Simon Glassed966652014-08-28 09:43:43 -0600347 list_error_boards: Show the boards which caused each error/warning
Simon Glass843312d2015-02-05 22:06:15 -0700348 show_config: Show config deltas
Alex Kiernan48ae4122018-05-31 04:48:34 +0000349 show_environment: Show environment deltas
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600350 """
351 self._show_errors = show_errors
352 self._show_sizes = show_sizes
353 self._show_detail = show_detail
354 self._show_bloat = show_bloat
Simon Glassed966652014-08-28 09:43:43 -0600355 self._list_error_boards = list_error_boards
Simon Glass843312d2015-02-05 22:06:15 -0700356 self._show_config = show_config
Alex Kiernan48ae4122018-05-31 04:48:34 +0000357 self._show_environment = show_environment
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600358
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000359 def _AddTimestamp(self):
360 """Add a new timestamp to the list and record the build period.
361
362 The build period is the length of time taken to perform a single
363 build (one board, one commit).
364 """
365 now = datetime.now()
366 self._timestamps.append(now)
367 count = len(self._timestamps)
368 delta = self._timestamps[-1] - self._timestamps[0]
369 seconds = delta.total_seconds()
370
371 # If we have enough data, estimate build period (time taken for a
372 # single build) and therefore completion time.
373 if count > 1 and self._next_delay_update < now:
374 self._next_delay_update = now + timedelta(seconds=2)
375 if seconds > 0:
376 self._build_period = float(seconds) / count
377 todo = self.count - self.upto
378 self._complete_delay = timedelta(microseconds=
379 self._build_period * todo * 1000000)
380 # Round it
381 self._complete_delay -= timedelta(
382 microseconds=self._complete_delay.microseconds)
383
384 if seconds > 60:
385 self._timestamps.popleft()
386 count -= 1
387
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000388 def SelectCommit(self, commit, checkout=True):
389 """Checkout the selected commit for this build
390 """
391 self.commit = commit
392 if checkout and self.checkout:
393 gitutil.Checkout(commit.hash)
394
395 def Make(self, commit, brd, stage, cwd, *args, **kwargs):
396 """Run make
397
398 Args:
399 commit: Commit object that is being built
400 brd: Board object that is being built
Roger Meierfd18a892014-08-20 22:10:29 +0200401 stage: Stage that we are at (mrproper, config, build)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000402 cwd: Directory where make should be run
403 args: Arguments to pass to make
404 kwargs: Arguments to pass to command.RunPipe()
405 """
Masahiro Yamada99796922014-07-22 11:19:09 +0900406 cmd = [self.gnu_make] + list(args)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000407 result = command.RunPipe([cmd], capture=True, capture_stderr=True,
Simon Glasse62a24c2018-09-17 23:55:42 -0600408 cwd=cwd, raise_on_error=False, infile='/dev/null', **kwargs)
Simon Glass40f11fc2015-02-05 22:06:12 -0700409 if self.verbose_build:
410 result.stdout = '%s\n' % (' '.join(cmd)) + result.stdout
411 result.combined = '%s\n' % (' '.join(cmd)) + result.combined
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000412 return result
413
414 def ProcessResult(self, result):
415 """Process the result of a build, showing progress information
416
417 Args:
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600418 result: A CommandResult object, which indicates the result for
419 a single build
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000420 """
421 col = terminal.Color()
422 if result:
423 target = result.brd.target
424
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000425 self.upto += 1
426 if result.return_code != 0:
427 self.fail += 1
428 elif result.stderr:
429 self.warned += 1
430 if result.already_done:
431 self.already_done += 1
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600432 if self._verbose:
Simon Glass102969bb2020-04-09 15:08:42 -0600433 terminal.PrintClear()
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600434 boards_selected = {target : result.brd}
435 self.ResetResultSummary(boards_selected)
436 self.ProduceResultSummary(result.commit_upto, self.commits,
437 boards_selected)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000438 else:
439 target = '(starting)'
440
441 # Display separate counts for ok, warned and fail
442 ok = self.upto - self.warned - self.fail
443 line = '\r' + self.col.Color(self.col.GREEN, '%5d' % ok)
444 line += self.col.Color(self.col.YELLOW, '%5d' % self.warned)
445 line += self.col.Color(self.col.RED, '%5d' % self.fail)
446
447 name = ' /%-5d ' % self.count
448
449 # Add our current completion time estimate
450 self._AddTimestamp()
451 if self._complete_delay:
452 name += '%s : ' % self._complete_delay
453 # When building all boards for a commit, we can print a commit
454 # progress message.
455 if result and result.commit_upto is None:
456 name += 'commit %2d/%-3d' % (self.commit_upto + 1,
457 self.commit_count)
458
459 name += target
Simon Glass102969bb2020-04-09 15:08:42 -0600460 terminal.PrintClear()
Simon Glass4653a882014-09-05 19:00:07 -0600461 Print(line + name, newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000462
463 def _GetOutputDir(self, commit_upto):
464 """Get the name of the output directory for a commit number
465
466 The output directory is typically .../<branch>/<commit>.
467
468 Args:
469 commit_upto: Commit number to use (0..self.count-1)
470 """
Simon Glass5971ab52014-12-01 17:33:55 -0700471 commit_dir = None
Simon Glassfea58582014-08-09 15:32:59 -0600472 if self.commits:
473 commit = self.commits[commit_upto]
474 subject = commit.subject.translate(trans_valid_chars)
Simon Glass925f6ad2020-03-18 09:42:45 -0600475 # See _GetOutputSpaceRemovals() which parses this name
Simon Glassfea58582014-08-09 15:32:59 -0600476 commit_dir = ('%02d_of_%02d_g%s_%s' % (commit_upto + 1,
477 self.commit_count, commit.hash, subject[:20]))
Simon Glass5971ab52014-12-01 17:33:55 -0700478 elif not self.no_subdirs:
Simon Glassfea58582014-08-09 15:32:59 -0600479 commit_dir = 'current'
Simon Glass5971ab52014-12-01 17:33:55 -0700480 if not commit_dir:
481 return self.base_dir
482 return os.path.join(self.base_dir, commit_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000483
484 def GetBuildDir(self, commit_upto, target):
485 """Get the name of the build directory for a commit number
486
487 The build directory is typically .../<branch>/<commit>/<target>.
488
489 Args:
490 commit_upto: Commit number to use (0..self.count-1)
491 target: Target name
492 """
493 output_dir = self._GetOutputDir(commit_upto)
494 return os.path.join(output_dir, target)
495
496 def GetDoneFile(self, commit_upto, target):
497 """Get the name of the done file for a commit number
498
499 Args:
500 commit_upto: Commit number to use (0..self.count-1)
501 target: Target name
502 """
503 return os.path.join(self.GetBuildDir(commit_upto, target), 'done')
504
505 def GetSizesFile(self, commit_upto, target):
506 """Get the name of the sizes file for a commit number
507
508 Args:
509 commit_upto: Commit number to use (0..self.count-1)
510 target: Target name
511 """
512 return os.path.join(self.GetBuildDir(commit_upto, target), 'sizes')
513
514 def GetFuncSizesFile(self, commit_upto, target, elf_fname):
515 """Get the name of the funcsizes file for a commit number and ELF file
516
517 Args:
518 commit_upto: Commit number to use (0..self.count-1)
519 target: Target name
520 elf_fname: Filename of elf image
521 """
522 return os.path.join(self.GetBuildDir(commit_upto, target),
523 '%s.sizes' % elf_fname.replace('/', '-'))
524
525 def GetObjdumpFile(self, commit_upto, target, elf_fname):
526 """Get the name of the objdump file for a commit number and ELF file
527
528 Args:
529 commit_upto: Commit number to use (0..self.count-1)
530 target: Target name
531 elf_fname: Filename of elf image
532 """
533 return os.path.join(self.GetBuildDir(commit_upto, target),
534 '%s.objdump' % elf_fname.replace('/', '-'))
535
536 def GetErrFile(self, commit_upto, target):
537 """Get the name of the err file for a commit number
538
539 Args:
540 commit_upto: Commit number to use (0..self.count-1)
541 target: Target name
542 """
543 output_dir = self.GetBuildDir(commit_upto, target)
544 return os.path.join(output_dir, 'err')
545
546 def FilterErrors(self, lines):
547 """Filter out errors in which we have no interest
548
549 We should probably use map().
550
551 Args:
552 lines: List of error lines, each a string
553 Returns:
554 New list with only interesting lines included
555 """
556 out_lines = []
557 for line in lines:
558 if not self.re_make_err.search(line):
559 out_lines.append(line)
560 return out_lines
561
562 def ReadFuncSizes(self, fname, fd):
563 """Read function sizes from the output of 'nm'
564
565 Args:
566 fd: File containing data to read
567 fname: Filename we are reading from (just for errors)
568
569 Returns:
570 Dictionary containing size of each function in bytes, indexed by
571 function name.
572 """
573 sym = {}
574 for line in fd.readlines():
575 try:
Tom Rinid08c38c2019-12-06 15:31:31 -0500576 if line.strip():
577 size, type, name = line[:-1].split()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000578 except:
Simon Glass4653a882014-09-05 19:00:07 -0600579 Print("Invalid line in file '%s': '%s'" % (fname, line[:-1]))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000580 continue
581 if type in 'tTdDbB':
582 # function names begin with '.' on 64-bit powerpc
583 if '.' in name[1:]:
584 name = 'static.' + name.split('.')[0]
585 sym[name] = sym.get(name, 0) + int(size, 16)
586 return sym
587
Simon Glass843312d2015-02-05 22:06:15 -0700588 def _ProcessConfig(self, fname):
589 """Read in a .config, autoconf.mk or autoconf.h file
590
591 This function handles all config file types. It ignores comments and
592 any #defines which don't start with CONFIG_.
593
594 Args:
595 fname: Filename to read
596
597 Returns:
598 Dictionary:
599 key: Config name (e.g. CONFIG_DM)
600 value: Config value (e.g. 1)
601 """
602 config = {}
603 if os.path.exists(fname):
604 with open(fname) as fd:
605 for line in fd:
606 line = line.strip()
607 if line.startswith('#define'):
608 values = line[8:].split(' ', 1)
609 if len(values) > 1:
610 key, value = values
611 else:
612 key = values[0]
Simon Glassb464f8e2016-11-13 14:25:53 -0700613 value = '1' if self.squash_config_y else ''
Simon Glass843312d2015-02-05 22:06:15 -0700614 if not key.startswith('CONFIG_'):
615 continue
616 elif not line or line[0] in ['#', '*', '/']:
617 continue
618 else:
619 key, value = line.split('=', 1)
Simon Glassb464f8e2016-11-13 14:25:53 -0700620 if self.squash_config_y and value == 'y':
621 value = '1'
Simon Glass843312d2015-02-05 22:06:15 -0700622 config[key] = value
623 return config
624
Alex Kiernan48ae4122018-05-31 04:48:34 +0000625 def _ProcessEnvironment(self, fname):
626 """Read in a uboot.env file
627
628 This function reads in environment variables from a file.
629
630 Args:
631 fname: Filename to read
632
633 Returns:
634 Dictionary:
635 key: environment variable (e.g. bootlimit)
636 value: value of environment variable (e.g. 1)
637 """
638 environment = {}
639 if os.path.exists(fname):
640 with open(fname) as fd:
641 for line in fd.read().split('\0'):
642 try:
643 key, value = line.split('=', 1)
644 environment[key] = value
645 except ValueError:
646 # ignore lines we can't parse
647 pass
648 return environment
649
Simon Glass843312d2015-02-05 22:06:15 -0700650 def GetBuildOutcome(self, commit_upto, target, read_func_sizes,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000651 read_config, read_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000652 """Work out the outcome of a build.
653
654 Args:
655 commit_upto: Commit number to check (0..n-1)
656 target: Target board to check
657 read_func_sizes: True to read function size information
Simon Glass843312d2015-02-05 22:06:15 -0700658 read_config: True to read .config and autoconf.h files
Alex Kiernan48ae4122018-05-31 04:48:34 +0000659 read_environment: True to read uboot.env files
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000660
661 Returns:
662 Outcome object
663 """
664 done_file = self.GetDoneFile(commit_upto, target)
665 sizes_file = self.GetSizesFile(commit_upto, target)
666 sizes = {}
667 func_sizes = {}
Simon Glass843312d2015-02-05 22:06:15 -0700668 config = {}
Alex Kiernan48ae4122018-05-31 04:48:34 +0000669 environment = {}
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000670 if os.path.exists(done_file):
671 with open(done_file, 'r') as fd:
Simon Glass347ea0b2019-04-26 19:02:23 -0600672 try:
673 return_code = int(fd.readline())
674 except ValueError:
675 # The file may be empty due to running out of disk space.
676 # Try a rebuild
677 return_code = 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000678 err_lines = []
679 err_file = self.GetErrFile(commit_upto, target)
680 if os.path.exists(err_file):
681 with open(err_file, 'r') as fd:
682 err_lines = self.FilterErrors(fd.readlines())
683
684 # Decide whether the build was ok, failed or created warnings
685 if return_code:
686 rc = OUTCOME_ERROR
687 elif len(err_lines):
688 rc = OUTCOME_WARNING
689 else:
690 rc = OUTCOME_OK
691
692 # Convert size information to our simple format
693 if os.path.exists(sizes_file):
694 with open(sizes_file, 'r') as fd:
695 for line in fd.readlines():
696 values = line.split()
697 rodata = 0
698 if len(values) > 6:
699 rodata = int(values[6], 16)
700 size_dict = {
701 'all' : int(values[0]) + int(values[1]) +
702 int(values[2]),
703 'text' : int(values[0]) - rodata,
704 'data' : int(values[1]),
705 'bss' : int(values[2]),
706 'rodata' : rodata,
707 }
708 sizes[values[5]] = size_dict
709
710 if read_func_sizes:
711 pattern = self.GetFuncSizesFile(commit_upto, target, '*')
712 for fname in glob.glob(pattern):
713 with open(fname, 'r') as fd:
714 dict_name = os.path.basename(fname).replace('.sizes',
715 '')
716 func_sizes[dict_name] = self.ReadFuncSizes(fname, fd)
717
Simon Glass843312d2015-02-05 22:06:15 -0700718 if read_config:
719 output_dir = self.GetBuildDir(commit_upto, target)
Simon Glassb464f8e2016-11-13 14:25:53 -0700720 for name in self.config_filenames:
Simon Glass843312d2015-02-05 22:06:15 -0700721 fname = os.path.join(output_dir, name)
722 config[name] = self._ProcessConfig(fname)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000723
Alex Kiernan48ae4122018-05-31 04:48:34 +0000724 if read_environment:
725 output_dir = self.GetBuildDir(commit_upto, target)
726 fname = os.path.join(output_dir, 'uboot.env')
727 environment = self._ProcessEnvironment(fname)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000728
Alex Kiernan48ae4122018-05-31 04:48:34 +0000729 return Builder.Outcome(rc, err_lines, sizes, func_sizes, config,
730 environment)
731
732 return Builder.Outcome(OUTCOME_UNKNOWN, [], {}, {}, {}, {})
Simon Glass843312d2015-02-05 22:06:15 -0700733
734 def GetResultSummary(self, boards_selected, commit_upto, read_func_sizes,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000735 read_config, read_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000736 """Calculate a summary of the results of building a commit.
737
738 Args:
739 board_selected: Dict containing boards to summarise
740 commit_upto: Commit number to summarize (0..self.count-1)
741 read_func_sizes: True to read function size information
Simon Glass843312d2015-02-05 22:06:15 -0700742 read_config: True to read .config and autoconf.h files
Alex Kiernan48ae4122018-05-31 04:48:34 +0000743 read_environment: True to read uboot.env files
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000744
745 Returns:
746 Tuple:
747 Dict containing boards which passed building this commit.
748 keyed by board.target
Simon Glasse30965d2014-08-28 09:43:44 -0600749 List containing a summary of error lines
Simon Glassed966652014-08-28 09:43:43 -0600750 Dict keyed by error line, containing a list of the Board
751 objects with that error
Simon Glasse30965d2014-08-28 09:43:44 -0600752 List containing a summary of warning lines
753 Dict keyed by error line, containing a list of the Board
754 objects with that warning
Simon Glass8270e3c2015-08-25 21:52:14 -0600755 Dictionary keyed by board.target. Each value is a dictionary:
756 key: filename - e.g. '.config'
Simon Glass843312d2015-02-05 22:06:15 -0700757 value is itself a dictionary:
758 key: config name
759 value: config value
Alex Kiernan48ae4122018-05-31 04:48:34 +0000760 Dictionary keyed by board.target. Each value is a dictionary:
761 key: environment variable
762 value: value of environment variable
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000763 """
Simon Glasse30965d2014-08-28 09:43:44 -0600764 def AddLine(lines_summary, lines_boards, line, board):
765 line = line.rstrip()
766 if line in lines_boards:
767 lines_boards[line].append(board)
768 else:
769 lines_boards[line] = [board]
770 lines_summary.append(line)
771
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000772 board_dict = {}
773 err_lines_summary = []
Simon Glassed966652014-08-28 09:43:43 -0600774 err_lines_boards = {}
Simon Glasse30965d2014-08-28 09:43:44 -0600775 warn_lines_summary = []
776 warn_lines_boards = {}
Simon Glass843312d2015-02-05 22:06:15 -0700777 config = {}
Alex Kiernan48ae4122018-05-31 04:48:34 +0000778 environment = {}
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000779
Simon Glassc05aa032019-10-31 07:42:53 -0600780 for board in boards_selected.values():
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000781 outcome = self.GetBuildOutcome(commit_upto, board.target,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000782 read_func_sizes, read_config,
783 read_environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000784 board_dict[board.target] = outcome
Simon Glasse30965d2014-08-28 09:43:44 -0600785 last_func = None
786 last_was_warning = False
787 for line in outcome.err_lines:
788 if line:
789 if (self._re_function.match(line) or
790 self._re_files.match(line)):
791 last_func = line
Simon Glassed966652014-08-28 09:43:43 -0600792 else:
Simon Glass2d483332018-11-06 16:02:11 -0700793 is_warning = (self._re_warning.match(line) or
794 self._re_dtb_warning.match(line))
Simon Glasse30965d2014-08-28 09:43:44 -0600795 is_note = self._re_note.match(line)
796 if is_warning or (last_was_warning and is_note):
797 if last_func:
798 AddLine(warn_lines_summary, warn_lines_boards,
799 last_func, board)
800 AddLine(warn_lines_summary, warn_lines_boards,
801 line, board)
802 else:
803 if last_func:
804 AddLine(err_lines_summary, err_lines_boards,
805 last_func, board)
806 AddLine(err_lines_summary, err_lines_boards,
807 line, board)
808 last_was_warning = is_warning
809 last_func = None
Simon Glassb464f8e2016-11-13 14:25:53 -0700810 tconfig = Config(self.config_filenames, board.target)
811 for fname in self.config_filenames:
Simon Glass843312d2015-02-05 22:06:15 -0700812 if outcome.config:
Simon Glassc05aa032019-10-31 07:42:53 -0600813 for key, value in outcome.config[fname].items():
Simon Glass8270e3c2015-08-25 21:52:14 -0600814 tconfig.Add(fname, key, value)
815 config[board.target] = tconfig
Simon Glass843312d2015-02-05 22:06:15 -0700816
Alex Kiernan48ae4122018-05-31 04:48:34 +0000817 tenvironment = Environment(board.target)
818 if outcome.environment:
Simon Glassc05aa032019-10-31 07:42:53 -0600819 for key, value in outcome.environment.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +0000820 tenvironment.Add(key, value)
821 environment[board.target] = tenvironment
822
Simon Glasse30965d2014-08-28 09:43:44 -0600823 return (board_dict, err_lines_summary, err_lines_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000824 warn_lines_summary, warn_lines_boards, config, environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000825
826 def AddOutcome(self, board_dict, arch_list, changes, char, color):
827 """Add an output to our list of outcomes for each architecture
828
829 This simple function adds failing boards (changes) to the
830 relevant architecture string, so we can print the results out
831 sorted by architecture.
832
833 Args:
834 board_dict: Dict containing all boards
835 arch_list: Dict keyed by arch name. Value is a string containing
836 a list of board names which failed for that arch.
837 changes: List of boards to add to arch_list
838 color: terminal.Colour object
839 """
840 done_arch = {}
841 for target in changes:
842 if target in board_dict:
843 arch = board_dict[target].arch
844 else:
845 arch = 'unknown'
846 str = self.col.Color(color, ' ' + target)
847 if not arch in done_arch:
Simon Glass63c619e2015-02-05 22:06:11 -0700848 str = ' %s %s' % (self.col.Color(color, char), str)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000849 done_arch[arch] = True
850 if not arch in arch_list:
851 arch_list[arch] = str
852 else:
853 arch_list[arch] += str
854
855
856 def ColourNum(self, num):
857 color = self.col.RED if num > 0 else self.col.GREEN
858 if num == 0:
859 return '0'
860 return self.col.Color(color, str(num))
861
862 def ResetResultSummary(self, board_selected):
863 """Reset the results summary ready for use.
864
865 Set up the base board list to be all those selected, and set the
866 error lines to empty.
867
868 Following this, calls to PrintResultSummary() will use this
869 information to work out what has changed.
870
871 Args:
872 board_selected: Dict containing boards to summarise, keyed by
873 board.target
874 """
875 self._base_board_dict = {}
876 for board in board_selected:
Alex Kiernan48ae4122018-05-31 04:48:34 +0000877 self._base_board_dict[board] = Builder.Outcome(0, [], [], {}, {},
878 {})
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000879 self._base_err_lines = []
Simon Glasse30965d2014-08-28 09:43:44 -0600880 self._base_warn_lines = []
881 self._base_err_line_boards = {}
882 self._base_warn_line_boards = {}
Simon Glass8270e3c2015-08-25 21:52:14 -0600883 self._base_config = None
Alex Kiernan48ae4122018-05-31 04:48:34 +0000884 self._base_environment = None
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000885
886 def PrintFuncSizeDetail(self, fname, old, new):
887 grow, shrink, add, remove, up, down = 0, 0, 0, 0, 0, 0
888 delta, common = [], {}
889
890 for a in old:
891 if a in new:
892 common[a] = 1
893
894 for name in old:
895 if name not in common:
896 remove += 1
897 down += old[name]
898 delta.append([-old[name], name])
899
900 for name in new:
901 if name not in common:
902 add += 1
903 up += new[name]
904 delta.append([new[name], name])
905
906 for name in common:
907 diff = new.get(name, 0) - old.get(name, 0)
908 if diff > 0:
909 grow, up = grow + 1, up + diff
910 elif diff < 0:
911 shrink, down = shrink + 1, down - diff
912 delta.append([diff, name])
913
914 delta.sort()
915 delta.reverse()
916
917 args = [add, -remove, grow, -shrink, up, -down, up - down]
Tom Rinid5686a62017-05-22 13:48:52 -0400918 if max(args) == 0 and min(args) == 0:
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000919 return
920 args = [self.ColourNum(x) for x in args]
921 indent = ' ' * 15
Simon Glass4653a882014-09-05 19:00:07 -0600922 Print('%s%s: add: %s/%s, grow: %s/%s bytes: %s/%s (%s)' %
923 tuple([indent, self.col.Color(self.col.YELLOW, fname)] + args))
924 Print('%s %-38s %7s %7s %+7s' % (indent, 'function', 'old', 'new',
925 'delta'))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000926 for diff, name in delta:
927 if diff:
928 color = self.col.RED if diff > 0 else self.col.GREEN
929 msg = '%s %-38s %7s %7s %+7d' % (indent, name,
930 old.get(name, '-'), new.get(name,'-'), diff)
Simon Glass4653a882014-09-05 19:00:07 -0600931 Print(msg, colour=color)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000932
933
934 def PrintSizeDetail(self, target_list, show_bloat):
935 """Show details size information for each board
936
937 Args:
938 target_list: List of targets, each a dict containing:
939 'target': Target name
940 'total_diff': Total difference in bytes across all areas
941 <part_name>: Difference for that part
942 show_bloat: Show detail for each function
943 """
944 targets_by_diff = sorted(target_list, reverse=True,
945 key=lambda x: x['_total_diff'])
946 for result in targets_by_diff:
947 printed_target = False
948 for name in sorted(result):
949 diff = result[name]
950 if name.startswith('_'):
951 continue
952 if diff != 0:
953 color = self.col.RED if diff > 0 else self.col.GREEN
954 msg = ' %s %+d' % (name, diff)
955 if not printed_target:
Simon Glass4653a882014-09-05 19:00:07 -0600956 Print('%10s %-15s:' % ('', result['_target']),
957 newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000958 printed_target = True
Simon Glass4653a882014-09-05 19:00:07 -0600959 Print(msg, colour=color, newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000960 if printed_target:
Simon Glass4653a882014-09-05 19:00:07 -0600961 Print()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000962 if show_bloat:
963 target = result['_target']
964 outcome = result['_outcome']
965 base_outcome = self._base_board_dict[target]
966 for fname in outcome.func_sizes:
967 self.PrintFuncSizeDetail(fname,
968 base_outcome.func_sizes[fname],
969 outcome.func_sizes[fname])
970
971
972 def PrintSizeSummary(self, board_selected, board_dict, show_detail,
973 show_bloat):
974 """Print a summary of image sizes broken down by section.
975
976 The summary takes the form of one line per architecture. The
977 line contains deltas for each of the sections (+ means the section
Flavio Suligoi9de5c392020-01-29 09:56:05 +0100978 got bigger, - means smaller). The numbers are the average number
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000979 of bytes that a board in this section increased by.
980
981 For example:
982 powerpc: (622 boards) text -0.0
983 arm: (285 boards) text -0.0
984 nds32: (3 boards) text -8.0
985
986 Args:
987 board_selected: Dict containing boards to summarise, keyed by
988 board.target
989 board_dict: Dict containing boards for which we built this
990 commit, keyed by board.target. The value is an Outcome object.
Simon Glassf9c094b2020-03-18 09:42:43 -0600991 show_detail: Show size delta detail for each board
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000992 show_bloat: Show detail for each function
993 """
994 arch_list = {}
995 arch_count = {}
996
997 # Calculate changes in size for different image parts
998 # The previous sizes are in Board.sizes, for each board
999 for target in board_dict:
1000 if target not in board_selected:
1001 continue
1002 base_sizes = self._base_board_dict[target].sizes
1003 outcome = board_dict[target]
1004 sizes = outcome.sizes
1005
1006 # Loop through the list of images, creating a dict of size
1007 # changes for each image/part. We end up with something like
1008 # {'target' : 'snapper9g45, 'data' : 5, 'u-boot-spl:text' : -4}
1009 # which means that U-Boot data increased by 5 bytes and SPL
1010 # text decreased by 4.
1011 err = {'_target' : target}
1012 for image in sizes:
1013 if image in base_sizes:
1014 base_image = base_sizes[image]
1015 # Loop through the text, data, bss parts
1016 for part in sorted(sizes[image]):
1017 diff = sizes[image][part] - base_image[part]
1018 col = None
1019 if diff:
1020 if image == 'u-boot':
1021 name = part
1022 else:
1023 name = image + ':' + part
1024 err[name] = diff
1025 arch = board_selected[target].arch
1026 if not arch in arch_count:
1027 arch_count[arch] = 1
1028 else:
1029 arch_count[arch] += 1
1030 if not sizes:
1031 pass # Only add to our list when we have some stats
1032 elif not arch in arch_list:
1033 arch_list[arch] = [err]
1034 else:
1035 arch_list[arch].append(err)
1036
1037 # We now have a list of image size changes sorted by arch
1038 # Print out a summary of these
Simon Glassc05aa032019-10-31 07:42:53 -06001039 for arch, target_list in arch_list.items():
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001040 # Get total difference for each type
1041 totals = {}
1042 for result in target_list:
1043 total = 0
Simon Glassc05aa032019-10-31 07:42:53 -06001044 for name, diff in result.items():
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001045 if name.startswith('_'):
1046 continue
1047 total += diff
1048 if name in totals:
1049 totals[name] += diff
1050 else:
1051 totals[name] = diff
1052 result['_total_diff'] = total
1053 result['_outcome'] = board_dict[result['_target']]
1054
1055 count = len(target_list)
1056 printed_arch = False
1057 for name in sorted(totals):
1058 diff = totals[name]
1059 if diff:
1060 # Display the average difference in this name for this
1061 # architecture
1062 avg_diff = float(diff) / count
1063 color = self.col.RED if avg_diff > 0 else self.col.GREEN
1064 msg = ' %s %+1.1f' % (name, avg_diff)
1065 if not printed_arch:
Simon Glass4653a882014-09-05 19:00:07 -06001066 Print('%10s: (for %d/%d boards)' % (arch, count,
1067 arch_count[arch]), newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001068 printed_arch = True
Simon Glass4653a882014-09-05 19:00:07 -06001069 Print(msg, colour=color, newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001070
1071 if printed_arch:
Simon Glass4653a882014-09-05 19:00:07 -06001072 Print()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001073 if show_detail:
1074 self.PrintSizeDetail(target_list, show_bloat)
1075
1076
1077 def PrintResultSummary(self, board_selected, board_dict, err_lines,
Simon Glasse30965d2014-08-28 09:43:44 -06001078 err_line_boards, warn_lines, warn_line_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001079 config, environment, show_sizes, show_detail,
1080 show_bloat, show_config, show_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001081 """Compare results with the base results and display delta.
1082
1083 Only boards mentioned in board_selected will be considered. This
1084 function is intended to be called repeatedly with the results of
1085 each commit. It therefore shows a 'diff' between what it saw in
1086 the last call and what it sees now.
1087
1088 Args:
1089 board_selected: Dict containing boards to summarise, keyed by
1090 board.target
1091 board_dict: Dict containing boards for which we built this
1092 commit, keyed by board.target. The value is an Outcome object.
1093 err_lines: A list of errors for this commit, or [] if there is
1094 none, or we don't want to print errors
Simon Glassed966652014-08-28 09:43:43 -06001095 err_line_boards: Dict keyed by error line, containing a list of
1096 the Board objects with that error
Simon Glasse30965d2014-08-28 09:43:44 -06001097 warn_lines: A list of warnings for this commit, or [] if there is
1098 none, or we don't want to print errors
1099 warn_line_boards: Dict keyed by warning line, containing a list of
1100 the Board objects with that warning
Simon Glass843312d2015-02-05 22:06:15 -07001101 config: Dictionary keyed by filename - e.g. '.config'. Each
1102 value is itself a dictionary:
1103 key: config name
1104 value: config value
Alex Kiernan48ae4122018-05-31 04:48:34 +00001105 environment: Dictionary keyed by environment variable, Each
1106 value is the value of environment variable.
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001107 show_sizes: Show image size deltas
Simon Glassf9c094b2020-03-18 09:42:43 -06001108 show_detail: Show size delta detail for each board if show_sizes
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001109 show_bloat: Show detail for each function
Simon Glass843312d2015-02-05 22:06:15 -07001110 show_config: Show config changes
Alex Kiernan48ae4122018-05-31 04:48:34 +00001111 show_environment: Show environment changes
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001112 """
Simon Glasse30965d2014-08-28 09:43:44 -06001113 def _BoardList(line, line_boards):
Simon Glassed966652014-08-28 09:43:43 -06001114 """Helper function to get a line of boards containing a line
1115
1116 Args:
1117 line: Error line to search for
Simon Glass35d696d2020-04-09 15:08:36 -06001118 line_boards: boards to search, each a Board
Simon Glassed966652014-08-28 09:43:43 -06001119 Return:
Simon Glass35d696d2020-04-09 15:08:36 -06001120 List of boards with that error line, or [] if the user has not
1121 requested such a list
Simon Glassed966652014-08-28 09:43:43 -06001122 """
Simon Glass35d696d2020-04-09 15:08:36 -06001123 boards = []
1124 board_set = set()
Simon Glassed966652014-08-28 09:43:43 -06001125 if self._list_error_boards:
Simon Glasse30965d2014-08-28 09:43:44 -06001126 for board in line_boards[line]:
Simon Glass35d696d2020-04-09 15:08:36 -06001127 if not board in board_set:
1128 boards.append(board)
1129 board_set.add(board)
1130 return boards
Simon Glassed966652014-08-28 09:43:43 -06001131
Simon Glasse30965d2014-08-28 09:43:44 -06001132 def _CalcErrorDelta(base_lines, base_line_boards, lines, line_boards,
1133 char):
Simon Glass35d696d2020-04-09 15:08:36 -06001134 """Calculate the required output based on changes in errors
1135
1136 Args:
1137 base_lines: List of errors/warnings for previous commit
1138 base_line_boards: Dict keyed by error line, containing a list
1139 of the Board objects with that error in the previous commit
1140 lines: List of errors/warning for this commit, each a str
1141 line_boards: Dict keyed by error line, containing a list
1142 of the Board objects with that error in this commit
1143 char: Character representing error ('') or warning ('w'). The
1144 broken ('+') or fixed ('-') characters are added in this
1145 function
1146
1147 Returns:
1148 Tuple
1149 List of ErrLine objects for 'better' lines
1150 List of ErrLine objects for 'worse' lines
1151 """
Simon Glasse30965d2014-08-28 09:43:44 -06001152 better_lines = []
1153 worse_lines = []
1154 for line in lines:
1155 if line not in base_lines:
Simon Glass35d696d2020-04-09 15:08:36 -06001156 errline = ErrLine(char + '+', _BoardList(line, line_boards),
1157 line)
1158 worse_lines.append(errline)
Simon Glasse30965d2014-08-28 09:43:44 -06001159 for line in base_lines:
1160 if line not in lines:
Simon Glass35d696d2020-04-09 15:08:36 -06001161 errline = ErrLine(char + '-',
1162 _BoardList(line, base_line_boards), line)
1163 better_lines.append(errline)
Simon Glasse30965d2014-08-28 09:43:44 -06001164 return better_lines, worse_lines
1165
Simon Glass843312d2015-02-05 22:06:15 -07001166 def _CalcConfig(delta, name, config):
1167 """Calculate configuration changes
1168
1169 Args:
1170 delta: Type of the delta, e.g. '+'
1171 name: name of the file which changed (e.g. .config)
1172 config: configuration change dictionary
1173 key: config name
1174 value: config value
1175 Returns:
1176 String containing the configuration changes which can be
1177 printed
1178 """
1179 out = ''
1180 for key in sorted(config.keys()):
1181 out += '%s=%s ' % (key, config[key])
Simon Glass8270e3c2015-08-25 21:52:14 -06001182 return '%s %s: %s' % (delta, name, out)
Simon Glass843312d2015-02-05 22:06:15 -07001183
Simon Glass8270e3c2015-08-25 21:52:14 -06001184 def _AddConfig(lines, name, config_plus, config_minus, config_change):
1185 """Add changes in configuration to a list
Simon Glass843312d2015-02-05 22:06:15 -07001186
1187 Args:
Simon Glass8270e3c2015-08-25 21:52:14 -06001188 lines: list to add to
1189 name: config file name
Simon Glass843312d2015-02-05 22:06:15 -07001190 config_plus: configurations added, dictionary
1191 key: config name
1192 value: config value
1193 config_minus: configurations removed, dictionary
1194 key: config name
1195 value: config value
1196 config_change: configurations changed, dictionary
1197 key: config name
1198 value: config value
1199 """
1200 if config_plus:
Simon Glass8270e3c2015-08-25 21:52:14 -06001201 lines.append(_CalcConfig('+', name, config_plus))
Simon Glass843312d2015-02-05 22:06:15 -07001202 if config_minus:
Simon Glass8270e3c2015-08-25 21:52:14 -06001203 lines.append(_CalcConfig('-', name, config_minus))
Simon Glass843312d2015-02-05 22:06:15 -07001204 if config_change:
Simon Glass8270e3c2015-08-25 21:52:14 -06001205 lines.append(_CalcConfig('c', name, config_change))
1206
1207 def _OutputConfigInfo(lines):
1208 for line in lines:
1209 if not line:
1210 continue
1211 if line[0] == '+':
1212 col = self.col.GREEN
1213 elif line[0] == '-':
1214 col = self.col.RED
1215 elif line[0] == 'c':
1216 col = self.col.YELLOW
1217 Print(' ' + line, newline=True, colour=col)
1218
Simon Glassb206d872020-04-09 15:08:28 -06001219 def _OutputErrLines(err_lines, colour):
1220 """Output the line of error/warning lines, if not empty
1221
1222 Also increments self._error_lines if err_lines not empty
1223
1224 Args:
Simon Glass35d696d2020-04-09 15:08:36 -06001225 err_lines: List of ErrLine objects, each an error or warning
1226 line, possibly including a list of boards with that
1227 error/warning
Simon Glassb206d872020-04-09 15:08:28 -06001228 colour: Colour to use for output
1229 """
1230 if err_lines:
Simon Glass8c9a2672020-04-09 15:08:37 -06001231 out_list = []
Simon Glass35d696d2020-04-09 15:08:36 -06001232 for line in err_lines:
1233 boards = ''
1234 names = [board.target for board in line.boards]
Simon Glass9ef0ceb2020-04-09 15:08:38 -06001235 board_str = ' '.join(names) if names else ''
Simon Glass8c9a2672020-04-09 15:08:37 -06001236 if board_str:
1237 out = self.col.Color(colour, line.char + '(')
1238 out += self.col.Color(self.col.MAGENTA, board_str,
1239 bright=False)
1240 out += self.col.Color(colour, ') %s' % line.errline)
1241 else:
1242 out = self.col.Color(colour, line.char + line.errline)
1243 out_list.append(out)
1244 Print('\n'.join(out_list))
Simon Glassb206d872020-04-09 15:08:28 -06001245 self._error_lines += 1
1246
Simon Glass843312d2015-02-05 22:06:15 -07001247
Simon Glass4cf2b222018-11-06 16:02:12 -07001248 ok_boards = [] # List of boards fixed since last commit
Simon Glass6af71012018-11-06 16:02:13 -07001249 warn_boards = [] # List of boards with warnings since last commit
Simon Glass4cf2b222018-11-06 16:02:12 -07001250 err_boards = [] # List of new broken boards since last commit
1251 new_boards = [] # List of boards that didn't exist last time
1252 unknown_boards = [] # List of boards that were not built
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001253
1254 for target in board_dict:
1255 if target not in board_selected:
1256 continue
1257
1258 # If the board was built last time, add its outcome to a list
1259 if target in self._base_board_dict:
1260 base_outcome = self._base_board_dict[target].rc
1261 outcome = board_dict[target]
1262 if outcome.rc == OUTCOME_UNKNOWN:
Simon Glass4cf2b222018-11-06 16:02:12 -07001263 unknown_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001264 elif outcome.rc < base_outcome:
Simon Glass6af71012018-11-06 16:02:13 -07001265 if outcome.rc == OUTCOME_WARNING:
1266 warn_boards.append(target)
1267 else:
1268 ok_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001269 elif outcome.rc > base_outcome:
Simon Glass6af71012018-11-06 16:02:13 -07001270 if outcome.rc == OUTCOME_WARNING:
1271 warn_boards.append(target)
1272 else:
1273 err_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001274 else:
Simon Glass4cf2b222018-11-06 16:02:12 -07001275 new_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001276
Simon Glassb206d872020-04-09 15:08:28 -06001277 # Get a list of errors and warnings that have appeared, and disappeared
Simon Glasse30965d2014-08-28 09:43:44 -06001278 better_err, worse_err = _CalcErrorDelta(self._base_err_lines,
1279 self._base_err_line_boards, err_lines, err_line_boards, '')
1280 better_warn, worse_warn = _CalcErrorDelta(self._base_warn_lines,
1281 self._base_warn_line_boards, warn_lines, warn_line_boards, 'w')
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001282
1283 # Display results by arch
Simon Glass6af71012018-11-06 16:02:13 -07001284 if any((ok_boards, warn_boards, err_boards, unknown_boards, new_boards,
1285 worse_err, better_err, worse_warn, better_warn)):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001286 arch_list = {}
Simon Glass4cf2b222018-11-06 16:02:12 -07001287 self.AddOutcome(board_selected, arch_list, ok_boards, '',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001288 self.col.GREEN)
Simon Glass6af71012018-11-06 16:02:13 -07001289 self.AddOutcome(board_selected, arch_list, warn_boards, 'w+',
1290 self.col.YELLOW)
Simon Glass4cf2b222018-11-06 16:02:12 -07001291 self.AddOutcome(board_selected, arch_list, err_boards, '+',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001292 self.col.RED)
Simon Glass4cf2b222018-11-06 16:02:12 -07001293 self.AddOutcome(board_selected, arch_list, new_boards, '*', self.col.BLUE)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001294 if self._show_unknown:
Simon Glass4cf2b222018-11-06 16:02:12 -07001295 self.AddOutcome(board_selected, arch_list, unknown_boards, '?',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001296 self.col.MAGENTA)
Simon Glassc05aa032019-10-31 07:42:53 -06001297 for arch, target_list in arch_list.items():
Simon Glass4653a882014-09-05 19:00:07 -06001298 Print('%10s: %s' % (arch, target_list))
Simon Glass28370c12014-08-09 15:33:06 -06001299 self._error_lines += 1
Simon Glassb206d872020-04-09 15:08:28 -06001300 _OutputErrLines(better_err, colour=self.col.GREEN)
1301 _OutputErrLines(worse_err, colour=self.col.RED)
1302 _OutputErrLines(better_warn, colour=self.col.CYAN)
Simon Glass5627bd92020-04-09 15:08:35 -06001303 _OutputErrLines(worse_warn, colour=self.col.YELLOW)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001304
1305 if show_sizes:
1306 self.PrintSizeSummary(board_selected, board_dict, show_detail,
1307 show_bloat)
1308
Alex Kiernan48ae4122018-05-31 04:48:34 +00001309 if show_environment and self._base_environment:
1310 lines = []
1311
1312 for target in board_dict:
1313 if target not in board_selected:
1314 continue
1315
1316 tbase = self._base_environment[target]
1317 tenvironment = environment[target]
1318 environment_plus = {}
1319 environment_minus = {}
1320 environment_change = {}
1321 base = tbase.environment
Simon Glassc05aa032019-10-31 07:42:53 -06001322 for key, value in tenvironment.environment.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001323 if key not in base:
1324 environment_plus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001325 for key, value in base.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001326 if key not in tenvironment.environment:
1327 environment_minus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001328 for key, value in base.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001329 new_value = tenvironment.environment.get(key)
1330 if new_value and value != new_value:
1331 desc = '%s -> %s' % (value, new_value)
1332 environment_change[key] = desc
1333
1334 _AddConfig(lines, target, environment_plus, environment_minus,
1335 environment_change)
1336
1337 _OutputConfigInfo(lines)
1338
Simon Glass8270e3c2015-08-25 21:52:14 -06001339 if show_config and self._base_config:
1340 summary = {}
1341 arch_config_plus = {}
1342 arch_config_minus = {}
1343 arch_config_change = {}
1344 arch_list = []
1345
1346 for target in board_dict:
1347 if target not in board_selected:
Simon Glass843312d2015-02-05 22:06:15 -07001348 continue
Simon Glass8270e3c2015-08-25 21:52:14 -06001349 arch = board_selected[target].arch
1350 if arch not in arch_list:
1351 arch_list.append(arch)
1352
1353 for arch in arch_list:
1354 arch_config_plus[arch] = {}
1355 arch_config_minus[arch] = {}
1356 arch_config_change[arch] = {}
Simon Glassb464f8e2016-11-13 14:25:53 -07001357 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001358 arch_config_plus[arch][name] = {}
1359 arch_config_minus[arch][name] = {}
1360 arch_config_change[arch][name] = {}
1361
1362 for target in board_dict:
1363 if target not in board_selected:
1364 continue
1365
1366 arch = board_selected[target].arch
1367
1368 all_config_plus = {}
1369 all_config_minus = {}
1370 all_config_change = {}
1371 tbase = self._base_config[target]
1372 tconfig = config[target]
1373 lines = []
Simon Glassb464f8e2016-11-13 14:25:53 -07001374 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001375 if not tconfig.config[name]:
1376 continue
1377 config_plus = {}
1378 config_minus = {}
1379 config_change = {}
1380 base = tbase.config[name]
Simon Glassc05aa032019-10-31 07:42:53 -06001381 for key, value in tconfig.config[name].items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001382 if key not in base:
1383 config_plus[key] = value
1384 all_config_plus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001385 for key, value in base.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001386 if key not in tconfig.config[name]:
1387 config_minus[key] = value
1388 all_config_minus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001389 for key, value in base.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001390 new_value = tconfig.config.get(key)
1391 if new_value and value != new_value:
1392 desc = '%s -> %s' % (value, new_value)
1393 config_change[key] = desc
1394 all_config_change[key] = desc
1395
1396 arch_config_plus[arch][name].update(config_plus)
1397 arch_config_minus[arch][name].update(config_minus)
1398 arch_config_change[arch][name].update(config_change)
1399
1400 _AddConfig(lines, name, config_plus, config_minus,
1401 config_change)
1402 _AddConfig(lines, 'all', all_config_plus, all_config_minus,
1403 all_config_change)
1404 summary[target] = '\n'.join(lines)
1405
1406 lines_by_target = {}
Simon Glassc05aa032019-10-31 07:42:53 -06001407 for target, lines in summary.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001408 if lines in lines_by_target:
1409 lines_by_target[lines].append(target)
1410 else:
1411 lines_by_target[lines] = [target]
1412
1413 for arch in arch_list:
1414 lines = []
1415 all_plus = {}
1416 all_minus = {}
1417 all_change = {}
Simon Glassb464f8e2016-11-13 14:25:53 -07001418 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001419 all_plus.update(arch_config_plus[arch][name])
1420 all_minus.update(arch_config_minus[arch][name])
1421 all_change.update(arch_config_change[arch][name])
1422 _AddConfig(lines, name, arch_config_plus[arch][name],
1423 arch_config_minus[arch][name],
1424 arch_config_change[arch][name])
1425 _AddConfig(lines, 'all', all_plus, all_minus, all_change)
1426 #arch_summary[target] = '\n'.join(lines)
1427 if lines:
1428 Print('%s:' % arch)
1429 _OutputConfigInfo(lines)
1430
Simon Glassc05aa032019-10-31 07:42:53 -06001431 for lines, targets in lines_by_target.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001432 if not lines:
1433 continue
1434 Print('%s :' % ' '.join(sorted(targets)))
1435 _OutputConfigInfo(lines.split('\n'))
1436
Simon Glass843312d2015-02-05 22:06:15 -07001437
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001438 # Save our updated information for the next call to this function
1439 self._base_board_dict = board_dict
1440 self._base_err_lines = err_lines
Simon Glasse30965d2014-08-28 09:43:44 -06001441 self._base_warn_lines = warn_lines
1442 self._base_err_line_boards = err_line_boards
1443 self._base_warn_line_boards = warn_line_boards
Simon Glass843312d2015-02-05 22:06:15 -07001444 self._base_config = config
Alex Kiernan48ae4122018-05-31 04:48:34 +00001445 self._base_environment = environment
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001446
1447 # Get a list of boards that did not get built, if needed
1448 not_built = []
1449 for board in board_selected:
1450 if not board in board_dict:
1451 not_built.append(board)
1452 if not_built:
Simon Glass4653a882014-09-05 19:00:07 -06001453 Print("Boards not built (%d): %s" % (len(not_built),
1454 ', '.join(not_built)))
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001455
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001456 def ProduceResultSummary(self, commit_upto, commits, board_selected):
Simon Glasse30965d2014-08-28 09:43:44 -06001457 (board_dict, err_lines, err_line_boards, warn_lines,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001458 warn_line_boards, config, environment) = self.GetResultSummary(
Simon Glassed966652014-08-28 09:43:43 -06001459 board_selected, commit_upto,
Simon Glass843312d2015-02-05 22:06:15 -07001460 read_func_sizes=self._show_bloat,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001461 read_config=self._show_config,
1462 read_environment=self._show_environment)
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001463 if commits:
1464 msg = '%02d: %s' % (commit_upto + 1,
1465 commits[commit_upto].subject)
Simon Glass4653a882014-09-05 19:00:07 -06001466 Print(msg, colour=self.col.BLUE)
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001467 self.PrintResultSummary(board_selected, board_dict,
Simon Glassed966652014-08-28 09:43:43 -06001468 err_lines if self._show_errors else [], err_line_boards,
Simon Glasse30965d2014-08-28 09:43:44 -06001469 warn_lines if self._show_errors else [], warn_line_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001470 config, environment, self._show_sizes, self._show_detail,
1471 self._show_bloat, self._show_config, self._show_environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001472
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001473 def ShowSummary(self, commits, board_selected):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001474 """Show a build summary for U-Boot for a given board list.
1475
1476 Reset the result summary, then repeatedly call GetResultSummary on
1477 each commit's results, then display the differences we see.
1478
1479 Args:
1480 commit: Commit objects to summarise
1481 board_selected: Dict containing boards to summarise
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001482 """
Simon Glassfea58582014-08-09 15:32:59 -06001483 self.commit_count = len(commits) if commits else 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001484 self.commits = commits
1485 self.ResetResultSummary(board_selected)
Simon Glass28370c12014-08-09 15:33:06 -06001486 self._error_lines = 0
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001487
1488 for commit_upto in range(0, self.commit_count, self._step):
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001489 self.ProduceResultSummary(commit_upto, commits, board_selected)
Simon Glass28370c12014-08-09 15:33:06 -06001490 if not self._error_lines:
Simon Glass4653a882014-09-05 19:00:07 -06001491 Print('(no errors to report)', colour=self.col.GREEN)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001492
1493
1494 def SetupBuild(self, board_selected, commits):
1495 """Set up ready to start a build.
1496
1497 Args:
1498 board_selected: Selected boards to build
1499 commits: Selected commits to build
1500 """
1501 # First work out how many commits we will build
Simon Glassc05aa032019-10-31 07:42:53 -06001502 count = (self.commit_count + self._step - 1) // self._step
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001503 self.count = len(board_selected) * count
1504 self.upto = self.warned = self.fail = 0
1505 self._timestamps = collections.deque()
1506
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001507 def GetThreadDir(self, thread_num):
1508 """Get the directory path to the working dir for a thread.
1509
1510 Args:
1511 thread_num: Number of thread to check.
1512 """
Simon Glassd829f122020-03-18 09:42:42 -06001513 if self.work_in_output:
1514 return self._working_dir
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001515 return os.path.join(self._working_dir, '%02d' % thread_num)
1516
Simon Glassfea58582014-08-09 15:32:59 -06001517 def _PrepareThread(self, thread_num, setup_git):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001518 """Prepare the working directory for a thread.
1519
1520 This clones or fetches the repo into the thread's work directory.
1521
1522 Args:
1523 thread_num: Thread number (0, 1, ...)
Simon Glassfea58582014-08-09 15:32:59 -06001524 setup_git: True to set up a git repo clone
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001525 """
1526 thread_dir = self.GetThreadDir(thread_num)
Simon Glass190064b2014-08-09 15:33:00 -06001527 builderthread.Mkdir(thread_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001528 git_dir = os.path.join(thread_dir, '.git')
1529
1530 # Clone the repo if it doesn't already exist
1531 # TODO(sjg@chromium): Perhaps some git hackery to symlink instead, so
1532 # we have a private index but uses the origin repo's contents?
Simon Glassfea58582014-08-09 15:32:59 -06001533 if setup_git and self.git_dir:
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001534 src_dir = os.path.abspath(self.git_dir)
1535 if os.path.exists(git_dir):
Simon Glass212c0b82020-04-09 15:08:43 -06001536 Print('\rFetching repo for thread %d' % thread_num,
1537 newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001538 gitutil.Fetch(git_dir, thread_dir)
Simon Glass212c0b82020-04-09 15:08:43 -06001539 terminal.PrintClear()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001540 else:
Simon Glass21f0eb32016-09-18 16:48:31 -06001541 Print('\rCloning repo for thread %d' % thread_num,
1542 newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001543 gitutil.Clone(src_dir, thread_dir)
Simon Glass102969bb2020-04-09 15:08:42 -06001544 terminal.PrintClear()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001545
Simon Glassfea58582014-08-09 15:32:59 -06001546 def _PrepareWorkingSpace(self, max_threads, setup_git):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001547 """Prepare the working directory for use.
1548
1549 Set up the git repo for each thread.
1550
1551 Args:
1552 max_threads: Maximum number of threads we expect to need.
Simon Glassfea58582014-08-09 15:32:59 -06001553 setup_git: True to set up a git repo clone
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001554 """
Simon Glass190064b2014-08-09 15:33:00 -06001555 builderthread.Mkdir(self._working_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001556 for thread in range(max_threads):
Simon Glassfea58582014-08-09 15:32:59 -06001557 self._PrepareThread(thread, setup_git)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001558
Simon Glass925f6ad2020-03-18 09:42:45 -06001559 def _GetOutputSpaceRemovals(self):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001560 """Get the output directories ready to receive files.
1561
Simon Glass925f6ad2020-03-18 09:42:45 -06001562 Figure out what needs to be deleted in the output directory before it
1563 can be used. We only delete old buildman directories which have the
1564 expected name pattern. See _GetOutputDir().
1565
1566 Returns:
1567 List of full paths of directories to remove
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001568 """
Simon Glass1a915672014-12-01 17:33:53 -07001569 if not self.commits:
1570 return
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001571 dir_list = []
1572 for commit_upto in range(self.commit_count):
1573 dir_list.append(self._GetOutputDir(commit_upto))
1574
Simon Glassb222abe2016-09-18 16:48:32 -06001575 to_remove = []
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001576 for dirname in glob.glob(os.path.join(self.base_dir, '*')):
1577 if dirname not in dir_list:
Simon Glass925f6ad2020-03-18 09:42:45 -06001578 leaf = dirname[len(self.base_dir) + 1:]
1579 m = re.match('[0-9]+_of_[0-9]+_g[0-9a-f]+_.*', leaf)
1580 if m:
1581 to_remove.append(dirname)
1582 return to_remove
1583
1584 def _PrepareOutputSpace(self):
1585 """Get the output directories ready to receive files.
1586
1587 We delete any output directories which look like ones we need to
1588 create. Having left over directories is confusing when the user wants
1589 to check the output manually.
1590 """
1591 to_remove = self._GetOutputSpaceRemovals()
Simon Glassb222abe2016-09-18 16:48:32 -06001592 if to_remove:
Simon Glassb2d89bc2020-03-18 09:42:46 -06001593 Print('Removing %d old build directories...' % len(to_remove),
Simon Glassb222abe2016-09-18 16:48:32 -06001594 newline=False)
1595 for dirname in to_remove:
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001596 shutil.rmtree(dirname)
Simon Glass212c0b82020-04-09 15:08:43 -06001597 terminal.PrintClear()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001598
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001599 def BuildBoards(self, commits, board_selected, keep_outputs, verbose):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001600 """Build all commits for a list of boards
1601
1602 Args:
1603 commits: List of commits to be build, each a Commit object
1604 boards_selected: Dict of selected boards, key is target name,
1605 value is Board object
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001606 keep_outputs: True to save build output files
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001607 verbose: Display build results as they are completed
Simon Glass2c3deb92014-08-28 09:43:39 -06001608 Returns:
1609 Tuple containing:
1610 - number of boards that failed to build
1611 - number of boards that issued warnings
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001612 """
Simon Glassfea58582014-08-09 15:32:59 -06001613 self.commit_count = len(commits) if commits else 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001614 self.commits = commits
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001615 self._verbose = verbose
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001616
1617 self.ResetResultSummary(board_selected)
Thierry Redingf3d015c2014-08-19 10:22:39 +02001618 builderthread.Mkdir(self.base_dir, parents = True)
Simon Glassfea58582014-08-09 15:32:59 -06001619 self._PrepareWorkingSpace(min(self.num_threads, len(board_selected)),
1620 commits is not None)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001621 self._PrepareOutputSpace()
Simon Glass745b3952016-09-18 16:48:33 -06001622 Print('\rStarting build...', newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001623 self.SetupBuild(board_selected, commits)
1624 self.ProcessResult(None)
1625
1626 # Create jobs to build all commits for each board
Simon Glassc05aa032019-10-31 07:42:53 -06001627 for brd in board_selected.values():
Simon Glass190064b2014-08-09 15:33:00 -06001628 job = builderthread.BuilderJob()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001629 job.board = brd
1630 job.commits = commits
1631 job.keep_outputs = keep_outputs
Simon Glassd829f122020-03-18 09:42:42 -06001632 job.work_in_output = self.work_in_output
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001633 job.step = self._step
1634 self.queue.put(job)
1635
Simon Glassd436e382016-09-18 16:48:35 -06001636 term = threading.Thread(target=self.queue.join)
1637 term.setDaemon(True)
1638 term.start()
1639 while term.isAlive():
1640 term.join(100)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001641
1642 # Wait until we have processed all output
1643 self.out_queue.join()
Simon Glass4653a882014-09-05 19:00:07 -06001644 Print()
Simon Glass2c3deb92014-08-28 09:43:39 -06001645 return (self.fail, self.warned)