blob: c794177f2baf7534a5f5e65ab1b6000728d23957 [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glassc05694f2013-04-03 11:07:16 +00002# Copyright (c) 2013 The Chromium OS Authors.
3#
4# Bloat-o-meter code used here Copyright 2004 Matt Mackall <mpm@selenic.com>
5#
Simon Glassc05694f2013-04-03 11:07:16 +00006
7import collections
Simon Glassc05694f2013-04-03 11:07:16 +00008from datetime import datetime, timedelta
9import glob
10import os
11import re
Simon Glassc78ed662019-10-31 07:42:53 -060012import queue
Simon Glassc05694f2013-04-03 11:07:16 +000013import shutil
Simon Glass205ac042016-09-18 16:48:37 -060014import signal
Simon Glassc05694f2013-04-03 11:07:16 +000015import string
16import sys
Simon Glassd26e1442016-09-18 16:48:35 -060017import threading
Simon Glassc05694f2013-04-03 11:07:16 +000018import time
19
Simon Glassf0d9c102020-04-17 18:09:02 -060020from buildman import builderthread
21from buildman import toolchain
Simon Glassa997ea52020-04-17 18:09:04 -060022from patman import gitutil
Simon Glass131444f2023-02-23 18:18:04 -070023from u_boot_pylib import command
24from u_boot_pylib import terminal
25from u_boot_pylib.terminal import tprint
Simon Glassc05694f2013-04-03 11:07:16 +000026
Simon Glass146b6022021-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 Glassdac73712023-10-23 00:52:43 -070038# Symbol types which appear in the bloat feature (-B). Others are silently
39# dropped when reading in the 'nm' output
40NM_SYMBOL_TYPES = 'tTdDbBr'
41
Simon Glassc05694f2013-04-03 11:07:16 +000042"""
43Theory of Operation
44
45Please see README for user documentation, and you should be familiar with
46that before trying to make sense of this.
47
48Buildman works by keeping the machine as busy as possible, building different
49commits for different boards on multiple CPUs at once.
50
51The source repo (self.git_dir) contains all the commits to be built. Each
52thread works on a single board at a time. It checks out the first commit,
53configures it for that board, then builds it. Then it checks out the next
54commit and builds it (typically without re-configuring). When it runs out
55of commits, it gets another job from the builder and starts again with that
56board.
57
58Clearly the builder threads could work either way - they could check out a
59commit and then built it for all boards. Using separate directories for each
60commit/board pair they could leave their build product around afterwards
61also.
62
63The intent behind building a single board for multiple commits, is to make
64use of incremental builds. Since each commit is built incrementally from
65the previous one, builds are faster. Reconfiguring for a different board
66removes all intermediate object files.
67
68Many threads can be working at once, but each has its own working directory.
69When a thread finishes a build, it puts the output files into a result
70directory.
71
72The base directory used by buildman is normally '../<branch>', i.e.
73a directory higher than the source repository and named after the branch
74being built.
75
76Within the base directory, we have one subdirectory for each commit. Within
77that is one subdirectory for each board. Within that is the build output for
78that commit/board combination.
79
80Buildman also create working directories for each thread, in a .bm-work/
81subdirectory in the base dir.
82
83As an example, say we are building branch 'us-net' for boards 'sandbox' and
84'seaboard', and say that us-net has two commits. We will have directories
85like this:
86
87us-net/ base directory
Ovidiu Panaitee8e9cb2020-05-15 09:30:12 +030088 01_g4ed4ebc_net--Add-tftp-speed-/
Simon Glassc05694f2013-04-03 11:07:16 +000089 sandbox/
90 u-boot.bin
91 seaboard/
92 u-boot.bin
Ovidiu Panaitee8e9cb2020-05-15 09:30:12 +030093 02_g4ed4ebc_net--Check-tftp-comp/
Simon Glassc05694f2013-04-03 11:07:16 +000094 sandbox/
95 u-boot.bin
96 seaboard/
97 u-boot.bin
98 .bm-work/
99 00/ working directory for thread 0 (contains source checkout)
100 build/ build output
101 01/ working directory for thread 1
102 build/ build output
103 ...
104u-boot/ source directory
105 .git/ repository
106"""
107
Simon Glassde0fefc2020-04-09 15:08:36 -0600108"""Holds information about a particular error line we are outputing
109
110 char: Character representation: '+': error, '-': fixed error, 'w+': warning,
111 'w-' = fixed warning
112 boards: List of Board objects which have line in the error/warning output
113 errline: The text of the error line
114"""
Simon Glass5df45222022-07-11 19:04:00 -0600115ErrLine = collections.namedtuple('ErrLine', 'char,brds,errline')
Simon Glassde0fefc2020-04-09 15:08:36 -0600116
Simon Glassc05694f2013-04-03 11:07:16 +0000117# Possible build outcomes
Simon Glassc78ed662019-10-31 07:42:53 -0600118OUTCOME_OK, OUTCOME_WARNING, OUTCOME_ERROR, OUTCOME_UNKNOWN = list(range(4))
Simon Glassc05694f2013-04-03 11:07:16 +0000119
Simon Glassd214bef2017-04-12 18:23:26 -0600120# Translate a commit subject into a valid filename (and handle unicode)
Simon Glassc78ed662019-10-31 07:42:53 -0600121trans_valid_chars = str.maketrans('/: ', '---')
Simon Glassc05694f2013-04-03 11:07:16 +0000122
Simon Glasscde5c302016-11-13 14:25:53 -0700123BASE_CONFIG_FILENAMES = [
124 'u-boot.cfg', 'u-boot-spl.cfg', 'u-boot-tpl.cfg'
125]
126
127EXTRA_CONFIG_FILENAMES = [
Simon Glassdb17fb82015-02-05 22:06:15 -0700128 '.config', '.config-spl', '.config-tpl',
129 'autoconf.mk', 'autoconf-spl.mk', 'autoconf-tpl.mk',
130 'autoconf.h', 'autoconf-spl.h','autoconf-tpl.h',
Simon Glassdb17fb82015-02-05 22:06:15 -0700131]
132
Simon Glasscad8abf2015-08-25 21:52:14 -0600133class Config:
134 """Holds information about configuration settings for a board."""
Simon Glasscde5c302016-11-13 14:25:53 -0700135 def __init__(self, config_filename, target):
Simon Glasscad8abf2015-08-25 21:52:14 -0600136 self.target = target
137 self.config = {}
Simon Glasscde5c302016-11-13 14:25:53 -0700138 for fname in config_filename:
Simon Glasscad8abf2015-08-25 21:52:14 -0600139 self.config[fname] = {}
140
Simon Glassbc74d942023-07-19 17:49:06 -0600141 def add(self, fname, key, value):
Simon Glasscad8abf2015-08-25 21:52:14 -0600142 self.config[fname][key] = value
143
144 def __hash__(self):
145 val = 0
146 for fname in self.config:
Simon Glassc78ed662019-10-31 07:42:53 -0600147 for key, value in self.config[fname].items():
148 print(key, value)
Simon Glasscad8abf2015-08-25 21:52:14 -0600149 val = val ^ hash(key) & hash(value)
150 return val
Simon Glassc05694f2013-04-03 11:07:16 +0000151
Alex Kiernan4059e302018-05-31 04:48:34 +0000152class Environment:
153 """Holds information about environment variables for a board."""
154 def __init__(self, target):
155 self.target = target
156 self.environment = {}
157
Simon Glassbc74d942023-07-19 17:49:06 -0600158 def add(self, key, value):
Alex Kiernan4059e302018-05-31 04:48:34 +0000159 self.environment[key] = value
160
Simon Glassc05694f2013-04-03 11:07:16 +0000161class Builder:
162 """Class for building U-Boot for a particular commit.
163
164 Public members: (many should ->private)
Simon Glassc05694f2013-04-03 11:07:16 +0000165 already_done: Number of builds already completed
166 base_dir: Base directory to use for builder
167 checkout: True to check out source, False to skip that step.
168 This is used for testing.
169 col: terminal.Color() object
Simon Glassf56cc292023-07-19 17:49:03 -0600170 count: Total number of commits to build, which is the number of commits
171 multiplied by the number of boards
Simon Glassc05694f2013-04-03 11:07:16 +0000172 do_make: Method to call to invoke Make
173 fail: Number of builds that failed due to error
174 force_build: Force building even if a build already exists
175 force_config_on_failure: If a commit fails for a board, disable
176 incremental building for the next commit we build for that
177 board, so that we will see all warnings/errors again.
Simon Glass7041c392014-07-13 12:22:31 -0600178 force_build_failures: If a previously-built build (i.e. built on
179 a previous run of buildman) is marked as failed, rebuild it.
Simon Glassc05694f2013-04-03 11:07:16 +0000180 git_dir: Git directory containing source repository
Simon Glassc05694f2013-04-03 11:07:16 +0000181 num_jobs: Number of jobs to run at once (passed to make as -j)
182 num_threads: Number of builder threads to run
183 out_queue: Queue of results to process
184 re_make_err: Compiled regular expression for ignore_lines
185 queue: Queue of jobs to run
186 threads: List of active threads
187 toolchains: Toolchains object to use for building
188 upto: Current commit number we are building (0.count-1)
189 warned: Number of builds that produced at least one warning
Simon Glassf3018b7a2014-07-14 17:51:02 -0600190 force_reconfig: Reconfigure U-Boot on each comiit. This disables
191 incremental building, where buildman reconfigures on the first
192 commit for a baord, and then just does an incremental build for
193 the following commits. In fact buildman will reconfigure and
194 retry for any failing commits, so generally the only effect of
195 this option is to slow things down.
Simon Glass38df2e22014-07-14 17:51:03 -0600196 in_tree: Build U-Boot in-tree instead of specifying an output
197 directory separate from the source code. This option is really
198 only useful for testing in-tree builds.
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600199 work_in_output: Use the output directory as the work directory and
200 don't write to a separate output directory.
Simon Glass9bf9a722021-04-11 16:27:27 +1200201 thread_exceptions: List of exceptions raised by thread jobs
Simon Glassf6bfcca2023-02-21 12:40:28 -0700202 no_lto (bool): True to set the NO_LTO flag when building
Simon Glass828d70d2023-02-21 12:40:29 -0700203 reproducible_builds (bool): True to set SOURCE_DATE_EPOCH=0 for builds
Simon Glassc05694f2013-04-03 11:07:16 +0000204
205 Private members:
206 _base_board_dict: Last-summarised Dict of boards
207 _base_err_lines: Last-summarised list of errors
Simon Glass03749d42014-08-28 09:43:44 -0600208 _base_warn_lines: Last-summarised list of warnings
Simon Glassc05694f2013-04-03 11:07:16 +0000209 _build_period_us: Time taken for a single build (float object).
210 _complete_delay: Expected delay until completion (timedelta)
211 _next_delay_update: Next time we plan to display a progress update
212 (datatime)
213 _show_unknown: Show unknown boards (those not built) in summary
Simon Glass726ae812020-04-09 15:08:47 -0600214 _start_time: Start time for the build
Simon Glassc05694f2013-04-03 11:07:16 +0000215 _timestamps: List of timestamps for the completion of the last
216 last _timestamp_count builds. Each is a datetime object.
217 _timestamp_count: Number of timestamps to keep in our list.
218 _working_dir: Base working directory containing all threads
Simon Glassc635d892021-01-30 22:17:46 -0700219 _single_builder: BuilderThread object for the singer builder, if
220 threading is not being used
Simon Glass146b6022021-10-19 21:43:24 -0600221 _terminated: Thread was terminated due to an error
222 _restarting_config: True if 'Restart config' is detected in output
Simon Glass6c435622022-07-11 19:03:56 -0600223 _ide: Produce output suitable for an Integrated Development Environment,
224 i.e. dont emit progress information and put errors/warnings on stderr
Simon Glassc05694f2013-04-03 11:07:16 +0000225 """
226 class Outcome:
227 """Records a build outcome for a single make invocation
228
229 Public Members:
230 rc: Outcome value (OUTCOME_...)
231 err_lines: List of error lines or [] if none
232 sizes: Dictionary of image size information, keyed by filename
233 - Each value is itself a dictionary containing
234 values for 'text', 'data' and 'bss', being the integer
235 size in bytes of each section.
236 func_sizes: Dictionary keyed by filename - e.g. 'u-boot'. Each
237 value is itself a dictionary:
238 key: function name
239 value: Size of function in bytes
Simon Glassdb17fb82015-02-05 22:06:15 -0700240 config: Dictionary keyed by filename - e.g. '.config'. Each
241 value is itself a dictionary:
242 key: config name
243 value: config value
Alex Kiernan4059e302018-05-31 04:48:34 +0000244 environment: Dictionary keyed by environment variable, Each
245 value is the value of environment variable.
Simon Glassc05694f2013-04-03 11:07:16 +0000246 """
Alex Kiernan4059e302018-05-31 04:48:34 +0000247 def __init__(self, rc, err_lines, sizes, func_sizes, config,
248 environment):
Simon Glassc05694f2013-04-03 11:07:16 +0000249 self.rc = rc
250 self.err_lines = err_lines
251 self.sizes = sizes
252 self.func_sizes = func_sizes
Simon Glassdb17fb82015-02-05 22:06:15 -0700253 self.config = config
Alex Kiernan4059e302018-05-31 04:48:34 +0000254 self.environment = environment
Simon Glassc05694f2013-04-03 11:07:16 +0000255
256 def __init__(self, toolchains, base_dir, git_dir, num_threads, num_jobs,
Simon Glasse87bde12014-12-01 17:33:55 -0700257 gnu_make='make', checkout=True, show_unknown=True, step=1,
Simon Glassf498efc2024-06-23 11:56:20 -0600258 no_subdirs=False, verbose_build=False,
Simon Glass222825b2024-06-23 11:55:13 -0600259 mrproper=False, fallback_mrproper=False,
260 per_board_out_dir=False, config_only=False,
261 squash_config_y=False, warnings_as_errors=False,
262 work_in_output=False, test_thread_exceptions=False,
263 adjust_cfg=None, allow_missing=False, no_lto=False,
264 reproducible_builds=False, force_build=False,
265 force_build_failures=False, force_reconfig=False,
266 in_tree=False, force_config_on_failure=False, make_func=None):
Simon Glassc05694f2013-04-03 11:07:16 +0000267 """Create a new Builder object
268
269 Args:
270 toolchains: Toolchains object to use for building
271 base_dir: Base directory to use for builder
272 git_dir: Git directory containing source repository
273 num_threads: Number of builder threads to run
274 num_jobs: Number of jobs to run at once (passed to make as -j)
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900275 gnu_make: the command name of GNU Make.
Simon Glassc05694f2013-04-03 11:07:16 +0000276 checkout: True to check out source, False to skip that step.
277 This is used for testing.
278 show_unknown: Show unknown boards (those not built) in summary
279 step: 1 to process every commit, n to process every nth commit
Simon Glassd48a46c2014-12-01 17:34:00 -0700280 no_subdirs: Don't create subdirectories when building current
281 source for a single board
Simon Glass655b6102014-12-01 17:34:07 -0700282 verbose_build: Run build with V=1 and don't use 'make -s'
Simon Glass6029af12020-04-09 15:08:51 -0600283 mrproper: Always run 'make mrproper' when configuring
Simon Glass222825b2024-06-23 11:55:13 -0600284 fallback_mrproper: Run 'make mrproper' and retry on build failure
Stephen Warren97c96902016-04-11 10:48:44 -0600285 per_board_out_dir: Build in a separate persistent directory per
286 board rather than a thread-specific directory
Simon Glass739e8512016-11-13 14:25:51 -0700287 config_only: Only configure each build, don't build it
Simon Glasscde5c302016-11-13 14:25:53 -0700288 squash_config_y: Convert CONFIG options with the value 'y' to '1'
Daniel Schwierzeck20e2ea92018-01-26 16:31:05 +0100289 warnings_as_errors: Treat all compiler warnings as errors
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600290 work_in_output: Use the output directory as the work directory and
291 don't write to a separate output directory.
Simon Glass9bf9a722021-04-11 16:27:27 +1200292 test_thread_exceptions: Uses for tests only, True to make the
293 threads raise an exception instead of reporting their result.
294 This simulates a failure in the code somewhere
Simon Glasse5650a82022-01-22 05:07:33 -0700295 adjust_cfg_list (list of str): List of changes to make to .config
296 file before building. Each is one of (where C is the config
297 option with or without the CONFIG_ prefix)
298
299 C to enable C
300 ~C to disable C
301 C=val to set the value of C (val must have quotes if C is
302 a string Kconfig
Tom Rini93ebd462022-11-09 19:14:53 -0700303 allow_missing: Run build with BINMAN_ALLOW_MISSING=1
Simon Glassf6bfcca2023-02-21 12:40:28 -0700304 no_lto (bool): True to set the NO_LTO flag when building
Simon Glasscf91d312023-07-19 17:48:52 -0600305 force_build (bool): Rebuild even commits that are already built
306 force_build_failures (bool): Rebuild commits that have not been
307 built, or failed to build
308 force_reconfig (bool): Reconfigure on each commit
309 in_tree (bool): Bulid in tree instead of out-of-tree
310 force_config_on_failure (bool): Reconfigure the build before
311 retrying a failed build
312 make_func (function): Function to call to run 'make'
Simon Glassc05694f2013-04-03 11:07:16 +0000313 """
314 self.toolchains = toolchains
315 self.base_dir = base_dir
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600316 if work_in_output:
317 self._working_dir = base_dir
318 else:
319 self._working_dir = os.path.join(base_dir, '.bm-work')
Simon Glassc05694f2013-04-03 11:07:16 +0000320 self.threads = []
Simon Glassbc74d942023-07-19 17:49:06 -0600321 self.do_make = make_func or self.make
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900322 self.gnu_make = gnu_make
Simon Glassc05694f2013-04-03 11:07:16 +0000323 self.checkout = checkout
324 self.num_threads = num_threads
325 self.num_jobs = num_jobs
326 self.already_done = 0
327 self.force_build = False
328 self.git_dir = git_dir
329 self._show_unknown = show_unknown
330 self._timestamp_count = 10
331 self._build_period_us = None
332 self._complete_delay = None
333 self._next_delay_update = datetime.now()
Simon Glass7190a172023-09-07 10:00:19 -0600334 self._start_time = None
Simon Glassc05694f2013-04-03 11:07:16 +0000335 self._step = step
Simon Glassbb4dffb2014-08-09 15:33:06 -0600336 self._error_lines = 0
Simon Glasse87bde12014-12-01 17:33:55 -0700337 self.no_subdirs = no_subdirs
Simon Glass655b6102014-12-01 17:34:07 -0700338 self.verbose_build = verbose_build
Simon Glass739e8512016-11-13 14:25:51 -0700339 self.config_only = config_only
Simon Glasscde5c302016-11-13 14:25:53 -0700340 self.squash_config_y = squash_config_y
341 self.config_filenames = BASE_CONFIG_FILENAMES
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600342 self.work_in_output = work_in_output
Simon Glasse5650a82022-01-22 05:07:33 -0700343 self.adjust_cfg = adjust_cfg
Tom Rini93ebd462022-11-09 19:14:53 -0700344 self.allow_missing = allow_missing
Simon Glass6c435622022-07-11 19:03:56 -0600345 self._ide = False
Simon Glassf6bfcca2023-02-21 12:40:28 -0700346 self.no_lto = no_lto
Simon Glass828d70d2023-02-21 12:40:29 -0700347 self.reproducible_builds = reproducible_builds
Simon Glasscf91d312023-07-19 17:48:52 -0600348 self.force_build = force_build
349 self.force_build_failures = force_build_failures
350 self.force_reconfig = force_reconfig
351 self.in_tree = in_tree
352 self.force_config_on_failure = force_config_on_failure
Simon Glass222825b2024-06-23 11:55:13 -0600353 self.fallback_mrproper = fallback_mrproper
Simon Glasse5650a82022-01-22 05:07:33 -0700354
Simon Glasscde5c302016-11-13 14:25:53 -0700355 if not self.squash_config_y:
356 self.config_filenames += EXTRA_CONFIG_FILENAMES
Simon Glass146b6022021-10-19 21:43:24 -0600357 self._terminated = False
358 self._restarting_config = False
Simon Glassc05694f2013-04-03 11:07:16 +0000359
Daniel Schwierzeck20e2ea92018-01-26 16:31:05 +0100360 self.warnings_as_errors = warnings_as_errors
Simon Glassc05694f2013-04-03 11:07:16 +0000361 self.col = terminal.Color()
362
Simon Glass03749d42014-08-28 09:43:44 -0600363 self._re_function = re.compile('(.*): In function.*')
364 self._re_files = re.compile('In file included from.*')
365 self._re_warning = re.compile('(.*):(\d*):(\d*): warning: .*')
Simon Glass0db94432018-11-06 16:02:11 -0700366 self._re_dtb_warning = re.compile('(.*): Warning .*')
Simon Glass03749d42014-08-28 09:43:44 -0600367 self._re_note = re.compile('(.*):(\d*):(\d*): note: this is the location of the previous.*')
Simon Glassf4ebfba2020-04-09 15:08:53 -0600368 self._re_migration_warning = re.compile(r'^={21} WARNING ={22}\n.*\n=+\n',
369 re.MULTILINE | re.DOTALL)
Simon Glass03749d42014-08-28 09:43:44 -0600370
Simon Glass9bf9a722021-04-11 16:27:27 +1200371 self.thread_exceptions = []
372 self.test_thread_exceptions = test_thread_exceptions
Simon Glassc635d892021-01-30 22:17:46 -0700373 if self.num_threads:
374 self._single_builder = None
375 self.queue = queue.Queue()
376 self.out_queue = queue.Queue()
377 for i in range(self.num_threads):
Simon Glass9bf9a722021-04-11 16:27:27 +1200378 t = builderthread.BuilderThread(
379 self, i, mrproper, per_board_out_dir,
380 test_exception=test_thread_exceptions)
Simon Glassc635d892021-01-30 22:17:46 -0700381 t.setDaemon(True)
382 t.start()
383 self.threads.append(t)
384
385 t = builderthread.ResultThread(self)
Simon Glassc05694f2013-04-03 11:07:16 +0000386 t.setDaemon(True)
387 t.start()
388 self.threads.append(t)
Simon Glassc635d892021-01-30 22:17:46 -0700389 else:
390 self._single_builder = builderthread.BuilderThread(
391 self, -1, mrproper, per_board_out_dir)
Simon Glassc05694f2013-04-03 11:07:16 +0000392
393 ignore_lines = ['(make.*Waiting for unfinished)', '(Segmentation fault)']
394 self.re_make_err = re.compile('|'.join(ignore_lines))
395
Simon Glass205ac042016-09-18 16:48:37 -0600396 # Handle existing graceful with SIGINT / Ctrl-C
397 signal.signal(signal.SIGINT, self.signal_handler)
398
Simon Glassc05694f2013-04-03 11:07:16 +0000399 def __del__(self):
400 """Get rid of all threads created by the builder"""
401 for t in self.threads:
402 del t
403
Simon Glass205ac042016-09-18 16:48:37 -0600404 def signal_handler(self, signal, frame):
405 sys.exit(1)
406
Simon Glassbc74d942023-07-19 17:49:06 -0600407 def set_display_options(self, show_errors=False, show_sizes=False,
Simon Glass3394c9f2014-08-28 09:43:43 -0600408 show_detail=False, show_bloat=False,
Alex Kiernan4059e302018-05-31 04:48:34 +0000409 list_error_boards=False, show_config=False,
Simon Glassf4ebfba2020-04-09 15:08:53 -0600410 show_environment=False, filter_dtb_warnings=False,
Simon Glass6c435622022-07-11 19:03:56 -0600411 filter_migration_warnings=False, ide=False):
Simon Glasseb48bbc2014-08-09 15:33:02 -0600412 """Setup display options for the builder.
413
Simon Glass9ea93812020-04-09 15:08:52 -0600414 Args:
415 show_errors: True to show summarised error/warning info
416 show_sizes: Show size deltas
417 show_detail: Show size delta detail for each board if show_sizes
418 show_bloat: Show detail for each function
419 list_error_boards: Show the boards which caused each error/warning
420 show_config: Show config deltas
421 show_environment: Show environment deltas
422 filter_dtb_warnings: Filter out any warnings from the device-tree
423 compiler
Simon Glassf4ebfba2020-04-09 15:08:53 -0600424 filter_migration_warnings: Filter out any warnings about migrating
425 a board to driver model
Simon Glass6c435622022-07-11 19:03:56 -0600426 ide: Create output that can be parsed by an IDE. There is no '+' prefix on
427 error lines and output on stderr stays on stderr.
Simon Glasseb48bbc2014-08-09 15:33:02 -0600428 """
429 self._show_errors = show_errors
430 self._show_sizes = show_sizes
431 self._show_detail = show_detail
432 self._show_bloat = show_bloat
Simon Glass3394c9f2014-08-28 09:43:43 -0600433 self._list_error_boards = list_error_boards
Simon Glassdb17fb82015-02-05 22:06:15 -0700434 self._show_config = show_config
Alex Kiernan4059e302018-05-31 04:48:34 +0000435 self._show_environment = show_environment
Simon Glass9ea93812020-04-09 15:08:52 -0600436 self._filter_dtb_warnings = filter_dtb_warnings
Simon Glassf4ebfba2020-04-09 15:08:53 -0600437 self._filter_migration_warnings = filter_migration_warnings
Simon Glass6c435622022-07-11 19:03:56 -0600438 self._ide = ide
Simon Glasseb48bbc2014-08-09 15:33:02 -0600439
Simon Glassbc74d942023-07-19 17:49:06 -0600440 def _add_timestamp(self):
Simon Glassc05694f2013-04-03 11:07:16 +0000441 """Add a new timestamp to the list and record the build period.
442
443 The build period is the length of time taken to perform a single
444 build (one board, one commit).
445 """
446 now = datetime.now()
447 self._timestamps.append(now)
448 count = len(self._timestamps)
449 delta = self._timestamps[-1] - self._timestamps[0]
450 seconds = delta.total_seconds()
451
452 # If we have enough data, estimate build period (time taken for a
453 # single build) and therefore completion time.
454 if count > 1 and self._next_delay_update < now:
455 self._next_delay_update = now + timedelta(seconds=2)
456 if seconds > 0:
457 self._build_period = float(seconds) / count
458 todo = self.count - self.upto
459 self._complete_delay = timedelta(microseconds=
460 self._build_period * todo * 1000000)
461 # Round it
462 self._complete_delay -= timedelta(
463 microseconds=self._complete_delay.microseconds)
464
465 if seconds > 60:
466 self._timestamps.popleft()
467 count -= 1
468
Simon Glassbc74d942023-07-19 17:49:06 -0600469 def select_commit(self, commit, checkout=True):
Simon Glassc05694f2013-04-03 11:07:16 +0000470 """Checkout the selected commit for this build
471 """
472 self.commit = commit
473 if checkout and self.checkout:
Simon Glass761648b2022-01-29 14:14:11 -0700474 gitutil.checkout(commit.hash)
Simon Glassc05694f2013-04-03 11:07:16 +0000475
Simon Glassbc74d942023-07-19 17:49:06 -0600476 def make(self, commit, brd, stage, cwd, *args, **kwargs):
Simon Glassc05694f2013-04-03 11:07:16 +0000477 """Run make
478
479 Args:
480 commit: Commit object that is being built
481 brd: Board object that is being built
Simon Glassd6c1ec82023-10-26 14:31:10 -0400482 stage: Stage that we are at (mrproper, config, oldconfig, build)
Simon Glassc05694f2013-04-03 11:07:16 +0000483 cwd: Directory where make should be run
484 args: Arguments to pass to make
Simon Glass840be732022-01-29 14:14:05 -0700485 kwargs: Arguments to pass to command.run_pipe()
Simon Glassc05694f2013-04-03 11:07:16 +0000486 """
Simon Glass146b6022021-10-19 21:43:24 -0600487
488 def check_output(stream, data):
489 if b'Restart config' in data:
490 self._restarting_config = True
491
492 # If we see 'Restart config' following by multiple errors
493 if self._restarting_config:
494 m = RE_NO_DEFAULT.findall(data)
495
496 # Number of occurences of each Kconfig item
497 multiple = [m.count(val) for val in set(m)]
498
499 # If any of them occur more than once, we have a loop
500 if [val for val in multiple if val > 1]:
501 self._terminated = True
502 return True
503 return False
504
505 self._restarting_config = False
506 self._terminated = False
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900507 cmd = [self.gnu_make] + list(args)
Simon Glass840be732022-01-29 14:14:05 -0700508 result = command.run_pipe([cmd], capture=True, capture_stderr=True,
Simon Glass146b6022021-10-19 21:43:24 -0600509 cwd=cwd, raise_on_error=False, infile='/dev/null',
510 output_func=check_output, **kwargs)
511
512 if self._terminated:
513 # Try to be helpful
514 result.stderr += '(** did you define an int/hex Kconfig with no default? **)'
515
Simon Glass413f91a2015-02-05 22:06:12 -0700516 if self.verbose_build:
517 result.stdout = '%s\n' % (' '.join(cmd)) + result.stdout
518 result.combined = '%s\n' % (' '.join(cmd)) + result.combined
Simon Glassc05694f2013-04-03 11:07:16 +0000519 return result
520
Simon Glassbc74d942023-07-19 17:49:06 -0600521 def process_result(self, result):
Simon Glassc05694f2013-04-03 11:07:16 +0000522 """Process the result of a build, showing progress information
523
524 Args:
Simon Glass78e418e2014-08-09 15:33:03 -0600525 result: A CommandResult object, which indicates the result for
526 a single build
Simon Glassc05694f2013-04-03 11:07:16 +0000527 """
528 col = terminal.Color()
529 if result:
530 target = result.brd.target
531
Simon Glassc05694f2013-04-03 11:07:16 +0000532 self.upto += 1
533 if result.return_code != 0:
534 self.fail += 1
535 elif result.stderr:
536 self.warned += 1
537 if result.already_done:
538 self.already_done += 1
Simon Glass78e418e2014-08-09 15:33:03 -0600539 if self._verbose:
Simon Glass02811582022-01-29 14:14:18 -0700540 terminal.print_clear()
Simon Glass78e418e2014-08-09 15:33:03 -0600541 boards_selected = {target : result.brd}
Simon Glassbc74d942023-07-19 17:49:06 -0600542 self.reset_result_summary(boards_selected)
543 self.produce_result_summary(result.commit_upto, self.commits,
Simon Glass78e418e2014-08-09 15:33:03 -0600544 boards_selected)
Simon Glassc05694f2013-04-03 11:07:16 +0000545 else:
546 target = '(starting)'
547
548 # Display separate counts for ok, warned and fail
549 ok = self.upto - self.warned - self.fail
Simon Glassf45d3742022-01-29 14:14:17 -0700550 line = '\r' + self.col.build(self.col.GREEN, '%5d' % ok)
551 line += self.col.build(self.col.YELLOW, '%5d' % self.warned)
552 line += self.col.build(self.col.RED, '%5d' % self.fail)
Simon Glassc05694f2013-04-03 11:07:16 +0000553
Simon Glass69c3a8a2020-04-09 15:08:45 -0600554 line += ' /%-5d ' % self.count
555 remaining = self.count - self.upto
556 if remaining:
Simon Glassf45d3742022-01-29 14:14:17 -0700557 line += self.col.build(self.col.MAGENTA, ' -%-5d ' % remaining)
Simon Glass69c3a8a2020-04-09 15:08:45 -0600558 else:
559 line += ' ' * 8
Simon Glassc05694f2013-04-03 11:07:16 +0000560
561 # Add our current completion time estimate
Simon Glassbc74d942023-07-19 17:49:06 -0600562 self._add_timestamp()
Simon Glassc05694f2013-04-03 11:07:16 +0000563 if self._complete_delay:
Simon Glass69c3a8a2020-04-09 15:08:45 -0600564 line += '%s : ' % self._complete_delay
Simon Glassc05694f2013-04-03 11:07:16 +0000565
Simon Glass69c3a8a2020-04-09 15:08:45 -0600566 line += target
Simon Glass6c435622022-07-11 19:03:56 -0600567 if not self._ide:
568 terminal.print_clear()
569 tprint(line, newline=False, limit_to_line=True)
Simon Glassc05694f2013-04-03 11:07:16 +0000570
Simon Glass4cb54682023-07-19 17:49:10 -0600571 def get_output_dir(self, commit_upto):
Simon Glassc05694f2013-04-03 11:07:16 +0000572 """Get the name of the output directory for a commit number
573
574 The output directory is typically .../<branch>/<commit>.
575
576 Args:
577 commit_upto: Commit number to use (0..self.count-1)
578 """
Simon Glasse3c85ab2020-04-17 17:51:34 -0600579 if self.work_in_output:
580 return self._working_dir
581
Simon Glasse87bde12014-12-01 17:33:55 -0700582 commit_dir = None
Simon Glassd326ad72014-08-09 15:32:59 -0600583 if self.commits:
584 commit = self.commits[commit_upto]
585 subject = commit.subject.translate(trans_valid_chars)
Simon Glassbc74d942023-07-19 17:49:06 -0600586 # See _get_output_space_removals() which parses this name
Ovidiu Panaitee8e9cb2020-05-15 09:30:12 +0300587 commit_dir = ('%02d_g%s_%s' % (commit_upto + 1,
588 commit.hash, subject[:20]))
Simon Glasse87bde12014-12-01 17:33:55 -0700589 elif not self.no_subdirs:
Simon Glassd326ad72014-08-09 15:32:59 -0600590 commit_dir = 'current'
Simon Glasse87bde12014-12-01 17:33:55 -0700591 if not commit_dir:
592 return self.base_dir
593 return os.path.join(self.base_dir, commit_dir)
Simon Glassc05694f2013-04-03 11:07:16 +0000594
Simon Glassbc74d942023-07-19 17:49:06 -0600595 def get_build_dir(self, commit_upto, target):
Simon Glassc05694f2013-04-03 11:07:16 +0000596 """Get the name of the build directory for a commit number
597
598 The build directory is typically .../<branch>/<commit>/<target>.
599
600 Args:
601 commit_upto: Commit number to use (0..self.count-1)
602 target: Target name
603 """
Simon Glass4cb54682023-07-19 17:49:10 -0600604 output_dir = self.get_output_dir(commit_upto)
Simon Glasse3c85ab2020-04-17 17:51:34 -0600605 if self.work_in_output:
606 return output_dir
Simon Glassc05694f2013-04-03 11:07:16 +0000607 return os.path.join(output_dir, target)
608
Simon Glassbc74d942023-07-19 17:49:06 -0600609 def get_done_file(self, commit_upto, target):
Simon Glassc05694f2013-04-03 11:07:16 +0000610 """Get the name of the done file for a commit number
611
612 Args:
613 commit_upto: Commit number to use (0..self.count-1)
614 target: Target name
615 """
Simon Glassbc74d942023-07-19 17:49:06 -0600616 return os.path.join(self.get_build_dir(commit_upto, target), 'done')
Simon Glassc05694f2013-04-03 11:07:16 +0000617
Simon Glassbc74d942023-07-19 17:49:06 -0600618 def get_sizes_file(self, commit_upto, target):
Simon Glassc05694f2013-04-03 11:07:16 +0000619 """Get the name of the sizes file for a commit number
620
621 Args:
622 commit_upto: Commit number to use (0..self.count-1)
623 target: Target name
624 """
Simon Glassbc74d942023-07-19 17:49:06 -0600625 return os.path.join(self.get_build_dir(commit_upto, target), 'sizes')
Simon Glassc05694f2013-04-03 11:07:16 +0000626
Simon Glassbc74d942023-07-19 17:49:06 -0600627 def get_func_sizes_file(self, commit_upto, target, elf_fname):
Simon Glassc05694f2013-04-03 11:07:16 +0000628 """Get the name of the funcsizes file for a commit number and ELF file
629
630 Args:
631 commit_upto: Commit number to use (0..self.count-1)
632 target: Target name
633 elf_fname: Filename of elf image
634 """
Simon Glassbc74d942023-07-19 17:49:06 -0600635 return os.path.join(self.get_build_dir(commit_upto, target),
Simon Glassc05694f2013-04-03 11:07:16 +0000636 '%s.sizes' % elf_fname.replace('/', '-'))
637
Simon Glassbc74d942023-07-19 17:49:06 -0600638 def get_objdump_file(self, commit_upto, target, elf_fname):
Simon Glassc05694f2013-04-03 11:07:16 +0000639 """Get the name of the objdump file for a commit number and ELF file
640
641 Args:
642 commit_upto: Commit number to use (0..self.count-1)
643 target: Target name
644 elf_fname: Filename of elf image
645 """
Simon Glassbc74d942023-07-19 17:49:06 -0600646 return os.path.join(self.get_build_dir(commit_upto, target),
Simon Glassc05694f2013-04-03 11:07:16 +0000647 '%s.objdump' % elf_fname.replace('/', '-'))
648
Simon Glassbc74d942023-07-19 17:49:06 -0600649 def get_err_file(self, commit_upto, target):
Simon Glassc05694f2013-04-03 11:07:16 +0000650 """Get the name of the err file for a commit number
651
652 Args:
653 commit_upto: Commit number to use (0..self.count-1)
654 target: Target name
655 """
Simon Glassbc74d942023-07-19 17:49:06 -0600656 output_dir = self.get_build_dir(commit_upto, target)
Simon Glassc05694f2013-04-03 11:07:16 +0000657 return os.path.join(output_dir, 'err')
658
Simon Glassbc74d942023-07-19 17:49:06 -0600659 def filter_errors(self, lines):
Simon Glassc05694f2013-04-03 11:07:16 +0000660 """Filter out errors in which we have no interest
661
662 We should probably use map().
663
664 Args:
665 lines: List of error lines, each a string
666 Returns:
667 New list with only interesting lines included
668 """
669 out_lines = []
Simon Glassf4ebfba2020-04-09 15:08:53 -0600670 if self._filter_migration_warnings:
671 text = '\n'.join(lines)
672 text = self._re_migration_warning.sub('', text)
673 lines = text.splitlines()
Simon Glassc05694f2013-04-03 11:07:16 +0000674 for line in lines:
Simon Glass9ea93812020-04-09 15:08:52 -0600675 if self.re_make_err.search(line):
676 continue
677 if self._filter_dtb_warnings and self._re_dtb_warning.search(line):
678 continue
679 out_lines.append(line)
Simon Glassc05694f2013-04-03 11:07:16 +0000680 return out_lines
681
Simon Glassbc74d942023-07-19 17:49:06 -0600682 def read_func_sizes(self, fname, fd):
Simon Glassc05694f2013-04-03 11:07:16 +0000683 """Read function sizes from the output of 'nm'
684
685 Args:
686 fd: File containing data to read
687 fname: Filename we are reading from (just for errors)
688
689 Returns:
690 Dictionary containing size of each function in bytes, indexed by
691 function name.
692 """
693 sym = {}
694 for line in fd.readlines():
Simon Glass86a2afe2022-07-11 19:04:11 -0600695 line = line.strip()
696 parts = line.split()
697 if line and len(parts) == 3:
698 size, type, name = line.split()
Simon Glassdac73712023-10-23 00:52:43 -0700699 if type in NM_SYMBOL_TYPES:
Simon Glass86a2afe2022-07-11 19:04:11 -0600700 # function names begin with '.' on 64-bit powerpc
701 if '.' in name[1:]:
702 name = 'static.' + name.split('.')[0]
703 sym[name] = sym.get(name, 0) + int(size, 16)
Simon Glassc05694f2013-04-03 11:07:16 +0000704 return sym
705
Simon Glassbc74d942023-07-19 17:49:06 -0600706 def _process_config(self, fname):
Simon Glassdb17fb82015-02-05 22:06:15 -0700707 """Read in a .config, autoconf.mk or autoconf.h file
708
709 This function handles all config file types. It ignores comments and
710 any #defines which don't start with CONFIG_.
711
712 Args:
713 fname: Filename to read
714
715 Returns:
716 Dictionary:
717 key: Config name (e.g. CONFIG_DM)
718 value: Config value (e.g. 1)
719 """
720 config = {}
721 if os.path.exists(fname):
722 with open(fname) as fd:
723 for line in fd:
724 line = line.strip()
725 if line.startswith('#define'):
726 values = line[8:].split(' ', 1)
727 if len(values) > 1:
728 key, value = values
729 else:
730 key = values[0]
Simon Glasscde5c302016-11-13 14:25:53 -0700731 value = '1' if self.squash_config_y else ''
Simon Glassdb17fb82015-02-05 22:06:15 -0700732 if not key.startswith('CONFIG_'):
733 continue
734 elif not line or line[0] in ['#', '*', '/']:
735 continue
736 else:
737 key, value = line.split('=', 1)
Simon Glasscde5c302016-11-13 14:25:53 -0700738 if self.squash_config_y and value == 'y':
739 value = '1'
Simon Glassdb17fb82015-02-05 22:06:15 -0700740 config[key] = value
741 return config
742
Simon Glassbc74d942023-07-19 17:49:06 -0600743 def _process_environment(self, fname):
Alex Kiernan4059e302018-05-31 04:48:34 +0000744 """Read in a uboot.env file
745
746 This function reads in environment variables from a file.
747
748 Args:
749 fname: Filename to read
750
751 Returns:
752 Dictionary:
753 key: environment variable (e.g. bootlimit)
754 value: value of environment variable (e.g. 1)
755 """
756 environment = {}
757 if os.path.exists(fname):
758 with open(fname) as fd:
759 for line in fd.read().split('\0'):
760 try:
761 key, value = line.split('=', 1)
762 environment[key] = value
763 except ValueError:
764 # ignore lines we can't parse
765 pass
766 return environment
767
Simon Glassbc74d942023-07-19 17:49:06 -0600768 def get_build_outcome(self, commit_upto, target, read_func_sizes,
Alex Kiernan4059e302018-05-31 04:48:34 +0000769 read_config, read_environment):
Simon Glassc05694f2013-04-03 11:07:16 +0000770 """Work out the outcome of a build.
771
772 Args:
773 commit_upto: Commit number to check (0..n-1)
774 target: Target board to check
775 read_func_sizes: True to read function size information
Simon Glassdb17fb82015-02-05 22:06:15 -0700776 read_config: True to read .config and autoconf.h files
Alex Kiernan4059e302018-05-31 04:48:34 +0000777 read_environment: True to read uboot.env files
Simon Glassc05694f2013-04-03 11:07:16 +0000778
779 Returns:
780 Outcome object
781 """
Simon Glassbc74d942023-07-19 17:49:06 -0600782 done_file = self.get_done_file(commit_upto, target)
783 sizes_file = self.get_sizes_file(commit_upto, target)
Simon Glassc05694f2013-04-03 11:07:16 +0000784 sizes = {}
785 func_sizes = {}
Simon Glassdb17fb82015-02-05 22:06:15 -0700786 config = {}
Alex Kiernan4059e302018-05-31 04:48:34 +0000787 environment = {}
Simon Glassc05694f2013-04-03 11:07:16 +0000788 if os.path.exists(done_file):
789 with open(done_file, 'r') as fd:
Simon Glass91c54a42019-04-26 19:02:23 -0600790 try:
791 return_code = int(fd.readline())
792 except ValueError:
793 # The file may be empty due to running out of disk space.
794 # Try a rebuild
795 return_code = 1
Simon Glassc05694f2013-04-03 11:07:16 +0000796 err_lines = []
Simon Glassbc74d942023-07-19 17:49:06 -0600797 err_file = self.get_err_file(commit_upto, target)
Simon Glassc05694f2013-04-03 11:07:16 +0000798 if os.path.exists(err_file):
799 with open(err_file, 'r') as fd:
Simon Glassbc74d942023-07-19 17:49:06 -0600800 err_lines = self.filter_errors(fd.readlines())
Simon Glassc05694f2013-04-03 11:07:16 +0000801
802 # Decide whether the build was ok, failed or created warnings
803 if return_code:
804 rc = OUTCOME_ERROR
805 elif len(err_lines):
806 rc = OUTCOME_WARNING
807 else:
808 rc = OUTCOME_OK
809
810 # Convert size information to our simple format
811 if os.path.exists(sizes_file):
812 with open(sizes_file, 'r') as fd:
813 for line in fd.readlines():
814 values = line.split()
815 rodata = 0
816 if len(values) > 6:
817 rodata = int(values[6], 16)
818 size_dict = {
819 'all' : int(values[0]) + int(values[1]) +
820 int(values[2]),
821 'text' : int(values[0]) - rodata,
822 'data' : int(values[1]),
823 'bss' : int(values[2]),
824 'rodata' : rodata,
825 }
826 sizes[values[5]] = size_dict
827
828 if read_func_sizes:
Simon Glassbc74d942023-07-19 17:49:06 -0600829 pattern = self.get_func_sizes_file(commit_upto, target, '*')
Simon Glassc05694f2013-04-03 11:07:16 +0000830 for fname in glob.glob(pattern):
831 with open(fname, 'r') as fd:
832 dict_name = os.path.basename(fname).replace('.sizes',
833 '')
Simon Glassbc74d942023-07-19 17:49:06 -0600834 func_sizes[dict_name] = self.read_func_sizes(fname, fd)
Simon Glassc05694f2013-04-03 11:07:16 +0000835
Simon Glassdb17fb82015-02-05 22:06:15 -0700836 if read_config:
Simon Glassbc74d942023-07-19 17:49:06 -0600837 output_dir = self.get_build_dir(commit_upto, target)
Simon Glasscde5c302016-11-13 14:25:53 -0700838 for name in self.config_filenames:
Simon Glassdb17fb82015-02-05 22:06:15 -0700839 fname = os.path.join(output_dir, name)
Simon Glassbc74d942023-07-19 17:49:06 -0600840 config[name] = self._process_config(fname)
Simon Glassdb17fb82015-02-05 22:06:15 -0700841
Alex Kiernan4059e302018-05-31 04:48:34 +0000842 if read_environment:
Simon Glassbc74d942023-07-19 17:49:06 -0600843 output_dir = self.get_build_dir(commit_upto, target)
Alex Kiernan4059e302018-05-31 04:48:34 +0000844 fname = os.path.join(output_dir, 'uboot.env')
Simon Glassbc74d942023-07-19 17:49:06 -0600845 environment = self._process_environment(fname)
Alex Kiernan4059e302018-05-31 04:48:34 +0000846
847 return Builder.Outcome(rc, err_lines, sizes, func_sizes, config,
848 environment)
Simon Glassc05694f2013-04-03 11:07:16 +0000849
Alex Kiernan4059e302018-05-31 04:48:34 +0000850 return Builder.Outcome(OUTCOME_UNKNOWN, [], {}, {}, {}, {})
Simon Glassc05694f2013-04-03 11:07:16 +0000851
Simon Glassbc74d942023-07-19 17:49:06 -0600852 def get_result_summary(self, boards_selected, commit_upto, read_func_sizes,
Alex Kiernan4059e302018-05-31 04:48:34 +0000853 read_config, read_environment):
Simon Glassc05694f2013-04-03 11:07:16 +0000854 """Calculate a summary of the results of building a commit.
855
856 Args:
857 board_selected: Dict containing boards to summarise
858 commit_upto: Commit number to summarize (0..self.count-1)
859 read_func_sizes: True to read function size information
Simon Glassdb17fb82015-02-05 22:06:15 -0700860 read_config: True to read .config and autoconf.h files
Alex Kiernan4059e302018-05-31 04:48:34 +0000861 read_environment: True to read uboot.env files
Simon Glassc05694f2013-04-03 11:07:16 +0000862
863 Returns:
864 Tuple:
Simon Glass6c435622022-07-11 19:03:56 -0600865 Dict containing boards which built this commit:
866 key: board.target
867 value: Builder.Outcome object
Simon Glass03749d42014-08-28 09:43:44 -0600868 List containing a summary of error lines
Simon Glass3394c9f2014-08-28 09:43:43 -0600869 Dict keyed by error line, containing a list of the Board
870 objects with that error
Simon Glass03749d42014-08-28 09:43:44 -0600871 List containing a summary of warning lines
872 Dict keyed by error line, containing a list of the Board
873 objects with that warning
Simon Glasscad8abf2015-08-25 21:52:14 -0600874 Dictionary keyed by board.target. Each value is a dictionary:
875 key: filename - e.g. '.config'
Simon Glassdb17fb82015-02-05 22:06:15 -0700876 value is itself a dictionary:
877 key: config name
878 value: config value
Alex Kiernan4059e302018-05-31 04:48:34 +0000879 Dictionary keyed by board.target. Each value is a dictionary:
880 key: environment variable
881 value: value of environment variable
Simon Glassc05694f2013-04-03 11:07:16 +0000882 """
Simon Glassbc74d942023-07-19 17:49:06 -0600883 def add_line(lines_summary, lines_boards, line, board):
Simon Glass03749d42014-08-28 09:43:44 -0600884 line = line.rstrip()
885 if line in lines_boards:
886 lines_boards[line].append(board)
887 else:
888 lines_boards[line] = [board]
889 lines_summary.append(line)
890
Simon Glassc05694f2013-04-03 11:07:16 +0000891 board_dict = {}
892 err_lines_summary = []
Simon Glass3394c9f2014-08-28 09:43:43 -0600893 err_lines_boards = {}
Simon Glass03749d42014-08-28 09:43:44 -0600894 warn_lines_summary = []
895 warn_lines_boards = {}
Simon Glassdb17fb82015-02-05 22:06:15 -0700896 config = {}
Alex Kiernan4059e302018-05-31 04:48:34 +0000897 environment = {}
Simon Glassc05694f2013-04-03 11:07:16 +0000898
Simon Glass8132f982022-07-11 19:03:57 -0600899 for brd in boards_selected.values():
Simon Glassbc74d942023-07-19 17:49:06 -0600900 outcome = self.get_build_outcome(commit_upto, brd.target,
Alex Kiernan4059e302018-05-31 04:48:34 +0000901 read_func_sizes, read_config,
902 read_environment)
Simon Glass8132f982022-07-11 19:03:57 -0600903 board_dict[brd.target] = outcome
Simon Glass03749d42014-08-28 09:43:44 -0600904 last_func = None
905 last_was_warning = False
906 for line in outcome.err_lines:
907 if line:
908 if (self._re_function.match(line) or
909 self._re_files.match(line)):
910 last_func = line
Simon Glass3394c9f2014-08-28 09:43:43 -0600911 else:
Simon Glass0db94432018-11-06 16:02:11 -0700912 is_warning = (self._re_warning.match(line) or
913 self._re_dtb_warning.match(line))
Simon Glass03749d42014-08-28 09:43:44 -0600914 is_note = self._re_note.match(line)
915 if is_warning or (last_was_warning and is_note):
916 if last_func:
Simon Glassbc74d942023-07-19 17:49:06 -0600917 add_line(warn_lines_summary, warn_lines_boards,
Simon Glass8132f982022-07-11 19:03:57 -0600918 last_func, brd)
Simon Glassbc74d942023-07-19 17:49:06 -0600919 add_line(warn_lines_summary, warn_lines_boards,
Simon Glass8132f982022-07-11 19:03:57 -0600920 line, brd)
Simon Glass03749d42014-08-28 09:43:44 -0600921 else:
922 if last_func:
Simon Glassbc74d942023-07-19 17:49:06 -0600923 add_line(err_lines_summary, err_lines_boards,
Simon Glass8132f982022-07-11 19:03:57 -0600924 last_func, brd)
Simon Glassbc74d942023-07-19 17:49:06 -0600925 add_line(err_lines_summary, err_lines_boards,
Simon Glass8132f982022-07-11 19:03:57 -0600926 line, brd)
Simon Glass03749d42014-08-28 09:43:44 -0600927 last_was_warning = is_warning
928 last_func = None
Simon Glass8132f982022-07-11 19:03:57 -0600929 tconfig = Config(self.config_filenames, brd.target)
Simon Glasscde5c302016-11-13 14:25:53 -0700930 for fname in self.config_filenames:
Simon Glassdb17fb82015-02-05 22:06:15 -0700931 if outcome.config:
Simon Glassc78ed662019-10-31 07:42:53 -0600932 for key, value in outcome.config[fname].items():
Simon Glassbc74d942023-07-19 17:49:06 -0600933 tconfig.add(fname, key, value)
Simon Glass8132f982022-07-11 19:03:57 -0600934 config[brd.target] = tconfig
Simon Glassdb17fb82015-02-05 22:06:15 -0700935
Simon Glass8132f982022-07-11 19:03:57 -0600936 tenvironment = Environment(brd.target)
Alex Kiernan4059e302018-05-31 04:48:34 +0000937 if outcome.environment:
Simon Glassc78ed662019-10-31 07:42:53 -0600938 for key, value in outcome.environment.items():
Simon Glassbc74d942023-07-19 17:49:06 -0600939 tenvironment.add(key, value)
Simon Glass8132f982022-07-11 19:03:57 -0600940 environment[brd.target] = tenvironment
Alex Kiernan4059e302018-05-31 04:48:34 +0000941
Simon Glass03749d42014-08-28 09:43:44 -0600942 return (board_dict, err_lines_summary, err_lines_boards,
Alex Kiernan4059e302018-05-31 04:48:34 +0000943 warn_lines_summary, warn_lines_boards, config, environment)
Simon Glassc05694f2013-04-03 11:07:16 +0000944
Simon Glassbc74d942023-07-19 17:49:06 -0600945 def add_outcome(self, board_dict, arch_list, changes, char, color):
Simon Glassc05694f2013-04-03 11:07:16 +0000946 """Add an output to our list of outcomes for each architecture
947
948 This simple function adds failing boards (changes) to the
949 relevant architecture string, so we can print the results out
950 sorted by architecture.
951
952 Args:
953 board_dict: Dict containing all boards
954 arch_list: Dict keyed by arch name. Value is a string containing
955 a list of board names which failed for that arch.
956 changes: List of boards to add to arch_list
957 color: terminal.Colour object
958 """
959 done_arch = {}
960 for target in changes:
961 if target in board_dict:
962 arch = board_dict[target].arch
963 else:
964 arch = 'unknown'
Simon Glassf45d3742022-01-29 14:14:17 -0700965 str = self.col.build(color, ' ' + target)
Simon Glassc05694f2013-04-03 11:07:16 +0000966 if not arch in done_arch:
Simon Glassf45d3742022-01-29 14:14:17 -0700967 str = ' %s %s' % (self.col.build(color, char), str)
Simon Glassc05694f2013-04-03 11:07:16 +0000968 done_arch[arch] = True
969 if not arch in arch_list:
970 arch_list[arch] = str
971 else:
972 arch_list[arch] += str
973
974
Simon Glassbc74d942023-07-19 17:49:06 -0600975 def colour_num(self, num):
Simon Glassc05694f2013-04-03 11:07:16 +0000976 color = self.col.RED if num > 0 else self.col.GREEN
977 if num == 0:
978 return '0'
Simon Glassf45d3742022-01-29 14:14:17 -0700979 return self.col.build(color, str(num))
Simon Glassc05694f2013-04-03 11:07:16 +0000980
Simon Glassbc74d942023-07-19 17:49:06 -0600981 def reset_result_summary(self, board_selected):
Simon Glassc05694f2013-04-03 11:07:16 +0000982 """Reset the results summary ready for use.
983
984 Set up the base board list to be all those selected, and set the
985 error lines to empty.
986
Simon Glassbc74d942023-07-19 17:49:06 -0600987 Following this, calls to print_result_summary() will use this
Simon Glassc05694f2013-04-03 11:07:16 +0000988 information to work out what has changed.
989
990 Args:
991 board_selected: Dict containing boards to summarise, keyed by
992 board.target
993 """
994 self._base_board_dict = {}
Simon Glass8132f982022-07-11 19:03:57 -0600995 for brd in board_selected:
996 self._base_board_dict[brd] = Builder.Outcome(0, [], [], {}, {}, {})
Simon Glassc05694f2013-04-03 11:07:16 +0000997 self._base_err_lines = []
Simon Glass03749d42014-08-28 09:43:44 -0600998 self._base_warn_lines = []
999 self._base_err_line_boards = {}
1000 self._base_warn_line_boards = {}
Simon Glasscad8abf2015-08-25 21:52:14 -06001001 self._base_config = None
Alex Kiernan4059e302018-05-31 04:48:34 +00001002 self._base_environment = None
Simon Glassc05694f2013-04-03 11:07:16 +00001003
Simon Glassbc74d942023-07-19 17:49:06 -06001004 def print_func_size_detail(self, fname, old, new):
Simon Glassc05694f2013-04-03 11:07:16 +00001005 grow, shrink, add, remove, up, down = 0, 0, 0, 0, 0, 0
1006 delta, common = [], {}
1007
1008 for a in old:
1009 if a in new:
1010 common[a] = 1
1011
1012 for name in old:
1013 if name not in common:
1014 remove += 1
1015 down += old[name]
1016 delta.append([-old[name], name])
1017
1018 for name in new:
1019 if name not in common:
1020 add += 1
1021 up += new[name]
1022 delta.append([new[name], name])
1023
1024 for name in common:
1025 diff = new.get(name, 0) - old.get(name, 0)
1026 if diff > 0:
1027 grow, up = grow + 1, up + diff
1028 elif diff < 0:
1029 shrink, down = shrink + 1, down - diff
1030 delta.append([diff, name])
1031
1032 delta.sort()
1033 delta.reverse()
1034
1035 args = [add, -remove, grow, -shrink, up, -down, up - down]
Tom Rini0b48cd62017-05-22 13:48:52 -04001036 if max(args) == 0 and min(args) == 0:
Simon Glassc05694f2013-04-03 11:07:16 +00001037 return
Simon Glassbc74d942023-07-19 17:49:06 -06001038 args = [self.colour_num(x) for x in args]
Simon Glassc05694f2013-04-03 11:07:16 +00001039 indent = ' ' * 15
Simon Glass02811582022-01-29 14:14:18 -07001040 tprint('%s%s: add: %s/%s, grow: %s/%s bytes: %s/%s (%s)' %
Simon Glassf45d3742022-01-29 14:14:17 -07001041 tuple([indent, self.col.build(self.col.YELLOW, fname)] + args))
Simon Glass02811582022-01-29 14:14:18 -07001042 tprint('%s %-38s %7s %7s %+7s' % (indent, 'function', 'old', 'new',
Simon Glass4433aa92014-09-05 19:00:07 -06001043 'delta'))
Simon Glassc05694f2013-04-03 11:07:16 +00001044 for diff, name in delta:
1045 if diff:
1046 color = self.col.RED if diff > 0 else self.col.GREEN
1047 msg = '%s %-38s %7s %7s %+7d' % (indent, name,
1048 old.get(name, '-'), new.get(name,'-'), diff)
Simon Glass02811582022-01-29 14:14:18 -07001049 tprint(msg, colour=color)
Simon Glassc05694f2013-04-03 11:07:16 +00001050
1051
Simon Glassbc74d942023-07-19 17:49:06 -06001052 def print_size_detail(self, target_list, show_bloat):
Simon Glassc05694f2013-04-03 11:07:16 +00001053 """Show details size information for each board
1054
1055 Args:
1056 target_list: List of targets, each a dict containing:
1057 'target': Target name
1058 'total_diff': Total difference in bytes across all areas
1059 <part_name>: Difference for that part
1060 show_bloat: Show detail for each function
1061 """
1062 targets_by_diff = sorted(target_list, reverse=True,
1063 key=lambda x: x['_total_diff'])
1064 for result in targets_by_diff:
1065 printed_target = False
1066 for name in sorted(result):
1067 diff = result[name]
1068 if name.startswith('_'):
1069 continue
1070 if diff != 0:
1071 color = self.col.RED if diff > 0 else self.col.GREEN
1072 msg = ' %s %+d' % (name, diff)
1073 if not printed_target:
Simon Glass02811582022-01-29 14:14:18 -07001074 tprint('%10s %-15s:' % ('', result['_target']),
Simon Glass4433aa92014-09-05 19:00:07 -06001075 newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001076 printed_target = True
Simon Glass02811582022-01-29 14:14:18 -07001077 tprint(msg, colour=color, newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001078 if printed_target:
Simon Glass02811582022-01-29 14:14:18 -07001079 tprint()
Simon Glassc05694f2013-04-03 11:07:16 +00001080 if show_bloat:
1081 target = result['_target']
1082 outcome = result['_outcome']
1083 base_outcome = self._base_board_dict[target]
1084 for fname in outcome.func_sizes:
Simon Glassbc74d942023-07-19 17:49:06 -06001085 self.print_func_size_detail(fname,
Simon Glassc05694f2013-04-03 11:07:16 +00001086 base_outcome.func_sizes[fname],
1087 outcome.func_sizes[fname])
1088
1089
Simon Glassbc74d942023-07-19 17:49:06 -06001090 def print_size_summary(self, board_selected, board_dict, show_detail,
Simon Glassc05694f2013-04-03 11:07:16 +00001091 show_bloat):
1092 """Print a summary of image sizes broken down by section.
1093
1094 The summary takes the form of one line per architecture. The
1095 line contains deltas for each of the sections (+ means the section
Flavio Suligoie5e3c112020-01-29 09:56:05 +01001096 got bigger, - means smaller). The numbers are the average number
Simon Glassc05694f2013-04-03 11:07:16 +00001097 of bytes that a board in this section increased by.
1098
1099 For example:
1100 powerpc: (622 boards) text -0.0
1101 arm: (285 boards) text -0.0
Simon Glassc05694f2013-04-03 11:07:16 +00001102
1103 Args:
1104 board_selected: Dict containing boards to summarise, keyed by
1105 board.target
1106 board_dict: Dict containing boards for which we built this
1107 commit, keyed by board.target. The value is an Outcome object.
Simon Glassb4002462020-03-18 09:42:43 -06001108 show_detail: Show size delta detail for each board
Simon Glassc05694f2013-04-03 11:07:16 +00001109 show_bloat: Show detail for each function
1110 """
1111 arch_list = {}
1112 arch_count = {}
1113
1114 # Calculate changes in size for different image parts
1115 # The previous sizes are in Board.sizes, for each board
1116 for target in board_dict:
1117 if target not in board_selected:
1118 continue
1119 base_sizes = self._base_board_dict[target].sizes
1120 outcome = board_dict[target]
1121 sizes = outcome.sizes
1122
1123 # Loop through the list of images, creating a dict of size
1124 # changes for each image/part. We end up with something like
1125 # {'target' : 'snapper9g45, 'data' : 5, 'u-boot-spl:text' : -4}
1126 # which means that U-Boot data increased by 5 bytes and SPL
1127 # text decreased by 4.
1128 err = {'_target' : target}
1129 for image in sizes:
1130 if image in base_sizes:
1131 base_image = base_sizes[image]
1132 # Loop through the text, data, bss parts
1133 for part in sorted(sizes[image]):
1134 diff = sizes[image][part] - base_image[part]
1135 col = None
1136 if diff:
1137 if image == 'u-boot':
1138 name = part
1139 else:
1140 name = image + ':' + part
1141 err[name] = diff
1142 arch = board_selected[target].arch
1143 if not arch in arch_count:
1144 arch_count[arch] = 1
1145 else:
1146 arch_count[arch] += 1
1147 if not sizes:
1148 pass # Only add to our list when we have some stats
1149 elif not arch in arch_list:
1150 arch_list[arch] = [err]
1151 else:
1152 arch_list[arch].append(err)
1153
1154 # We now have a list of image size changes sorted by arch
1155 # Print out a summary of these
Simon Glassc78ed662019-10-31 07:42:53 -06001156 for arch, target_list in arch_list.items():
Simon Glassc05694f2013-04-03 11:07:16 +00001157 # Get total difference for each type
1158 totals = {}
1159 for result in target_list:
1160 total = 0
Simon Glassc78ed662019-10-31 07:42:53 -06001161 for name, diff in result.items():
Simon Glassc05694f2013-04-03 11:07:16 +00001162 if name.startswith('_'):
1163 continue
1164 total += diff
1165 if name in totals:
1166 totals[name] += diff
1167 else:
1168 totals[name] = diff
1169 result['_total_diff'] = total
1170 result['_outcome'] = board_dict[result['_target']]
1171
1172 count = len(target_list)
1173 printed_arch = False
1174 for name in sorted(totals):
1175 diff = totals[name]
1176 if diff:
1177 # Display the average difference in this name for this
1178 # architecture
1179 avg_diff = float(diff) / count
1180 color = self.col.RED if avg_diff > 0 else self.col.GREEN
1181 msg = ' %s %+1.1f' % (name, avg_diff)
1182 if not printed_arch:
Simon Glass02811582022-01-29 14:14:18 -07001183 tprint('%10s: (for %d/%d boards)' % (arch, count,
Simon Glass4433aa92014-09-05 19:00:07 -06001184 arch_count[arch]), newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001185 printed_arch = True
Simon Glass02811582022-01-29 14:14:18 -07001186 tprint(msg, colour=color, newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001187
1188 if printed_arch:
Simon Glass02811582022-01-29 14:14:18 -07001189 tprint()
Simon Glassc05694f2013-04-03 11:07:16 +00001190 if show_detail:
Simon Glassbc74d942023-07-19 17:49:06 -06001191 self.print_size_detail(target_list, show_bloat)
Simon Glassc05694f2013-04-03 11:07:16 +00001192
1193
Simon Glassbc74d942023-07-19 17:49:06 -06001194 def print_result_summary(self, board_selected, board_dict, err_lines,
Simon Glass03749d42014-08-28 09:43:44 -06001195 err_line_boards, warn_lines, warn_line_boards,
Alex Kiernan4059e302018-05-31 04:48:34 +00001196 config, environment, show_sizes, show_detail,
1197 show_bloat, show_config, show_environment):
Simon Glassc05694f2013-04-03 11:07:16 +00001198 """Compare results with the base results and display delta.
1199
1200 Only boards mentioned in board_selected will be considered. This
1201 function is intended to be called repeatedly with the results of
1202 each commit. It therefore shows a 'diff' between what it saw in
1203 the last call and what it sees now.
1204
1205 Args:
1206 board_selected: Dict containing boards to summarise, keyed by
1207 board.target
1208 board_dict: Dict containing boards for which we built this
1209 commit, keyed by board.target. The value is an Outcome object.
1210 err_lines: A list of errors for this commit, or [] if there is
1211 none, or we don't want to print errors
Simon Glass3394c9f2014-08-28 09:43:43 -06001212 err_line_boards: Dict keyed by error line, containing a list of
1213 the Board objects with that error
Simon Glass03749d42014-08-28 09:43:44 -06001214 warn_lines: A list of warnings for this commit, or [] if there is
1215 none, or we don't want to print errors
1216 warn_line_boards: Dict keyed by warning line, containing a list of
1217 the Board objects with that warning
Simon Glassdb17fb82015-02-05 22:06:15 -07001218 config: Dictionary keyed by filename - e.g. '.config'. Each
1219 value is itself a dictionary:
1220 key: config name
1221 value: config value
Alex Kiernan4059e302018-05-31 04:48:34 +00001222 environment: Dictionary keyed by environment variable, Each
1223 value is the value of environment variable.
Simon Glassc05694f2013-04-03 11:07:16 +00001224 show_sizes: Show image size deltas
Simon Glassb4002462020-03-18 09:42:43 -06001225 show_detail: Show size delta detail for each board if show_sizes
Simon Glassc05694f2013-04-03 11:07:16 +00001226 show_bloat: Show detail for each function
Simon Glassdb17fb82015-02-05 22:06:15 -07001227 show_config: Show config changes
Alex Kiernan4059e302018-05-31 04:48:34 +00001228 show_environment: Show environment changes
Simon Glassc05694f2013-04-03 11:07:16 +00001229 """
Simon Glassbc74d942023-07-19 17:49:06 -06001230 def _board_list(line, line_boards):
Simon Glass3394c9f2014-08-28 09:43:43 -06001231 """Helper function to get a line of boards containing a line
1232
1233 Args:
1234 line: Error line to search for
Simon Glassde0fefc2020-04-09 15:08:36 -06001235 line_boards: boards to search, each a Board
Simon Glass3394c9f2014-08-28 09:43:43 -06001236 Return:
Simon Glassde0fefc2020-04-09 15:08:36 -06001237 List of boards with that error line, or [] if the user has not
1238 requested such a list
Simon Glass3394c9f2014-08-28 09:43:43 -06001239 """
Simon Glass5df45222022-07-11 19:04:00 -06001240 brds = []
Simon Glassde0fefc2020-04-09 15:08:36 -06001241 board_set = set()
Simon Glass3394c9f2014-08-28 09:43:43 -06001242 if self._list_error_boards:
Simon Glass8132f982022-07-11 19:03:57 -06001243 for brd in line_boards[line]:
1244 if not brd in board_set:
Simon Glass5df45222022-07-11 19:04:00 -06001245 brds.append(brd)
Simon Glass8132f982022-07-11 19:03:57 -06001246 board_set.add(brd)
Simon Glass5df45222022-07-11 19:04:00 -06001247 return brds
Simon Glass3394c9f2014-08-28 09:43:43 -06001248
Simon Glassbc74d942023-07-19 17:49:06 -06001249 def _calc_error_delta(base_lines, base_line_boards, lines, line_boards,
Simon Glass03749d42014-08-28 09:43:44 -06001250 char):
Simon Glassde0fefc2020-04-09 15:08:36 -06001251 """Calculate the required output based on changes in errors
1252
1253 Args:
1254 base_lines: List of errors/warnings for previous commit
1255 base_line_boards: Dict keyed by error line, containing a list
1256 of the Board objects with that error in the previous commit
1257 lines: List of errors/warning for this commit, each a str
1258 line_boards: Dict keyed by error line, containing a list
1259 of the Board objects with that error in this commit
1260 char: Character representing error ('') or warning ('w'). The
1261 broken ('+') or fixed ('-') characters are added in this
1262 function
1263
1264 Returns:
1265 Tuple
1266 List of ErrLine objects for 'better' lines
1267 List of ErrLine objects for 'worse' lines
1268 """
Simon Glass03749d42014-08-28 09:43:44 -06001269 better_lines = []
1270 worse_lines = []
1271 for line in lines:
1272 if line not in base_lines:
Simon Glassbc74d942023-07-19 17:49:06 -06001273 errline = ErrLine(char + '+', _board_list(line, line_boards),
Simon Glassde0fefc2020-04-09 15:08:36 -06001274 line)
1275 worse_lines.append(errline)
Simon Glass03749d42014-08-28 09:43:44 -06001276 for line in base_lines:
1277 if line not in lines:
Simon Glassde0fefc2020-04-09 15:08:36 -06001278 errline = ErrLine(char + '-',
Simon Glassbc74d942023-07-19 17:49:06 -06001279 _board_list(line, base_line_boards), line)
Simon Glassde0fefc2020-04-09 15:08:36 -06001280 better_lines.append(errline)
Simon Glass03749d42014-08-28 09:43:44 -06001281 return better_lines, worse_lines
1282
Simon Glassbc74d942023-07-19 17:49:06 -06001283 def _calc_config(delta, name, config):
Simon Glassdb17fb82015-02-05 22:06:15 -07001284 """Calculate configuration changes
1285
1286 Args:
1287 delta: Type of the delta, e.g. '+'
1288 name: name of the file which changed (e.g. .config)
1289 config: configuration change dictionary
1290 key: config name
1291 value: config value
1292 Returns:
1293 String containing the configuration changes which can be
1294 printed
1295 """
1296 out = ''
1297 for key in sorted(config.keys()):
1298 out += '%s=%s ' % (key, config[key])
Simon Glasscad8abf2015-08-25 21:52:14 -06001299 return '%s %s: %s' % (delta, name, out)
Simon Glassdb17fb82015-02-05 22:06:15 -07001300
Simon Glassbc74d942023-07-19 17:49:06 -06001301 def _add_config(lines, name, config_plus, config_minus, config_change):
Simon Glasscad8abf2015-08-25 21:52:14 -06001302 """Add changes in configuration to a list
Simon Glassdb17fb82015-02-05 22:06:15 -07001303
1304 Args:
Simon Glasscad8abf2015-08-25 21:52:14 -06001305 lines: list to add to
1306 name: config file name
Simon Glassdb17fb82015-02-05 22:06:15 -07001307 config_plus: configurations added, dictionary
1308 key: config name
1309 value: config value
1310 config_minus: configurations removed, dictionary
1311 key: config name
1312 value: config value
1313 config_change: configurations changed, dictionary
1314 key: config name
1315 value: config value
1316 """
1317 if config_plus:
Simon Glassbc74d942023-07-19 17:49:06 -06001318 lines.append(_calc_config('+', name, config_plus))
Simon Glassdb17fb82015-02-05 22:06:15 -07001319 if config_minus:
Simon Glassbc74d942023-07-19 17:49:06 -06001320 lines.append(_calc_config('-', name, config_minus))
Simon Glassdb17fb82015-02-05 22:06:15 -07001321 if config_change:
Simon Glassbc74d942023-07-19 17:49:06 -06001322 lines.append(_calc_config('c', name, config_change))
Simon Glasscad8abf2015-08-25 21:52:14 -06001323
Simon Glassbc74d942023-07-19 17:49:06 -06001324 def _output_config_info(lines):
Simon Glasscad8abf2015-08-25 21:52:14 -06001325 for line in lines:
1326 if not line:
1327 continue
1328 if line[0] == '+':
1329 col = self.col.GREEN
1330 elif line[0] == '-':
1331 col = self.col.RED
1332 elif line[0] == 'c':
1333 col = self.col.YELLOW
Simon Glass02811582022-01-29 14:14:18 -07001334 tprint(' ' + line, newline=True, colour=col)
Simon Glasscad8abf2015-08-25 21:52:14 -06001335
Simon Glassbc74d942023-07-19 17:49:06 -06001336 def _output_err_lines(err_lines, colour):
Simon Glassac500222020-04-09 15:08:28 -06001337 """Output the line of error/warning lines, if not empty
1338
1339 Also increments self._error_lines if err_lines not empty
1340
1341 Args:
Simon Glassde0fefc2020-04-09 15:08:36 -06001342 err_lines: List of ErrLine objects, each an error or warning
1343 line, possibly including a list of boards with that
1344 error/warning
Simon Glassac500222020-04-09 15:08:28 -06001345 colour: Colour to use for output
1346 """
1347 if err_lines:
Simon Glassea49f9b2020-04-09 15:08:37 -06001348 out_list = []
Simon Glassde0fefc2020-04-09 15:08:36 -06001349 for line in err_lines:
Simon Glass5df45222022-07-11 19:04:00 -06001350 names = [brd.target for brd in line.brds]
Simon Glass070589b2020-04-09 15:08:38 -06001351 board_str = ' '.join(names) if names else ''
Simon Glassea49f9b2020-04-09 15:08:37 -06001352 if board_str:
Simon Glassf45d3742022-01-29 14:14:17 -07001353 out = self.col.build(colour, line.char + '(')
1354 out += self.col.build(self.col.MAGENTA, board_str,
Simon Glassea49f9b2020-04-09 15:08:37 -06001355 bright=False)
Simon Glassf45d3742022-01-29 14:14:17 -07001356 out += self.col.build(colour, ') %s' % line.errline)
Simon Glassea49f9b2020-04-09 15:08:37 -06001357 else:
Simon Glassf45d3742022-01-29 14:14:17 -07001358 out = self.col.build(colour, line.char + line.errline)
Simon Glassea49f9b2020-04-09 15:08:37 -06001359 out_list.append(out)
Simon Glass02811582022-01-29 14:14:18 -07001360 tprint('\n'.join(out_list))
Simon Glassac500222020-04-09 15:08:28 -06001361 self._error_lines += 1
1362
Simon Glassdb17fb82015-02-05 22:06:15 -07001363
Simon Glass454507f2018-11-06 16:02:12 -07001364 ok_boards = [] # List of boards fixed since last commit
Simon Glass071a1782018-11-06 16:02:13 -07001365 warn_boards = [] # List of boards with warnings since last commit
Simon Glass454507f2018-11-06 16:02:12 -07001366 err_boards = [] # List of new broken boards since last commit
1367 new_boards = [] # List of boards that didn't exist last time
1368 unknown_boards = [] # List of boards that were not built
Simon Glassc05694f2013-04-03 11:07:16 +00001369
1370 for target in board_dict:
1371 if target not in board_selected:
1372 continue
1373
1374 # If the board was built last time, add its outcome to a list
1375 if target in self._base_board_dict:
1376 base_outcome = self._base_board_dict[target].rc
1377 outcome = board_dict[target]
1378 if outcome.rc == OUTCOME_UNKNOWN:
Simon Glass454507f2018-11-06 16:02:12 -07001379 unknown_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001380 elif outcome.rc < base_outcome:
Simon Glass071a1782018-11-06 16:02:13 -07001381 if outcome.rc == OUTCOME_WARNING:
1382 warn_boards.append(target)
1383 else:
1384 ok_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001385 elif outcome.rc > base_outcome:
Simon Glass071a1782018-11-06 16:02:13 -07001386 if outcome.rc == OUTCOME_WARNING:
1387 warn_boards.append(target)
1388 else:
1389 err_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001390 else:
Simon Glass454507f2018-11-06 16:02:12 -07001391 new_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001392
Simon Glassac500222020-04-09 15:08:28 -06001393 # Get a list of errors and warnings that have appeared, and disappeared
Simon Glassbc74d942023-07-19 17:49:06 -06001394 better_err, worse_err = _calc_error_delta(self._base_err_lines,
Simon Glass03749d42014-08-28 09:43:44 -06001395 self._base_err_line_boards, err_lines, err_line_boards, '')
Simon Glassbc74d942023-07-19 17:49:06 -06001396 better_warn, worse_warn = _calc_error_delta(self._base_warn_lines,
Simon Glass03749d42014-08-28 09:43:44 -06001397 self._base_warn_line_boards, warn_lines, warn_line_boards, 'w')
Simon Glassc05694f2013-04-03 11:07:16 +00001398
Simon Glass6c435622022-07-11 19:03:56 -06001399 # For the IDE mode, print out all the output
1400 if self._ide:
1401 outcome = board_dict[target]
1402 for line in outcome.err_lines:
1403 sys.stderr.write(line)
1404
Simon Glassc05694f2013-04-03 11:07:16 +00001405 # Display results by arch
Simon Glass6c435622022-07-11 19:03:56 -06001406 elif any((ok_boards, warn_boards, err_boards, unknown_boards, new_boards,
Simon Glass071a1782018-11-06 16:02:13 -07001407 worse_err, better_err, worse_warn, better_warn)):
Simon Glassc05694f2013-04-03 11:07:16 +00001408 arch_list = {}
Simon Glassbc74d942023-07-19 17:49:06 -06001409 self.add_outcome(board_selected, arch_list, ok_boards, '',
Simon Glassc05694f2013-04-03 11:07:16 +00001410 self.col.GREEN)
Simon Glassbc74d942023-07-19 17:49:06 -06001411 self.add_outcome(board_selected, arch_list, warn_boards, 'w+',
Simon Glass071a1782018-11-06 16:02:13 -07001412 self.col.YELLOW)
Simon Glassbc74d942023-07-19 17:49:06 -06001413 self.add_outcome(board_selected, arch_list, err_boards, '+',
Simon Glassc05694f2013-04-03 11:07:16 +00001414 self.col.RED)
Simon Glassbc74d942023-07-19 17:49:06 -06001415 self.add_outcome(board_selected, arch_list, new_boards, '*', self.col.BLUE)
Simon Glassc05694f2013-04-03 11:07:16 +00001416 if self._show_unknown:
Simon Glassbc74d942023-07-19 17:49:06 -06001417 self.add_outcome(board_selected, arch_list, unknown_boards, '?',
Simon Glassc05694f2013-04-03 11:07:16 +00001418 self.col.MAGENTA)
Simon Glassc78ed662019-10-31 07:42:53 -06001419 for arch, target_list in arch_list.items():
Simon Glass02811582022-01-29 14:14:18 -07001420 tprint('%10s: %s' % (arch, target_list))
Simon Glassbb4dffb2014-08-09 15:33:06 -06001421 self._error_lines += 1
Simon Glassbc74d942023-07-19 17:49:06 -06001422 _output_err_lines(better_err, colour=self.col.GREEN)
1423 _output_err_lines(worse_err, colour=self.col.RED)
1424 _output_err_lines(better_warn, colour=self.col.CYAN)
1425 _output_err_lines(worse_warn, colour=self.col.YELLOW)
Simon Glassc05694f2013-04-03 11:07:16 +00001426
1427 if show_sizes:
Simon Glassbc74d942023-07-19 17:49:06 -06001428 self.print_size_summary(board_selected, board_dict, show_detail,
Simon Glassc05694f2013-04-03 11:07:16 +00001429 show_bloat)
1430
Alex Kiernan4059e302018-05-31 04:48:34 +00001431 if show_environment and self._base_environment:
1432 lines = []
1433
1434 for target in board_dict:
1435 if target not in board_selected:
1436 continue
1437
1438 tbase = self._base_environment[target]
1439 tenvironment = environment[target]
1440 environment_plus = {}
1441 environment_minus = {}
1442 environment_change = {}
1443 base = tbase.environment
Simon Glassc78ed662019-10-31 07:42:53 -06001444 for key, value in tenvironment.environment.items():
Alex Kiernan4059e302018-05-31 04:48:34 +00001445 if key not in base:
1446 environment_plus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001447 for key, value in base.items():
Alex Kiernan4059e302018-05-31 04:48:34 +00001448 if key not in tenvironment.environment:
1449 environment_minus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001450 for key, value in base.items():
Alex Kiernan4059e302018-05-31 04:48:34 +00001451 new_value = tenvironment.environment.get(key)
1452 if new_value and value != new_value:
1453 desc = '%s -> %s' % (value, new_value)
1454 environment_change[key] = desc
1455
Simon Glassbc74d942023-07-19 17:49:06 -06001456 _add_config(lines, target, environment_plus, environment_minus,
Alex Kiernan4059e302018-05-31 04:48:34 +00001457 environment_change)
1458
Simon Glassbc74d942023-07-19 17:49:06 -06001459 _output_config_info(lines)
Alex Kiernan4059e302018-05-31 04:48:34 +00001460
Simon Glasscad8abf2015-08-25 21:52:14 -06001461 if show_config and self._base_config:
1462 summary = {}
1463 arch_config_plus = {}
1464 arch_config_minus = {}
1465 arch_config_change = {}
1466 arch_list = []
1467
1468 for target in board_dict:
1469 if target not in board_selected:
1470 continue
1471 arch = board_selected[target].arch
1472 if arch not in arch_list:
1473 arch_list.append(arch)
1474
1475 for arch in arch_list:
1476 arch_config_plus[arch] = {}
1477 arch_config_minus[arch] = {}
1478 arch_config_change[arch] = {}
Simon Glasscde5c302016-11-13 14:25:53 -07001479 for name in self.config_filenames:
Simon Glasscad8abf2015-08-25 21:52:14 -06001480 arch_config_plus[arch][name] = {}
1481 arch_config_minus[arch][name] = {}
1482 arch_config_change[arch][name] = {}
1483
1484 for target in board_dict:
1485 if target not in board_selected:
Simon Glassdb17fb82015-02-05 22:06:15 -07001486 continue
Simon Glasscad8abf2015-08-25 21:52:14 -06001487
1488 arch = board_selected[target].arch
1489
1490 all_config_plus = {}
1491 all_config_minus = {}
1492 all_config_change = {}
1493 tbase = self._base_config[target]
1494 tconfig = config[target]
1495 lines = []
Simon Glasscde5c302016-11-13 14:25:53 -07001496 for name in self.config_filenames:
Simon Glasscad8abf2015-08-25 21:52:14 -06001497 if not tconfig.config[name]:
1498 continue
1499 config_plus = {}
1500 config_minus = {}
1501 config_change = {}
1502 base = tbase.config[name]
Simon Glassc78ed662019-10-31 07:42:53 -06001503 for key, value in tconfig.config[name].items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001504 if key not in base:
1505 config_plus[key] = value
1506 all_config_plus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001507 for key, value in base.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001508 if key not in tconfig.config[name]:
1509 config_minus[key] = value
1510 all_config_minus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001511 for key, value in base.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001512 new_value = tconfig.config.get(key)
1513 if new_value and value != new_value:
1514 desc = '%s -> %s' % (value, new_value)
1515 config_change[key] = desc
1516 all_config_change[key] = desc
1517
1518 arch_config_plus[arch][name].update(config_plus)
1519 arch_config_minus[arch][name].update(config_minus)
1520 arch_config_change[arch][name].update(config_change)
1521
Simon Glassbc74d942023-07-19 17:49:06 -06001522 _add_config(lines, name, config_plus, config_minus,
Simon Glasscad8abf2015-08-25 21:52:14 -06001523 config_change)
Simon Glassbc74d942023-07-19 17:49:06 -06001524 _add_config(lines, 'all', all_config_plus, all_config_minus,
Simon Glasscad8abf2015-08-25 21:52:14 -06001525 all_config_change)
1526 summary[target] = '\n'.join(lines)
1527
1528 lines_by_target = {}
Simon Glassc78ed662019-10-31 07:42:53 -06001529 for target, lines in summary.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001530 if lines in lines_by_target:
1531 lines_by_target[lines].append(target)
1532 else:
1533 lines_by_target[lines] = [target]
1534
1535 for arch in arch_list:
1536 lines = []
1537 all_plus = {}
1538 all_minus = {}
1539 all_change = {}
Simon Glasscde5c302016-11-13 14:25:53 -07001540 for name in self.config_filenames:
Simon Glasscad8abf2015-08-25 21:52:14 -06001541 all_plus.update(arch_config_plus[arch][name])
1542 all_minus.update(arch_config_minus[arch][name])
1543 all_change.update(arch_config_change[arch][name])
Simon Glassbc74d942023-07-19 17:49:06 -06001544 _add_config(lines, name, arch_config_plus[arch][name],
Simon Glasscad8abf2015-08-25 21:52:14 -06001545 arch_config_minus[arch][name],
1546 arch_config_change[arch][name])
Simon Glassbc74d942023-07-19 17:49:06 -06001547 _add_config(lines, 'all', all_plus, all_minus, all_change)
Simon Glasscad8abf2015-08-25 21:52:14 -06001548 #arch_summary[target] = '\n'.join(lines)
1549 if lines:
Simon Glass02811582022-01-29 14:14:18 -07001550 tprint('%s:' % arch)
Simon Glassbc74d942023-07-19 17:49:06 -06001551 _output_config_info(lines)
Simon Glasscad8abf2015-08-25 21:52:14 -06001552
Simon Glassc78ed662019-10-31 07:42:53 -06001553 for lines, targets in lines_by_target.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001554 if not lines:
1555 continue
Simon Glass02811582022-01-29 14:14:18 -07001556 tprint('%s :' % ' '.join(sorted(targets)))
Simon Glassbc74d942023-07-19 17:49:06 -06001557 _output_config_info(lines.split('\n'))
Simon Glasscad8abf2015-08-25 21:52:14 -06001558
Simon Glassdb17fb82015-02-05 22:06:15 -07001559
Simon Glassc05694f2013-04-03 11:07:16 +00001560 # Save our updated information for the next call to this function
1561 self._base_board_dict = board_dict
1562 self._base_err_lines = err_lines
Simon Glass03749d42014-08-28 09:43:44 -06001563 self._base_warn_lines = warn_lines
1564 self._base_err_line_boards = err_line_boards
1565 self._base_warn_line_boards = warn_line_boards
Simon Glassdb17fb82015-02-05 22:06:15 -07001566 self._base_config = config
Alex Kiernan4059e302018-05-31 04:48:34 +00001567 self._base_environment = environment
Simon Glassc05694f2013-04-03 11:07:16 +00001568
1569 # Get a list of boards that did not get built, if needed
1570 not_built = []
Simon Glass8132f982022-07-11 19:03:57 -06001571 for brd in board_selected:
1572 if not brd in board_dict:
1573 not_built.append(brd)
Simon Glassc05694f2013-04-03 11:07:16 +00001574 if not_built:
Simon Glass02811582022-01-29 14:14:18 -07001575 tprint("Boards not built (%d): %s" % (len(not_built),
Simon Glass4433aa92014-09-05 19:00:07 -06001576 ', '.join(not_built)))
Simon Glassc05694f2013-04-03 11:07:16 +00001577
Simon Glassbc74d942023-07-19 17:49:06 -06001578 def produce_result_summary(self, commit_upto, commits, board_selected):
Simon Glass03749d42014-08-28 09:43:44 -06001579 (board_dict, err_lines, err_line_boards, warn_lines,
Simon Glassbc74d942023-07-19 17:49:06 -06001580 warn_line_boards, config, environment) = self.get_result_summary(
Simon Glass3394c9f2014-08-28 09:43:43 -06001581 board_selected, commit_upto,
Simon Glassdb17fb82015-02-05 22:06:15 -07001582 read_func_sizes=self._show_bloat,
Alex Kiernan4059e302018-05-31 04:48:34 +00001583 read_config=self._show_config,
1584 read_environment=self._show_environment)
Simon Glasseb48bbc2014-08-09 15:33:02 -06001585 if commits:
1586 msg = '%02d: %s' % (commit_upto + 1,
1587 commits[commit_upto].subject)
Simon Glass02811582022-01-29 14:14:18 -07001588 tprint(msg, colour=self.col.BLUE)
Simon Glassbc74d942023-07-19 17:49:06 -06001589 self.print_result_summary(board_selected, board_dict,
Simon Glass3394c9f2014-08-28 09:43:43 -06001590 err_lines if self._show_errors else [], err_line_boards,
Simon Glass03749d42014-08-28 09:43:44 -06001591 warn_lines if self._show_errors else [], warn_line_boards,
Alex Kiernan4059e302018-05-31 04:48:34 +00001592 config, environment, self._show_sizes, self._show_detail,
1593 self._show_bloat, self._show_config, self._show_environment)
Simon Glassc05694f2013-04-03 11:07:16 +00001594
Simon Glassbc74d942023-07-19 17:49:06 -06001595 def show_summary(self, commits, board_selected):
Simon Glassc05694f2013-04-03 11:07:16 +00001596 """Show a build summary for U-Boot for a given board list.
1597
1598 Reset the result summary, then repeatedly call GetResultSummary on
1599 each commit's results, then display the differences we see.
1600
1601 Args:
1602 commit: Commit objects to summarise
1603 board_selected: Dict containing boards to summarise
Simon Glassc05694f2013-04-03 11:07:16 +00001604 """
Simon Glassd326ad72014-08-09 15:32:59 -06001605 self.commit_count = len(commits) if commits else 1
Simon Glassc05694f2013-04-03 11:07:16 +00001606 self.commits = commits
Simon Glassbc74d942023-07-19 17:49:06 -06001607 self.reset_result_summary(board_selected)
Simon Glassbb4dffb2014-08-09 15:33:06 -06001608 self._error_lines = 0
Simon Glassc05694f2013-04-03 11:07:16 +00001609
1610 for commit_upto in range(0, self.commit_count, self._step):
Simon Glassbc74d942023-07-19 17:49:06 -06001611 self.produce_result_summary(commit_upto, commits, board_selected)
Simon Glassbb4dffb2014-08-09 15:33:06 -06001612 if not self._error_lines:
Simon Glass02811582022-01-29 14:14:18 -07001613 tprint('(no errors to report)', colour=self.col.GREEN)
Simon Glassc05694f2013-04-03 11:07:16 +00001614
1615
Simon Glassbc74d942023-07-19 17:49:06 -06001616 def setup_build(self, board_selected, commits):
Simon Glassc05694f2013-04-03 11:07:16 +00001617 """Set up ready to start a build.
1618
1619 Args:
1620 board_selected: Selected boards to build
1621 commits: Selected commits to build
1622 """
1623 # First work out how many commits we will build
Simon Glassc78ed662019-10-31 07:42:53 -06001624 count = (self.commit_count + self._step - 1) // self._step
Simon Glassc05694f2013-04-03 11:07:16 +00001625 self.count = len(board_selected) * count
1626 self.upto = self.warned = self.fail = 0
1627 self._timestamps = collections.deque()
1628
Simon Glassbc74d942023-07-19 17:49:06 -06001629 def get_thread_dir(self, thread_num):
Simon Glassc05694f2013-04-03 11:07:16 +00001630 """Get the directory path to the working dir for a thread.
1631
1632 Args:
Simon Glassc635d892021-01-30 22:17:46 -07001633 thread_num: Number of thread to check (-1 for main process, which
1634 is treated as 0)
Simon Glassc05694f2013-04-03 11:07:16 +00001635 """
Simon Glassb6eb8cf2020-03-18 09:42:42 -06001636 if self.work_in_output:
1637 return self._working_dir
Simon Glassc635d892021-01-30 22:17:46 -07001638 return os.path.join(self._working_dir, '%02d' % max(thread_num, 0))
Simon Glassc05694f2013-04-03 11:07:16 +00001639
Simon Glassbc74d942023-07-19 17:49:06 -06001640 def _prepare_thread(self, thread_num, setup_git):
Simon Glassc05694f2013-04-03 11:07:16 +00001641 """Prepare the working directory for a thread.
1642
1643 This clones or fetches the repo into the thread's work directory.
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001644 Optionally, it can create a linked working tree of the repo in the
1645 thread's work directory instead.
Simon Glassc05694f2013-04-03 11:07:16 +00001646
1647 Args:
1648 thread_num: Thread number (0, 1, ...)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001649 setup_git:
1650 'clone' to set up a git clone
1651 'worktree' to set up a git worktree
Simon Glassc05694f2013-04-03 11:07:16 +00001652 """
Simon Glassbc74d942023-07-19 17:49:06 -06001653 thread_dir = self.get_thread_dir(thread_num)
Simon Glassc5077c32023-07-19 17:49:08 -06001654 builderthread.mkdir(thread_dir)
Simon Glassc05694f2013-04-03 11:07:16 +00001655 git_dir = os.path.join(thread_dir, '.git')
1656
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001657 # Create a worktree or a git repo clone for this thread if it
1658 # doesn't already exist
Simon Glassd326ad72014-08-09 15:32:59 -06001659 if setup_git and self.git_dir:
Simon Glassc05694f2013-04-03 11:07:16 +00001660 src_dir = os.path.abspath(self.git_dir)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001661 if os.path.isdir(git_dir):
1662 # This is a clone of the src_dir repo, we can keep using
1663 # it but need to fetch from src_dir.
Simon Glass02811582022-01-29 14:14:18 -07001664 tprint('\rFetching repo for thread %d' % thread_num,
Simon Glass43054932020-04-09 15:08:43 -06001665 newline=False)
Simon Glass761648b2022-01-29 14:14:11 -07001666 gitutil.fetch(git_dir, thread_dir)
Simon Glass02811582022-01-29 14:14:18 -07001667 terminal.print_clear()
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001668 elif os.path.isfile(git_dir):
1669 # This is a worktree of the src_dir repo, we don't need to
1670 # create it again or update it in any way.
1671 pass
1672 elif os.path.exists(git_dir):
1673 # Don't know what could trigger this, but we probably
1674 # can't create a git worktree/clone here.
1675 raise ValueError('Git dir %s exists, but is not a file '
1676 'or a directory.' % git_dir)
1677 elif setup_git == 'worktree':
Simon Glass02811582022-01-29 14:14:18 -07001678 tprint('\rChecking out worktree for thread %d' % thread_num,
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001679 newline=False)
Simon Glass761648b2022-01-29 14:14:11 -07001680 gitutil.add_worktree(src_dir, thread_dir)
Simon Glass02811582022-01-29 14:14:18 -07001681 terminal.print_clear()
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001682 elif setup_git == 'clone' or setup_git == True:
Simon Glass02811582022-01-29 14:14:18 -07001683 tprint('\rCloning repo for thread %d' % thread_num,
Simon Glass2e2a6e62016-09-18 16:48:31 -06001684 newline=False)
Simon Glass761648b2022-01-29 14:14:11 -07001685 gitutil.clone(src_dir, thread_dir)
Simon Glass02811582022-01-29 14:14:18 -07001686 terminal.print_clear()
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001687 else:
1688 raise ValueError("Can't setup git repo with %s." % setup_git)
Simon Glassc05694f2013-04-03 11:07:16 +00001689
Simon Glassbc74d942023-07-19 17:49:06 -06001690 def _prepare_working_space(self, max_threads, setup_git):
Simon Glassc05694f2013-04-03 11:07:16 +00001691 """Prepare the working directory for use.
1692
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001693 Set up the git repo for each thread. Creates a linked working tree
1694 if git-worktree is available, or clones the repo if it isn't.
Simon Glassc05694f2013-04-03 11:07:16 +00001695
1696 Args:
Simon Glassc635d892021-01-30 22:17:46 -07001697 max_threads: Maximum number of threads we expect to need. If 0 then
1698 1 is set up, since the main process still needs somewhere to
1699 work
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001700 setup_git: True to set up a git worktree or a git clone
Simon Glassc05694f2013-04-03 11:07:16 +00001701 """
Simon Glassc5077c32023-07-19 17:49:08 -06001702 builderthread.mkdir(self._working_dir)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001703 if setup_git and self.git_dir:
1704 src_dir = os.path.abspath(self.git_dir)
Simon Glass761648b2022-01-29 14:14:11 -07001705 if gitutil.check_worktree_is_available(src_dir):
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001706 setup_git = 'worktree'
1707 # If we previously added a worktree but the directory for it
1708 # got deleted, we need to prune its files from the repo so
1709 # that we can check out another in its place.
Simon Glass761648b2022-01-29 14:14:11 -07001710 gitutil.prune_worktrees(src_dir)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001711 else:
1712 setup_git = 'clone'
Simon Glassc635d892021-01-30 22:17:46 -07001713
1714 # Always do at least one thread
1715 for thread in range(max(max_threads, 1)):
Simon Glassbc74d942023-07-19 17:49:06 -06001716 self._prepare_thread(thread, setup_git)
Simon Glassc05694f2013-04-03 11:07:16 +00001717
Simon Glassbc74d942023-07-19 17:49:06 -06001718 def _get_output_space_removals(self):
Simon Glassc05694f2013-04-03 11:07:16 +00001719 """Get the output directories ready to receive files.
1720
Simon Glass5dc1ca72020-03-18 09:42:45 -06001721 Figure out what needs to be deleted in the output directory before it
1722 can be used. We only delete old buildman directories which have the
Simon Glass4cb54682023-07-19 17:49:10 -06001723 expected name pattern. See get_output_dir().
Simon Glass5dc1ca72020-03-18 09:42:45 -06001724
1725 Returns:
1726 List of full paths of directories to remove
Simon Glassc05694f2013-04-03 11:07:16 +00001727 """
Simon Glassb9a9b7c2014-12-01 17:33:53 -07001728 if not self.commits:
1729 return
Simon Glassc05694f2013-04-03 11:07:16 +00001730 dir_list = []
1731 for commit_upto in range(self.commit_count):
Simon Glass4cb54682023-07-19 17:49:10 -06001732 dir_list.append(self.get_output_dir(commit_upto))
Simon Glassc05694f2013-04-03 11:07:16 +00001733
Simon Glass83cb6cc2016-09-18 16:48:32 -06001734 to_remove = []
Simon Glassc05694f2013-04-03 11:07:16 +00001735 for dirname in glob.glob(os.path.join(self.base_dir, '*')):
1736 if dirname not in dir_list:
Simon Glass5dc1ca72020-03-18 09:42:45 -06001737 leaf = dirname[len(self.base_dir) + 1:]
Ovidiu Panaitee8e9cb2020-05-15 09:30:12 +03001738 m = re.match('[0-9]+_g[0-9a-f]+_.*', leaf)
Simon Glass5dc1ca72020-03-18 09:42:45 -06001739 if m:
1740 to_remove.append(dirname)
1741 return to_remove
1742
Simon Glassbc74d942023-07-19 17:49:06 -06001743 def _prepare_output_space(self):
Simon Glass5dc1ca72020-03-18 09:42:45 -06001744 """Get the output directories ready to receive files.
1745
1746 We delete any output directories which look like ones we need to
1747 create. Having left over directories is confusing when the user wants
1748 to check the output manually.
1749 """
Simon Glassbc74d942023-07-19 17:49:06 -06001750 to_remove = self._get_output_space_removals()
Simon Glass83cb6cc2016-09-18 16:48:32 -06001751 if to_remove:
Simon Glass02811582022-01-29 14:14:18 -07001752 tprint('Removing %d old build directories...' % len(to_remove),
Simon Glass83cb6cc2016-09-18 16:48:32 -06001753 newline=False)
1754 for dirname in to_remove:
Simon Glassc05694f2013-04-03 11:07:16 +00001755 shutil.rmtree(dirname)
Simon Glass02811582022-01-29 14:14:18 -07001756 terminal.print_clear()
Simon Glassc05694f2013-04-03 11:07:16 +00001757
Simon Glassbc74d942023-07-19 17:49:06 -06001758 def build_boards(self, commits, board_selected, keep_outputs, verbose):
Simon Glassc05694f2013-04-03 11:07:16 +00001759 """Build all commits for a list of boards
1760
1761 Args:
1762 commits: List of commits to be build, each a Commit object
1763 boards_selected: Dict of selected boards, key is target name,
1764 value is Board object
Simon Glassc05694f2013-04-03 11:07:16 +00001765 keep_outputs: True to save build output files
Simon Glass78e418e2014-08-09 15:33:03 -06001766 verbose: Display build results as they are completed
Simon Glassc2f91072014-08-28 09:43:39 -06001767 Returns:
1768 Tuple containing:
1769 - number of boards that failed to build
1770 - number of boards that issued warnings
Simon Glass9bf9a722021-04-11 16:27:27 +12001771 - list of thread exceptions raised
Simon Glassc05694f2013-04-03 11:07:16 +00001772 """
Simon Glassd326ad72014-08-09 15:32:59 -06001773 self.commit_count = len(commits) if commits else 1
Simon Glassc05694f2013-04-03 11:07:16 +00001774 self.commits = commits
Simon Glass78e418e2014-08-09 15:33:03 -06001775 self._verbose = verbose
Simon Glassc05694f2013-04-03 11:07:16 +00001776
Simon Glassbc74d942023-07-19 17:49:06 -06001777 self.reset_result_summary(board_selected)
Simon Glassc5077c32023-07-19 17:49:08 -06001778 builderthread.mkdir(self.base_dir, parents = True)
Simon Glassbc74d942023-07-19 17:49:06 -06001779 self._prepare_working_space(min(self.num_threads, len(board_selected)),
Simon Glassd326ad72014-08-09 15:32:59 -06001780 commits is not None)
Simon Glassbc74d942023-07-19 17:49:06 -06001781 self._prepare_output_space()
Simon Glass6c435622022-07-11 19:03:56 -06001782 if not self._ide:
1783 tprint('\rStarting build...', newline=False)
Simon Glass7190a172023-09-07 10:00:19 -06001784 self._start_time = datetime.now()
Simon Glassbc74d942023-07-19 17:49:06 -06001785 self.setup_build(board_selected, commits)
1786 self.process_result(None)
Simon Glass9bf9a722021-04-11 16:27:27 +12001787 self.thread_exceptions = []
Simon Glassc05694f2013-04-03 11:07:16 +00001788 # Create jobs to build all commits for each board
Simon Glassc78ed662019-10-31 07:42:53 -06001789 for brd in board_selected.values():
Simon Glass4a1e88b2014-08-09 15:33:00 -06001790 job = builderthread.BuilderJob()
Simon Glass8132f982022-07-11 19:03:57 -06001791 job.brd = brd
Simon Glassc05694f2013-04-03 11:07:16 +00001792 job.commits = commits
1793 job.keep_outputs = keep_outputs
Simon Glassb6eb8cf2020-03-18 09:42:42 -06001794 job.work_in_output = self.work_in_output
Simon Glasse5650a82022-01-22 05:07:33 -07001795 job.adjust_cfg = self.adjust_cfg
Simon Glassc05694f2013-04-03 11:07:16 +00001796 job.step = self._step
Simon Glassc635d892021-01-30 22:17:46 -07001797 if self.num_threads:
1798 self.queue.put(job)
1799 else:
Simon Glassc5077c32023-07-19 17:49:08 -06001800 self._single_builder.run_job(job)
Simon Glassc05694f2013-04-03 11:07:16 +00001801
Simon Glassc635d892021-01-30 22:17:46 -07001802 if self.num_threads:
1803 term = threading.Thread(target=self.queue.join)
1804 term.setDaemon(True)
1805 term.start()
1806 while term.is_alive():
1807 term.join(100)
Simon Glassc05694f2013-04-03 11:07:16 +00001808
Simon Glassc635d892021-01-30 22:17:46 -07001809 # Wait until we have processed all output
1810 self.out_queue.join()
Simon Glass6c435622022-07-11 19:03:56 -06001811 if not self._ide:
1812 tprint()
Simon Glass726ae812020-04-09 15:08:47 -06001813
Simon Glass6c435622022-07-11 19:03:56 -06001814 msg = 'Completed: %d total built' % self.count
1815 if self.already_done:
1816 msg += ' (%d previously' % self.already_done
1817 if self.already_done != self.count:
1818 msg += ', %d newly' % (self.count - self.already_done)
1819 msg += ')'
1820 duration = datetime.now() - self._start_time
1821 if duration > timedelta(microseconds=1000000):
1822 if duration.microseconds >= 500000:
1823 duration = duration + timedelta(seconds=1)
1824 duration = duration - timedelta(microseconds=duration.microseconds)
1825 rate = float(self.count) / duration.total_seconds()
1826 msg += ', duration %s, rate %1.2f' % (duration, rate)
1827 tprint(msg)
1828 if self.thread_exceptions:
1829 tprint('Failed: %d thread exceptions' % len(self.thread_exceptions),
1830 colour=self.col.RED)
Simon Glass726ae812020-04-09 15:08:47 -06001831
Simon Glass9bf9a722021-04-11 16:27:27 +12001832 return (self.fail, self.warned, self.thread_exceptions)