blob: 45569aa1f8165786ef6024267204bd8acc60932b [file] [log] [blame]
Tom Rini83d290c2018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glassfc3fe1c2013-04-03 11:07:16 +00002# Copyright (c) 2013 The Chromium OS Authors.
3#
4# Bloat-o-meter code used here Copyright 2004 Matt Mackall <mpm@selenic.com>
5#
Simon Glassfc3fe1c2013-04-03 11:07:16 +00006
7import collections
Simon Glassfc3fe1c2013-04-03 11:07:16 +00008from datetime import datetime, timedelta
9import glob
10import os
11import re
Simon Glassc05aa032019-10-31 07:42:53 -060012import queue
Simon Glassfc3fe1c2013-04-03 11:07:16 +000013import shutil
Simon Glass2f256642016-09-18 16:48:37 -060014import signal
Simon Glassfc3fe1c2013-04-03 11:07:16 +000015import string
16import sys
Simon Glassd436e382016-09-18 16:48:35 -060017import threading
Simon Glassfc3fe1c2013-04-03 11:07:16 +000018import time
19
Simon Glass190064b2014-08-09 15:33:00 -060020import builderthread
Simon Glassfc3fe1c2013-04-03 11:07:16 +000021import command
22import gitutil
23import terminal
Simon Glass4653a882014-09-05 19:00:07 -060024from terminal import Print
Simon Glassfc3fe1c2013-04-03 11:07:16 +000025import toolchain
26
Simon Glassfc3fe1c2013-04-03 11:07:16 +000027"""
28Theory of Operation
29
30Please see README for user documentation, and you should be familiar with
31that before trying to make sense of this.
32
33Buildman works by keeping the machine as busy as possible, building different
34commits for different boards on multiple CPUs at once.
35
36The source repo (self.git_dir) contains all the commits to be built. Each
37thread works on a single board at a time. It checks out the first commit,
38configures it for that board, then builds it. Then it checks out the next
39commit and builds it (typically without re-configuring). When it runs out
40of commits, it gets another job from the builder and starts again with that
41board.
42
43Clearly the builder threads could work either way - they could check out a
44commit and then built it for all boards. Using separate directories for each
45commit/board pair they could leave their build product around afterwards
46also.
47
48The intent behind building a single board for multiple commits, is to make
49use of incremental builds. Since each commit is built incrementally from
50the previous one, builds are faster. Reconfiguring for a different board
51removes all intermediate object files.
52
53Many threads can be working at once, but each has its own working directory.
54When a thread finishes a build, it puts the output files into a result
55directory.
56
57The base directory used by buildman is normally '../<branch>', i.e.
58a directory higher than the source repository and named after the branch
59being built.
60
61Within the base directory, we have one subdirectory for each commit. Within
62that is one subdirectory for each board. Within that is the build output for
63that commit/board combination.
64
65Buildman also create working directories for each thread, in a .bm-work/
66subdirectory in the base dir.
67
68As an example, say we are building branch 'us-net' for boards 'sandbox' and
69'seaboard', and say that us-net has two commits. We will have directories
70like this:
71
72us-net/ base directory
73 01_of_02_g4ed4ebc_net--Add-tftp-speed-/
74 sandbox/
75 u-boot.bin
76 seaboard/
77 u-boot.bin
78 02_of_02_g4ed4ebc_net--Check-tftp-comp/
79 sandbox/
80 u-boot.bin
81 seaboard/
82 u-boot.bin
83 .bm-work/
84 00/ working directory for thread 0 (contains source checkout)
85 build/ build output
86 01/ working directory for thread 1
87 build/ build output
88 ...
89u-boot/ source directory
90 .git/ repository
91"""
92
Simon Glass35d696d2020-04-09 15:08:36 -060093"""Holds information about a particular error line we are outputing
94
95 char: Character representation: '+': error, '-': fixed error, 'w+': warning,
96 'w-' = fixed warning
97 boards: List of Board objects which have line in the error/warning output
98 errline: The text of the error line
99"""
100ErrLine = collections.namedtuple('ErrLine', 'char,boards,errline')
101
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000102# Possible build outcomes
Simon Glassc05aa032019-10-31 07:42:53 -0600103OUTCOME_OK, OUTCOME_WARNING, OUTCOME_ERROR, OUTCOME_UNKNOWN = list(range(4))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000104
Simon Glass9a6d2e22017-04-12 18:23:26 -0600105# Translate a commit subject into a valid filename (and handle unicode)
Simon Glassc05aa032019-10-31 07:42:53 -0600106trans_valid_chars = str.maketrans('/: ', '---')
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000107
Simon Glassb464f8e2016-11-13 14:25:53 -0700108BASE_CONFIG_FILENAMES = [
109 'u-boot.cfg', 'u-boot-spl.cfg', 'u-boot-tpl.cfg'
110]
111
112EXTRA_CONFIG_FILENAMES = [
Simon Glass843312d2015-02-05 22:06:15 -0700113 '.config', '.config-spl', '.config-tpl',
114 'autoconf.mk', 'autoconf-spl.mk', 'autoconf-tpl.mk',
115 'autoconf.h', 'autoconf-spl.h','autoconf-tpl.h',
Simon Glass843312d2015-02-05 22:06:15 -0700116]
117
Simon Glass8270e3c2015-08-25 21:52:14 -0600118class Config:
119 """Holds information about configuration settings for a board."""
Simon Glassb464f8e2016-11-13 14:25:53 -0700120 def __init__(self, config_filename, target):
Simon Glass8270e3c2015-08-25 21:52:14 -0600121 self.target = target
122 self.config = {}
Simon Glassb464f8e2016-11-13 14:25:53 -0700123 for fname in config_filename:
Simon Glass8270e3c2015-08-25 21:52:14 -0600124 self.config[fname] = {}
125
126 def Add(self, fname, key, value):
127 self.config[fname][key] = value
128
129 def __hash__(self):
130 val = 0
131 for fname in self.config:
Simon Glassc05aa032019-10-31 07:42:53 -0600132 for key, value in self.config[fname].items():
133 print(key, value)
Simon Glass8270e3c2015-08-25 21:52:14 -0600134 val = val ^ hash(key) & hash(value)
135 return val
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000136
Alex Kiernan48ae4122018-05-31 04:48:34 +0000137class Environment:
138 """Holds information about environment variables for a board."""
139 def __init__(self, target):
140 self.target = target
141 self.environment = {}
142
143 def Add(self, key, value):
144 self.environment[key] = value
145
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000146class Builder:
147 """Class for building U-Boot for a particular commit.
148
149 Public members: (many should ->private)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000150 already_done: Number of builds already completed
151 base_dir: Base directory to use for builder
152 checkout: True to check out source, False to skip that step.
153 This is used for testing.
154 col: terminal.Color() object
155 count: Number of commits to build
156 do_make: Method to call to invoke Make
157 fail: Number of builds that failed due to error
158 force_build: Force building even if a build already exists
159 force_config_on_failure: If a commit fails for a board, disable
160 incremental building for the next commit we build for that
161 board, so that we will see all warnings/errors again.
Simon Glass4266dc22014-07-13 12:22:31 -0600162 force_build_failures: If a previously-built build (i.e. built on
163 a previous run of buildman) is marked as failed, rebuild it.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000164 git_dir: Git directory containing source repository
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000165 num_jobs: Number of jobs to run at once (passed to make as -j)
166 num_threads: Number of builder threads to run
167 out_queue: Queue of results to process
168 re_make_err: Compiled regular expression for ignore_lines
169 queue: Queue of jobs to run
170 threads: List of active threads
171 toolchains: Toolchains object to use for building
172 upto: Current commit number we are building (0.count-1)
173 warned: Number of builds that produced at least one warning
Simon Glass97e91522014-07-14 17:51:02 -0600174 force_reconfig: Reconfigure U-Boot on each comiit. This disables
175 incremental building, where buildman reconfigures on the first
176 commit for a baord, and then just does an incremental build for
177 the following commits. In fact buildman will reconfigure and
178 retry for any failing commits, so generally the only effect of
179 this option is to slow things down.
Simon Glass189a4962014-07-14 17:51:03 -0600180 in_tree: Build U-Boot in-tree instead of specifying an output
181 directory separate from the source code. This option is really
182 only useful for testing in-tree builds.
Simon Glassd829f122020-03-18 09:42:42 -0600183 work_in_output: Use the output directory as the work directory and
184 don't write to a separate output directory.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000185
186 Private members:
187 _base_board_dict: Last-summarised Dict of boards
188 _base_err_lines: Last-summarised list of errors
Simon Glasse30965d2014-08-28 09:43:44 -0600189 _base_warn_lines: Last-summarised list of warnings
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000190 _build_period_us: Time taken for a single build (float object).
191 _complete_delay: Expected delay until completion (timedelta)
192 _next_delay_update: Next time we plan to display a progress update
193 (datatime)
194 _show_unknown: Show unknown boards (those not built) in summary
Simon Glass7b33f212020-04-09 15:08:47 -0600195 _start_time: Start time for the build
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000196 _timestamps: List of timestamps for the completion of the last
197 last _timestamp_count builds. Each is a datetime object.
198 _timestamp_count: Number of timestamps to keep in our list.
199 _working_dir: Base working directory containing all threads
200 """
201 class Outcome:
202 """Records a build outcome for a single make invocation
203
204 Public Members:
205 rc: Outcome value (OUTCOME_...)
206 err_lines: List of error lines or [] if none
207 sizes: Dictionary of image size information, keyed by filename
208 - Each value is itself a dictionary containing
209 values for 'text', 'data' and 'bss', being the integer
210 size in bytes of each section.
211 func_sizes: Dictionary keyed by filename - e.g. 'u-boot'. Each
212 value is itself a dictionary:
213 key: function name
214 value: Size of function in bytes
Simon Glass843312d2015-02-05 22:06:15 -0700215 config: Dictionary keyed by filename - e.g. '.config'. Each
216 value is itself a dictionary:
217 key: config name
218 value: config value
Alex Kiernan48ae4122018-05-31 04:48:34 +0000219 environment: Dictionary keyed by environment variable, Each
220 value is the value of environment variable.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000221 """
Alex Kiernan48ae4122018-05-31 04:48:34 +0000222 def __init__(self, rc, err_lines, sizes, func_sizes, config,
223 environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000224 self.rc = rc
225 self.err_lines = err_lines
226 self.sizes = sizes
227 self.func_sizes = func_sizes
Simon Glass843312d2015-02-05 22:06:15 -0700228 self.config = config
Alex Kiernan48ae4122018-05-31 04:48:34 +0000229 self.environment = environment
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000230
231 def __init__(self, toolchains, base_dir, git_dir, num_threads, num_jobs,
Simon Glass5971ab52014-12-01 17:33:55 -0700232 gnu_make='make', checkout=True, show_unknown=True, step=1,
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600233 no_subdirs=False, full_path=False, verbose_build=False,
Simon Glasseb70a2c2020-04-09 15:08:51 -0600234 mrproper=False, per_board_out_dir=False,
Daniel Schwierzeck2371d1b2018-01-26 16:31:05 +0100235 config_only=False, squash_config_y=False,
Simon Glassd829f122020-03-18 09:42:42 -0600236 warnings_as_errors=False, work_in_output=False):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000237 """Create a new Builder object
238
239 Args:
240 toolchains: Toolchains object to use for building
241 base_dir: Base directory to use for builder
242 git_dir: Git directory containing source repository
243 num_threads: Number of builder threads to run
244 num_jobs: Number of jobs to run at once (passed to make as -j)
Masahiro Yamada99796922014-07-22 11:19:09 +0900245 gnu_make: the command name of GNU Make.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000246 checkout: True to check out source, False to skip that step.
247 This is used for testing.
248 show_unknown: Show unknown boards (those not built) in summary
249 step: 1 to process every commit, n to process every nth commit
Simon Glassbb1501f2014-12-01 17:34:00 -0700250 no_subdirs: Don't create subdirectories when building current
251 source for a single board
252 full_path: Return the full path in CROSS_COMPILE and don't set
253 PATH
Simon Glassd2ce6582014-12-01 17:34:07 -0700254 verbose_build: Run build with V=1 and don't use 'make -s'
Simon Glasseb70a2c2020-04-09 15:08:51 -0600255 mrproper: Always run 'make mrproper' when configuring
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600256 per_board_out_dir: Build in a separate persistent directory per
257 board rather than a thread-specific directory
Simon Glassb50113f2016-11-13 14:25:51 -0700258 config_only: Only configure each build, don't build it
Simon Glassb464f8e2016-11-13 14:25:53 -0700259 squash_config_y: Convert CONFIG options with the value 'y' to '1'
Daniel Schwierzeck2371d1b2018-01-26 16:31:05 +0100260 warnings_as_errors: Treat all compiler warnings as errors
Simon Glassd829f122020-03-18 09:42:42 -0600261 work_in_output: Use the output directory as the work directory and
262 don't write to a separate output directory.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000263 """
264 self.toolchains = toolchains
265 self.base_dir = base_dir
Simon Glassd829f122020-03-18 09:42:42 -0600266 if work_in_output:
267 self._working_dir = base_dir
268 else:
269 self._working_dir = os.path.join(base_dir, '.bm-work')
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000270 self.threads = []
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000271 self.do_make = self.Make
Masahiro Yamada99796922014-07-22 11:19:09 +0900272 self.gnu_make = gnu_make
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000273 self.checkout = checkout
274 self.num_threads = num_threads
275 self.num_jobs = num_jobs
276 self.already_done = 0
277 self.force_build = False
278 self.git_dir = git_dir
279 self._show_unknown = show_unknown
280 self._timestamp_count = 10
281 self._build_period_us = None
282 self._complete_delay = None
283 self._next_delay_update = datetime.now()
Simon Glass7b33f212020-04-09 15:08:47 -0600284 self._start_time = datetime.now()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000285 self.force_config_on_failure = True
Simon Glass4266dc22014-07-13 12:22:31 -0600286 self.force_build_failures = False
Simon Glass97e91522014-07-14 17:51:02 -0600287 self.force_reconfig = False
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000288 self._step = step
Simon Glass189a4962014-07-14 17:51:03 -0600289 self.in_tree = False
Simon Glass28370c12014-08-09 15:33:06 -0600290 self._error_lines = 0
Simon Glass5971ab52014-12-01 17:33:55 -0700291 self.no_subdirs = no_subdirs
Simon Glassbb1501f2014-12-01 17:34:00 -0700292 self.full_path = full_path
Simon Glassd2ce6582014-12-01 17:34:07 -0700293 self.verbose_build = verbose_build
Simon Glassb50113f2016-11-13 14:25:51 -0700294 self.config_only = config_only
Simon Glassb464f8e2016-11-13 14:25:53 -0700295 self.squash_config_y = squash_config_y
296 self.config_filenames = BASE_CONFIG_FILENAMES
Simon Glassd829f122020-03-18 09:42:42 -0600297 self.work_in_output = work_in_output
Simon Glassb464f8e2016-11-13 14:25:53 -0700298 if not self.squash_config_y:
299 self.config_filenames += EXTRA_CONFIG_FILENAMES
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000300
Daniel Schwierzeck2371d1b2018-01-26 16:31:05 +0100301 self.warnings_as_errors = warnings_as_errors
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000302 self.col = terminal.Color()
303
Simon Glasse30965d2014-08-28 09:43:44 -0600304 self._re_function = re.compile('(.*): In function.*')
305 self._re_files = re.compile('In file included from.*')
306 self._re_warning = re.compile('(.*):(\d*):(\d*): warning: .*')
Simon Glass2d483332018-11-06 16:02:11 -0700307 self._re_dtb_warning = re.compile('(.*): Warning .*')
Simon Glasse30965d2014-08-28 09:43:44 -0600308 self._re_note = re.compile('(.*):(\d*):(\d*): note: this is the location of the previous.*')
309
Simon Glassc05aa032019-10-31 07:42:53 -0600310 self.queue = queue.Queue()
311 self.out_queue = queue.Queue()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000312 for i in range(self.num_threads):
Simon Glasseb70a2c2020-04-09 15:08:51 -0600313 t = builderthread.BuilderThread(self, i, mrproper,
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600314 per_board_out_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000315 t.setDaemon(True)
316 t.start()
317 self.threads.append(t)
318
Simon Glass190064b2014-08-09 15:33:00 -0600319 t = builderthread.ResultThread(self)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000320 t.setDaemon(True)
321 t.start()
322 self.threads.append(t)
323
324 ignore_lines = ['(make.*Waiting for unfinished)', '(Segmentation fault)']
325 self.re_make_err = re.compile('|'.join(ignore_lines))
326
Simon Glass2f256642016-09-18 16:48:37 -0600327 # Handle existing graceful with SIGINT / Ctrl-C
328 signal.signal(signal.SIGINT, self.signal_handler)
329
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000330 def __del__(self):
331 """Get rid of all threads created by the builder"""
332 for t in self.threads:
333 del t
334
Simon Glass2f256642016-09-18 16:48:37 -0600335 def signal_handler(self, signal, frame):
336 sys.exit(1)
337
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600338 def SetDisplayOptions(self, show_errors=False, show_sizes=False,
Simon Glassed966652014-08-28 09:43:43 -0600339 show_detail=False, show_bloat=False,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000340 list_error_boards=False, show_config=False,
Simon Glass174592b2020-04-09 15:08:52 -0600341 show_environment=False, filter_dtb_warnings=False):
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600342 """Setup display options for the builder.
343
Simon Glass174592b2020-04-09 15:08:52 -0600344 Args:
345 show_errors: True to show summarised error/warning info
346 show_sizes: Show size deltas
347 show_detail: Show size delta detail for each board if show_sizes
348 show_bloat: Show detail for each function
349 list_error_boards: Show the boards which caused each error/warning
350 show_config: Show config deltas
351 show_environment: Show environment deltas
352 filter_dtb_warnings: Filter out any warnings from the device-tree
353 compiler
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600354 """
355 self._show_errors = show_errors
356 self._show_sizes = show_sizes
357 self._show_detail = show_detail
358 self._show_bloat = show_bloat
Simon Glassed966652014-08-28 09:43:43 -0600359 self._list_error_boards = list_error_boards
Simon Glass843312d2015-02-05 22:06:15 -0700360 self._show_config = show_config
Alex Kiernan48ae4122018-05-31 04:48:34 +0000361 self._show_environment = show_environment
Simon Glass174592b2020-04-09 15:08:52 -0600362 self._filter_dtb_warnings = filter_dtb_warnings
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600363
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000364 def _AddTimestamp(self):
365 """Add a new timestamp to the list and record the build period.
366
367 The build period is the length of time taken to perform a single
368 build (one board, one commit).
369 """
370 now = datetime.now()
371 self._timestamps.append(now)
372 count = len(self._timestamps)
373 delta = self._timestamps[-1] - self._timestamps[0]
374 seconds = delta.total_seconds()
375
376 # If we have enough data, estimate build period (time taken for a
377 # single build) and therefore completion time.
378 if count > 1 and self._next_delay_update < now:
379 self._next_delay_update = now + timedelta(seconds=2)
380 if seconds > 0:
381 self._build_period = float(seconds) / count
382 todo = self.count - self.upto
383 self._complete_delay = timedelta(microseconds=
384 self._build_period * todo * 1000000)
385 # Round it
386 self._complete_delay -= timedelta(
387 microseconds=self._complete_delay.microseconds)
388
389 if seconds > 60:
390 self._timestamps.popleft()
391 count -= 1
392
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000393 def SelectCommit(self, commit, checkout=True):
394 """Checkout the selected commit for this build
395 """
396 self.commit = commit
397 if checkout and self.checkout:
398 gitutil.Checkout(commit.hash)
399
400 def Make(self, commit, brd, stage, cwd, *args, **kwargs):
401 """Run make
402
403 Args:
404 commit: Commit object that is being built
405 brd: Board object that is being built
Roger Meierfd18a892014-08-20 22:10:29 +0200406 stage: Stage that we are at (mrproper, config, build)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000407 cwd: Directory where make should be run
408 args: Arguments to pass to make
409 kwargs: Arguments to pass to command.RunPipe()
410 """
Masahiro Yamada99796922014-07-22 11:19:09 +0900411 cmd = [self.gnu_make] + list(args)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000412 result = command.RunPipe([cmd], capture=True, capture_stderr=True,
Simon Glasse62a24c2018-09-17 23:55:42 -0600413 cwd=cwd, raise_on_error=False, infile='/dev/null', **kwargs)
Simon Glass40f11fc2015-02-05 22:06:12 -0700414 if self.verbose_build:
415 result.stdout = '%s\n' % (' '.join(cmd)) + result.stdout
416 result.combined = '%s\n' % (' '.join(cmd)) + result.combined
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000417 return result
418
419 def ProcessResult(self, result):
420 """Process the result of a build, showing progress information
421
422 Args:
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600423 result: A CommandResult object, which indicates the result for
424 a single build
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000425 """
426 col = terminal.Color()
427 if result:
428 target = result.brd.target
429
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000430 self.upto += 1
431 if result.return_code != 0:
432 self.fail += 1
433 elif result.stderr:
434 self.warned += 1
435 if result.already_done:
436 self.already_done += 1
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600437 if self._verbose:
Simon Glass102969b2020-04-09 15:08:42 -0600438 terminal.PrintClear()
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600439 boards_selected = {target : result.brd}
440 self.ResetResultSummary(boards_selected)
441 self.ProduceResultSummary(result.commit_upto, self.commits,
442 boards_selected)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000443 else:
444 target = '(starting)'
445
446 # Display separate counts for ok, warned and fail
447 ok = self.upto - self.warned - self.fail
448 line = '\r' + self.col.Color(self.col.GREEN, '%5d' % ok)
449 line += self.col.Color(self.col.YELLOW, '%5d' % self.warned)
450 line += self.col.Color(self.col.RED, '%5d' % self.fail)
451
Simon Glass6eb76ca2020-04-09 15:08:45 -0600452 line += ' /%-5d ' % self.count
453 remaining = self.count - self.upto
454 if remaining:
455 line += self.col.Color(self.col.MAGENTA, ' -%-5d ' % remaining)
456 else:
457 line += ' ' * 8
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000458
459 # Add our current completion time estimate
460 self._AddTimestamp()
461 if self._complete_delay:
Simon Glass6eb76ca2020-04-09 15:08:45 -0600462 line += '%s : ' % self._complete_delay
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000463
Simon Glass6eb76ca2020-04-09 15:08:45 -0600464 line += target
Simon Glass102969b2020-04-09 15:08:42 -0600465 terminal.PrintClear()
Simon Glass95ed0a22020-04-09 15:08:46 -0600466 Print(line, newline=False, limit_to_line=True)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000467
468 def _GetOutputDir(self, commit_upto):
469 """Get the name of the output directory for a commit number
470
471 The output directory is typically .../<branch>/<commit>.
472
473 Args:
474 commit_upto: Commit number to use (0..self.count-1)
475 """
Simon Glass5971ab52014-12-01 17:33:55 -0700476 commit_dir = None
Simon Glassfea58582014-08-09 15:32:59 -0600477 if self.commits:
478 commit = self.commits[commit_upto]
479 subject = commit.subject.translate(trans_valid_chars)
Simon Glass925f6ad2020-03-18 09:42:45 -0600480 # See _GetOutputSpaceRemovals() which parses this name
Simon Glassfea58582014-08-09 15:32:59 -0600481 commit_dir = ('%02d_of_%02d_g%s_%s' % (commit_upto + 1,
482 self.commit_count, commit.hash, subject[:20]))
Simon Glass5971ab52014-12-01 17:33:55 -0700483 elif not self.no_subdirs:
Simon Glassfea58582014-08-09 15:32:59 -0600484 commit_dir = 'current'
Simon Glass5971ab52014-12-01 17:33:55 -0700485 if not commit_dir:
486 return self.base_dir
487 return os.path.join(self.base_dir, commit_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000488
489 def GetBuildDir(self, commit_upto, target):
490 """Get the name of the build directory for a commit number
491
492 The build directory is typically .../<branch>/<commit>/<target>.
493
494 Args:
495 commit_upto: Commit number to use (0..self.count-1)
496 target: Target name
497 """
498 output_dir = self._GetOutputDir(commit_upto)
499 return os.path.join(output_dir, target)
500
501 def GetDoneFile(self, commit_upto, target):
502 """Get the name of the done file for a commit number
503
504 Args:
505 commit_upto: Commit number to use (0..self.count-1)
506 target: Target name
507 """
508 return os.path.join(self.GetBuildDir(commit_upto, target), 'done')
509
510 def GetSizesFile(self, commit_upto, target):
511 """Get the name of the sizes file for a commit number
512
513 Args:
514 commit_upto: Commit number to use (0..self.count-1)
515 target: Target name
516 """
517 return os.path.join(self.GetBuildDir(commit_upto, target), 'sizes')
518
519 def GetFuncSizesFile(self, commit_upto, target, elf_fname):
520 """Get the name of the funcsizes file for a commit number and ELF file
521
522 Args:
523 commit_upto: Commit number to use (0..self.count-1)
524 target: Target name
525 elf_fname: Filename of elf image
526 """
527 return os.path.join(self.GetBuildDir(commit_upto, target),
528 '%s.sizes' % elf_fname.replace('/', '-'))
529
530 def GetObjdumpFile(self, commit_upto, target, elf_fname):
531 """Get the name of the objdump file for a commit number and ELF file
532
533 Args:
534 commit_upto: Commit number to use (0..self.count-1)
535 target: Target name
536 elf_fname: Filename of elf image
537 """
538 return os.path.join(self.GetBuildDir(commit_upto, target),
539 '%s.objdump' % elf_fname.replace('/', '-'))
540
541 def GetErrFile(self, commit_upto, target):
542 """Get the name of the err file for a commit number
543
544 Args:
545 commit_upto: Commit number to use (0..self.count-1)
546 target: Target name
547 """
548 output_dir = self.GetBuildDir(commit_upto, target)
549 return os.path.join(output_dir, 'err')
550
551 def FilterErrors(self, lines):
552 """Filter out errors in which we have no interest
553
554 We should probably use map().
555
556 Args:
557 lines: List of error lines, each a string
558 Returns:
559 New list with only interesting lines included
560 """
561 out_lines = []
562 for line in lines:
Simon Glass174592b2020-04-09 15:08:52 -0600563 if self.re_make_err.search(line):
564 continue
565 if self._filter_dtb_warnings and self._re_dtb_warning.search(line):
566 continue
567 out_lines.append(line)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000568 return out_lines
569
570 def ReadFuncSizes(self, fname, fd):
571 """Read function sizes from the output of 'nm'
572
573 Args:
574 fd: File containing data to read
575 fname: Filename we are reading from (just for errors)
576
577 Returns:
578 Dictionary containing size of each function in bytes, indexed by
579 function name.
580 """
581 sym = {}
582 for line in fd.readlines():
583 try:
Tom Rinid08c38c2019-12-06 15:31:31 -0500584 if line.strip():
585 size, type, name = line[:-1].split()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000586 except:
Simon Glass4653a882014-09-05 19:00:07 -0600587 Print("Invalid line in file '%s': '%s'" % (fname, line[:-1]))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000588 continue
589 if type in 'tTdDbB':
590 # function names begin with '.' on 64-bit powerpc
591 if '.' in name[1:]:
592 name = 'static.' + name.split('.')[0]
593 sym[name] = sym.get(name, 0) + int(size, 16)
594 return sym
595
Simon Glass843312d2015-02-05 22:06:15 -0700596 def _ProcessConfig(self, fname):
597 """Read in a .config, autoconf.mk or autoconf.h file
598
599 This function handles all config file types. It ignores comments and
600 any #defines which don't start with CONFIG_.
601
602 Args:
603 fname: Filename to read
604
605 Returns:
606 Dictionary:
607 key: Config name (e.g. CONFIG_DM)
608 value: Config value (e.g. 1)
609 """
610 config = {}
611 if os.path.exists(fname):
612 with open(fname) as fd:
613 for line in fd:
614 line = line.strip()
615 if line.startswith('#define'):
616 values = line[8:].split(' ', 1)
617 if len(values) > 1:
618 key, value = values
619 else:
620 key = values[0]
Simon Glassb464f8e2016-11-13 14:25:53 -0700621 value = '1' if self.squash_config_y else ''
Simon Glass843312d2015-02-05 22:06:15 -0700622 if not key.startswith('CONFIG_'):
623 continue
624 elif not line or line[0] in ['#', '*', '/']:
625 continue
626 else:
627 key, value = line.split('=', 1)
Simon Glassb464f8e2016-11-13 14:25:53 -0700628 if self.squash_config_y and value == 'y':
629 value = '1'
Simon Glass843312d2015-02-05 22:06:15 -0700630 config[key] = value
631 return config
632
Alex Kiernan48ae4122018-05-31 04:48:34 +0000633 def _ProcessEnvironment(self, fname):
634 """Read in a uboot.env file
635
636 This function reads in environment variables from a file.
637
638 Args:
639 fname: Filename to read
640
641 Returns:
642 Dictionary:
643 key: environment variable (e.g. bootlimit)
644 value: value of environment variable (e.g. 1)
645 """
646 environment = {}
647 if os.path.exists(fname):
648 with open(fname) as fd:
649 for line in fd.read().split('\0'):
650 try:
651 key, value = line.split('=', 1)
652 environment[key] = value
653 except ValueError:
654 # ignore lines we can't parse
655 pass
656 return environment
657
Simon Glass843312d2015-02-05 22:06:15 -0700658 def GetBuildOutcome(self, commit_upto, target, read_func_sizes,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000659 read_config, read_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000660 """Work out the outcome of a build.
661
662 Args:
663 commit_upto: Commit number to check (0..n-1)
664 target: Target board to check
665 read_func_sizes: True to read function size information
Simon Glass843312d2015-02-05 22:06:15 -0700666 read_config: True to read .config and autoconf.h files
Alex Kiernan48ae4122018-05-31 04:48:34 +0000667 read_environment: True to read uboot.env files
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000668
669 Returns:
670 Outcome object
671 """
672 done_file = self.GetDoneFile(commit_upto, target)
673 sizes_file = self.GetSizesFile(commit_upto, target)
674 sizes = {}
675 func_sizes = {}
Simon Glass843312d2015-02-05 22:06:15 -0700676 config = {}
Alex Kiernan48ae4122018-05-31 04:48:34 +0000677 environment = {}
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000678 if os.path.exists(done_file):
679 with open(done_file, 'r') as fd:
Simon Glass347ea0b2019-04-26 19:02:23 -0600680 try:
681 return_code = int(fd.readline())
682 except ValueError:
683 # The file may be empty due to running out of disk space.
684 # Try a rebuild
685 return_code = 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000686 err_lines = []
687 err_file = self.GetErrFile(commit_upto, target)
688 if os.path.exists(err_file):
689 with open(err_file, 'r') as fd:
690 err_lines = self.FilterErrors(fd.readlines())
691
692 # Decide whether the build was ok, failed or created warnings
693 if return_code:
694 rc = OUTCOME_ERROR
695 elif len(err_lines):
696 rc = OUTCOME_WARNING
697 else:
698 rc = OUTCOME_OK
699
700 # Convert size information to our simple format
701 if os.path.exists(sizes_file):
702 with open(sizes_file, 'r') as fd:
703 for line in fd.readlines():
704 values = line.split()
705 rodata = 0
706 if len(values) > 6:
707 rodata = int(values[6], 16)
708 size_dict = {
709 'all' : int(values[0]) + int(values[1]) +
710 int(values[2]),
711 'text' : int(values[0]) - rodata,
712 'data' : int(values[1]),
713 'bss' : int(values[2]),
714 'rodata' : rodata,
715 }
716 sizes[values[5]] = size_dict
717
718 if read_func_sizes:
719 pattern = self.GetFuncSizesFile(commit_upto, target, '*')
720 for fname in glob.glob(pattern):
721 with open(fname, 'r') as fd:
722 dict_name = os.path.basename(fname).replace('.sizes',
723 '')
724 func_sizes[dict_name] = self.ReadFuncSizes(fname, fd)
725
Simon Glass843312d2015-02-05 22:06:15 -0700726 if read_config:
727 output_dir = self.GetBuildDir(commit_upto, target)
Simon Glassb464f8e2016-11-13 14:25:53 -0700728 for name in self.config_filenames:
Simon Glass843312d2015-02-05 22:06:15 -0700729 fname = os.path.join(output_dir, name)
730 config[name] = self._ProcessConfig(fname)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000731
Alex Kiernan48ae4122018-05-31 04:48:34 +0000732 if read_environment:
733 output_dir = self.GetBuildDir(commit_upto, target)
734 fname = os.path.join(output_dir, 'uboot.env')
735 environment = self._ProcessEnvironment(fname)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000736
Alex Kiernan48ae4122018-05-31 04:48:34 +0000737 return Builder.Outcome(rc, err_lines, sizes, func_sizes, config,
738 environment)
739
740 return Builder.Outcome(OUTCOME_UNKNOWN, [], {}, {}, {}, {})
Simon Glass843312d2015-02-05 22:06:15 -0700741
742 def GetResultSummary(self, boards_selected, commit_upto, read_func_sizes,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000743 read_config, read_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000744 """Calculate a summary of the results of building a commit.
745
746 Args:
747 board_selected: Dict containing boards to summarise
748 commit_upto: Commit number to summarize (0..self.count-1)
749 read_func_sizes: True to read function size information
Simon Glass843312d2015-02-05 22:06:15 -0700750 read_config: True to read .config and autoconf.h files
Alex Kiernan48ae4122018-05-31 04:48:34 +0000751 read_environment: True to read uboot.env files
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000752
753 Returns:
754 Tuple:
755 Dict containing boards which passed building this commit.
756 keyed by board.target
Simon Glasse30965d2014-08-28 09:43:44 -0600757 List containing a summary of error lines
Simon Glassed966652014-08-28 09:43:43 -0600758 Dict keyed by error line, containing a list of the Board
759 objects with that error
Simon Glasse30965d2014-08-28 09:43:44 -0600760 List containing a summary of warning lines
761 Dict keyed by error line, containing a list of the Board
762 objects with that warning
Simon Glass8270e3c2015-08-25 21:52:14 -0600763 Dictionary keyed by board.target. Each value is a dictionary:
764 key: filename - e.g. '.config'
Simon Glass843312d2015-02-05 22:06:15 -0700765 value is itself a dictionary:
766 key: config name
767 value: config value
Alex Kiernan48ae4122018-05-31 04:48:34 +0000768 Dictionary keyed by board.target. Each value is a dictionary:
769 key: environment variable
770 value: value of environment variable
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000771 """
Simon Glasse30965d2014-08-28 09:43:44 -0600772 def AddLine(lines_summary, lines_boards, line, board):
773 line = line.rstrip()
774 if line in lines_boards:
775 lines_boards[line].append(board)
776 else:
777 lines_boards[line] = [board]
778 lines_summary.append(line)
779
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000780 board_dict = {}
781 err_lines_summary = []
Simon Glassed966652014-08-28 09:43:43 -0600782 err_lines_boards = {}
Simon Glasse30965d2014-08-28 09:43:44 -0600783 warn_lines_summary = []
784 warn_lines_boards = {}
Simon Glass843312d2015-02-05 22:06:15 -0700785 config = {}
Alex Kiernan48ae4122018-05-31 04:48:34 +0000786 environment = {}
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000787
Simon Glassc05aa032019-10-31 07:42:53 -0600788 for board in boards_selected.values():
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000789 outcome = self.GetBuildOutcome(commit_upto, board.target,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000790 read_func_sizes, read_config,
791 read_environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000792 board_dict[board.target] = outcome
Simon Glasse30965d2014-08-28 09:43:44 -0600793 last_func = None
794 last_was_warning = False
795 for line in outcome.err_lines:
796 if line:
797 if (self._re_function.match(line) or
798 self._re_files.match(line)):
799 last_func = line
Simon Glassed966652014-08-28 09:43:43 -0600800 else:
Simon Glass2d483332018-11-06 16:02:11 -0700801 is_warning = (self._re_warning.match(line) or
802 self._re_dtb_warning.match(line))
Simon Glasse30965d2014-08-28 09:43:44 -0600803 is_note = self._re_note.match(line)
804 if is_warning or (last_was_warning and is_note):
805 if last_func:
806 AddLine(warn_lines_summary, warn_lines_boards,
807 last_func, board)
808 AddLine(warn_lines_summary, warn_lines_boards,
809 line, board)
810 else:
811 if last_func:
812 AddLine(err_lines_summary, err_lines_boards,
813 last_func, board)
814 AddLine(err_lines_summary, err_lines_boards,
815 line, board)
816 last_was_warning = is_warning
817 last_func = None
Simon Glassb464f8e2016-11-13 14:25:53 -0700818 tconfig = Config(self.config_filenames, board.target)
819 for fname in self.config_filenames:
Simon Glass843312d2015-02-05 22:06:15 -0700820 if outcome.config:
Simon Glassc05aa032019-10-31 07:42:53 -0600821 for key, value in outcome.config[fname].items():
Simon Glass8270e3c2015-08-25 21:52:14 -0600822 tconfig.Add(fname, key, value)
823 config[board.target] = tconfig
Simon Glass843312d2015-02-05 22:06:15 -0700824
Alex Kiernan48ae4122018-05-31 04:48:34 +0000825 tenvironment = Environment(board.target)
826 if outcome.environment:
Simon Glassc05aa032019-10-31 07:42:53 -0600827 for key, value in outcome.environment.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +0000828 tenvironment.Add(key, value)
829 environment[board.target] = tenvironment
830
Simon Glasse30965d2014-08-28 09:43:44 -0600831 return (board_dict, err_lines_summary, err_lines_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000832 warn_lines_summary, warn_lines_boards, config, environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000833
834 def AddOutcome(self, board_dict, arch_list, changes, char, color):
835 """Add an output to our list of outcomes for each architecture
836
837 This simple function adds failing boards (changes) to the
838 relevant architecture string, so we can print the results out
839 sorted by architecture.
840
841 Args:
842 board_dict: Dict containing all boards
843 arch_list: Dict keyed by arch name. Value is a string containing
844 a list of board names which failed for that arch.
845 changes: List of boards to add to arch_list
846 color: terminal.Colour object
847 """
848 done_arch = {}
849 for target in changes:
850 if target in board_dict:
851 arch = board_dict[target].arch
852 else:
853 arch = 'unknown'
854 str = self.col.Color(color, ' ' + target)
855 if not arch in done_arch:
Simon Glass63c619e2015-02-05 22:06:11 -0700856 str = ' %s %s' % (self.col.Color(color, char), str)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000857 done_arch[arch] = True
858 if not arch in arch_list:
859 arch_list[arch] = str
860 else:
861 arch_list[arch] += str
862
863
864 def ColourNum(self, num):
865 color = self.col.RED if num > 0 else self.col.GREEN
866 if num == 0:
867 return '0'
868 return self.col.Color(color, str(num))
869
870 def ResetResultSummary(self, board_selected):
871 """Reset the results summary ready for use.
872
873 Set up the base board list to be all those selected, and set the
874 error lines to empty.
875
876 Following this, calls to PrintResultSummary() will use this
877 information to work out what has changed.
878
879 Args:
880 board_selected: Dict containing boards to summarise, keyed by
881 board.target
882 """
883 self._base_board_dict = {}
884 for board in board_selected:
Alex Kiernan48ae4122018-05-31 04:48:34 +0000885 self._base_board_dict[board] = Builder.Outcome(0, [], [], {}, {},
886 {})
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000887 self._base_err_lines = []
Simon Glasse30965d2014-08-28 09:43:44 -0600888 self._base_warn_lines = []
889 self._base_err_line_boards = {}
890 self._base_warn_line_boards = {}
Simon Glass8270e3c2015-08-25 21:52:14 -0600891 self._base_config = None
Alex Kiernan48ae4122018-05-31 04:48:34 +0000892 self._base_environment = None
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000893
894 def PrintFuncSizeDetail(self, fname, old, new):
895 grow, shrink, add, remove, up, down = 0, 0, 0, 0, 0, 0
896 delta, common = [], {}
897
898 for a in old:
899 if a in new:
900 common[a] = 1
901
902 for name in old:
903 if name not in common:
904 remove += 1
905 down += old[name]
906 delta.append([-old[name], name])
907
908 for name in new:
909 if name not in common:
910 add += 1
911 up += new[name]
912 delta.append([new[name], name])
913
914 for name in common:
915 diff = new.get(name, 0) - old.get(name, 0)
916 if diff > 0:
917 grow, up = grow + 1, up + diff
918 elif diff < 0:
919 shrink, down = shrink + 1, down - diff
920 delta.append([diff, name])
921
922 delta.sort()
923 delta.reverse()
924
925 args = [add, -remove, grow, -shrink, up, -down, up - down]
Tom Rinid5686a62017-05-22 13:48:52 -0400926 if max(args) == 0 and min(args) == 0:
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000927 return
928 args = [self.ColourNum(x) for x in args]
929 indent = ' ' * 15
Simon Glass4653a882014-09-05 19:00:07 -0600930 Print('%s%s: add: %s/%s, grow: %s/%s bytes: %s/%s (%s)' %
931 tuple([indent, self.col.Color(self.col.YELLOW, fname)] + args))
932 Print('%s %-38s %7s %7s %+7s' % (indent, 'function', 'old', 'new',
933 'delta'))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000934 for diff, name in delta:
935 if diff:
936 color = self.col.RED if diff > 0 else self.col.GREEN
937 msg = '%s %-38s %7s %7s %+7d' % (indent, name,
938 old.get(name, '-'), new.get(name,'-'), diff)
Simon Glass4653a882014-09-05 19:00:07 -0600939 Print(msg, colour=color)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000940
941
942 def PrintSizeDetail(self, target_list, show_bloat):
943 """Show details size information for each board
944
945 Args:
946 target_list: List of targets, each a dict containing:
947 'target': Target name
948 'total_diff': Total difference in bytes across all areas
949 <part_name>: Difference for that part
950 show_bloat: Show detail for each function
951 """
952 targets_by_diff = sorted(target_list, reverse=True,
953 key=lambda x: x['_total_diff'])
954 for result in targets_by_diff:
955 printed_target = False
956 for name in sorted(result):
957 diff = result[name]
958 if name.startswith('_'):
959 continue
960 if diff != 0:
961 color = self.col.RED if diff > 0 else self.col.GREEN
962 msg = ' %s %+d' % (name, diff)
963 if not printed_target:
Simon Glass4653a882014-09-05 19:00:07 -0600964 Print('%10s %-15s:' % ('', result['_target']),
965 newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000966 printed_target = True
Simon Glass4653a882014-09-05 19:00:07 -0600967 Print(msg, colour=color, newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000968 if printed_target:
Simon Glass4653a882014-09-05 19:00:07 -0600969 Print()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000970 if show_bloat:
971 target = result['_target']
972 outcome = result['_outcome']
973 base_outcome = self._base_board_dict[target]
974 for fname in outcome.func_sizes:
975 self.PrintFuncSizeDetail(fname,
976 base_outcome.func_sizes[fname],
977 outcome.func_sizes[fname])
978
979
980 def PrintSizeSummary(self, board_selected, board_dict, show_detail,
981 show_bloat):
982 """Print a summary of image sizes broken down by section.
983
984 The summary takes the form of one line per architecture. The
985 line contains deltas for each of the sections (+ means the section
Flavio Suligoi9de5c392020-01-29 09:56:05 +0100986 got bigger, - means smaller). The numbers are the average number
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000987 of bytes that a board in this section increased by.
988
989 For example:
990 powerpc: (622 boards) text -0.0
991 arm: (285 boards) text -0.0
992 nds32: (3 boards) text -8.0
993
994 Args:
995 board_selected: Dict containing boards to summarise, keyed by
996 board.target
997 board_dict: Dict containing boards for which we built this
998 commit, keyed by board.target. The value is an Outcome object.
Simon Glassf9c094b2020-03-18 09:42:43 -0600999 show_detail: Show size delta detail for each board
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001000 show_bloat: Show detail for each function
1001 """
1002 arch_list = {}
1003 arch_count = {}
1004
1005 # Calculate changes in size for different image parts
1006 # The previous sizes are in Board.sizes, for each board
1007 for target in board_dict:
1008 if target not in board_selected:
1009 continue
1010 base_sizes = self._base_board_dict[target].sizes
1011 outcome = board_dict[target]
1012 sizes = outcome.sizes
1013
1014 # Loop through the list of images, creating a dict of size
1015 # changes for each image/part. We end up with something like
1016 # {'target' : 'snapper9g45, 'data' : 5, 'u-boot-spl:text' : -4}
1017 # which means that U-Boot data increased by 5 bytes and SPL
1018 # text decreased by 4.
1019 err = {'_target' : target}
1020 for image in sizes:
1021 if image in base_sizes:
1022 base_image = base_sizes[image]
1023 # Loop through the text, data, bss parts
1024 for part in sorted(sizes[image]):
1025 diff = sizes[image][part] - base_image[part]
1026 col = None
1027 if diff:
1028 if image == 'u-boot':
1029 name = part
1030 else:
1031 name = image + ':' + part
1032 err[name] = diff
1033 arch = board_selected[target].arch
1034 if not arch in arch_count:
1035 arch_count[arch] = 1
1036 else:
1037 arch_count[arch] += 1
1038 if not sizes:
1039 pass # Only add to our list when we have some stats
1040 elif not arch in arch_list:
1041 arch_list[arch] = [err]
1042 else:
1043 arch_list[arch].append(err)
1044
1045 # We now have a list of image size changes sorted by arch
1046 # Print out a summary of these
Simon Glassc05aa032019-10-31 07:42:53 -06001047 for arch, target_list in arch_list.items():
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001048 # Get total difference for each type
1049 totals = {}
1050 for result in target_list:
1051 total = 0
Simon Glassc05aa032019-10-31 07:42:53 -06001052 for name, diff in result.items():
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001053 if name.startswith('_'):
1054 continue
1055 total += diff
1056 if name in totals:
1057 totals[name] += diff
1058 else:
1059 totals[name] = diff
1060 result['_total_diff'] = total
1061 result['_outcome'] = board_dict[result['_target']]
1062
1063 count = len(target_list)
1064 printed_arch = False
1065 for name in sorted(totals):
1066 diff = totals[name]
1067 if diff:
1068 # Display the average difference in this name for this
1069 # architecture
1070 avg_diff = float(diff) / count
1071 color = self.col.RED if avg_diff > 0 else self.col.GREEN
1072 msg = ' %s %+1.1f' % (name, avg_diff)
1073 if not printed_arch:
Simon Glass4653a882014-09-05 19:00:07 -06001074 Print('%10s: (for %d/%d boards)' % (arch, count,
1075 arch_count[arch]), newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001076 printed_arch = True
Simon Glass4653a882014-09-05 19:00:07 -06001077 Print(msg, colour=color, newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001078
1079 if printed_arch:
Simon Glass4653a882014-09-05 19:00:07 -06001080 Print()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001081 if show_detail:
1082 self.PrintSizeDetail(target_list, show_bloat)
1083
1084
1085 def PrintResultSummary(self, board_selected, board_dict, err_lines,
Simon Glasse30965d2014-08-28 09:43:44 -06001086 err_line_boards, warn_lines, warn_line_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001087 config, environment, show_sizes, show_detail,
1088 show_bloat, show_config, show_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001089 """Compare results with the base results and display delta.
1090
1091 Only boards mentioned in board_selected will be considered. This
1092 function is intended to be called repeatedly with the results of
1093 each commit. It therefore shows a 'diff' between what it saw in
1094 the last call and what it sees now.
1095
1096 Args:
1097 board_selected: Dict containing boards to summarise, keyed by
1098 board.target
1099 board_dict: Dict containing boards for which we built this
1100 commit, keyed by board.target. The value is an Outcome object.
1101 err_lines: A list of errors for this commit, or [] if there is
1102 none, or we don't want to print errors
Simon Glassed966652014-08-28 09:43:43 -06001103 err_line_boards: Dict keyed by error line, containing a list of
1104 the Board objects with that error
Simon Glasse30965d2014-08-28 09:43:44 -06001105 warn_lines: A list of warnings for this commit, or [] if there is
1106 none, or we don't want to print errors
1107 warn_line_boards: Dict keyed by warning line, containing a list of
1108 the Board objects with that warning
Simon Glass843312d2015-02-05 22:06:15 -07001109 config: Dictionary keyed by filename - e.g. '.config'. Each
1110 value is itself a dictionary:
1111 key: config name
1112 value: config value
Alex Kiernan48ae4122018-05-31 04:48:34 +00001113 environment: Dictionary keyed by environment variable, Each
1114 value is the value of environment variable.
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001115 show_sizes: Show image size deltas
Simon Glassf9c094b2020-03-18 09:42:43 -06001116 show_detail: Show size delta detail for each board if show_sizes
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001117 show_bloat: Show detail for each function
Simon Glass843312d2015-02-05 22:06:15 -07001118 show_config: Show config changes
Alex Kiernan48ae4122018-05-31 04:48:34 +00001119 show_environment: Show environment changes
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001120 """
Simon Glasse30965d2014-08-28 09:43:44 -06001121 def _BoardList(line, line_boards):
Simon Glassed966652014-08-28 09:43:43 -06001122 """Helper function to get a line of boards containing a line
1123
1124 Args:
1125 line: Error line to search for
Simon Glass35d696d2020-04-09 15:08:36 -06001126 line_boards: boards to search, each a Board
Simon Glassed966652014-08-28 09:43:43 -06001127 Return:
Simon Glass35d696d2020-04-09 15:08:36 -06001128 List of boards with that error line, or [] if the user has not
1129 requested such a list
Simon Glassed966652014-08-28 09:43:43 -06001130 """
Simon Glass35d696d2020-04-09 15:08:36 -06001131 boards = []
1132 board_set = set()
Simon Glassed966652014-08-28 09:43:43 -06001133 if self._list_error_boards:
Simon Glasse30965d2014-08-28 09:43:44 -06001134 for board in line_boards[line]:
Simon Glass35d696d2020-04-09 15:08:36 -06001135 if not board in board_set:
1136 boards.append(board)
1137 board_set.add(board)
1138 return boards
Simon Glassed966652014-08-28 09:43:43 -06001139
Simon Glasse30965d2014-08-28 09:43:44 -06001140 def _CalcErrorDelta(base_lines, base_line_boards, lines, line_boards,
1141 char):
Simon Glass35d696d2020-04-09 15:08:36 -06001142 """Calculate the required output based on changes in errors
1143
1144 Args:
1145 base_lines: List of errors/warnings for previous commit
1146 base_line_boards: Dict keyed by error line, containing a list
1147 of the Board objects with that error in the previous commit
1148 lines: List of errors/warning for this commit, each a str
1149 line_boards: Dict keyed by error line, containing a list
1150 of the Board objects with that error in this commit
1151 char: Character representing error ('') or warning ('w'). The
1152 broken ('+') or fixed ('-') characters are added in this
1153 function
1154
1155 Returns:
1156 Tuple
1157 List of ErrLine objects for 'better' lines
1158 List of ErrLine objects for 'worse' lines
1159 """
Simon Glasse30965d2014-08-28 09:43:44 -06001160 better_lines = []
1161 worse_lines = []
1162 for line in lines:
1163 if line not in base_lines:
Simon Glass35d696d2020-04-09 15:08:36 -06001164 errline = ErrLine(char + '+', _BoardList(line, line_boards),
1165 line)
1166 worse_lines.append(errline)
Simon Glasse30965d2014-08-28 09:43:44 -06001167 for line in base_lines:
1168 if line not in lines:
Simon Glass35d696d2020-04-09 15:08:36 -06001169 errline = ErrLine(char + '-',
1170 _BoardList(line, base_line_boards), line)
1171 better_lines.append(errline)
Simon Glasse30965d2014-08-28 09:43:44 -06001172 return better_lines, worse_lines
1173
Simon Glass843312d2015-02-05 22:06:15 -07001174 def _CalcConfig(delta, name, config):
1175 """Calculate configuration changes
1176
1177 Args:
1178 delta: Type of the delta, e.g. '+'
1179 name: name of the file which changed (e.g. .config)
1180 config: configuration change dictionary
1181 key: config name
1182 value: config value
1183 Returns:
1184 String containing the configuration changes which can be
1185 printed
1186 """
1187 out = ''
1188 for key in sorted(config.keys()):
1189 out += '%s=%s ' % (key, config[key])
Simon Glass8270e3c2015-08-25 21:52:14 -06001190 return '%s %s: %s' % (delta, name, out)
Simon Glass843312d2015-02-05 22:06:15 -07001191
Simon Glass8270e3c2015-08-25 21:52:14 -06001192 def _AddConfig(lines, name, config_plus, config_minus, config_change):
1193 """Add changes in configuration to a list
Simon Glass843312d2015-02-05 22:06:15 -07001194
1195 Args:
Simon Glass8270e3c2015-08-25 21:52:14 -06001196 lines: list to add to
1197 name: config file name
Simon Glass843312d2015-02-05 22:06:15 -07001198 config_plus: configurations added, dictionary
1199 key: config name
1200 value: config value
1201 config_minus: configurations removed, dictionary
1202 key: config name
1203 value: config value
1204 config_change: configurations changed, dictionary
1205 key: config name
1206 value: config value
1207 """
1208 if config_plus:
Simon Glass8270e3c2015-08-25 21:52:14 -06001209 lines.append(_CalcConfig('+', name, config_plus))
Simon Glass843312d2015-02-05 22:06:15 -07001210 if config_minus:
Simon Glass8270e3c2015-08-25 21:52:14 -06001211 lines.append(_CalcConfig('-', name, config_minus))
Simon Glass843312d2015-02-05 22:06:15 -07001212 if config_change:
Simon Glass8270e3c2015-08-25 21:52:14 -06001213 lines.append(_CalcConfig('c', name, config_change))
1214
1215 def _OutputConfigInfo(lines):
1216 for line in lines:
1217 if not line:
1218 continue
1219 if line[0] == '+':
1220 col = self.col.GREEN
1221 elif line[0] == '-':
1222 col = self.col.RED
1223 elif line[0] == 'c':
1224 col = self.col.YELLOW
1225 Print(' ' + line, newline=True, colour=col)
1226
Simon Glassb206d872020-04-09 15:08:28 -06001227 def _OutputErrLines(err_lines, colour):
1228 """Output the line of error/warning lines, if not empty
1229
1230 Also increments self._error_lines if err_lines not empty
1231
1232 Args:
Simon Glass35d696d2020-04-09 15:08:36 -06001233 err_lines: List of ErrLine objects, each an error or warning
1234 line, possibly including a list of boards with that
1235 error/warning
Simon Glassb206d872020-04-09 15:08:28 -06001236 colour: Colour to use for output
1237 """
1238 if err_lines:
Simon Glass8c9a2672020-04-09 15:08:37 -06001239 out_list = []
Simon Glass35d696d2020-04-09 15:08:36 -06001240 for line in err_lines:
1241 boards = ''
1242 names = [board.target for board in line.boards]
Simon Glass9ef0ceb2020-04-09 15:08:38 -06001243 board_str = ' '.join(names) if names else ''
Simon Glass8c9a2672020-04-09 15:08:37 -06001244 if board_str:
1245 out = self.col.Color(colour, line.char + '(')
1246 out += self.col.Color(self.col.MAGENTA, board_str,
1247 bright=False)
1248 out += self.col.Color(colour, ') %s' % line.errline)
1249 else:
1250 out = self.col.Color(colour, line.char + line.errline)
1251 out_list.append(out)
1252 Print('\n'.join(out_list))
Simon Glassb206d872020-04-09 15:08:28 -06001253 self._error_lines += 1
1254
Simon Glass843312d2015-02-05 22:06:15 -07001255
Simon Glass4cf2b222018-11-06 16:02:12 -07001256 ok_boards = [] # List of boards fixed since last commit
Simon Glass6af71012018-11-06 16:02:13 -07001257 warn_boards = [] # List of boards with warnings since last commit
Simon Glass4cf2b222018-11-06 16:02:12 -07001258 err_boards = [] # List of new broken boards since last commit
1259 new_boards = [] # List of boards that didn't exist last time
1260 unknown_boards = [] # List of boards that were not built
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001261
1262 for target in board_dict:
1263 if target not in board_selected:
1264 continue
1265
1266 # If the board was built last time, add its outcome to a list
1267 if target in self._base_board_dict:
1268 base_outcome = self._base_board_dict[target].rc
1269 outcome = board_dict[target]
1270 if outcome.rc == OUTCOME_UNKNOWN:
Simon Glass4cf2b222018-11-06 16:02:12 -07001271 unknown_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001272 elif outcome.rc < base_outcome:
Simon Glass6af71012018-11-06 16:02:13 -07001273 if outcome.rc == OUTCOME_WARNING:
1274 warn_boards.append(target)
1275 else:
1276 ok_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001277 elif outcome.rc > base_outcome:
Simon Glass6af71012018-11-06 16:02:13 -07001278 if outcome.rc == OUTCOME_WARNING:
1279 warn_boards.append(target)
1280 else:
1281 err_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001282 else:
Simon Glass4cf2b222018-11-06 16:02:12 -07001283 new_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001284
Simon Glassb206d872020-04-09 15:08:28 -06001285 # Get a list of errors and warnings that have appeared, and disappeared
Simon Glasse30965d2014-08-28 09:43:44 -06001286 better_err, worse_err = _CalcErrorDelta(self._base_err_lines,
1287 self._base_err_line_boards, err_lines, err_line_boards, '')
1288 better_warn, worse_warn = _CalcErrorDelta(self._base_warn_lines,
1289 self._base_warn_line_boards, warn_lines, warn_line_boards, 'w')
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001290
1291 # Display results by arch
Simon Glass6af71012018-11-06 16:02:13 -07001292 if any((ok_boards, warn_boards, err_boards, unknown_boards, new_boards,
1293 worse_err, better_err, worse_warn, better_warn)):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001294 arch_list = {}
Simon Glass4cf2b222018-11-06 16:02:12 -07001295 self.AddOutcome(board_selected, arch_list, ok_boards, '',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001296 self.col.GREEN)
Simon Glass6af71012018-11-06 16:02:13 -07001297 self.AddOutcome(board_selected, arch_list, warn_boards, 'w+',
1298 self.col.YELLOW)
Simon Glass4cf2b222018-11-06 16:02:12 -07001299 self.AddOutcome(board_selected, arch_list, err_boards, '+',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001300 self.col.RED)
Simon Glass4cf2b222018-11-06 16:02:12 -07001301 self.AddOutcome(board_selected, arch_list, new_boards, '*', self.col.BLUE)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001302 if self._show_unknown:
Simon Glass4cf2b222018-11-06 16:02:12 -07001303 self.AddOutcome(board_selected, arch_list, unknown_boards, '?',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001304 self.col.MAGENTA)
Simon Glassc05aa032019-10-31 07:42:53 -06001305 for arch, target_list in arch_list.items():
Simon Glass4653a882014-09-05 19:00:07 -06001306 Print('%10s: %s' % (arch, target_list))
Simon Glass28370c12014-08-09 15:33:06 -06001307 self._error_lines += 1
Simon Glassb206d872020-04-09 15:08:28 -06001308 _OutputErrLines(better_err, colour=self.col.GREEN)
1309 _OutputErrLines(worse_err, colour=self.col.RED)
1310 _OutputErrLines(better_warn, colour=self.col.CYAN)
Simon Glass5627bd92020-04-09 15:08:35 -06001311 _OutputErrLines(worse_warn, colour=self.col.YELLOW)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001312
1313 if show_sizes:
1314 self.PrintSizeSummary(board_selected, board_dict, show_detail,
1315 show_bloat)
1316
Alex Kiernan48ae4122018-05-31 04:48:34 +00001317 if show_environment and self._base_environment:
1318 lines = []
1319
1320 for target in board_dict:
1321 if target not in board_selected:
1322 continue
1323
1324 tbase = self._base_environment[target]
1325 tenvironment = environment[target]
1326 environment_plus = {}
1327 environment_minus = {}
1328 environment_change = {}
1329 base = tbase.environment
Simon Glassc05aa032019-10-31 07:42:53 -06001330 for key, value in tenvironment.environment.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001331 if key not in base:
1332 environment_plus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001333 for key, value in base.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001334 if key not in tenvironment.environment:
1335 environment_minus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001336 for key, value in base.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001337 new_value = tenvironment.environment.get(key)
1338 if new_value and value != new_value:
1339 desc = '%s -> %s' % (value, new_value)
1340 environment_change[key] = desc
1341
1342 _AddConfig(lines, target, environment_plus, environment_minus,
1343 environment_change)
1344
1345 _OutputConfigInfo(lines)
1346
Simon Glass8270e3c2015-08-25 21:52:14 -06001347 if show_config and self._base_config:
1348 summary = {}
1349 arch_config_plus = {}
1350 arch_config_minus = {}
1351 arch_config_change = {}
1352 arch_list = []
1353
1354 for target in board_dict:
1355 if target not in board_selected:
Simon Glass843312d2015-02-05 22:06:15 -07001356 continue
Simon Glass8270e3c2015-08-25 21:52:14 -06001357 arch = board_selected[target].arch
1358 if arch not in arch_list:
1359 arch_list.append(arch)
1360
1361 for arch in arch_list:
1362 arch_config_plus[arch] = {}
1363 arch_config_minus[arch] = {}
1364 arch_config_change[arch] = {}
Simon Glassb464f8e2016-11-13 14:25:53 -07001365 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001366 arch_config_plus[arch][name] = {}
1367 arch_config_minus[arch][name] = {}
1368 arch_config_change[arch][name] = {}
1369
1370 for target in board_dict:
1371 if target not in board_selected:
1372 continue
1373
1374 arch = board_selected[target].arch
1375
1376 all_config_plus = {}
1377 all_config_minus = {}
1378 all_config_change = {}
1379 tbase = self._base_config[target]
1380 tconfig = config[target]
1381 lines = []
Simon Glassb464f8e2016-11-13 14:25:53 -07001382 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001383 if not tconfig.config[name]:
1384 continue
1385 config_plus = {}
1386 config_minus = {}
1387 config_change = {}
1388 base = tbase.config[name]
Simon Glassc05aa032019-10-31 07:42:53 -06001389 for key, value in tconfig.config[name].items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001390 if key not in base:
1391 config_plus[key] = value
1392 all_config_plus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001393 for key, value in base.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001394 if key not in tconfig.config[name]:
1395 config_minus[key] = value
1396 all_config_minus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001397 for key, value in base.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001398 new_value = tconfig.config.get(key)
1399 if new_value and value != new_value:
1400 desc = '%s -> %s' % (value, new_value)
1401 config_change[key] = desc
1402 all_config_change[key] = desc
1403
1404 arch_config_plus[arch][name].update(config_plus)
1405 arch_config_minus[arch][name].update(config_minus)
1406 arch_config_change[arch][name].update(config_change)
1407
1408 _AddConfig(lines, name, config_plus, config_minus,
1409 config_change)
1410 _AddConfig(lines, 'all', all_config_plus, all_config_minus,
1411 all_config_change)
1412 summary[target] = '\n'.join(lines)
1413
1414 lines_by_target = {}
Simon Glassc05aa032019-10-31 07:42:53 -06001415 for target, lines in summary.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001416 if lines in lines_by_target:
1417 lines_by_target[lines].append(target)
1418 else:
1419 lines_by_target[lines] = [target]
1420
1421 for arch in arch_list:
1422 lines = []
1423 all_plus = {}
1424 all_minus = {}
1425 all_change = {}
Simon Glassb464f8e2016-11-13 14:25:53 -07001426 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001427 all_plus.update(arch_config_plus[arch][name])
1428 all_minus.update(arch_config_minus[arch][name])
1429 all_change.update(arch_config_change[arch][name])
1430 _AddConfig(lines, name, arch_config_plus[arch][name],
1431 arch_config_minus[arch][name],
1432 arch_config_change[arch][name])
1433 _AddConfig(lines, 'all', all_plus, all_minus, all_change)
1434 #arch_summary[target] = '\n'.join(lines)
1435 if lines:
1436 Print('%s:' % arch)
1437 _OutputConfigInfo(lines)
1438
Simon Glassc05aa032019-10-31 07:42:53 -06001439 for lines, targets in lines_by_target.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001440 if not lines:
1441 continue
1442 Print('%s :' % ' '.join(sorted(targets)))
1443 _OutputConfigInfo(lines.split('\n'))
1444
Simon Glass843312d2015-02-05 22:06:15 -07001445
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001446 # Save our updated information for the next call to this function
1447 self._base_board_dict = board_dict
1448 self._base_err_lines = err_lines
Simon Glasse30965d2014-08-28 09:43:44 -06001449 self._base_warn_lines = warn_lines
1450 self._base_err_line_boards = err_line_boards
1451 self._base_warn_line_boards = warn_line_boards
Simon Glass843312d2015-02-05 22:06:15 -07001452 self._base_config = config
Alex Kiernan48ae4122018-05-31 04:48:34 +00001453 self._base_environment = environment
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001454
1455 # Get a list of boards that did not get built, if needed
1456 not_built = []
1457 for board in board_selected:
1458 if not board in board_dict:
1459 not_built.append(board)
1460 if not_built:
Simon Glass4653a882014-09-05 19:00:07 -06001461 Print("Boards not built (%d): %s" % (len(not_built),
1462 ', '.join(not_built)))
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001463
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001464 def ProduceResultSummary(self, commit_upto, commits, board_selected):
Simon Glasse30965d2014-08-28 09:43:44 -06001465 (board_dict, err_lines, err_line_boards, warn_lines,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001466 warn_line_boards, config, environment) = self.GetResultSummary(
Simon Glassed966652014-08-28 09:43:43 -06001467 board_selected, commit_upto,
Simon Glass843312d2015-02-05 22:06:15 -07001468 read_func_sizes=self._show_bloat,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001469 read_config=self._show_config,
1470 read_environment=self._show_environment)
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001471 if commits:
1472 msg = '%02d: %s' % (commit_upto + 1,
1473 commits[commit_upto].subject)
Simon Glass4653a882014-09-05 19:00:07 -06001474 Print(msg, colour=self.col.BLUE)
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001475 self.PrintResultSummary(board_selected, board_dict,
Simon Glassed966652014-08-28 09:43:43 -06001476 err_lines if self._show_errors else [], err_line_boards,
Simon Glasse30965d2014-08-28 09:43:44 -06001477 warn_lines if self._show_errors else [], warn_line_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001478 config, environment, self._show_sizes, self._show_detail,
1479 self._show_bloat, self._show_config, self._show_environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001480
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001481 def ShowSummary(self, commits, board_selected):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001482 """Show a build summary for U-Boot for a given board list.
1483
1484 Reset the result summary, then repeatedly call GetResultSummary on
1485 each commit's results, then display the differences we see.
1486
1487 Args:
1488 commit: Commit objects to summarise
1489 board_selected: Dict containing boards to summarise
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001490 """
Simon Glassfea58582014-08-09 15:32:59 -06001491 self.commit_count = len(commits) if commits else 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001492 self.commits = commits
1493 self.ResetResultSummary(board_selected)
Simon Glass28370c12014-08-09 15:33:06 -06001494 self._error_lines = 0
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001495
1496 for commit_upto in range(0, self.commit_count, self._step):
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001497 self.ProduceResultSummary(commit_upto, commits, board_selected)
Simon Glass28370c12014-08-09 15:33:06 -06001498 if not self._error_lines:
Simon Glass4653a882014-09-05 19:00:07 -06001499 Print('(no errors to report)', colour=self.col.GREEN)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001500
1501
1502 def SetupBuild(self, board_selected, commits):
1503 """Set up ready to start a build.
1504
1505 Args:
1506 board_selected: Selected boards to build
1507 commits: Selected commits to build
1508 """
1509 # First work out how many commits we will build
Simon Glassc05aa032019-10-31 07:42:53 -06001510 count = (self.commit_count + self._step - 1) // self._step
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001511 self.count = len(board_selected) * count
1512 self.upto = self.warned = self.fail = 0
1513 self._timestamps = collections.deque()
1514
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001515 def GetThreadDir(self, thread_num):
1516 """Get the directory path to the working dir for a thread.
1517
1518 Args:
1519 thread_num: Number of thread to check.
1520 """
Simon Glassd829f122020-03-18 09:42:42 -06001521 if self.work_in_output:
1522 return self._working_dir
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001523 return os.path.join(self._working_dir, '%02d' % thread_num)
1524
Simon Glassfea58582014-08-09 15:32:59 -06001525 def _PrepareThread(self, thread_num, setup_git):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001526 """Prepare the working directory for a thread.
1527
1528 This clones or fetches the repo into the thread's work directory.
1529
1530 Args:
1531 thread_num: Thread number (0, 1, ...)
Simon Glassfea58582014-08-09 15:32:59 -06001532 setup_git: True to set up a git repo clone
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001533 """
1534 thread_dir = self.GetThreadDir(thread_num)
Simon Glass190064b2014-08-09 15:33:00 -06001535 builderthread.Mkdir(thread_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001536 git_dir = os.path.join(thread_dir, '.git')
1537
1538 # Clone the repo if it doesn't already exist
1539 # TODO(sjg@chromium): Perhaps some git hackery to symlink instead, so
1540 # we have a private index but uses the origin repo's contents?
Simon Glassfea58582014-08-09 15:32:59 -06001541 if setup_git and self.git_dir:
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001542 src_dir = os.path.abspath(self.git_dir)
1543 if os.path.exists(git_dir):
Simon Glass212c0b82020-04-09 15:08:43 -06001544 Print('\rFetching repo for thread %d' % thread_num,
1545 newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001546 gitutil.Fetch(git_dir, thread_dir)
Simon Glass212c0b82020-04-09 15:08:43 -06001547 terminal.PrintClear()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001548 else:
Simon Glass21f0eb32016-09-18 16:48:31 -06001549 Print('\rCloning repo for thread %d' % thread_num,
1550 newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001551 gitutil.Clone(src_dir, thread_dir)
Simon Glass102969b2020-04-09 15:08:42 -06001552 terminal.PrintClear()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001553
Simon Glassfea58582014-08-09 15:32:59 -06001554 def _PrepareWorkingSpace(self, max_threads, setup_git):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001555 """Prepare the working directory for use.
1556
1557 Set up the git repo for each thread.
1558
1559 Args:
1560 max_threads: Maximum number of threads we expect to need.
Simon Glassfea58582014-08-09 15:32:59 -06001561 setup_git: True to set up a git repo clone
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001562 """
Simon Glass190064b2014-08-09 15:33:00 -06001563 builderthread.Mkdir(self._working_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001564 for thread in range(max_threads):
Simon Glassfea58582014-08-09 15:32:59 -06001565 self._PrepareThread(thread, setup_git)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001566
Simon Glass925f6ad2020-03-18 09:42:45 -06001567 def _GetOutputSpaceRemovals(self):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001568 """Get the output directories ready to receive files.
1569
Simon Glass925f6ad2020-03-18 09:42:45 -06001570 Figure out what needs to be deleted in the output directory before it
1571 can be used. We only delete old buildman directories which have the
1572 expected name pattern. See _GetOutputDir().
1573
1574 Returns:
1575 List of full paths of directories to remove
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001576 """
Simon Glass1a915672014-12-01 17:33:53 -07001577 if not self.commits:
1578 return
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001579 dir_list = []
1580 for commit_upto in range(self.commit_count):
1581 dir_list.append(self._GetOutputDir(commit_upto))
1582
Simon Glassb222abe2016-09-18 16:48:32 -06001583 to_remove = []
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001584 for dirname in glob.glob(os.path.join(self.base_dir, '*')):
1585 if dirname not in dir_list:
Simon Glass925f6ad2020-03-18 09:42:45 -06001586 leaf = dirname[len(self.base_dir) + 1:]
1587 m = re.match('[0-9]+_of_[0-9]+_g[0-9a-f]+_.*', leaf)
1588 if m:
1589 to_remove.append(dirname)
1590 return to_remove
1591
1592 def _PrepareOutputSpace(self):
1593 """Get the output directories ready to receive files.
1594
1595 We delete any output directories which look like ones we need to
1596 create. Having left over directories is confusing when the user wants
1597 to check the output manually.
1598 """
1599 to_remove = self._GetOutputSpaceRemovals()
Simon Glassb222abe2016-09-18 16:48:32 -06001600 if to_remove:
Simon Glassb2d89bc2020-03-18 09:42:46 -06001601 Print('Removing %d old build directories...' % len(to_remove),
Simon Glassb222abe2016-09-18 16:48:32 -06001602 newline=False)
1603 for dirname in to_remove:
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001604 shutil.rmtree(dirname)
Simon Glass212c0b82020-04-09 15:08:43 -06001605 terminal.PrintClear()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001606
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001607 def BuildBoards(self, commits, board_selected, keep_outputs, verbose):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001608 """Build all commits for a list of boards
1609
1610 Args:
1611 commits: List of commits to be build, each a Commit object
1612 boards_selected: Dict of selected boards, key is target name,
1613 value is Board object
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001614 keep_outputs: True to save build output files
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001615 verbose: Display build results as they are completed
Simon Glass2c3deb92014-08-28 09:43:39 -06001616 Returns:
1617 Tuple containing:
1618 - number of boards that failed to build
1619 - number of boards that issued warnings
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001620 """
Simon Glassfea58582014-08-09 15:32:59 -06001621 self.commit_count = len(commits) if commits else 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001622 self.commits = commits
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001623 self._verbose = verbose
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001624
1625 self.ResetResultSummary(board_selected)
Thierry Redingf3d015c2014-08-19 10:22:39 +02001626 builderthread.Mkdir(self.base_dir, parents = True)
Simon Glassfea58582014-08-09 15:32:59 -06001627 self._PrepareWorkingSpace(min(self.num_threads, len(board_selected)),
1628 commits is not None)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001629 self._PrepareOutputSpace()
Simon Glass745b3952016-09-18 16:48:33 -06001630 Print('\rStarting build...', newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001631 self.SetupBuild(board_selected, commits)
1632 self.ProcessResult(None)
1633
1634 # Create jobs to build all commits for each board
Simon Glassc05aa032019-10-31 07:42:53 -06001635 for brd in board_selected.values():
Simon Glass190064b2014-08-09 15:33:00 -06001636 job = builderthread.BuilderJob()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001637 job.board = brd
1638 job.commits = commits
1639 job.keep_outputs = keep_outputs
Simon Glassd829f122020-03-18 09:42:42 -06001640 job.work_in_output = self.work_in_output
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001641 job.step = self._step
1642 self.queue.put(job)
1643
Simon Glassd436e382016-09-18 16:48:35 -06001644 term = threading.Thread(target=self.queue.join)
1645 term.setDaemon(True)
1646 term.start()
1647 while term.isAlive():
1648 term.join(100)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001649
1650 # Wait until we have processed all output
1651 self.out_queue.join()
Simon Glass4653a882014-09-05 19:00:07 -06001652 Print()
Simon Glass7b33f212020-04-09 15:08:47 -06001653
1654 msg = 'Completed: %d total built' % self.count
1655 if self.already_done:
1656 msg += ' (%d previously' % self.already_done
1657 if self.already_done != self.count:
1658 msg += ', %d newly' % (self.count - self.already_done)
1659 msg += ')'
1660 duration = datetime.now() - self._start_time
1661 if duration > timedelta(microseconds=1000000):
1662 if duration.microseconds >= 500000:
1663 duration = duration + timedelta(seconds=1)
1664 duration = duration - timedelta(microseconds=duration.microseconds)
1665 msg += ', duration %s' % duration
1666 Print(msg)
1667
Simon Glass2c3deb92014-08-28 09:43:39 -06001668 return (self.fail, self.warned)