blob: 6538a3d296fe937a75ec9a95b731bae344af3d09 [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 Glass131444f2023-02-23 18:18:04 -070022from u_boot_pylib import command
Simon Glassba1b3b92025-02-09 14:26:00 -070023from u_boot_pylib import gitutil
Simon Glass131444f2023-02-23 18:18:04 -070024from 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,
Simon Glassb8ec3002024-12-17 06:26:16 -0700268 dtc_skip=False, build_target=None):
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 Glassb8ec3002024-12-17 06:26:16 -0700318 build_target (str): Build target to use (None to use the default)
Simon Glassc05694f2013-04-03 11:07:16 +0000319 """
320 self.toolchains = toolchains
321 self.base_dir = base_dir
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600322 if work_in_output:
323 self._working_dir = base_dir
324 else:
325 self._working_dir = os.path.join(base_dir, '.bm-work')
Simon Glassc05694f2013-04-03 11:07:16 +0000326 self.threads = []
Simon Glassbc74d942023-07-19 17:49:06 -0600327 self.do_make = make_func or self.make
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900328 self.gnu_make = gnu_make
Simon Glassc05694f2013-04-03 11:07:16 +0000329 self.checkout = checkout
330 self.num_threads = num_threads
331 self.num_jobs = num_jobs
332 self.already_done = 0
333 self.force_build = False
334 self.git_dir = git_dir
335 self._show_unknown = show_unknown
336 self._timestamp_count = 10
337 self._build_period_us = None
338 self._complete_delay = None
339 self._next_delay_update = datetime.now()
Simon Glass7190a172023-09-07 10:00:19 -0600340 self._start_time = None
Simon Glassc05694f2013-04-03 11:07:16 +0000341 self._step = step
Simon Glassbb4dffb2014-08-09 15:33:06 -0600342 self._error_lines = 0
Simon Glasse87bde12014-12-01 17:33:55 -0700343 self.no_subdirs = no_subdirs
Tom Rini3f6f9b22024-07-05 14:34:07 -0600344 self.full_path = full_path
Simon Glass655b6102014-12-01 17:34:07 -0700345 self.verbose_build = verbose_build
Simon Glass739e8512016-11-13 14:25:51 -0700346 self.config_only = config_only
Simon Glasscde5c302016-11-13 14:25:53 -0700347 self.squash_config_y = squash_config_y
348 self.config_filenames = BASE_CONFIG_FILENAMES
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600349 self.work_in_output = work_in_output
Simon Glasse5650a82022-01-22 05:07:33 -0700350 self.adjust_cfg = adjust_cfg
Tom Rini93ebd462022-11-09 19:14:53 -0700351 self.allow_missing = allow_missing
Simon Glass6c435622022-07-11 19:03:56 -0600352 self._ide = False
Simon Glassf6bfcca2023-02-21 12:40:28 -0700353 self.no_lto = no_lto
Simon Glass828d70d2023-02-21 12:40:29 -0700354 self.reproducible_builds = reproducible_builds
Simon Glasscf91d312023-07-19 17:48:52 -0600355 self.force_build = force_build
356 self.force_build_failures = force_build_failures
357 self.force_reconfig = force_reconfig
358 self.in_tree = in_tree
359 self.force_config_on_failure = force_config_on_failure
Simon Glass222825b2024-06-23 11:55:13 -0600360 self.fallback_mrproper = fallback_mrproper
Simon Glass600ede92024-08-15 13:57:45 -0600361 if dtc_skip:
362 self.dtc = shutil.which('dtc')
363 if not self.dtc:
364 raise ValueError('Cannot find dtc')
365 else:
366 self.dtc = None
Simon Glassb8ec3002024-12-17 06:26:16 -0700367 self.build_target = build_target
Simon Glasse5650a82022-01-22 05:07:33 -0700368
Simon Glasscde5c302016-11-13 14:25:53 -0700369 if not self.squash_config_y:
370 self.config_filenames += EXTRA_CONFIG_FILENAMES
Simon Glass146b6022021-10-19 21:43:24 -0600371 self._terminated = False
372 self._restarting_config = False
Simon Glassc05694f2013-04-03 11:07:16 +0000373
Daniel Schwierzeck20e2ea92018-01-26 16:31:05 +0100374 self.warnings_as_errors = warnings_as_errors
Simon Glassc05694f2013-04-03 11:07:16 +0000375 self.col = terminal.Color()
376
Simon Glass03749d42014-08-28 09:43:44 -0600377 self._re_function = re.compile('(.*): In function.*')
378 self._re_files = re.compile('In file included from.*')
Simon Glass1f3a1222024-09-21 19:57:57 +0200379 self._re_warning = re.compile(r'(.*):(\d*):(\d*): warning: .*')
Simon Glass0db94432018-11-06 16:02:11 -0700380 self._re_dtb_warning = re.compile('(.*): Warning .*')
Simon Glass1f3a1222024-09-21 19:57:57 +0200381 self._re_note = re.compile(r'(.*):(\d*):(\d*): note: this is the location of the previous.*')
Simon Glassf4ebfba2020-04-09 15:08:53 -0600382 self._re_migration_warning = re.compile(r'^={21} WARNING ={22}\n.*\n=+\n',
383 re.MULTILINE | re.DOTALL)
Simon Glass03749d42014-08-28 09:43:44 -0600384
Simon Glass9bf9a722021-04-11 16:27:27 +1200385 self.thread_exceptions = []
386 self.test_thread_exceptions = test_thread_exceptions
Simon Glassc635d892021-01-30 22:17:46 -0700387 if self.num_threads:
388 self._single_builder = None
389 self.queue = queue.Queue()
390 self.out_queue = queue.Queue()
391 for i in range(self.num_threads):
Simon Glass9bf9a722021-04-11 16:27:27 +1200392 t = builderthread.BuilderThread(
393 self, i, mrproper, per_board_out_dir,
394 test_exception=test_thread_exceptions)
Simon Glassc635d892021-01-30 22:17:46 -0700395 t.setDaemon(True)
396 t.start()
397 self.threads.append(t)
398
399 t = builderthread.ResultThread(self)
Simon Glassc05694f2013-04-03 11:07:16 +0000400 t.setDaemon(True)
401 t.start()
402 self.threads.append(t)
Simon Glassc635d892021-01-30 22:17:46 -0700403 else:
404 self._single_builder = builderthread.BuilderThread(
405 self, -1, mrproper, per_board_out_dir)
Simon Glassc05694f2013-04-03 11:07:16 +0000406
407 ignore_lines = ['(make.*Waiting for unfinished)', '(Segmentation fault)']
408 self.re_make_err = re.compile('|'.join(ignore_lines))
409
Simon Glass205ac042016-09-18 16:48:37 -0600410 # Handle existing graceful with SIGINT / Ctrl-C
411 signal.signal(signal.SIGINT, self.signal_handler)
412
Simon Glassc05694f2013-04-03 11:07:16 +0000413 def __del__(self):
414 """Get rid of all threads created by the builder"""
415 for t in self.threads:
416 del t
417
Simon Glass205ac042016-09-18 16:48:37 -0600418 def signal_handler(self, signal, frame):
419 sys.exit(1)
420
Simon Glass600ede92024-08-15 13:57:45 -0600421 def make_environment(self, toolchain):
422 """Create the environment to use for building
423
424 Args:
425 toolchain (Toolchain): Toolchain to use for building
426
427 Returns:
428 dict:
429 key (str): Variable name
430 value (str): Variable value
431 """
432 env = toolchain.MakeEnvironment(self.full_path)
433 if self.dtc:
434 env[b'DTC'] = tools.to_bytes(self.dtc)
435 return env
436
Simon Glassbc74d942023-07-19 17:49:06 -0600437 def set_display_options(self, show_errors=False, show_sizes=False,
Simon Glass3394c9f2014-08-28 09:43:43 -0600438 show_detail=False, show_bloat=False,
Alex Kiernan4059e302018-05-31 04:48:34 +0000439 list_error_boards=False, show_config=False,
Simon Glassf4ebfba2020-04-09 15:08:53 -0600440 show_environment=False, filter_dtb_warnings=False,
Simon Glass6c435622022-07-11 19:03:56 -0600441 filter_migration_warnings=False, ide=False):
Simon Glasseb48bbc2014-08-09 15:33:02 -0600442 """Setup display options for the builder.
443
Simon Glass9ea93812020-04-09 15:08:52 -0600444 Args:
445 show_errors: True to show summarised error/warning info
446 show_sizes: Show size deltas
447 show_detail: Show size delta detail for each board if show_sizes
448 show_bloat: Show detail for each function
449 list_error_boards: Show the boards which caused each error/warning
450 show_config: Show config deltas
451 show_environment: Show environment deltas
452 filter_dtb_warnings: Filter out any warnings from the device-tree
453 compiler
Simon Glassf4ebfba2020-04-09 15:08:53 -0600454 filter_migration_warnings: Filter out any warnings about migrating
455 a board to driver model
Simon Glass6c435622022-07-11 19:03:56 -0600456 ide: Create output that can be parsed by an IDE. There is no '+' prefix on
457 error lines and output on stderr stays on stderr.
Simon Glasseb48bbc2014-08-09 15:33:02 -0600458 """
459 self._show_errors = show_errors
460 self._show_sizes = show_sizes
461 self._show_detail = show_detail
462 self._show_bloat = show_bloat
Simon Glass3394c9f2014-08-28 09:43:43 -0600463 self._list_error_boards = list_error_boards
Simon Glassdb17fb82015-02-05 22:06:15 -0700464 self._show_config = show_config
Alex Kiernan4059e302018-05-31 04:48:34 +0000465 self._show_environment = show_environment
Simon Glass9ea93812020-04-09 15:08:52 -0600466 self._filter_dtb_warnings = filter_dtb_warnings
Simon Glassf4ebfba2020-04-09 15:08:53 -0600467 self._filter_migration_warnings = filter_migration_warnings
Simon Glass6c435622022-07-11 19:03:56 -0600468 self._ide = ide
Simon Glasseb48bbc2014-08-09 15:33:02 -0600469
Simon Glassbc74d942023-07-19 17:49:06 -0600470 def _add_timestamp(self):
Simon Glassc05694f2013-04-03 11:07:16 +0000471 """Add a new timestamp to the list and record the build period.
472
473 The build period is the length of time taken to perform a single
474 build (one board, one commit).
475 """
476 now = datetime.now()
477 self._timestamps.append(now)
478 count = len(self._timestamps)
479 delta = self._timestamps[-1] - self._timestamps[0]
480 seconds = delta.total_seconds()
481
482 # If we have enough data, estimate build period (time taken for a
483 # single build) and therefore completion time.
484 if count > 1 and self._next_delay_update < now:
485 self._next_delay_update = now + timedelta(seconds=2)
486 if seconds > 0:
487 self._build_period = float(seconds) / count
488 todo = self.count - self.upto
489 self._complete_delay = timedelta(microseconds=
490 self._build_period * todo * 1000000)
491 # Round it
492 self._complete_delay -= timedelta(
493 microseconds=self._complete_delay.microseconds)
494
495 if seconds > 60:
496 self._timestamps.popleft()
497 count -= 1
498
Simon Glassbc74d942023-07-19 17:49:06 -0600499 def select_commit(self, commit, checkout=True):
Simon Glassc05694f2013-04-03 11:07:16 +0000500 """Checkout the selected commit for this build
501 """
502 self.commit = commit
503 if checkout and self.checkout:
Simon Glass761648b2022-01-29 14:14:11 -0700504 gitutil.checkout(commit.hash)
Simon Glassc05694f2013-04-03 11:07:16 +0000505
Simon Glassbc74d942023-07-19 17:49:06 -0600506 def make(self, commit, brd, stage, cwd, *args, **kwargs):
Simon Glassc05694f2013-04-03 11:07:16 +0000507 """Run make
508
509 Args:
510 commit: Commit object that is being built
511 brd: Board object that is being built
Simon Glassd6c1ec82023-10-26 14:31:10 -0400512 stage: Stage that we are at (mrproper, config, oldconfig, build)
Simon Glassc05694f2013-04-03 11:07:16 +0000513 cwd: Directory where make should be run
514 args: Arguments to pass to make
Simon Glass51f55182025-02-03 09:26:45 -0700515 kwargs: Arguments to pass to command.run_one()
Simon Glassc05694f2013-04-03 11:07:16 +0000516 """
Simon Glass146b6022021-10-19 21:43:24 -0600517
518 def check_output(stream, data):
519 if b'Restart config' in data:
520 self._restarting_config = True
521
522 # If we see 'Restart config' following by multiple errors
523 if self._restarting_config:
524 m = RE_NO_DEFAULT.findall(data)
525
526 # Number of occurences of each Kconfig item
527 multiple = [m.count(val) for val in set(m)]
528
529 # If any of them occur more than once, we have a loop
530 if [val for val in multiple if val > 1]:
531 self._terminated = True
532 return True
533 return False
534
535 self._restarting_config = False
Simon Glass51f55182025-02-03 09:26:45 -0700536 self._terminated = False
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900537 cmd = [self.gnu_make] + list(args)
Simon Glass51f55182025-02-03 09:26:45 -0700538 result = command.run_one(*cmd, capture=True, capture_stderr=True,
539 cwd=cwd, raise_on_error=False,
540 infile='/dev/null', output_func=check_output,
541 **kwargs)
Simon Glass146b6022021-10-19 21:43:24 -0600542
543 if self._terminated:
544 # Try to be helpful
545 result.stderr += '(** did you define an int/hex Kconfig with no default? **)'
546
Simon Glass413f91a2015-02-05 22:06:12 -0700547 if self.verbose_build:
548 result.stdout = '%s\n' % (' '.join(cmd)) + result.stdout
549 result.combined = '%s\n' % (' '.join(cmd)) + result.combined
Simon Glassc05694f2013-04-03 11:07:16 +0000550 return result
551
Simon Glassbc74d942023-07-19 17:49:06 -0600552 def process_result(self, result):
Simon Glassc05694f2013-04-03 11:07:16 +0000553 """Process the result of a build, showing progress information
554
555 Args:
Simon Glass78e418e2014-08-09 15:33:03 -0600556 result: A CommandResult object, which indicates the result for
557 a single build
Simon Glassc05694f2013-04-03 11:07:16 +0000558 """
559 col = terminal.Color()
560 if result:
561 target = result.brd.target
562
Simon Glassc05694f2013-04-03 11:07:16 +0000563 self.upto += 1
564 if result.return_code != 0:
565 self.fail += 1
566 elif result.stderr:
567 self.warned += 1
568 if result.already_done:
569 self.already_done += 1
Simon Glass78e418e2014-08-09 15:33:03 -0600570 if self._verbose:
Simon Glass02811582022-01-29 14:14:18 -0700571 terminal.print_clear()
Simon Glass78e418e2014-08-09 15:33:03 -0600572 boards_selected = {target : result.brd}
Simon Glassbc74d942023-07-19 17:49:06 -0600573 self.reset_result_summary(boards_selected)
574 self.produce_result_summary(result.commit_upto, self.commits,
Simon Glass78e418e2014-08-09 15:33:03 -0600575 boards_selected)
Simon Glassc05694f2013-04-03 11:07:16 +0000576 else:
577 target = '(starting)'
578
579 # Display separate counts for ok, warned and fail
580 ok = self.upto - self.warned - self.fail
Simon Glassf45d3742022-01-29 14:14:17 -0700581 line = '\r' + self.col.build(self.col.GREEN, '%5d' % ok)
582 line += self.col.build(self.col.YELLOW, '%5d' % self.warned)
583 line += self.col.build(self.col.RED, '%5d' % self.fail)
Simon Glassc05694f2013-04-03 11:07:16 +0000584
Simon Glass69c3a8a2020-04-09 15:08:45 -0600585 line += ' /%-5d ' % self.count
586 remaining = self.count - self.upto
587 if remaining:
Simon Glassf45d3742022-01-29 14:14:17 -0700588 line += self.col.build(self.col.MAGENTA, ' -%-5d ' % remaining)
Simon Glass69c3a8a2020-04-09 15:08:45 -0600589 else:
590 line += ' ' * 8
Simon Glassc05694f2013-04-03 11:07:16 +0000591
592 # Add our current completion time estimate
Simon Glassbc74d942023-07-19 17:49:06 -0600593 self._add_timestamp()
Simon Glassc05694f2013-04-03 11:07:16 +0000594 if self._complete_delay:
Simon Glass69c3a8a2020-04-09 15:08:45 -0600595 line += '%s : ' % self._complete_delay
Simon Glassc05694f2013-04-03 11:07:16 +0000596
Simon Glass69c3a8a2020-04-09 15:08:45 -0600597 line += target
Simon Glass6c435622022-07-11 19:03:56 -0600598 if not self._ide:
599 terminal.print_clear()
600 tprint(line, newline=False, limit_to_line=True)
Simon Glassc05694f2013-04-03 11:07:16 +0000601
Simon Glass4cb54682023-07-19 17:49:10 -0600602 def get_output_dir(self, commit_upto):
Simon Glassc05694f2013-04-03 11:07:16 +0000603 """Get the name of the output directory for a commit number
604
605 The output directory is typically .../<branch>/<commit>.
606
607 Args:
608 commit_upto: Commit number to use (0..self.count-1)
609 """
Simon Glasse3c85ab2020-04-17 17:51:34 -0600610 if self.work_in_output:
611 return self._working_dir
612
Simon Glasse87bde12014-12-01 17:33:55 -0700613 commit_dir = None
Simon Glassd326ad72014-08-09 15:32:59 -0600614 if self.commits:
615 commit = self.commits[commit_upto]
616 subject = commit.subject.translate(trans_valid_chars)
Simon Glassbc74d942023-07-19 17:49:06 -0600617 # See _get_output_space_removals() which parses this name
Ovidiu Panaitee8e9cb2020-05-15 09:30:12 +0300618 commit_dir = ('%02d_g%s_%s' % (commit_upto + 1,
619 commit.hash, subject[:20]))
Simon Glasse87bde12014-12-01 17:33:55 -0700620 elif not self.no_subdirs:
Simon Glassd326ad72014-08-09 15:32:59 -0600621 commit_dir = 'current'
Simon Glasse87bde12014-12-01 17:33:55 -0700622 if not commit_dir:
623 return self.base_dir
624 return os.path.join(self.base_dir, commit_dir)
Simon Glassc05694f2013-04-03 11:07:16 +0000625
Simon Glassbc74d942023-07-19 17:49:06 -0600626 def get_build_dir(self, commit_upto, target):
Simon Glassc05694f2013-04-03 11:07:16 +0000627 """Get the name of the build directory for a commit number
628
629 The build directory is typically .../<branch>/<commit>/<target>.
630
631 Args:
632 commit_upto: Commit number to use (0..self.count-1)
633 target: Target name
634 """
Simon Glass4cb54682023-07-19 17:49:10 -0600635 output_dir = self.get_output_dir(commit_upto)
Simon Glasse3c85ab2020-04-17 17:51:34 -0600636 if self.work_in_output:
637 return output_dir
Simon Glassc05694f2013-04-03 11:07:16 +0000638 return os.path.join(output_dir, target)
639
Simon Glassbc74d942023-07-19 17:49:06 -0600640 def get_done_file(self, commit_upto, target):
Simon Glassc05694f2013-04-03 11:07:16 +0000641 """Get the name of the done file for a commit number
642
643 Args:
644 commit_upto: Commit number to use (0..self.count-1)
645 target: Target name
646 """
Simon Glassbc74d942023-07-19 17:49:06 -0600647 return os.path.join(self.get_build_dir(commit_upto, target), 'done')
Simon Glassc05694f2013-04-03 11:07:16 +0000648
Simon Glassbc74d942023-07-19 17:49:06 -0600649 def get_sizes_file(self, commit_upto, target):
Simon Glassc05694f2013-04-03 11:07:16 +0000650 """Get the name of the sizes file for a commit number
651
652 Args:
653 commit_upto: Commit number to use (0..self.count-1)
654 target: Target name
655 """
Simon Glassbc74d942023-07-19 17:49:06 -0600656 return os.path.join(self.get_build_dir(commit_upto, target), 'sizes')
Simon Glassc05694f2013-04-03 11:07:16 +0000657
Simon Glassbc74d942023-07-19 17:49:06 -0600658 def get_func_sizes_file(self, commit_upto, target, elf_fname):
Simon Glassc05694f2013-04-03 11:07:16 +0000659 """Get the name of the funcsizes file for a commit number and ELF file
660
661 Args:
662 commit_upto: Commit number to use (0..self.count-1)
663 target: Target name
664 elf_fname: Filename of elf image
665 """
Simon Glassbc74d942023-07-19 17:49:06 -0600666 return os.path.join(self.get_build_dir(commit_upto, target),
Simon Glassc05694f2013-04-03 11:07:16 +0000667 '%s.sizes' % elf_fname.replace('/', '-'))
668
Simon Glassbc74d942023-07-19 17:49:06 -0600669 def get_objdump_file(self, commit_upto, target, elf_fname):
Simon Glassc05694f2013-04-03 11:07:16 +0000670 """Get the name of the objdump file for a commit number and ELF file
671
672 Args:
673 commit_upto: Commit number to use (0..self.count-1)
674 target: Target name
675 elf_fname: Filename of elf image
676 """
Simon Glassbc74d942023-07-19 17:49:06 -0600677 return os.path.join(self.get_build_dir(commit_upto, target),
Simon Glassc05694f2013-04-03 11:07:16 +0000678 '%s.objdump' % elf_fname.replace('/', '-'))
679
Simon Glassbc74d942023-07-19 17:49:06 -0600680 def get_err_file(self, commit_upto, target):
Simon Glassc05694f2013-04-03 11:07:16 +0000681 """Get the name of the err file for a commit number
682
683 Args:
684 commit_upto: Commit number to use (0..self.count-1)
685 target: Target name
686 """
Simon Glassbc74d942023-07-19 17:49:06 -0600687 output_dir = self.get_build_dir(commit_upto, target)
Simon Glassc05694f2013-04-03 11:07:16 +0000688 return os.path.join(output_dir, 'err')
689
Simon Glassbc74d942023-07-19 17:49:06 -0600690 def filter_errors(self, lines):
Simon Glassc05694f2013-04-03 11:07:16 +0000691 """Filter out errors in which we have no interest
692
693 We should probably use map().
694
695 Args:
696 lines: List of error lines, each a string
697 Returns:
698 New list with only interesting lines included
699 """
700 out_lines = []
Simon Glassf4ebfba2020-04-09 15:08:53 -0600701 if self._filter_migration_warnings:
702 text = '\n'.join(lines)
703 text = self._re_migration_warning.sub('', text)
704 lines = text.splitlines()
Simon Glassc05694f2013-04-03 11:07:16 +0000705 for line in lines:
Simon Glass9ea93812020-04-09 15:08:52 -0600706 if self.re_make_err.search(line):
707 continue
708 if self._filter_dtb_warnings and self._re_dtb_warning.search(line):
709 continue
710 out_lines.append(line)
Simon Glassc05694f2013-04-03 11:07:16 +0000711 return out_lines
712
Simon Glassbc74d942023-07-19 17:49:06 -0600713 def read_func_sizes(self, fname, fd):
Simon Glassc05694f2013-04-03 11:07:16 +0000714 """Read function sizes from the output of 'nm'
715
716 Args:
717 fd: File containing data to read
718 fname: Filename we are reading from (just for errors)
719
720 Returns:
721 Dictionary containing size of each function in bytes, indexed by
722 function name.
723 """
724 sym = {}
725 for line in fd.readlines():
Simon Glass86a2afe2022-07-11 19:04:11 -0600726 line = line.strip()
727 parts = line.split()
728 if line and len(parts) == 3:
729 size, type, name = line.split()
Simon Glassdac73712023-10-23 00:52:43 -0700730 if type in NM_SYMBOL_TYPES:
Simon Glass86a2afe2022-07-11 19:04:11 -0600731 # function names begin with '.' on 64-bit powerpc
732 if '.' in name[1:]:
733 name = 'static.' + name.split('.')[0]
734 sym[name] = sym.get(name, 0) + int(size, 16)
Simon Glassc05694f2013-04-03 11:07:16 +0000735 return sym
736
Simon Glassbc74d942023-07-19 17:49:06 -0600737 def _process_config(self, fname):
Simon Glassdb17fb82015-02-05 22:06:15 -0700738 """Read in a .config, autoconf.mk or autoconf.h file
739
740 This function handles all config file types. It ignores comments and
741 any #defines which don't start with CONFIG_.
742
743 Args:
744 fname: Filename to read
745
746 Returns:
747 Dictionary:
748 key: Config name (e.g. CONFIG_DM)
749 value: Config value (e.g. 1)
750 """
751 config = {}
752 if os.path.exists(fname):
753 with open(fname) as fd:
754 for line in fd:
755 line = line.strip()
756 if line.startswith('#define'):
757 values = line[8:].split(' ', 1)
758 if len(values) > 1:
759 key, value = values
760 else:
761 key = values[0]
Simon Glasscde5c302016-11-13 14:25:53 -0700762 value = '1' if self.squash_config_y else ''
Simon Glassdb17fb82015-02-05 22:06:15 -0700763 if not key.startswith('CONFIG_'):
764 continue
765 elif not line or line[0] in ['#', '*', '/']:
766 continue
767 else:
768 key, value = line.split('=', 1)
Simon Glasscde5c302016-11-13 14:25:53 -0700769 if self.squash_config_y and value == 'y':
770 value = '1'
Simon Glassdb17fb82015-02-05 22:06:15 -0700771 config[key] = value
772 return config
773
Simon Glassbc74d942023-07-19 17:49:06 -0600774 def _process_environment(self, fname):
Alex Kiernan4059e302018-05-31 04:48:34 +0000775 """Read in a uboot.env file
776
777 This function reads in environment variables from a file.
778
779 Args:
780 fname: Filename to read
781
782 Returns:
783 Dictionary:
784 key: environment variable (e.g. bootlimit)
785 value: value of environment variable (e.g. 1)
786 """
787 environment = {}
788 if os.path.exists(fname):
789 with open(fname) as fd:
790 for line in fd.read().split('\0'):
791 try:
792 key, value = line.split('=', 1)
793 environment[key] = value
794 except ValueError:
795 # ignore lines we can't parse
796 pass
797 return environment
798
Simon Glassbc74d942023-07-19 17:49:06 -0600799 def get_build_outcome(self, commit_upto, target, read_func_sizes,
Alex Kiernan4059e302018-05-31 04:48:34 +0000800 read_config, read_environment):
Simon Glassc05694f2013-04-03 11:07:16 +0000801 """Work out the outcome of a build.
802
803 Args:
804 commit_upto: Commit number to check (0..n-1)
805 target: Target board to check
806 read_func_sizes: True to read function size information
Simon Glassdb17fb82015-02-05 22:06:15 -0700807 read_config: True to read .config and autoconf.h files
Alex Kiernan4059e302018-05-31 04:48:34 +0000808 read_environment: True to read uboot.env files
Simon Glassc05694f2013-04-03 11:07:16 +0000809
810 Returns:
811 Outcome object
812 """
Simon Glassbc74d942023-07-19 17:49:06 -0600813 done_file = self.get_done_file(commit_upto, target)
814 sizes_file = self.get_sizes_file(commit_upto, target)
Simon Glassc05694f2013-04-03 11:07:16 +0000815 sizes = {}
816 func_sizes = {}
Simon Glassdb17fb82015-02-05 22:06:15 -0700817 config = {}
Alex Kiernan4059e302018-05-31 04:48:34 +0000818 environment = {}
Simon Glassc05694f2013-04-03 11:07:16 +0000819 if os.path.exists(done_file):
820 with open(done_file, 'r') as fd:
Simon Glass91c54a42019-04-26 19:02:23 -0600821 try:
822 return_code = int(fd.readline())
823 except ValueError:
824 # The file may be empty due to running out of disk space.
825 # Try a rebuild
826 return_code = 1
Simon Glassc05694f2013-04-03 11:07:16 +0000827 err_lines = []
Simon Glassbc74d942023-07-19 17:49:06 -0600828 err_file = self.get_err_file(commit_upto, target)
Simon Glassc05694f2013-04-03 11:07:16 +0000829 if os.path.exists(err_file):
830 with open(err_file, 'r') as fd:
Simon Glassbc74d942023-07-19 17:49:06 -0600831 err_lines = self.filter_errors(fd.readlines())
Simon Glassc05694f2013-04-03 11:07:16 +0000832
833 # Decide whether the build was ok, failed or created warnings
834 if return_code:
835 rc = OUTCOME_ERROR
836 elif len(err_lines):
837 rc = OUTCOME_WARNING
838 else:
839 rc = OUTCOME_OK
840
841 # Convert size information to our simple format
842 if os.path.exists(sizes_file):
843 with open(sizes_file, 'r') as fd:
844 for line in fd.readlines():
845 values = line.split()
846 rodata = 0
847 if len(values) > 6:
848 rodata = int(values[6], 16)
849 size_dict = {
850 'all' : int(values[0]) + int(values[1]) +
851 int(values[2]),
852 'text' : int(values[0]) - rodata,
853 'data' : int(values[1]),
854 'bss' : int(values[2]),
855 'rodata' : rodata,
856 }
857 sizes[values[5]] = size_dict
858
859 if read_func_sizes:
Simon Glassbc74d942023-07-19 17:49:06 -0600860 pattern = self.get_func_sizes_file(commit_upto, target, '*')
Simon Glassc05694f2013-04-03 11:07:16 +0000861 for fname in glob.glob(pattern):
862 with open(fname, 'r') as fd:
863 dict_name = os.path.basename(fname).replace('.sizes',
864 '')
Simon Glassbc74d942023-07-19 17:49:06 -0600865 func_sizes[dict_name] = self.read_func_sizes(fname, fd)
Simon Glassc05694f2013-04-03 11:07:16 +0000866
Simon Glassdb17fb82015-02-05 22:06:15 -0700867 if read_config:
Simon Glassbc74d942023-07-19 17:49:06 -0600868 output_dir = self.get_build_dir(commit_upto, target)
Simon Glasscde5c302016-11-13 14:25:53 -0700869 for name in self.config_filenames:
Simon Glassdb17fb82015-02-05 22:06:15 -0700870 fname = os.path.join(output_dir, name)
Simon Glassbc74d942023-07-19 17:49:06 -0600871 config[name] = self._process_config(fname)
Simon Glassdb17fb82015-02-05 22:06:15 -0700872
Alex Kiernan4059e302018-05-31 04:48:34 +0000873 if read_environment:
Simon Glassbc74d942023-07-19 17:49:06 -0600874 output_dir = self.get_build_dir(commit_upto, target)
Alex Kiernan4059e302018-05-31 04:48:34 +0000875 fname = os.path.join(output_dir, 'uboot.env')
Simon Glassbc74d942023-07-19 17:49:06 -0600876 environment = self._process_environment(fname)
Alex Kiernan4059e302018-05-31 04:48:34 +0000877
878 return Builder.Outcome(rc, err_lines, sizes, func_sizes, config,
879 environment)
Simon Glassc05694f2013-04-03 11:07:16 +0000880
Alex Kiernan4059e302018-05-31 04:48:34 +0000881 return Builder.Outcome(OUTCOME_UNKNOWN, [], {}, {}, {}, {})
Simon Glassc05694f2013-04-03 11:07:16 +0000882
Simon Glassbc74d942023-07-19 17:49:06 -0600883 def get_result_summary(self, boards_selected, commit_upto, read_func_sizes,
Alex Kiernan4059e302018-05-31 04:48:34 +0000884 read_config, read_environment):
Simon Glassc05694f2013-04-03 11:07:16 +0000885 """Calculate a summary of the results of building a commit.
886
887 Args:
888 board_selected: Dict containing boards to summarise
889 commit_upto: Commit number to summarize (0..self.count-1)
890 read_func_sizes: True to read function size information
Simon Glassdb17fb82015-02-05 22:06:15 -0700891 read_config: True to read .config and autoconf.h files
Alex Kiernan4059e302018-05-31 04:48:34 +0000892 read_environment: True to read uboot.env files
Simon Glassc05694f2013-04-03 11:07:16 +0000893
894 Returns:
895 Tuple:
Simon Glass6c435622022-07-11 19:03:56 -0600896 Dict containing boards which built this commit:
897 key: board.target
898 value: Builder.Outcome object
Simon Glass03749d42014-08-28 09:43:44 -0600899 List containing a summary of error lines
Simon Glass3394c9f2014-08-28 09:43:43 -0600900 Dict keyed by error line, containing a list of the Board
901 objects with that error
Simon Glass03749d42014-08-28 09:43:44 -0600902 List containing a summary of warning lines
903 Dict keyed by error line, containing a list of the Board
904 objects with that warning
Simon Glasscad8abf2015-08-25 21:52:14 -0600905 Dictionary keyed by board.target. Each value is a dictionary:
906 key: filename - e.g. '.config'
Simon Glassdb17fb82015-02-05 22:06:15 -0700907 value is itself a dictionary:
908 key: config name
909 value: config value
Alex Kiernan4059e302018-05-31 04:48:34 +0000910 Dictionary keyed by board.target. Each value is a dictionary:
911 key: environment variable
912 value: value of environment variable
Simon Glassc05694f2013-04-03 11:07:16 +0000913 """
Simon Glassbc74d942023-07-19 17:49:06 -0600914 def add_line(lines_summary, lines_boards, line, board):
Simon Glass03749d42014-08-28 09:43:44 -0600915 line = line.rstrip()
916 if line in lines_boards:
917 lines_boards[line].append(board)
918 else:
919 lines_boards[line] = [board]
920 lines_summary.append(line)
921
Simon Glassc05694f2013-04-03 11:07:16 +0000922 board_dict = {}
923 err_lines_summary = []
Simon Glass3394c9f2014-08-28 09:43:43 -0600924 err_lines_boards = {}
Simon Glass03749d42014-08-28 09:43:44 -0600925 warn_lines_summary = []
926 warn_lines_boards = {}
Simon Glassdb17fb82015-02-05 22:06:15 -0700927 config = {}
Alex Kiernan4059e302018-05-31 04:48:34 +0000928 environment = {}
Simon Glassc05694f2013-04-03 11:07:16 +0000929
Simon Glass8132f982022-07-11 19:03:57 -0600930 for brd in boards_selected.values():
Simon Glassbc74d942023-07-19 17:49:06 -0600931 outcome = self.get_build_outcome(commit_upto, brd.target,
Alex Kiernan4059e302018-05-31 04:48:34 +0000932 read_func_sizes, read_config,
933 read_environment)
Simon Glass8132f982022-07-11 19:03:57 -0600934 board_dict[brd.target] = outcome
Simon Glass03749d42014-08-28 09:43:44 -0600935 last_func = None
936 last_was_warning = False
937 for line in outcome.err_lines:
938 if line:
939 if (self._re_function.match(line) or
940 self._re_files.match(line)):
941 last_func = line
Simon Glass3394c9f2014-08-28 09:43:43 -0600942 else:
Simon Glass0db94432018-11-06 16:02:11 -0700943 is_warning = (self._re_warning.match(line) or
944 self._re_dtb_warning.match(line))
Simon Glass03749d42014-08-28 09:43:44 -0600945 is_note = self._re_note.match(line)
946 if is_warning or (last_was_warning and is_note):
947 if last_func:
Simon Glassbc74d942023-07-19 17:49:06 -0600948 add_line(warn_lines_summary, warn_lines_boards,
Simon Glass8132f982022-07-11 19:03:57 -0600949 last_func, brd)
Simon Glassbc74d942023-07-19 17:49:06 -0600950 add_line(warn_lines_summary, warn_lines_boards,
Simon Glass8132f982022-07-11 19:03:57 -0600951 line, brd)
Simon Glass03749d42014-08-28 09:43:44 -0600952 else:
953 if last_func:
Simon Glassbc74d942023-07-19 17:49:06 -0600954 add_line(err_lines_summary, err_lines_boards,
Simon Glass8132f982022-07-11 19:03:57 -0600955 last_func, brd)
Simon Glassbc74d942023-07-19 17:49:06 -0600956 add_line(err_lines_summary, err_lines_boards,
Simon Glass8132f982022-07-11 19:03:57 -0600957 line, brd)
Simon Glass03749d42014-08-28 09:43:44 -0600958 last_was_warning = is_warning
959 last_func = None
Simon Glass8132f982022-07-11 19:03:57 -0600960 tconfig = Config(self.config_filenames, brd.target)
Simon Glasscde5c302016-11-13 14:25:53 -0700961 for fname in self.config_filenames:
Simon Glassdb17fb82015-02-05 22:06:15 -0700962 if outcome.config:
Simon Glassc78ed662019-10-31 07:42:53 -0600963 for key, value in outcome.config[fname].items():
Simon Glassbc74d942023-07-19 17:49:06 -0600964 tconfig.add(fname, key, value)
Simon Glass8132f982022-07-11 19:03:57 -0600965 config[brd.target] = tconfig
Simon Glassdb17fb82015-02-05 22:06:15 -0700966
Simon Glass8132f982022-07-11 19:03:57 -0600967 tenvironment = Environment(brd.target)
Alex Kiernan4059e302018-05-31 04:48:34 +0000968 if outcome.environment:
Simon Glassc78ed662019-10-31 07:42:53 -0600969 for key, value in outcome.environment.items():
Simon Glassbc74d942023-07-19 17:49:06 -0600970 tenvironment.add(key, value)
Simon Glass8132f982022-07-11 19:03:57 -0600971 environment[brd.target] = tenvironment
Alex Kiernan4059e302018-05-31 04:48:34 +0000972
Simon Glass03749d42014-08-28 09:43:44 -0600973 return (board_dict, err_lines_summary, err_lines_boards,
Alex Kiernan4059e302018-05-31 04:48:34 +0000974 warn_lines_summary, warn_lines_boards, config, environment)
Simon Glassc05694f2013-04-03 11:07:16 +0000975
Simon Glassbc74d942023-07-19 17:49:06 -0600976 def add_outcome(self, board_dict, arch_list, changes, char, color):
Simon Glassc05694f2013-04-03 11:07:16 +0000977 """Add an output to our list of outcomes for each architecture
978
979 This simple function adds failing boards (changes) to the
980 relevant architecture string, so we can print the results out
981 sorted by architecture.
982
983 Args:
984 board_dict: Dict containing all boards
985 arch_list: Dict keyed by arch name. Value is a string containing
986 a list of board names which failed for that arch.
987 changes: List of boards to add to arch_list
988 color: terminal.Colour object
989 """
990 done_arch = {}
991 for target in changes:
992 if target in board_dict:
993 arch = board_dict[target].arch
994 else:
995 arch = 'unknown'
Simon Glassf45d3742022-01-29 14:14:17 -0700996 str = self.col.build(color, ' ' + target)
Simon Glassc05694f2013-04-03 11:07:16 +0000997 if not arch in done_arch:
Simon Glassf45d3742022-01-29 14:14:17 -0700998 str = ' %s %s' % (self.col.build(color, char), str)
Simon Glassc05694f2013-04-03 11:07:16 +0000999 done_arch[arch] = True
1000 if not arch in arch_list:
1001 arch_list[arch] = str
1002 else:
1003 arch_list[arch] += str
1004
1005
Simon Glassbc74d942023-07-19 17:49:06 -06001006 def colour_num(self, num):
Simon Glassc05694f2013-04-03 11:07:16 +00001007 color = self.col.RED if num > 0 else self.col.GREEN
1008 if num == 0:
1009 return '0'
Simon Glassf45d3742022-01-29 14:14:17 -07001010 return self.col.build(color, str(num))
Simon Glassc05694f2013-04-03 11:07:16 +00001011
Simon Glassbc74d942023-07-19 17:49:06 -06001012 def reset_result_summary(self, board_selected):
Simon Glassc05694f2013-04-03 11:07:16 +00001013 """Reset the results summary ready for use.
1014
1015 Set up the base board list to be all those selected, and set the
1016 error lines to empty.
1017
Simon Glassbc74d942023-07-19 17:49:06 -06001018 Following this, calls to print_result_summary() will use this
Simon Glassc05694f2013-04-03 11:07:16 +00001019 information to work out what has changed.
1020
1021 Args:
1022 board_selected: Dict containing boards to summarise, keyed by
1023 board.target
1024 """
1025 self._base_board_dict = {}
Simon Glass8132f982022-07-11 19:03:57 -06001026 for brd in board_selected:
1027 self._base_board_dict[brd] = Builder.Outcome(0, [], [], {}, {}, {})
Simon Glassc05694f2013-04-03 11:07:16 +00001028 self._base_err_lines = []
Simon Glass03749d42014-08-28 09:43:44 -06001029 self._base_warn_lines = []
1030 self._base_err_line_boards = {}
1031 self._base_warn_line_boards = {}
Simon Glasscad8abf2015-08-25 21:52:14 -06001032 self._base_config = None
Alex Kiernan4059e302018-05-31 04:48:34 +00001033 self._base_environment = None
Simon Glassc05694f2013-04-03 11:07:16 +00001034
Simon Glassbc74d942023-07-19 17:49:06 -06001035 def print_func_size_detail(self, fname, old, new):
Simon Glassc05694f2013-04-03 11:07:16 +00001036 grow, shrink, add, remove, up, down = 0, 0, 0, 0, 0, 0
1037 delta, common = [], {}
1038
1039 for a in old:
1040 if a in new:
1041 common[a] = 1
1042
1043 for name in old:
1044 if name not in common:
1045 remove += 1
1046 down += old[name]
1047 delta.append([-old[name], name])
1048
1049 for name in new:
1050 if name not in common:
1051 add += 1
1052 up += new[name]
1053 delta.append([new[name], name])
1054
1055 for name in common:
1056 diff = new.get(name, 0) - old.get(name, 0)
1057 if diff > 0:
1058 grow, up = grow + 1, up + diff
1059 elif diff < 0:
1060 shrink, down = shrink + 1, down - diff
1061 delta.append([diff, name])
1062
1063 delta.sort()
1064 delta.reverse()
1065
1066 args = [add, -remove, grow, -shrink, up, -down, up - down]
Tom Rini0b48cd62017-05-22 13:48:52 -04001067 if max(args) == 0 and min(args) == 0:
Simon Glassc05694f2013-04-03 11:07:16 +00001068 return
Simon Glassbc74d942023-07-19 17:49:06 -06001069 args = [self.colour_num(x) for x in args]
Simon Glassc05694f2013-04-03 11:07:16 +00001070 indent = ' ' * 15
Simon Glass02811582022-01-29 14:14:18 -07001071 tprint('%s%s: add: %s/%s, grow: %s/%s bytes: %s/%s (%s)' %
Simon Glassf45d3742022-01-29 14:14:17 -07001072 tuple([indent, self.col.build(self.col.YELLOW, fname)] + args))
Simon Glass02811582022-01-29 14:14:18 -07001073 tprint('%s %-38s %7s %7s %+7s' % (indent, 'function', 'old', 'new',
Simon Glass4433aa92014-09-05 19:00:07 -06001074 'delta'))
Simon Glassc05694f2013-04-03 11:07:16 +00001075 for diff, name in delta:
1076 if diff:
1077 color = self.col.RED if diff > 0 else self.col.GREEN
1078 msg = '%s %-38s %7s %7s %+7d' % (indent, name,
1079 old.get(name, '-'), new.get(name,'-'), diff)
Simon Glass02811582022-01-29 14:14:18 -07001080 tprint(msg, colour=color)
Simon Glassc05694f2013-04-03 11:07:16 +00001081
1082
Simon Glassbc74d942023-07-19 17:49:06 -06001083 def print_size_detail(self, target_list, show_bloat):
Simon Glassc05694f2013-04-03 11:07:16 +00001084 """Show details size information for each board
1085
1086 Args:
1087 target_list: List of targets, each a dict containing:
1088 'target': Target name
1089 'total_diff': Total difference in bytes across all areas
1090 <part_name>: Difference for that part
1091 show_bloat: Show detail for each function
1092 """
1093 targets_by_diff = sorted(target_list, reverse=True,
1094 key=lambda x: x['_total_diff'])
1095 for result in targets_by_diff:
1096 printed_target = False
1097 for name in sorted(result):
1098 diff = result[name]
1099 if name.startswith('_'):
1100 continue
Simon Glassd3d3a102025-02-19 08:11:16 -07001101 colour = self.col.RED if diff > 0 else self.col.GREEN
Simon Glassc05694f2013-04-03 11:07:16 +00001102 msg = ' %s %+d' % (name, diff)
1103 if not printed_target:
Simon Glass02811582022-01-29 14:14:18 -07001104 tprint('%10s %-15s:' % ('', result['_target']),
Simon Glass4433aa92014-09-05 19:00:07 -06001105 newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001106 printed_target = True
Simon Glassd3d3a102025-02-19 08:11:16 -07001107 tprint(msg, colour=colour, newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001108 if printed_target:
Simon Glass02811582022-01-29 14:14:18 -07001109 tprint()
Simon Glassc05694f2013-04-03 11:07:16 +00001110 if show_bloat:
1111 target = result['_target']
1112 outcome = result['_outcome']
1113 base_outcome = self._base_board_dict[target]
1114 for fname in outcome.func_sizes:
Simon Glassbc74d942023-07-19 17:49:06 -06001115 self.print_func_size_detail(fname,
Simon Glassc05694f2013-04-03 11:07:16 +00001116 base_outcome.func_sizes[fname],
1117 outcome.func_sizes[fname])
1118
1119
Simon Glassbc74d942023-07-19 17:49:06 -06001120 def print_size_summary(self, board_selected, board_dict, show_detail,
Simon Glassc05694f2013-04-03 11:07:16 +00001121 show_bloat):
1122 """Print a summary of image sizes broken down by section.
1123
1124 The summary takes the form of one line per architecture. The
1125 line contains deltas for each of the sections (+ means the section
Flavio Suligoie5e3c112020-01-29 09:56:05 +01001126 got bigger, - means smaller). The numbers are the average number
Simon Glassc05694f2013-04-03 11:07:16 +00001127 of bytes that a board in this section increased by.
1128
1129 For example:
1130 powerpc: (622 boards) text -0.0
1131 arm: (285 boards) text -0.0
Simon Glassc05694f2013-04-03 11:07:16 +00001132
1133 Args:
1134 board_selected: Dict containing boards to summarise, keyed by
1135 board.target
1136 board_dict: Dict containing boards for which we built this
1137 commit, keyed by board.target. The value is an Outcome object.
Simon Glassb4002462020-03-18 09:42:43 -06001138 show_detail: Show size delta detail for each board
Simon Glassc05694f2013-04-03 11:07:16 +00001139 show_bloat: Show detail for each function
1140 """
1141 arch_list = {}
1142 arch_count = {}
1143
1144 # Calculate changes in size for different image parts
1145 # The previous sizes are in Board.sizes, for each board
1146 for target in board_dict:
1147 if target not in board_selected:
1148 continue
1149 base_sizes = self._base_board_dict[target].sizes
1150 outcome = board_dict[target]
1151 sizes = outcome.sizes
1152
1153 # Loop through the list of images, creating a dict of size
1154 # changes for each image/part. We end up with something like
1155 # {'target' : 'snapper9g45, 'data' : 5, 'u-boot-spl:text' : -4}
1156 # which means that U-Boot data increased by 5 bytes and SPL
1157 # text decreased by 4.
1158 err = {'_target' : target}
1159 for image in sizes:
1160 if image in base_sizes:
1161 base_image = base_sizes[image]
1162 # Loop through the text, data, bss parts
1163 for part in sorted(sizes[image]):
1164 diff = sizes[image][part] - base_image[part]
1165 col = None
1166 if diff:
1167 if image == 'u-boot':
1168 name = part
1169 else:
1170 name = image + ':' + part
1171 err[name] = diff
1172 arch = board_selected[target].arch
1173 if not arch in arch_count:
1174 arch_count[arch] = 1
1175 else:
1176 arch_count[arch] += 1
1177 if not sizes:
1178 pass # Only add to our list when we have some stats
1179 elif not arch in arch_list:
1180 arch_list[arch] = [err]
1181 else:
1182 arch_list[arch].append(err)
1183
1184 # We now have a list of image size changes sorted by arch
1185 # Print out a summary of these
Simon Glassc78ed662019-10-31 07:42:53 -06001186 for arch, target_list in arch_list.items():
Simon Glassc05694f2013-04-03 11:07:16 +00001187 # Get total difference for each type
1188 totals = {}
1189 for result in target_list:
1190 total = 0
Simon Glassc78ed662019-10-31 07:42:53 -06001191 for name, diff in result.items():
Simon Glassc05694f2013-04-03 11:07:16 +00001192 if name.startswith('_'):
1193 continue
1194 total += diff
1195 if name in totals:
1196 totals[name] += diff
1197 else:
1198 totals[name] = diff
1199 result['_total_diff'] = total
1200 result['_outcome'] = board_dict[result['_target']]
1201
1202 count = len(target_list)
1203 printed_arch = False
1204 for name in sorted(totals):
1205 diff = totals[name]
1206 if diff:
1207 # Display the average difference in this name for this
1208 # architecture
1209 avg_diff = float(diff) / count
1210 color = self.col.RED if avg_diff > 0 else self.col.GREEN
1211 msg = ' %s %+1.1f' % (name, avg_diff)
1212 if not printed_arch:
Simon Glass02811582022-01-29 14:14:18 -07001213 tprint('%10s: (for %d/%d boards)' % (arch, count,
Simon Glass4433aa92014-09-05 19:00:07 -06001214 arch_count[arch]), newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001215 printed_arch = True
Simon Glass02811582022-01-29 14:14:18 -07001216 tprint(msg, colour=color, newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001217
1218 if printed_arch:
Simon Glass02811582022-01-29 14:14:18 -07001219 tprint()
Simon Glassc05694f2013-04-03 11:07:16 +00001220 if show_detail:
Simon Glassbc74d942023-07-19 17:49:06 -06001221 self.print_size_detail(target_list, show_bloat)
Simon Glassc05694f2013-04-03 11:07:16 +00001222
1223
Simon Glassbc74d942023-07-19 17:49:06 -06001224 def print_result_summary(self, board_selected, board_dict, err_lines,
Simon Glass03749d42014-08-28 09:43:44 -06001225 err_line_boards, warn_lines, warn_line_boards,
Alex Kiernan4059e302018-05-31 04:48:34 +00001226 config, environment, show_sizes, show_detail,
1227 show_bloat, show_config, show_environment):
Simon Glassc05694f2013-04-03 11:07:16 +00001228 """Compare results with the base results and display delta.
1229
1230 Only boards mentioned in board_selected will be considered. This
1231 function is intended to be called repeatedly with the results of
1232 each commit. It therefore shows a 'diff' between what it saw in
1233 the last call and what it sees now.
1234
1235 Args:
1236 board_selected: Dict containing boards to summarise, keyed by
1237 board.target
1238 board_dict: Dict containing boards for which we built this
1239 commit, keyed by board.target. The value is an Outcome object.
1240 err_lines: A list of errors for this commit, or [] if there is
1241 none, or we don't want to print errors
Simon Glass3394c9f2014-08-28 09:43:43 -06001242 err_line_boards: Dict keyed by error line, containing a list of
1243 the Board objects with that error
Simon Glass03749d42014-08-28 09:43:44 -06001244 warn_lines: A list of warnings for this commit, or [] if there is
1245 none, or we don't want to print errors
1246 warn_line_boards: Dict keyed by warning line, containing a list of
1247 the Board objects with that warning
Simon Glassdb17fb82015-02-05 22:06:15 -07001248 config: Dictionary keyed by filename - e.g. '.config'. Each
1249 value is itself a dictionary:
1250 key: config name
1251 value: config value
Alex Kiernan4059e302018-05-31 04:48:34 +00001252 environment: Dictionary keyed by environment variable, Each
1253 value is the value of environment variable.
Simon Glassc05694f2013-04-03 11:07:16 +00001254 show_sizes: Show image size deltas
Simon Glassb4002462020-03-18 09:42:43 -06001255 show_detail: Show size delta detail for each board if show_sizes
Simon Glassc05694f2013-04-03 11:07:16 +00001256 show_bloat: Show detail for each function
Simon Glassdb17fb82015-02-05 22:06:15 -07001257 show_config: Show config changes
Alex Kiernan4059e302018-05-31 04:48:34 +00001258 show_environment: Show environment changes
Simon Glassc05694f2013-04-03 11:07:16 +00001259 """
Simon Glassbc74d942023-07-19 17:49:06 -06001260 def _board_list(line, line_boards):
Simon Glass3394c9f2014-08-28 09:43:43 -06001261 """Helper function to get a line of boards containing a line
1262
1263 Args:
1264 line: Error line to search for
Simon Glassde0fefc2020-04-09 15:08:36 -06001265 line_boards: boards to search, each a Board
Simon Glass3394c9f2014-08-28 09:43:43 -06001266 Return:
Simon Glassde0fefc2020-04-09 15:08:36 -06001267 List of boards with that error line, or [] if the user has not
1268 requested such a list
Simon Glass3394c9f2014-08-28 09:43:43 -06001269 """
Simon Glass5df45222022-07-11 19:04:00 -06001270 brds = []
Simon Glassde0fefc2020-04-09 15:08:36 -06001271 board_set = set()
Simon Glass3394c9f2014-08-28 09:43:43 -06001272 if self._list_error_boards:
Simon Glass8132f982022-07-11 19:03:57 -06001273 for brd in line_boards[line]:
1274 if not brd in board_set:
Simon Glass5df45222022-07-11 19:04:00 -06001275 brds.append(brd)
Simon Glass8132f982022-07-11 19:03:57 -06001276 board_set.add(brd)
Simon Glass5df45222022-07-11 19:04:00 -06001277 return brds
Simon Glass3394c9f2014-08-28 09:43:43 -06001278
Simon Glassbc74d942023-07-19 17:49:06 -06001279 def _calc_error_delta(base_lines, base_line_boards, lines, line_boards,
Simon Glass03749d42014-08-28 09:43:44 -06001280 char):
Simon Glassde0fefc2020-04-09 15:08:36 -06001281 """Calculate the required output based on changes in errors
1282
1283 Args:
1284 base_lines: List of errors/warnings for previous commit
1285 base_line_boards: Dict keyed by error line, containing a list
1286 of the Board objects with that error in the previous commit
1287 lines: List of errors/warning for this commit, each a str
1288 line_boards: Dict keyed by error line, containing a list
1289 of the Board objects with that error in this commit
1290 char: Character representing error ('') or warning ('w'). The
1291 broken ('+') or fixed ('-') characters are added in this
1292 function
1293
1294 Returns:
1295 Tuple
1296 List of ErrLine objects for 'better' lines
1297 List of ErrLine objects for 'worse' lines
1298 """
Simon Glass03749d42014-08-28 09:43:44 -06001299 better_lines = []
1300 worse_lines = []
1301 for line in lines:
1302 if line not in base_lines:
Simon Glassbc74d942023-07-19 17:49:06 -06001303 errline = ErrLine(char + '+', _board_list(line, line_boards),
Simon Glassde0fefc2020-04-09 15:08:36 -06001304 line)
1305 worse_lines.append(errline)
Simon Glass03749d42014-08-28 09:43:44 -06001306 for line in base_lines:
1307 if line not in lines:
Simon Glassde0fefc2020-04-09 15:08:36 -06001308 errline = ErrLine(char + '-',
Simon Glassbc74d942023-07-19 17:49:06 -06001309 _board_list(line, base_line_boards), line)
Simon Glassde0fefc2020-04-09 15:08:36 -06001310 better_lines.append(errline)
Simon Glass03749d42014-08-28 09:43:44 -06001311 return better_lines, worse_lines
1312
Simon Glassbc74d942023-07-19 17:49:06 -06001313 def _calc_config(delta, name, config):
Simon Glassdb17fb82015-02-05 22:06:15 -07001314 """Calculate configuration changes
1315
1316 Args:
1317 delta: Type of the delta, e.g. '+'
1318 name: name of the file which changed (e.g. .config)
1319 config: configuration change dictionary
1320 key: config name
1321 value: config value
1322 Returns:
1323 String containing the configuration changes which can be
1324 printed
1325 """
1326 out = ''
1327 for key in sorted(config.keys()):
1328 out += '%s=%s ' % (key, config[key])
Simon Glasscad8abf2015-08-25 21:52:14 -06001329 return '%s %s: %s' % (delta, name, out)
Simon Glassdb17fb82015-02-05 22:06:15 -07001330
Simon Glassbc74d942023-07-19 17:49:06 -06001331 def _add_config(lines, name, config_plus, config_minus, config_change):
Simon Glasscad8abf2015-08-25 21:52:14 -06001332 """Add changes in configuration to a list
Simon Glassdb17fb82015-02-05 22:06:15 -07001333
1334 Args:
Simon Glasscad8abf2015-08-25 21:52:14 -06001335 lines: list to add to
1336 name: config file name
Simon Glassdb17fb82015-02-05 22:06:15 -07001337 config_plus: configurations added, dictionary
1338 key: config name
1339 value: config value
1340 config_minus: configurations removed, dictionary
1341 key: config name
1342 value: config value
1343 config_change: configurations changed, dictionary
1344 key: config name
1345 value: config value
1346 """
1347 if config_plus:
Simon Glassbc74d942023-07-19 17:49:06 -06001348 lines.append(_calc_config('+', name, config_plus))
Simon Glassdb17fb82015-02-05 22:06:15 -07001349 if config_minus:
Simon Glassbc74d942023-07-19 17:49:06 -06001350 lines.append(_calc_config('-', name, config_minus))
Simon Glassdb17fb82015-02-05 22:06:15 -07001351 if config_change:
Simon Glassbc74d942023-07-19 17:49:06 -06001352 lines.append(_calc_config('c', name, config_change))
Simon Glasscad8abf2015-08-25 21:52:14 -06001353
Simon Glassbc74d942023-07-19 17:49:06 -06001354 def _output_config_info(lines):
Simon Glasscad8abf2015-08-25 21:52:14 -06001355 for line in lines:
1356 if not line:
1357 continue
Simon Glassd3d3a102025-02-19 08:11:16 -07001358 col = None
Simon Glasscad8abf2015-08-25 21:52:14 -06001359 if line[0] == '+':
1360 col = self.col.GREEN
1361 elif line[0] == '-':
1362 col = self.col.RED
1363 elif line[0] == 'c':
1364 col = self.col.YELLOW
Simon Glass02811582022-01-29 14:14:18 -07001365 tprint(' ' + line, newline=True, colour=col)
Simon Glasscad8abf2015-08-25 21:52:14 -06001366
Simon Glassbc74d942023-07-19 17:49:06 -06001367 def _output_err_lines(err_lines, colour):
Simon Glassac500222020-04-09 15:08:28 -06001368 """Output the line of error/warning lines, if not empty
1369
1370 Also increments self._error_lines if err_lines not empty
1371
1372 Args:
Simon Glassde0fefc2020-04-09 15:08:36 -06001373 err_lines: List of ErrLine objects, each an error or warning
1374 line, possibly including a list of boards with that
1375 error/warning
Simon Glassac500222020-04-09 15:08:28 -06001376 colour: Colour to use for output
1377 """
1378 if err_lines:
Simon Glassea49f9b2020-04-09 15:08:37 -06001379 out_list = []
Simon Glassde0fefc2020-04-09 15:08:36 -06001380 for line in err_lines:
Simon Glass5df45222022-07-11 19:04:00 -06001381 names = [brd.target for brd in line.brds]
Simon Glass070589b2020-04-09 15:08:38 -06001382 board_str = ' '.join(names) if names else ''
Simon Glassea49f9b2020-04-09 15:08:37 -06001383 if board_str:
Simon Glassf45d3742022-01-29 14:14:17 -07001384 out = self.col.build(colour, line.char + '(')
1385 out += self.col.build(self.col.MAGENTA, board_str,
Simon Glassea49f9b2020-04-09 15:08:37 -06001386 bright=False)
Simon Glassf45d3742022-01-29 14:14:17 -07001387 out += self.col.build(colour, ') %s' % line.errline)
Simon Glassea49f9b2020-04-09 15:08:37 -06001388 else:
Simon Glassf45d3742022-01-29 14:14:17 -07001389 out = self.col.build(colour, line.char + line.errline)
Simon Glassea49f9b2020-04-09 15:08:37 -06001390 out_list.append(out)
Simon Glass02811582022-01-29 14:14:18 -07001391 tprint('\n'.join(out_list))
Simon Glassac500222020-04-09 15:08:28 -06001392 self._error_lines += 1
1393
Simon Glassdb17fb82015-02-05 22:06:15 -07001394
Simon Glass454507f2018-11-06 16:02:12 -07001395 ok_boards = [] # List of boards fixed since last commit
Simon Glass071a1782018-11-06 16:02:13 -07001396 warn_boards = [] # List of boards with warnings since last commit
Simon Glass454507f2018-11-06 16:02:12 -07001397 err_boards = [] # List of new broken boards since last commit
1398 new_boards = [] # List of boards that didn't exist last time
1399 unknown_boards = [] # List of boards that were not built
Simon Glassc05694f2013-04-03 11:07:16 +00001400
1401 for target in board_dict:
1402 if target not in board_selected:
1403 continue
1404
1405 # If the board was built last time, add its outcome to a list
1406 if target in self._base_board_dict:
1407 base_outcome = self._base_board_dict[target].rc
1408 outcome = board_dict[target]
1409 if outcome.rc == OUTCOME_UNKNOWN:
Simon Glass454507f2018-11-06 16:02:12 -07001410 unknown_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001411 elif outcome.rc < base_outcome:
Simon Glass071a1782018-11-06 16:02:13 -07001412 if outcome.rc == OUTCOME_WARNING:
1413 warn_boards.append(target)
1414 else:
1415 ok_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001416 elif outcome.rc > base_outcome:
Simon Glass071a1782018-11-06 16:02:13 -07001417 if outcome.rc == OUTCOME_WARNING:
1418 warn_boards.append(target)
1419 else:
1420 err_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001421 else:
Simon Glass454507f2018-11-06 16:02:12 -07001422 new_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001423
Simon Glassac500222020-04-09 15:08:28 -06001424 # Get a list of errors and warnings that have appeared, and disappeared
Simon Glassbc74d942023-07-19 17:49:06 -06001425 better_err, worse_err = _calc_error_delta(self._base_err_lines,
Simon Glass03749d42014-08-28 09:43:44 -06001426 self._base_err_line_boards, err_lines, err_line_boards, '')
Simon Glassbc74d942023-07-19 17:49:06 -06001427 better_warn, worse_warn = _calc_error_delta(self._base_warn_lines,
Simon Glass03749d42014-08-28 09:43:44 -06001428 self._base_warn_line_boards, warn_lines, warn_line_boards, 'w')
Simon Glassc05694f2013-04-03 11:07:16 +00001429
Simon Glass6c435622022-07-11 19:03:56 -06001430 # For the IDE mode, print out all the output
1431 if self._ide:
1432 outcome = board_dict[target]
1433 for line in outcome.err_lines:
1434 sys.stderr.write(line)
1435
Simon Glassc05694f2013-04-03 11:07:16 +00001436 # Display results by arch
Simon Glass6c435622022-07-11 19:03:56 -06001437 elif any((ok_boards, warn_boards, err_boards, unknown_boards, new_boards,
Simon Glass071a1782018-11-06 16:02:13 -07001438 worse_err, better_err, worse_warn, better_warn)):
Simon Glassc05694f2013-04-03 11:07:16 +00001439 arch_list = {}
Simon Glassbc74d942023-07-19 17:49:06 -06001440 self.add_outcome(board_selected, arch_list, ok_boards, '',
Simon Glassc05694f2013-04-03 11:07:16 +00001441 self.col.GREEN)
Simon Glassbc74d942023-07-19 17:49:06 -06001442 self.add_outcome(board_selected, arch_list, warn_boards, 'w+',
Simon Glass071a1782018-11-06 16:02:13 -07001443 self.col.YELLOW)
Simon Glassbc74d942023-07-19 17:49:06 -06001444 self.add_outcome(board_selected, arch_list, err_boards, '+',
Simon Glassc05694f2013-04-03 11:07:16 +00001445 self.col.RED)
Simon Glassbc74d942023-07-19 17:49:06 -06001446 self.add_outcome(board_selected, arch_list, new_boards, '*', self.col.BLUE)
Simon Glassc05694f2013-04-03 11:07:16 +00001447 if self._show_unknown:
Simon Glassbc74d942023-07-19 17:49:06 -06001448 self.add_outcome(board_selected, arch_list, unknown_boards, '?',
Simon Glassc05694f2013-04-03 11:07:16 +00001449 self.col.MAGENTA)
Simon Glassc78ed662019-10-31 07:42:53 -06001450 for arch, target_list in arch_list.items():
Simon Glass02811582022-01-29 14:14:18 -07001451 tprint('%10s: %s' % (arch, target_list))
Simon Glassbb4dffb2014-08-09 15:33:06 -06001452 self._error_lines += 1
Simon Glassbc74d942023-07-19 17:49:06 -06001453 _output_err_lines(better_err, colour=self.col.GREEN)
1454 _output_err_lines(worse_err, colour=self.col.RED)
1455 _output_err_lines(better_warn, colour=self.col.CYAN)
1456 _output_err_lines(worse_warn, colour=self.col.YELLOW)
Simon Glassc05694f2013-04-03 11:07:16 +00001457
1458 if show_sizes:
Simon Glassbc74d942023-07-19 17:49:06 -06001459 self.print_size_summary(board_selected, board_dict, show_detail,
Simon Glassc05694f2013-04-03 11:07:16 +00001460 show_bloat)
1461
Alex Kiernan4059e302018-05-31 04:48:34 +00001462 if show_environment and self._base_environment:
1463 lines = []
1464
1465 for target in board_dict:
1466 if target not in board_selected:
1467 continue
1468
1469 tbase = self._base_environment[target]
1470 tenvironment = environment[target]
1471 environment_plus = {}
1472 environment_minus = {}
1473 environment_change = {}
1474 base = tbase.environment
Simon Glassc78ed662019-10-31 07:42:53 -06001475 for key, value in tenvironment.environment.items():
Alex Kiernan4059e302018-05-31 04:48:34 +00001476 if key not in base:
1477 environment_plus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001478 for key, value in base.items():
Alex Kiernan4059e302018-05-31 04:48:34 +00001479 if key not in tenvironment.environment:
1480 environment_minus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001481 for key, value in base.items():
Alex Kiernan4059e302018-05-31 04:48:34 +00001482 new_value = tenvironment.environment.get(key)
1483 if new_value and value != new_value:
1484 desc = '%s -> %s' % (value, new_value)
1485 environment_change[key] = desc
1486
Simon Glassbc74d942023-07-19 17:49:06 -06001487 _add_config(lines, target, environment_plus, environment_minus,
Alex Kiernan4059e302018-05-31 04:48:34 +00001488 environment_change)
1489
Simon Glassbc74d942023-07-19 17:49:06 -06001490 _output_config_info(lines)
Alex Kiernan4059e302018-05-31 04:48:34 +00001491
Simon Glasscad8abf2015-08-25 21:52:14 -06001492 if show_config and self._base_config:
1493 summary = {}
1494 arch_config_plus = {}
1495 arch_config_minus = {}
1496 arch_config_change = {}
1497 arch_list = []
1498
1499 for target in board_dict:
1500 if target not in board_selected:
1501 continue
1502 arch = board_selected[target].arch
1503 if arch not in arch_list:
1504 arch_list.append(arch)
1505
1506 for arch in arch_list:
1507 arch_config_plus[arch] = {}
1508 arch_config_minus[arch] = {}
1509 arch_config_change[arch] = {}
Simon Glasscde5c302016-11-13 14:25:53 -07001510 for name in self.config_filenames:
Simon Glasscad8abf2015-08-25 21:52:14 -06001511 arch_config_plus[arch][name] = {}
1512 arch_config_minus[arch][name] = {}
1513 arch_config_change[arch][name] = {}
1514
1515 for target in board_dict:
1516 if target not in board_selected:
Simon Glassdb17fb82015-02-05 22:06:15 -07001517 continue
Simon Glasscad8abf2015-08-25 21:52:14 -06001518
1519 arch = board_selected[target].arch
1520
1521 all_config_plus = {}
1522 all_config_minus = {}
1523 all_config_change = {}
1524 tbase = self._base_config[target]
1525 tconfig = config[target]
1526 lines = []
Simon Glasscde5c302016-11-13 14:25:53 -07001527 for name in self.config_filenames:
Simon Glasscad8abf2015-08-25 21:52:14 -06001528 if not tconfig.config[name]:
1529 continue
1530 config_plus = {}
1531 config_minus = {}
1532 config_change = {}
1533 base = tbase.config[name]
Simon Glassc78ed662019-10-31 07:42:53 -06001534 for key, value in tconfig.config[name].items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001535 if key not in base:
1536 config_plus[key] = value
1537 all_config_plus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001538 for key, value in base.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001539 if key not in tconfig.config[name]:
1540 config_minus[key] = value
1541 all_config_minus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001542 for key, value in base.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001543 new_value = tconfig.config.get(key)
1544 if new_value and value != new_value:
1545 desc = '%s -> %s' % (value, new_value)
1546 config_change[key] = desc
1547 all_config_change[key] = desc
1548
1549 arch_config_plus[arch][name].update(config_plus)
1550 arch_config_minus[arch][name].update(config_minus)
1551 arch_config_change[arch][name].update(config_change)
1552
Simon Glassbc74d942023-07-19 17:49:06 -06001553 _add_config(lines, name, config_plus, config_minus,
Simon Glasscad8abf2015-08-25 21:52:14 -06001554 config_change)
Simon Glassbc74d942023-07-19 17:49:06 -06001555 _add_config(lines, 'all', all_config_plus, all_config_minus,
Simon Glasscad8abf2015-08-25 21:52:14 -06001556 all_config_change)
1557 summary[target] = '\n'.join(lines)
1558
1559 lines_by_target = {}
Simon Glassc78ed662019-10-31 07:42:53 -06001560 for target, lines in summary.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001561 if lines in lines_by_target:
1562 lines_by_target[lines].append(target)
1563 else:
1564 lines_by_target[lines] = [target]
1565
1566 for arch in arch_list:
1567 lines = []
1568 all_plus = {}
1569 all_minus = {}
1570 all_change = {}
Simon Glasscde5c302016-11-13 14:25:53 -07001571 for name in self.config_filenames:
Simon Glasscad8abf2015-08-25 21:52:14 -06001572 all_plus.update(arch_config_plus[arch][name])
1573 all_minus.update(arch_config_minus[arch][name])
1574 all_change.update(arch_config_change[arch][name])
Simon Glassbc74d942023-07-19 17:49:06 -06001575 _add_config(lines, name, arch_config_plus[arch][name],
Simon Glasscad8abf2015-08-25 21:52:14 -06001576 arch_config_minus[arch][name],
1577 arch_config_change[arch][name])
Simon Glassbc74d942023-07-19 17:49:06 -06001578 _add_config(lines, 'all', all_plus, all_minus, all_change)
Simon Glasscad8abf2015-08-25 21:52:14 -06001579 #arch_summary[target] = '\n'.join(lines)
1580 if lines:
Simon Glass02811582022-01-29 14:14:18 -07001581 tprint('%s:' % arch)
Simon Glassbc74d942023-07-19 17:49:06 -06001582 _output_config_info(lines)
Simon Glasscad8abf2015-08-25 21:52:14 -06001583
Simon Glassc78ed662019-10-31 07:42:53 -06001584 for lines, targets in lines_by_target.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001585 if not lines:
1586 continue
Simon Glass02811582022-01-29 14:14:18 -07001587 tprint('%s :' % ' '.join(sorted(targets)))
Simon Glassbc74d942023-07-19 17:49:06 -06001588 _output_config_info(lines.split('\n'))
Simon Glasscad8abf2015-08-25 21:52:14 -06001589
Simon Glassdb17fb82015-02-05 22:06:15 -07001590
Simon Glassc05694f2013-04-03 11:07:16 +00001591 # Save our updated information for the next call to this function
1592 self._base_board_dict = board_dict
1593 self._base_err_lines = err_lines
Simon Glass03749d42014-08-28 09:43:44 -06001594 self._base_warn_lines = warn_lines
1595 self._base_err_line_boards = err_line_boards
1596 self._base_warn_line_boards = warn_line_boards
Simon Glassdb17fb82015-02-05 22:06:15 -07001597 self._base_config = config
Alex Kiernan4059e302018-05-31 04:48:34 +00001598 self._base_environment = environment
Simon Glassc05694f2013-04-03 11:07:16 +00001599
1600 # Get a list of boards that did not get built, if needed
1601 not_built = []
Simon Glass8132f982022-07-11 19:03:57 -06001602 for brd in board_selected:
1603 if not brd in board_dict:
1604 not_built.append(brd)
Simon Glassc05694f2013-04-03 11:07:16 +00001605 if not_built:
Simon Glass02811582022-01-29 14:14:18 -07001606 tprint("Boards not built (%d): %s" % (len(not_built),
Simon Glass4433aa92014-09-05 19:00:07 -06001607 ', '.join(not_built)))
Simon Glassc05694f2013-04-03 11:07:16 +00001608
Simon Glassbc74d942023-07-19 17:49:06 -06001609 def produce_result_summary(self, commit_upto, commits, board_selected):
Simon Glass03749d42014-08-28 09:43:44 -06001610 (board_dict, err_lines, err_line_boards, warn_lines,
Simon Glassbc74d942023-07-19 17:49:06 -06001611 warn_line_boards, config, environment) = self.get_result_summary(
Simon Glass3394c9f2014-08-28 09:43:43 -06001612 board_selected, commit_upto,
Simon Glassdb17fb82015-02-05 22:06:15 -07001613 read_func_sizes=self._show_bloat,
Alex Kiernan4059e302018-05-31 04:48:34 +00001614 read_config=self._show_config,
1615 read_environment=self._show_environment)
Simon Glasseb48bbc2014-08-09 15:33:02 -06001616 if commits:
1617 msg = '%02d: %s' % (commit_upto + 1,
1618 commits[commit_upto].subject)
Simon Glass02811582022-01-29 14:14:18 -07001619 tprint(msg, colour=self.col.BLUE)
Simon Glassbc74d942023-07-19 17:49:06 -06001620 self.print_result_summary(board_selected, board_dict,
Simon Glass3394c9f2014-08-28 09:43:43 -06001621 err_lines if self._show_errors else [], err_line_boards,
Simon Glass03749d42014-08-28 09:43:44 -06001622 warn_lines if self._show_errors else [], warn_line_boards,
Alex Kiernan4059e302018-05-31 04:48:34 +00001623 config, environment, self._show_sizes, self._show_detail,
1624 self._show_bloat, self._show_config, self._show_environment)
Simon Glassc05694f2013-04-03 11:07:16 +00001625
Simon Glassbc74d942023-07-19 17:49:06 -06001626 def show_summary(self, commits, board_selected):
Simon Glassc05694f2013-04-03 11:07:16 +00001627 """Show a build summary for U-Boot for a given board list.
1628
1629 Reset the result summary, then repeatedly call GetResultSummary on
1630 each commit's results, then display the differences we see.
1631
1632 Args:
1633 commit: Commit objects to summarise
1634 board_selected: Dict containing boards to summarise
Simon Glassc05694f2013-04-03 11:07:16 +00001635 """
Simon Glassd326ad72014-08-09 15:32:59 -06001636 self.commit_count = len(commits) if commits else 1
Simon Glassc05694f2013-04-03 11:07:16 +00001637 self.commits = commits
Simon Glassbc74d942023-07-19 17:49:06 -06001638 self.reset_result_summary(board_selected)
Simon Glassbb4dffb2014-08-09 15:33:06 -06001639 self._error_lines = 0
Simon Glassc05694f2013-04-03 11:07:16 +00001640
1641 for commit_upto in range(0, self.commit_count, self._step):
Simon Glassbc74d942023-07-19 17:49:06 -06001642 self.produce_result_summary(commit_upto, commits, board_selected)
Simon Glassbb4dffb2014-08-09 15:33:06 -06001643 if not self._error_lines:
Simon Glass02811582022-01-29 14:14:18 -07001644 tprint('(no errors to report)', colour=self.col.GREEN)
Simon Glassc05694f2013-04-03 11:07:16 +00001645
1646
Simon Glassbc74d942023-07-19 17:49:06 -06001647 def setup_build(self, board_selected, commits):
Simon Glassc05694f2013-04-03 11:07:16 +00001648 """Set up ready to start a build.
1649
1650 Args:
1651 board_selected: Selected boards to build
1652 commits: Selected commits to build
1653 """
1654 # First work out how many commits we will build
Simon Glassc78ed662019-10-31 07:42:53 -06001655 count = (self.commit_count + self._step - 1) // self._step
Simon Glassc05694f2013-04-03 11:07:16 +00001656 self.count = len(board_selected) * count
1657 self.upto = self.warned = self.fail = 0
1658 self._timestamps = collections.deque()
1659
Simon Glassbc74d942023-07-19 17:49:06 -06001660 def get_thread_dir(self, thread_num):
Simon Glassc05694f2013-04-03 11:07:16 +00001661 """Get the directory path to the working dir for a thread.
1662
1663 Args:
Simon Glassc635d892021-01-30 22:17:46 -07001664 thread_num: Number of thread to check (-1 for main process, which
1665 is treated as 0)
Simon Glassc05694f2013-04-03 11:07:16 +00001666 """
Simon Glassb6eb8cf2020-03-18 09:42:42 -06001667 if self.work_in_output:
1668 return self._working_dir
Simon Glassc635d892021-01-30 22:17:46 -07001669 return os.path.join(self._working_dir, '%02d' % max(thread_num, 0))
Simon Glassc05694f2013-04-03 11:07:16 +00001670
Simon Glassbc74d942023-07-19 17:49:06 -06001671 def _prepare_thread(self, thread_num, setup_git):
Simon Glassc05694f2013-04-03 11:07:16 +00001672 """Prepare the working directory for a thread.
1673
1674 This clones or fetches the repo into the thread's work directory.
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001675 Optionally, it can create a linked working tree of the repo in the
1676 thread's work directory instead.
Simon Glassc05694f2013-04-03 11:07:16 +00001677
1678 Args:
1679 thread_num: Thread number (0, 1, ...)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001680 setup_git:
1681 'clone' to set up a git clone
1682 'worktree' to set up a git worktree
Simon Glassc05694f2013-04-03 11:07:16 +00001683 """
Simon Glassbc74d942023-07-19 17:49:06 -06001684 thread_dir = self.get_thread_dir(thread_num)
Simon Glassc5077c32023-07-19 17:49:08 -06001685 builderthread.mkdir(thread_dir)
Simon Glassc05694f2013-04-03 11:07:16 +00001686 git_dir = os.path.join(thread_dir, '.git')
1687
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001688 # Create a worktree or a git repo clone for this thread if it
1689 # doesn't already exist
Simon Glassd326ad72014-08-09 15:32:59 -06001690 if setup_git and self.git_dir:
Simon Glassc05694f2013-04-03 11:07:16 +00001691 src_dir = os.path.abspath(self.git_dir)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001692 if os.path.isdir(git_dir):
1693 # This is a clone of the src_dir repo, we can keep using
1694 # it but need to fetch from src_dir.
Simon Glass02811582022-01-29 14:14:18 -07001695 tprint('\rFetching repo for thread %d' % thread_num,
Simon Glass43054932020-04-09 15:08:43 -06001696 newline=False)
Simon Glass761648b2022-01-29 14:14:11 -07001697 gitutil.fetch(git_dir, thread_dir)
Simon Glass02811582022-01-29 14:14:18 -07001698 terminal.print_clear()
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001699 elif os.path.isfile(git_dir):
1700 # This is a worktree of the src_dir repo, we don't need to
1701 # create it again or update it in any way.
1702 pass
1703 elif os.path.exists(git_dir):
1704 # Don't know what could trigger this, but we probably
1705 # can't create a git worktree/clone here.
1706 raise ValueError('Git dir %s exists, but is not a file '
1707 'or a directory.' % git_dir)
1708 elif setup_git == 'worktree':
Simon Glass02811582022-01-29 14:14:18 -07001709 tprint('\rChecking out worktree for thread %d' % thread_num,
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001710 newline=False)
Simon Glass761648b2022-01-29 14:14:11 -07001711 gitutil.add_worktree(src_dir, thread_dir)
Simon Glass02811582022-01-29 14:14:18 -07001712 terminal.print_clear()
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001713 elif setup_git == 'clone' or setup_git == True:
Simon Glass02811582022-01-29 14:14:18 -07001714 tprint('\rCloning repo for thread %d' % thread_num,
Simon Glass2e2a6e62016-09-18 16:48:31 -06001715 newline=False)
Simon Glass761648b2022-01-29 14:14:11 -07001716 gitutil.clone(src_dir, thread_dir)
Simon Glass02811582022-01-29 14:14:18 -07001717 terminal.print_clear()
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001718 else:
1719 raise ValueError("Can't setup git repo with %s." % setup_git)
Simon Glassc05694f2013-04-03 11:07:16 +00001720
Simon Glassbc74d942023-07-19 17:49:06 -06001721 def _prepare_working_space(self, max_threads, setup_git):
Simon Glassc05694f2013-04-03 11:07:16 +00001722 """Prepare the working directory for use.
1723
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001724 Set up the git repo for each thread. Creates a linked working tree
1725 if git-worktree is available, or clones the repo if it isn't.
Simon Glassc05694f2013-04-03 11:07:16 +00001726
1727 Args:
Simon Glassc635d892021-01-30 22:17:46 -07001728 max_threads: Maximum number of threads we expect to need. If 0 then
1729 1 is set up, since the main process still needs somewhere to
1730 work
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001731 setup_git: True to set up a git worktree or a git clone
Simon Glassc05694f2013-04-03 11:07:16 +00001732 """
Simon Glassc5077c32023-07-19 17:49:08 -06001733 builderthread.mkdir(self._working_dir)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001734 if setup_git and self.git_dir:
1735 src_dir = os.path.abspath(self.git_dir)
Simon Glass761648b2022-01-29 14:14:11 -07001736 if gitutil.check_worktree_is_available(src_dir):
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001737 setup_git = 'worktree'
1738 # If we previously added a worktree but the directory for it
1739 # got deleted, we need to prune its files from the repo so
1740 # that we can check out another in its place.
Simon Glass761648b2022-01-29 14:14:11 -07001741 gitutil.prune_worktrees(src_dir)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001742 else:
1743 setup_git = 'clone'
Simon Glassc635d892021-01-30 22:17:46 -07001744
1745 # Always do at least one thread
1746 for thread in range(max(max_threads, 1)):
Simon Glassbc74d942023-07-19 17:49:06 -06001747 self._prepare_thread(thread, setup_git)
Simon Glassc05694f2013-04-03 11:07:16 +00001748
Simon Glassbc74d942023-07-19 17:49:06 -06001749 def _get_output_space_removals(self):
Simon Glassc05694f2013-04-03 11:07:16 +00001750 """Get the output directories ready to receive files.
1751
Simon Glass5dc1ca72020-03-18 09:42:45 -06001752 Figure out what needs to be deleted in the output directory before it
1753 can be used. We only delete old buildman directories which have the
Simon Glass4cb54682023-07-19 17:49:10 -06001754 expected name pattern. See get_output_dir().
Simon Glass5dc1ca72020-03-18 09:42:45 -06001755
1756 Returns:
1757 List of full paths of directories to remove
Simon Glassc05694f2013-04-03 11:07:16 +00001758 """
Simon Glassb9a9b7c2014-12-01 17:33:53 -07001759 if not self.commits:
1760 return
Simon Glassc05694f2013-04-03 11:07:16 +00001761 dir_list = []
1762 for commit_upto in range(self.commit_count):
Simon Glass4cb54682023-07-19 17:49:10 -06001763 dir_list.append(self.get_output_dir(commit_upto))
Simon Glassc05694f2013-04-03 11:07:16 +00001764
Simon Glass83cb6cc2016-09-18 16:48:32 -06001765 to_remove = []
Simon Glassc05694f2013-04-03 11:07:16 +00001766 for dirname in glob.glob(os.path.join(self.base_dir, '*')):
1767 if dirname not in dir_list:
Simon Glass5dc1ca72020-03-18 09:42:45 -06001768 leaf = dirname[len(self.base_dir) + 1:]
Ovidiu Panaitee8e9cb2020-05-15 09:30:12 +03001769 m = re.match('[0-9]+_g[0-9a-f]+_.*', leaf)
Simon Glass5dc1ca72020-03-18 09:42:45 -06001770 if m:
1771 to_remove.append(dirname)
1772 return to_remove
1773
Simon Glassbc74d942023-07-19 17:49:06 -06001774 def _prepare_output_space(self):
Simon Glass5dc1ca72020-03-18 09:42:45 -06001775 """Get the output directories ready to receive files.
1776
1777 We delete any output directories which look like ones we need to
1778 create. Having left over directories is confusing when the user wants
1779 to check the output manually.
1780 """
Simon Glassbc74d942023-07-19 17:49:06 -06001781 to_remove = self._get_output_space_removals()
Simon Glass83cb6cc2016-09-18 16:48:32 -06001782 if to_remove:
Simon Glass02811582022-01-29 14:14:18 -07001783 tprint('Removing %d old build directories...' % len(to_remove),
Simon Glass83cb6cc2016-09-18 16:48:32 -06001784 newline=False)
1785 for dirname in to_remove:
Simon Glassc05694f2013-04-03 11:07:16 +00001786 shutil.rmtree(dirname)
Simon Glass02811582022-01-29 14:14:18 -07001787 terminal.print_clear()
Simon Glassc05694f2013-04-03 11:07:16 +00001788
Simon Glassbc74d942023-07-19 17:49:06 -06001789 def build_boards(self, commits, board_selected, keep_outputs, verbose):
Simon Glassc05694f2013-04-03 11:07:16 +00001790 """Build all commits for a list of boards
1791
1792 Args:
1793 commits: List of commits to be build, each a Commit object
1794 boards_selected: Dict of selected boards, key is target name,
1795 value is Board object
Simon Glassc05694f2013-04-03 11:07:16 +00001796 keep_outputs: True to save build output files
Simon Glass78e418e2014-08-09 15:33:03 -06001797 verbose: Display build results as they are completed
Simon Glassc2f91072014-08-28 09:43:39 -06001798 Returns:
1799 Tuple containing:
1800 - number of boards that failed to build
1801 - number of boards that issued warnings
Simon Glass9bf9a722021-04-11 16:27:27 +12001802 - list of thread exceptions raised
Simon Glassc05694f2013-04-03 11:07:16 +00001803 """
Simon Glassd326ad72014-08-09 15:32:59 -06001804 self.commit_count = len(commits) if commits else 1
Simon Glassc05694f2013-04-03 11:07:16 +00001805 self.commits = commits
Simon Glass78e418e2014-08-09 15:33:03 -06001806 self._verbose = verbose
Simon Glassc05694f2013-04-03 11:07:16 +00001807
Simon Glassbc74d942023-07-19 17:49:06 -06001808 self.reset_result_summary(board_selected)
Simon Glassc5077c32023-07-19 17:49:08 -06001809 builderthread.mkdir(self.base_dir, parents = True)
Simon Glassbc74d942023-07-19 17:49:06 -06001810 self._prepare_working_space(min(self.num_threads, len(board_selected)),
Simon Glassd326ad72014-08-09 15:32:59 -06001811 commits is not None)
Simon Glassbc74d942023-07-19 17:49:06 -06001812 self._prepare_output_space()
Simon Glass6c435622022-07-11 19:03:56 -06001813 if not self._ide:
1814 tprint('\rStarting build...', newline=False)
Simon Glass7190a172023-09-07 10:00:19 -06001815 self._start_time = datetime.now()
Simon Glassbc74d942023-07-19 17:49:06 -06001816 self.setup_build(board_selected, commits)
1817 self.process_result(None)
Simon Glass9bf9a722021-04-11 16:27:27 +12001818 self.thread_exceptions = []
Simon Glassc05694f2013-04-03 11:07:16 +00001819 # Create jobs to build all commits for each board
Simon Glassc78ed662019-10-31 07:42:53 -06001820 for brd in board_selected.values():
Simon Glass4a1e88b2014-08-09 15:33:00 -06001821 job = builderthread.BuilderJob()
Simon Glass8132f982022-07-11 19:03:57 -06001822 job.brd = brd
Simon Glassc05694f2013-04-03 11:07:16 +00001823 job.commits = commits
1824 job.keep_outputs = keep_outputs
Simon Glassb6eb8cf2020-03-18 09:42:42 -06001825 job.work_in_output = self.work_in_output
Simon Glasse5650a82022-01-22 05:07:33 -07001826 job.adjust_cfg = self.adjust_cfg
Simon Glassc05694f2013-04-03 11:07:16 +00001827 job.step = self._step
Simon Glassc635d892021-01-30 22:17:46 -07001828 if self.num_threads:
1829 self.queue.put(job)
1830 else:
Simon Glassc5077c32023-07-19 17:49:08 -06001831 self._single_builder.run_job(job)
Simon Glassc05694f2013-04-03 11:07:16 +00001832
Simon Glassc635d892021-01-30 22:17:46 -07001833 if self.num_threads:
1834 term = threading.Thread(target=self.queue.join)
1835 term.setDaemon(True)
1836 term.start()
1837 while term.is_alive():
1838 term.join(100)
Simon Glassc05694f2013-04-03 11:07:16 +00001839
Simon Glassc635d892021-01-30 22:17:46 -07001840 # Wait until we have processed all output
1841 self.out_queue.join()
Simon Glass6c435622022-07-11 19:03:56 -06001842 if not self._ide:
1843 tprint()
Simon Glass726ae812020-04-09 15:08:47 -06001844
Simon Glass6c435622022-07-11 19:03:56 -06001845 msg = 'Completed: %d total built' % self.count
1846 if self.already_done:
1847 msg += ' (%d previously' % self.already_done
1848 if self.already_done != self.count:
1849 msg += ', %d newly' % (self.count - self.already_done)
1850 msg += ')'
1851 duration = datetime.now() - self._start_time
1852 if duration > timedelta(microseconds=1000000):
1853 if duration.microseconds >= 500000:
1854 duration = duration + timedelta(seconds=1)
1855 duration = duration - timedelta(microseconds=duration.microseconds)
1856 rate = float(self.count) / duration.total_seconds()
1857 msg += ', duration %s, rate %1.2f' % (duration, rate)
1858 tprint(msg)
1859 if self.thread_exceptions:
1860 tprint('Failed: %d thread exceptions' % len(self.thread_exceptions),
1861 colour=self.col.RED)
Simon Glass726ae812020-04-09 15:08:47 -06001862
Simon Glass9bf9a722021-04-11 16:27:27 +12001863 return (self.fail, self.warned, self.thread_exceptions)