blob: 5305477c5be622684dc13f98cee6fafe2857e451 [file] [log] [blame]
Tom Rini83d290c2018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glassfc3fe1c2013-04-03 11:07:16 +00002# Copyright (c) 2013 The Chromium OS Authors.
3#
4# Bloat-o-meter code used here Copyright 2004 Matt Mackall <mpm@selenic.com>
5#
Simon Glassfc3fe1c2013-04-03 11:07:16 +00006
7import collections
Simon Glassfc3fe1c2013-04-03 11:07:16 +00008from datetime import datetime, timedelta
9import glob
10import os
11import re
Simon Glassc05aa032019-10-31 07:42:53 -060012import queue
Simon Glassfc3fe1c2013-04-03 11:07:16 +000013import shutil
Simon Glass2f256642016-09-18 16:48:37 -060014import signal
Simon Glassfc3fe1c2013-04-03 11:07:16 +000015import string
16import sys
Simon Glassd436e382016-09-18 16:48:35 -060017import threading
Simon Glassfc3fe1c2013-04-03 11:07:16 +000018import time
19
Simon Glass0ede00f2020-04-17 18:09:02 -060020from buildman import builderthread
21from buildman import toolchain
Simon Glassbf776672020-04-17 18:09:04 -060022from patman import gitutil
Simon Glass4583c002023-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 Glassfc3fe1c2013-04-03 11:07:16 +000026
Simon Glass7bf83a52021-10-19 21:43:24 -060027# This indicates an new int or hex Kconfig property with no default
28# It hangs the build since the 'conf' tool cannot proceed without valid input.
29#
30# We get a repeat sequence of something like this:
31# >>
32# Break things (BREAK_ME) [] (NEW)
33# Error in reading or end of file.
34# <<
35# which indicates that BREAK_ME has an empty default
36RE_NO_DEFAULT = re.compile(b'\((\w+)\) \[] \(NEW\)')
37
Simon Glassfc3fe1c2013-04-03 11:07:16 +000038"""
39Theory of Operation
40
41Please see README for user documentation, and you should be familiar with
42that before trying to make sense of this.
43
44Buildman works by keeping the machine as busy as possible, building different
45commits for different boards on multiple CPUs at once.
46
47The source repo (self.git_dir) contains all the commits to be built. Each
48thread works on a single board at a time. It checks out the first commit,
49configures it for that board, then builds it. Then it checks out the next
50commit and builds it (typically without re-configuring). When it runs out
51of commits, it gets another job from the builder and starts again with that
52board.
53
54Clearly the builder threads could work either way - they could check out a
55commit and then built it for all boards. Using separate directories for each
56commit/board pair they could leave their build product around afterwards
57also.
58
59The intent behind building a single board for multiple commits, is to make
60use of incremental builds. Since each commit is built incrementally from
61the previous one, builds are faster. Reconfiguring for a different board
62removes all intermediate object files.
63
64Many threads can be working at once, but each has its own working directory.
65When a thread finishes a build, it puts the output files into a result
66directory.
67
68The base directory used by buildman is normally '../<branch>', i.e.
69a directory higher than the source repository and named after the branch
70being built.
71
72Within the base directory, we have one subdirectory for each commit. Within
73that is one subdirectory for each board. Within that is the build output for
74that commit/board combination.
75
76Buildman also create working directories for each thread, in a .bm-work/
77subdirectory in the base dir.
78
79As an example, say we are building branch 'us-net' for boards 'sandbox' and
80'seaboard', and say that us-net has two commits. We will have directories
81like this:
82
83us-net/ base directory
Ovidiu Panait7664b032020-05-15 09:30:12 +030084 01_g4ed4ebc_net--Add-tftp-speed-/
Simon Glassfc3fe1c2013-04-03 11:07:16 +000085 sandbox/
86 u-boot.bin
87 seaboard/
88 u-boot.bin
Ovidiu Panait7664b032020-05-15 09:30:12 +030089 02_g4ed4ebc_net--Check-tftp-comp/
Simon Glassfc3fe1c2013-04-03 11:07:16 +000090 sandbox/
91 u-boot.bin
92 seaboard/
93 u-boot.bin
94 .bm-work/
95 00/ working directory for thread 0 (contains source checkout)
96 build/ build output
97 01/ working directory for thread 1
98 build/ build output
99 ...
100u-boot/ source directory
101 .git/ repository
102"""
103
Simon Glass35d696d2020-04-09 15:08:36 -0600104"""Holds information about a particular error line we are outputing
105
106 char: Character representation: '+': error, '-': fixed error, 'w+': warning,
107 'w-' = fixed warning
108 boards: List of Board objects which have line in the error/warning output
109 errline: The text of the error line
110"""
Simon Glasscc2c0d12022-07-11 19:04:00 -0600111ErrLine = collections.namedtuple('ErrLine', 'char,brds,errline')
Simon Glass35d696d2020-04-09 15:08:36 -0600112
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000113# Possible build outcomes
Simon Glassc05aa032019-10-31 07:42:53 -0600114OUTCOME_OK, OUTCOME_WARNING, OUTCOME_ERROR, OUTCOME_UNKNOWN = list(range(4))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000115
Simon Glass9a6d2e22017-04-12 18:23:26 -0600116# Translate a commit subject into a valid filename (and handle unicode)
Simon Glassc05aa032019-10-31 07:42:53 -0600117trans_valid_chars = str.maketrans('/: ', '---')
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000118
Simon Glassb464f8e2016-11-13 14:25:53 -0700119BASE_CONFIG_FILENAMES = [
120 'u-boot.cfg', 'u-boot-spl.cfg', 'u-boot-tpl.cfg'
121]
122
123EXTRA_CONFIG_FILENAMES = [
Simon Glass843312d2015-02-05 22:06:15 -0700124 '.config', '.config-spl', '.config-tpl',
125 'autoconf.mk', 'autoconf-spl.mk', 'autoconf-tpl.mk',
126 'autoconf.h', 'autoconf-spl.h','autoconf-tpl.h',
Simon Glass843312d2015-02-05 22:06:15 -0700127]
128
Simon Glass8270e3c2015-08-25 21:52:14 -0600129class Config:
130 """Holds information about configuration settings for a board."""
Simon Glassb464f8e2016-11-13 14:25:53 -0700131 def __init__(self, config_filename, target):
Simon Glass8270e3c2015-08-25 21:52:14 -0600132 self.target = target
133 self.config = {}
Simon Glassb464f8e2016-11-13 14:25:53 -0700134 for fname in config_filename:
Simon Glass8270e3c2015-08-25 21:52:14 -0600135 self.config[fname] = {}
136
Simon Glass37edf5f2023-07-19 17:49:06 -0600137 def add(self, fname, key, value):
Simon Glass8270e3c2015-08-25 21:52:14 -0600138 self.config[fname][key] = value
139
140 def __hash__(self):
141 val = 0
142 for fname in self.config:
Simon Glassc05aa032019-10-31 07:42:53 -0600143 for key, value in self.config[fname].items():
144 print(key, value)
Simon Glass8270e3c2015-08-25 21:52:14 -0600145 val = val ^ hash(key) & hash(value)
146 return val
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000147
Alex Kiernan48ae4122018-05-31 04:48:34 +0000148class Environment:
149 """Holds information about environment variables for a board."""
150 def __init__(self, target):
151 self.target = target
152 self.environment = {}
153
Simon Glass37edf5f2023-07-19 17:49:06 -0600154 def add(self, key, value):
Alex Kiernan48ae4122018-05-31 04:48:34 +0000155 self.environment[key] = value
156
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000157class Builder:
158 """Class for building U-Boot for a particular commit.
159
160 Public members: (many should ->private)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000161 already_done: Number of builds already completed
162 base_dir: Base directory to use for builder
163 checkout: True to check out source, False to skip that step.
164 This is used for testing.
165 col: terminal.Color() object
Simon Glass6a0c7b42023-07-19 17:49:03 -0600166 count: Total number of commits to build, which is the number of commits
167 multiplied by the number of boards
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000168 do_make: Method to call to invoke Make
169 fail: Number of builds that failed due to error
170 force_build: Force building even if a build already exists
171 force_config_on_failure: If a commit fails for a board, disable
172 incremental building for the next commit we build for that
173 board, so that we will see all warnings/errors again.
Simon Glass4266dc22014-07-13 12:22:31 -0600174 force_build_failures: If a previously-built build (i.e. built on
175 a previous run of buildman) is marked as failed, rebuild it.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000176 git_dir: Git directory containing source repository
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000177 num_jobs: Number of jobs to run at once (passed to make as -j)
178 num_threads: Number of builder threads to run
179 out_queue: Queue of results to process
180 re_make_err: Compiled regular expression for ignore_lines
181 queue: Queue of jobs to run
182 threads: List of active threads
183 toolchains: Toolchains object to use for building
184 upto: Current commit number we are building (0.count-1)
185 warned: Number of builds that produced at least one warning
Simon Glass97e91522014-07-14 17:51:02 -0600186 force_reconfig: Reconfigure U-Boot on each comiit. This disables
187 incremental building, where buildman reconfigures on the first
188 commit for a baord, and then just does an incremental build for
189 the following commits. In fact buildman will reconfigure and
190 retry for any failing commits, so generally the only effect of
191 this option is to slow things down.
Simon Glass189a4962014-07-14 17:51:03 -0600192 in_tree: Build U-Boot in-tree instead of specifying an output
193 directory separate from the source code. This option is really
194 only useful for testing in-tree builds.
Simon Glassd829f122020-03-18 09:42:42 -0600195 work_in_output: Use the output directory as the work directory and
196 don't write to a separate output directory.
Simon Glass8116c782021-04-11 16:27:27 +1200197 thread_exceptions: List of exceptions raised by thread jobs
Simon Glass93202d72023-02-21 12:40:28 -0700198 no_lto (bool): True to set the NO_LTO flag when building
Simon Glassbfb708a2023-02-21 12:40:29 -0700199 reproducible_builds (bool): True to set SOURCE_DATE_EPOCH=0 for builds
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000200
201 Private members:
202 _base_board_dict: Last-summarised Dict of boards
203 _base_err_lines: Last-summarised list of errors
Simon Glasse30965d2014-08-28 09:43:44 -0600204 _base_warn_lines: Last-summarised list of warnings
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000205 _build_period_us: Time taken for a single build (float object).
206 _complete_delay: Expected delay until completion (timedelta)
207 _next_delay_update: Next time we plan to display a progress update
208 (datatime)
209 _show_unknown: Show unknown boards (those not built) in summary
Simon Glass7b33f212020-04-09 15:08:47 -0600210 _start_time: Start time for the build
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000211 _timestamps: List of timestamps for the completion of the last
212 last _timestamp_count builds. Each is a datetime object.
213 _timestamp_count: Number of timestamps to keep in our list.
214 _working_dir: Base working directory containing all threads
Simon Glassb82492b2021-01-30 22:17:46 -0700215 _single_builder: BuilderThread object for the singer builder, if
216 threading is not being used
Simon Glass7bf83a52021-10-19 21:43:24 -0600217 _terminated: Thread was terminated due to an error
218 _restarting_config: True if 'Restart config' is detected in output
Simon Glassae1a09f2022-07-11 19:03:56 -0600219 _ide: Produce output suitable for an Integrated Development Environment,
220 i.e. dont emit progress information and put errors/warnings on stderr
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000221 """
222 class Outcome:
223 """Records a build outcome for a single make invocation
224
225 Public Members:
226 rc: Outcome value (OUTCOME_...)
227 err_lines: List of error lines or [] if none
228 sizes: Dictionary of image size information, keyed by filename
229 - Each value is itself a dictionary containing
230 values for 'text', 'data' and 'bss', being the integer
231 size in bytes of each section.
232 func_sizes: Dictionary keyed by filename - e.g. 'u-boot'. Each
233 value is itself a dictionary:
234 key: function name
235 value: Size of function in bytes
Simon Glass843312d2015-02-05 22:06:15 -0700236 config: Dictionary keyed by filename - e.g. '.config'. Each
237 value is itself a dictionary:
238 key: config name
239 value: config value
Alex Kiernan48ae4122018-05-31 04:48:34 +0000240 environment: Dictionary keyed by environment variable, Each
241 value is the value of environment variable.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000242 """
Alex Kiernan48ae4122018-05-31 04:48:34 +0000243 def __init__(self, rc, err_lines, sizes, func_sizes, config,
244 environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000245 self.rc = rc
246 self.err_lines = err_lines
247 self.sizes = sizes
248 self.func_sizes = func_sizes
Simon Glass843312d2015-02-05 22:06:15 -0700249 self.config = config
Alex Kiernan48ae4122018-05-31 04:48:34 +0000250 self.environment = environment
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000251
252 def __init__(self, toolchains, base_dir, git_dir, num_threads, num_jobs,
Simon Glass5971ab52014-12-01 17:33:55 -0700253 gnu_make='make', checkout=True, show_unknown=True, step=1,
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600254 no_subdirs=False, full_path=False, verbose_build=False,
Simon Glasseb70a2c2020-04-09 15:08:51 -0600255 mrproper=False, per_board_out_dir=False,
Daniel Schwierzeck2371d1b2018-01-26 16:31:05 +0100256 config_only=False, squash_config_y=False,
Simon Glass8116c782021-04-11 16:27:27 +1200257 warnings_as_errors=False, work_in_output=False,
Tom Rinid7713ad2022-11-09 19:14:53 -0700258 test_thread_exceptions=False, adjust_cfg=None,
Simon Glassffd06d32023-07-19 17:48:52 -0600259 allow_missing=False, no_lto=False, reproducible_builds=False,
260 force_build=False, force_build_failures=False,
261 force_reconfig=False, in_tree=False,
262 force_config_on_failure=False, make_func=None):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000263 """Create a new Builder object
264
265 Args:
266 toolchains: Toolchains object to use for building
267 base_dir: Base directory to use for builder
268 git_dir: Git directory containing source repository
269 num_threads: Number of builder threads to run
270 num_jobs: Number of jobs to run at once (passed to make as -j)
Masahiro Yamada99796922014-07-22 11:19:09 +0900271 gnu_make: the command name of GNU Make.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000272 checkout: True to check out source, False to skip that step.
273 This is used for testing.
274 show_unknown: Show unknown boards (those not built) in summary
275 step: 1 to process every commit, n to process every nth commit
Simon Glassbb1501f2014-12-01 17:34:00 -0700276 no_subdirs: Don't create subdirectories when building current
277 source for a single board
278 full_path: Return the full path in CROSS_COMPILE and don't set
279 PATH
Simon Glassd2ce6582014-12-01 17:34:07 -0700280 verbose_build: Run build with V=1 and don't use 'make -s'
Simon Glasseb70a2c2020-04-09 15:08:51 -0600281 mrproper: Always run 'make mrproper' when configuring
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600282 per_board_out_dir: Build in a separate persistent directory per
283 board rather than a thread-specific directory
Simon Glassb50113f2016-11-13 14:25:51 -0700284 config_only: Only configure each build, don't build it
Simon Glassb464f8e2016-11-13 14:25:53 -0700285 squash_config_y: Convert CONFIG options with the value 'y' to '1'
Daniel Schwierzeck2371d1b2018-01-26 16:31:05 +0100286 warnings_as_errors: Treat all compiler warnings as errors
Simon Glassd829f122020-03-18 09:42:42 -0600287 work_in_output: Use the output directory as the work directory and
288 don't write to a separate output directory.
Simon Glass8116c782021-04-11 16:27:27 +1200289 test_thread_exceptions: Uses for tests only, True to make the
290 threads raise an exception instead of reporting their result.
291 This simulates a failure in the code somewhere
Simon Glass2b4806e2022-01-22 05:07:33 -0700292 adjust_cfg_list (list of str): List of changes to make to .config
293 file before building. Each is one of (where C is the config
294 option with or without the CONFIG_ prefix)
295
296 C to enable C
297 ~C to disable C
298 C=val to set the value of C (val must have quotes if C is
299 a string Kconfig
Tom Rinid7713ad2022-11-09 19:14:53 -0700300 allow_missing: Run build with BINMAN_ALLOW_MISSING=1
Simon Glass93202d72023-02-21 12:40:28 -0700301 no_lto (bool): True to set the NO_LTO flag when building
Simon Glassffd06d32023-07-19 17:48:52 -0600302 force_build (bool): Rebuild even commits that are already built
303 force_build_failures (bool): Rebuild commits that have not been
304 built, or failed to build
305 force_reconfig (bool): Reconfigure on each commit
306 in_tree (bool): Bulid in tree instead of out-of-tree
307 force_config_on_failure (bool): Reconfigure the build before
308 retrying a failed build
309 make_func (function): Function to call to run 'make'
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000310 """
311 self.toolchains = toolchains
312 self.base_dir = base_dir
Simon Glassd829f122020-03-18 09:42:42 -0600313 if work_in_output:
314 self._working_dir = base_dir
315 else:
316 self._working_dir = os.path.join(base_dir, '.bm-work')
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000317 self.threads = []
Simon Glass37edf5f2023-07-19 17:49:06 -0600318 self.do_make = make_func or self.make
Masahiro Yamada99796922014-07-22 11:19:09 +0900319 self.gnu_make = gnu_make
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000320 self.checkout = checkout
321 self.num_threads = num_threads
322 self.num_jobs = num_jobs
323 self.already_done = 0
324 self.force_build = False
325 self.git_dir = git_dir
326 self._show_unknown = show_unknown
327 self._timestamp_count = 10
328 self._build_period_us = None
329 self._complete_delay = None
330 self._next_delay_update = datetime.now()
Simon Glass2ce06f52023-09-07 10:00:19 -0600331 self._start_time = None
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000332 self._step = step
Simon Glass28370c12014-08-09 15:33:06 -0600333 self._error_lines = 0
Simon Glass5971ab52014-12-01 17:33:55 -0700334 self.no_subdirs = no_subdirs
Simon Glassbb1501f2014-12-01 17:34:00 -0700335 self.full_path = full_path
Simon Glassd2ce6582014-12-01 17:34:07 -0700336 self.verbose_build = verbose_build
Simon Glassb50113f2016-11-13 14:25:51 -0700337 self.config_only = config_only
Simon Glassb464f8e2016-11-13 14:25:53 -0700338 self.squash_config_y = squash_config_y
339 self.config_filenames = BASE_CONFIG_FILENAMES
Simon Glassd829f122020-03-18 09:42:42 -0600340 self.work_in_output = work_in_output
Simon Glass2b4806e2022-01-22 05:07:33 -0700341 self.adjust_cfg = adjust_cfg
Tom Rinid7713ad2022-11-09 19:14:53 -0700342 self.allow_missing = allow_missing
Simon Glassae1a09f2022-07-11 19:03:56 -0600343 self._ide = False
Simon Glass93202d72023-02-21 12:40:28 -0700344 self.no_lto = no_lto
Simon Glassbfb708a2023-02-21 12:40:29 -0700345 self.reproducible_builds = reproducible_builds
Simon Glassffd06d32023-07-19 17:48:52 -0600346 self.force_build = force_build
347 self.force_build_failures = force_build_failures
348 self.force_reconfig = force_reconfig
349 self.in_tree = in_tree
350 self.force_config_on_failure = force_config_on_failure
Simon Glass2b4806e2022-01-22 05:07:33 -0700351
Simon Glassb464f8e2016-11-13 14:25:53 -0700352 if not self.squash_config_y:
353 self.config_filenames += EXTRA_CONFIG_FILENAMES
Simon Glass7bf83a52021-10-19 21:43:24 -0600354 self._terminated = False
355 self._restarting_config = False
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000356
Daniel Schwierzeck2371d1b2018-01-26 16:31:05 +0100357 self.warnings_as_errors = warnings_as_errors
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000358 self.col = terminal.Color()
359
Simon Glasse30965d2014-08-28 09:43:44 -0600360 self._re_function = re.compile('(.*): In function.*')
361 self._re_files = re.compile('In file included from.*')
362 self._re_warning = re.compile('(.*):(\d*):(\d*): warning: .*')
Simon Glass2d483332018-11-06 16:02:11 -0700363 self._re_dtb_warning = re.compile('(.*): Warning .*')
Simon Glasse30965d2014-08-28 09:43:44 -0600364 self._re_note = re.compile('(.*):(\d*):(\d*): note: this is the location of the previous.*')
Simon Glass113a8a52020-04-09 15:08:53 -0600365 self._re_migration_warning = re.compile(r'^={21} WARNING ={22}\n.*\n=+\n',
366 re.MULTILINE | re.DOTALL)
Simon Glasse30965d2014-08-28 09:43:44 -0600367
Simon Glass8116c782021-04-11 16:27:27 +1200368 self.thread_exceptions = []
369 self.test_thread_exceptions = test_thread_exceptions
Simon Glassb82492b2021-01-30 22:17:46 -0700370 if self.num_threads:
371 self._single_builder = None
372 self.queue = queue.Queue()
373 self.out_queue = queue.Queue()
374 for i in range(self.num_threads):
Simon Glass8116c782021-04-11 16:27:27 +1200375 t = builderthread.BuilderThread(
376 self, i, mrproper, per_board_out_dir,
377 test_exception=test_thread_exceptions)
Simon Glassb82492b2021-01-30 22:17:46 -0700378 t.setDaemon(True)
379 t.start()
380 self.threads.append(t)
381
382 t = builderthread.ResultThread(self)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000383 t.setDaemon(True)
384 t.start()
385 self.threads.append(t)
Simon Glassb82492b2021-01-30 22:17:46 -0700386 else:
387 self._single_builder = builderthread.BuilderThread(
388 self, -1, mrproper, per_board_out_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000389
390 ignore_lines = ['(make.*Waiting for unfinished)', '(Segmentation fault)']
391 self.re_make_err = re.compile('|'.join(ignore_lines))
392
Simon Glass2f256642016-09-18 16:48:37 -0600393 # Handle existing graceful with SIGINT / Ctrl-C
394 signal.signal(signal.SIGINT, self.signal_handler)
395
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000396 def __del__(self):
397 """Get rid of all threads created by the builder"""
398 for t in self.threads:
399 del t
400
Simon Glass2f256642016-09-18 16:48:37 -0600401 def signal_handler(self, signal, frame):
402 sys.exit(1)
403
Simon Glass37edf5f2023-07-19 17:49:06 -0600404 def set_display_options(self, show_errors=False, show_sizes=False,
Simon Glassed966652014-08-28 09:43:43 -0600405 show_detail=False, show_bloat=False,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000406 list_error_boards=False, show_config=False,
Simon Glass113a8a52020-04-09 15:08:53 -0600407 show_environment=False, filter_dtb_warnings=False,
Simon Glassae1a09f2022-07-11 19:03:56 -0600408 filter_migration_warnings=False, ide=False):
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600409 """Setup display options for the builder.
410
Simon Glass174592b2020-04-09 15:08:52 -0600411 Args:
412 show_errors: True to show summarised error/warning info
413 show_sizes: Show size deltas
414 show_detail: Show size delta detail for each board if show_sizes
415 show_bloat: Show detail for each function
416 list_error_boards: Show the boards which caused each error/warning
417 show_config: Show config deltas
418 show_environment: Show environment deltas
419 filter_dtb_warnings: Filter out any warnings from the device-tree
420 compiler
Simon Glass113a8a52020-04-09 15:08:53 -0600421 filter_migration_warnings: Filter out any warnings about migrating
422 a board to driver model
Simon Glassae1a09f2022-07-11 19:03:56 -0600423 ide: Create output that can be parsed by an IDE. There is no '+' prefix on
424 error lines and output on stderr stays on stderr.
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600425 """
426 self._show_errors = show_errors
427 self._show_sizes = show_sizes
428 self._show_detail = show_detail
429 self._show_bloat = show_bloat
Simon Glassed966652014-08-28 09:43:43 -0600430 self._list_error_boards = list_error_boards
Simon Glass843312d2015-02-05 22:06:15 -0700431 self._show_config = show_config
Alex Kiernan48ae4122018-05-31 04:48:34 +0000432 self._show_environment = show_environment
Simon Glass174592b2020-04-09 15:08:52 -0600433 self._filter_dtb_warnings = filter_dtb_warnings
Simon Glass113a8a52020-04-09 15:08:53 -0600434 self._filter_migration_warnings = filter_migration_warnings
Simon Glassae1a09f2022-07-11 19:03:56 -0600435 self._ide = ide
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600436
Simon Glass37edf5f2023-07-19 17:49:06 -0600437 def _add_timestamp(self):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000438 """Add a new timestamp to the list and record the build period.
439
440 The build period is the length of time taken to perform a single
441 build (one board, one commit).
442 """
443 now = datetime.now()
444 self._timestamps.append(now)
445 count = len(self._timestamps)
446 delta = self._timestamps[-1] - self._timestamps[0]
447 seconds = delta.total_seconds()
448
449 # If we have enough data, estimate build period (time taken for a
450 # single build) and therefore completion time.
451 if count > 1 and self._next_delay_update < now:
452 self._next_delay_update = now + timedelta(seconds=2)
453 if seconds > 0:
454 self._build_period = float(seconds) / count
455 todo = self.count - self.upto
456 self._complete_delay = timedelta(microseconds=
457 self._build_period * todo * 1000000)
458 # Round it
459 self._complete_delay -= timedelta(
460 microseconds=self._complete_delay.microseconds)
461
462 if seconds > 60:
463 self._timestamps.popleft()
464 count -= 1
465
Simon Glass37edf5f2023-07-19 17:49:06 -0600466 def select_commit(self, commit, checkout=True):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000467 """Checkout the selected commit for this build
468 """
469 self.commit = commit
470 if checkout and self.checkout:
Simon Glass0157b182022-01-29 14:14:11 -0700471 gitutil.checkout(commit.hash)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000472
Simon Glass37edf5f2023-07-19 17:49:06 -0600473 def make(self, commit, brd, stage, cwd, *args, **kwargs):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000474 """Run make
475
476 Args:
477 commit: Commit object that is being built
478 brd: Board object that is being built
Roger Meierfd18a892014-08-20 22:10:29 +0200479 stage: Stage that we are at (mrproper, config, build)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000480 cwd: Directory where make should be run
481 args: Arguments to pass to make
Simon Glassd9800692022-01-29 14:14:05 -0700482 kwargs: Arguments to pass to command.run_pipe()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000483 """
Simon Glass7bf83a52021-10-19 21:43:24 -0600484
485 def check_output(stream, data):
486 if b'Restart config' in data:
487 self._restarting_config = True
488
489 # If we see 'Restart config' following by multiple errors
490 if self._restarting_config:
491 m = RE_NO_DEFAULT.findall(data)
492
493 # Number of occurences of each Kconfig item
494 multiple = [m.count(val) for val in set(m)]
495
496 # If any of them occur more than once, we have a loop
497 if [val for val in multiple if val > 1]:
498 self._terminated = True
499 return True
500 return False
501
502 self._restarting_config = False
503 self._terminated = False
Masahiro Yamada99796922014-07-22 11:19:09 +0900504 cmd = [self.gnu_make] + list(args)
Simon Glassd9800692022-01-29 14:14:05 -0700505 result = command.run_pipe([cmd], capture=True, capture_stderr=True,
Simon Glass7bf83a52021-10-19 21:43:24 -0600506 cwd=cwd, raise_on_error=False, infile='/dev/null',
507 output_func=check_output, **kwargs)
508
509 if self._terminated:
510 # Try to be helpful
511 result.stderr += '(** did you define an int/hex Kconfig with no default? **)'
512
Simon Glass40f11fc2015-02-05 22:06:12 -0700513 if self.verbose_build:
514 result.stdout = '%s\n' % (' '.join(cmd)) + result.stdout
515 result.combined = '%s\n' % (' '.join(cmd)) + result.combined
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000516 return result
517
Simon Glass37edf5f2023-07-19 17:49:06 -0600518 def process_result(self, result):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000519 """Process the result of a build, showing progress information
520
521 Args:
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600522 result: A CommandResult object, which indicates the result for
523 a single build
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000524 """
525 col = terminal.Color()
526 if result:
527 target = result.brd.target
528
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000529 self.upto += 1
530 if result.return_code != 0:
531 self.fail += 1
532 elif result.stderr:
533 self.warned += 1
534 if result.already_done:
535 self.already_done += 1
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600536 if self._verbose:
Simon Glass098b10f2022-01-29 14:14:18 -0700537 terminal.print_clear()
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600538 boards_selected = {target : result.brd}
Simon Glass37edf5f2023-07-19 17:49:06 -0600539 self.reset_result_summary(boards_selected)
540 self.produce_result_summary(result.commit_upto, self.commits,
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600541 boards_selected)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000542 else:
543 target = '(starting)'
544
545 # Display separate counts for ok, warned and fail
546 ok = self.upto - self.warned - self.fail
Simon Glass252ac582022-01-29 14:14:17 -0700547 line = '\r' + self.col.build(self.col.GREEN, '%5d' % ok)
548 line += self.col.build(self.col.YELLOW, '%5d' % self.warned)
549 line += self.col.build(self.col.RED, '%5d' % self.fail)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000550
Simon Glass6eb76ca2020-04-09 15:08:45 -0600551 line += ' /%-5d ' % self.count
552 remaining = self.count - self.upto
553 if remaining:
Simon Glass252ac582022-01-29 14:14:17 -0700554 line += self.col.build(self.col.MAGENTA, ' -%-5d ' % remaining)
Simon Glass6eb76ca2020-04-09 15:08:45 -0600555 else:
556 line += ' ' * 8
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000557
558 # Add our current completion time estimate
Simon Glass37edf5f2023-07-19 17:49:06 -0600559 self._add_timestamp()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000560 if self._complete_delay:
Simon Glass6eb76ca2020-04-09 15:08:45 -0600561 line += '%s : ' % self._complete_delay
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000562
Simon Glass6eb76ca2020-04-09 15:08:45 -0600563 line += target
Simon Glassae1a09f2022-07-11 19:03:56 -0600564 if not self._ide:
565 terminal.print_clear()
566 tprint(line, newline=False, limit_to_line=True)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000567
Simon Glass4a7419b2023-07-19 17:49:10 -0600568 def get_output_dir(self, commit_upto):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000569 """Get the name of the output directory for a commit number
570
571 The output directory is typically .../<branch>/<commit>.
572
573 Args:
574 commit_upto: Commit number to use (0..self.count-1)
575 """
Simon Glass60b285f2020-04-17 17:51:34 -0600576 if self.work_in_output:
577 return self._working_dir
578
Simon Glass5971ab52014-12-01 17:33:55 -0700579 commit_dir = None
Simon Glassfea58582014-08-09 15:32:59 -0600580 if self.commits:
581 commit = self.commits[commit_upto]
582 subject = commit.subject.translate(trans_valid_chars)
Simon Glass37edf5f2023-07-19 17:49:06 -0600583 # See _get_output_space_removals() which parses this name
Ovidiu Panait7664b032020-05-15 09:30:12 +0300584 commit_dir = ('%02d_g%s_%s' % (commit_upto + 1,
585 commit.hash, subject[:20]))
Simon Glass5971ab52014-12-01 17:33:55 -0700586 elif not self.no_subdirs:
Simon Glassfea58582014-08-09 15:32:59 -0600587 commit_dir = 'current'
Simon Glass5971ab52014-12-01 17:33:55 -0700588 if not commit_dir:
589 return self.base_dir
590 return os.path.join(self.base_dir, commit_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000591
Simon Glass37edf5f2023-07-19 17:49:06 -0600592 def get_build_dir(self, commit_upto, target):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000593 """Get the name of the build directory for a commit number
594
595 The build directory is typically .../<branch>/<commit>/<target>.
596
597 Args:
598 commit_upto: Commit number to use (0..self.count-1)
599 target: Target name
600 """
Simon Glass4a7419b2023-07-19 17:49:10 -0600601 output_dir = self.get_output_dir(commit_upto)
Simon Glass60b285f2020-04-17 17:51:34 -0600602 if self.work_in_output:
603 return output_dir
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000604 return os.path.join(output_dir, target)
605
Simon Glass37edf5f2023-07-19 17:49:06 -0600606 def get_done_file(self, commit_upto, target):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000607 """Get the name of the done file for a commit number
608
609 Args:
610 commit_upto: Commit number to use (0..self.count-1)
611 target: Target name
612 """
Simon Glass37edf5f2023-07-19 17:49:06 -0600613 return os.path.join(self.get_build_dir(commit_upto, target), 'done')
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000614
Simon Glass37edf5f2023-07-19 17:49:06 -0600615 def get_sizes_file(self, commit_upto, target):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000616 """Get the name of the sizes file for a commit number
617
618 Args:
619 commit_upto: Commit number to use (0..self.count-1)
620 target: Target name
621 """
Simon Glass37edf5f2023-07-19 17:49:06 -0600622 return os.path.join(self.get_build_dir(commit_upto, target), 'sizes')
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000623
Simon Glass37edf5f2023-07-19 17:49:06 -0600624 def get_func_sizes_file(self, commit_upto, target, elf_fname):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000625 """Get the name of the funcsizes file for a commit number and ELF file
626
627 Args:
628 commit_upto: Commit number to use (0..self.count-1)
629 target: Target name
630 elf_fname: Filename of elf image
631 """
Simon Glass37edf5f2023-07-19 17:49:06 -0600632 return os.path.join(self.get_build_dir(commit_upto, target),
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000633 '%s.sizes' % elf_fname.replace('/', '-'))
634
Simon Glass37edf5f2023-07-19 17:49:06 -0600635 def get_objdump_file(self, commit_upto, target, elf_fname):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000636 """Get the name of the objdump file for a commit number and ELF file
637
638 Args:
639 commit_upto: Commit number to use (0..self.count-1)
640 target: Target name
641 elf_fname: Filename of elf image
642 """
Simon Glass37edf5f2023-07-19 17:49:06 -0600643 return os.path.join(self.get_build_dir(commit_upto, target),
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000644 '%s.objdump' % elf_fname.replace('/', '-'))
645
Simon Glass37edf5f2023-07-19 17:49:06 -0600646 def get_err_file(self, commit_upto, target):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000647 """Get the name of the err file for a commit number
648
649 Args:
650 commit_upto: Commit number to use (0..self.count-1)
651 target: Target name
652 """
Simon Glass37edf5f2023-07-19 17:49:06 -0600653 output_dir = self.get_build_dir(commit_upto, target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000654 return os.path.join(output_dir, 'err')
655
Simon Glass37edf5f2023-07-19 17:49:06 -0600656 def filter_errors(self, lines):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000657 """Filter out errors in which we have no interest
658
659 We should probably use map().
660
661 Args:
662 lines: List of error lines, each a string
663 Returns:
664 New list with only interesting lines included
665 """
666 out_lines = []
Simon Glass113a8a52020-04-09 15:08:53 -0600667 if self._filter_migration_warnings:
668 text = '\n'.join(lines)
669 text = self._re_migration_warning.sub('', text)
670 lines = text.splitlines()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000671 for line in lines:
Simon Glass174592b2020-04-09 15:08:52 -0600672 if self.re_make_err.search(line):
673 continue
674 if self._filter_dtb_warnings and self._re_dtb_warning.search(line):
675 continue
676 out_lines.append(line)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000677 return out_lines
678
Simon Glass37edf5f2023-07-19 17:49:06 -0600679 def read_func_sizes(self, fname, fd):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000680 """Read function sizes from the output of 'nm'
681
682 Args:
683 fd: File containing data to read
684 fname: Filename we are reading from (just for errors)
685
686 Returns:
687 Dictionary containing size of each function in bytes, indexed by
688 function name.
689 """
690 sym = {}
691 for line in fd.readlines():
Simon Glassf2e67752022-07-11 19:04:11 -0600692 line = line.strip()
693 parts = line.split()
694 if line and len(parts) == 3:
695 size, type, name = line.split()
696 if type in 'tTdDbB':
697 # function names begin with '.' on 64-bit powerpc
698 if '.' in name[1:]:
699 name = 'static.' + name.split('.')[0]
700 sym[name] = sym.get(name, 0) + int(size, 16)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000701 return sym
702
Simon Glass37edf5f2023-07-19 17:49:06 -0600703 def _process_config(self, fname):
Simon Glass843312d2015-02-05 22:06:15 -0700704 """Read in a .config, autoconf.mk or autoconf.h file
705
706 This function handles all config file types. It ignores comments and
707 any #defines which don't start with CONFIG_.
708
709 Args:
710 fname: Filename to read
711
712 Returns:
713 Dictionary:
714 key: Config name (e.g. CONFIG_DM)
715 value: Config value (e.g. 1)
716 """
717 config = {}
718 if os.path.exists(fname):
719 with open(fname) as fd:
720 for line in fd:
721 line = line.strip()
722 if line.startswith('#define'):
723 values = line[8:].split(' ', 1)
724 if len(values) > 1:
725 key, value = values
726 else:
727 key = values[0]
Simon Glassb464f8e2016-11-13 14:25:53 -0700728 value = '1' if self.squash_config_y else ''
Simon Glass843312d2015-02-05 22:06:15 -0700729 if not key.startswith('CONFIG_'):
730 continue
731 elif not line or line[0] in ['#', '*', '/']:
732 continue
733 else:
734 key, value = line.split('=', 1)
Simon Glassb464f8e2016-11-13 14:25:53 -0700735 if self.squash_config_y and value == 'y':
736 value = '1'
Simon Glass843312d2015-02-05 22:06:15 -0700737 config[key] = value
738 return config
739
Simon Glass37edf5f2023-07-19 17:49:06 -0600740 def _process_environment(self, fname):
Alex Kiernan48ae4122018-05-31 04:48:34 +0000741 """Read in a uboot.env file
742
743 This function reads in environment variables from a file.
744
745 Args:
746 fname: Filename to read
747
748 Returns:
749 Dictionary:
750 key: environment variable (e.g. bootlimit)
751 value: value of environment variable (e.g. 1)
752 """
753 environment = {}
754 if os.path.exists(fname):
755 with open(fname) as fd:
756 for line in fd.read().split('\0'):
757 try:
758 key, value = line.split('=', 1)
759 environment[key] = value
760 except ValueError:
761 # ignore lines we can't parse
762 pass
763 return environment
764
Simon Glass37edf5f2023-07-19 17:49:06 -0600765 def get_build_outcome(self, commit_upto, target, read_func_sizes,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000766 read_config, read_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000767 """Work out the outcome of a build.
768
769 Args:
770 commit_upto: Commit number to check (0..n-1)
771 target: Target board to check
772 read_func_sizes: True to read function size information
Simon Glass843312d2015-02-05 22:06:15 -0700773 read_config: True to read .config and autoconf.h files
Alex Kiernan48ae4122018-05-31 04:48:34 +0000774 read_environment: True to read uboot.env files
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000775
776 Returns:
777 Outcome object
778 """
Simon Glass37edf5f2023-07-19 17:49:06 -0600779 done_file = self.get_done_file(commit_upto, target)
780 sizes_file = self.get_sizes_file(commit_upto, target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000781 sizes = {}
782 func_sizes = {}
Simon Glass843312d2015-02-05 22:06:15 -0700783 config = {}
Alex Kiernan48ae4122018-05-31 04:48:34 +0000784 environment = {}
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000785 if os.path.exists(done_file):
786 with open(done_file, 'r') as fd:
Simon Glass347ea0b2019-04-26 19:02:23 -0600787 try:
788 return_code = int(fd.readline())
789 except ValueError:
790 # The file may be empty due to running out of disk space.
791 # Try a rebuild
792 return_code = 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000793 err_lines = []
Simon Glass37edf5f2023-07-19 17:49:06 -0600794 err_file = self.get_err_file(commit_upto, target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000795 if os.path.exists(err_file):
796 with open(err_file, 'r') as fd:
Simon Glass37edf5f2023-07-19 17:49:06 -0600797 err_lines = self.filter_errors(fd.readlines())
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000798
799 # Decide whether the build was ok, failed or created warnings
800 if return_code:
801 rc = OUTCOME_ERROR
802 elif len(err_lines):
803 rc = OUTCOME_WARNING
804 else:
805 rc = OUTCOME_OK
806
807 # Convert size information to our simple format
808 if os.path.exists(sizes_file):
809 with open(sizes_file, 'r') as fd:
810 for line in fd.readlines():
811 values = line.split()
812 rodata = 0
813 if len(values) > 6:
814 rodata = int(values[6], 16)
815 size_dict = {
816 'all' : int(values[0]) + int(values[1]) +
817 int(values[2]),
818 'text' : int(values[0]) - rodata,
819 'data' : int(values[1]),
820 'bss' : int(values[2]),
821 'rodata' : rodata,
822 }
823 sizes[values[5]] = size_dict
824
825 if read_func_sizes:
Simon Glass37edf5f2023-07-19 17:49:06 -0600826 pattern = self.get_func_sizes_file(commit_upto, target, '*')
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000827 for fname in glob.glob(pattern):
828 with open(fname, 'r') as fd:
829 dict_name = os.path.basename(fname).replace('.sizes',
830 '')
Simon Glass37edf5f2023-07-19 17:49:06 -0600831 func_sizes[dict_name] = self.read_func_sizes(fname, fd)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000832
Simon Glass843312d2015-02-05 22:06:15 -0700833 if read_config:
Simon Glass37edf5f2023-07-19 17:49:06 -0600834 output_dir = self.get_build_dir(commit_upto, target)
Simon Glassb464f8e2016-11-13 14:25:53 -0700835 for name in self.config_filenames:
Simon Glass843312d2015-02-05 22:06:15 -0700836 fname = os.path.join(output_dir, name)
Simon Glass37edf5f2023-07-19 17:49:06 -0600837 config[name] = self._process_config(fname)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000838
Alex Kiernan48ae4122018-05-31 04:48:34 +0000839 if read_environment:
Simon Glass37edf5f2023-07-19 17:49:06 -0600840 output_dir = self.get_build_dir(commit_upto, target)
Alex Kiernan48ae4122018-05-31 04:48:34 +0000841 fname = os.path.join(output_dir, 'uboot.env')
Simon Glass37edf5f2023-07-19 17:49:06 -0600842 environment = self._process_environment(fname)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000843
Alex Kiernan48ae4122018-05-31 04:48:34 +0000844 return Builder.Outcome(rc, err_lines, sizes, func_sizes, config,
845 environment)
846
847 return Builder.Outcome(OUTCOME_UNKNOWN, [], {}, {}, {}, {})
Simon Glass843312d2015-02-05 22:06:15 -0700848
Simon Glass37edf5f2023-07-19 17:49:06 -0600849 def get_result_summary(self, boards_selected, commit_upto, read_func_sizes,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000850 read_config, read_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000851 """Calculate a summary of the results of building a commit.
852
853 Args:
854 board_selected: Dict containing boards to summarise
855 commit_upto: Commit number to summarize (0..self.count-1)
856 read_func_sizes: True to read function size information
Simon Glass843312d2015-02-05 22:06:15 -0700857 read_config: True to read .config and autoconf.h files
Alex Kiernan48ae4122018-05-31 04:48:34 +0000858 read_environment: True to read uboot.env files
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000859
860 Returns:
861 Tuple:
Simon Glassae1a09f2022-07-11 19:03:56 -0600862 Dict containing boards which built this commit:
863 key: board.target
864 value: Builder.Outcome object
Simon Glasse30965d2014-08-28 09:43:44 -0600865 List containing a summary of error lines
Simon Glassed966652014-08-28 09:43:43 -0600866 Dict keyed by error line, containing a list of the Board
867 objects with that error
Simon Glasse30965d2014-08-28 09:43:44 -0600868 List containing a summary of warning lines
869 Dict keyed by error line, containing a list of the Board
870 objects with that warning
Simon Glass8270e3c2015-08-25 21:52:14 -0600871 Dictionary keyed by board.target. Each value is a dictionary:
872 key: filename - e.g. '.config'
Simon Glass843312d2015-02-05 22:06:15 -0700873 value is itself a dictionary:
874 key: config name
875 value: config value
Alex Kiernan48ae4122018-05-31 04:48:34 +0000876 Dictionary keyed by board.target. Each value is a dictionary:
877 key: environment variable
878 value: value of environment variable
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000879 """
Simon Glass37edf5f2023-07-19 17:49:06 -0600880 def add_line(lines_summary, lines_boards, line, board):
Simon Glasse30965d2014-08-28 09:43:44 -0600881 line = line.rstrip()
882 if line in lines_boards:
883 lines_boards[line].append(board)
884 else:
885 lines_boards[line] = [board]
886 lines_summary.append(line)
887
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000888 board_dict = {}
889 err_lines_summary = []
Simon Glassed966652014-08-28 09:43:43 -0600890 err_lines_boards = {}
Simon Glasse30965d2014-08-28 09:43:44 -0600891 warn_lines_summary = []
892 warn_lines_boards = {}
Simon Glass843312d2015-02-05 22:06:15 -0700893 config = {}
Alex Kiernan48ae4122018-05-31 04:48:34 +0000894 environment = {}
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000895
Simon Glassf4ed4702022-07-11 19:03:57 -0600896 for brd in boards_selected.values():
Simon Glass37edf5f2023-07-19 17:49:06 -0600897 outcome = self.get_build_outcome(commit_upto, brd.target,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000898 read_func_sizes, read_config,
899 read_environment)
Simon Glassf4ed4702022-07-11 19:03:57 -0600900 board_dict[brd.target] = outcome
Simon Glasse30965d2014-08-28 09:43:44 -0600901 last_func = None
902 last_was_warning = False
903 for line in outcome.err_lines:
904 if line:
905 if (self._re_function.match(line) or
906 self._re_files.match(line)):
907 last_func = line
Simon Glassed966652014-08-28 09:43:43 -0600908 else:
Simon Glass2d483332018-11-06 16:02:11 -0700909 is_warning = (self._re_warning.match(line) or
910 self._re_dtb_warning.match(line))
Simon Glasse30965d2014-08-28 09:43:44 -0600911 is_note = self._re_note.match(line)
912 if is_warning or (last_was_warning and is_note):
913 if last_func:
Simon Glass37edf5f2023-07-19 17:49:06 -0600914 add_line(warn_lines_summary, warn_lines_boards,
Simon Glassf4ed4702022-07-11 19:03:57 -0600915 last_func, brd)
Simon Glass37edf5f2023-07-19 17:49:06 -0600916 add_line(warn_lines_summary, warn_lines_boards,
Simon Glassf4ed4702022-07-11 19:03:57 -0600917 line, brd)
Simon Glasse30965d2014-08-28 09:43:44 -0600918 else:
919 if last_func:
Simon Glass37edf5f2023-07-19 17:49:06 -0600920 add_line(err_lines_summary, err_lines_boards,
Simon Glassf4ed4702022-07-11 19:03:57 -0600921 last_func, brd)
Simon Glass37edf5f2023-07-19 17:49:06 -0600922 add_line(err_lines_summary, err_lines_boards,
Simon Glassf4ed4702022-07-11 19:03:57 -0600923 line, brd)
Simon Glasse30965d2014-08-28 09:43:44 -0600924 last_was_warning = is_warning
925 last_func = None
Simon Glassf4ed4702022-07-11 19:03:57 -0600926 tconfig = Config(self.config_filenames, brd.target)
Simon Glassb464f8e2016-11-13 14:25:53 -0700927 for fname in self.config_filenames:
Simon Glass843312d2015-02-05 22:06:15 -0700928 if outcome.config:
Simon Glassc05aa032019-10-31 07:42:53 -0600929 for key, value in outcome.config[fname].items():
Simon Glass37edf5f2023-07-19 17:49:06 -0600930 tconfig.add(fname, key, value)
Simon Glassf4ed4702022-07-11 19:03:57 -0600931 config[brd.target] = tconfig
Simon Glass843312d2015-02-05 22:06:15 -0700932
Simon Glassf4ed4702022-07-11 19:03:57 -0600933 tenvironment = Environment(brd.target)
Alex Kiernan48ae4122018-05-31 04:48:34 +0000934 if outcome.environment:
Simon Glassc05aa032019-10-31 07:42:53 -0600935 for key, value in outcome.environment.items():
Simon Glass37edf5f2023-07-19 17:49:06 -0600936 tenvironment.add(key, value)
Simon Glassf4ed4702022-07-11 19:03:57 -0600937 environment[brd.target] = tenvironment
Alex Kiernan48ae4122018-05-31 04:48:34 +0000938
Simon Glasse30965d2014-08-28 09:43:44 -0600939 return (board_dict, err_lines_summary, err_lines_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000940 warn_lines_summary, warn_lines_boards, config, environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000941
Simon Glass37edf5f2023-07-19 17:49:06 -0600942 def add_outcome(self, board_dict, arch_list, changes, char, color):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000943 """Add an output to our list of outcomes for each architecture
944
945 This simple function adds failing boards (changes) to the
946 relevant architecture string, so we can print the results out
947 sorted by architecture.
948
949 Args:
950 board_dict: Dict containing all boards
951 arch_list: Dict keyed by arch name. Value is a string containing
952 a list of board names which failed for that arch.
953 changes: List of boards to add to arch_list
954 color: terminal.Colour object
955 """
956 done_arch = {}
957 for target in changes:
958 if target in board_dict:
959 arch = board_dict[target].arch
960 else:
961 arch = 'unknown'
Simon Glass252ac582022-01-29 14:14:17 -0700962 str = self.col.build(color, ' ' + target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000963 if not arch in done_arch:
Simon Glass252ac582022-01-29 14:14:17 -0700964 str = ' %s %s' % (self.col.build(color, char), str)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000965 done_arch[arch] = True
966 if not arch in arch_list:
967 arch_list[arch] = str
968 else:
969 arch_list[arch] += str
970
971
Simon Glass37edf5f2023-07-19 17:49:06 -0600972 def colour_num(self, num):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000973 color = self.col.RED if num > 0 else self.col.GREEN
974 if num == 0:
975 return '0'
Simon Glass252ac582022-01-29 14:14:17 -0700976 return self.col.build(color, str(num))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000977
Simon Glass37edf5f2023-07-19 17:49:06 -0600978 def reset_result_summary(self, board_selected):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000979 """Reset the results summary ready for use.
980
981 Set up the base board list to be all those selected, and set the
982 error lines to empty.
983
Simon Glass37edf5f2023-07-19 17:49:06 -0600984 Following this, calls to print_result_summary() will use this
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000985 information to work out what has changed.
986
987 Args:
988 board_selected: Dict containing boards to summarise, keyed by
989 board.target
990 """
991 self._base_board_dict = {}
Simon Glassf4ed4702022-07-11 19:03:57 -0600992 for brd in board_selected:
993 self._base_board_dict[brd] = Builder.Outcome(0, [], [], {}, {}, {})
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000994 self._base_err_lines = []
Simon Glasse30965d2014-08-28 09:43:44 -0600995 self._base_warn_lines = []
996 self._base_err_line_boards = {}
997 self._base_warn_line_boards = {}
Simon Glass8270e3c2015-08-25 21:52:14 -0600998 self._base_config = None
Alex Kiernan48ae4122018-05-31 04:48:34 +0000999 self._base_environment = None
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001000
Simon Glass37edf5f2023-07-19 17:49:06 -06001001 def print_func_size_detail(self, fname, old, new):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001002 grow, shrink, add, remove, up, down = 0, 0, 0, 0, 0, 0
1003 delta, common = [], {}
1004
1005 for a in old:
1006 if a in new:
1007 common[a] = 1
1008
1009 for name in old:
1010 if name not in common:
1011 remove += 1
1012 down += old[name]
1013 delta.append([-old[name], name])
1014
1015 for name in new:
1016 if name not in common:
1017 add += 1
1018 up += new[name]
1019 delta.append([new[name], name])
1020
1021 for name in common:
1022 diff = new.get(name, 0) - old.get(name, 0)
1023 if diff > 0:
1024 grow, up = grow + 1, up + diff
1025 elif diff < 0:
1026 shrink, down = shrink + 1, down - diff
1027 delta.append([diff, name])
1028
1029 delta.sort()
1030 delta.reverse()
1031
1032 args = [add, -remove, grow, -shrink, up, -down, up - down]
Tom Rinid5686a62017-05-22 13:48:52 -04001033 if max(args) == 0 and min(args) == 0:
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001034 return
Simon Glass37edf5f2023-07-19 17:49:06 -06001035 args = [self.colour_num(x) for x in args]
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001036 indent = ' ' * 15
Simon Glass098b10f2022-01-29 14:14:18 -07001037 tprint('%s%s: add: %s/%s, grow: %s/%s bytes: %s/%s (%s)' %
Simon Glass252ac582022-01-29 14:14:17 -07001038 tuple([indent, self.col.build(self.col.YELLOW, fname)] + args))
Simon Glass098b10f2022-01-29 14:14:18 -07001039 tprint('%s %-38s %7s %7s %+7s' % (indent, 'function', 'old', 'new',
Simon Glass4653a882014-09-05 19:00:07 -06001040 'delta'))
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001041 for diff, name in delta:
1042 if diff:
1043 color = self.col.RED if diff > 0 else self.col.GREEN
1044 msg = '%s %-38s %7s %7s %+7d' % (indent, name,
1045 old.get(name, '-'), new.get(name,'-'), diff)
Simon Glass098b10f2022-01-29 14:14:18 -07001046 tprint(msg, colour=color)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001047
1048
Simon Glass37edf5f2023-07-19 17:49:06 -06001049 def print_size_detail(self, target_list, show_bloat):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001050 """Show details size information for each board
1051
1052 Args:
1053 target_list: List of targets, each a dict containing:
1054 'target': Target name
1055 'total_diff': Total difference in bytes across all areas
1056 <part_name>: Difference for that part
1057 show_bloat: Show detail for each function
1058 """
1059 targets_by_diff = sorted(target_list, reverse=True,
1060 key=lambda x: x['_total_diff'])
1061 for result in targets_by_diff:
1062 printed_target = False
1063 for name in sorted(result):
1064 diff = result[name]
1065 if name.startswith('_'):
1066 continue
1067 if diff != 0:
1068 color = self.col.RED if diff > 0 else self.col.GREEN
1069 msg = ' %s %+d' % (name, diff)
1070 if not printed_target:
Simon Glass098b10f2022-01-29 14:14:18 -07001071 tprint('%10s %-15s:' % ('', result['_target']),
Simon Glass4653a882014-09-05 19:00:07 -06001072 newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001073 printed_target = True
Simon Glass098b10f2022-01-29 14:14:18 -07001074 tprint(msg, colour=color, newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001075 if printed_target:
Simon Glass098b10f2022-01-29 14:14:18 -07001076 tprint()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001077 if show_bloat:
1078 target = result['_target']
1079 outcome = result['_outcome']
1080 base_outcome = self._base_board_dict[target]
1081 for fname in outcome.func_sizes:
Simon Glass37edf5f2023-07-19 17:49:06 -06001082 self.print_func_size_detail(fname,
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001083 base_outcome.func_sizes[fname],
1084 outcome.func_sizes[fname])
1085
1086
Simon Glass37edf5f2023-07-19 17:49:06 -06001087 def print_size_summary(self, board_selected, board_dict, show_detail,
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001088 show_bloat):
1089 """Print a summary of image sizes broken down by section.
1090
1091 The summary takes the form of one line per architecture. The
1092 line contains deltas for each of the sections (+ means the section
Flavio Suligoi9de5c392020-01-29 09:56:05 +01001093 got bigger, - means smaller). The numbers are the average number
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001094 of bytes that a board in this section increased by.
1095
1096 For example:
1097 powerpc: (622 boards) text -0.0
1098 arm: (285 boards) text -0.0
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001099
1100 Args:
1101 board_selected: Dict containing boards to summarise, keyed by
1102 board.target
1103 board_dict: Dict containing boards for which we built this
1104 commit, keyed by board.target. The value is an Outcome object.
Simon Glassf9c094b2020-03-18 09:42:43 -06001105 show_detail: Show size delta detail for each board
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001106 show_bloat: Show detail for each function
1107 """
1108 arch_list = {}
1109 arch_count = {}
1110
1111 # Calculate changes in size for different image parts
1112 # The previous sizes are in Board.sizes, for each board
1113 for target in board_dict:
1114 if target not in board_selected:
1115 continue
1116 base_sizes = self._base_board_dict[target].sizes
1117 outcome = board_dict[target]
1118 sizes = outcome.sizes
1119
1120 # Loop through the list of images, creating a dict of size
1121 # changes for each image/part. We end up with something like
1122 # {'target' : 'snapper9g45, 'data' : 5, 'u-boot-spl:text' : -4}
1123 # which means that U-Boot data increased by 5 bytes and SPL
1124 # text decreased by 4.
1125 err = {'_target' : target}
1126 for image in sizes:
1127 if image in base_sizes:
1128 base_image = base_sizes[image]
1129 # Loop through the text, data, bss parts
1130 for part in sorted(sizes[image]):
1131 diff = sizes[image][part] - base_image[part]
1132 col = None
1133 if diff:
1134 if image == 'u-boot':
1135 name = part
1136 else:
1137 name = image + ':' + part
1138 err[name] = diff
1139 arch = board_selected[target].arch
1140 if not arch in arch_count:
1141 arch_count[arch] = 1
1142 else:
1143 arch_count[arch] += 1
1144 if not sizes:
1145 pass # Only add to our list when we have some stats
1146 elif not arch in arch_list:
1147 arch_list[arch] = [err]
1148 else:
1149 arch_list[arch].append(err)
1150
1151 # We now have a list of image size changes sorted by arch
1152 # Print out a summary of these
Simon Glassc05aa032019-10-31 07:42:53 -06001153 for arch, target_list in arch_list.items():
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001154 # Get total difference for each type
1155 totals = {}
1156 for result in target_list:
1157 total = 0
Simon Glassc05aa032019-10-31 07:42:53 -06001158 for name, diff in result.items():
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001159 if name.startswith('_'):
1160 continue
1161 total += diff
1162 if name in totals:
1163 totals[name] += diff
1164 else:
1165 totals[name] = diff
1166 result['_total_diff'] = total
1167 result['_outcome'] = board_dict[result['_target']]
1168
1169 count = len(target_list)
1170 printed_arch = False
1171 for name in sorted(totals):
1172 diff = totals[name]
1173 if diff:
1174 # Display the average difference in this name for this
1175 # architecture
1176 avg_diff = float(diff) / count
1177 color = self.col.RED if avg_diff > 0 else self.col.GREEN
1178 msg = ' %s %+1.1f' % (name, avg_diff)
1179 if not printed_arch:
Simon Glass098b10f2022-01-29 14:14:18 -07001180 tprint('%10s: (for %d/%d boards)' % (arch, count,
Simon Glass4653a882014-09-05 19:00:07 -06001181 arch_count[arch]), newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001182 printed_arch = True
Simon Glass098b10f2022-01-29 14:14:18 -07001183 tprint(msg, colour=color, newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001184
1185 if printed_arch:
Simon Glass098b10f2022-01-29 14:14:18 -07001186 tprint()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001187 if show_detail:
Simon Glass37edf5f2023-07-19 17:49:06 -06001188 self.print_size_detail(target_list, show_bloat)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001189
1190
Simon Glass37edf5f2023-07-19 17:49:06 -06001191 def print_result_summary(self, board_selected, board_dict, err_lines,
Simon Glasse30965d2014-08-28 09:43:44 -06001192 err_line_boards, warn_lines, warn_line_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001193 config, environment, show_sizes, show_detail,
1194 show_bloat, show_config, show_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001195 """Compare results with the base results and display delta.
1196
1197 Only boards mentioned in board_selected will be considered. This
1198 function is intended to be called repeatedly with the results of
1199 each commit. It therefore shows a 'diff' between what it saw in
1200 the last call and what it sees now.
1201
1202 Args:
1203 board_selected: Dict containing boards to summarise, keyed by
1204 board.target
1205 board_dict: Dict containing boards for which we built this
1206 commit, keyed by board.target. The value is an Outcome object.
1207 err_lines: A list of errors for this commit, or [] if there is
1208 none, or we don't want to print errors
Simon Glassed966652014-08-28 09:43:43 -06001209 err_line_boards: Dict keyed by error line, containing a list of
1210 the Board objects with that error
Simon Glasse30965d2014-08-28 09:43:44 -06001211 warn_lines: A list of warnings for this commit, or [] if there is
1212 none, or we don't want to print errors
1213 warn_line_boards: Dict keyed by warning line, containing a list of
1214 the Board objects with that warning
Simon Glass843312d2015-02-05 22:06:15 -07001215 config: Dictionary keyed by filename - e.g. '.config'. Each
1216 value is itself a dictionary:
1217 key: config name
1218 value: config value
Alex Kiernan48ae4122018-05-31 04:48:34 +00001219 environment: Dictionary keyed by environment variable, Each
1220 value is the value of environment variable.
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001221 show_sizes: Show image size deltas
Simon Glassf9c094b2020-03-18 09:42:43 -06001222 show_detail: Show size delta detail for each board if show_sizes
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001223 show_bloat: Show detail for each function
Simon Glass843312d2015-02-05 22:06:15 -07001224 show_config: Show config changes
Alex Kiernan48ae4122018-05-31 04:48:34 +00001225 show_environment: Show environment changes
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001226 """
Simon Glass37edf5f2023-07-19 17:49:06 -06001227 def _board_list(line, line_boards):
Simon Glassed966652014-08-28 09:43:43 -06001228 """Helper function to get a line of boards containing a line
1229
1230 Args:
1231 line: Error line to search for
Simon Glass35d696d2020-04-09 15:08:36 -06001232 line_boards: boards to search, each a Board
Simon Glassed966652014-08-28 09:43:43 -06001233 Return:
Simon Glass35d696d2020-04-09 15:08:36 -06001234 List of boards with that error line, or [] if the user has not
1235 requested such a list
Simon Glassed966652014-08-28 09:43:43 -06001236 """
Simon Glasscc2c0d12022-07-11 19:04:00 -06001237 brds = []
Simon Glass35d696d2020-04-09 15:08:36 -06001238 board_set = set()
Simon Glassed966652014-08-28 09:43:43 -06001239 if self._list_error_boards:
Simon Glassf4ed4702022-07-11 19:03:57 -06001240 for brd in line_boards[line]:
1241 if not brd in board_set:
Simon Glasscc2c0d12022-07-11 19:04:00 -06001242 brds.append(brd)
Simon Glassf4ed4702022-07-11 19:03:57 -06001243 board_set.add(brd)
Simon Glasscc2c0d12022-07-11 19:04:00 -06001244 return brds
Simon Glassed966652014-08-28 09:43:43 -06001245
Simon Glass37edf5f2023-07-19 17:49:06 -06001246 def _calc_error_delta(base_lines, base_line_boards, lines, line_boards,
Simon Glasse30965d2014-08-28 09:43:44 -06001247 char):
Simon Glass35d696d2020-04-09 15:08:36 -06001248 """Calculate the required output based on changes in errors
1249
1250 Args:
1251 base_lines: List of errors/warnings for previous commit
1252 base_line_boards: Dict keyed by error line, containing a list
1253 of the Board objects with that error in the previous commit
1254 lines: List of errors/warning for this commit, each a str
1255 line_boards: Dict keyed by error line, containing a list
1256 of the Board objects with that error in this commit
1257 char: Character representing error ('') or warning ('w'). The
1258 broken ('+') or fixed ('-') characters are added in this
1259 function
1260
1261 Returns:
1262 Tuple
1263 List of ErrLine objects for 'better' lines
1264 List of ErrLine objects for 'worse' lines
1265 """
Simon Glasse30965d2014-08-28 09:43:44 -06001266 better_lines = []
1267 worse_lines = []
1268 for line in lines:
1269 if line not in base_lines:
Simon Glass37edf5f2023-07-19 17:49:06 -06001270 errline = ErrLine(char + '+', _board_list(line, line_boards),
Simon Glass35d696d2020-04-09 15:08:36 -06001271 line)
1272 worse_lines.append(errline)
Simon Glasse30965d2014-08-28 09:43:44 -06001273 for line in base_lines:
1274 if line not in lines:
Simon Glass35d696d2020-04-09 15:08:36 -06001275 errline = ErrLine(char + '-',
Simon Glass37edf5f2023-07-19 17:49:06 -06001276 _board_list(line, base_line_boards), line)
Simon Glass35d696d2020-04-09 15:08:36 -06001277 better_lines.append(errline)
Simon Glasse30965d2014-08-28 09:43:44 -06001278 return better_lines, worse_lines
1279
Simon Glass37edf5f2023-07-19 17:49:06 -06001280 def _calc_config(delta, name, config):
Simon Glass843312d2015-02-05 22:06:15 -07001281 """Calculate configuration changes
1282
1283 Args:
1284 delta: Type of the delta, e.g. '+'
1285 name: name of the file which changed (e.g. .config)
1286 config: configuration change dictionary
1287 key: config name
1288 value: config value
1289 Returns:
1290 String containing the configuration changes which can be
1291 printed
1292 """
1293 out = ''
1294 for key in sorted(config.keys()):
1295 out += '%s=%s ' % (key, config[key])
Simon Glass8270e3c2015-08-25 21:52:14 -06001296 return '%s %s: %s' % (delta, name, out)
Simon Glass843312d2015-02-05 22:06:15 -07001297
Simon Glass37edf5f2023-07-19 17:49:06 -06001298 def _add_config(lines, name, config_plus, config_minus, config_change):
Simon Glass8270e3c2015-08-25 21:52:14 -06001299 """Add changes in configuration to a list
Simon Glass843312d2015-02-05 22:06:15 -07001300
1301 Args:
Simon Glass8270e3c2015-08-25 21:52:14 -06001302 lines: list to add to
1303 name: config file name
Simon Glass843312d2015-02-05 22:06:15 -07001304 config_plus: configurations added, dictionary
1305 key: config name
1306 value: config value
1307 config_minus: configurations removed, dictionary
1308 key: config name
1309 value: config value
1310 config_change: configurations changed, dictionary
1311 key: config name
1312 value: config value
1313 """
1314 if config_plus:
Simon Glass37edf5f2023-07-19 17:49:06 -06001315 lines.append(_calc_config('+', name, config_plus))
Simon Glass843312d2015-02-05 22:06:15 -07001316 if config_minus:
Simon Glass37edf5f2023-07-19 17:49:06 -06001317 lines.append(_calc_config('-', name, config_minus))
Simon Glass843312d2015-02-05 22:06:15 -07001318 if config_change:
Simon Glass37edf5f2023-07-19 17:49:06 -06001319 lines.append(_calc_config('c', name, config_change))
Simon Glass8270e3c2015-08-25 21:52:14 -06001320
Simon Glass37edf5f2023-07-19 17:49:06 -06001321 def _output_config_info(lines):
Simon Glass8270e3c2015-08-25 21:52:14 -06001322 for line in lines:
1323 if not line:
1324 continue
1325 if line[0] == '+':
1326 col = self.col.GREEN
1327 elif line[0] == '-':
1328 col = self.col.RED
1329 elif line[0] == 'c':
1330 col = self.col.YELLOW
Simon Glass098b10f2022-01-29 14:14:18 -07001331 tprint(' ' + line, newline=True, colour=col)
Simon Glass8270e3c2015-08-25 21:52:14 -06001332
Simon Glass37edf5f2023-07-19 17:49:06 -06001333 def _output_err_lines(err_lines, colour):
Simon Glassb206d872020-04-09 15:08:28 -06001334 """Output the line of error/warning lines, if not empty
1335
1336 Also increments self._error_lines if err_lines not empty
1337
1338 Args:
Simon Glass35d696d2020-04-09 15:08:36 -06001339 err_lines: List of ErrLine objects, each an error or warning
1340 line, possibly including a list of boards with that
1341 error/warning
Simon Glassb206d872020-04-09 15:08:28 -06001342 colour: Colour to use for output
1343 """
1344 if err_lines:
Simon Glass8c9a2672020-04-09 15:08:37 -06001345 out_list = []
Simon Glass35d696d2020-04-09 15:08:36 -06001346 for line in err_lines:
Simon Glasscc2c0d12022-07-11 19:04:00 -06001347 names = [brd.target for brd in line.brds]
Simon Glass9ef0ceb2020-04-09 15:08:38 -06001348 board_str = ' '.join(names) if names else ''
Simon Glass8c9a2672020-04-09 15:08:37 -06001349 if board_str:
Simon Glass252ac582022-01-29 14:14:17 -07001350 out = self.col.build(colour, line.char + '(')
1351 out += self.col.build(self.col.MAGENTA, board_str,
Simon Glass8c9a2672020-04-09 15:08:37 -06001352 bright=False)
Simon Glass252ac582022-01-29 14:14:17 -07001353 out += self.col.build(colour, ') %s' % line.errline)
Simon Glass8c9a2672020-04-09 15:08:37 -06001354 else:
Simon Glass252ac582022-01-29 14:14:17 -07001355 out = self.col.build(colour, line.char + line.errline)
Simon Glass8c9a2672020-04-09 15:08:37 -06001356 out_list.append(out)
Simon Glass098b10f2022-01-29 14:14:18 -07001357 tprint('\n'.join(out_list))
Simon Glassb206d872020-04-09 15:08:28 -06001358 self._error_lines += 1
1359
Simon Glass843312d2015-02-05 22:06:15 -07001360
Simon Glass4cf2b222018-11-06 16:02:12 -07001361 ok_boards = [] # List of boards fixed since last commit
Simon Glass6af71012018-11-06 16:02:13 -07001362 warn_boards = [] # List of boards with warnings since last commit
Simon Glass4cf2b222018-11-06 16:02:12 -07001363 err_boards = [] # List of new broken boards since last commit
1364 new_boards = [] # List of boards that didn't exist last time
1365 unknown_boards = [] # List of boards that were not built
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001366
1367 for target in board_dict:
1368 if target not in board_selected:
1369 continue
1370
1371 # If the board was built last time, add its outcome to a list
1372 if target in self._base_board_dict:
1373 base_outcome = self._base_board_dict[target].rc
1374 outcome = board_dict[target]
1375 if outcome.rc == OUTCOME_UNKNOWN:
Simon Glass4cf2b222018-11-06 16:02:12 -07001376 unknown_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001377 elif outcome.rc < base_outcome:
Simon Glass6af71012018-11-06 16:02:13 -07001378 if outcome.rc == OUTCOME_WARNING:
1379 warn_boards.append(target)
1380 else:
1381 ok_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001382 elif outcome.rc > base_outcome:
Simon Glass6af71012018-11-06 16:02:13 -07001383 if outcome.rc == OUTCOME_WARNING:
1384 warn_boards.append(target)
1385 else:
1386 err_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001387 else:
Simon Glass4cf2b222018-11-06 16:02:12 -07001388 new_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001389
Simon Glassb206d872020-04-09 15:08:28 -06001390 # Get a list of errors and warnings that have appeared, and disappeared
Simon Glass37edf5f2023-07-19 17:49:06 -06001391 better_err, worse_err = _calc_error_delta(self._base_err_lines,
Simon Glasse30965d2014-08-28 09:43:44 -06001392 self._base_err_line_boards, err_lines, err_line_boards, '')
Simon Glass37edf5f2023-07-19 17:49:06 -06001393 better_warn, worse_warn = _calc_error_delta(self._base_warn_lines,
Simon Glasse30965d2014-08-28 09:43:44 -06001394 self._base_warn_line_boards, warn_lines, warn_line_boards, 'w')
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001395
Simon Glassae1a09f2022-07-11 19:03:56 -06001396 # For the IDE mode, print out all the output
1397 if self._ide:
1398 outcome = board_dict[target]
1399 for line in outcome.err_lines:
1400 sys.stderr.write(line)
1401
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001402 # Display results by arch
Simon Glassae1a09f2022-07-11 19:03:56 -06001403 elif any((ok_boards, warn_boards, err_boards, unknown_boards, new_boards,
Simon Glass6af71012018-11-06 16:02:13 -07001404 worse_err, better_err, worse_warn, better_warn)):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001405 arch_list = {}
Simon Glass37edf5f2023-07-19 17:49:06 -06001406 self.add_outcome(board_selected, arch_list, ok_boards, '',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001407 self.col.GREEN)
Simon Glass37edf5f2023-07-19 17:49:06 -06001408 self.add_outcome(board_selected, arch_list, warn_boards, 'w+',
Simon Glass6af71012018-11-06 16:02:13 -07001409 self.col.YELLOW)
Simon Glass37edf5f2023-07-19 17:49:06 -06001410 self.add_outcome(board_selected, arch_list, err_boards, '+',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001411 self.col.RED)
Simon Glass37edf5f2023-07-19 17:49:06 -06001412 self.add_outcome(board_selected, arch_list, new_boards, '*', self.col.BLUE)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001413 if self._show_unknown:
Simon Glass37edf5f2023-07-19 17:49:06 -06001414 self.add_outcome(board_selected, arch_list, unknown_boards, '?',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001415 self.col.MAGENTA)
Simon Glassc05aa032019-10-31 07:42:53 -06001416 for arch, target_list in arch_list.items():
Simon Glass098b10f2022-01-29 14:14:18 -07001417 tprint('%10s: %s' % (arch, target_list))
Simon Glass28370c12014-08-09 15:33:06 -06001418 self._error_lines += 1
Simon Glass37edf5f2023-07-19 17:49:06 -06001419 _output_err_lines(better_err, colour=self.col.GREEN)
1420 _output_err_lines(worse_err, colour=self.col.RED)
1421 _output_err_lines(better_warn, colour=self.col.CYAN)
1422 _output_err_lines(worse_warn, colour=self.col.YELLOW)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001423
1424 if show_sizes:
Simon Glass37edf5f2023-07-19 17:49:06 -06001425 self.print_size_summary(board_selected, board_dict, show_detail,
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001426 show_bloat)
1427
Alex Kiernan48ae4122018-05-31 04:48:34 +00001428 if show_environment and self._base_environment:
1429 lines = []
1430
1431 for target in board_dict:
1432 if target not in board_selected:
1433 continue
1434
1435 tbase = self._base_environment[target]
1436 tenvironment = environment[target]
1437 environment_plus = {}
1438 environment_minus = {}
1439 environment_change = {}
1440 base = tbase.environment
Simon Glassc05aa032019-10-31 07:42:53 -06001441 for key, value in tenvironment.environment.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001442 if key not in base:
1443 environment_plus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001444 for key, value in base.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001445 if key not in tenvironment.environment:
1446 environment_minus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001447 for key, value in base.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001448 new_value = tenvironment.environment.get(key)
1449 if new_value and value != new_value:
1450 desc = '%s -> %s' % (value, new_value)
1451 environment_change[key] = desc
1452
Simon Glass37edf5f2023-07-19 17:49:06 -06001453 _add_config(lines, target, environment_plus, environment_minus,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001454 environment_change)
1455
Simon Glass37edf5f2023-07-19 17:49:06 -06001456 _output_config_info(lines)
Alex Kiernan48ae4122018-05-31 04:48:34 +00001457
Simon Glass8270e3c2015-08-25 21:52:14 -06001458 if show_config and self._base_config:
1459 summary = {}
1460 arch_config_plus = {}
1461 arch_config_minus = {}
1462 arch_config_change = {}
1463 arch_list = []
1464
1465 for target in board_dict:
1466 if target not in board_selected:
Simon Glass843312d2015-02-05 22:06:15 -07001467 continue
Simon Glass8270e3c2015-08-25 21:52:14 -06001468 arch = board_selected[target].arch
1469 if arch not in arch_list:
1470 arch_list.append(arch)
1471
1472 for arch in arch_list:
1473 arch_config_plus[arch] = {}
1474 arch_config_minus[arch] = {}
1475 arch_config_change[arch] = {}
Simon Glassb464f8e2016-11-13 14:25:53 -07001476 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001477 arch_config_plus[arch][name] = {}
1478 arch_config_minus[arch][name] = {}
1479 arch_config_change[arch][name] = {}
1480
1481 for target in board_dict:
1482 if target not in board_selected:
1483 continue
1484
1485 arch = board_selected[target].arch
1486
1487 all_config_plus = {}
1488 all_config_minus = {}
1489 all_config_change = {}
1490 tbase = self._base_config[target]
1491 tconfig = config[target]
1492 lines = []
Simon Glassb464f8e2016-11-13 14:25:53 -07001493 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001494 if not tconfig.config[name]:
1495 continue
1496 config_plus = {}
1497 config_minus = {}
1498 config_change = {}
1499 base = tbase.config[name]
Simon Glassc05aa032019-10-31 07:42:53 -06001500 for key, value in tconfig.config[name].items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001501 if key not in base:
1502 config_plus[key] = value
1503 all_config_plus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001504 for key, value in base.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001505 if key not in tconfig.config[name]:
1506 config_minus[key] = value
1507 all_config_minus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001508 for key, value in base.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001509 new_value = tconfig.config.get(key)
1510 if new_value and value != new_value:
1511 desc = '%s -> %s' % (value, new_value)
1512 config_change[key] = desc
1513 all_config_change[key] = desc
1514
1515 arch_config_plus[arch][name].update(config_plus)
1516 arch_config_minus[arch][name].update(config_minus)
1517 arch_config_change[arch][name].update(config_change)
1518
Simon Glass37edf5f2023-07-19 17:49:06 -06001519 _add_config(lines, name, config_plus, config_minus,
Simon Glass8270e3c2015-08-25 21:52:14 -06001520 config_change)
Simon Glass37edf5f2023-07-19 17:49:06 -06001521 _add_config(lines, 'all', all_config_plus, all_config_minus,
Simon Glass8270e3c2015-08-25 21:52:14 -06001522 all_config_change)
1523 summary[target] = '\n'.join(lines)
1524
1525 lines_by_target = {}
Simon Glassc05aa032019-10-31 07:42:53 -06001526 for target, lines in summary.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001527 if lines in lines_by_target:
1528 lines_by_target[lines].append(target)
1529 else:
1530 lines_by_target[lines] = [target]
1531
1532 for arch in arch_list:
1533 lines = []
1534 all_plus = {}
1535 all_minus = {}
1536 all_change = {}
Simon Glassb464f8e2016-11-13 14:25:53 -07001537 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001538 all_plus.update(arch_config_plus[arch][name])
1539 all_minus.update(arch_config_minus[arch][name])
1540 all_change.update(arch_config_change[arch][name])
Simon Glass37edf5f2023-07-19 17:49:06 -06001541 _add_config(lines, name, arch_config_plus[arch][name],
Simon Glass8270e3c2015-08-25 21:52:14 -06001542 arch_config_minus[arch][name],
1543 arch_config_change[arch][name])
Simon Glass37edf5f2023-07-19 17:49:06 -06001544 _add_config(lines, 'all', all_plus, all_minus, all_change)
Simon Glass8270e3c2015-08-25 21:52:14 -06001545 #arch_summary[target] = '\n'.join(lines)
1546 if lines:
Simon Glass098b10f2022-01-29 14:14:18 -07001547 tprint('%s:' % arch)
Simon Glass37edf5f2023-07-19 17:49:06 -06001548 _output_config_info(lines)
Simon Glass8270e3c2015-08-25 21:52:14 -06001549
Simon Glassc05aa032019-10-31 07:42:53 -06001550 for lines, targets in lines_by_target.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001551 if not lines:
1552 continue
Simon Glass098b10f2022-01-29 14:14:18 -07001553 tprint('%s :' % ' '.join(sorted(targets)))
Simon Glass37edf5f2023-07-19 17:49:06 -06001554 _output_config_info(lines.split('\n'))
Simon Glass8270e3c2015-08-25 21:52:14 -06001555
Simon Glass843312d2015-02-05 22:06:15 -07001556
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001557 # Save our updated information for the next call to this function
1558 self._base_board_dict = board_dict
1559 self._base_err_lines = err_lines
Simon Glasse30965d2014-08-28 09:43:44 -06001560 self._base_warn_lines = warn_lines
1561 self._base_err_line_boards = err_line_boards
1562 self._base_warn_line_boards = warn_line_boards
Simon Glass843312d2015-02-05 22:06:15 -07001563 self._base_config = config
Alex Kiernan48ae4122018-05-31 04:48:34 +00001564 self._base_environment = environment
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001565
1566 # Get a list of boards that did not get built, if needed
1567 not_built = []
Simon Glassf4ed4702022-07-11 19:03:57 -06001568 for brd in board_selected:
1569 if not brd in board_dict:
1570 not_built.append(brd)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001571 if not_built:
Simon Glass098b10f2022-01-29 14:14:18 -07001572 tprint("Boards not built (%d): %s" % (len(not_built),
Simon Glass4653a882014-09-05 19:00:07 -06001573 ', '.join(not_built)))
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001574
Simon Glass37edf5f2023-07-19 17:49:06 -06001575 def produce_result_summary(self, commit_upto, commits, board_selected):
Simon Glasse30965d2014-08-28 09:43:44 -06001576 (board_dict, err_lines, err_line_boards, warn_lines,
Simon Glass37edf5f2023-07-19 17:49:06 -06001577 warn_line_boards, config, environment) = self.get_result_summary(
Simon Glassed966652014-08-28 09:43:43 -06001578 board_selected, commit_upto,
Simon Glass843312d2015-02-05 22:06:15 -07001579 read_func_sizes=self._show_bloat,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001580 read_config=self._show_config,
1581 read_environment=self._show_environment)
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001582 if commits:
1583 msg = '%02d: %s' % (commit_upto + 1,
1584 commits[commit_upto].subject)
Simon Glass098b10f2022-01-29 14:14:18 -07001585 tprint(msg, colour=self.col.BLUE)
Simon Glass37edf5f2023-07-19 17:49:06 -06001586 self.print_result_summary(board_selected, board_dict,
Simon Glassed966652014-08-28 09:43:43 -06001587 err_lines if self._show_errors else [], err_line_boards,
Simon Glasse30965d2014-08-28 09:43:44 -06001588 warn_lines if self._show_errors else [], warn_line_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001589 config, environment, self._show_sizes, self._show_detail,
1590 self._show_bloat, self._show_config, self._show_environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001591
Simon Glass37edf5f2023-07-19 17:49:06 -06001592 def show_summary(self, commits, board_selected):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001593 """Show a build summary for U-Boot for a given board list.
1594
1595 Reset the result summary, then repeatedly call GetResultSummary on
1596 each commit's results, then display the differences we see.
1597
1598 Args:
1599 commit: Commit objects to summarise
1600 board_selected: Dict containing boards to summarise
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001601 """
Simon Glassfea58582014-08-09 15:32:59 -06001602 self.commit_count = len(commits) if commits else 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001603 self.commits = commits
Simon Glass37edf5f2023-07-19 17:49:06 -06001604 self.reset_result_summary(board_selected)
Simon Glass28370c12014-08-09 15:33:06 -06001605 self._error_lines = 0
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001606
1607 for commit_upto in range(0, self.commit_count, self._step):
Simon Glass37edf5f2023-07-19 17:49:06 -06001608 self.produce_result_summary(commit_upto, commits, board_selected)
Simon Glass28370c12014-08-09 15:33:06 -06001609 if not self._error_lines:
Simon Glass098b10f2022-01-29 14:14:18 -07001610 tprint('(no errors to report)', colour=self.col.GREEN)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001611
1612
Simon Glass37edf5f2023-07-19 17:49:06 -06001613 def setup_build(self, board_selected, commits):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001614 """Set up ready to start a build.
1615
1616 Args:
1617 board_selected: Selected boards to build
1618 commits: Selected commits to build
1619 """
1620 # First work out how many commits we will build
Simon Glassc05aa032019-10-31 07:42:53 -06001621 count = (self.commit_count + self._step - 1) // self._step
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001622 self.count = len(board_selected) * count
1623 self.upto = self.warned = self.fail = 0
1624 self._timestamps = collections.deque()
1625
Simon Glass37edf5f2023-07-19 17:49:06 -06001626 def get_thread_dir(self, thread_num):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001627 """Get the directory path to the working dir for a thread.
1628
1629 Args:
Simon Glassb82492b2021-01-30 22:17:46 -07001630 thread_num: Number of thread to check (-1 for main process, which
1631 is treated as 0)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001632 """
Simon Glassd829f122020-03-18 09:42:42 -06001633 if self.work_in_output:
1634 return self._working_dir
Simon Glassb82492b2021-01-30 22:17:46 -07001635 return os.path.join(self._working_dir, '%02d' % max(thread_num, 0))
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001636
Simon Glass37edf5f2023-07-19 17:49:06 -06001637 def _prepare_thread(self, thread_num, setup_git):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001638 """Prepare the working directory for a thread.
1639
1640 This clones or fetches the repo into the thread's work directory.
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001641 Optionally, it can create a linked working tree of the repo in the
1642 thread's work directory instead.
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001643
1644 Args:
1645 thread_num: Thread number (0, 1, ...)
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001646 setup_git:
1647 'clone' to set up a git clone
1648 'worktree' to set up a git worktree
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001649 """
Simon Glass37edf5f2023-07-19 17:49:06 -06001650 thread_dir = self.get_thread_dir(thread_num)
Simon Glassf06d3332023-07-19 17:49:08 -06001651 builderthread.mkdir(thread_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001652 git_dir = os.path.join(thread_dir, '.git')
1653
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001654 # Create a worktree or a git repo clone for this thread if it
1655 # doesn't already exist
Simon Glassfea58582014-08-09 15:32:59 -06001656 if setup_git and self.git_dir:
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001657 src_dir = os.path.abspath(self.git_dir)
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001658 if os.path.isdir(git_dir):
1659 # This is a clone of the src_dir repo, we can keep using
1660 # it but need to fetch from src_dir.
Simon Glass098b10f2022-01-29 14:14:18 -07001661 tprint('\rFetching repo for thread %d' % thread_num,
Simon Glass212c0b82020-04-09 15:08:43 -06001662 newline=False)
Simon Glass0157b182022-01-29 14:14:11 -07001663 gitutil.fetch(git_dir, thread_dir)
Simon Glass098b10f2022-01-29 14:14:18 -07001664 terminal.print_clear()
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001665 elif os.path.isfile(git_dir):
1666 # This is a worktree of the src_dir repo, we don't need to
1667 # create it again or update it in any way.
1668 pass
1669 elif os.path.exists(git_dir):
1670 # Don't know what could trigger this, but we probably
1671 # can't create a git worktree/clone here.
1672 raise ValueError('Git dir %s exists, but is not a file '
1673 'or a directory.' % git_dir)
1674 elif setup_git == 'worktree':
Simon Glass098b10f2022-01-29 14:14:18 -07001675 tprint('\rChecking out worktree for thread %d' % thread_num,
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001676 newline=False)
Simon Glass0157b182022-01-29 14:14:11 -07001677 gitutil.add_worktree(src_dir, thread_dir)
Simon Glass098b10f2022-01-29 14:14:18 -07001678 terminal.print_clear()
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001679 elif setup_git == 'clone' or setup_git == True:
Simon Glass098b10f2022-01-29 14:14:18 -07001680 tprint('\rCloning repo for thread %d' % thread_num,
Simon Glass21f0eb32016-09-18 16:48:31 -06001681 newline=False)
Simon Glass0157b182022-01-29 14:14:11 -07001682 gitutil.clone(src_dir, thread_dir)
Simon Glass098b10f2022-01-29 14:14:18 -07001683 terminal.print_clear()
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001684 else:
1685 raise ValueError("Can't setup git repo with %s." % setup_git)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001686
Simon Glass37edf5f2023-07-19 17:49:06 -06001687 def _prepare_working_space(self, max_threads, setup_git):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001688 """Prepare the working directory for use.
1689
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001690 Set up the git repo for each thread. Creates a linked working tree
1691 if git-worktree is available, or clones the repo if it isn't.
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001692
1693 Args:
Simon Glassb82492b2021-01-30 22:17:46 -07001694 max_threads: Maximum number of threads we expect to need. If 0 then
1695 1 is set up, since the main process still needs somewhere to
1696 work
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001697 setup_git: True to set up a git worktree or a git clone
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001698 """
Simon Glassf06d3332023-07-19 17:49:08 -06001699 builderthread.mkdir(self._working_dir)
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001700 if setup_git and self.git_dir:
1701 src_dir = os.path.abspath(self.git_dir)
Simon Glass0157b182022-01-29 14:14:11 -07001702 if gitutil.check_worktree_is_available(src_dir):
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001703 setup_git = 'worktree'
1704 # If we previously added a worktree but the directory for it
1705 # got deleted, we need to prune its files from the repo so
1706 # that we can check out another in its place.
Simon Glass0157b182022-01-29 14:14:11 -07001707 gitutil.prune_worktrees(src_dir)
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +03001708 else:
1709 setup_git = 'clone'
Simon Glassb82492b2021-01-30 22:17:46 -07001710
1711 # Always do at least one thread
1712 for thread in range(max(max_threads, 1)):
Simon Glass37edf5f2023-07-19 17:49:06 -06001713 self._prepare_thread(thread, setup_git)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001714
Simon Glass37edf5f2023-07-19 17:49:06 -06001715 def _get_output_space_removals(self):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001716 """Get the output directories ready to receive files.
1717
Simon Glass925f6ad2020-03-18 09:42:45 -06001718 Figure out what needs to be deleted in the output directory before it
1719 can be used. We only delete old buildman directories which have the
Simon Glass4a7419b2023-07-19 17:49:10 -06001720 expected name pattern. See get_output_dir().
Simon Glass925f6ad2020-03-18 09:42:45 -06001721
1722 Returns:
1723 List of full paths of directories to remove
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001724 """
Simon Glass1a915672014-12-01 17:33:53 -07001725 if not self.commits:
1726 return
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001727 dir_list = []
1728 for commit_upto in range(self.commit_count):
Simon Glass4a7419b2023-07-19 17:49:10 -06001729 dir_list.append(self.get_output_dir(commit_upto))
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001730
Simon Glassb222abe2016-09-18 16:48:32 -06001731 to_remove = []
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001732 for dirname in glob.glob(os.path.join(self.base_dir, '*')):
1733 if dirname not in dir_list:
Simon Glass925f6ad2020-03-18 09:42:45 -06001734 leaf = dirname[len(self.base_dir) + 1:]
Ovidiu Panait7664b032020-05-15 09:30:12 +03001735 m = re.match('[0-9]+_g[0-9a-f]+_.*', leaf)
Simon Glass925f6ad2020-03-18 09:42:45 -06001736 if m:
1737 to_remove.append(dirname)
1738 return to_remove
1739
Simon Glass37edf5f2023-07-19 17:49:06 -06001740 def _prepare_output_space(self):
Simon Glass925f6ad2020-03-18 09:42:45 -06001741 """Get the output directories ready to receive files.
1742
1743 We delete any output directories which look like ones we need to
1744 create. Having left over directories is confusing when the user wants
1745 to check the output manually.
1746 """
Simon Glass37edf5f2023-07-19 17:49:06 -06001747 to_remove = self._get_output_space_removals()
Simon Glassb222abe2016-09-18 16:48:32 -06001748 if to_remove:
Simon Glass098b10f2022-01-29 14:14:18 -07001749 tprint('Removing %d old build directories...' % len(to_remove),
Simon Glassb222abe2016-09-18 16:48:32 -06001750 newline=False)
1751 for dirname in to_remove:
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001752 shutil.rmtree(dirname)
Simon Glass098b10f2022-01-29 14:14:18 -07001753 terminal.print_clear()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001754
Simon Glass37edf5f2023-07-19 17:49:06 -06001755 def build_boards(self, commits, board_selected, keep_outputs, verbose):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001756 """Build all commits for a list of boards
1757
1758 Args:
1759 commits: List of commits to be build, each a Commit object
1760 boards_selected: Dict of selected boards, key is target name,
1761 value is Board object
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001762 keep_outputs: True to save build output files
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001763 verbose: Display build results as they are completed
Simon Glass2c3deb92014-08-28 09:43:39 -06001764 Returns:
1765 Tuple containing:
1766 - number of boards that failed to build
1767 - number of boards that issued warnings
Simon Glass8116c782021-04-11 16:27:27 +12001768 - list of thread exceptions raised
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001769 """
Simon Glassfea58582014-08-09 15:32:59 -06001770 self.commit_count = len(commits) if commits else 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001771 self.commits = commits
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001772 self._verbose = verbose
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001773
Simon Glass37edf5f2023-07-19 17:49:06 -06001774 self.reset_result_summary(board_selected)
Simon Glassf06d3332023-07-19 17:49:08 -06001775 builderthread.mkdir(self.base_dir, parents = True)
Simon Glass37edf5f2023-07-19 17:49:06 -06001776 self._prepare_working_space(min(self.num_threads, len(board_selected)),
Simon Glassfea58582014-08-09 15:32:59 -06001777 commits is not None)
Simon Glass37edf5f2023-07-19 17:49:06 -06001778 self._prepare_output_space()
Simon Glassae1a09f2022-07-11 19:03:56 -06001779 if not self._ide:
1780 tprint('\rStarting build...', newline=False)
Simon Glass2ce06f52023-09-07 10:00:19 -06001781 self._start_time = datetime.now()
Simon Glass37edf5f2023-07-19 17:49:06 -06001782 self.setup_build(board_selected, commits)
1783 self.process_result(None)
Simon Glass8116c782021-04-11 16:27:27 +12001784 self.thread_exceptions = []
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001785 # Create jobs to build all commits for each board
Simon Glassc05aa032019-10-31 07:42:53 -06001786 for brd in board_selected.values():
Simon Glass190064b2014-08-09 15:33:00 -06001787 job = builderthread.BuilderJob()
Simon Glassf4ed4702022-07-11 19:03:57 -06001788 job.brd = brd
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001789 job.commits = commits
1790 job.keep_outputs = keep_outputs
Simon Glassd829f122020-03-18 09:42:42 -06001791 job.work_in_output = self.work_in_output
Simon Glass2b4806e2022-01-22 05:07:33 -07001792 job.adjust_cfg = self.adjust_cfg
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001793 job.step = self._step
Simon Glassb82492b2021-01-30 22:17:46 -07001794 if self.num_threads:
1795 self.queue.put(job)
1796 else:
Simon Glassf06d3332023-07-19 17:49:08 -06001797 self._single_builder.run_job(job)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001798
Simon Glassb82492b2021-01-30 22:17:46 -07001799 if self.num_threads:
1800 term = threading.Thread(target=self.queue.join)
1801 term.setDaemon(True)
1802 term.start()
1803 while term.is_alive():
1804 term.join(100)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001805
Simon Glassb82492b2021-01-30 22:17:46 -07001806 # Wait until we have processed all output
1807 self.out_queue.join()
Simon Glassae1a09f2022-07-11 19:03:56 -06001808 if not self._ide:
1809 tprint()
Simon Glass7b33f212020-04-09 15:08:47 -06001810
Simon Glassae1a09f2022-07-11 19:03:56 -06001811 msg = 'Completed: %d total built' % self.count
1812 if self.already_done:
1813 msg += ' (%d previously' % self.already_done
1814 if self.already_done != self.count:
1815 msg += ', %d newly' % (self.count - self.already_done)
1816 msg += ')'
1817 duration = datetime.now() - self._start_time
1818 if duration > timedelta(microseconds=1000000):
1819 if duration.microseconds >= 500000:
1820 duration = duration + timedelta(seconds=1)
1821 duration = duration - timedelta(microseconds=duration.microseconds)
1822 rate = float(self.count) / duration.total_seconds()
1823 msg += ', duration %s, rate %1.2f' % (duration, rate)
1824 tprint(msg)
1825 if self.thread_exceptions:
1826 tprint('Failed: %d thread exceptions' % len(self.thread_exceptions),
1827 colour=self.col.RED)
Simon Glass7b33f212020-04-09 15:08:47 -06001828
Simon Glass8116c782021-04-11 16:27:27 +12001829 return (self.fail, self.warned, self.thread_exceptions)