blob: cbf1345281bfba64cf489b287331d708b08301b4 [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glassc05694f2013-04-03 11:07:16 +00002# Copyright (c) 2013 The Chromium OS Authors.
3#
4# Bloat-o-meter code used here Copyright 2004 Matt Mackall <mpm@selenic.com>
5#
Simon Glassc05694f2013-04-03 11:07:16 +00006
7import collections
Simon Glassc05694f2013-04-03 11:07:16 +00008from datetime import datetime, timedelta
9import glob
10import os
11import re
Simon Glassc78ed662019-10-31 07:42:53 -060012import queue
Simon Glassc05694f2013-04-03 11:07:16 +000013import shutil
Simon Glass205ac042016-09-18 16:48:37 -060014import signal
Simon Glassc05694f2013-04-03 11:07:16 +000015import string
16import sys
Simon Glassd26e1442016-09-18 16:48:35 -060017import threading
Simon Glassc05694f2013-04-03 11:07:16 +000018import time
19
Simon Glassf0d9c102020-04-17 18:09:02 -060020from buildman import builderthread
21from buildman import toolchain
Simon Glassa997ea52020-04-17 18:09:04 -060022from patman import gitutil
Simon Glass131444f2023-02-23 18:18:04 -070023from u_boot_pylib import command
24from u_boot_pylib import terminal
Simon Glass600ede92024-08-15 13:57:45 -060025from u_boot_pylib import tools
Simon Glass131444f2023-02-23 18:18:04 -070026from u_boot_pylib.terminal import tprint
Simon Glassc05694f2013-04-03 11:07:16 +000027
Simon Glass146b6022021-10-19 21:43:24 -060028# This indicates an new int or hex Kconfig property with no default
29# It hangs the build since the 'conf' tool cannot proceed without valid input.
30#
31# We get a repeat sequence of something like this:
32# >>
33# Break things (BREAK_ME) [] (NEW)
34# Error in reading or end of file.
35# <<
36# which indicates that BREAK_ME has an empty default
Simon Glass1f3a1222024-09-21 19:57:57 +020037RE_NO_DEFAULT = re.compile(br'\((\w+)\) \[] \(NEW\)')
Simon Glass146b6022021-10-19 21:43:24 -060038
Simon Glassdac73712023-10-23 00:52:43 -070039# Symbol types which appear in the bloat feature (-B). Others are silently
40# dropped when reading in the 'nm' output
41NM_SYMBOL_TYPES = 'tTdDbBr'
42
Simon Glassc05694f2013-04-03 11:07:16 +000043"""
44Theory of Operation
45
46Please see README for user documentation, and you should be familiar with
47that before trying to make sense of this.
48
49Buildman works by keeping the machine as busy as possible, building different
50commits for different boards on multiple CPUs at once.
51
52The source repo (self.git_dir) contains all the commits to be built. Each
53thread works on a single board at a time. It checks out the first commit,
54configures it for that board, then builds it. Then it checks out the next
55commit and builds it (typically without re-configuring). When it runs out
56of commits, it gets another job from the builder and starts again with that
57board.
58
59Clearly the builder threads could work either way - they could check out a
60commit and then built it for all boards. Using separate directories for each
61commit/board pair they could leave their build product around afterwards
62also.
63
64The intent behind building a single board for multiple commits, is to make
65use of incremental builds. Since each commit is built incrementally from
66the previous one, builds are faster. Reconfiguring for a different board
67removes all intermediate object files.
68
69Many threads can be working at once, but each has its own working directory.
70When a thread finishes a build, it puts the output files into a result
71directory.
72
73The base directory used by buildman is normally '../<branch>', i.e.
74a directory higher than the source repository and named after the branch
75being built.
76
77Within the base directory, we have one subdirectory for each commit. Within
78that is one subdirectory for each board. Within that is the build output for
79that commit/board combination.
80
81Buildman also create working directories for each thread, in a .bm-work/
82subdirectory in the base dir.
83
84As an example, say we are building branch 'us-net' for boards 'sandbox' and
85'seaboard', and say that us-net has two commits. We will have directories
86like this:
87
88us-net/ base directory
Ovidiu Panaitee8e9cb2020-05-15 09:30:12 +030089 01_g4ed4ebc_net--Add-tftp-speed-/
Simon Glassc05694f2013-04-03 11:07:16 +000090 sandbox/
91 u-boot.bin
92 seaboard/
93 u-boot.bin
Ovidiu Panaitee8e9cb2020-05-15 09:30:12 +030094 02_g4ed4ebc_net--Check-tftp-comp/
Simon Glassc05694f2013-04-03 11:07:16 +000095 sandbox/
96 u-boot.bin
97 seaboard/
98 u-boot.bin
99 .bm-work/
100 00/ working directory for thread 0 (contains source checkout)
101 build/ build output
102 01/ working directory for thread 1
103 build/ build output
104 ...
105u-boot/ source directory
106 .git/ repository
107"""
108
Simon Glassde0fefc2020-04-09 15:08:36 -0600109"""Holds information about a particular error line we are outputing
110
111 char: Character representation: '+': error, '-': fixed error, 'w+': warning,
112 'w-' = fixed warning
113 boards: List of Board objects which have line in the error/warning output
114 errline: The text of the error line
115"""
Simon Glass5df45222022-07-11 19:04:00 -0600116ErrLine = collections.namedtuple('ErrLine', 'char,brds,errline')
Simon Glassde0fefc2020-04-09 15:08:36 -0600117
Simon Glassc05694f2013-04-03 11:07:16 +0000118# Possible build outcomes
Simon Glassc78ed662019-10-31 07:42:53 -0600119OUTCOME_OK, OUTCOME_WARNING, OUTCOME_ERROR, OUTCOME_UNKNOWN = list(range(4))
Simon Glassc05694f2013-04-03 11:07:16 +0000120
Simon Glassd214bef2017-04-12 18:23:26 -0600121# Translate a commit subject into a valid filename (and handle unicode)
Simon Glassc78ed662019-10-31 07:42:53 -0600122trans_valid_chars = str.maketrans('/: ', '---')
Simon Glassc05694f2013-04-03 11:07:16 +0000123
Simon Glasscde5c302016-11-13 14:25:53 -0700124BASE_CONFIG_FILENAMES = [
125 'u-boot.cfg', 'u-boot-spl.cfg', 'u-boot-tpl.cfg'
126]
127
128EXTRA_CONFIG_FILENAMES = [
Simon Glassdb17fb82015-02-05 22:06:15 -0700129 '.config', '.config-spl', '.config-tpl',
130 'autoconf.mk', 'autoconf-spl.mk', 'autoconf-tpl.mk',
131 'autoconf.h', 'autoconf-spl.h','autoconf-tpl.h',
Simon Glassdb17fb82015-02-05 22:06:15 -0700132]
133
Simon Glasscad8abf2015-08-25 21:52:14 -0600134class Config:
135 """Holds information about configuration settings for a board."""
Simon Glasscde5c302016-11-13 14:25:53 -0700136 def __init__(self, config_filename, target):
Simon Glasscad8abf2015-08-25 21:52:14 -0600137 self.target = target
138 self.config = {}
Simon Glasscde5c302016-11-13 14:25:53 -0700139 for fname in config_filename:
Simon Glasscad8abf2015-08-25 21:52:14 -0600140 self.config[fname] = {}
141
Simon Glassbc74d942023-07-19 17:49:06 -0600142 def add(self, fname, key, value):
Simon Glasscad8abf2015-08-25 21:52:14 -0600143 self.config[fname][key] = value
144
145 def __hash__(self):
146 val = 0
147 for fname in self.config:
Simon Glassc78ed662019-10-31 07:42:53 -0600148 for key, value in self.config[fname].items():
149 print(key, value)
Simon Glasscad8abf2015-08-25 21:52:14 -0600150 val = val ^ hash(key) & hash(value)
151 return val
Simon Glassc05694f2013-04-03 11:07:16 +0000152
Alex Kiernan4059e302018-05-31 04:48:34 +0000153class Environment:
154 """Holds information about environment variables for a board."""
155 def __init__(self, target):
156 self.target = target
157 self.environment = {}
158
Simon Glassbc74d942023-07-19 17:49:06 -0600159 def add(self, key, value):
Alex Kiernan4059e302018-05-31 04:48:34 +0000160 self.environment[key] = value
161
Simon Glassc05694f2013-04-03 11:07:16 +0000162class Builder:
163 """Class for building U-Boot for a particular commit.
164
165 Public members: (many should ->private)
Simon Glassc05694f2013-04-03 11:07:16 +0000166 already_done: Number of builds already completed
167 base_dir: Base directory to use for builder
168 checkout: True to check out source, False to skip that step.
169 This is used for testing.
170 col: terminal.Color() object
Simon Glassf56cc292023-07-19 17:49:03 -0600171 count: Total number of commits to build, which is the number of commits
172 multiplied by the number of boards
Simon Glassc05694f2013-04-03 11:07:16 +0000173 do_make: Method to call to invoke Make
174 fail: Number of builds that failed due to error
175 force_build: Force building even if a build already exists
176 force_config_on_failure: If a commit fails for a board, disable
177 incremental building for the next commit we build for that
178 board, so that we will see all warnings/errors again.
Simon Glass7041c392014-07-13 12:22:31 -0600179 force_build_failures: If a previously-built build (i.e. built on
180 a previous run of buildman) is marked as failed, rebuild it.
Simon Glassc05694f2013-04-03 11:07:16 +0000181 git_dir: Git directory containing source repository
Simon Glassc05694f2013-04-03 11:07:16 +0000182 num_jobs: Number of jobs to run at once (passed to make as -j)
183 num_threads: Number of builder threads to run
184 out_queue: Queue of results to process
185 re_make_err: Compiled regular expression for ignore_lines
186 queue: Queue of jobs to run
187 threads: List of active threads
188 toolchains: Toolchains object to use for building
189 upto: Current commit number we are building (0.count-1)
190 warned: Number of builds that produced at least one warning
Simon Glassf3018b7a2014-07-14 17:51:02 -0600191 force_reconfig: Reconfigure U-Boot on each comiit. This disables
192 incremental building, where buildman reconfigures on the first
193 commit for a baord, and then just does an incremental build for
194 the following commits. In fact buildman will reconfigure and
195 retry for any failing commits, so generally the only effect of
196 this option is to slow things down.
Simon Glass38df2e22014-07-14 17:51:03 -0600197 in_tree: Build U-Boot in-tree instead of specifying an output
198 directory separate from the source code. This option is really
199 only useful for testing in-tree builds.
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600200 work_in_output: Use the output directory as the work directory and
201 don't write to a separate output directory.
Simon Glass9bf9a722021-04-11 16:27:27 +1200202 thread_exceptions: List of exceptions raised by thread jobs
Simon Glassf6bfcca2023-02-21 12:40:28 -0700203 no_lto (bool): True to set the NO_LTO flag when building
Simon Glass828d70d2023-02-21 12:40:29 -0700204 reproducible_builds (bool): True to set SOURCE_DATE_EPOCH=0 for builds
Simon Glassc05694f2013-04-03 11:07:16 +0000205
206 Private members:
207 _base_board_dict: Last-summarised Dict of boards
208 _base_err_lines: Last-summarised list of errors
Simon Glass03749d42014-08-28 09:43:44 -0600209 _base_warn_lines: Last-summarised list of warnings
Simon Glassc05694f2013-04-03 11:07:16 +0000210 _build_period_us: Time taken for a single build (float object).
211 _complete_delay: Expected delay until completion (timedelta)
212 _next_delay_update: Next time we plan to display a progress update
213 (datatime)
214 _show_unknown: Show unknown boards (those not built) in summary
Simon Glass726ae812020-04-09 15:08:47 -0600215 _start_time: Start time for the build
Simon Glassc05694f2013-04-03 11:07:16 +0000216 _timestamps: List of timestamps for the completion of the last
217 last _timestamp_count builds. Each is a datetime object.
218 _timestamp_count: Number of timestamps to keep in our list.
219 _working_dir: Base working directory containing all threads
Simon Glassc635d892021-01-30 22:17:46 -0700220 _single_builder: BuilderThread object for the singer builder, if
221 threading is not being used
Simon Glass146b6022021-10-19 21:43:24 -0600222 _terminated: Thread was terminated due to an error
223 _restarting_config: True if 'Restart config' is detected in output
Simon Glass6c435622022-07-11 19:03:56 -0600224 _ide: Produce output suitable for an Integrated Development Environment,
225 i.e. dont emit progress information and put errors/warnings on stderr
Simon Glassc05694f2013-04-03 11:07:16 +0000226 """
227 class Outcome:
228 """Records a build outcome for a single make invocation
229
230 Public Members:
231 rc: Outcome value (OUTCOME_...)
232 err_lines: List of error lines or [] if none
233 sizes: Dictionary of image size information, keyed by filename
234 - Each value is itself a dictionary containing
235 values for 'text', 'data' and 'bss', being the integer
236 size in bytes of each section.
237 func_sizes: Dictionary keyed by filename - e.g. 'u-boot'. Each
238 value is itself a dictionary:
239 key: function name
240 value: Size of function in bytes
Simon Glassdb17fb82015-02-05 22:06:15 -0700241 config: Dictionary keyed by filename - e.g. '.config'. Each
242 value is itself a dictionary:
243 key: config name
244 value: config value
Alex Kiernan4059e302018-05-31 04:48:34 +0000245 environment: Dictionary keyed by environment variable, Each
246 value is the value of environment variable.
Simon Glassc05694f2013-04-03 11:07:16 +0000247 """
Alex Kiernan4059e302018-05-31 04:48:34 +0000248 def __init__(self, rc, err_lines, sizes, func_sizes, config,
249 environment):
Simon Glassc05694f2013-04-03 11:07:16 +0000250 self.rc = rc
251 self.err_lines = err_lines
252 self.sizes = sizes
253 self.func_sizes = func_sizes
Simon Glassdb17fb82015-02-05 22:06:15 -0700254 self.config = config
Alex Kiernan4059e302018-05-31 04:48:34 +0000255 self.environment = environment
Simon Glassc05694f2013-04-03 11:07:16 +0000256
257 def __init__(self, toolchains, base_dir, git_dir, num_threads, num_jobs,
Simon Glasse87bde12014-12-01 17:33:55 -0700258 gnu_make='make', checkout=True, show_unknown=True, step=1,
Tom Rini3f6f9b22024-07-05 14:34:07 -0600259 no_subdirs=False, full_path=False, verbose_build=False,
Simon Glass222825b2024-06-23 11:55:13 -0600260 mrproper=False, fallback_mrproper=False,
261 per_board_out_dir=False, config_only=False,
262 squash_config_y=False, warnings_as_errors=False,
263 work_in_output=False, test_thread_exceptions=False,
264 adjust_cfg=None, allow_missing=False, no_lto=False,
265 reproducible_builds=False, force_build=False,
266 force_build_failures=False, force_reconfig=False,
Simon Glass600ede92024-08-15 13:57:45 -0600267 in_tree=False, force_config_on_failure=False, make_func=None,
268 dtc_skip=False):
Simon Glassc05694f2013-04-03 11:07:16 +0000269 """Create a new Builder object
270
271 Args:
272 toolchains: Toolchains object to use for building
273 base_dir: Base directory to use for builder
274 git_dir: Git directory containing source repository
275 num_threads: Number of builder threads to run
276 num_jobs: Number of jobs to run at once (passed to make as -j)
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900277 gnu_make: the command name of GNU Make.
Simon Glassc05694f2013-04-03 11:07:16 +0000278 checkout: True to check out source, False to skip that step.
279 This is used for testing.
280 show_unknown: Show unknown boards (those not built) in summary
281 step: 1 to process every commit, n to process every nth commit
Simon Glassd48a46c2014-12-01 17:34:00 -0700282 no_subdirs: Don't create subdirectories when building current
283 source for a single board
Tom Rini3f6f9b22024-07-05 14:34:07 -0600284 full_path: Return the full path in CROSS_COMPILE and don't set
285 PATH
Simon Glass655b6102014-12-01 17:34:07 -0700286 verbose_build: Run build with V=1 and don't use 'make -s'
Simon Glass6029af12020-04-09 15:08:51 -0600287 mrproper: Always run 'make mrproper' when configuring
Simon Glass222825b2024-06-23 11:55:13 -0600288 fallback_mrproper: Run 'make mrproper' and retry on build failure
Stephen Warren97c96902016-04-11 10:48:44 -0600289 per_board_out_dir: Build in a separate persistent directory per
290 board rather than a thread-specific directory
Simon Glass739e8512016-11-13 14:25:51 -0700291 config_only: Only configure each build, don't build it
Simon Glasscde5c302016-11-13 14:25:53 -0700292 squash_config_y: Convert CONFIG options with the value 'y' to '1'
Daniel Schwierzeck20e2ea92018-01-26 16:31:05 +0100293 warnings_as_errors: Treat all compiler warnings as errors
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600294 work_in_output: Use the output directory as the work directory and
295 don't write to a separate output directory.
Simon Glass9bf9a722021-04-11 16:27:27 +1200296 test_thread_exceptions: Uses for tests only, True to make the
297 threads raise an exception instead of reporting their result.
298 This simulates a failure in the code somewhere
Simon Glasse5650a82022-01-22 05:07:33 -0700299 adjust_cfg_list (list of str): List of changes to make to .config
300 file before building. Each is one of (where C is the config
301 option with or without the CONFIG_ prefix)
302
303 C to enable C
304 ~C to disable C
305 C=val to set the value of C (val must have quotes if C is
306 a string Kconfig
Tom Rini93ebd462022-11-09 19:14:53 -0700307 allow_missing: Run build with BINMAN_ALLOW_MISSING=1
Simon Glassf6bfcca2023-02-21 12:40:28 -0700308 no_lto (bool): True to set the NO_LTO flag when building
Simon Glasscf91d312023-07-19 17:48:52 -0600309 force_build (bool): Rebuild even commits that are already built
310 force_build_failures (bool): Rebuild commits that have not been
311 built, or failed to build
312 force_reconfig (bool): Reconfigure on each commit
313 in_tree (bool): Bulid in tree instead of out-of-tree
314 force_config_on_failure (bool): Reconfigure the build before
315 retrying a failed build
316 make_func (function): Function to call to run 'make'
Simon Glass600ede92024-08-15 13:57:45 -0600317 dtc_skip (bool): True to skip building dtc and use the system one
Simon Glassc05694f2013-04-03 11:07:16 +0000318 """
319 self.toolchains = toolchains
320 self.base_dir = base_dir
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600321 if work_in_output:
322 self._working_dir = base_dir
323 else:
324 self._working_dir = os.path.join(base_dir, '.bm-work')
Simon Glassc05694f2013-04-03 11:07:16 +0000325 self.threads = []
Simon Glassbc74d942023-07-19 17:49:06 -0600326 self.do_make = make_func or self.make
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900327 self.gnu_make = gnu_make
Simon Glassc05694f2013-04-03 11:07:16 +0000328 self.checkout = checkout
329 self.num_threads = num_threads
330 self.num_jobs = num_jobs
331 self.already_done = 0
332 self.force_build = False
333 self.git_dir = git_dir
334 self._show_unknown = show_unknown
335 self._timestamp_count = 10
336 self._build_period_us = None
337 self._complete_delay = None
338 self._next_delay_update = datetime.now()
Simon Glass7190a172023-09-07 10:00:19 -0600339 self._start_time = None
Simon Glassc05694f2013-04-03 11:07:16 +0000340 self._step = step
Simon Glassbb4dffb2014-08-09 15:33:06 -0600341 self._error_lines = 0
Simon Glasse87bde12014-12-01 17:33:55 -0700342 self.no_subdirs = no_subdirs
Tom Rini3f6f9b22024-07-05 14:34:07 -0600343 self.full_path = full_path
Simon Glass655b6102014-12-01 17:34:07 -0700344 self.verbose_build = verbose_build
Simon Glass739e8512016-11-13 14:25:51 -0700345 self.config_only = config_only
Simon Glasscde5c302016-11-13 14:25:53 -0700346 self.squash_config_y = squash_config_y
347 self.config_filenames = BASE_CONFIG_FILENAMES
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600348 self.work_in_output = work_in_output
Simon Glasse5650a82022-01-22 05:07:33 -0700349 self.adjust_cfg = adjust_cfg
Tom Rini93ebd462022-11-09 19:14:53 -0700350 self.allow_missing = allow_missing
Simon Glass6c435622022-07-11 19:03:56 -0600351 self._ide = False
Simon Glassf6bfcca2023-02-21 12:40:28 -0700352 self.no_lto = no_lto
Simon Glass828d70d2023-02-21 12:40:29 -0700353 self.reproducible_builds = reproducible_builds
Simon Glasscf91d312023-07-19 17:48:52 -0600354 self.force_build = force_build
355 self.force_build_failures = force_build_failures
356 self.force_reconfig = force_reconfig
357 self.in_tree = in_tree
358 self.force_config_on_failure = force_config_on_failure
Simon Glass222825b2024-06-23 11:55:13 -0600359 self.fallback_mrproper = fallback_mrproper
Simon Glass600ede92024-08-15 13:57:45 -0600360 if dtc_skip:
361 self.dtc = shutil.which('dtc')
362 if not self.dtc:
363 raise ValueError('Cannot find dtc')
364 else:
365 self.dtc = None
Simon Glasse5650a82022-01-22 05:07:33 -0700366
Simon Glasscde5c302016-11-13 14:25:53 -0700367 if not self.squash_config_y:
368 self.config_filenames += EXTRA_CONFIG_FILENAMES
Simon Glass146b6022021-10-19 21:43:24 -0600369 self._terminated = False
370 self._restarting_config = False
Simon Glassc05694f2013-04-03 11:07:16 +0000371
Daniel Schwierzeck20e2ea92018-01-26 16:31:05 +0100372 self.warnings_as_errors = warnings_as_errors
Simon Glassc05694f2013-04-03 11:07:16 +0000373 self.col = terminal.Color()
374
Simon Glass03749d42014-08-28 09:43:44 -0600375 self._re_function = re.compile('(.*): In function.*')
376 self._re_files = re.compile('In file included from.*')
Simon Glass1f3a1222024-09-21 19:57:57 +0200377 self._re_warning = re.compile(r'(.*):(\d*):(\d*): warning: .*')
Simon Glass0db94432018-11-06 16:02:11 -0700378 self._re_dtb_warning = re.compile('(.*): Warning .*')
Simon Glass1f3a1222024-09-21 19:57:57 +0200379 self._re_note = re.compile(r'(.*):(\d*):(\d*): note: this is the location of the previous.*')
Simon Glassf4ebfba2020-04-09 15:08:53 -0600380 self._re_migration_warning = re.compile(r'^={21} WARNING ={22}\n.*\n=+\n',
381 re.MULTILINE | re.DOTALL)
Simon Glass03749d42014-08-28 09:43:44 -0600382
Simon Glass9bf9a722021-04-11 16:27:27 +1200383 self.thread_exceptions = []
384 self.test_thread_exceptions = test_thread_exceptions
Simon Glassc635d892021-01-30 22:17:46 -0700385 if self.num_threads:
386 self._single_builder = None
387 self.queue = queue.Queue()
388 self.out_queue = queue.Queue()
389 for i in range(self.num_threads):
Simon Glass9bf9a722021-04-11 16:27:27 +1200390 t = builderthread.BuilderThread(
391 self, i, mrproper, per_board_out_dir,
392 test_exception=test_thread_exceptions)
Simon Glassc635d892021-01-30 22:17:46 -0700393 t.setDaemon(True)
394 t.start()
395 self.threads.append(t)
396
397 t = builderthread.ResultThread(self)
Simon Glassc05694f2013-04-03 11:07:16 +0000398 t.setDaemon(True)
399 t.start()
400 self.threads.append(t)
Simon Glassc635d892021-01-30 22:17:46 -0700401 else:
402 self._single_builder = builderthread.BuilderThread(
403 self, -1, mrproper, per_board_out_dir)
Simon Glassc05694f2013-04-03 11:07:16 +0000404
405 ignore_lines = ['(make.*Waiting for unfinished)', '(Segmentation fault)']
406 self.re_make_err = re.compile('|'.join(ignore_lines))
407
Simon Glass205ac042016-09-18 16:48:37 -0600408 # Handle existing graceful with SIGINT / Ctrl-C
409 signal.signal(signal.SIGINT, self.signal_handler)
410
Simon Glassc05694f2013-04-03 11:07:16 +0000411 def __del__(self):
412 """Get rid of all threads created by the builder"""
413 for t in self.threads:
414 del t
415
Simon Glass205ac042016-09-18 16:48:37 -0600416 def signal_handler(self, signal, frame):
417 sys.exit(1)
418
Simon Glass600ede92024-08-15 13:57:45 -0600419 def make_environment(self, toolchain):
420 """Create the environment to use for building
421
422 Args:
423 toolchain (Toolchain): Toolchain to use for building
424
425 Returns:
426 dict:
427 key (str): Variable name
428 value (str): Variable value
429 """
430 env = toolchain.MakeEnvironment(self.full_path)
431 if self.dtc:
432 env[b'DTC'] = tools.to_bytes(self.dtc)
433 return env
434
Simon Glassbc74d942023-07-19 17:49:06 -0600435 def set_display_options(self, show_errors=False, show_sizes=False,
Simon Glass3394c9f2014-08-28 09:43:43 -0600436 show_detail=False, show_bloat=False,
Alex Kiernan4059e302018-05-31 04:48:34 +0000437 list_error_boards=False, show_config=False,
Simon Glassf4ebfba2020-04-09 15:08:53 -0600438 show_environment=False, filter_dtb_warnings=False,
Simon Glass6c435622022-07-11 19:03:56 -0600439 filter_migration_warnings=False, ide=False):
Simon Glasseb48bbc2014-08-09 15:33:02 -0600440 """Setup display options for the builder.
441
Simon Glass9ea93812020-04-09 15:08:52 -0600442 Args:
443 show_errors: True to show summarised error/warning info
444 show_sizes: Show size deltas
445 show_detail: Show size delta detail for each board if show_sizes
446 show_bloat: Show detail for each function
447 list_error_boards: Show the boards which caused each error/warning
448 show_config: Show config deltas
449 show_environment: Show environment deltas
450 filter_dtb_warnings: Filter out any warnings from the device-tree
451 compiler
Simon Glassf4ebfba2020-04-09 15:08:53 -0600452 filter_migration_warnings: Filter out any warnings about migrating
453 a board to driver model
Simon Glass6c435622022-07-11 19:03:56 -0600454 ide: Create output that can be parsed by an IDE. There is no '+' prefix on
455 error lines and output on stderr stays on stderr.
Simon Glasseb48bbc2014-08-09 15:33:02 -0600456 """
457 self._show_errors = show_errors
458 self._show_sizes = show_sizes
459 self._show_detail = show_detail
460 self._show_bloat = show_bloat
Simon Glass3394c9f2014-08-28 09:43:43 -0600461 self._list_error_boards = list_error_boards
Simon Glassdb17fb82015-02-05 22:06:15 -0700462 self._show_config = show_config
Alex Kiernan4059e302018-05-31 04:48:34 +0000463 self._show_environment = show_environment
Simon Glass9ea93812020-04-09 15:08:52 -0600464 self._filter_dtb_warnings = filter_dtb_warnings
Simon Glassf4ebfba2020-04-09 15:08:53 -0600465 self._filter_migration_warnings = filter_migration_warnings
Simon Glass6c435622022-07-11 19:03:56 -0600466 self._ide = ide
Simon Glasseb48bbc2014-08-09 15:33:02 -0600467
Simon Glassbc74d942023-07-19 17:49:06 -0600468 def _add_timestamp(self):
Simon Glassc05694f2013-04-03 11:07:16 +0000469 """Add a new timestamp to the list and record the build period.
470
471 The build period is the length of time taken to perform a single
472 build (one board, one commit).
473 """
474 now = datetime.now()
475 self._timestamps.append(now)
476 count = len(self._timestamps)
477 delta = self._timestamps[-1] - self._timestamps[0]
478 seconds = delta.total_seconds()
479
480 # If we have enough data, estimate build period (time taken for a
481 # single build) and therefore completion time.
482 if count > 1 and self._next_delay_update < now:
483 self._next_delay_update = now + timedelta(seconds=2)
484 if seconds > 0:
485 self._build_period = float(seconds) / count
486 todo = self.count - self.upto
487 self._complete_delay = timedelta(microseconds=
488 self._build_period * todo * 1000000)
489 # Round it
490 self._complete_delay -= timedelta(
491 microseconds=self._complete_delay.microseconds)
492
493 if seconds > 60:
494 self._timestamps.popleft()
495 count -= 1
496
Simon Glassbc74d942023-07-19 17:49:06 -0600497 def select_commit(self, commit, checkout=True):
Simon Glassc05694f2013-04-03 11:07:16 +0000498 """Checkout the selected commit for this build
499 """
500 self.commit = commit
501 if checkout and self.checkout:
Simon Glass761648b2022-01-29 14:14:11 -0700502 gitutil.checkout(commit.hash)
Simon Glassc05694f2013-04-03 11:07:16 +0000503
Simon Glassbc74d942023-07-19 17:49:06 -0600504 def make(self, commit, brd, stage, cwd, *args, **kwargs):
Simon Glassc05694f2013-04-03 11:07:16 +0000505 """Run make
506
507 Args:
508 commit: Commit object that is being built
509 brd: Board object that is being built
Simon Glassd6c1ec82023-10-26 14:31:10 -0400510 stage: Stage that we are at (mrproper, config, oldconfig, build)
Simon Glassc05694f2013-04-03 11:07:16 +0000511 cwd: Directory where make should be run
512 args: Arguments to pass to make
Simon Glass840be732022-01-29 14:14:05 -0700513 kwargs: Arguments to pass to command.run_pipe()
Simon Glassc05694f2013-04-03 11:07:16 +0000514 """
Simon Glass146b6022021-10-19 21:43:24 -0600515
516 def check_output(stream, data):
517 if b'Restart config' in data:
518 self._restarting_config = True
519
520 # If we see 'Restart config' following by multiple errors
521 if self._restarting_config:
522 m = RE_NO_DEFAULT.findall(data)
523
524 # Number of occurences of each Kconfig item
525 multiple = [m.count(val) for val in set(m)]
526
527 # If any of them occur more than once, we have a loop
528 if [val for val in multiple if val > 1]:
529 self._terminated = True
530 return True
531 return False
532
533 self._restarting_config = False
534 self._terminated = False
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900535 cmd = [self.gnu_make] + list(args)
Simon Glass840be732022-01-29 14:14:05 -0700536 result = command.run_pipe([cmd], capture=True, capture_stderr=True,
Simon Glass146b6022021-10-19 21:43:24 -0600537 cwd=cwd, raise_on_error=False, infile='/dev/null',
538 output_func=check_output, **kwargs)
539
540 if self._terminated:
541 # Try to be helpful
542 result.stderr += '(** did you define an int/hex Kconfig with no default? **)'
543
Simon Glass413f91a2015-02-05 22:06:12 -0700544 if self.verbose_build:
545 result.stdout = '%s\n' % (' '.join(cmd)) + result.stdout
546 result.combined = '%s\n' % (' '.join(cmd)) + result.combined
Simon Glassc05694f2013-04-03 11:07:16 +0000547 return result
548
Simon Glassbc74d942023-07-19 17:49:06 -0600549 def process_result(self, result):
Simon Glassc05694f2013-04-03 11:07:16 +0000550 """Process the result of a build, showing progress information
551
552 Args:
Simon Glass78e418e2014-08-09 15:33:03 -0600553 result: A CommandResult object, which indicates the result for
554 a single build
Simon Glassc05694f2013-04-03 11:07:16 +0000555 """
556 col = terminal.Color()
557 if result:
558 target = result.brd.target
559
Simon Glassc05694f2013-04-03 11:07:16 +0000560 self.upto += 1
561 if result.return_code != 0:
562 self.fail += 1
563 elif result.stderr:
564 self.warned += 1
565 if result.already_done:
566 self.already_done += 1
Simon Glass78e418e2014-08-09 15:33:03 -0600567 if self._verbose:
Simon Glass02811582022-01-29 14:14:18 -0700568 terminal.print_clear()
Simon Glass78e418e2014-08-09 15:33:03 -0600569 boards_selected = {target : result.brd}
Simon Glassbc74d942023-07-19 17:49:06 -0600570 self.reset_result_summary(boards_selected)
571 self.produce_result_summary(result.commit_upto, self.commits,
Simon Glass78e418e2014-08-09 15:33:03 -0600572 boards_selected)
Simon Glassc05694f2013-04-03 11:07:16 +0000573 else:
574 target = '(starting)'
575
576 # Display separate counts for ok, warned and fail
577 ok = self.upto - self.warned - self.fail
Simon Glassf45d3742022-01-29 14:14:17 -0700578 line = '\r' + self.col.build(self.col.GREEN, '%5d' % ok)
579 line += self.col.build(self.col.YELLOW, '%5d' % self.warned)
580 line += self.col.build(self.col.RED, '%5d' % self.fail)
Simon Glassc05694f2013-04-03 11:07:16 +0000581
Simon Glass69c3a8a2020-04-09 15:08:45 -0600582 line += ' /%-5d ' % self.count
583 remaining = self.count - self.upto
584 if remaining:
Simon Glassf45d3742022-01-29 14:14:17 -0700585 line += self.col.build(self.col.MAGENTA, ' -%-5d ' % remaining)
Simon Glass69c3a8a2020-04-09 15:08:45 -0600586 else:
587 line += ' ' * 8
Simon Glassc05694f2013-04-03 11:07:16 +0000588
589 # Add our current completion time estimate
Simon Glassbc74d942023-07-19 17:49:06 -0600590 self._add_timestamp()
Simon Glassc05694f2013-04-03 11:07:16 +0000591 if self._complete_delay:
Simon Glass69c3a8a2020-04-09 15:08:45 -0600592 line += '%s : ' % self._complete_delay
Simon Glassc05694f2013-04-03 11:07:16 +0000593
Simon Glass69c3a8a2020-04-09 15:08:45 -0600594 line += target
Simon Glass6c435622022-07-11 19:03:56 -0600595 if not self._ide:
596 terminal.print_clear()
597 tprint(line, newline=False, limit_to_line=True)
Simon Glassc05694f2013-04-03 11:07:16 +0000598
Simon Glass4cb54682023-07-19 17:49:10 -0600599 def get_output_dir(self, commit_upto):
Simon Glassc05694f2013-04-03 11:07:16 +0000600 """Get the name of the output directory for a commit number
601
602 The output directory is typically .../<branch>/<commit>.
603
604 Args:
605 commit_upto: Commit number to use (0..self.count-1)
606 """
Simon Glasse3c85ab2020-04-17 17:51:34 -0600607 if self.work_in_output:
608 return self._working_dir
609
Simon Glasse87bde12014-12-01 17:33:55 -0700610 commit_dir = None
Simon Glassd326ad72014-08-09 15:32:59 -0600611 if self.commits:
612 commit = self.commits[commit_upto]
613 subject = commit.subject.translate(trans_valid_chars)
Simon Glassbc74d942023-07-19 17:49:06 -0600614 # See _get_output_space_removals() which parses this name
Ovidiu Panaitee8e9cb2020-05-15 09:30:12 +0300615 commit_dir = ('%02d_g%s_%s' % (commit_upto + 1,
616 commit.hash, subject[:20]))
Simon Glasse87bde12014-12-01 17:33:55 -0700617 elif not self.no_subdirs:
Simon Glassd326ad72014-08-09 15:32:59 -0600618 commit_dir = 'current'
Simon Glasse87bde12014-12-01 17:33:55 -0700619 if not commit_dir:
620 return self.base_dir
621 return os.path.join(self.base_dir, commit_dir)
Simon Glassc05694f2013-04-03 11:07:16 +0000622
Simon Glassbc74d942023-07-19 17:49:06 -0600623 def get_build_dir(self, commit_upto, target):
Simon Glassc05694f2013-04-03 11:07:16 +0000624 """Get the name of the build directory for a commit number
625
626 The build directory is typically .../<branch>/<commit>/<target>.
627
628 Args:
629 commit_upto: Commit number to use (0..self.count-1)
630 target: Target name
631 """
Simon Glass4cb54682023-07-19 17:49:10 -0600632 output_dir = self.get_output_dir(commit_upto)
Simon Glasse3c85ab2020-04-17 17:51:34 -0600633 if self.work_in_output:
634 return output_dir
Simon Glassc05694f2013-04-03 11:07:16 +0000635 return os.path.join(output_dir, target)
636
Simon Glassbc74d942023-07-19 17:49:06 -0600637 def get_done_file(self, commit_upto, target):
Simon Glassc05694f2013-04-03 11:07:16 +0000638 """Get the name of the done file for a commit number
639
640 Args:
641 commit_upto: Commit number to use (0..self.count-1)
642 target: Target name
643 """
Simon Glassbc74d942023-07-19 17:49:06 -0600644 return os.path.join(self.get_build_dir(commit_upto, target), 'done')
Simon Glassc05694f2013-04-03 11:07:16 +0000645
Simon Glassbc74d942023-07-19 17:49:06 -0600646 def get_sizes_file(self, commit_upto, target):
Simon Glassc05694f2013-04-03 11:07:16 +0000647 """Get the name of the sizes 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 Glassbc74d942023-07-19 17:49:06 -0600653 return os.path.join(self.get_build_dir(commit_upto, target), 'sizes')
Simon Glassc05694f2013-04-03 11:07:16 +0000654
Simon Glassbc74d942023-07-19 17:49:06 -0600655 def get_func_sizes_file(self, commit_upto, target, elf_fname):
Simon Glassc05694f2013-04-03 11:07:16 +0000656 """Get the name of the funcsizes file for a commit number and ELF file
657
658 Args:
659 commit_upto: Commit number to use (0..self.count-1)
660 target: Target name
661 elf_fname: Filename of elf image
662 """
Simon Glassbc74d942023-07-19 17:49:06 -0600663 return os.path.join(self.get_build_dir(commit_upto, target),
Simon Glassc05694f2013-04-03 11:07:16 +0000664 '%s.sizes' % elf_fname.replace('/', '-'))
665
Simon Glassbc74d942023-07-19 17:49:06 -0600666 def get_objdump_file(self, commit_upto, target, elf_fname):
Simon Glassc05694f2013-04-03 11:07:16 +0000667 """Get the name of the objdump file for a commit number and ELF file
668
669 Args:
670 commit_upto: Commit number to use (0..self.count-1)
671 target: Target name
672 elf_fname: Filename of elf image
673 """
Simon Glassbc74d942023-07-19 17:49:06 -0600674 return os.path.join(self.get_build_dir(commit_upto, target),
Simon Glassc05694f2013-04-03 11:07:16 +0000675 '%s.objdump' % elf_fname.replace('/', '-'))
676
Simon Glassbc74d942023-07-19 17:49:06 -0600677 def get_err_file(self, commit_upto, target):
Simon Glassc05694f2013-04-03 11:07:16 +0000678 """Get the name of the err file for a commit number
679
680 Args:
681 commit_upto: Commit number to use (0..self.count-1)
682 target: Target name
683 """
Simon Glassbc74d942023-07-19 17:49:06 -0600684 output_dir = self.get_build_dir(commit_upto, target)
Simon Glassc05694f2013-04-03 11:07:16 +0000685 return os.path.join(output_dir, 'err')
686
Simon Glassbc74d942023-07-19 17:49:06 -0600687 def filter_errors(self, lines):
Simon Glassc05694f2013-04-03 11:07:16 +0000688 """Filter out errors in which we have no interest
689
690 We should probably use map().
691
692 Args:
693 lines: List of error lines, each a string
694 Returns:
695 New list with only interesting lines included
696 """
697 out_lines = []
Simon Glassf4ebfba2020-04-09 15:08:53 -0600698 if self._filter_migration_warnings:
699 text = '\n'.join(lines)
700 text = self._re_migration_warning.sub('', text)
701 lines = text.splitlines()
Simon Glassc05694f2013-04-03 11:07:16 +0000702 for line in lines:
Simon Glass9ea93812020-04-09 15:08:52 -0600703 if self.re_make_err.search(line):
704 continue
705 if self._filter_dtb_warnings and self._re_dtb_warning.search(line):
706 continue
707 out_lines.append(line)
Simon Glassc05694f2013-04-03 11:07:16 +0000708 return out_lines
709
Simon Glassbc74d942023-07-19 17:49:06 -0600710 def read_func_sizes(self, fname, fd):
Simon Glassc05694f2013-04-03 11:07:16 +0000711 """Read function sizes from the output of 'nm'
712
713 Args:
714 fd: File containing data to read
715 fname: Filename we are reading from (just for errors)
716
717 Returns:
718 Dictionary containing size of each function in bytes, indexed by
719 function name.
720 """
721 sym = {}
722 for line in fd.readlines():
Simon Glass86a2afe2022-07-11 19:04:11 -0600723 line = line.strip()
724 parts = line.split()
725 if line and len(parts) == 3:
726 size, type, name = line.split()
Simon Glassdac73712023-10-23 00:52:43 -0700727 if type in NM_SYMBOL_TYPES:
Simon Glass86a2afe2022-07-11 19:04:11 -0600728 # function names begin with '.' on 64-bit powerpc
729 if '.' in name[1:]:
730 name = 'static.' + name.split('.')[0]
731 sym[name] = sym.get(name, 0) + int(size, 16)
Simon Glassc05694f2013-04-03 11:07:16 +0000732 return sym
733
Simon Glassbc74d942023-07-19 17:49:06 -0600734 def _process_config(self, fname):
Simon Glassdb17fb82015-02-05 22:06:15 -0700735 """Read in a .config, autoconf.mk or autoconf.h file
736
737 This function handles all config file types. It ignores comments and
738 any #defines which don't start with CONFIG_.
739
740 Args:
741 fname: Filename to read
742
743 Returns:
744 Dictionary:
745 key: Config name (e.g. CONFIG_DM)
746 value: Config value (e.g. 1)
747 """
748 config = {}
749 if os.path.exists(fname):
750 with open(fname) as fd:
751 for line in fd:
752 line = line.strip()
753 if line.startswith('#define'):
754 values = line[8:].split(' ', 1)
755 if len(values) > 1:
756 key, value = values
757 else:
758 key = values[0]
Simon Glasscde5c302016-11-13 14:25:53 -0700759 value = '1' if self.squash_config_y else ''
Simon Glassdb17fb82015-02-05 22:06:15 -0700760 if not key.startswith('CONFIG_'):
761 continue
762 elif not line or line[0] in ['#', '*', '/']:
763 continue
764 else:
765 key, value = line.split('=', 1)
Simon Glasscde5c302016-11-13 14:25:53 -0700766 if self.squash_config_y and value == 'y':
767 value = '1'
Simon Glassdb17fb82015-02-05 22:06:15 -0700768 config[key] = value
769 return config
770
Simon Glassbc74d942023-07-19 17:49:06 -0600771 def _process_environment(self, fname):
Alex Kiernan4059e302018-05-31 04:48:34 +0000772 """Read in a uboot.env file
773
774 This function reads in environment variables from a file.
775
776 Args:
777 fname: Filename to read
778
779 Returns:
780 Dictionary:
781 key: environment variable (e.g. bootlimit)
782 value: value of environment variable (e.g. 1)
783 """
784 environment = {}
785 if os.path.exists(fname):
786 with open(fname) as fd:
787 for line in fd.read().split('\0'):
788 try:
789 key, value = line.split('=', 1)
790 environment[key] = value
791 except ValueError:
792 # ignore lines we can't parse
793 pass
794 return environment
795
Simon Glassbc74d942023-07-19 17:49:06 -0600796 def get_build_outcome(self, commit_upto, target, read_func_sizes,
Alex Kiernan4059e302018-05-31 04:48:34 +0000797 read_config, read_environment):
Simon Glassc05694f2013-04-03 11:07:16 +0000798 """Work out the outcome of a build.
799
800 Args:
801 commit_upto: Commit number to check (0..n-1)
802 target: Target board to check
803 read_func_sizes: True to read function size information
Simon Glassdb17fb82015-02-05 22:06:15 -0700804 read_config: True to read .config and autoconf.h files
Alex Kiernan4059e302018-05-31 04:48:34 +0000805 read_environment: True to read uboot.env files
Simon Glassc05694f2013-04-03 11:07:16 +0000806
807 Returns:
808 Outcome object
809 """
Simon Glassbc74d942023-07-19 17:49:06 -0600810 done_file = self.get_done_file(commit_upto, target)
811 sizes_file = self.get_sizes_file(commit_upto, target)
Simon Glassc05694f2013-04-03 11:07:16 +0000812 sizes = {}
813 func_sizes = {}
Simon Glassdb17fb82015-02-05 22:06:15 -0700814 config = {}
Alex Kiernan4059e302018-05-31 04:48:34 +0000815 environment = {}
Simon Glassc05694f2013-04-03 11:07:16 +0000816 if os.path.exists(done_file):
817 with open(done_file, 'r') as fd:
Simon Glass91c54a42019-04-26 19:02:23 -0600818 try:
819 return_code = int(fd.readline())
820 except ValueError:
821 # The file may be empty due to running out of disk space.
822 # Try a rebuild
823 return_code = 1
Simon Glassc05694f2013-04-03 11:07:16 +0000824 err_lines = []
Simon Glassbc74d942023-07-19 17:49:06 -0600825 err_file = self.get_err_file(commit_upto, target)
Simon Glassc05694f2013-04-03 11:07:16 +0000826 if os.path.exists(err_file):
827 with open(err_file, 'r') as fd:
Simon Glassbc74d942023-07-19 17:49:06 -0600828 err_lines = self.filter_errors(fd.readlines())
Simon Glassc05694f2013-04-03 11:07:16 +0000829
830 # Decide whether the build was ok, failed or created warnings
831 if return_code:
832 rc = OUTCOME_ERROR
833 elif len(err_lines):
834 rc = OUTCOME_WARNING
835 else:
836 rc = OUTCOME_OK
837
838 # Convert size information to our simple format
839 if os.path.exists(sizes_file):
840 with open(sizes_file, 'r') as fd:
841 for line in fd.readlines():
842 values = line.split()
843 rodata = 0
844 if len(values) > 6:
845 rodata = int(values[6], 16)
846 size_dict = {
847 'all' : int(values[0]) + int(values[1]) +
848 int(values[2]),
849 'text' : int(values[0]) - rodata,
850 'data' : int(values[1]),
851 'bss' : int(values[2]),
852 'rodata' : rodata,
853 }
854 sizes[values[5]] = size_dict
855
856 if read_func_sizes:
Simon Glassbc74d942023-07-19 17:49:06 -0600857 pattern = self.get_func_sizes_file(commit_upto, target, '*')
Simon Glassc05694f2013-04-03 11:07:16 +0000858 for fname in glob.glob(pattern):
859 with open(fname, 'r') as fd:
860 dict_name = os.path.basename(fname).replace('.sizes',
861 '')
Simon Glassbc74d942023-07-19 17:49:06 -0600862 func_sizes[dict_name] = self.read_func_sizes(fname, fd)
Simon Glassc05694f2013-04-03 11:07:16 +0000863
Simon Glassdb17fb82015-02-05 22:06:15 -0700864 if read_config:
Simon Glassbc74d942023-07-19 17:49:06 -0600865 output_dir = self.get_build_dir(commit_upto, target)
Simon Glasscde5c302016-11-13 14:25:53 -0700866 for name in self.config_filenames:
Simon Glassdb17fb82015-02-05 22:06:15 -0700867 fname = os.path.join(output_dir, name)
Simon Glassbc74d942023-07-19 17:49:06 -0600868 config[name] = self._process_config(fname)
Simon Glassdb17fb82015-02-05 22:06:15 -0700869
Alex Kiernan4059e302018-05-31 04:48:34 +0000870 if read_environment:
Simon Glassbc74d942023-07-19 17:49:06 -0600871 output_dir = self.get_build_dir(commit_upto, target)
Alex Kiernan4059e302018-05-31 04:48:34 +0000872 fname = os.path.join(output_dir, 'uboot.env')
Simon Glassbc74d942023-07-19 17:49:06 -0600873 environment = self._process_environment(fname)
Alex Kiernan4059e302018-05-31 04:48:34 +0000874
875 return Builder.Outcome(rc, err_lines, sizes, func_sizes, config,
876 environment)
Simon Glassc05694f2013-04-03 11:07:16 +0000877
Alex Kiernan4059e302018-05-31 04:48:34 +0000878 return Builder.Outcome(OUTCOME_UNKNOWN, [], {}, {}, {}, {})
Simon Glassc05694f2013-04-03 11:07:16 +0000879
Simon Glassbc74d942023-07-19 17:49:06 -0600880 def get_result_summary(self, boards_selected, commit_upto, read_func_sizes,
Alex Kiernan4059e302018-05-31 04:48:34 +0000881 read_config, read_environment):
Simon Glassc05694f2013-04-03 11:07:16 +0000882 """Calculate a summary of the results of building a commit.
883
884 Args:
885 board_selected: Dict containing boards to summarise
886 commit_upto: Commit number to summarize (0..self.count-1)
887 read_func_sizes: True to read function size information
Simon Glassdb17fb82015-02-05 22:06:15 -0700888 read_config: True to read .config and autoconf.h files
Alex Kiernan4059e302018-05-31 04:48:34 +0000889 read_environment: True to read uboot.env files
Simon Glassc05694f2013-04-03 11:07:16 +0000890
891 Returns:
892 Tuple:
Simon Glass6c435622022-07-11 19:03:56 -0600893 Dict containing boards which built this commit:
894 key: board.target
895 value: Builder.Outcome object
Simon Glass03749d42014-08-28 09:43:44 -0600896 List containing a summary of error lines
Simon Glass3394c9f2014-08-28 09:43:43 -0600897 Dict keyed by error line, containing a list of the Board
898 objects with that error
Simon Glass03749d42014-08-28 09:43:44 -0600899 List containing a summary of warning lines
900 Dict keyed by error line, containing a list of the Board
901 objects with that warning
Simon Glasscad8abf2015-08-25 21:52:14 -0600902 Dictionary keyed by board.target. Each value is a dictionary:
903 key: filename - e.g. '.config'
Simon Glassdb17fb82015-02-05 22:06:15 -0700904 value is itself a dictionary:
905 key: config name
906 value: config value
Alex Kiernan4059e302018-05-31 04:48:34 +0000907 Dictionary keyed by board.target. Each value is a dictionary:
908 key: environment variable
909 value: value of environment variable
Simon Glassc05694f2013-04-03 11:07:16 +0000910 """
Simon Glassbc74d942023-07-19 17:49:06 -0600911 def add_line(lines_summary, lines_boards, line, board):
Simon Glass03749d42014-08-28 09:43:44 -0600912 line = line.rstrip()
913 if line in lines_boards:
914 lines_boards[line].append(board)
915 else:
916 lines_boards[line] = [board]
917 lines_summary.append(line)
918
Simon Glassc05694f2013-04-03 11:07:16 +0000919 board_dict = {}
920 err_lines_summary = []
Simon Glass3394c9f2014-08-28 09:43:43 -0600921 err_lines_boards = {}
Simon Glass03749d42014-08-28 09:43:44 -0600922 warn_lines_summary = []
923 warn_lines_boards = {}
Simon Glassdb17fb82015-02-05 22:06:15 -0700924 config = {}
Alex Kiernan4059e302018-05-31 04:48:34 +0000925 environment = {}
Simon Glassc05694f2013-04-03 11:07:16 +0000926
Simon Glass8132f982022-07-11 19:03:57 -0600927 for brd in boards_selected.values():
Simon Glassbc74d942023-07-19 17:49:06 -0600928 outcome = self.get_build_outcome(commit_upto, brd.target,
Alex Kiernan4059e302018-05-31 04:48:34 +0000929 read_func_sizes, read_config,
930 read_environment)
Simon Glass8132f982022-07-11 19:03:57 -0600931 board_dict[brd.target] = outcome
Simon Glass03749d42014-08-28 09:43:44 -0600932 last_func = None
933 last_was_warning = False
934 for line in outcome.err_lines:
935 if line:
936 if (self._re_function.match(line) or
937 self._re_files.match(line)):
938 last_func = line
Simon Glass3394c9f2014-08-28 09:43:43 -0600939 else:
Simon Glass0db94432018-11-06 16:02:11 -0700940 is_warning = (self._re_warning.match(line) or
941 self._re_dtb_warning.match(line))
Simon Glass03749d42014-08-28 09:43:44 -0600942 is_note = self._re_note.match(line)
943 if is_warning or (last_was_warning and is_note):
944 if last_func:
Simon Glassbc74d942023-07-19 17:49:06 -0600945 add_line(warn_lines_summary, warn_lines_boards,
Simon Glass8132f982022-07-11 19:03:57 -0600946 last_func, brd)
Simon Glassbc74d942023-07-19 17:49:06 -0600947 add_line(warn_lines_summary, warn_lines_boards,
Simon Glass8132f982022-07-11 19:03:57 -0600948 line, brd)
Simon Glass03749d42014-08-28 09:43:44 -0600949 else:
950 if last_func:
Simon Glassbc74d942023-07-19 17:49:06 -0600951 add_line(err_lines_summary, err_lines_boards,
Simon Glass8132f982022-07-11 19:03:57 -0600952 last_func, brd)
Simon Glassbc74d942023-07-19 17:49:06 -0600953 add_line(err_lines_summary, err_lines_boards,
Simon Glass8132f982022-07-11 19:03:57 -0600954 line, brd)
Simon Glass03749d42014-08-28 09:43:44 -0600955 last_was_warning = is_warning
956 last_func = None
Simon Glass8132f982022-07-11 19:03:57 -0600957 tconfig = Config(self.config_filenames, brd.target)
Simon Glasscde5c302016-11-13 14:25:53 -0700958 for fname in self.config_filenames:
Simon Glassdb17fb82015-02-05 22:06:15 -0700959 if outcome.config:
Simon Glassc78ed662019-10-31 07:42:53 -0600960 for key, value in outcome.config[fname].items():
Simon Glassbc74d942023-07-19 17:49:06 -0600961 tconfig.add(fname, key, value)
Simon Glass8132f982022-07-11 19:03:57 -0600962 config[brd.target] = tconfig
Simon Glassdb17fb82015-02-05 22:06:15 -0700963
Simon Glass8132f982022-07-11 19:03:57 -0600964 tenvironment = Environment(brd.target)
Alex Kiernan4059e302018-05-31 04:48:34 +0000965 if outcome.environment:
Simon Glassc78ed662019-10-31 07:42:53 -0600966 for key, value in outcome.environment.items():
Simon Glassbc74d942023-07-19 17:49:06 -0600967 tenvironment.add(key, value)
Simon Glass8132f982022-07-11 19:03:57 -0600968 environment[brd.target] = tenvironment
Alex Kiernan4059e302018-05-31 04:48:34 +0000969
Simon Glass03749d42014-08-28 09:43:44 -0600970 return (board_dict, err_lines_summary, err_lines_boards,
Alex Kiernan4059e302018-05-31 04:48:34 +0000971 warn_lines_summary, warn_lines_boards, config, environment)
Simon Glassc05694f2013-04-03 11:07:16 +0000972
Simon Glassbc74d942023-07-19 17:49:06 -0600973 def add_outcome(self, board_dict, arch_list, changes, char, color):
Simon Glassc05694f2013-04-03 11:07:16 +0000974 """Add an output to our list of outcomes for each architecture
975
976 This simple function adds failing boards (changes) to the
977 relevant architecture string, so we can print the results out
978 sorted by architecture.
979
980 Args:
981 board_dict: Dict containing all boards
982 arch_list: Dict keyed by arch name. Value is a string containing
983 a list of board names which failed for that arch.
984 changes: List of boards to add to arch_list
985 color: terminal.Colour object
986 """
987 done_arch = {}
988 for target in changes:
989 if target in board_dict:
990 arch = board_dict[target].arch
991 else:
992 arch = 'unknown'
Simon Glassf45d3742022-01-29 14:14:17 -0700993 str = self.col.build(color, ' ' + target)
Simon Glassc05694f2013-04-03 11:07:16 +0000994 if not arch in done_arch:
Simon Glassf45d3742022-01-29 14:14:17 -0700995 str = ' %s %s' % (self.col.build(color, char), str)
Simon Glassc05694f2013-04-03 11:07:16 +0000996 done_arch[arch] = True
997 if not arch in arch_list:
998 arch_list[arch] = str
999 else:
1000 arch_list[arch] += str
1001
1002
Simon Glassbc74d942023-07-19 17:49:06 -06001003 def colour_num(self, num):
Simon Glassc05694f2013-04-03 11:07:16 +00001004 color = self.col.RED if num > 0 else self.col.GREEN
1005 if num == 0:
1006 return '0'
Simon Glassf45d3742022-01-29 14:14:17 -07001007 return self.col.build(color, str(num))
Simon Glassc05694f2013-04-03 11:07:16 +00001008
Simon Glassbc74d942023-07-19 17:49:06 -06001009 def reset_result_summary(self, board_selected):
Simon Glassc05694f2013-04-03 11:07:16 +00001010 """Reset the results summary ready for use.
1011
1012 Set up the base board list to be all those selected, and set the
1013 error lines to empty.
1014
Simon Glassbc74d942023-07-19 17:49:06 -06001015 Following this, calls to print_result_summary() will use this
Simon Glassc05694f2013-04-03 11:07:16 +00001016 information to work out what has changed.
1017
1018 Args:
1019 board_selected: Dict containing boards to summarise, keyed by
1020 board.target
1021 """
1022 self._base_board_dict = {}
Simon Glass8132f982022-07-11 19:03:57 -06001023 for brd in board_selected:
1024 self._base_board_dict[brd] = Builder.Outcome(0, [], [], {}, {}, {})
Simon Glassc05694f2013-04-03 11:07:16 +00001025 self._base_err_lines = []
Simon Glass03749d42014-08-28 09:43:44 -06001026 self._base_warn_lines = []
1027 self._base_err_line_boards = {}
1028 self._base_warn_line_boards = {}
Simon Glasscad8abf2015-08-25 21:52:14 -06001029 self._base_config = None
Alex Kiernan4059e302018-05-31 04:48:34 +00001030 self._base_environment = None
Simon Glassc05694f2013-04-03 11:07:16 +00001031
Simon Glassbc74d942023-07-19 17:49:06 -06001032 def print_func_size_detail(self, fname, old, new):
Simon Glassc05694f2013-04-03 11:07:16 +00001033 grow, shrink, add, remove, up, down = 0, 0, 0, 0, 0, 0
1034 delta, common = [], {}
1035
1036 for a in old:
1037 if a in new:
1038 common[a] = 1
1039
1040 for name in old:
1041 if name not in common:
1042 remove += 1
1043 down += old[name]
1044 delta.append([-old[name], name])
1045
1046 for name in new:
1047 if name not in common:
1048 add += 1
1049 up += new[name]
1050 delta.append([new[name], name])
1051
1052 for name in common:
1053 diff = new.get(name, 0) - old.get(name, 0)
1054 if diff > 0:
1055 grow, up = grow + 1, up + diff
1056 elif diff < 0:
1057 shrink, down = shrink + 1, down - diff
1058 delta.append([diff, name])
1059
1060 delta.sort()
1061 delta.reverse()
1062
1063 args = [add, -remove, grow, -shrink, up, -down, up - down]
Tom Rini0b48cd62017-05-22 13:48:52 -04001064 if max(args) == 0 and min(args) == 0:
Simon Glassc05694f2013-04-03 11:07:16 +00001065 return
Simon Glassbc74d942023-07-19 17:49:06 -06001066 args = [self.colour_num(x) for x in args]
Simon Glassc05694f2013-04-03 11:07:16 +00001067 indent = ' ' * 15
Simon Glass02811582022-01-29 14:14:18 -07001068 tprint('%s%s: add: %s/%s, grow: %s/%s bytes: %s/%s (%s)' %
Simon Glassf45d3742022-01-29 14:14:17 -07001069 tuple([indent, self.col.build(self.col.YELLOW, fname)] + args))
Simon Glass02811582022-01-29 14:14:18 -07001070 tprint('%s %-38s %7s %7s %+7s' % (indent, 'function', 'old', 'new',
Simon Glass4433aa92014-09-05 19:00:07 -06001071 'delta'))
Simon Glassc05694f2013-04-03 11:07:16 +00001072 for diff, name in delta:
1073 if diff:
1074 color = self.col.RED if diff > 0 else self.col.GREEN
1075 msg = '%s %-38s %7s %7s %+7d' % (indent, name,
1076 old.get(name, '-'), new.get(name,'-'), diff)
Simon Glass02811582022-01-29 14:14:18 -07001077 tprint(msg, colour=color)
Simon Glassc05694f2013-04-03 11:07:16 +00001078
1079
Simon Glassbc74d942023-07-19 17:49:06 -06001080 def print_size_detail(self, target_list, show_bloat):
Simon Glassc05694f2013-04-03 11:07:16 +00001081 """Show details size information for each board
1082
1083 Args:
1084 target_list: List of targets, each a dict containing:
1085 'target': Target name
1086 'total_diff': Total difference in bytes across all areas
1087 <part_name>: Difference for that part
1088 show_bloat: Show detail for each function
1089 """
1090 targets_by_diff = sorted(target_list, reverse=True,
1091 key=lambda x: x['_total_diff'])
1092 for result in targets_by_diff:
1093 printed_target = False
1094 for name in sorted(result):
1095 diff = result[name]
1096 if name.startswith('_'):
1097 continue
1098 if diff != 0:
1099 color = self.col.RED if diff > 0 else self.col.GREEN
1100 msg = ' %s %+d' % (name, diff)
1101 if not printed_target:
Simon Glass02811582022-01-29 14:14:18 -07001102 tprint('%10s %-15s:' % ('', result['_target']),
Simon Glass4433aa92014-09-05 19:00:07 -06001103 newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001104 printed_target = True
Simon Glass02811582022-01-29 14:14:18 -07001105 tprint(msg, colour=color, newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001106 if printed_target:
Simon Glass02811582022-01-29 14:14:18 -07001107 tprint()
Simon Glassc05694f2013-04-03 11:07:16 +00001108 if show_bloat:
1109 target = result['_target']
1110 outcome = result['_outcome']
1111 base_outcome = self._base_board_dict[target]
1112 for fname in outcome.func_sizes:
Simon Glassbc74d942023-07-19 17:49:06 -06001113 self.print_func_size_detail(fname,
Simon Glassc05694f2013-04-03 11:07:16 +00001114 base_outcome.func_sizes[fname],
1115 outcome.func_sizes[fname])
1116
1117
Simon Glassbc74d942023-07-19 17:49:06 -06001118 def print_size_summary(self, board_selected, board_dict, show_detail,
Simon Glassc05694f2013-04-03 11:07:16 +00001119 show_bloat):
1120 """Print a summary of image sizes broken down by section.
1121
1122 The summary takes the form of one line per architecture. The
1123 line contains deltas for each of the sections (+ means the section
Flavio Suligoie5e3c112020-01-29 09:56:05 +01001124 got bigger, - means smaller). The numbers are the average number
Simon Glassc05694f2013-04-03 11:07:16 +00001125 of bytes that a board in this section increased by.
1126
1127 For example:
1128 powerpc: (622 boards) text -0.0
1129 arm: (285 boards) text -0.0
Simon Glassc05694f2013-04-03 11:07:16 +00001130
1131 Args:
1132 board_selected: Dict containing boards to summarise, keyed by
1133 board.target
1134 board_dict: Dict containing boards for which we built this
1135 commit, keyed by board.target. The value is an Outcome object.
Simon Glassb4002462020-03-18 09:42:43 -06001136 show_detail: Show size delta detail for each board
Simon Glassc05694f2013-04-03 11:07:16 +00001137 show_bloat: Show detail for each function
1138 """
1139 arch_list = {}
1140 arch_count = {}
1141
1142 # Calculate changes in size for different image parts
1143 # The previous sizes are in Board.sizes, for each board
1144 for target in board_dict:
1145 if target not in board_selected:
1146 continue
1147 base_sizes = self._base_board_dict[target].sizes
1148 outcome = board_dict[target]
1149 sizes = outcome.sizes
1150
1151 # Loop through the list of images, creating a dict of size
1152 # changes for each image/part. We end up with something like
1153 # {'target' : 'snapper9g45, 'data' : 5, 'u-boot-spl:text' : -4}
1154 # which means that U-Boot data increased by 5 bytes and SPL
1155 # text decreased by 4.
1156 err = {'_target' : target}
1157 for image in sizes:
1158 if image in base_sizes:
1159 base_image = base_sizes[image]
1160 # Loop through the text, data, bss parts
1161 for part in sorted(sizes[image]):
1162 diff = sizes[image][part] - base_image[part]
1163 col = None
1164 if diff:
1165 if image == 'u-boot':
1166 name = part
1167 else:
1168 name = image + ':' + part
1169 err[name] = diff
1170 arch = board_selected[target].arch
1171 if not arch in arch_count:
1172 arch_count[arch] = 1
1173 else:
1174 arch_count[arch] += 1
1175 if not sizes:
1176 pass # Only add to our list when we have some stats
1177 elif not arch in arch_list:
1178 arch_list[arch] = [err]
1179 else:
1180 arch_list[arch].append(err)
1181
1182 # We now have a list of image size changes sorted by arch
1183 # Print out a summary of these
Simon Glassc78ed662019-10-31 07:42:53 -06001184 for arch, target_list in arch_list.items():
Simon Glassc05694f2013-04-03 11:07:16 +00001185 # Get total difference for each type
1186 totals = {}
1187 for result in target_list:
1188 total = 0
Simon Glassc78ed662019-10-31 07:42:53 -06001189 for name, diff in result.items():
Simon Glassc05694f2013-04-03 11:07:16 +00001190 if name.startswith('_'):
1191 continue
1192 total += diff
1193 if name in totals:
1194 totals[name] += diff
1195 else:
1196 totals[name] = diff
1197 result['_total_diff'] = total
1198 result['_outcome'] = board_dict[result['_target']]
1199
1200 count = len(target_list)
1201 printed_arch = False
1202 for name in sorted(totals):
1203 diff = totals[name]
1204 if diff:
1205 # Display the average difference in this name for this
1206 # architecture
1207 avg_diff = float(diff) / count
1208 color = self.col.RED if avg_diff > 0 else self.col.GREEN
1209 msg = ' %s %+1.1f' % (name, avg_diff)
1210 if not printed_arch:
Simon Glass02811582022-01-29 14:14:18 -07001211 tprint('%10s: (for %d/%d boards)' % (arch, count,
Simon Glass4433aa92014-09-05 19:00:07 -06001212 arch_count[arch]), newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001213 printed_arch = True
Simon Glass02811582022-01-29 14:14:18 -07001214 tprint(msg, colour=color, newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001215
1216 if printed_arch:
Simon Glass02811582022-01-29 14:14:18 -07001217 tprint()
Simon Glassc05694f2013-04-03 11:07:16 +00001218 if show_detail:
Simon Glassbc74d942023-07-19 17:49:06 -06001219 self.print_size_detail(target_list, show_bloat)
Simon Glassc05694f2013-04-03 11:07:16 +00001220
1221
Simon Glassbc74d942023-07-19 17:49:06 -06001222 def print_result_summary(self, board_selected, board_dict, err_lines,
Simon Glass03749d42014-08-28 09:43:44 -06001223 err_line_boards, warn_lines, warn_line_boards,
Alex Kiernan4059e302018-05-31 04:48:34 +00001224 config, environment, show_sizes, show_detail,
1225 show_bloat, show_config, show_environment):
Simon Glassc05694f2013-04-03 11:07:16 +00001226 """Compare results with the base results and display delta.
1227
1228 Only boards mentioned in board_selected will be considered. This
1229 function is intended to be called repeatedly with the results of
1230 each commit. It therefore shows a 'diff' between what it saw in
1231 the last call and what it sees now.
1232
1233 Args:
1234 board_selected: Dict containing boards to summarise, keyed by
1235 board.target
1236 board_dict: Dict containing boards for which we built this
1237 commit, keyed by board.target. The value is an Outcome object.
1238 err_lines: A list of errors for this commit, or [] if there is
1239 none, or we don't want to print errors
Simon Glass3394c9f2014-08-28 09:43:43 -06001240 err_line_boards: Dict keyed by error line, containing a list of
1241 the Board objects with that error
Simon Glass03749d42014-08-28 09:43:44 -06001242 warn_lines: A list of warnings for this commit, or [] if there is
1243 none, or we don't want to print errors
1244 warn_line_boards: Dict keyed by warning line, containing a list of
1245 the Board objects with that warning
Simon Glassdb17fb82015-02-05 22:06:15 -07001246 config: Dictionary keyed by filename - e.g. '.config'. Each
1247 value is itself a dictionary:
1248 key: config name
1249 value: config value
Alex Kiernan4059e302018-05-31 04:48:34 +00001250 environment: Dictionary keyed by environment variable, Each
1251 value is the value of environment variable.
Simon Glassc05694f2013-04-03 11:07:16 +00001252 show_sizes: Show image size deltas
Simon Glassb4002462020-03-18 09:42:43 -06001253 show_detail: Show size delta detail for each board if show_sizes
Simon Glassc05694f2013-04-03 11:07:16 +00001254 show_bloat: Show detail for each function
Simon Glassdb17fb82015-02-05 22:06:15 -07001255 show_config: Show config changes
Alex Kiernan4059e302018-05-31 04:48:34 +00001256 show_environment: Show environment changes
Simon Glassc05694f2013-04-03 11:07:16 +00001257 """
Simon Glassbc74d942023-07-19 17:49:06 -06001258 def _board_list(line, line_boards):
Simon Glass3394c9f2014-08-28 09:43:43 -06001259 """Helper function to get a line of boards containing a line
1260
1261 Args:
1262 line: Error line to search for
Simon Glassde0fefc2020-04-09 15:08:36 -06001263 line_boards: boards to search, each a Board
Simon Glass3394c9f2014-08-28 09:43:43 -06001264 Return:
Simon Glassde0fefc2020-04-09 15:08:36 -06001265 List of boards with that error line, or [] if the user has not
1266 requested such a list
Simon Glass3394c9f2014-08-28 09:43:43 -06001267 """
Simon Glass5df45222022-07-11 19:04:00 -06001268 brds = []
Simon Glassde0fefc2020-04-09 15:08:36 -06001269 board_set = set()
Simon Glass3394c9f2014-08-28 09:43:43 -06001270 if self._list_error_boards:
Simon Glass8132f982022-07-11 19:03:57 -06001271 for brd in line_boards[line]:
1272 if not brd in board_set:
Simon Glass5df45222022-07-11 19:04:00 -06001273 brds.append(brd)
Simon Glass8132f982022-07-11 19:03:57 -06001274 board_set.add(brd)
Simon Glass5df45222022-07-11 19:04:00 -06001275 return brds
Simon Glass3394c9f2014-08-28 09:43:43 -06001276
Simon Glassbc74d942023-07-19 17:49:06 -06001277 def _calc_error_delta(base_lines, base_line_boards, lines, line_boards,
Simon Glass03749d42014-08-28 09:43:44 -06001278 char):
Simon Glassde0fefc2020-04-09 15:08:36 -06001279 """Calculate the required output based on changes in errors
1280
1281 Args:
1282 base_lines: List of errors/warnings for previous commit
1283 base_line_boards: Dict keyed by error line, containing a list
1284 of the Board objects with that error in the previous commit
1285 lines: List of errors/warning for this commit, each a str
1286 line_boards: Dict keyed by error line, containing a list
1287 of the Board objects with that error in this commit
1288 char: Character representing error ('') or warning ('w'). The
1289 broken ('+') or fixed ('-') characters are added in this
1290 function
1291
1292 Returns:
1293 Tuple
1294 List of ErrLine objects for 'better' lines
1295 List of ErrLine objects for 'worse' lines
1296 """
Simon Glass03749d42014-08-28 09:43:44 -06001297 better_lines = []
1298 worse_lines = []
1299 for line in lines:
1300 if line not in base_lines:
Simon Glassbc74d942023-07-19 17:49:06 -06001301 errline = ErrLine(char + '+', _board_list(line, line_boards),
Simon Glassde0fefc2020-04-09 15:08:36 -06001302 line)
1303 worse_lines.append(errline)
Simon Glass03749d42014-08-28 09:43:44 -06001304 for line in base_lines:
1305 if line not in lines:
Simon Glassde0fefc2020-04-09 15:08:36 -06001306 errline = ErrLine(char + '-',
Simon Glassbc74d942023-07-19 17:49:06 -06001307 _board_list(line, base_line_boards), line)
Simon Glassde0fefc2020-04-09 15:08:36 -06001308 better_lines.append(errline)
Simon Glass03749d42014-08-28 09:43:44 -06001309 return better_lines, worse_lines
1310
Simon Glassbc74d942023-07-19 17:49:06 -06001311 def _calc_config(delta, name, config):
Simon Glassdb17fb82015-02-05 22:06:15 -07001312 """Calculate configuration changes
1313
1314 Args:
1315 delta: Type of the delta, e.g. '+'
1316 name: name of the file which changed (e.g. .config)
1317 config: configuration change dictionary
1318 key: config name
1319 value: config value
1320 Returns:
1321 String containing the configuration changes which can be
1322 printed
1323 """
1324 out = ''
1325 for key in sorted(config.keys()):
1326 out += '%s=%s ' % (key, config[key])
Simon Glasscad8abf2015-08-25 21:52:14 -06001327 return '%s %s: %s' % (delta, name, out)
Simon Glassdb17fb82015-02-05 22:06:15 -07001328
Simon Glassbc74d942023-07-19 17:49:06 -06001329 def _add_config(lines, name, config_plus, config_minus, config_change):
Simon Glasscad8abf2015-08-25 21:52:14 -06001330 """Add changes in configuration to a list
Simon Glassdb17fb82015-02-05 22:06:15 -07001331
1332 Args:
Simon Glasscad8abf2015-08-25 21:52:14 -06001333 lines: list to add to
1334 name: config file name
Simon Glassdb17fb82015-02-05 22:06:15 -07001335 config_plus: configurations added, dictionary
1336 key: config name
1337 value: config value
1338 config_minus: configurations removed, dictionary
1339 key: config name
1340 value: config value
1341 config_change: configurations changed, dictionary
1342 key: config name
1343 value: config value
1344 """
1345 if config_plus:
Simon Glassbc74d942023-07-19 17:49:06 -06001346 lines.append(_calc_config('+', name, config_plus))
Simon Glassdb17fb82015-02-05 22:06:15 -07001347 if config_minus:
Simon Glassbc74d942023-07-19 17:49:06 -06001348 lines.append(_calc_config('-', name, config_minus))
Simon Glassdb17fb82015-02-05 22:06:15 -07001349 if config_change:
Simon Glassbc74d942023-07-19 17:49:06 -06001350 lines.append(_calc_config('c', name, config_change))
Simon Glasscad8abf2015-08-25 21:52:14 -06001351
Simon Glassbc74d942023-07-19 17:49:06 -06001352 def _output_config_info(lines):
Simon Glasscad8abf2015-08-25 21:52:14 -06001353 for line in lines:
1354 if not line:
1355 continue
1356 if line[0] == '+':
1357 col = self.col.GREEN
1358 elif line[0] == '-':
1359 col = self.col.RED
1360 elif line[0] == 'c':
1361 col = self.col.YELLOW
Simon Glass02811582022-01-29 14:14:18 -07001362 tprint(' ' + line, newline=True, colour=col)
Simon Glasscad8abf2015-08-25 21:52:14 -06001363
Simon Glassbc74d942023-07-19 17:49:06 -06001364 def _output_err_lines(err_lines, colour):
Simon Glassac500222020-04-09 15:08:28 -06001365 """Output the line of error/warning lines, if not empty
1366
1367 Also increments self._error_lines if err_lines not empty
1368
1369 Args:
Simon Glassde0fefc2020-04-09 15:08:36 -06001370 err_lines: List of ErrLine objects, each an error or warning
1371 line, possibly including a list of boards with that
1372 error/warning
Simon Glassac500222020-04-09 15:08:28 -06001373 colour: Colour to use for output
1374 """
1375 if err_lines:
Simon Glassea49f9b2020-04-09 15:08:37 -06001376 out_list = []
Simon Glassde0fefc2020-04-09 15:08:36 -06001377 for line in err_lines:
Simon Glass5df45222022-07-11 19:04:00 -06001378 names = [brd.target for brd in line.brds]
Simon Glass070589b2020-04-09 15:08:38 -06001379 board_str = ' '.join(names) if names else ''
Simon Glassea49f9b2020-04-09 15:08:37 -06001380 if board_str:
Simon Glassf45d3742022-01-29 14:14:17 -07001381 out = self.col.build(colour, line.char + '(')
1382 out += self.col.build(self.col.MAGENTA, board_str,
Simon Glassea49f9b2020-04-09 15:08:37 -06001383 bright=False)
Simon Glassf45d3742022-01-29 14:14:17 -07001384 out += self.col.build(colour, ') %s' % line.errline)
Simon Glassea49f9b2020-04-09 15:08:37 -06001385 else:
Simon Glassf45d3742022-01-29 14:14:17 -07001386 out = self.col.build(colour, line.char + line.errline)
Simon Glassea49f9b2020-04-09 15:08:37 -06001387 out_list.append(out)
Simon Glass02811582022-01-29 14:14:18 -07001388 tprint('\n'.join(out_list))
Simon Glassac500222020-04-09 15:08:28 -06001389 self._error_lines += 1
1390
Simon Glassdb17fb82015-02-05 22:06:15 -07001391
Simon Glass454507f2018-11-06 16:02:12 -07001392 ok_boards = [] # List of boards fixed since last commit
Simon Glass071a1782018-11-06 16:02:13 -07001393 warn_boards = [] # List of boards with warnings since last commit
Simon Glass454507f2018-11-06 16:02:12 -07001394 err_boards = [] # List of new broken boards since last commit
1395 new_boards = [] # List of boards that didn't exist last time
1396 unknown_boards = [] # List of boards that were not built
Simon Glassc05694f2013-04-03 11:07:16 +00001397
1398 for target in board_dict:
1399 if target not in board_selected:
1400 continue
1401
1402 # If the board was built last time, add its outcome to a list
1403 if target in self._base_board_dict:
1404 base_outcome = self._base_board_dict[target].rc
1405 outcome = board_dict[target]
1406 if outcome.rc == OUTCOME_UNKNOWN:
Simon Glass454507f2018-11-06 16:02:12 -07001407 unknown_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001408 elif outcome.rc < base_outcome:
Simon Glass071a1782018-11-06 16:02:13 -07001409 if outcome.rc == OUTCOME_WARNING:
1410 warn_boards.append(target)
1411 else:
1412 ok_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001413 elif outcome.rc > base_outcome:
Simon Glass071a1782018-11-06 16:02:13 -07001414 if outcome.rc == OUTCOME_WARNING:
1415 warn_boards.append(target)
1416 else:
1417 err_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001418 else:
Simon Glass454507f2018-11-06 16:02:12 -07001419 new_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001420
Simon Glassac500222020-04-09 15:08:28 -06001421 # Get a list of errors and warnings that have appeared, and disappeared
Simon Glassbc74d942023-07-19 17:49:06 -06001422 better_err, worse_err = _calc_error_delta(self._base_err_lines,
Simon Glass03749d42014-08-28 09:43:44 -06001423 self._base_err_line_boards, err_lines, err_line_boards, '')
Simon Glassbc74d942023-07-19 17:49:06 -06001424 better_warn, worse_warn = _calc_error_delta(self._base_warn_lines,
Simon Glass03749d42014-08-28 09:43:44 -06001425 self._base_warn_line_boards, warn_lines, warn_line_boards, 'w')
Simon Glassc05694f2013-04-03 11:07:16 +00001426
Simon Glass6c435622022-07-11 19:03:56 -06001427 # For the IDE mode, print out all the output
1428 if self._ide:
1429 outcome = board_dict[target]
1430 for line in outcome.err_lines:
1431 sys.stderr.write(line)
1432
Simon Glassc05694f2013-04-03 11:07:16 +00001433 # Display results by arch
Simon Glass6c435622022-07-11 19:03:56 -06001434 elif any((ok_boards, warn_boards, err_boards, unknown_boards, new_boards,
Simon Glass071a1782018-11-06 16:02:13 -07001435 worse_err, better_err, worse_warn, better_warn)):
Simon Glassc05694f2013-04-03 11:07:16 +00001436 arch_list = {}
Simon Glassbc74d942023-07-19 17:49:06 -06001437 self.add_outcome(board_selected, arch_list, ok_boards, '',
Simon Glassc05694f2013-04-03 11:07:16 +00001438 self.col.GREEN)
Simon Glassbc74d942023-07-19 17:49:06 -06001439 self.add_outcome(board_selected, arch_list, warn_boards, 'w+',
Simon Glass071a1782018-11-06 16:02:13 -07001440 self.col.YELLOW)
Simon Glassbc74d942023-07-19 17:49:06 -06001441 self.add_outcome(board_selected, arch_list, err_boards, '+',
Simon Glassc05694f2013-04-03 11:07:16 +00001442 self.col.RED)
Simon Glassbc74d942023-07-19 17:49:06 -06001443 self.add_outcome(board_selected, arch_list, new_boards, '*', self.col.BLUE)
Simon Glassc05694f2013-04-03 11:07:16 +00001444 if self._show_unknown:
Simon Glassbc74d942023-07-19 17:49:06 -06001445 self.add_outcome(board_selected, arch_list, unknown_boards, '?',
Simon Glassc05694f2013-04-03 11:07:16 +00001446 self.col.MAGENTA)
Simon Glassc78ed662019-10-31 07:42:53 -06001447 for arch, target_list in arch_list.items():
Simon Glass02811582022-01-29 14:14:18 -07001448 tprint('%10s: %s' % (arch, target_list))
Simon Glassbb4dffb2014-08-09 15:33:06 -06001449 self._error_lines += 1
Simon Glassbc74d942023-07-19 17:49:06 -06001450 _output_err_lines(better_err, colour=self.col.GREEN)
1451 _output_err_lines(worse_err, colour=self.col.RED)
1452 _output_err_lines(better_warn, colour=self.col.CYAN)
1453 _output_err_lines(worse_warn, colour=self.col.YELLOW)
Simon Glassc05694f2013-04-03 11:07:16 +00001454
1455 if show_sizes:
Simon Glassbc74d942023-07-19 17:49:06 -06001456 self.print_size_summary(board_selected, board_dict, show_detail,
Simon Glassc05694f2013-04-03 11:07:16 +00001457 show_bloat)
1458
Alex Kiernan4059e302018-05-31 04:48:34 +00001459 if show_environment and self._base_environment:
1460 lines = []
1461
1462 for target in board_dict:
1463 if target not in board_selected:
1464 continue
1465
1466 tbase = self._base_environment[target]
1467 tenvironment = environment[target]
1468 environment_plus = {}
1469 environment_minus = {}
1470 environment_change = {}
1471 base = tbase.environment
Simon Glassc78ed662019-10-31 07:42:53 -06001472 for key, value in tenvironment.environment.items():
Alex Kiernan4059e302018-05-31 04:48:34 +00001473 if key not in base:
1474 environment_plus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001475 for key, value in base.items():
Alex Kiernan4059e302018-05-31 04:48:34 +00001476 if key not in tenvironment.environment:
1477 environment_minus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001478 for key, value in base.items():
Alex Kiernan4059e302018-05-31 04:48:34 +00001479 new_value = tenvironment.environment.get(key)
1480 if new_value and value != new_value:
1481 desc = '%s -> %s' % (value, new_value)
1482 environment_change[key] = desc
1483
Simon Glassbc74d942023-07-19 17:49:06 -06001484 _add_config(lines, target, environment_plus, environment_minus,
Alex Kiernan4059e302018-05-31 04:48:34 +00001485 environment_change)
1486
Simon Glassbc74d942023-07-19 17:49:06 -06001487 _output_config_info(lines)
Alex Kiernan4059e302018-05-31 04:48:34 +00001488
Simon Glasscad8abf2015-08-25 21:52:14 -06001489 if show_config and self._base_config:
1490 summary = {}
1491 arch_config_plus = {}
1492 arch_config_minus = {}
1493 arch_config_change = {}
1494 arch_list = []
1495
1496 for target in board_dict:
1497 if target not in board_selected:
1498 continue
1499 arch = board_selected[target].arch
1500 if arch not in arch_list:
1501 arch_list.append(arch)
1502
1503 for arch in arch_list:
1504 arch_config_plus[arch] = {}
1505 arch_config_minus[arch] = {}
1506 arch_config_change[arch] = {}
Simon Glasscde5c302016-11-13 14:25:53 -07001507 for name in self.config_filenames:
Simon Glasscad8abf2015-08-25 21:52:14 -06001508 arch_config_plus[arch][name] = {}
1509 arch_config_minus[arch][name] = {}
1510 arch_config_change[arch][name] = {}
1511
1512 for target in board_dict:
1513 if target not in board_selected:
Simon Glassdb17fb82015-02-05 22:06:15 -07001514 continue
Simon Glasscad8abf2015-08-25 21:52:14 -06001515
1516 arch = board_selected[target].arch
1517
1518 all_config_plus = {}
1519 all_config_minus = {}
1520 all_config_change = {}
1521 tbase = self._base_config[target]
1522 tconfig = config[target]
1523 lines = []
Simon Glasscde5c302016-11-13 14:25:53 -07001524 for name in self.config_filenames:
Simon Glasscad8abf2015-08-25 21:52:14 -06001525 if not tconfig.config[name]:
1526 continue
1527 config_plus = {}
1528 config_minus = {}
1529 config_change = {}
1530 base = tbase.config[name]
Simon Glassc78ed662019-10-31 07:42:53 -06001531 for key, value in tconfig.config[name].items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001532 if key not in base:
1533 config_plus[key] = value
1534 all_config_plus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001535 for key, value in base.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001536 if key not in tconfig.config[name]:
1537 config_minus[key] = value
1538 all_config_minus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001539 for key, value in base.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001540 new_value = tconfig.config.get(key)
1541 if new_value and value != new_value:
1542 desc = '%s -> %s' % (value, new_value)
1543 config_change[key] = desc
1544 all_config_change[key] = desc
1545
1546 arch_config_plus[arch][name].update(config_plus)
1547 arch_config_minus[arch][name].update(config_minus)
1548 arch_config_change[arch][name].update(config_change)
1549
Simon Glassbc74d942023-07-19 17:49:06 -06001550 _add_config(lines, name, config_plus, config_minus,
Simon Glasscad8abf2015-08-25 21:52:14 -06001551 config_change)
Simon Glassbc74d942023-07-19 17:49:06 -06001552 _add_config(lines, 'all', all_config_plus, all_config_minus,
Simon Glasscad8abf2015-08-25 21:52:14 -06001553 all_config_change)
1554 summary[target] = '\n'.join(lines)
1555
1556 lines_by_target = {}
Simon Glassc78ed662019-10-31 07:42:53 -06001557 for target, lines in summary.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001558 if lines in lines_by_target:
1559 lines_by_target[lines].append(target)
1560 else:
1561 lines_by_target[lines] = [target]
1562
1563 for arch in arch_list:
1564 lines = []
1565 all_plus = {}
1566 all_minus = {}
1567 all_change = {}
Simon Glasscde5c302016-11-13 14:25:53 -07001568 for name in self.config_filenames:
Simon Glasscad8abf2015-08-25 21:52:14 -06001569 all_plus.update(arch_config_plus[arch][name])
1570 all_minus.update(arch_config_minus[arch][name])
1571 all_change.update(arch_config_change[arch][name])
Simon Glassbc74d942023-07-19 17:49:06 -06001572 _add_config(lines, name, arch_config_plus[arch][name],
Simon Glasscad8abf2015-08-25 21:52:14 -06001573 arch_config_minus[arch][name],
1574 arch_config_change[arch][name])
Simon Glassbc74d942023-07-19 17:49:06 -06001575 _add_config(lines, 'all', all_plus, all_minus, all_change)
Simon Glasscad8abf2015-08-25 21:52:14 -06001576 #arch_summary[target] = '\n'.join(lines)
1577 if lines:
Simon Glass02811582022-01-29 14:14:18 -07001578 tprint('%s:' % arch)
Simon Glassbc74d942023-07-19 17:49:06 -06001579 _output_config_info(lines)
Simon Glasscad8abf2015-08-25 21:52:14 -06001580
Simon Glassc78ed662019-10-31 07:42:53 -06001581 for lines, targets in lines_by_target.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001582 if not lines:
1583 continue
Simon Glass02811582022-01-29 14:14:18 -07001584 tprint('%s :' % ' '.join(sorted(targets)))
Simon Glassbc74d942023-07-19 17:49:06 -06001585 _output_config_info(lines.split('\n'))
Simon Glasscad8abf2015-08-25 21:52:14 -06001586
Simon Glassdb17fb82015-02-05 22:06:15 -07001587
Simon Glassc05694f2013-04-03 11:07:16 +00001588 # Save our updated information for the next call to this function
1589 self._base_board_dict = board_dict
1590 self._base_err_lines = err_lines
Simon Glass03749d42014-08-28 09:43:44 -06001591 self._base_warn_lines = warn_lines
1592 self._base_err_line_boards = err_line_boards
1593 self._base_warn_line_boards = warn_line_boards
Simon Glassdb17fb82015-02-05 22:06:15 -07001594 self._base_config = config
Alex Kiernan4059e302018-05-31 04:48:34 +00001595 self._base_environment = environment
Simon Glassc05694f2013-04-03 11:07:16 +00001596
1597 # Get a list of boards that did not get built, if needed
1598 not_built = []
Simon Glass8132f982022-07-11 19:03:57 -06001599 for brd in board_selected:
1600 if not brd in board_dict:
1601 not_built.append(brd)
Simon Glassc05694f2013-04-03 11:07:16 +00001602 if not_built:
Simon Glass02811582022-01-29 14:14:18 -07001603 tprint("Boards not built (%d): %s" % (len(not_built),
Simon Glass4433aa92014-09-05 19:00:07 -06001604 ', '.join(not_built)))
Simon Glassc05694f2013-04-03 11:07:16 +00001605
Simon Glassbc74d942023-07-19 17:49:06 -06001606 def produce_result_summary(self, commit_upto, commits, board_selected):
Simon Glass03749d42014-08-28 09:43:44 -06001607 (board_dict, err_lines, err_line_boards, warn_lines,
Simon Glassbc74d942023-07-19 17:49:06 -06001608 warn_line_boards, config, environment) = self.get_result_summary(
Simon Glass3394c9f2014-08-28 09:43:43 -06001609 board_selected, commit_upto,
Simon Glassdb17fb82015-02-05 22:06:15 -07001610 read_func_sizes=self._show_bloat,
Alex Kiernan4059e302018-05-31 04:48:34 +00001611 read_config=self._show_config,
1612 read_environment=self._show_environment)
Simon Glasseb48bbc2014-08-09 15:33:02 -06001613 if commits:
1614 msg = '%02d: %s' % (commit_upto + 1,
1615 commits[commit_upto].subject)
Simon Glass02811582022-01-29 14:14:18 -07001616 tprint(msg, colour=self.col.BLUE)
Simon Glassbc74d942023-07-19 17:49:06 -06001617 self.print_result_summary(board_selected, board_dict,
Simon Glass3394c9f2014-08-28 09:43:43 -06001618 err_lines if self._show_errors else [], err_line_boards,
Simon Glass03749d42014-08-28 09:43:44 -06001619 warn_lines if self._show_errors else [], warn_line_boards,
Alex Kiernan4059e302018-05-31 04:48:34 +00001620 config, environment, self._show_sizes, self._show_detail,
1621 self._show_bloat, self._show_config, self._show_environment)
Simon Glassc05694f2013-04-03 11:07:16 +00001622
Simon Glassbc74d942023-07-19 17:49:06 -06001623 def show_summary(self, commits, board_selected):
Simon Glassc05694f2013-04-03 11:07:16 +00001624 """Show a build summary for U-Boot for a given board list.
1625
1626 Reset the result summary, then repeatedly call GetResultSummary on
1627 each commit's results, then display the differences we see.
1628
1629 Args:
1630 commit: Commit objects to summarise
1631 board_selected: Dict containing boards to summarise
Simon Glassc05694f2013-04-03 11:07:16 +00001632 """
Simon Glassd326ad72014-08-09 15:32:59 -06001633 self.commit_count = len(commits) if commits else 1
Simon Glassc05694f2013-04-03 11:07:16 +00001634 self.commits = commits
Simon Glassbc74d942023-07-19 17:49:06 -06001635 self.reset_result_summary(board_selected)
Simon Glassbb4dffb2014-08-09 15:33:06 -06001636 self._error_lines = 0
Simon Glassc05694f2013-04-03 11:07:16 +00001637
1638 for commit_upto in range(0, self.commit_count, self._step):
Simon Glassbc74d942023-07-19 17:49:06 -06001639 self.produce_result_summary(commit_upto, commits, board_selected)
Simon Glassbb4dffb2014-08-09 15:33:06 -06001640 if not self._error_lines:
Simon Glass02811582022-01-29 14:14:18 -07001641 tprint('(no errors to report)', colour=self.col.GREEN)
Simon Glassc05694f2013-04-03 11:07:16 +00001642
1643
Simon Glassbc74d942023-07-19 17:49:06 -06001644 def setup_build(self, board_selected, commits):
Simon Glassc05694f2013-04-03 11:07:16 +00001645 """Set up ready to start a build.
1646
1647 Args:
1648 board_selected: Selected boards to build
1649 commits: Selected commits to build
1650 """
1651 # First work out how many commits we will build
Simon Glassc78ed662019-10-31 07:42:53 -06001652 count = (self.commit_count + self._step - 1) // self._step
Simon Glassc05694f2013-04-03 11:07:16 +00001653 self.count = len(board_selected) * count
1654 self.upto = self.warned = self.fail = 0
1655 self._timestamps = collections.deque()
1656
Simon Glassbc74d942023-07-19 17:49:06 -06001657 def get_thread_dir(self, thread_num):
Simon Glassc05694f2013-04-03 11:07:16 +00001658 """Get the directory path to the working dir for a thread.
1659
1660 Args:
Simon Glassc635d892021-01-30 22:17:46 -07001661 thread_num: Number of thread to check (-1 for main process, which
1662 is treated as 0)
Simon Glassc05694f2013-04-03 11:07:16 +00001663 """
Simon Glassb6eb8cf2020-03-18 09:42:42 -06001664 if self.work_in_output:
1665 return self._working_dir
Simon Glassc635d892021-01-30 22:17:46 -07001666 return os.path.join(self._working_dir, '%02d' % max(thread_num, 0))
Simon Glassc05694f2013-04-03 11:07:16 +00001667
Simon Glassbc74d942023-07-19 17:49:06 -06001668 def _prepare_thread(self, thread_num, setup_git):
Simon Glassc05694f2013-04-03 11:07:16 +00001669 """Prepare the working directory for a thread.
1670
1671 This clones or fetches the repo into the thread's work directory.
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001672 Optionally, it can create a linked working tree of the repo in the
1673 thread's work directory instead.
Simon Glassc05694f2013-04-03 11:07:16 +00001674
1675 Args:
1676 thread_num: Thread number (0, 1, ...)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001677 setup_git:
1678 'clone' to set up a git clone
1679 'worktree' to set up a git worktree
Simon Glassc05694f2013-04-03 11:07:16 +00001680 """
Simon Glassbc74d942023-07-19 17:49:06 -06001681 thread_dir = self.get_thread_dir(thread_num)
Simon Glassc5077c32023-07-19 17:49:08 -06001682 builderthread.mkdir(thread_dir)
Simon Glassc05694f2013-04-03 11:07:16 +00001683 git_dir = os.path.join(thread_dir, '.git')
1684
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001685 # Create a worktree or a git repo clone for this thread if it
1686 # doesn't already exist
Simon Glassd326ad72014-08-09 15:32:59 -06001687 if setup_git and self.git_dir:
Simon Glassc05694f2013-04-03 11:07:16 +00001688 src_dir = os.path.abspath(self.git_dir)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001689 if os.path.isdir(git_dir):
1690 # This is a clone of the src_dir repo, we can keep using
1691 # it but need to fetch from src_dir.
Simon Glass02811582022-01-29 14:14:18 -07001692 tprint('\rFetching repo for thread %d' % thread_num,
Simon Glass43054932020-04-09 15:08:43 -06001693 newline=False)
Simon Glass761648b2022-01-29 14:14:11 -07001694 gitutil.fetch(git_dir, thread_dir)
Simon Glass02811582022-01-29 14:14:18 -07001695 terminal.print_clear()
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001696 elif os.path.isfile(git_dir):
1697 # This is a worktree of the src_dir repo, we don't need to
1698 # create it again or update it in any way.
1699 pass
1700 elif os.path.exists(git_dir):
1701 # Don't know what could trigger this, but we probably
1702 # can't create a git worktree/clone here.
1703 raise ValueError('Git dir %s exists, but is not a file '
1704 'or a directory.' % git_dir)
1705 elif setup_git == 'worktree':
Simon Glass02811582022-01-29 14:14:18 -07001706 tprint('\rChecking out worktree for thread %d' % thread_num,
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001707 newline=False)
Simon Glass761648b2022-01-29 14:14:11 -07001708 gitutil.add_worktree(src_dir, thread_dir)
Simon Glass02811582022-01-29 14:14:18 -07001709 terminal.print_clear()
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001710 elif setup_git == 'clone' or setup_git == True:
Simon Glass02811582022-01-29 14:14:18 -07001711 tprint('\rCloning repo for thread %d' % thread_num,
Simon Glass2e2a6e62016-09-18 16:48:31 -06001712 newline=False)
Simon Glass761648b2022-01-29 14:14:11 -07001713 gitutil.clone(src_dir, thread_dir)
Simon Glass02811582022-01-29 14:14:18 -07001714 terminal.print_clear()
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001715 else:
1716 raise ValueError("Can't setup git repo with %s." % setup_git)
Simon Glassc05694f2013-04-03 11:07:16 +00001717
Simon Glassbc74d942023-07-19 17:49:06 -06001718 def _prepare_working_space(self, max_threads, setup_git):
Simon Glassc05694f2013-04-03 11:07:16 +00001719 """Prepare the working directory for use.
1720
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001721 Set up the git repo for each thread. Creates a linked working tree
1722 if git-worktree is available, or clones the repo if it isn't.
Simon Glassc05694f2013-04-03 11:07:16 +00001723
1724 Args:
Simon Glassc635d892021-01-30 22:17:46 -07001725 max_threads: Maximum number of threads we expect to need. If 0 then
1726 1 is set up, since the main process still needs somewhere to
1727 work
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001728 setup_git: True to set up a git worktree or a git clone
Simon Glassc05694f2013-04-03 11:07:16 +00001729 """
Simon Glassc5077c32023-07-19 17:49:08 -06001730 builderthread.mkdir(self._working_dir)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001731 if setup_git and self.git_dir:
1732 src_dir = os.path.abspath(self.git_dir)
Simon Glass761648b2022-01-29 14:14:11 -07001733 if gitutil.check_worktree_is_available(src_dir):
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001734 setup_git = 'worktree'
1735 # If we previously added a worktree but the directory for it
1736 # got deleted, we need to prune its files from the repo so
1737 # that we can check out another in its place.
Simon Glass761648b2022-01-29 14:14:11 -07001738 gitutil.prune_worktrees(src_dir)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001739 else:
1740 setup_git = 'clone'
Simon Glassc635d892021-01-30 22:17:46 -07001741
1742 # Always do at least one thread
1743 for thread in range(max(max_threads, 1)):
Simon Glassbc74d942023-07-19 17:49:06 -06001744 self._prepare_thread(thread, setup_git)
Simon Glassc05694f2013-04-03 11:07:16 +00001745
Simon Glassbc74d942023-07-19 17:49:06 -06001746 def _get_output_space_removals(self):
Simon Glassc05694f2013-04-03 11:07:16 +00001747 """Get the output directories ready to receive files.
1748
Simon Glass5dc1ca72020-03-18 09:42:45 -06001749 Figure out what needs to be deleted in the output directory before it
1750 can be used. We only delete old buildman directories which have the
Simon Glass4cb54682023-07-19 17:49:10 -06001751 expected name pattern. See get_output_dir().
Simon Glass5dc1ca72020-03-18 09:42:45 -06001752
1753 Returns:
1754 List of full paths of directories to remove
Simon Glassc05694f2013-04-03 11:07:16 +00001755 """
Simon Glassb9a9b7c2014-12-01 17:33:53 -07001756 if not self.commits:
1757 return
Simon Glassc05694f2013-04-03 11:07:16 +00001758 dir_list = []
1759 for commit_upto in range(self.commit_count):
Simon Glass4cb54682023-07-19 17:49:10 -06001760 dir_list.append(self.get_output_dir(commit_upto))
Simon Glassc05694f2013-04-03 11:07:16 +00001761
Simon Glass83cb6cc2016-09-18 16:48:32 -06001762 to_remove = []
Simon Glassc05694f2013-04-03 11:07:16 +00001763 for dirname in glob.glob(os.path.join(self.base_dir, '*')):
1764 if dirname not in dir_list:
Simon Glass5dc1ca72020-03-18 09:42:45 -06001765 leaf = dirname[len(self.base_dir) + 1:]
Ovidiu Panaitee8e9cb2020-05-15 09:30:12 +03001766 m = re.match('[0-9]+_g[0-9a-f]+_.*', leaf)
Simon Glass5dc1ca72020-03-18 09:42:45 -06001767 if m:
1768 to_remove.append(dirname)
1769 return to_remove
1770
Simon Glassbc74d942023-07-19 17:49:06 -06001771 def _prepare_output_space(self):
Simon Glass5dc1ca72020-03-18 09:42:45 -06001772 """Get the output directories ready to receive files.
1773
1774 We delete any output directories which look like ones we need to
1775 create. Having left over directories is confusing when the user wants
1776 to check the output manually.
1777 """
Simon Glassbc74d942023-07-19 17:49:06 -06001778 to_remove = self._get_output_space_removals()
Simon Glass83cb6cc2016-09-18 16:48:32 -06001779 if to_remove:
Simon Glass02811582022-01-29 14:14:18 -07001780 tprint('Removing %d old build directories...' % len(to_remove),
Simon Glass83cb6cc2016-09-18 16:48:32 -06001781 newline=False)
1782 for dirname in to_remove:
Simon Glassc05694f2013-04-03 11:07:16 +00001783 shutil.rmtree(dirname)
Simon Glass02811582022-01-29 14:14:18 -07001784 terminal.print_clear()
Simon Glassc05694f2013-04-03 11:07:16 +00001785
Simon Glassbc74d942023-07-19 17:49:06 -06001786 def build_boards(self, commits, board_selected, keep_outputs, verbose):
Simon Glassc05694f2013-04-03 11:07:16 +00001787 """Build all commits for a list of boards
1788
1789 Args:
1790 commits: List of commits to be build, each a Commit object
1791 boards_selected: Dict of selected boards, key is target name,
1792 value is Board object
Simon Glassc05694f2013-04-03 11:07:16 +00001793 keep_outputs: True to save build output files
Simon Glass78e418e2014-08-09 15:33:03 -06001794 verbose: Display build results as they are completed
Simon Glassc2f91072014-08-28 09:43:39 -06001795 Returns:
1796 Tuple containing:
1797 - number of boards that failed to build
1798 - number of boards that issued warnings
Simon Glass9bf9a722021-04-11 16:27:27 +12001799 - list of thread exceptions raised
Simon Glassc05694f2013-04-03 11:07:16 +00001800 """
Simon Glassd326ad72014-08-09 15:32:59 -06001801 self.commit_count = len(commits) if commits else 1
Simon Glassc05694f2013-04-03 11:07:16 +00001802 self.commits = commits
Simon Glass78e418e2014-08-09 15:33:03 -06001803 self._verbose = verbose
Simon Glassc05694f2013-04-03 11:07:16 +00001804
Simon Glassbc74d942023-07-19 17:49:06 -06001805 self.reset_result_summary(board_selected)
Simon Glassc5077c32023-07-19 17:49:08 -06001806 builderthread.mkdir(self.base_dir, parents = True)
Simon Glassbc74d942023-07-19 17:49:06 -06001807 self._prepare_working_space(min(self.num_threads, len(board_selected)),
Simon Glassd326ad72014-08-09 15:32:59 -06001808 commits is not None)
Simon Glassbc74d942023-07-19 17:49:06 -06001809 self._prepare_output_space()
Simon Glass6c435622022-07-11 19:03:56 -06001810 if not self._ide:
1811 tprint('\rStarting build...', newline=False)
Simon Glass7190a172023-09-07 10:00:19 -06001812 self._start_time = datetime.now()
Simon Glassbc74d942023-07-19 17:49:06 -06001813 self.setup_build(board_selected, commits)
1814 self.process_result(None)
Simon Glass9bf9a722021-04-11 16:27:27 +12001815 self.thread_exceptions = []
Simon Glassc05694f2013-04-03 11:07:16 +00001816 # Create jobs to build all commits for each board
Simon Glassc78ed662019-10-31 07:42:53 -06001817 for brd in board_selected.values():
Simon Glass4a1e88b2014-08-09 15:33:00 -06001818 job = builderthread.BuilderJob()
Simon Glass8132f982022-07-11 19:03:57 -06001819 job.brd = brd
Simon Glassc05694f2013-04-03 11:07:16 +00001820 job.commits = commits
1821 job.keep_outputs = keep_outputs
Simon Glassb6eb8cf2020-03-18 09:42:42 -06001822 job.work_in_output = self.work_in_output
Simon Glasse5650a82022-01-22 05:07:33 -07001823 job.adjust_cfg = self.adjust_cfg
Simon Glassc05694f2013-04-03 11:07:16 +00001824 job.step = self._step
Simon Glassc635d892021-01-30 22:17:46 -07001825 if self.num_threads:
1826 self.queue.put(job)
1827 else:
Simon Glassc5077c32023-07-19 17:49:08 -06001828 self._single_builder.run_job(job)
Simon Glassc05694f2013-04-03 11:07:16 +00001829
Simon Glassc635d892021-01-30 22:17:46 -07001830 if self.num_threads:
1831 term = threading.Thread(target=self.queue.join)
1832 term.setDaemon(True)
1833 term.start()
1834 while term.is_alive():
1835 term.join(100)
Simon Glassc05694f2013-04-03 11:07:16 +00001836
Simon Glassc635d892021-01-30 22:17:46 -07001837 # Wait until we have processed all output
1838 self.out_queue.join()
Simon Glass6c435622022-07-11 19:03:56 -06001839 if not self._ide:
1840 tprint()
Simon Glass726ae812020-04-09 15:08:47 -06001841
Simon Glass6c435622022-07-11 19:03:56 -06001842 msg = 'Completed: %d total built' % self.count
1843 if self.already_done:
1844 msg += ' (%d previously' % self.already_done
1845 if self.already_done != self.count:
1846 msg += ', %d newly' % (self.count - self.already_done)
1847 msg += ')'
1848 duration = datetime.now() - self._start_time
1849 if duration > timedelta(microseconds=1000000):
1850 if duration.microseconds >= 500000:
1851 duration = duration + timedelta(seconds=1)
1852 duration = duration - timedelta(microseconds=duration.microseconds)
1853 rate = float(self.count) / duration.total_seconds()
1854 msg += ', duration %s, rate %1.2f' % (duration, rate)
1855 tprint(msg)
1856 if self.thread_exceptions:
1857 tprint('Failed: %d thread exceptions' % len(self.thread_exceptions),
1858 colour=self.col.RED)
Simon Glass726ae812020-04-09 15:08:47 -06001859
Simon Glass9bf9a722021-04-11 16:27:27 +12001860 return (self.fail, self.warned, self.thread_exceptions)