blob: 122f0d140654d80da94749d421e12cee3eefcfe7 [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 Glass0ede00f2020-04-17 18:09:02 -060020from buildman import builderthread
21from buildman import toolchain
Simon Glassbf776672020-04-17 18:09:04 -060022from patman import command
23from patman import gitutil
24from patman import terminal
25from patman.terminal import Print
Simon Glassfc3fe1c2013-04-03 11:07:16 +000026
Simon Glass7bf83a52021-10-19 21:43:24 -060027# This indicates an new int or hex Kconfig property with no default
28# It hangs the build since the 'conf' tool cannot proceed without valid input.
29#
30# We get a repeat sequence of something like this:
31# >>
32# Break things (BREAK_ME) [] (NEW)
33# Error in reading or end of file.
34# <<
35# which indicates that BREAK_ME has an empty default
36RE_NO_DEFAULT = re.compile(b'\((\w+)\) \[] \(NEW\)')
37
Simon Glassfc3fe1c2013-04-03 11:07:16 +000038"""
39Theory of Operation
40
41Please see README for user documentation, and you should be familiar with
42that before trying to make sense of this.
43
44Buildman works by keeping the machine as busy as possible, building different
45commits for different boards on multiple CPUs at once.
46
47The source repo (self.git_dir) contains all the commits to be built. Each
48thread works on a single board at a time. It checks out the first commit,
49configures it for that board, then builds it. Then it checks out the next
50commit and builds it (typically without re-configuring). When it runs out
51of commits, it gets another job from the builder and starts again with that
52board.
53
54Clearly the builder threads could work either way - they could check out a
55commit and then built it for all boards. Using separate directories for each
56commit/board pair they could leave their build product around afterwards
57also.
58
59The intent behind building a single board for multiple commits, is to make
60use of incremental builds. Since each commit is built incrementally from
61the previous one, builds are faster. Reconfiguring for a different board
62removes all intermediate object files.
63
64Many threads can be working at once, but each has its own working directory.
65When a thread finishes a build, it puts the output files into a result
66directory.
67
68The base directory used by buildman is normally '../<branch>', i.e.
69a directory higher than the source repository and named after the branch
70being built.
71
72Within the base directory, we have one subdirectory for each commit. Within
73that is one subdirectory for each board. Within that is the build output for
74that commit/board combination.
75
76Buildman also create working directories for each thread, in a .bm-work/
77subdirectory in the base dir.
78
79As an example, say we are building branch 'us-net' for boards 'sandbox' and
80'seaboard', and say that us-net has two commits. We will have directories
81like this:
82
83us-net/ base directory
Ovidiu Panait7664b032020-05-15 09:30:12 +030084 01_g4ed4ebc_net--Add-tftp-speed-/
Simon Glassfc3fe1c2013-04-03 11:07:16 +000085 sandbox/
86 u-boot.bin
87 seaboard/
88 u-boot.bin
Ovidiu Panait7664b032020-05-15 09:30:12 +030089 02_g4ed4ebc_net--Check-tftp-comp/
Simon Glassfc3fe1c2013-04-03 11:07:16 +000090 sandbox/
91 u-boot.bin
92 seaboard/
93 u-boot.bin
94 .bm-work/
95 00/ working directory for thread 0 (contains source checkout)
96 build/ build output
97 01/ working directory for thread 1
98 build/ build output
99 ...
100u-boot/ source directory
101 .git/ repository
102"""
103
Simon Glass35d696d2020-04-09 15:08:36 -0600104"""Holds information about a particular error line we are outputing
105
106 char: Character representation: '+': error, '-': fixed error, 'w+': warning,
107 'w-' = fixed warning
108 boards: List of Board objects which have line in the error/warning output
109 errline: The text of the error line
110"""
111ErrLine = collections.namedtuple('ErrLine', 'char,boards,errline')
112
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000113# Possible build outcomes
Simon Glassc05aa032019-10-31 07:42:53 -0600114OUTCOME_OK, OUTCOME_WARNING, OUTCOME_ERROR, OUTCOME_UNKNOWN = list(range(4))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000115
Simon Glass9a6d2e22017-04-12 18:23:26 -0600116# Translate a commit subject into a valid filename (and handle unicode)
Simon Glassc05aa032019-10-31 07:42:53 -0600117trans_valid_chars = str.maketrans('/: ', '---')
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000118
Simon Glassb464f8e2016-11-13 14:25:53 -0700119BASE_CONFIG_FILENAMES = [
120 'u-boot.cfg', 'u-boot-spl.cfg', 'u-boot-tpl.cfg'
121]
122
123EXTRA_CONFIG_FILENAMES = [
Simon Glass843312d2015-02-05 22:06:15 -0700124 '.config', '.config-spl', '.config-tpl',
125 'autoconf.mk', 'autoconf-spl.mk', 'autoconf-tpl.mk',
126 'autoconf.h', 'autoconf-spl.h','autoconf-tpl.h',
Simon Glass843312d2015-02-05 22:06:15 -0700127]
128
Simon Glass8270e3c2015-08-25 21:52:14 -0600129class Config:
130 """Holds information about configuration settings for a board."""
Simon Glassb464f8e2016-11-13 14:25:53 -0700131 def __init__(self, config_filename, target):
Simon Glass8270e3c2015-08-25 21:52:14 -0600132 self.target = target
133 self.config = {}
Simon Glassb464f8e2016-11-13 14:25:53 -0700134 for fname in config_filename:
Simon Glass8270e3c2015-08-25 21:52:14 -0600135 self.config[fname] = {}
136
137 def Add(self, fname, key, value):
138 self.config[fname][key] = value
139
140 def __hash__(self):
141 val = 0
142 for fname in self.config:
Simon Glassc05aa032019-10-31 07:42:53 -0600143 for key, value in self.config[fname].items():
144 print(key, value)
Simon Glass8270e3c2015-08-25 21:52:14 -0600145 val = val ^ hash(key) & hash(value)
146 return val
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000147
Alex Kiernan48ae4122018-05-31 04:48:34 +0000148class Environment:
149 """Holds information about environment variables for a board."""
150 def __init__(self, target):
151 self.target = target
152 self.environment = {}
153
154 def Add(self, key, value):
155 self.environment[key] = value
156
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000157class Builder:
158 """Class for building U-Boot for a particular commit.
159
160 Public members: (many should ->private)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000161 already_done: Number of builds already completed
162 base_dir: Base directory to use for builder
163 checkout: True to check out source, False to skip that step.
164 This is used for testing.
165 col: terminal.Color() object
166 count: Number of commits to build
167 do_make: Method to call to invoke Make
168 fail: Number of builds that failed due to error
169 force_build: Force building even if a build already exists
170 force_config_on_failure: If a commit fails for a board, disable
171 incremental building for the next commit we build for that
172 board, so that we will see all warnings/errors again.
Simon Glass4266dc22014-07-13 12:22:31 -0600173 force_build_failures: If a previously-built build (i.e. built on
174 a previous run of buildman) is marked as failed, rebuild it.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000175 git_dir: Git directory containing source repository
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000176 num_jobs: Number of jobs to run at once (passed to make as -j)
177 num_threads: Number of builder threads to run
178 out_queue: Queue of results to process
179 re_make_err: Compiled regular expression for ignore_lines
180 queue: Queue of jobs to run
181 threads: List of active threads
182 toolchains: Toolchains object to use for building
183 upto: Current commit number we are building (0.count-1)
184 warned: Number of builds that produced at least one warning
Simon Glass97e91522014-07-14 17:51:02 -0600185 force_reconfig: Reconfigure U-Boot on each comiit. This disables
186 incremental building, where buildman reconfigures on the first
187 commit for a baord, and then just does an incremental build for
188 the following commits. In fact buildman will reconfigure and
189 retry for any failing commits, so generally the only effect of
190 this option is to slow things down.
Simon Glass189a4962014-07-14 17:51:03 -0600191 in_tree: Build U-Boot in-tree instead of specifying an output
192 directory separate from the source code. This option is really
193 only useful for testing in-tree builds.
Simon Glassd829f122020-03-18 09:42:42 -0600194 work_in_output: Use the output directory as the work directory and
195 don't write to a separate output directory.
Simon Glass8116c782021-04-11 16:27:27 +1200196 thread_exceptions: List of exceptions raised by thread jobs
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000197
198 Private members:
199 _base_board_dict: Last-summarised Dict of boards
200 _base_err_lines: Last-summarised list of errors
Simon Glasse30965d2014-08-28 09:43:44 -0600201 _base_warn_lines: Last-summarised list of warnings
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000202 _build_period_us: Time taken for a single build (float object).
203 _complete_delay: Expected delay until completion (timedelta)
204 _next_delay_update: Next time we plan to display a progress update
205 (datatime)
206 _show_unknown: Show unknown boards (those not built) in summary
Simon Glass7b33f212020-04-09 15:08:47 -0600207 _start_time: Start time for the build
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000208 _timestamps: List of timestamps for the completion of the last
209 last _timestamp_count builds. Each is a datetime object.
210 _timestamp_count: Number of timestamps to keep in our list.
211 _working_dir: Base working directory containing all threads
Simon Glassb82492b2021-01-30 22:17:46 -0700212 _single_builder: BuilderThread object for the singer builder, if
213 threading is not being used
Simon Glass7bf83a52021-10-19 21:43:24 -0600214 _terminated: Thread was terminated due to an error
215 _restarting_config: True if 'Restart config' is detected in output
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000216 """
217 class Outcome:
218 """Records a build outcome for a single make invocation
219
220 Public Members:
221 rc: Outcome value (OUTCOME_...)
222 err_lines: List of error lines or [] if none
223 sizes: Dictionary of image size information, keyed by filename
224 - Each value is itself a dictionary containing
225 values for 'text', 'data' and 'bss', being the integer
226 size in bytes of each section.
227 func_sizes: Dictionary keyed by filename - e.g. 'u-boot'. Each
228 value is itself a dictionary:
229 key: function name
230 value: Size of function in bytes
Simon Glass843312d2015-02-05 22:06:15 -0700231 config: Dictionary keyed by filename - e.g. '.config'. Each
232 value is itself a dictionary:
233 key: config name
234 value: config value
Alex Kiernan48ae4122018-05-31 04:48:34 +0000235 environment: Dictionary keyed by environment variable, Each
236 value is the value of environment variable.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000237 """
Alex Kiernan48ae4122018-05-31 04:48:34 +0000238 def __init__(self, rc, err_lines, sizes, func_sizes, config,
239 environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000240 self.rc = rc
241 self.err_lines = err_lines
242 self.sizes = sizes
243 self.func_sizes = func_sizes
Simon Glass843312d2015-02-05 22:06:15 -0700244 self.config = config
Alex Kiernan48ae4122018-05-31 04:48:34 +0000245 self.environment = environment
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000246
247 def __init__(self, toolchains, base_dir, git_dir, num_threads, num_jobs,
Simon Glass5971ab52014-12-01 17:33:55 -0700248 gnu_make='make', checkout=True, show_unknown=True, step=1,
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600249 no_subdirs=False, full_path=False, verbose_build=False,
Simon Glasseb70a2c2020-04-09 15:08:51 -0600250 mrproper=False, per_board_out_dir=False,
Daniel Schwierzeck2371d1b2018-01-26 16:31:05 +0100251 config_only=False, squash_config_y=False,
Simon Glass8116c782021-04-11 16:27:27 +1200252 warnings_as_errors=False, work_in_output=False,
253 test_thread_exceptions=False):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000254 """Create a new Builder object
255
256 Args:
257 toolchains: Toolchains object to use for building
258 base_dir: Base directory to use for builder
259 git_dir: Git directory containing source repository
260 num_threads: Number of builder threads to run
261 num_jobs: Number of jobs to run at once (passed to make as -j)
Masahiro Yamada99796922014-07-22 11:19:09 +0900262 gnu_make: the command name of GNU Make.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000263 checkout: True to check out source, False to skip that step.
264 This is used for testing.
265 show_unknown: Show unknown boards (those not built) in summary
266 step: 1 to process every commit, n to process every nth commit
Simon Glassbb1501f2014-12-01 17:34:00 -0700267 no_subdirs: Don't create subdirectories when building current
268 source for a single board
269 full_path: Return the full path in CROSS_COMPILE and don't set
270 PATH
Simon Glassd2ce6582014-12-01 17:34:07 -0700271 verbose_build: Run build with V=1 and don't use 'make -s'
Simon Glasseb70a2c2020-04-09 15:08:51 -0600272 mrproper: Always run 'make mrproper' when configuring
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600273 per_board_out_dir: Build in a separate persistent directory per
274 board rather than a thread-specific directory
Simon Glassb50113f2016-11-13 14:25:51 -0700275 config_only: Only configure each build, don't build it
Simon Glassb464f8e2016-11-13 14:25:53 -0700276 squash_config_y: Convert CONFIG options with the value 'y' to '1'
Daniel Schwierzeck2371d1b2018-01-26 16:31:05 +0100277 warnings_as_errors: Treat all compiler warnings as errors
Simon Glassd829f122020-03-18 09:42:42 -0600278 work_in_output: Use the output directory as the work directory and
279 don't write to a separate output directory.
Simon Glass8116c782021-04-11 16:27:27 +1200280 test_thread_exceptions: Uses for tests only, True to make the
281 threads raise an exception instead of reporting their result.
282 This simulates a failure in the code somewhere
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000283 """
284 self.toolchains = toolchains
285 self.base_dir = base_dir
Simon Glassd829f122020-03-18 09:42:42 -0600286 if work_in_output:
287 self._working_dir = base_dir
288 else:
289 self._working_dir = os.path.join(base_dir, '.bm-work')
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000290 self.threads = []
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000291 self.do_make = self.Make
Masahiro Yamada99796922014-07-22 11:19:09 +0900292 self.gnu_make = gnu_make
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000293 self.checkout = checkout
294 self.num_threads = num_threads
295 self.num_jobs = num_jobs
296 self.already_done = 0
297 self.force_build = False
298 self.git_dir = git_dir
299 self._show_unknown = show_unknown
300 self._timestamp_count = 10
301 self._build_period_us = None
302 self._complete_delay = None
303 self._next_delay_update = datetime.now()
Simon Glass7b33f212020-04-09 15:08:47 -0600304 self._start_time = datetime.now()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000305 self.force_config_on_failure = True
Simon Glass4266dc22014-07-13 12:22:31 -0600306 self.force_build_failures = False
Simon Glass97e91522014-07-14 17:51:02 -0600307 self.force_reconfig = False
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000308 self._step = step
Simon Glass189a4962014-07-14 17:51:03 -0600309 self.in_tree = False
Simon Glass28370c12014-08-09 15:33:06 -0600310 self._error_lines = 0
Simon Glass5971ab52014-12-01 17:33:55 -0700311 self.no_subdirs = no_subdirs
Simon Glassbb1501f2014-12-01 17:34:00 -0700312 self.full_path = full_path
Simon Glassd2ce6582014-12-01 17:34:07 -0700313 self.verbose_build = verbose_build
Simon Glassb50113f2016-11-13 14:25:51 -0700314 self.config_only = config_only
Simon Glassb464f8e2016-11-13 14:25:53 -0700315 self.squash_config_y = squash_config_y
316 self.config_filenames = BASE_CONFIG_FILENAMES
Simon Glassd829f122020-03-18 09:42:42 -0600317 self.work_in_output = work_in_output
Simon Glassb464f8e2016-11-13 14:25:53 -0700318 if not self.squash_config_y:
319 self.config_filenames += EXTRA_CONFIG_FILENAMES
Simon Glass7bf83a52021-10-19 21:43:24 -0600320 self._terminated = False
321 self._restarting_config = False
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000322
Daniel Schwierzeck2371d1b2018-01-26 16:31:05 +0100323 self.warnings_as_errors = warnings_as_errors
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000324 self.col = terminal.Color()
325
Simon Glasse30965d2014-08-28 09:43:44 -0600326 self._re_function = re.compile('(.*): In function.*')
327 self._re_files = re.compile('In file included from.*')
328 self._re_warning = re.compile('(.*):(\d*):(\d*): warning: .*')
Simon Glass2d483332018-11-06 16:02:11 -0700329 self._re_dtb_warning = re.compile('(.*): Warning .*')
Simon Glasse30965d2014-08-28 09:43:44 -0600330 self._re_note = re.compile('(.*):(\d*):(\d*): note: this is the location of the previous.*')
Simon Glass113a8a52020-04-09 15:08:53 -0600331 self._re_migration_warning = re.compile(r'^={21} WARNING ={22}\n.*\n=+\n',
332 re.MULTILINE | re.DOTALL)
Simon Glasse30965d2014-08-28 09:43:44 -0600333
Simon Glass8116c782021-04-11 16:27:27 +1200334 self.thread_exceptions = []
335 self.test_thread_exceptions = test_thread_exceptions
Simon Glassb82492b2021-01-30 22:17:46 -0700336 if self.num_threads:
337 self._single_builder = None
338 self.queue = queue.Queue()
339 self.out_queue = queue.Queue()
340 for i in range(self.num_threads):
Simon Glass8116c782021-04-11 16:27:27 +1200341 t = builderthread.BuilderThread(
342 self, i, mrproper, per_board_out_dir,
343 test_exception=test_thread_exceptions)
Simon Glassb82492b2021-01-30 22:17:46 -0700344 t.setDaemon(True)
345 t.start()
346 self.threads.append(t)
347
348 t = builderthread.ResultThread(self)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000349 t.setDaemon(True)
350 t.start()
351 self.threads.append(t)
Simon Glassb82492b2021-01-30 22:17:46 -0700352 else:
353 self._single_builder = builderthread.BuilderThread(
354 self, -1, mrproper, per_board_out_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000355
356 ignore_lines = ['(make.*Waiting for unfinished)', '(Segmentation fault)']
357 self.re_make_err = re.compile('|'.join(ignore_lines))
358
Simon Glass2f256642016-09-18 16:48:37 -0600359 # Handle existing graceful with SIGINT / Ctrl-C
360 signal.signal(signal.SIGINT, self.signal_handler)
361
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000362 def __del__(self):
363 """Get rid of all threads created by the builder"""
364 for t in self.threads:
365 del t
366
Simon Glass2f256642016-09-18 16:48:37 -0600367 def signal_handler(self, signal, frame):
368 sys.exit(1)
369
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600370 def SetDisplayOptions(self, show_errors=False, show_sizes=False,
Simon Glassed966652014-08-28 09:43:43 -0600371 show_detail=False, show_bloat=False,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000372 list_error_boards=False, show_config=False,
Simon Glass113a8a52020-04-09 15:08:53 -0600373 show_environment=False, filter_dtb_warnings=False,
374 filter_migration_warnings=False):
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600375 """Setup display options for the builder.
376
Simon Glass174592b2020-04-09 15:08:52 -0600377 Args:
378 show_errors: True to show summarised error/warning info
379 show_sizes: Show size deltas
380 show_detail: Show size delta detail for each board if show_sizes
381 show_bloat: Show detail for each function
382 list_error_boards: Show the boards which caused each error/warning
383 show_config: Show config deltas
384 show_environment: Show environment deltas
385 filter_dtb_warnings: Filter out any warnings from the device-tree
386 compiler
Simon Glass113a8a52020-04-09 15:08:53 -0600387 filter_migration_warnings: Filter out any warnings about migrating
388 a board to driver model
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600389 """
390 self._show_errors = show_errors
391 self._show_sizes = show_sizes
392 self._show_detail = show_detail
393 self._show_bloat = show_bloat
Simon Glassed966652014-08-28 09:43:43 -0600394 self._list_error_boards = list_error_boards
Simon Glass843312d2015-02-05 22:06:15 -0700395 self._show_config = show_config
Alex Kiernan48ae4122018-05-31 04:48:34 +0000396 self._show_environment = show_environment
Simon Glass174592b2020-04-09 15:08:52 -0600397 self._filter_dtb_warnings = filter_dtb_warnings
Simon Glass113a8a52020-04-09 15:08:53 -0600398 self._filter_migration_warnings = filter_migration_warnings
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600399
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000400 def _AddTimestamp(self):
401 """Add a new timestamp to the list and record the build period.
402
403 The build period is the length of time taken to perform a single
404 build (one board, one commit).
405 """
406 now = datetime.now()
407 self._timestamps.append(now)
408 count = len(self._timestamps)
409 delta = self._timestamps[-1] - self._timestamps[0]
410 seconds = delta.total_seconds()
411
412 # If we have enough data, estimate build period (time taken for a
413 # single build) and therefore completion time.
414 if count > 1 and self._next_delay_update < now:
415 self._next_delay_update = now + timedelta(seconds=2)
416 if seconds > 0:
417 self._build_period = float(seconds) / count
418 todo = self.count - self.upto
419 self._complete_delay = timedelta(microseconds=
420 self._build_period * todo * 1000000)
421 # Round it
422 self._complete_delay -= timedelta(
423 microseconds=self._complete_delay.microseconds)
424
425 if seconds > 60:
426 self._timestamps.popleft()
427 count -= 1
428
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000429 def SelectCommit(self, commit, checkout=True):
430 """Checkout the selected commit for this build
431 """
432 self.commit = commit
433 if checkout and self.checkout:
434 gitutil.Checkout(commit.hash)
435
436 def Make(self, commit, brd, stage, cwd, *args, **kwargs):
437 """Run make
438
439 Args:
440 commit: Commit object that is being built
441 brd: Board object that is being built
Roger Meierfd18a892014-08-20 22:10:29 +0200442 stage: Stage that we are at (mrproper, config, build)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000443 cwd: Directory where make should be run
444 args: Arguments to pass to make
445 kwargs: Arguments to pass to command.RunPipe()
446 """
Simon Glass7bf83a52021-10-19 21:43:24 -0600447
448 def check_output(stream, data):
449 if b'Restart config' in data:
450 self._restarting_config = True
451
452 # If we see 'Restart config' following by multiple errors
453 if self._restarting_config:
454 m = RE_NO_DEFAULT.findall(data)
455
456 # Number of occurences of each Kconfig item
457 multiple = [m.count(val) for val in set(m)]
458
459 # If any of them occur more than once, we have a loop
460 if [val for val in multiple if val > 1]:
461 self._terminated = True
462 return True
463 return False
464
465 self._restarting_config = False
466 self._terminated = False
Masahiro Yamada99796922014-07-22 11:19:09 +0900467 cmd = [self.gnu_make] + list(args)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000468 result = command.RunPipe([cmd], capture=True, capture_stderr=True,
Simon Glass7bf83a52021-10-19 21:43:24 -0600469 cwd=cwd, raise_on_error=False, infile='/dev/null',
470 output_func=check_output, **kwargs)
471
472 if self._terminated:
473 # Try to be helpful
474 result.stderr += '(** did you define an int/hex Kconfig with no default? **)'
475
Simon Glass40f11fc2015-02-05 22:06:12 -0700476 if self.verbose_build:
477 result.stdout = '%s\n' % (' '.join(cmd)) + result.stdout
478 result.combined = '%s\n' % (' '.join(cmd)) + result.combined
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000479 return result
480
481 def ProcessResult(self, result):
482 """Process the result of a build, showing progress information
483
484 Args:
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600485 result: A CommandResult object, which indicates the result for
486 a single build
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000487 """
488 col = terminal.Color()
489 if result:
490 target = result.brd.target
491
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000492 self.upto += 1
493 if result.return_code != 0:
494 self.fail += 1
495 elif result.stderr:
496 self.warned += 1
497 if result.already_done:
498 self.already_done += 1
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600499 if self._verbose:
Simon Glass102969b2020-04-09 15:08:42 -0600500 terminal.PrintClear()
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600501 boards_selected = {target : result.brd}
502 self.ResetResultSummary(boards_selected)
503 self.ProduceResultSummary(result.commit_upto, self.commits,
504 boards_selected)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000505 else:
506 target = '(starting)'
507
508 # Display separate counts for ok, warned and fail
509 ok = self.upto - self.warned - self.fail
510 line = '\r' + self.col.Color(self.col.GREEN, '%5d' % ok)
511 line += self.col.Color(self.col.YELLOW, '%5d' % self.warned)
512 line += self.col.Color(self.col.RED, '%5d' % self.fail)
513
Simon Glass6eb76ca2020-04-09 15:08:45 -0600514 line += ' /%-5d ' % self.count
515 remaining = self.count - self.upto
516 if remaining:
517 line += self.col.Color(self.col.MAGENTA, ' -%-5d ' % remaining)
518 else:
519 line += ' ' * 8
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000520
521 # Add our current completion time estimate
522 self._AddTimestamp()
523 if self._complete_delay:
Simon Glass6eb76ca2020-04-09 15:08:45 -0600524 line += '%s : ' % self._complete_delay
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000525
Simon Glass6eb76ca2020-04-09 15:08:45 -0600526 line += target
Simon Glass102969b2020-04-09 15:08:42 -0600527 terminal.PrintClear()
Simon Glass95ed0a22020-04-09 15:08:46 -0600528 Print(line, newline=False, limit_to_line=True)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000529
530 def _GetOutputDir(self, commit_upto):
531 """Get the name of the output directory for a commit number
532
533 The output directory is typically .../<branch>/<commit>.
534
535 Args:
536 commit_upto: Commit number to use (0..self.count-1)
537 """
Simon Glass60b285f2020-04-17 17:51:34 -0600538 if self.work_in_output:
539 return self._working_dir
540
Simon Glass5971ab52014-12-01 17:33:55 -0700541 commit_dir = None
Simon Glassfea58582014-08-09 15:32:59 -0600542 if self.commits:
543 commit = self.commits[commit_upto]
544 subject = commit.subject.translate(trans_valid_chars)
Simon Glass925f6ad2020-03-18 09:42:45 -0600545 # See _GetOutputSpaceRemovals() which parses this name
Ovidiu Panait7664b032020-05-15 09:30:12 +0300546 commit_dir = ('%02d_g%s_%s' % (commit_upto + 1,
547 commit.hash, subject[:20]))
Simon Glass5971ab52014-12-01 17:33:55 -0700548 elif not self.no_subdirs:
Simon Glassfea58582014-08-09 15:32:59 -0600549 commit_dir = 'current'
Simon Glass5971ab52014-12-01 17:33:55 -0700550 if not commit_dir:
551 return self.base_dir
552 return os.path.join(self.base_dir, commit_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000553
554 def GetBuildDir(self, commit_upto, target):
555 """Get the name of the build directory for a commit number
556
557 The build directory is typically .../<branch>/<commit>/<target>.
558
559 Args:
560 commit_upto: Commit number to use (0..self.count-1)
561 target: Target name
562 """
563 output_dir = self._GetOutputDir(commit_upto)
Simon Glass60b285f2020-04-17 17:51:34 -0600564 if self.work_in_output:
565 return output_dir
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000566 return os.path.join(output_dir, target)
567
568 def GetDoneFile(self, commit_upto, target):
569 """Get the name of the done file for a commit number
570
571 Args:
572 commit_upto: Commit number to use (0..self.count-1)
573 target: Target name
574 """
575 return os.path.join(self.GetBuildDir(commit_upto, target), 'done')
576
577 def GetSizesFile(self, commit_upto, target):
578 """Get the name of the sizes file for a commit number
579
580 Args:
581 commit_upto: Commit number to use (0..self.count-1)
582 target: Target name
583 """
584 return os.path.join(self.GetBuildDir(commit_upto, target), 'sizes')
585
586 def GetFuncSizesFile(self, commit_upto, target, elf_fname):
587 """Get the name of the funcsizes file for a commit number and ELF file
588
589 Args:
590 commit_upto: Commit number to use (0..self.count-1)
591 target: Target name
592 elf_fname: Filename of elf image
593 """
594 return os.path.join(self.GetBuildDir(commit_upto, target),
595 '%s.sizes' % elf_fname.replace('/', '-'))
596
597 def GetObjdumpFile(self, commit_upto, target, elf_fname):
598 """Get the name of the objdump file for a commit number and ELF file
599
600 Args:
601 commit_upto: Commit number to use (0..self.count-1)
602 target: Target name
603 elf_fname: Filename of elf image
604 """
605 return os.path.join(self.GetBuildDir(commit_upto, target),
606 '%s.objdump' % elf_fname.replace('/', '-'))
607
608 def GetErrFile(self, commit_upto, target):
609 """Get the name of the err file for a commit number
610
611 Args:
612 commit_upto: Commit number to use (0..self.count-1)
613 target: Target name
614 """
615 output_dir = self.GetBuildDir(commit_upto, target)
616 return os.path.join(output_dir, 'err')
617
618 def FilterErrors(self, lines):
619 """Filter out errors in which we have no interest
620
621 We should probably use map().
622
623 Args:
624 lines: List of error lines, each a string
625 Returns:
626 New list with only interesting lines included
627 """
628 out_lines = []
Simon Glass113a8a52020-04-09 15:08:53 -0600629 if self._filter_migration_warnings:
630 text = '\n'.join(lines)
631 text = self._re_migration_warning.sub('', text)
632 lines = text.splitlines()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000633 for line in lines:
Simon Glass174592b2020-04-09 15:08:52 -0600634 if self.re_make_err.search(line):
635 continue
636 if self._filter_dtb_warnings and self._re_dtb_warning.search(line):
637 continue
638 out_lines.append(line)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000639 return out_lines
640
641 def ReadFuncSizes(self, fname, fd):
642 """Read function sizes from the output of 'nm'
643
644 Args:
645 fd: File containing data to read
646 fname: Filename we are reading from (just for errors)
647
648 Returns:
649 Dictionary containing size of each function in bytes, indexed by
650 function name.
651 """
652 sym = {}
653 for line in fd.readlines():
654 try:
Tom Rinid08c38c2019-12-06 15:31:31 -0500655 if line.strip():
656 size, type, name = line[:-1].split()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000657 except:
Simon Glass4653a882014-09-05 19:00:07 -0600658 Print("Invalid line in file '%s': '%s'" % (fname, line[:-1]))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000659 continue
660 if type in 'tTdDbB':
661 # function names begin with '.' on 64-bit powerpc
662 if '.' in name[1:]:
663 name = 'static.' + name.split('.')[0]
664 sym[name] = sym.get(name, 0) + int(size, 16)
665 return sym
666
Simon Glass843312d2015-02-05 22:06:15 -0700667 def _ProcessConfig(self, fname):
668 """Read in a .config, autoconf.mk or autoconf.h file
669
670 This function handles all config file types. It ignores comments and
671 any #defines which don't start with CONFIG_.
672
673 Args:
674 fname: Filename to read
675
676 Returns:
677 Dictionary:
678 key: Config name (e.g. CONFIG_DM)
679 value: Config value (e.g. 1)
680 """
681 config = {}
682 if os.path.exists(fname):
683 with open(fname) as fd:
684 for line in fd:
685 line = line.strip()
686 if line.startswith('#define'):
687 values = line[8:].split(' ', 1)
688 if len(values) > 1:
689 key, value = values
690 else:
691 key = values[0]
Simon Glassb464f8e2016-11-13 14:25:53 -0700692 value = '1' if self.squash_config_y else ''
Simon Glass843312d2015-02-05 22:06:15 -0700693 if not key.startswith('CONFIG_'):
694 continue
695 elif not line or line[0] in ['#', '*', '/']:
696 continue
697 else:
698 key, value = line.split('=', 1)
Simon Glassb464f8e2016-11-13 14:25:53 -0700699 if self.squash_config_y and value == 'y':
700 value = '1'
Simon Glass843312d2015-02-05 22:06:15 -0700701 config[key] = value
702 return config
703
Alex Kiernan48ae4122018-05-31 04:48:34 +0000704 def _ProcessEnvironment(self, fname):
705 """Read in a uboot.env file
706
707 This function reads in environment variables from a file.
708
709 Args:
710 fname: Filename to read
711
712 Returns:
713 Dictionary:
714 key: environment variable (e.g. bootlimit)
715 value: value of environment variable (e.g. 1)
716 """
717 environment = {}
718 if os.path.exists(fname):
719 with open(fname) as fd:
720 for line in fd.read().split('\0'):
721 try:
722 key, value = line.split('=', 1)
723 environment[key] = value
724 except ValueError:
725 # ignore lines we can't parse
726 pass
727 return environment
728
Simon Glass843312d2015-02-05 22:06:15 -0700729 def GetBuildOutcome(self, commit_upto, target, read_func_sizes,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000730 read_config, read_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000731 """Work out the outcome of a build.
732
733 Args:
734 commit_upto: Commit number to check (0..n-1)
735 target: Target board to check
736 read_func_sizes: True to read function size information
Simon Glass843312d2015-02-05 22:06:15 -0700737 read_config: True to read .config and autoconf.h files
Alex Kiernan48ae4122018-05-31 04:48:34 +0000738 read_environment: True to read uboot.env files
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000739
740 Returns:
741 Outcome object
742 """
743 done_file = self.GetDoneFile(commit_upto, target)
744 sizes_file = self.GetSizesFile(commit_upto, target)
745 sizes = {}
746 func_sizes = {}
Simon Glass843312d2015-02-05 22:06:15 -0700747 config = {}
Alex Kiernan48ae4122018-05-31 04:48:34 +0000748 environment = {}
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000749 if os.path.exists(done_file):
750 with open(done_file, 'r') as fd:
Simon Glass347ea0b2019-04-26 19:02:23 -0600751 try:
752 return_code = int(fd.readline())
753 except ValueError:
754 # The file may be empty due to running out of disk space.
755 # Try a rebuild
756 return_code = 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000757 err_lines = []
758 err_file = self.GetErrFile(commit_upto, target)
759 if os.path.exists(err_file):
760 with open(err_file, 'r') as fd:
761 err_lines = self.FilterErrors(fd.readlines())
762
763 # Decide whether the build was ok, failed or created warnings
764 if return_code:
765 rc = OUTCOME_ERROR
766 elif len(err_lines):
767 rc = OUTCOME_WARNING
768 else:
769 rc = OUTCOME_OK
770
771 # Convert size information to our simple format
772 if os.path.exists(sizes_file):
773 with open(sizes_file, 'r') as fd:
774 for line in fd.readlines():
775 values = line.split()
776 rodata = 0
777 if len(values) > 6:
778 rodata = int(values[6], 16)
779 size_dict = {
780 'all' : int(values[0]) + int(values[1]) +
781 int(values[2]),
782 'text' : int(values[0]) - rodata,
783 'data' : int(values[1]),
784 'bss' : int(values[2]),
785 'rodata' : rodata,
786 }
787 sizes[values[5]] = size_dict
788
789 if read_func_sizes:
790 pattern = self.GetFuncSizesFile(commit_upto, target, '*')
791 for fname in glob.glob(pattern):
792 with open(fname, 'r') as fd:
793 dict_name = os.path.basename(fname).replace('.sizes',
794 '')
795 func_sizes[dict_name] = self.ReadFuncSizes(fname, fd)
796
Simon Glass843312d2015-02-05 22:06:15 -0700797 if read_config:
798 output_dir = self.GetBuildDir(commit_upto, target)
Simon Glassb464f8e2016-11-13 14:25:53 -0700799 for name in self.config_filenames:
Simon Glass843312d2015-02-05 22:06:15 -0700800 fname = os.path.join(output_dir, name)
801 config[name] = self._ProcessConfig(fname)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000802
Alex Kiernan48ae4122018-05-31 04:48:34 +0000803 if read_environment:
804 output_dir = self.GetBuildDir(commit_upto, target)
805 fname = os.path.join(output_dir, 'uboot.env')
806 environment = self._ProcessEnvironment(fname)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000807
Alex Kiernan48ae4122018-05-31 04:48:34 +0000808 return Builder.Outcome(rc, err_lines, sizes, func_sizes, config,
809 environment)
810
811 return Builder.Outcome(OUTCOME_UNKNOWN, [], {}, {}, {}, {})
Simon Glass843312d2015-02-05 22:06:15 -0700812
813 def GetResultSummary(self, boards_selected, commit_upto, read_func_sizes,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000814 read_config, read_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000815 """Calculate a summary of the results of building a commit.
816
817 Args:
818 board_selected: Dict containing boards to summarise
819 commit_upto: Commit number to summarize (0..self.count-1)
820 read_func_sizes: True to read function size information
Simon Glass843312d2015-02-05 22:06:15 -0700821 read_config: True to read .config and autoconf.h files
Alex Kiernan48ae4122018-05-31 04:48:34 +0000822 read_environment: True to read uboot.env files
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000823
824 Returns:
825 Tuple:
826 Dict containing boards which passed building this commit.
827 keyed by board.target
Simon Glasse30965d2014-08-28 09:43:44 -0600828 List containing a summary of error lines
Simon Glassed966652014-08-28 09:43:43 -0600829 Dict keyed by error line, containing a list of the Board
830 objects with that error
Simon Glasse30965d2014-08-28 09:43:44 -0600831 List containing a summary of warning lines
832 Dict keyed by error line, containing a list of the Board
833 objects with that warning
Simon Glass8270e3c2015-08-25 21:52:14 -0600834 Dictionary keyed by board.target. Each value is a dictionary:
835 key: filename - e.g. '.config'
Simon Glass843312d2015-02-05 22:06:15 -0700836 value is itself a dictionary:
837 key: config name
838 value: config value
Alex Kiernan48ae4122018-05-31 04:48:34 +0000839 Dictionary keyed by board.target. Each value is a dictionary:
840 key: environment variable
841 value: value of environment variable
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000842 """
Simon Glasse30965d2014-08-28 09:43:44 -0600843 def AddLine(lines_summary, lines_boards, line, board):
844 line = line.rstrip()
845 if line in lines_boards:
846 lines_boards[line].append(board)
847 else:
848 lines_boards[line] = [board]
849 lines_summary.append(line)
850
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000851 board_dict = {}
852 err_lines_summary = []
Simon Glassed966652014-08-28 09:43:43 -0600853 err_lines_boards = {}
Simon Glasse30965d2014-08-28 09:43:44 -0600854 warn_lines_summary = []
855 warn_lines_boards = {}
Simon Glass843312d2015-02-05 22:06:15 -0700856 config = {}
Alex Kiernan48ae4122018-05-31 04:48:34 +0000857 environment = {}
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000858
Simon Glassc05aa032019-10-31 07:42:53 -0600859 for board in boards_selected.values():
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000860 outcome = self.GetBuildOutcome(commit_upto, board.target,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000861 read_func_sizes, read_config,
862 read_environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000863 board_dict[board.target] = outcome
Simon Glasse30965d2014-08-28 09:43:44 -0600864 last_func = None
865 last_was_warning = False
866 for line in outcome.err_lines:
867 if line:
868 if (self._re_function.match(line) or
869 self._re_files.match(line)):
870 last_func = line
Simon Glassed966652014-08-28 09:43:43 -0600871 else:
Simon Glass2d483332018-11-06 16:02:11 -0700872 is_warning = (self._re_warning.match(line) or
873 self._re_dtb_warning.match(line))
Simon Glasse30965d2014-08-28 09:43:44 -0600874 is_note = self._re_note.match(line)
875 if is_warning or (last_was_warning and is_note):
876 if last_func:
877 AddLine(warn_lines_summary, warn_lines_boards,
878 last_func, board)
879 AddLine(warn_lines_summary, warn_lines_boards,
880 line, board)
881 else:
882 if last_func:
883 AddLine(err_lines_summary, err_lines_boards,
884 last_func, board)
885 AddLine(err_lines_summary, err_lines_boards,
886 line, board)
887 last_was_warning = is_warning
888 last_func = None
Simon Glassb464f8e2016-11-13 14:25:53 -0700889 tconfig = Config(self.config_filenames, board.target)
890 for fname in self.config_filenames:
Simon Glass843312d2015-02-05 22:06:15 -0700891 if outcome.config:
Simon Glassc05aa032019-10-31 07:42:53 -0600892 for key, value in outcome.config[fname].items():
Simon Glass8270e3c2015-08-25 21:52:14 -0600893 tconfig.Add(fname, key, value)
894 config[board.target] = tconfig
Simon Glass843312d2015-02-05 22:06:15 -0700895
Alex Kiernan48ae4122018-05-31 04:48:34 +0000896 tenvironment = Environment(board.target)
897 if outcome.environment:
Simon Glassc05aa032019-10-31 07:42:53 -0600898 for key, value in outcome.environment.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +0000899 tenvironment.Add(key, value)
900 environment[board.target] = tenvironment
901
Simon Glasse30965d2014-08-28 09:43:44 -0600902 return (board_dict, err_lines_summary, err_lines_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000903 warn_lines_summary, warn_lines_boards, config, environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000904
905 def AddOutcome(self, board_dict, arch_list, changes, char, color):
906 """Add an output to our list of outcomes for each architecture
907
908 This simple function adds failing boards (changes) to the
909 relevant architecture string, so we can print the results out
910 sorted by architecture.
911
912 Args:
913 board_dict: Dict containing all boards
914 arch_list: Dict keyed by arch name. Value is a string containing
915 a list of board names which failed for that arch.
916 changes: List of boards to add to arch_list
917 color: terminal.Colour object
918 """
919 done_arch = {}
920 for target in changes:
921 if target in board_dict:
922 arch = board_dict[target].arch
923 else:
924 arch = 'unknown'
925 str = self.col.Color(color, ' ' + target)
926 if not arch in done_arch:
Simon Glass63c619e2015-02-05 22:06:11 -0700927 str = ' %s %s' % (self.col.Color(color, char), str)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000928 done_arch[arch] = True
929 if not arch in arch_list:
930 arch_list[arch] = str
931 else:
932 arch_list[arch] += str
933
934
935 def ColourNum(self, num):
936 color = self.col.RED if num > 0 else self.col.GREEN
937 if num == 0:
938 return '0'
939 return self.col.Color(color, str(num))
940
941 def ResetResultSummary(self, board_selected):
942 """Reset the results summary ready for use.
943
944 Set up the base board list to be all those selected, and set the
945 error lines to empty.
946
947 Following this, calls to PrintResultSummary() will use this
948 information to work out what has changed.
949
950 Args:
951 board_selected: Dict containing boards to summarise, keyed by
952 board.target
953 """
954 self._base_board_dict = {}
955 for board in board_selected:
Alex Kiernan48ae4122018-05-31 04:48:34 +0000956 self._base_board_dict[board] = Builder.Outcome(0, [], [], {}, {},
957 {})
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000958 self._base_err_lines = []
Simon Glasse30965d2014-08-28 09:43:44 -0600959 self._base_warn_lines = []
960 self._base_err_line_boards = {}
961 self._base_warn_line_boards = {}
Simon Glass8270e3c2015-08-25 21:52:14 -0600962 self._base_config = None
Alex Kiernan48ae4122018-05-31 04:48:34 +0000963 self._base_environment = None
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000964
965 def PrintFuncSizeDetail(self, fname, old, new):
966 grow, shrink, add, remove, up, down = 0, 0, 0, 0, 0, 0
967 delta, common = [], {}
968
969 for a in old:
970 if a in new:
971 common[a] = 1
972
973 for name in old:
974 if name not in common:
975 remove += 1
976 down += old[name]
977 delta.append([-old[name], name])
978
979 for name in new:
980 if name not in common:
981 add += 1
982 up += new[name]
983 delta.append([new[name], name])
984
985 for name in common:
986 diff = new.get(name, 0) - old.get(name, 0)
987 if diff > 0:
988 grow, up = grow + 1, up + diff
989 elif diff < 0:
990 shrink, down = shrink + 1, down - diff
991 delta.append([diff, name])
992
993 delta.sort()
994 delta.reverse()
995
996 args = [add, -remove, grow, -shrink, up, -down, up - down]
Tom Rinid5686a62017-05-22 13:48:52 -0400997 if max(args) == 0 and min(args) == 0:
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000998 return
999 args = [self.ColourNum(x) for x in args]
1000 indent = ' ' * 15
Simon Glass4653a882014-09-05 19:00:07 -06001001 Print('%s%s: add: %s/%s, grow: %s/%s bytes: %s/%s (%s)' %
1002 tuple([indent, self.col.Color(self.col.YELLOW, fname)] + args))
1003 Print('%s %-38s %7s %7s %+7s' % (indent, 'function', 'old', 'new',
1004 'delta'))
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001005 for diff, name in delta:
1006 if diff:
1007 color = self.col.RED if diff > 0 else self.col.GREEN
1008 msg = '%s %-38s %7s %7s %+7d' % (indent, name,
1009 old.get(name, '-'), new.get(name,'-'), diff)
Simon Glass4653a882014-09-05 19:00:07 -06001010 Print(msg, colour=color)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001011
1012
1013 def PrintSizeDetail(self, target_list, show_bloat):
1014 """Show details size information for each board
1015
1016 Args:
1017 target_list: List of targets, each a dict containing:
1018 'target': Target name
1019 'total_diff': Total difference in bytes across all areas
1020 <part_name>: Difference for that part
1021 show_bloat: Show detail for each function
1022 """
1023 targets_by_diff = sorted(target_list, reverse=True,
1024 key=lambda x: x['_total_diff'])
1025 for result in targets_by_diff:
1026 printed_target = False
1027 for name in sorted(result):
1028 diff = result[name]
1029 if name.startswith('_'):
1030 continue
1031 if diff != 0:
1032 color = self.col.RED if diff > 0 else self.col.GREEN
1033 msg = ' %s %+d' % (name, diff)
1034 if not printed_target:
Simon Glass4653a882014-09-05 19:00:07 -06001035 Print('%10s %-15s:' % ('', result['_target']),
1036 newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001037 printed_target = True
Simon Glass4653a882014-09-05 19:00:07 -06001038 Print(msg, colour=color, newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001039 if printed_target:
Simon Glass4653a882014-09-05 19:00:07 -06001040 Print()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001041 if show_bloat:
1042 target = result['_target']
1043 outcome = result['_outcome']
1044 base_outcome = self._base_board_dict[target]
1045 for fname in outcome.func_sizes:
1046 self.PrintFuncSizeDetail(fname,
1047 base_outcome.func_sizes[fname],
1048 outcome.func_sizes[fname])
1049
1050
1051 def PrintSizeSummary(self, board_selected, board_dict, show_detail,
1052 show_bloat):
1053 """Print a summary of image sizes broken down by section.
1054
1055 The summary takes the form of one line per architecture. The
1056 line contains deltas for each of the sections (+ means the section
Flavio Suligoi9de5c392020-01-29 09:56:05 +01001057 got bigger, - means smaller). The numbers are the average number
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001058 of bytes that a board in this section increased by.
1059
1060 For example:
1061 powerpc: (622 boards) text -0.0
1062 arm: (285 boards) text -0.0
1063 nds32: (3 boards) text -8.0
1064
1065 Args:
1066 board_selected: Dict containing boards to summarise, keyed by
1067 board.target
1068 board_dict: Dict containing boards for which we built this
1069 commit, keyed by board.target. The value is an Outcome object.
Simon Glassf9c094b2020-03-18 09:42:43 -06001070 show_detail: Show size delta detail for each board
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001071 show_bloat: Show detail for each function
1072 """
1073 arch_list = {}
1074 arch_count = {}
1075
1076 # Calculate changes in size for different image parts
1077 # The previous sizes are in Board.sizes, for each board
1078 for target in board_dict:
1079 if target not in board_selected:
1080 continue
1081 base_sizes = self._base_board_dict[target].sizes
1082 outcome = board_dict[target]
1083 sizes = outcome.sizes
1084
1085 # Loop through the list of images, creating a dict of size
1086 # changes for each image/part. We end up with something like
1087 # {'target' : 'snapper9g45, 'data' : 5, 'u-boot-spl:text' : -4}
1088 # which means that U-Boot data increased by 5 bytes and SPL
1089 # text decreased by 4.
1090 err = {'_target' : target}
1091 for image in sizes:
1092 if image in base_sizes:
1093 base_image = base_sizes[image]
1094 # Loop through the text, data, bss parts
1095 for part in sorted(sizes[image]):
1096 diff = sizes[image][part] - base_image[part]
1097 col = None
1098 if diff:
1099 if image == 'u-boot':
1100 name = part
1101 else:
1102 name = image + ':' + part
1103 err[name] = diff
1104 arch = board_selected[target].arch
1105 if not arch in arch_count:
1106 arch_count[arch] = 1
1107 else:
1108 arch_count[arch] += 1
1109 if not sizes:
1110 pass # Only add to our list when we have some stats
1111 elif not arch in arch_list:
1112 arch_list[arch] = [err]
1113 else:
1114 arch_list[arch].append(err)
1115
1116 # We now have a list of image size changes sorted by arch
1117 # Print out a summary of these
Simon Glassc05aa032019-10-31 07:42:53 -06001118 for arch, target_list in arch_list.items():
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001119 # Get total difference for each type
1120 totals = {}
1121 for result in target_list:
1122 total = 0
Simon Glassc05aa032019-10-31 07:42:53 -06001123 for name, diff in result.items():
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001124 if name.startswith('_'):
1125 continue
1126 total += diff
1127 if name in totals:
1128 totals[name] += diff
1129 else:
1130 totals[name] = diff
1131 result['_total_diff'] = total
1132 result['_outcome'] = board_dict[result['_target']]
1133
1134 count = len(target_list)
1135 printed_arch = False
1136 for name in sorted(totals):
1137 diff = totals[name]
1138 if diff:
1139 # Display the average difference in this name for this
1140 # architecture
1141 avg_diff = float(diff) / count
1142 color = self.col.RED if avg_diff > 0 else self.col.GREEN
1143 msg = ' %s %+1.1f' % (name, avg_diff)
1144 if not printed_arch:
Simon Glass4653a882014-09-05 19:00:07 -06001145 Print('%10s: (for %d/%d boards)' % (arch, count,
1146 arch_count[arch]), newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001147 printed_arch = True
Simon Glass4653a882014-09-05 19:00:07 -06001148 Print(msg, colour=color, newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001149
1150 if printed_arch:
Simon Glass4653a882014-09-05 19:00:07 -06001151 Print()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001152 if show_detail:
1153 self.PrintSizeDetail(target_list, show_bloat)
1154
1155
1156 def PrintResultSummary(self, board_selected, board_dict, err_lines,
Simon Glasse30965d2014-08-28 09:43:44 -06001157 err_line_boards, warn_lines, warn_line_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001158 config, environment, show_sizes, show_detail,
1159 show_bloat, show_config, show_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001160 """Compare results with the base results and display delta.
1161
1162 Only boards mentioned in board_selected will be considered. This
1163 function is intended to be called repeatedly with the results of
1164 each commit. It therefore shows a 'diff' between what it saw in
1165 the last call and what it sees now.
1166
1167 Args:
1168 board_selected: Dict containing boards to summarise, keyed by
1169 board.target
1170 board_dict: Dict containing boards for which we built this
1171 commit, keyed by board.target. The value is an Outcome object.
1172 err_lines: A list of errors for this commit, or [] if there is
1173 none, or we don't want to print errors
Simon Glassed966652014-08-28 09:43:43 -06001174 err_line_boards: Dict keyed by error line, containing a list of
1175 the Board objects with that error
Simon Glasse30965d2014-08-28 09:43:44 -06001176 warn_lines: A list of warnings for this commit, or [] if there is
1177 none, or we don't want to print errors
1178 warn_line_boards: Dict keyed by warning line, containing a list of
1179 the Board objects with that warning
Simon Glass843312d2015-02-05 22:06:15 -07001180 config: Dictionary keyed by filename - e.g. '.config'. Each
1181 value is itself a dictionary:
1182 key: config name
1183 value: config value
Alex Kiernan48ae4122018-05-31 04:48:34 +00001184 environment: Dictionary keyed by environment variable, Each
1185 value is the value of environment variable.
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001186 show_sizes: Show image size deltas
Simon Glassf9c094b2020-03-18 09:42:43 -06001187 show_detail: Show size delta detail for each board if show_sizes
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001188 show_bloat: Show detail for each function
Simon Glass843312d2015-02-05 22:06:15 -07001189 show_config: Show config changes
Alex Kiernan48ae4122018-05-31 04:48:34 +00001190 show_environment: Show environment changes
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001191 """
Simon Glasse30965d2014-08-28 09:43:44 -06001192 def _BoardList(line, line_boards):
Simon Glassed966652014-08-28 09:43:43 -06001193 """Helper function to get a line of boards containing a line
1194
1195 Args:
1196 line: Error line to search for
Simon Glass35d696d2020-04-09 15:08:36 -06001197 line_boards: boards to search, each a Board
Simon Glassed966652014-08-28 09:43:43 -06001198 Return:
Simon Glass35d696d2020-04-09 15:08:36 -06001199 List of boards with that error line, or [] if the user has not
1200 requested such a list
Simon Glassed966652014-08-28 09:43:43 -06001201 """
Simon Glass35d696d2020-04-09 15:08:36 -06001202 boards = []
1203 board_set = set()
Simon Glassed966652014-08-28 09:43:43 -06001204 if self._list_error_boards:
Simon Glasse30965d2014-08-28 09:43:44 -06001205 for board in line_boards[line]:
Simon Glass35d696d2020-04-09 15:08:36 -06001206 if not board in board_set:
1207 boards.append(board)
1208 board_set.add(board)
1209 return boards
Simon Glassed966652014-08-28 09:43:43 -06001210
Simon Glasse30965d2014-08-28 09:43:44 -06001211 def _CalcErrorDelta(base_lines, base_line_boards, lines, line_boards,
1212 char):
Simon Glass35d696d2020-04-09 15:08:36 -06001213 """Calculate the required output based on changes in errors
1214
1215 Args:
1216 base_lines: List of errors/warnings for previous commit
1217 base_line_boards: Dict keyed by error line, containing a list
1218 of the Board objects with that error in the previous commit
1219 lines: List of errors/warning for this commit, each a str
1220 line_boards: Dict keyed by error line, containing a list
1221 of the Board objects with that error in this commit
1222 char: Character representing error ('') or warning ('w'). The
1223 broken ('+') or fixed ('-') characters are added in this
1224 function
1225
1226 Returns:
1227 Tuple
1228 List of ErrLine objects for 'better' lines
1229 List of ErrLine objects for 'worse' lines
1230 """
Simon Glasse30965d2014-08-28 09:43:44 -06001231 better_lines = []
1232 worse_lines = []
1233 for line in lines:
1234 if line not in base_lines:
Simon Glass35d696d2020-04-09 15:08:36 -06001235 errline = ErrLine(char + '+', _BoardList(line, line_boards),
1236 line)
1237 worse_lines.append(errline)
Simon Glasse30965d2014-08-28 09:43:44 -06001238 for line in base_lines:
1239 if line not in lines:
Simon Glass35d696d2020-04-09 15:08:36 -06001240 errline = ErrLine(char + '-',
1241 _BoardList(line, base_line_boards), line)
1242 better_lines.append(errline)
Simon Glasse30965d2014-08-28 09:43:44 -06001243 return better_lines, worse_lines
1244
Simon Glass843312d2015-02-05 22:06:15 -07001245 def _CalcConfig(delta, name, config):
1246 """Calculate configuration changes
1247
1248 Args:
1249 delta: Type of the delta, e.g. '+'
1250 name: name of the file which changed (e.g. .config)
1251 config: configuration change dictionary
1252 key: config name
1253 value: config value
1254 Returns:
1255 String containing the configuration changes which can be
1256 printed
1257 """
1258 out = ''
1259 for key in sorted(config.keys()):
1260 out += '%s=%s ' % (key, config[key])
Simon Glass8270e3c2015-08-25 21:52:14 -06001261 return '%s %s: %s' % (delta, name, out)
Simon Glass843312d2015-02-05 22:06:15 -07001262
Simon Glass8270e3c2015-08-25 21:52:14 -06001263 def _AddConfig(lines, name, config_plus, config_minus, config_change):
1264 """Add changes in configuration to a list
Simon Glass843312d2015-02-05 22:06:15 -07001265
1266 Args:
Simon Glass8270e3c2015-08-25 21:52:14 -06001267 lines: list to add to
1268 name: config file name
Simon Glass843312d2015-02-05 22:06:15 -07001269 config_plus: configurations added, dictionary
1270 key: config name
1271 value: config value
1272 config_minus: configurations removed, dictionary
1273 key: config name
1274 value: config value
1275 config_change: configurations changed, dictionary
1276 key: config name
1277 value: config value
1278 """
1279 if config_plus:
Simon Glass8270e3c2015-08-25 21:52:14 -06001280 lines.append(_CalcConfig('+', name, config_plus))
Simon Glass843312d2015-02-05 22:06:15 -07001281 if config_minus:
Simon Glass8270e3c2015-08-25 21:52:14 -06001282 lines.append(_CalcConfig('-', name, config_minus))
Simon Glass843312d2015-02-05 22:06:15 -07001283 if config_change:
Simon Glass8270e3c2015-08-25 21:52:14 -06001284 lines.append(_CalcConfig('c', name, config_change))
1285
1286 def _OutputConfigInfo(lines):
1287 for line in lines:
1288 if not line:
1289 continue
1290 if line[0] == '+':
1291 col = self.col.GREEN
1292 elif line[0] == '-':
1293 col = self.col.RED
1294 elif line[0] == 'c':
1295 col = self.col.YELLOW
1296 Print(' ' + line, newline=True, colour=col)
1297
Simon Glassb206d872020-04-09 15:08:28 -06001298 def _OutputErrLines(err_lines, colour):
1299 """Output the line of error/warning lines, if not empty
1300
1301 Also increments self._error_lines if err_lines not empty
1302
1303 Args:
Simon Glass35d696d2020-04-09 15:08:36 -06001304 err_lines: List of ErrLine objects, each an error or warning
1305 line, possibly including a list of boards with that
1306 error/warning
Simon Glassb206d872020-04-09 15:08:28 -06001307 colour: Colour to use for output
1308 """
1309 if err_lines:
Simon Glass8c9a2672020-04-09 15:08:37 -06001310 out_list = []
Simon Glass35d696d2020-04-09 15:08:36 -06001311 for line in err_lines:
1312 boards = ''
1313 names = [board.target for board in line.boards]
Simon Glass9ef0ceb2020-04-09 15:08:38 -06001314 board_str = ' '.join(names) if names else ''
Simon Glass8c9a2672020-04-09 15:08:37 -06001315 if board_str:
1316 out = self.col.Color(colour, line.char + '(')
1317 out += self.col.Color(self.col.MAGENTA, board_str,
1318 bright=False)
1319 out += self.col.Color(colour, ') %s' % line.errline)
1320 else:
1321 out = self.col.Color(colour, line.char + line.errline)
1322 out_list.append(out)
1323 Print('\n'.join(out_list))
Simon Glassb206d872020-04-09 15:08:28 -06001324 self._error_lines += 1
1325
Simon Glass843312d2015-02-05 22:06:15 -07001326
Simon Glass4cf2b222018-11-06 16:02:12 -07001327 ok_boards = [] # List of boards fixed since last commit
Simon Glass6af71012018-11-06 16:02:13 -07001328 warn_boards = [] # List of boards with warnings since last commit
Simon Glass4cf2b222018-11-06 16:02:12 -07001329 err_boards = [] # List of new broken boards since last commit
1330 new_boards = [] # List of boards that didn't exist last time
1331 unknown_boards = [] # List of boards that were not built
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001332
1333 for target in board_dict:
1334 if target not in board_selected:
1335 continue
1336
1337 # If the board was built last time, add its outcome to a list
1338 if target in self._base_board_dict:
1339 base_outcome = self._base_board_dict[target].rc
1340 outcome = board_dict[target]
1341 if outcome.rc == OUTCOME_UNKNOWN:
Simon Glass4cf2b222018-11-06 16:02:12 -07001342 unknown_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001343 elif outcome.rc < base_outcome:
Simon Glass6af71012018-11-06 16:02:13 -07001344 if outcome.rc == OUTCOME_WARNING:
1345 warn_boards.append(target)
1346 else:
1347 ok_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001348 elif outcome.rc > base_outcome:
Simon Glass6af71012018-11-06 16:02:13 -07001349 if outcome.rc == OUTCOME_WARNING:
1350 warn_boards.append(target)
1351 else:
1352 err_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001353 else:
Simon Glass4cf2b222018-11-06 16:02:12 -07001354 new_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001355
Simon Glassb206d872020-04-09 15:08:28 -06001356 # Get a list of errors and warnings that have appeared, and disappeared
Simon Glasse30965d2014-08-28 09:43:44 -06001357 better_err, worse_err = _CalcErrorDelta(self._base_err_lines,
1358 self._base_err_line_boards, err_lines, err_line_boards, '')
1359 better_warn, worse_warn = _CalcErrorDelta(self._base_warn_lines,
1360 self._base_warn_line_boards, warn_lines, warn_line_boards, 'w')
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001361
1362 # Display results by arch
Simon Glass6af71012018-11-06 16:02:13 -07001363 if any((ok_boards, warn_boards, err_boards, unknown_boards, new_boards,
1364 worse_err, better_err, worse_warn, better_warn)):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001365 arch_list = {}
Simon Glass4cf2b222018-11-06 16:02:12 -07001366 self.AddOutcome(board_selected, arch_list, ok_boards, '',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001367 self.col.GREEN)
Simon Glass6af71012018-11-06 16:02:13 -07001368 self.AddOutcome(board_selected, arch_list, warn_boards, 'w+',
1369 self.col.YELLOW)
Simon Glass4cf2b222018-11-06 16:02:12 -07001370 self.AddOutcome(board_selected, arch_list, err_boards, '+',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001371 self.col.RED)
Simon Glass4cf2b222018-11-06 16:02:12 -07001372 self.AddOutcome(board_selected, arch_list, new_boards, '*', self.col.BLUE)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001373 if self._show_unknown:
Simon Glass4cf2b222018-11-06 16:02:12 -07001374 self.AddOutcome(board_selected, arch_list, unknown_boards, '?',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001375 self.col.MAGENTA)
Simon Glassc05aa032019-10-31 07:42:53 -06001376 for arch, target_list in arch_list.items():
Simon Glass4653a882014-09-05 19:00:07 -06001377 Print('%10s: %s' % (arch, target_list))
Simon Glass28370c12014-08-09 15:33:06 -06001378 self._error_lines += 1
Simon Glassb206d872020-04-09 15:08:28 -06001379 _OutputErrLines(better_err, colour=self.col.GREEN)
1380 _OutputErrLines(worse_err, colour=self.col.RED)
1381 _OutputErrLines(better_warn, colour=self.col.CYAN)
Simon Glass5627bd92020-04-09 15:08:35 -06001382 _OutputErrLines(worse_warn, colour=self.col.YELLOW)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001383
1384 if show_sizes:
1385 self.PrintSizeSummary(board_selected, board_dict, show_detail,
1386 show_bloat)
1387
Alex Kiernan48ae4122018-05-31 04:48:34 +00001388 if show_environment and self._base_environment:
1389 lines = []
1390
1391 for target in board_dict:
1392 if target not in board_selected:
1393 continue
1394
1395 tbase = self._base_environment[target]
1396 tenvironment = environment[target]
1397 environment_plus = {}
1398 environment_minus = {}
1399 environment_change = {}
1400 base = tbase.environment
Simon Glassc05aa032019-10-31 07:42:53 -06001401 for key, value in tenvironment.environment.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001402 if key not in base:
1403 environment_plus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001404 for key, value in base.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001405 if key not in tenvironment.environment:
1406 environment_minus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001407 for key, value in base.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001408 new_value = tenvironment.environment.get(key)
1409 if new_value and value != new_value:
1410 desc = '%s -> %s' % (value, new_value)
1411 environment_change[key] = desc
1412
1413 _AddConfig(lines, target, environment_plus, environment_minus,
1414 environment_change)
1415
1416 _OutputConfigInfo(lines)
1417
Simon Glass8270e3c2015-08-25 21:52:14 -06001418 if show_config and self._base_config:
1419 summary = {}
1420 arch_config_plus = {}
1421 arch_config_minus = {}
1422 arch_config_change = {}
1423 arch_list = []
1424
1425 for target in board_dict:
1426 if target not in board_selected:
Simon Glass843312d2015-02-05 22:06:15 -07001427 continue
Simon Glass8270e3c2015-08-25 21:52:14 -06001428 arch = board_selected[target].arch
1429 if arch not in arch_list:
1430 arch_list.append(arch)
1431
1432 for arch in arch_list:
1433 arch_config_plus[arch] = {}
1434 arch_config_minus[arch] = {}
1435 arch_config_change[arch] = {}
Simon Glassb464f8e2016-11-13 14:25:53 -07001436 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001437 arch_config_plus[arch][name] = {}
1438 arch_config_minus[arch][name] = {}
1439 arch_config_change[arch][name] = {}
1440
1441 for target in board_dict:
1442 if target not in board_selected:
1443 continue
1444
1445 arch = board_selected[target].arch
1446
1447 all_config_plus = {}
1448 all_config_minus = {}
1449 all_config_change = {}
1450 tbase = self._base_config[target]
1451 tconfig = config[target]
1452 lines = []
Simon Glassb464f8e2016-11-13 14:25:53 -07001453 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001454 if not tconfig.config[name]:
1455 continue
1456 config_plus = {}
1457 config_minus = {}
1458 config_change = {}
1459 base = tbase.config[name]
Simon Glassc05aa032019-10-31 07:42:53 -06001460 for key, value in tconfig.config[name].items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001461 if key not in base:
1462 config_plus[key] = value
1463 all_config_plus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001464 for key, value in base.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001465 if key not in tconfig.config[name]:
1466 config_minus[key] = value
1467 all_config_minus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001468 for key, value in base.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001469 new_value = tconfig.config.get(key)
1470 if new_value and value != new_value:
1471 desc = '%s -> %s' % (value, new_value)
1472 config_change[key] = desc
1473 all_config_change[key] = desc
1474
1475 arch_config_plus[arch][name].update(config_plus)
1476 arch_config_minus[arch][name].update(config_minus)
1477 arch_config_change[arch][name].update(config_change)
1478
1479 _AddConfig(lines, name, config_plus, config_minus,
1480 config_change)
1481 _AddConfig(lines, 'all', all_config_plus, all_config_minus,
1482 all_config_change)
1483 summary[target] = '\n'.join(lines)
1484
1485 lines_by_target = {}
Simon Glassc05aa032019-10-31 07:42:53 -06001486 for target, lines in summary.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001487 if lines in lines_by_target:
1488 lines_by_target[lines].append(target)
1489 else:
1490 lines_by_target[lines] = [target]
1491
1492 for arch in arch_list:
1493 lines = []
1494 all_plus = {}
1495 all_minus = {}
1496 all_change = {}
Simon Glassb464f8e2016-11-13 14:25:53 -07001497 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001498 all_plus.update(arch_config_plus[arch][name])
1499 all_minus.update(arch_config_minus[arch][name])
1500 all_change.update(arch_config_change[arch][name])
1501 _AddConfig(lines, name, arch_config_plus[arch][name],
1502 arch_config_minus[arch][name],
1503 arch_config_change[arch][name])
1504 _AddConfig(lines, 'all', all_plus, all_minus, all_change)
1505 #arch_summary[target] = '\n'.join(lines)
1506 if lines:
1507 Print('%s:' % arch)
1508 _OutputConfigInfo(lines)
1509
Simon Glassc05aa032019-10-31 07:42:53 -06001510 for lines, targets in lines_by_target.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001511 if not lines:
1512 continue
1513 Print('%s :' % ' '.join(sorted(targets)))
1514 _OutputConfigInfo(lines.split('\n'))
1515
Simon Glass843312d2015-02-05 22:06:15 -07001516
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001517 # Save our updated information for the next call to this function
1518 self._base_board_dict = board_dict
1519 self._base_err_lines = err_lines
Simon Glasse30965d2014-08-28 09:43:44 -06001520 self._base_warn_lines = warn_lines
1521 self._base_err_line_boards = err_line_boards
1522 self._base_warn_line_boards = warn_line_boards
Simon Glass843312d2015-02-05 22:06:15 -07001523 self._base_config = config
Alex Kiernan48ae4122018-05-31 04:48:34 +00001524 self._base_environment = environment
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001525
1526 # Get a list of boards that did not get built, if needed
1527 not_built = []
1528 for board in board_selected:
1529 if not board in board_dict:
1530 not_built.append(board)
1531 if not_built:
Simon Glass4653a882014-09-05 19:00:07 -06001532 Print("Boards not built (%d): %s" % (len(not_built),
1533 ', '.join(not_built)))
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001534
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001535 def ProduceResultSummary(self, commit_upto, commits, board_selected):
Simon Glasse30965d2014-08-28 09:43:44 -06001536 (board_dict, err_lines, err_line_boards, warn_lines,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001537 warn_line_boards, config, environment) = self.GetResultSummary(
Simon Glassed966652014-08-28 09:43:43 -06001538 board_selected, commit_upto,
Simon Glass843312d2015-02-05 22:06:15 -07001539 read_func_sizes=self._show_bloat,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001540 read_config=self._show_config,
1541 read_environment=self._show_environment)
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001542 if commits:
1543 msg = '%02d: %s' % (commit_upto + 1,
1544 commits[commit_upto].subject)
Simon Glass4653a882014-09-05 19:00:07 -06001545 Print(msg, colour=self.col.BLUE)
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001546 self.PrintResultSummary(board_selected, board_dict,
Simon Glassed966652014-08-28 09:43:43 -06001547 err_lines if self._show_errors else [], err_line_boards,
Simon Glasse30965d2014-08-28 09:43:44 -06001548 warn_lines if self._show_errors else [], warn_line_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001549 config, environment, self._show_sizes, self._show_detail,
1550 self._show_bloat, self._show_config, self._show_environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001551
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001552 def ShowSummary(self, commits, board_selected):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001553 """Show a build summary for U-Boot for a given board list.
1554
1555 Reset the result summary, then repeatedly call GetResultSummary on
1556 each commit's results, then display the differences we see.
1557
1558 Args:
1559 commit: Commit objects to summarise
1560 board_selected: Dict containing boards to summarise
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001561 """
Simon Glassfea58582014-08-09 15:32:59 -06001562 self.commit_count = len(commits) if commits else 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001563 self.commits = commits
1564 self.ResetResultSummary(board_selected)
Simon Glass28370c12014-08-09 15:33:06 -06001565 self._error_lines = 0
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001566
1567 for commit_upto in range(0, self.commit_count, self._step):
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001568 self.ProduceResultSummary(commit_upto, commits, board_selected)
Simon Glass28370c12014-08-09 15:33:06 -06001569 if not self._error_lines:
Simon Glass4653a882014-09-05 19:00:07 -06001570 Print('(no errors to report)', colour=self.col.GREEN)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001571
1572
1573 def SetupBuild(self, board_selected, commits):
1574 """Set up ready to start a build.
1575
1576 Args:
1577 board_selected: Selected boards to build
1578 commits: Selected commits to build
1579 """
1580 # First work out how many commits we will build
Simon Glassc05aa032019-10-31 07:42:53 -06001581 count = (self.commit_count + self._step - 1) // self._step
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001582 self.count = len(board_selected) * count
1583 self.upto = self.warned = self.fail = 0
1584 self._timestamps = collections.deque()
1585
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001586 def GetThreadDir(self, thread_num):
1587 """Get the directory path to the working dir for a thread.
1588
1589 Args:
Simon Glassb82492b2021-01-30 22:17:46 -07001590 thread_num: Number of thread to check (-1 for main process, which
1591 is treated as 0)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001592 """
Simon Glassd829f122020-03-18 09:42:42 -06001593 if self.work_in_output:
1594 return self._working_dir
Simon Glassb82492b2021-01-30 22:17:46 -07001595 return os.path.join(self._working_dir, '%02d' % max(thread_num, 0))
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001596
Simon Glassfea58582014-08-09 15:32:59 -06001597 def _PrepareThread(self, thread_num, setup_git):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001598 """Prepare the working directory for a thread.
1599
1600 This clones or fetches the repo into the thread's work directory.
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001601 Optionally, it can create a linked working tree of the repo in the
1602 thread's work directory instead.
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001603
1604 Args:
1605 thread_num: Thread number (0, 1, ...)
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001606 setup_git:
1607 'clone' to set up a git clone
1608 'worktree' to set up a git worktree
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001609 """
1610 thread_dir = self.GetThreadDir(thread_num)
Simon Glass190064b2014-08-09 15:33:00 -06001611 builderthread.Mkdir(thread_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001612 git_dir = os.path.join(thread_dir, '.git')
1613
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001614 # Create a worktree or a git repo clone for this thread if it
1615 # doesn't already exist
Simon Glassfea58582014-08-09 15:32:59 -06001616 if setup_git and self.git_dir:
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001617 src_dir = os.path.abspath(self.git_dir)
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001618 if os.path.isdir(git_dir):
1619 # This is a clone of the src_dir repo, we can keep using
1620 # it but need to fetch from src_dir.
Simon Glass212c0b82020-04-09 15:08:43 -06001621 Print('\rFetching repo for thread %d' % thread_num,
1622 newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001623 gitutil.Fetch(git_dir, thread_dir)
Simon Glass212c0b82020-04-09 15:08:43 -06001624 terminal.PrintClear()
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001625 elif os.path.isfile(git_dir):
1626 # This is a worktree of the src_dir repo, we don't need to
1627 # create it again or update it in any way.
1628 pass
1629 elif os.path.exists(git_dir):
1630 # Don't know what could trigger this, but we probably
1631 # can't create a git worktree/clone here.
1632 raise ValueError('Git dir %s exists, but is not a file '
1633 'or a directory.' % git_dir)
1634 elif setup_git == 'worktree':
1635 Print('\rChecking out worktree for thread %d' % thread_num,
1636 newline=False)
1637 gitutil.AddWorktree(src_dir, thread_dir)
1638 terminal.PrintClear()
1639 elif setup_git == 'clone' or setup_git == True:
Simon Glass21f0eb32016-09-18 16:48:31 -06001640 Print('\rCloning repo for thread %d' % thread_num,
1641 newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001642 gitutil.Clone(src_dir, thread_dir)
Simon Glass102969b2020-04-09 15:08:42 -06001643 terminal.PrintClear()
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001644 else:
1645 raise ValueError("Can't setup git repo with %s." % setup_git)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001646
Simon Glassfea58582014-08-09 15:32:59 -06001647 def _PrepareWorkingSpace(self, max_threads, setup_git):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001648 """Prepare the working directory for use.
1649
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001650 Set up the git repo for each thread. Creates a linked working tree
1651 if git-worktree is available, or clones the repo if it isn't.
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001652
1653 Args:
Simon Glassb82492b2021-01-30 22:17:46 -07001654 max_threads: Maximum number of threads we expect to need. If 0 then
1655 1 is set up, since the main process still needs somewhere to
1656 work
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001657 setup_git: True to set up a git worktree or a git clone
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001658 """
Simon Glass190064b2014-08-09 15:33:00 -06001659 builderthread.Mkdir(self._working_dir)
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001660 if setup_git and self.git_dir:
1661 src_dir = os.path.abspath(self.git_dir)
1662 if gitutil.CheckWorktreeIsAvailable(src_dir):
1663 setup_git = 'worktree'
1664 # If we previously added a worktree but the directory for it
1665 # got deleted, we need to prune its files from the repo so
1666 # that we can check out another in its place.
1667 gitutil.PruneWorktrees(src_dir)
1668 else:
1669 setup_git = 'clone'
Simon Glassb82492b2021-01-30 22:17:46 -07001670
1671 # Always do at least one thread
1672 for thread in range(max(max_threads, 1)):
Simon Glassfea58582014-08-09 15:32:59 -06001673 self._PrepareThread(thread, setup_git)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001674
Simon Glass925f6ad2020-03-18 09:42:45 -06001675 def _GetOutputSpaceRemovals(self):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001676 """Get the output directories ready to receive files.
1677
Simon Glass925f6ad2020-03-18 09:42:45 -06001678 Figure out what needs to be deleted in the output directory before it
1679 can be used. We only delete old buildman directories which have the
1680 expected name pattern. See _GetOutputDir().
1681
1682 Returns:
1683 List of full paths of directories to remove
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001684 """
Simon Glass1a915672014-12-01 17:33:53 -07001685 if not self.commits:
1686 return
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001687 dir_list = []
1688 for commit_upto in range(self.commit_count):
1689 dir_list.append(self._GetOutputDir(commit_upto))
1690
Simon Glassb222abe2016-09-18 16:48:32 -06001691 to_remove = []
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001692 for dirname in glob.glob(os.path.join(self.base_dir, '*')):
1693 if dirname not in dir_list:
Simon Glass925f6ad2020-03-18 09:42:45 -06001694 leaf = dirname[len(self.base_dir) + 1:]
Ovidiu Panait7664b032020-05-15 09:30:12 +03001695 m = re.match('[0-9]+_g[0-9a-f]+_.*', leaf)
Simon Glass925f6ad2020-03-18 09:42:45 -06001696 if m:
1697 to_remove.append(dirname)
1698 return to_remove
1699
1700 def _PrepareOutputSpace(self):
1701 """Get the output directories ready to receive files.
1702
1703 We delete any output directories which look like ones we need to
1704 create. Having left over directories is confusing when the user wants
1705 to check the output manually.
1706 """
1707 to_remove = self._GetOutputSpaceRemovals()
Simon Glassb222abe2016-09-18 16:48:32 -06001708 if to_remove:
Simon Glassb2d89bc2020-03-18 09:42:46 -06001709 Print('Removing %d old build directories...' % len(to_remove),
Simon Glassb222abe2016-09-18 16:48:32 -06001710 newline=False)
1711 for dirname in to_remove:
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001712 shutil.rmtree(dirname)
Simon Glass212c0b82020-04-09 15:08:43 -06001713 terminal.PrintClear()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001714
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001715 def BuildBoards(self, commits, board_selected, keep_outputs, verbose):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001716 """Build all commits for a list of boards
1717
1718 Args:
1719 commits: List of commits to be build, each a Commit object
1720 boards_selected: Dict of selected boards, key is target name,
1721 value is Board object
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001722 keep_outputs: True to save build output files
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001723 verbose: Display build results as they are completed
Simon Glass2c3deb92014-08-28 09:43:39 -06001724 Returns:
1725 Tuple containing:
1726 - number of boards that failed to build
1727 - number of boards that issued warnings
Simon Glass8116c782021-04-11 16:27:27 +12001728 - list of thread exceptions raised
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001729 """
Simon Glassfea58582014-08-09 15:32:59 -06001730 self.commit_count = len(commits) if commits else 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001731 self.commits = commits
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001732 self._verbose = verbose
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001733
1734 self.ResetResultSummary(board_selected)
Thierry Redingf3d015c2014-08-19 10:22:39 +02001735 builderthread.Mkdir(self.base_dir, parents = True)
Simon Glassfea58582014-08-09 15:32:59 -06001736 self._PrepareWorkingSpace(min(self.num_threads, len(board_selected)),
1737 commits is not None)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001738 self._PrepareOutputSpace()
Simon Glass745b3952016-09-18 16:48:33 -06001739 Print('\rStarting build...', newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001740 self.SetupBuild(board_selected, commits)
1741 self.ProcessResult(None)
Simon Glass8116c782021-04-11 16:27:27 +12001742 self.thread_exceptions = []
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001743 # Create jobs to build all commits for each board
Simon Glassc05aa032019-10-31 07:42:53 -06001744 for brd in board_selected.values():
Simon Glass190064b2014-08-09 15:33:00 -06001745 job = builderthread.BuilderJob()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001746 job.board = brd
1747 job.commits = commits
1748 job.keep_outputs = keep_outputs
Simon Glassd829f122020-03-18 09:42:42 -06001749 job.work_in_output = self.work_in_output
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001750 job.step = self._step
Simon Glassb82492b2021-01-30 22:17:46 -07001751 if self.num_threads:
1752 self.queue.put(job)
1753 else:
1754 results = self._single_builder.RunJob(job)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001755
Simon Glassb82492b2021-01-30 22:17:46 -07001756 if self.num_threads:
1757 term = threading.Thread(target=self.queue.join)
1758 term.setDaemon(True)
1759 term.start()
1760 while term.is_alive():
1761 term.join(100)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001762
Simon Glassb82492b2021-01-30 22:17:46 -07001763 # Wait until we have processed all output
1764 self.out_queue.join()
Simon Glass4653a882014-09-05 19:00:07 -06001765 Print()
Simon Glass7b33f212020-04-09 15:08:47 -06001766
1767 msg = 'Completed: %d total built' % self.count
1768 if self.already_done:
1769 msg += ' (%d previously' % self.already_done
1770 if self.already_done != self.count:
1771 msg += ', %d newly' % (self.count - self.already_done)
1772 msg += ')'
1773 duration = datetime.now() - self._start_time
1774 if duration > timedelta(microseconds=1000000):
1775 if duration.microseconds >= 500000:
1776 duration = duration + timedelta(seconds=1)
1777 duration = duration - timedelta(microseconds=duration.microseconds)
Simon Glass38f159c2020-07-19 12:40:26 -06001778 rate = float(self.count) / duration.total_seconds()
1779 msg += ', duration %s, rate %1.2f' % (duration, rate)
Simon Glass7b33f212020-04-09 15:08:47 -06001780 Print(msg)
Simon Glass8116c782021-04-11 16:27:27 +12001781 if self.thread_exceptions:
1782 Print('Failed: %d thread exceptions' % len(self.thread_exceptions),
1783 colour=self.col.RED)
Simon Glass7b33f212020-04-09 15:08:47 -06001784
Simon Glass8116c782021-04-11 16:27:27 +12001785 return (self.fail, self.warned, self.thread_exceptions)