blob: 3eac17ac212982ecafbe91305fcfd025644ff570 [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glassc05694f2013-04-03 11:07:16 +00002# Copyright (c) 2013 The Chromium OS Authors.
3#
4# Bloat-o-meter code used here Copyright 2004 Matt Mackall <mpm@selenic.com>
5#
Simon Glassc05694f2013-04-03 11:07:16 +00006
7import collections
Simon Glassc05694f2013-04-03 11:07:16 +00008from datetime import datetime, timedelta
9import glob
10import os
11import re
Simon Glassc78ed662019-10-31 07:42:53 -060012import queue
Simon Glassc05694f2013-04-03 11:07:16 +000013import shutil
Simon Glass205ac042016-09-18 16:48:37 -060014import signal
Simon Glassc05694f2013-04-03 11:07:16 +000015import string
16import sys
Simon Glassd26e1442016-09-18 16:48:35 -060017import threading
Simon Glassc05694f2013-04-03 11:07:16 +000018import time
19
Simon Glassf0d9c102020-04-17 18:09:02 -060020from buildman import builderthread
21from buildman import toolchain
Simon Glassa997ea52020-04-17 18:09:04 -060022from patman import gitutil
Simon Glass131444f2023-02-23 18:18:04 -070023from u_boot_pylib import command
24from u_boot_pylib import terminal
Simon Glass600ede92024-08-15 13:57:45 -060025from u_boot_pylib import tools
Simon Glass131444f2023-02-23 18:18:04 -070026from u_boot_pylib.terminal import tprint
Simon Glassc05694f2013-04-03 11:07:16 +000027
Simon Glass146b6022021-10-19 21:43:24 -060028# This indicates an new int or hex Kconfig property with no default
29# It hangs the build since the 'conf' tool cannot proceed without valid input.
30#
31# We get a repeat sequence of something like this:
32# >>
33# Break things (BREAK_ME) [] (NEW)
34# Error in reading or end of file.
35# <<
36# which indicates that BREAK_ME has an empty default
Simon Glass1f3a1222024-09-21 19:57:57 +020037RE_NO_DEFAULT = re.compile(br'\((\w+)\) \[] \(NEW\)')
Simon Glass146b6022021-10-19 21:43:24 -060038
Simon Glassdac73712023-10-23 00:52:43 -070039# Symbol types which appear in the bloat feature (-B). Others are silently
40# dropped when reading in the 'nm' output
41NM_SYMBOL_TYPES = 'tTdDbBr'
42
Simon Glassc05694f2013-04-03 11:07:16 +000043"""
44Theory of Operation
45
46Please see README for user documentation, and you should be familiar with
47that before trying to make sense of this.
48
49Buildman works by keeping the machine as busy as possible, building different
50commits for different boards on multiple CPUs at once.
51
52The source repo (self.git_dir) contains all the commits to be built. Each
53thread works on a single board at a time. It checks out the first commit,
54configures it for that board, then builds it. Then it checks out the next
55commit and builds it (typically without re-configuring). When it runs out
56of commits, it gets another job from the builder and starts again with that
57board.
58
59Clearly the builder threads could work either way - they could check out a
60commit and then built it for all boards. Using separate directories for each
61commit/board pair they could leave their build product around afterwards
62also.
63
64The intent behind building a single board for multiple commits, is to make
65use of incremental builds. Since each commit is built incrementally from
66the previous one, builds are faster. Reconfiguring for a different board
67removes all intermediate object files.
68
69Many threads can be working at once, but each has its own working directory.
70When a thread finishes a build, it puts the output files into a result
71directory.
72
73The base directory used by buildman is normally '../<branch>', i.e.
74a directory higher than the source repository and named after the branch
75being built.
76
77Within the base directory, we have one subdirectory for each commit. Within
78that is one subdirectory for each board. Within that is the build output for
79that commit/board combination.
80
81Buildman also create working directories for each thread, in a .bm-work/
82subdirectory in the base dir.
83
84As an example, say we are building branch 'us-net' for boards 'sandbox' and
85'seaboard', and say that us-net has two commits. We will have directories
86like this:
87
88us-net/ base directory
Ovidiu Panaitee8e9cb2020-05-15 09:30:12 +030089 01_g4ed4ebc_net--Add-tftp-speed-/
Simon Glassc05694f2013-04-03 11:07:16 +000090 sandbox/
91 u-boot.bin
92 seaboard/
93 u-boot.bin
Ovidiu Panaitee8e9cb2020-05-15 09:30:12 +030094 02_g4ed4ebc_net--Check-tftp-comp/
Simon Glassc05694f2013-04-03 11:07:16 +000095 sandbox/
96 u-boot.bin
97 seaboard/
98 u-boot.bin
99 .bm-work/
100 00/ working directory for thread 0 (contains source checkout)
101 build/ build output
102 01/ working directory for thread 1
103 build/ build output
104 ...
105u-boot/ source directory
106 .git/ repository
107"""
108
Simon Glassde0fefc2020-04-09 15:08:36 -0600109"""Holds information about a particular error line we are outputing
110
111 char: Character representation: '+': error, '-': fixed error, 'w+': warning,
112 'w-' = fixed warning
113 boards: List of Board objects which have line in the error/warning output
114 errline: The text of the error line
115"""
Simon Glass5df45222022-07-11 19:04:00 -0600116ErrLine = collections.namedtuple('ErrLine', 'char,brds,errline')
Simon Glassde0fefc2020-04-09 15:08:36 -0600117
Simon Glassc05694f2013-04-03 11:07:16 +0000118# Possible build outcomes
Simon Glassc78ed662019-10-31 07:42:53 -0600119OUTCOME_OK, OUTCOME_WARNING, OUTCOME_ERROR, OUTCOME_UNKNOWN = list(range(4))
Simon Glassc05694f2013-04-03 11:07:16 +0000120
Simon Glassd214bef2017-04-12 18:23:26 -0600121# Translate a commit subject into a valid filename (and handle unicode)
Simon Glassc78ed662019-10-31 07:42:53 -0600122trans_valid_chars = str.maketrans('/: ', '---')
Simon Glassc05694f2013-04-03 11:07:16 +0000123
Simon Glasscde5c302016-11-13 14:25:53 -0700124BASE_CONFIG_FILENAMES = [
125 'u-boot.cfg', 'u-boot-spl.cfg', 'u-boot-tpl.cfg'
126]
127
128EXTRA_CONFIG_FILENAMES = [
Simon Glassdb17fb82015-02-05 22:06:15 -0700129 '.config', '.config-spl', '.config-tpl',
130 'autoconf.mk', 'autoconf-spl.mk', 'autoconf-tpl.mk',
131 'autoconf.h', 'autoconf-spl.h','autoconf-tpl.h',
Simon Glassdb17fb82015-02-05 22:06:15 -0700132]
133
Simon Glasscad8abf2015-08-25 21:52:14 -0600134class Config:
135 """Holds information about configuration settings for a board."""
Simon Glasscde5c302016-11-13 14:25:53 -0700136 def __init__(self, config_filename, target):
Simon Glasscad8abf2015-08-25 21:52:14 -0600137 self.target = target
138 self.config = {}
Simon Glasscde5c302016-11-13 14:25:53 -0700139 for fname in config_filename:
Simon Glasscad8abf2015-08-25 21:52:14 -0600140 self.config[fname] = {}
141
Simon Glassbc74d942023-07-19 17:49:06 -0600142 def add(self, fname, key, value):
Simon Glasscad8abf2015-08-25 21:52:14 -0600143 self.config[fname][key] = value
144
145 def __hash__(self):
146 val = 0
147 for fname in self.config:
Simon Glassc78ed662019-10-31 07:42:53 -0600148 for key, value in self.config[fname].items():
149 print(key, value)
Simon Glasscad8abf2015-08-25 21:52:14 -0600150 val = val ^ hash(key) & hash(value)
151 return val
Simon Glassc05694f2013-04-03 11:07:16 +0000152
Alex Kiernan4059e302018-05-31 04:48:34 +0000153class Environment:
154 """Holds information about environment variables for a board."""
155 def __init__(self, target):
156 self.target = target
157 self.environment = {}
158
Simon Glassbc74d942023-07-19 17:49:06 -0600159 def add(self, key, value):
Alex Kiernan4059e302018-05-31 04:48:34 +0000160 self.environment[key] = value
161
Simon Glassc05694f2013-04-03 11:07:16 +0000162class Builder:
163 """Class for building U-Boot for a particular commit.
164
165 Public members: (many should ->private)
Simon Glassc05694f2013-04-03 11:07:16 +0000166 already_done: Number of builds already completed
167 base_dir: Base directory to use for builder
168 checkout: True to check out source, False to skip that step.
169 This is used for testing.
170 col: terminal.Color() object
Simon Glassf56cc292023-07-19 17:49:03 -0600171 count: Total number of commits to build, which is the number of commits
172 multiplied by the number of boards
Simon Glassc05694f2013-04-03 11:07:16 +0000173 do_make: Method to call to invoke Make
174 fail: Number of builds that failed due to error
175 force_build: Force building even if a build already exists
176 force_config_on_failure: If a commit fails for a board, disable
177 incremental building for the next commit we build for that
178 board, so that we will see all warnings/errors again.
Simon Glass7041c392014-07-13 12:22:31 -0600179 force_build_failures: If a previously-built build (i.e. built on
180 a previous run of buildman) is marked as failed, rebuild it.
Simon Glassc05694f2013-04-03 11:07:16 +0000181 git_dir: Git directory containing source repository
Simon Glassc05694f2013-04-03 11:07:16 +0000182 num_jobs: Number of jobs to run at once (passed to make as -j)
183 num_threads: Number of builder threads to run
184 out_queue: Queue of results to process
185 re_make_err: Compiled regular expression for ignore_lines
186 queue: Queue of jobs to run
187 threads: List of active threads
188 toolchains: Toolchains object to use for building
189 upto: Current commit number we are building (0.count-1)
190 warned: Number of builds that produced at least one warning
Simon Glassf3018b7a2014-07-14 17:51:02 -0600191 force_reconfig: Reconfigure U-Boot on each comiit. This disables
192 incremental building, where buildman reconfigures on the first
193 commit for a baord, and then just does an incremental build for
194 the following commits. In fact buildman will reconfigure and
195 retry for any failing commits, so generally the only effect of
196 this option is to slow things down.
Simon Glass38df2e22014-07-14 17:51:03 -0600197 in_tree: Build U-Boot in-tree instead of specifying an output
198 directory separate from the source code. This option is really
199 only useful for testing in-tree builds.
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600200 work_in_output: Use the output directory as the work directory and
201 don't write to a separate output directory.
Simon Glass9bf9a722021-04-11 16:27:27 +1200202 thread_exceptions: List of exceptions raised by thread jobs
Simon Glassf6bfcca2023-02-21 12:40:28 -0700203 no_lto (bool): True to set the NO_LTO flag when building
Simon Glass828d70d2023-02-21 12:40:29 -0700204 reproducible_builds (bool): True to set SOURCE_DATE_EPOCH=0 for builds
Simon Glassc05694f2013-04-03 11:07:16 +0000205
206 Private members:
207 _base_board_dict: Last-summarised Dict of boards
208 _base_err_lines: Last-summarised list of errors
Simon Glass03749d42014-08-28 09:43:44 -0600209 _base_warn_lines: Last-summarised list of warnings
Simon Glassc05694f2013-04-03 11:07:16 +0000210 _build_period_us: Time taken for a single build (float object).
211 _complete_delay: Expected delay until completion (timedelta)
212 _next_delay_update: Next time we plan to display a progress update
213 (datatime)
214 _show_unknown: Show unknown boards (those not built) in summary
Simon Glass726ae812020-04-09 15:08:47 -0600215 _start_time: Start time for the build
Simon Glassc05694f2013-04-03 11:07:16 +0000216 _timestamps: List of timestamps for the completion of the last
217 last _timestamp_count builds. Each is a datetime object.
218 _timestamp_count: Number of timestamps to keep in our list.
219 _working_dir: Base working directory containing all threads
Simon Glassc635d892021-01-30 22:17:46 -0700220 _single_builder: BuilderThread object for the singer builder, if
221 threading is not being used
Simon Glass146b6022021-10-19 21:43:24 -0600222 _terminated: Thread was terminated due to an error
223 _restarting_config: True if 'Restart config' is detected in output
Simon Glass6c435622022-07-11 19:03:56 -0600224 _ide: Produce output suitable for an Integrated Development Environment,
225 i.e. dont emit progress information and put errors/warnings on stderr
Simon Glassc05694f2013-04-03 11:07:16 +0000226 """
227 class Outcome:
228 """Records a build outcome for a single make invocation
229
230 Public Members:
231 rc: Outcome value (OUTCOME_...)
232 err_lines: List of error lines or [] if none
233 sizes: Dictionary of image size information, keyed by filename
234 - Each value is itself a dictionary containing
235 values for 'text', 'data' and 'bss', being the integer
236 size in bytes of each section.
237 func_sizes: Dictionary keyed by filename - e.g. 'u-boot'. Each
238 value is itself a dictionary:
239 key: function name
240 value: Size of function in bytes
Simon Glassdb17fb82015-02-05 22:06:15 -0700241 config: Dictionary keyed by filename - e.g. '.config'. Each
242 value is itself a dictionary:
243 key: config name
244 value: config value
Alex Kiernan4059e302018-05-31 04:48:34 +0000245 environment: Dictionary keyed by environment variable, Each
246 value is the value of environment variable.
Simon Glassc05694f2013-04-03 11:07:16 +0000247 """
Alex Kiernan4059e302018-05-31 04:48:34 +0000248 def __init__(self, rc, err_lines, sizes, func_sizes, config,
249 environment):
Simon Glassc05694f2013-04-03 11:07:16 +0000250 self.rc = rc
251 self.err_lines = err_lines
252 self.sizes = sizes
253 self.func_sizes = func_sizes
Simon Glassdb17fb82015-02-05 22:06:15 -0700254 self.config = config
Alex Kiernan4059e302018-05-31 04:48:34 +0000255 self.environment = environment
Simon Glassc05694f2013-04-03 11:07:16 +0000256
257 def __init__(self, toolchains, base_dir, git_dir, num_threads, num_jobs,
Simon Glasse87bde12014-12-01 17:33:55 -0700258 gnu_make='make', checkout=True, show_unknown=True, step=1,
Tom Rini3f6f9b22024-07-05 14:34:07 -0600259 no_subdirs=False, full_path=False, verbose_build=False,
Simon Glass222825b2024-06-23 11:55:13 -0600260 mrproper=False, fallback_mrproper=False,
261 per_board_out_dir=False, config_only=False,
262 squash_config_y=False, warnings_as_errors=False,
263 work_in_output=False, test_thread_exceptions=False,
264 adjust_cfg=None, allow_missing=False, no_lto=False,
265 reproducible_builds=False, force_build=False,
266 force_build_failures=False, force_reconfig=False,
Simon Glass600ede92024-08-15 13:57:45 -0600267 in_tree=False, force_config_on_failure=False, make_func=None,
268 dtc_skip=False):
Simon Glassc05694f2013-04-03 11:07:16 +0000269 """Create a new Builder object
270
271 Args:
272 toolchains: Toolchains object to use for building
273 base_dir: Base directory to use for builder
274 git_dir: Git directory containing source repository
275 num_threads: Number of builder threads to run
276 num_jobs: Number of jobs to run at once (passed to make as -j)
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900277 gnu_make: the command name of GNU Make.
Simon Glassc05694f2013-04-03 11:07:16 +0000278 checkout: True to check out source, False to skip that step.
279 This is used for testing.
280 show_unknown: Show unknown boards (those not built) in summary
281 step: 1 to process every commit, n to process every nth commit
Simon Glassd48a46c2014-12-01 17:34:00 -0700282 no_subdirs: Don't create subdirectories when building current
283 source for a single board
Tom Rini3f6f9b22024-07-05 14:34:07 -0600284 full_path: Return the full path in CROSS_COMPILE and don't set
285 PATH
Simon Glass655b6102014-12-01 17:34:07 -0700286 verbose_build: Run build with V=1 and don't use 'make -s'
Simon Glass6029af12020-04-09 15:08:51 -0600287 mrproper: Always run 'make mrproper' when configuring
Simon Glass222825b2024-06-23 11:55:13 -0600288 fallback_mrproper: Run 'make mrproper' and retry on build failure
Stephen Warren97c96902016-04-11 10:48:44 -0600289 per_board_out_dir: Build in a separate persistent directory per
290 board rather than a thread-specific directory
Simon Glass739e8512016-11-13 14:25:51 -0700291 config_only: Only configure each build, don't build it
Simon Glasscde5c302016-11-13 14:25:53 -0700292 squash_config_y: Convert CONFIG options with the value 'y' to '1'
Daniel Schwierzeck20e2ea92018-01-26 16:31:05 +0100293 warnings_as_errors: Treat all compiler warnings as errors
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600294 work_in_output: Use the output directory as the work directory and
295 don't write to a separate output directory.
Simon Glass9bf9a722021-04-11 16:27:27 +1200296 test_thread_exceptions: Uses for tests only, True to make the
297 threads raise an exception instead of reporting their result.
298 This simulates a failure in the code somewhere
Simon Glasse5650a82022-01-22 05:07:33 -0700299 adjust_cfg_list (list of str): List of changes to make to .config
300 file before building. Each is one of (where C is the config
301 option with or without the CONFIG_ prefix)
302
303 C to enable C
304 ~C to disable C
305 C=val to set the value of C (val must have quotes if C is
306 a string Kconfig
Tom Rini93ebd462022-11-09 19:14:53 -0700307 allow_missing: Run build with BINMAN_ALLOW_MISSING=1
Simon Glassf6bfcca2023-02-21 12:40:28 -0700308 no_lto (bool): True to set the NO_LTO flag when building
Simon Glasscf91d312023-07-19 17:48:52 -0600309 force_build (bool): Rebuild even commits that are already built
310 force_build_failures (bool): Rebuild commits that have not been
311 built, or failed to build
312 force_reconfig (bool): Reconfigure on each commit
313 in_tree (bool): Bulid in tree instead of out-of-tree
314 force_config_on_failure (bool): Reconfigure the build before
315 retrying a failed build
316 make_func (function): Function to call to run 'make'
Simon Glass600ede92024-08-15 13:57:45 -0600317 dtc_skip (bool): True to skip building dtc and use the system one
Simon Glassc05694f2013-04-03 11:07:16 +0000318 """
319 self.toolchains = toolchains
320 self.base_dir = base_dir
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600321 if work_in_output:
322 self._working_dir = base_dir
323 else:
324 self._working_dir = os.path.join(base_dir, '.bm-work')
Simon Glassc05694f2013-04-03 11:07:16 +0000325 self.threads = []
Simon Glassbc74d942023-07-19 17:49:06 -0600326 self.do_make = make_func or self.make
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900327 self.gnu_make = gnu_make
Simon Glassc05694f2013-04-03 11:07:16 +0000328 self.checkout = checkout
329 self.num_threads = num_threads
330 self.num_jobs = num_jobs
331 self.already_done = 0
332 self.force_build = False
333 self.git_dir = git_dir
334 self._show_unknown = show_unknown
335 self._timestamp_count = 10
336 self._build_period_us = None
337 self._complete_delay = None
338 self._next_delay_update = datetime.now()
Simon Glass7190a172023-09-07 10:00:19 -0600339 self._start_time = None
Simon Glassc05694f2013-04-03 11:07:16 +0000340 self._step = step
Simon Glassbb4dffb2014-08-09 15:33:06 -0600341 self._error_lines = 0
Simon Glasse87bde12014-12-01 17:33:55 -0700342 self.no_subdirs = no_subdirs
Tom Rini3f6f9b22024-07-05 14:34:07 -0600343 self.full_path = full_path
Simon Glass655b6102014-12-01 17:34:07 -0700344 self.verbose_build = verbose_build
Simon Glass739e8512016-11-13 14:25:51 -0700345 self.config_only = config_only
Simon Glasscde5c302016-11-13 14:25:53 -0700346 self.squash_config_y = squash_config_y
347 self.config_filenames = BASE_CONFIG_FILENAMES
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600348 self.work_in_output = work_in_output
Simon Glasse5650a82022-01-22 05:07:33 -0700349 self.adjust_cfg = adjust_cfg
Tom Rini93ebd462022-11-09 19:14:53 -0700350 self.allow_missing = allow_missing
Simon Glass6c435622022-07-11 19:03:56 -0600351 self._ide = False
Simon Glassf6bfcca2023-02-21 12:40:28 -0700352 self.no_lto = no_lto
Simon Glass828d70d2023-02-21 12:40:29 -0700353 self.reproducible_builds = reproducible_builds
Simon Glasscf91d312023-07-19 17:48:52 -0600354 self.force_build = force_build
355 self.force_build_failures = force_build_failures
356 self.force_reconfig = force_reconfig
357 self.in_tree = in_tree
358 self.force_config_on_failure = force_config_on_failure
Simon Glass222825b2024-06-23 11:55:13 -0600359 self.fallback_mrproper = fallback_mrproper
Simon Glass600ede92024-08-15 13:57:45 -0600360 if dtc_skip:
361 self.dtc = shutil.which('dtc')
362 if not self.dtc:
363 raise ValueError('Cannot find dtc')
364 else:
365 self.dtc = None
Simon Glasse5650a82022-01-22 05:07:33 -0700366
Simon Glasscde5c302016-11-13 14:25:53 -0700367 if not self.squash_config_y:
368 self.config_filenames += EXTRA_CONFIG_FILENAMES
Simon Glass146b6022021-10-19 21:43:24 -0600369 self._terminated = False
370 self._restarting_config = False
Simon Glassc05694f2013-04-03 11:07:16 +0000371
Daniel Schwierzeck20e2ea92018-01-26 16:31:05 +0100372 self.warnings_as_errors = warnings_as_errors
Simon Glassc05694f2013-04-03 11:07:16 +0000373 self.col = terminal.Color()
374
Simon Glass03749d42014-08-28 09:43:44 -0600375 self._re_function = re.compile('(.*): In function.*')
376 self._re_files = re.compile('In file included from.*')
Simon Glass1f3a1222024-09-21 19:57:57 +0200377 self._re_warning = re.compile(r'(.*):(\d*):(\d*): warning: .*')
Simon Glass0db94432018-11-06 16:02:11 -0700378 self._re_dtb_warning = re.compile('(.*): Warning .*')
Simon Glass1f3a1222024-09-21 19:57:57 +0200379 self._re_note = re.compile(r'(.*):(\d*):(\d*): note: this is the location of the previous.*')
Simon Glassf4ebfba2020-04-09 15:08:53 -0600380 self._re_migration_warning = re.compile(r'^={21} WARNING ={22}\n.*\n=+\n',
381 re.MULTILINE | re.DOTALL)
Simon Glass03749d42014-08-28 09:43:44 -0600382
Simon Glass9bf9a722021-04-11 16:27:27 +1200383 self.thread_exceptions = []
384 self.test_thread_exceptions = test_thread_exceptions
Simon Glassc635d892021-01-30 22:17:46 -0700385 if self.num_threads:
386 self._single_builder = None
387 self.queue = queue.Queue()
388 self.out_queue = queue.Queue()
389 for i in range(self.num_threads):
Simon Glass9bf9a722021-04-11 16:27:27 +1200390 t = builderthread.BuilderThread(
391 self, i, mrproper, per_board_out_dir,
392 test_exception=test_thread_exceptions)
Simon Glassc635d892021-01-30 22:17:46 -0700393 t.setDaemon(True)
394 t.start()
395 self.threads.append(t)
396
397 t = builderthread.ResultThread(self)
Simon Glassc05694f2013-04-03 11:07:16 +0000398 t.setDaemon(True)
399 t.start()
400 self.threads.append(t)
Simon Glassc635d892021-01-30 22:17:46 -0700401 else:
402 self._single_builder = builderthread.BuilderThread(
403 self, -1, mrproper, per_board_out_dir)
Simon Glassc05694f2013-04-03 11:07:16 +0000404
405 ignore_lines = ['(make.*Waiting for unfinished)', '(Segmentation fault)']
406 self.re_make_err = re.compile('|'.join(ignore_lines))
407
Simon Glass205ac042016-09-18 16:48:37 -0600408 # Handle existing graceful with SIGINT / Ctrl-C
409 signal.signal(signal.SIGINT, self.signal_handler)
410
Simon Glassc05694f2013-04-03 11:07:16 +0000411 def __del__(self):
412 """Get rid of all threads created by the builder"""
413 for t in self.threads:
414 del t
415
Simon Glass205ac042016-09-18 16:48:37 -0600416 def signal_handler(self, signal, frame):
417 sys.exit(1)
418
Simon Glass600ede92024-08-15 13:57:45 -0600419 def make_environment(self, toolchain):
420 """Create the environment to use for building
421
422 Args:
423 toolchain (Toolchain): Toolchain to use for building
424
425 Returns:
426 dict:
427 key (str): Variable name
428 value (str): Variable value
429 """
430 env = toolchain.MakeEnvironment(self.full_path)
431 if self.dtc:
432 env[b'DTC'] = tools.to_bytes(self.dtc)
433 return env
434
Simon Glassbc74d942023-07-19 17:49:06 -0600435 def set_display_options(self, show_errors=False, show_sizes=False,
Simon Glass3394c9f2014-08-28 09:43:43 -0600436 show_detail=False, show_bloat=False,
Alex Kiernan4059e302018-05-31 04:48:34 +0000437 list_error_boards=False, show_config=False,
Simon Glassf4ebfba2020-04-09 15:08:53 -0600438 show_environment=False, filter_dtb_warnings=False,
Simon Glass6c435622022-07-11 19:03:56 -0600439 filter_migration_warnings=False, ide=False):
Simon Glasseb48bbc2014-08-09 15:33:02 -0600440 """Setup display options for the builder.
441
Simon Glass9ea93812020-04-09 15:08:52 -0600442 Args:
443 show_errors: True to show summarised error/warning info
444 show_sizes: Show size deltas
445 show_detail: Show size delta detail for each board if show_sizes
446 show_bloat: Show detail for each function
447 list_error_boards: Show the boards which caused each error/warning
448 show_config: Show config deltas
449 show_environment: Show environment deltas
450 filter_dtb_warnings: Filter out any warnings from the device-tree
451 compiler
Simon Glassf4ebfba2020-04-09 15:08:53 -0600452 filter_migration_warnings: Filter out any warnings about migrating
453 a board to driver model
Simon Glass6c435622022-07-11 19:03:56 -0600454 ide: Create output that can be parsed by an IDE. There is no '+' prefix on
455 error lines and output on stderr stays on stderr.
Simon Glasseb48bbc2014-08-09 15:33:02 -0600456 """
457 self._show_errors = show_errors
458 self._show_sizes = show_sizes
459 self._show_detail = show_detail
460 self._show_bloat = show_bloat
Simon Glass3394c9f2014-08-28 09:43:43 -0600461 self._list_error_boards = list_error_boards
Simon Glassdb17fb82015-02-05 22:06:15 -0700462 self._show_config = show_config
Alex Kiernan4059e302018-05-31 04:48:34 +0000463 self._show_environment = show_environment
Simon Glass9ea93812020-04-09 15:08:52 -0600464 self._filter_dtb_warnings = filter_dtb_warnings
Simon Glassf4ebfba2020-04-09 15:08:53 -0600465 self._filter_migration_warnings = filter_migration_warnings
Simon Glass6c435622022-07-11 19:03:56 -0600466 self._ide = ide
Simon Glasseb48bbc2014-08-09 15:33:02 -0600467
Simon Glassbc74d942023-07-19 17:49:06 -0600468 def _add_timestamp(self):
Simon Glassc05694f2013-04-03 11:07:16 +0000469 """Add a new timestamp to the list and record the build period.
470
471 The build period is the length of time taken to perform a single
472 build (one board, one commit).
473 """
474 now = datetime.now()
475 self._timestamps.append(now)
476 count = len(self._timestamps)
477 delta = self._timestamps[-1] - self._timestamps[0]
478 seconds = delta.total_seconds()
479
480 # If we have enough data, estimate build period (time taken for a
481 # single build) and therefore completion time.
482 if count > 1 and self._next_delay_update < now:
483 self._next_delay_update = now + timedelta(seconds=2)
484 if seconds > 0:
485 self._build_period = float(seconds) / count
486 todo = self.count - self.upto
487 self._complete_delay = timedelta(microseconds=
488 self._build_period * todo * 1000000)
489 # Round it
490 self._complete_delay -= timedelta(
491 microseconds=self._complete_delay.microseconds)
492
493 if seconds > 60:
494 self._timestamps.popleft()
495 count -= 1
496
Simon Glassbc74d942023-07-19 17:49:06 -0600497 def select_commit(self, commit, checkout=True):
Simon Glassc05694f2013-04-03 11:07:16 +0000498 """Checkout the selected commit for this build
499 """
500 self.commit = commit
501 if checkout and self.checkout:
Simon Glass761648b2022-01-29 14:14:11 -0700502 gitutil.checkout(commit.hash)
Simon Glassc05694f2013-04-03 11:07:16 +0000503
Simon Glassbc74d942023-07-19 17:49:06 -0600504 def make(self, commit, brd, stage, cwd, *args, **kwargs):
Simon Glassc05694f2013-04-03 11:07:16 +0000505 """Run make
506
507 Args:
508 commit: Commit object that is being built
509 brd: Board object that is being built
Simon Glassd6c1ec82023-10-26 14:31:10 -0400510 stage: Stage that we are at (mrproper, config, oldconfig, build)
Simon Glassc05694f2013-04-03 11:07:16 +0000511 cwd: Directory where make should be run
512 args: Arguments to pass to make
Simon Glass51f55182025-02-03 09:26:45 -0700513 kwargs: Arguments to pass to command.run_one()
Simon Glassc05694f2013-04-03 11:07:16 +0000514 """
Simon Glass146b6022021-10-19 21:43:24 -0600515
516 def check_output(stream, data):
517 if b'Restart config' in data:
518 self._restarting_config = True
519
520 # If we see 'Restart config' following by multiple errors
521 if self._restarting_config:
522 m = RE_NO_DEFAULT.findall(data)
523
524 # Number of occurences of each Kconfig item
525 multiple = [m.count(val) for val in set(m)]
526
527 # If any of them occur more than once, we have a loop
528 if [val for val in multiple if val > 1]:
529 self._terminated = True
530 return True
531 return False
532
533 self._restarting_config = False
Simon Glass51f55182025-02-03 09:26:45 -0700534 self._terminated = False
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900535 cmd = [self.gnu_make] + list(args)
Simon Glass51f55182025-02-03 09:26:45 -0700536 result = command.run_one(*cmd, capture=True, capture_stderr=True,
537 cwd=cwd, raise_on_error=False,
538 infile='/dev/null', output_func=check_output,
539 **kwargs)
Simon Glass146b6022021-10-19 21:43:24 -0600540
541 if self._terminated:
542 # Try to be helpful
543 result.stderr += '(** did you define an int/hex Kconfig with no default? **)'
544
Simon Glass413f91a2015-02-05 22:06:12 -0700545 if self.verbose_build:
546 result.stdout = '%s\n' % (' '.join(cmd)) + result.stdout
547 result.combined = '%s\n' % (' '.join(cmd)) + result.combined
Simon Glassc05694f2013-04-03 11:07:16 +0000548 return result
549
Simon Glassbc74d942023-07-19 17:49:06 -0600550 def process_result(self, result):
Simon Glassc05694f2013-04-03 11:07:16 +0000551 """Process the result of a build, showing progress information
552
553 Args:
Simon Glass78e418e2014-08-09 15:33:03 -0600554 result: A CommandResult object, which indicates the result for
555 a single build
Simon Glassc05694f2013-04-03 11:07:16 +0000556 """
557 col = terminal.Color()
558 if result:
559 target = result.brd.target
560
Simon Glassc05694f2013-04-03 11:07:16 +0000561 self.upto += 1
562 if result.return_code != 0:
563 self.fail += 1
564 elif result.stderr:
565 self.warned += 1
566 if result.already_done:
567 self.already_done += 1
Simon Glass78e418e2014-08-09 15:33:03 -0600568 if self._verbose:
Simon Glass02811582022-01-29 14:14:18 -0700569 terminal.print_clear()
Simon Glass78e418e2014-08-09 15:33:03 -0600570 boards_selected = {target : result.brd}
Simon Glassbc74d942023-07-19 17:49:06 -0600571 self.reset_result_summary(boards_selected)
572 self.produce_result_summary(result.commit_upto, self.commits,
Simon Glass78e418e2014-08-09 15:33:03 -0600573 boards_selected)
Simon Glassc05694f2013-04-03 11:07:16 +0000574 else:
575 target = '(starting)'
576
577 # Display separate counts for ok, warned and fail
578 ok = self.upto - self.warned - self.fail
Simon Glassf45d3742022-01-29 14:14:17 -0700579 line = '\r' + self.col.build(self.col.GREEN, '%5d' % ok)
580 line += self.col.build(self.col.YELLOW, '%5d' % self.warned)
581 line += self.col.build(self.col.RED, '%5d' % self.fail)
Simon Glassc05694f2013-04-03 11:07:16 +0000582
Simon Glass69c3a8a2020-04-09 15:08:45 -0600583 line += ' /%-5d ' % self.count
584 remaining = self.count - self.upto
585 if remaining:
Simon Glassf45d3742022-01-29 14:14:17 -0700586 line += self.col.build(self.col.MAGENTA, ' -%-5d ' % remaining)
Simon Glass69c3a8a2020-04-09 15:08:45 -0600587 else:
588 line += ' ' * 8
Simon Glassc05694f2013-04-03 11:07:16 +0000589
590 # Add our current completion time estimate
Simon Glassbc74d942023-07-19 17:49:06 -0600591 self._add_timestamp()
Simon Glassc05694f2013-04-03 11:07:16 +0000592 if self._complete_delay:
Simon Glass69c3a8a2020-04-09 15:08:45 -0600593 line += '%s : ' % self._complete_delay
Simon Glassc05694f2013-04-03 11:07:16 +0000594
Simon Glass69c3a8a2020-04-09 15:08:45 -0600595 line += target
Simon Glass6c435622022-07-11 19:03:56 -0600596 if not self._ide:
597 terminal.print_clear()
598 tprint(line, newline=False, limit_to_line=True)
Simon Glassc05694f2013-04-03 11:07:16 +0000599
Simon Glass4cb54682023-07-19 17:49:10 -0600600 def get_output_dir(self, commit_upto):
Simon Glassc05694f2013-04-03 11:07:16 +0000601 """Get the name of the output directory for a commit number
602
603 The output directory is typically .../<branch>/<commit>.
604
605 Args:
606 commit_upto: Commit number to use (0..self.count-1)
607 """
Simon Glasse3c85ab2020-04-17 17:51:34 -0600608 if self.work_in_output:
609 return self._working_dir
610
Simon Glasse87bde12014-12-01 17:33:55 -0700611 commit_dir = None
Simon Glassd326ad72014-08-09 15:32:59 -0600612 if self.commits:
613 commit = self.commits[commit_upto]
614 subject = commit.subject.translate(trans_valid_chars)
Simon Glassbc74d942023-07-19 17:49:06 -0600615 # See _get_output_space_removals() which parses this name
Ovidiu Panaitee8e9cb2020-05-15 09:30:12 +0300616 commit_dir = ('%02d_g%s_%s' % (commit_upto + 1,
617 commit.hash, subject[:20]))
Simon Glasse87bde12014-12-01 17:33:55 -0700618 elif not self.no_subdirs:
Simon Glassd326ad72014-08-09 15:32:59 -0600619 commit_dir = 'current'
Simon Glasse87bde12014-12-01 17:33:55 -0700620 if not commit_dir:
621 return self.base_dir
622 return os.path.join(self.base_dir, commit_dir)
Simon Glassc05694f2013-04-03 11:07:16 +0000623
Simon Glassbc74d942023-07-19 17:49:06 -0600624 def get_build_dir(self, commit_upto, target):
Simon Glassc05694f2013-04-03 11:07:16 +0000625 """Get the name of the build directory for a commit number
626
627 The build directory is typically .../<branch>/<commit>/<target>.
628
629 Args:
630 commit_upto: Commit number to use (0..self.count-1)
631 target: Target name
632 """
Simon Glass4cb54682023-07-19 17:49:10 -0600633 output_dir = self.get_output_dir(commit_upto)
Simon Glasse3c85ab2020-04-17 17:51:34 -0600634 if self.work_in_output:
635 return output_dir
Simon Glassc05694f2013-04-03 11:07:16 +0000636 return os.path.join(output_dir, target)
637
Simon Glassbc74d942023-07-19 17:49:06 -0600638 def get_done_file(self, commit_upto, target):
Simon Glassc05694f2013-04-03 11:07:16 +0000639 """Get the name of the done file for a commit number
640
641 Args:
642 commit_upto: Commit number to use (0..self.count-1)
643 target: Target name
644 """
Simon Glassbc74d942023-07-19 17:49:06 -0600645 return os.path.join(self.get_build_dir(commit_upto, target), 'done')
Simon Glassc05694f2013-04-03 11:07:16 +0000646
Simon Glassbc74d942023-07-19 17:49:06 -0600647 def get_sizes_file(self, commit_upto, target):
Simon Glassc05694f2013-04-03 11:07:16 +0000648 """Get the name of the sizes file for a commit number
649
650 Args:
651 commit_upto: Commit number to use (0..self.count-1)
652 target: Target name
653 """
Simon Glassbc74d942023-07-19 17:49:06 -0600654 return os.path.join(self.get_build_dir(commit_upto, target), 'sizes')
Simon Glassc05694f2013-04-03 11:07:16 +0000655
Simon Glassbc74d942023-07-19 17:49:06 -0600656 def get_func_sizes_file(self, commit_upto, target, elf_fname):
Simon Glassc05694f2013-04-03 11:07:16 +0000657 """Get the name of the funcsizes file for a commit number and ELF file
658
659 Args:
660 commit_upto: Commit number to use (0..self.count-1)
661 target: Target name
662 elf_fname: Filename of elf image
663 """
Simon Glassbc74d942023-07-19 17:49:06 -0600664 return os.path.join(self.get_build_dir(commit_upto, target),
Simon Glassc05694f2013-04-03 11:07:16 +0000665 '%s.sizes' % elf_fname.replace('/', '-'))
666
Simon Glassbc74d942023-07-19 17:49:06 -0600667 def get_objdump_file(self, commit_upto, target, elf_fname):
Simon Glassc05694f2013-04-03 11:07:16 +0000668 """Get the name of the objdump file for a commit number and ELF file
669
670 Args:
671 commit_upto: Commit number to use (0..self.count-1)
672 target: Target name
673 elf_fname: Filename of elf image
674 """
Simon Glassbc74d942023-07-19 17:49:06 -0600675 return os.path.join(self.get_build_dir(commit_upto, target),
Simon Glassc05694f2013-04-03 11:07:16 +0000676 '%s.objdump' % elf_fname.replace('/', '-'))
677
Simon Glassbc74d942023-07-19 17:49:06 -0600678 def get_err_file(self, commit_upto, target):
Simon Glassc05694f2013-04-03 11:07:16 +0000679 """Get the name of the err file for a commit number
680
681 Args:
682 commit_upto: Commit number to use (0..self.count-1)
683 target: Target name
684 """
Simon Glassbc74d942023-07-19 17:49:06 -0600685 output_dir = self.get_build_dir(commit_upto, target)
Simon Glassc05694f2013-04-03 11:07:16 +0000686 return os.path.join(output_dir, 'err')
687
Simon Glassbc74d942023-07-19 17:49:06 -0600688 def filter_errors(self, lines):
Simon Glassc05694f2013-04-03 11:07:16 +0000689 """Filter out errors in which we have no interest
690
691 We should probably use map().
692
693 Args:
694 lines: List of error lines, each a string
695 Returns:
696 New list with only interesting lines included
697 """
698 out_lines = []
Simon Glassf4ebfba2020-04-09 15:08:53 -0600699 if self._filter_migration_warnings:
700 text = '\n'.join(lines)
701 text = self._re_migration_warning.sub('', text)
702 lines = text.splitlines()
Simon Glassc05694f2013-04-03 11:07:16 +0000703 for line in lines:
Simon Glass9ea93812020-04-09 15:08:52 -0600704 if self.re_make_err.search(line):
705 continue
706 if self._filter_dtb_warnings and self._re_dtb_warning.search(line):
707 continue
708 out_lines.append(line)
Simon Glassc05694f2013-04-03 11:07:16 +0000709 return out_lines
710
Simon Glassbc74d942023-07-19 17:49:06 -0600711 def read_func_sizes(self, fname, fd):
Simon Glassc05694f2013-04-03 11:07:16 +0000712 """Read function sizes from the output of 'nm'
713
714 Args:
715 fd: File containing data to read
716 fname: Filename we are reading from (just for errors)
717
718 Returns:
719 Dictionary containing size of each function in bytes, indexed by
720 function name.
721 """
722 sym = {}
723 for line in fd.readlines():
Simon Glass86a2afe2022-07-11 19:04:11 -0600724 line = line.strip()
725 parts = line.split()
726 if line and len(parts) == 3:
727 size, type, name = line.split()
Simon Glassdac73712023-10-23 00:52:43 -0700728 if type in NM_SYMBOL_TYPES:
Simon Glass86a2afe2022-07-11 19:04:11 -0600729 # function names begin with '.' on 64-bit powerpc
730 if '.' in name[1:]:
731 name = 'static.' + name.split('.')[0]
732 sym[name] = sym.get(name, 0) + int(size, 16)
Simon Glassc05694f2013-04-03 11:07:16 +0000733 return sym
734
Simon Glassbc74d942023-07-19 17:49:06 -0600735 def _process_config(self, fname):
Simon Glassdb17fb82015-02-05 22:06:15 -0700736 """Read in a .config, autoconf.mk or autoconf.h file
737
738 This function handles all config file types. It ignores comments and
739 any #defines which don't start with CONFIG_.
740
741 Args:
742 fname: Filename to read
743
744 Returns:
745 Dictionary:
746 key: Config name (e.g. CONFIG_DM)
747 value: Config value (e.g. 1)
748 """
749 config = {}
750 if os.path.exists(fname):
751 with open(fname) as fd:
752 for line in fd:
753 line = line.strip()
754 if line.startswith('#define'):
755 values = line[8:].split(' ', 1)
756 if len(values) > 1:
757 key, value = values
758 else:
759 key = values[0]
Simon Glasscde5c302016-11-13 14:25:53 -0700760 value = '1' if self.squash_config_y else ''
Simon Glassdb17fb82015-02-05 22:06:15 -0700761 if not key.startswith('CONFIG_'):
762 continue
763 elif not line or line[0] in ['#', '*', '/']:
764 continue
765 else:
766 key, value = line.split('=', 1)
Simon Glasscde5c302016-11-13 14:25:53 -0700767 if self.squash_config_y and value == 'y':
768 value = '1'
Simon Glassdb17fb82015-02-05 22:06:15 -0700769 config[key] = value
770 return config
771
Simon Glassbc74d942023-07-19 17:49:06 -0600772 def _process_environment(self, fname):
Alex Kiernan4059e302018-05-31 04:48:34 +0000773 """Read in a uboot.env file
774
775 This function reads in environment variables from a file.
776
777 Args:
778 fname: Filename to read
779
780 Returns:
781 Dictionary:
782 key: environment variable (e.g. bootlimit)
783 value: value of environment variable (e.g. 1)
784 """
785 environment = {}
786 if os.path.exists(fname):
787 with open(fname) as fd:
788 for line in fd.read().split('\0'):
789 try:
790 key, value = line.split('=', 1)
791 environment[key] = value
792 except ValueError:
793 # ignore lines we can't parse
794 pass
795 return environment
796
Simon Glassbc74d942023-07-19 17:49:06 -0600797 def get_build_outcome(self, commit_upto, target, read_func_sizes,
Alex Kiernan4059e302018-05-31 04:48:34 +0000798 read_config, read_environment):
Simon Glassc05694f2013-04-03 11:07:16 +0000799 """Work out the outcome of a build.
800
801 Args:
802 commit_upto: Commit number to check (0..n-1)
803 target: Target board to check
804 read_func_sizes: True to read function size information
Simon Glassdb17fb82015-02-05 22:06:15 -0700805 read_config: True to read .config and autoconf.h files
Alex Kiernan4059e302018-05-31 04:48:34 +0000806 read_environment: True to read uboot.env files
Simon Glassc05694f2013-04-03 11:07:16 +0000807
808 Returns:
809 Outcome object
810 """
Simon Glassbc74d942023-07-19 17:49:06 -0600811 done_file = self.get_done_file(commit_upto, target)
812 sizes_file = self.get_sizes_file(commit_upto, target)
Simon Glassc05694f2013-04-03 11:07:16 +0000813 sizes = {}
814 func_sizes = {}
Simon Glassdb17fb82015-02-05 22:06:15 -0700815 config = {}
Alex Kiernan4059e302018-05-31 04:48:34 +0000816 environment = {}
Simon Glassc05694f2013-04-03 11:07:16 +0000817 if os.path.exists(done_file):
818 with open(done_file, 'r') as fd:
Simon Glass91c54a42019-04-26 19:02:23 -0600819 try:
820 return_code = int(fd.readline())
821 except ValueError:
822 # The file may be empty due to running out of disk space.
823 # Try a rebuild
824 return_code = 1
Simon Glassc05694f2013-04-03 11:07:16 +0000825 err_lines = []
Simon Glassbc74d942023-07-19 17:49:06 -0600826 err_file = self.get_err_file(commit_upto, target)
Simon Glassc05694f2013-04-03 11:07:16 +0000827 if os.path.exists(err_file):
828 with open(err_file, 'r') as fd:
Simon Glassbc74d942023-07-19 17:49:06 -0600829 err_lines = self.filter_errors(fd.readlines())
Simon Glassc05694f2013-04-03 11:07:16 +0000830
831 # Decide whether the build was ok, failed or created warnings
832 if return_code:
833 rc = OUTCOME_ERROR
834 elif len(err_lines):
835 rc = OUTCOME_WARNING
836 else:
837 rc = OUTCOME_OK
838
839 # Convert size information to our simple format
840 if os.path.exists(sizes_file):
841 with open(sizes_file, 'r') as fd:
842 for line in fd.readlines():
843 values = line.split()
844 rodata = 0
845 if len(values) > 6:
846 rodata = int(values[6], 16)
847 size_dict = {
848 'all' : int(values[0]) + int(values[1]) +
849 int(values[2]),
850 'text' : int(values[0]) - rodata,
851 'data' : int(values[1]),
852 'bss' : int(values[2]),
853 'rodata' : rodata,
854 }
855 sizes[values[5]] = size_dict
856
857 if read_func_sizes:
Simon Glassbc74d942023-07-19 17:49:06 -0600858 pattern = self.get_func_sizes_file(commit_upto, target, '*')
Simon Glassc05694f2013-04-03 11:07:16 +0000859 for fname in glob.glob(pattern):
860 with open(fname, 'r') as fd:
861 dict_name = os.path.basename(fname).replace('.sizes',
862 '')
Simon Glassbc74d942023-07-19 17:49:06 -0600863 func_sizes[dict_name] = self.read_func_sizes(fname, fd)
Simon Glassc05694f2013-04-03 11:07:16 +0000864
Simon Glassdb17fb82015-02-05 22:06:15 -0700865 if read_config:
Simon Glassbc74d942023-07-19 17:49:06 -0600866 output_dir = self.get_build_dir(commit_upto, target)
Simon Glasscde5c302016-11-13 14:25:53 -0700867 for name in self.config_filenames:
Simon Glassdb17fb82015-02-05 22:06:15 -0700868 fname = os.path.join(output_dir, name)
Simon Glassbc74d942023-07-19 17:49:06 -0600869 config[name] = self._process_config(fname)
Simon Glassdb17fb82015-02-05 22:06:15 -0700870
Alex Kiernan4059e302018-05-31 04:48:34 +0000871 if read_environment:
Simon Glassbc74d942023-07-19 17:49:06 -0600872 output_dir = self.get_build_dir(commit_upto, target)
Alex Kiernan4059e302018-05-31 04:48:34 +0000873 fname = os.path.join(output_dir, 'uboot.env')
Simon Glassbc74d942023-07-19 17:49:06 -0600874 environment = self._process_environment(fname)
Alex Kiernan4059e302018-05-31 04:48:34 +0000875
876 return Builder.Outcome(rc, err_lines, sizes, func_sizes, config,
877 environment)
Simon Glassc05694f2013-04-03 11:07:16 +0000878
Alex Kiernan4059e302018-05-31 04:48:34 +0000879 return Builder.Outcome(OUTCOME_UNKNOWN, [], {}, {}, {}, {})
Simon Glassc05694f2013-04-03 11:07:16 +0000880
Simon Glassbc74d942023-07-19 17:49:06 -0600881 def get_result_summary(self, boards_selected, commit_upto, read_func_sizes,
Alex Kiernan4059e302018-05-31 04:48:34 +0000882 read_config, read_environment):
Simon Glassc05694f2013-04-03 11:07:16 +0000883 """Calculate a summary of the results of building a commit.
884
885 Args:
886 board_selected: Dict containing boards to summarise
887 commit_upto: Commit number to summarize (0..self.count-1)
888 read_func_sizes: True to read function size information
Simon Glassdb17fb82015-02-05 22:06:15 -0700889 read_config: True to read .config and autoconf.h files
Alex Kiernan4059e302018-05-31 04:48:34 +0000890 read_environment: True to read uboot.env files
Simon Glassc05694f2013-04-03 11:07:16 +0000891
892 Returns:
893 Tuple:
Simon Glass6c435622022-07-11 19:03:56 -0600894 Dict containing boards which built this commit:
895 key: board.target
896 value: Builder.Outcome object
Simon Glass03749d42014-08-28 09:43:44 -0600897 List containing a summary of error lines
Simon Glass3394c9f2014-08-28 09:43:43 -0600898 Dict keyed by error line, containing a list of the Board
899 objects with that error
Simon Glass03749d42014-08-28 09:43:44 -0600900 List containing a summary of warning lines
901 Dict keyed by error line, containing a list of the Board
902 objects with that warning
Simon Glasscad8abf2015-08-25 21:52:14 -0600903 Dictionary keyed by board.target. Each value is a dictionary:
904 key: filename - e.g. '.config'
Simon Glassdb17fb82015-02-05 22:06:15 -0700905 value is itself a dictionary:
906 key: config name
907 value: config value
Alex Kiernan4059e302018-05-31 04:48:34 +0000908 Dictionary keyed by board.target. Each value is a dictionary:
909 key: environment variable
910 value: value of environment variable
Simon Glassc05694f2013-04-03 11:07:16 +0000911 """
Simon Glassbc74d942023-07-19 17:49:06 -0600912 def add_line(lines_summary, lines_boards, line, board):
Simon Glass03749d42014-08-28 09:43:44 -0600913 line = line.rstrip()
914 if line in lines_boards:
915 lines_boards[line].append(board)
916 else:
917 lines_boards[line] = [board]
918 lines_summary.append(line)
919
Simon Glassc05694f2013-04-03 11:07:16 +0000920 board_dict = {}
921 err_lines_summary = []
Simon Glass3394c9f2014-08-28 09:43:43 -0600922 err_lines_boards = {}
Simon Glass03749d42014-08-28 09:43:44 -0600923 warn_lines_summary = []
924 warn_lines_boards = {}
Simon Glassdb17fb82015-02-05 22:06:15 -0700925 config = {}
Alex Kiernan4059e302018-05-31 04:48:34 +0000926 environment = {}
Simon Glassc05694f2013-04-03 11:07:16 +0000927
Simon Glass8132f982022-07-11 19:03:57 -0600928 for brd in boards_selected.values():
Simon Glassbc74d942023-07-19 17:49:06 -0600929 outcome = self.get_build_outcome(commit_upto, brd.target,
Alex Kiernan4059e302018-05-31 04:48:34 +0000930 read_func_sizes, read_config,
931 read_environment)
Simon Glass8132f982022-07-11 19:03:57 -0600932 board_dict[brd.target] = outcome
Simon Glass03749d42014-08-28 09:43:44 -0600933 last_func = None
934 last_was_warning = False
935 for line in outcome.err_lines:
936 if line:
937 if (self._re_function.match(line) or
938 self._re_files.match(line)):
939 last_func = line
Simon Glass3394c9f2014-08-28 09:43:43 -0600940 else:
Simon Glass0db94432018-11-06 16:02:11 -0700941 is_warning = (self._re_warning.match(line) or
942 self._re_dtb_warning.match(line))
Simon Glass03749d42014-08-28 09:43:44 -0600943 is_note = self._re_note.match(line)
944 if is_warning or (last_was_warning and is_note):
945 if last_func:
Simon Glassbc74d942023-07-19 17:49:06 -0600946 add_line(warn_lines_summary, warn_lines_boards,
Simon Glass8132f982022-07-11 19:03:57 -0600947 last_func, brd)
Simon Glassbc74d942023-07-19 17:49:06 -0600948 add_line(warn_lines_summary, warn_lines_boards,
Simon Glass8132f982022-07-11 19:03:57 -0600949 line, brd)
Simon Glass03749d42014-08-28 09:43:44 -0600950 else:
951 if last_func:
Simon Glassbc74d942023-07-19 17:49:06 -0600952 add_line(err_lines_summary, err_lines_boards,
Simon Glass8132f982022-07-11 19:03:57 -0600953 last_func, brd)
Simon Glassbc74d942023-07-19 17:49:06 -0600954 add_line(err_lines_summary, err_lines_boards,
Simon Glass8132f982022-07-11 19:03:57 -0600955 line, brd)
Simon Glass03749d42014-08-28 09:43:44 -0600956 last_was_warning = is_warning
957 last_func = None
Simon Glass8132f982022-07-11 19:03:57 -0600958 tconfig = Config(self.config_filenames, brd.target)
Simon Glasscde5c302016-11-13 14:25:53 -0700959 for fname in self.config_filenames:
Simon Glassdb17fb82015-02-05 22:06:15 -0700960 if outcome.config:
Simon Glassc78ed662019-10-31 07:42:53 -0600961 for key, value in outcome.config[fname].items():
Simon Glassbc74d942023-07-19 17:49:06 -0600962 tconfig.add(fname, key, value)
Simon Glass8132f982022-07-11 19:03:57 -0600963 config[brd.target] = tconfig
Simon Glassdb17fb82015-02-05 22:06:15 -0700964
Simon Glass8132f982022-07-11 19:03:57 -0600965 tenvironment = Environment(brd.target)
Alex Kiernan4059e302018-05-31 04:48:34 +0000966 if outcome.environment:
Simon Glassc78ed662019-10-31 07:42:53 -0600967 for key, value in outcome.environment.items():
Simon Glassbc74d942023-07-19 17:49:06 -0600968 tenvironment.add(key, value)
Simon Glass8132f982022-07-11 19:03:57 -0600969 environment[brd.target] = tenvironment
Alex Kiernan4059e302018-05-31 04:48:34 +0000970
Simon Glass03749d42014-08-28 09:43:44 -0600971 return (board_dict, err_lines_summary, err_lines_boards,
Alex Kiernan4059e302018-05-31 04:48:34 +0000972 warn_lines_summary, warn_lines_boards, config, environment)
Simon Glassc05694f2013-04-03 11:07:16 +0000973
Simon Glassbc74d942023-07-19 17:49:06 -0600974 def add_outcome(self, board_dict, arch_list, changes, char, color):
Simon Glassc05694f2013-04-03 11:07:16 +0000975 """Add an output to our list of outcomes for each architecture
976
977 This simple function adds failing boards (changes) to the
978 relevant architecture string, so we can print the results out
979 sorted by architecture.
980
981 Args:
982 board_dict: Dict containing all boards
983 arch_list: Dict keyed by arch name. Value is a string containing
984 a list of board names which failed for that arch.
985 changes: List of boards to add to arch_list
986 color: terminal.Colour object
987 """
988 done_arch = {}
989 for target in changes:
990 if target in board_dict:
991 arch = board_dict[target].arch
992 else:
993 arch = 'unknown'
Simon Glassf45d3742022-01-29 14:14:17 -0700994 str = self.col.build(color, ' ' + target)
Simon Glassc05694f2013-04-03 11:07:16 +0000995 if not arch in done_arch:
Simon Glassf45d3742022-01-29 14:14:17 -0700996 str = ' %s %s' % (self.col.build(color, char), str)
Simon Glassc05694f2013-04-03 11:07:16 +0000997 done_arch[arch] = True
998 if not arch in arch_list:
999 arch_list[arch] = str
1000 else:
1001 arch_list[arch] += str
1002
1003
Simon Glassbc74d942023-07-19 17:49:06 -06001004 def colour_num(self, num):
Simon Glassc05694f2013-04-03 11:07:16 +00001005 color = self.col.RED if num > 0 else self.col.GREEN
1006 if num == 0:
1007 return '0'
Simon Glassf45d3742022-01-29 14:14:17 -07001008 return self.col.build(color, str(num))
Simon Glassc05694f2013-04-03 11:07:16 +00001009
Simon Glassbc74d942023-07-19 17:49:06 -06001010 def reset_result_summary(self, board_selected):
Simon Glassc05694f2013-04-03 11:07:16 +00001011 """Reset the results summary ready for use.
1012
1013 Set up the base board list to be all those selected, and set the
1014 error lines to empty.
1015
Simon Glassbc74d942023-07-19 17:49:06 -06001016 Following this, calls to print_result_summary() will use this
Simon Glassc05694f2013-04-03 11:07:16 +00001017 information to work out what has changed.
1018
1019 Args:
1020 board_selected: Dict containing boards to summarise, keyed by
1021 board.target
1022 """
1023 self._base_board_dict = {}
Simon Glass8132f982022-07-11 19:03:57 -06001024 for brd in board_selected:
1025 self._base_board_dict[brd] = Builder.Outcome(0, [], [], {}, {}, {})
Simon Glassc05694f2013-04-03 11:07:16 +00001026 self._base_err_lines = []
Simon Glass03749d42014-08-28 09:43:44 -06001027 self._base_warn_lines = []
1028 self._base_err_line_boards = {}
1029 self._base_warn_line_boards = {}
Simon Glasscad8abf2015-08-25 21:52:14 -06001030 self._base_config = None
Alex Kiernan4059e302018-05-31 04:48:34 +00001031 self._base_environment = None
Simon Glassc05694f2013-04-03 11:07:16 +00001032
Simon Glassbc74d942023-07-19 17:49:06 -06001033 def print_func_size_detail(self, fname, old, new):
Simon Glassc05694f2013-04-03 11:07:16 +00001034 grow, shrink, add, remove, up, down = 0, 0, 0, 0, 0, 0
1035 delta, common = [], {}
1036
1037 for a in old:
1038 if a in new:
1039 common[a] = 1
1040
1041 for name in old:
1042 if name not in common:
1043 remove += 1
1044 down += old[name]
1045 delta.append([-old[name], name])
1046
1047 for name in new:
1048 if name not in common:
1049 add += 1
1050 up += new[name]
1051 delta.append([new[name], name])
1052
1053 for name in common:
1054 diff = new.get(name, 0) - old.get(name, 0)
1055 if diff > 0:
1056 grow, up = grow + 1, up + diff
1057 elif diff < 0:
1058 shrink, down = shrink + 1, down - diff
1059 delta.append([diff, name])
1060
1061 delta.sort()
1062 delta.reverse()
1063
1064 args = [add, -remove, grow, -shrink, up, -down, up - down]
Tom Rini0b48cd62017-05-22 13:48:52 -04001065 if max(args) == 0 and min(args) == 0:
Simon Glassc05694f2013-04-03 11:07:16 +00001066 return
Simon Glassbc74d942023-07-19 17:49:06 -06001067 args = [self.colour_num(x) for x in args]
Simon Glassc05694f2013-04-03 11:07:16 +00001068 indent = ' ' * 15
Simon Glass02811582022-01-29 14:14:18 -07001069 tprint('%s%s: add: %s/%s, grow: %s/%s bytes: %s/%s (%s)' %
Simon Glassf45d3742022-01-29 14:14:17 -07001070 tuple([indent, self.col.build(self.col.YELLOW, fname)] + args))
Simon Glass02811582022-01-29 14:14:18 -07001071 tprint('%s %-38s %7s %7s %+7s' % (indent, 'function', 'old', 'new',
Simon Glass4433aa92014-09-05 19:00:07 -06001072 'delta'))
Simon Glassc05694f2013-04-03 11:07:16 +00001073 for diff, name in delta:
1074 if diff:
1075 color = self.col.RED if diff > 0 else self.col.GREEN
1076 msg = '%s %-38s %7s %7s %+7d' % (indent, name,
1077 old.get(name, '-'), new.get(name,'-'), diff)
Simon Glass02811582022-01-29 14:14:18 -07001078 tprint(msg, colour=color)
Simon Glassc05694f2013-04-03 11:07:16 +00001079
1080
Simon Glassbc74d942023-07-19 17:49:06 -06001081 def print_size_detail(self, target_list, show_bloat):
Simon Glassc05694f2013-04-03 11:07:16 +00001082 """Show details size information for each board
1083
1084 Args:
1085 target_list: List of targets, each a dict containing:
1086 'target': Target name
1087 'total_diff': Total difference in bytes across all areas
1088 <part_name>: Difference for that part
1089 show_bloat: Show detail for each function
1090 """
1091 targets_by_diff = sorted(target_list, reverse=True,
1092 key=lambda x: x['_total_diff'])
1093 for result in targets_by_diff:
1094 printed_target = False
1095 for name in sorted(result):
1096 diff = result[name]
1097 if name.startswith('_'):
1098 continue
1099 if diff != 0:
1100 color = self.col.RED if diff > 0 else self.col.GREEN
1101 msg = ' %s %+d' % (name, diff)
1102 if not printed_target:
Simon Glass02811582022-01-29 14:14:18 -07001103 tprint('%10s %-15s:' % ('', result['_target']),
Simon Glass4433aa92014-09-05 19:00:07 -06001104 newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001105 printed_target = True
Simon Glass02811582022-01-29 14:14:18 -07001106 tprint(msg, colour=color, newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001107 if printed_target:
Simon Glass02811582022-01-29 14:14:18 -07001108 tprint()
Simon Glassc05694f2013-04-03 11:07:16 +00001109 if show_bloat:
1110 target = result['_target']
1111 outcome = result['_outcome']
1112 base_outcome = self._base_board_dict[target]
1113 for fname in outcome.func_sizes:
Simon Glassbc74d942023-07-19 17:49:06 -06001114 self.print_func_size_detail(fname,
Simon Glassc05694f2013-04-03 11:07:16 +00001115 base_outcome.func_sizes[fname],
1116 outcome.func_sizes[fname])
1117
1118
Simon Glassbc74d942023-07-19 17:49:06 -06001119 def print_size_summary(self, board_selected, board_dict, show_detail,
Simon Glassc05694f2013-04-03 11:07:16 +00001120 show_bloat):
1121 """Print a summary of image sizes broken down by section.
1122
1123 The summary takes the form of one line per architecture. The
1124 line contains deltas for each of the sections (+ means the section
Flavio Suligoie5e3c112020-01-29 09:56:05 +01001125 got bigger, - means smaller). The numbers are the average number
Simon Glassc05694f2013-04-03 11:07:16 +00001126 of bytes that a board in this section increased by.
1127
1128 For example:
1129 powerpc: (622 boards) text -0.0
1130 arm: (285 boards) text -0.0
Simon Glassc05694f2013-04-03 11:07:16 +00001131
1132 Args:
1133 board_selected: Dict containing boards to summarise, keyed by
1134 board.target
1135 board_dict: Dict containing boards for which we built this
1136 commit, keyed by board.target. The value is an Outcome object.
Simon Glassb4002462020-03-18 09:42:43 -06001137 show_detail: Show size delta detail for each board
Simon Glassc05694f2013-04-03 11:07:16 +00001138 show_bloat: Show detail for each function
1139 """
1140 arch_list = {}
1141 arch_count = {}
1142
1143 # Calculate changes in size for different image parts
1144 # The previous sizes are in Board.sizes, for each board
1145 for target in board_dict:
1146 if target not in board_selected:
1147 continue
1148 base_sizes = self._base_board_dict[target].sizes
1149 outcome = board_dict[target]
1150 sizes = outcome.sizes
1151
1152 # Loop through the list of images, creating a dict of size
1153 # changes for each image/part. We end up with something like
1154 # {'target' : 'snapper9g45, 'data' : 5, 'u-boot-spl:text' : -4}
1155 # which means that U-Boot data increased by 5 bytes and SPL
1156 # text decreased by 4.
1157 err = {'_target' : target}
1158 for image in sizes:
1159 if image in base_sizes:
1160 base_image = base_sizes[image]
1161 # Loop through the text, data, bss parts
1162 for part in sorted(sizes[image]):
1163 diff = sizes[image][part] - base_image[part]
1164 col = None
1165 if diff:
1166 if image == 'u-boot':
1167 name = part
1168 else:
1169 name = image + ':' + part
1170 err[name] = diff
1171 arch = board_selected[target].arch
1172 if not arch in arch_count:
1173 arch_count[arch] = 1
1174 else:
1175 arch_count[arch] += 1
1176 if not sizes:
1177 pass # Only add to our list when we have some stats
1178 elif not arch in arch_list:
1179 arch_list[arch] = [err]
1180 else:
1181 arch_list[arch].append(err)
1182
1183 # We now have a list of image size changes sorted by arch
1184 # Print out a summary of these
Simon Glassc78ed662019-10-31 07:42:53 -06001185 for arch, target_list in arch_list.items():
Simon Glassc05694f2013-04-03 11:07:16 +00001186 # Get total difference for each type
1187 totals = {}
1188 for result in target_list:
1189 total = 0
Simon Glassc78ed662019-10-31 07:42:53 -06001190 for name, diff in result.items():
Simon Glassc05694f2013-04-03 11:07:16 +00001191 if name.startswith('_'):
1192 continue
1193 total += diff
1194 if name in totals:
1195 totals[name] += diff
1196 else:
1197 totals[name] = diff
1198 result['_total_diff'] = total
1199 result['_outcome'] = board_dict[result['_target']]
1200
1201 count = len(target_list)
1202 printed_arch = False
1203 for name in sorted(totals):
1204 diff = totals[name]
1205 if diff:
1206 # Display the average difference in this name for this
1207 # architecture
1208 avg_diff = float(diff) / count
1209 color = self.col.RED if avg_diff > 0 else self.col.GREEN
1210 msg = ' %s %+1.1f' % (name, avg_diff)
1211 if not printed_arch:
Simon Glass02811582022-01-29 14:14:18 -07001212 tprint('%10s: (for %d/%d boards)' % (arch, count,
Simon Glass4433aa92014-09-05 19:00:07 -06001213 arch_count[arch]), newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001214 printed_arch = True
Simon Glass02811582022-01-29 14:14:18 -07001215 tprint(msg, colour=color, newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001216
1217 if printed_arch:
Simon Glass02811582022-01-29 14:14:18 -07001218 tprint()
Simon Glassc05694f2013-04-03 11:07:16 +00001219 if show_detail:
Simon Glassbc74d942023-07-19 17:49:06 -06001220 self.print_size_detail(target_list, show_bloat)
Simon Glassc05694f2013-04-03 11:07:16 +00001221
1222
Simon Glassbc74d942023-07-19 17:49:06 -06001223 def print_result_summary(self, board_selected, board_dict, err_lines,
Simon Glass03749d42014-08-28 09:43:44 -06001224 err_line_boards, warn_lines, warn_line_boards,
Alex Kiernan4059e302018-05-31 04:48:34 +00001225 config, environment, show_sizes, show_detail,
1226 show_bloat, show_config, show_environment):
Simon Glassc05694f2013-04-03 11:07:16 +00001227 """Compare results with the base results and display delta.
1228
1229 Only boards mentioned in board_selected will be considered. This
1230 function is intended to be called repeatedly with the results of
1231 each commit. It therefore shows a 'diff' between what it saw in
1232 the last call and what it sees now.
1233
1234 Args:
1235 board_selected: Dict containing boards to summarise, keyed by
1236 board.target
1237 board_dict: Dict containing boards for which we built this
1238 commit, keyed by board.target. The value is an Outcome object.
1239 err_lines: A list of errors for this commit, or [] if there is
1240 none, or we don't want to print errors
Simon Glass3394c9f2014-08-28 09:43:43 -06001241 err_line_boards: Dict keyed by error line, containing a list of
1242 the Board objects with that error
Simon Glass03749d42014-08-28 09:43:44 -06001243 warn_lines: A list of warnings for this commit, or [] if there is
1244 none, or we don't want to print errors
1245 warn_line_boards: Dict keyed by warning line, containing a list of
1246 the Board objects with that warning
Simon Glassdb17fb82015-02-05 22:06:15 -07001247 config: Dictionary keyed by filename - e.g. '.config'. Each
1248 value is itself a dictionary:
1249 key: config name
1250 value: config value
Alex Kiernan4059e302018-05-31 04:48:34 +00001251 environment: Dictionary keyed by environment variable, Each
1252 value is the value of environment variable.
Simon Glassc05694f2013-04-03 11:07:16 +00001253 show_sizes: Show image size deltas
Simon Glassb4002462020-03-18 09:42:43 -06001254 show_detail: Show size delta detail for each board if show_sizes
Simon Glassc05694f2013-04-03 11:07:16 +00001255 show_bloat: Show detail for each function
Simon Glassdb17fb82015-02-05 22:06:15 -07001256 show_config: Show config changes
Alex Kiernan4059e302018-05-31 04:48:34 +00001257 show_environment: Show environment changes
Simon Glassc05694f2013-04-03 11:07:16 +00001258 """
Simon Glassbc74d942023-07-19 17:49:06 -06001259 def _board_list(line, line_boards):
Simon Glass3394c9f2014-08-28 09:43:43 -06001260 """Helper function to get a line of boards containing a line
1261
1262 Args:
1263 line: Error line to search for
Simon Glassde0fefc2020-04-09 15:08:36 -06001264 line_boards: boards to search, each a Board
Simon Glass3394c9f2014-08-28 09:43:43 -06001265 Return:
Simon Glassde0fefc2020-04-09 15:08:36 -06001266 List of boards with that error line, or [] if the user has not
1267 requested such a list
Simon Glass3394c9f2014-08-28 09:43:43 -06001268 """
Simon Glass5df45222022-07-11 19:04:00 -06001269 brds = []
Simon Glassde0fefc2020-04-09 15:08:36 -06001270 board_set = set()
Simon Glass3394c9f2014-08-28 09:43:43 -06001271 if self._list_error_boards:
Simon Glass8132f982022-07-11 19:03:57 -06001272 for brd in line_boards[line]:
1273 if not brd in board_set:
Simon Glass5df45222022-07-11 19:04:00 -06001274 brds.append(brd)
Simon Glass8132f982022-07-11 19:03:57 -06001275 board_set.add(brd)
Simon Glass5df45222022-07-11 19:04:00 -06001276 return brds
Simon Glass3394c9f2014-08-28 09:43:43 -06001277
Simon Glassbc74d942023-07-19 17:49:06 -06001278 def _calc_error_delta(base_lines, base_line_boards, lines, line_boards,
Simon Glass03749d42014-08-28 09:43:44 -06001279 char):
Simon Glassde0fefc2020-04-09 15:08:36 -06001280 """Calculate the required output based on changes in errors
1281
1282 Args:
1283 base_lines: List of errors/warnings for previous commit
1284 base_line_boards: Dict keyed by error line, containing a list
1285 of the Board objects with that error in the previous commit
1286 lines: List of errors/warning for this commit, each a str
1287 line_boards: Dict keyed by error line, containing a list
1288 of the Board objects with that error in this commit
1289 char: Character representing error ('') or warning ('w'). The
1290 broken ('+') or fixed ('-') characters are added in this
1291 function
1292
1293 Returns:
1294 Tuple
1295 List of ErrLine objects for 'better' lines
1296 List of ErrLine objects for 'worse' lines
1297 """
Simon Glass03749d42014-08-28 09:43:44 -06001298 better_lines = []
1299 worse_lines = []
1300 for line in lines:
1301 if line not in base_lines:
Simon Glassbc74d942023-07-19 17:49:06 -06001302 errline = ErrLine(char + '+', _board_list(line, line_boards),
Simon Glassde0fefc2020-04-09 15:08:36 -06001303 line)
1304 worse_lines.append(errline)
Simon Glass03749d42014-08-28 09:43:44 -06001305 for line in base_lines:
1306 if line not in lines:
Simon Glassde0fefc2020-04-09 15:08:36 -06001307 errline = ErrLine(char + '-',
Simon Glassbc74d942023-07-19 17:49:06 -06001308 _board_list(line, base_line_boards), line)
Simon Glassde0fefc2020-04-09 15:08:36 -06001309 better_lines.append(errline)
Simon Glass03749d42014-08-28 09:43:44 -06001310 return better_lines, worse_lines
1311
Simon Glassbc74d942023-07-19 17:49:06 -06001312 def _calc_config(delta, name, config):
Simon Glassdb17fb82015-02-05 22:06:15 -07001313 """Calculate configuration changes
1314
1315 Args:
1316 delta: Type of the delta, e.g. '+'
1317 name: name of the file which changed (e.g. .config)
1318 config: configuration change dictionary
1319 key: config name
1320 value: config value
1321 Returns:
1322 String containing the configuration changes which can be
1323 printed
1324 """
1325 out = ''
1326 for key in sorted(config.keys()):
1327 out += '%s=%s ' % (key, config[key])
Simon Glasscad8abf2015-08-25 21:52:14 -06001328 return '%s %s: %s' % (delta, name, out)
Simon Glassdb17fb82015-02-05 22:06:15 -07001329
Simon Glassbc74d942023-07-19 17:49:06 -06001330 def _add_config(lines, name, config_plus, config_minus, config_change):
Simon Glasscad8abf2015-08-25 21:52:14 -06001331 """Add changes in configuration to a list
Simon Glassdb17fb82015-02-05 22:06:15 -07001332
1333 Args:
Simon Glasscad8abf2015-08-25 21:52:14 -06001334 lines: list to add to
1335 name: config file name
Simon Glassdb17fb82015-02-05 22:06:15 -07001336 config_plus: configurations added, dictionary
1337 key: config name
1338 value: config value
1339 config_minus: configurations removed, dictionary
1340 key: config name
1341 value: config value
1342 config_change: configurations changed, dictionary
1343 key: config name
1344 value: config value
1345 """
1346 if config_plus:
Simon Glassbc74d942023-07-19 17:49:06 -06001347 lines.append(_calc_config('+', name, config_plus))
Simon Glassdb17fb82015-02-05 22:06:15 -07001348 if config_minus:
Simon Glassbc74d942023-07-19 17:49:06 -06001349 lines.append(_calc_config('-', name, config_minus))
Simon Glassdb17fb82015-02-05 22:06:15 -07001350 if config_change:
Simon Glassbc74d942023-07-19 17:49:06 -06001351 lines.append(_calc_config('c', name, config_change))
Simon Glasscad8abf2015-08-25 21:52:14 -06001352
Simon Glassbc74d942023-07-19 17:49:06 -06001353 def _output_config_info(lines):
Simon Glasscad8abf2015-08-25 21:52:14 -06001354 for line in lines:
1355 if not line:
1356 continue
1357 if line[0] == '+':
1358 col = self.col.GREEN
1359 elif line[0] == '-':
1360 col = self.col.RED
1361 elif line[0] == 'c':
1362 col = self.col.YELLOW
Simon Glass02811582022-01-29 14:14:18 -07001363 tprint(' ' + line, newline=True, colour=col)
Simon Glasscad8abf2015-08-25 21:52:14 -06001364
Simon Glassbc74d942023-07-19 17:49:06 -06001365 def _output_err_lines(err_lines, colour):
Simon Glassac500222020-04-09 15:08:28 -06001366 """Output the line of error/warning lines, if not empty
1367
1368 Also increments self._error_lines if err_lines not empty
1369
1370 Args:
Simon Glassde0fefc2020-04-09 15:08:36 -06001371 err_lines: List of ErrLine objects, each an error or warning
1372 line, possibly including a list of boards with that
1373 error/warning
Simon Glassac500222020-04-09 15:08:28 -06001374 colour: Colour to use for output
1375 """
1376 if err_lines:
Simon Glassea49f9b2020-04-09 15:08:37 -06001377 out_list = []
Simon Glassde0fefc2020-04-09 15:08:36 -06001378 for line in err_lines:
Simon Glass5df45222022-07-11 19:04:00 -06001379 names = [brd.target for brd in line.brds]
Simon Glass070589b2020-04-09 15:08:38 -06001380 board_str = ' '.join(names) if names else ''
Simon Glassea49f9b2020-04-09 15:08:37 -06001381 if board_str:
Simon Glassf45d3742022-01-29 14:14:17 -07001382 out = self.col.build(colour, line.char + '(')
1383 out += self.col.build(self.col.MAGENTA, board_str,
Simon Glassea49f9b2020-04-09 15:08:37 -06001384 bright=False)
Simon Glassf45d3742022-01-29 14:14:17 -07001385 out += self.col.build(colour, ') %s' % line.errline)
Simon Glassea49f9b2020-04-09 15:08:37 -06001386 else:
Simon Glassf45d3742022-01-29 14:14:17 -07001387 out = self.col.build(colour, line.char + line.errline)
Simon Glassea49f9b2020-04-09 15:08:37 -06001388 out_list.append(out)
Simon Glass02811582022-01-29 14:14:18 -07001389 tprint('\n'.join(out_list))
Simon Glassac500222020-04-09 15:08:28 -06001390 self._error_lines += 1
1391
Simon Glassdb17fb82015-02-05 22:06:15 -07001392
Simon Glass454507f2018-11-06 16:02:12 -07001393 ok_boards = [] # List of boards fixed since last commit
Simon Glass071a1782018-11-06 16:02:13 -07001394 warn_boards = [] # List of boards with warnings since last commit
Simon Glass454507f2018-11-06 16:02:12 -07001395 err_boards = [] # List of new broken boards since last commit
1396 new_boards = [] # List of boards that didn't exist last time
1397 unknown_boards = [] # List of boards that were not built
Simon Glassc05694f2013-04-03 11:07:16 +00001398
1399 for target in board_dict:
1400 if target not in board_selected:
1401 continue
1402
1403 # If the board was built last time, add its outcome to a list
1404 if target in self._base_board_dict:
1405 base_outcome = self._base_board_dict[target].rc
1406 outcome = board_dict[target]
1407 if outcome.rc == OUTCOME_UNKNOWN:
Simon Glass454507f2018-11-06 16:02:12 -07001408 unknown_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001409 elif outcome.rc < base_outcome:
Simon Glass071a1782018-11-06 16:02:13 -07001410 if outcome.rc == OUTCOME_WARNING:
1411 warn_boards.append(target)
1412 else:
1413 ok_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001414 elif outcome.rc > base_outcome:
Simon Glass071a1782018-11-06 16:02:13 -07001415 if outcome.rc == OUTCOME_WARNING:
1416 warn_boards.append(target)
1417 else:
1418 err_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001419 else:
Simon Glass454507f2018-11-06 16:02:12 -07001420 new_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001421
Simon Glassac500222020-04-09 15:08:28 -06001422 # Get a list of errors and warnings that have appeared, and disappeared
Simon Glassbc74d942023-07-19 17:49:06 -06001423 better_err, worse_err = _calc_error_delta(self._base_err_lines,
Simon Glass03749d42014-08-28 09:43:44 -06001424 self._base_err_line_boards, err_lines, err_line_boards, '')
Simon Glassbc74d942023-07-19 17:49:06 -06001425 better_warn, worse_warn = _calc_error_delta(self._base_warn_lines,
Simon Glass03749d42014-08-28 09:43:44 -06001426 self._base_warn_line_boards, warn_lines, warn_line_boards, 'w')
Simon Glassc05694f2013-04-03 11:07:16 +00001427
Simon Glass6c435622022-07-11 19:03:56 -06001428 # For the IDE mode, print out all the output
1429 if self._ide:
1430 outcome = board_dict[target]
1431 for line in outcome.err_lines:
1432 sys.stderr.write(line)
1433
Simon Glassc05694f2013-04-03 11:07:16 +00001434 # Display results by arch
Simon Glass6c435622022-07-11 19:03:56 -06001435 elif any((ok_boards, warn_boards, err_boards, unknown_boards, new_boards,
Simon Glass071a1782018-11-06 16:02:13 -07001436 worse_err, better_err, worse_warn, better_warn)):
Simon Glassc05694f2013-04-03 11:07:16 +00001437 arch_list = {}
Simon Glassbc74d942023-07-19 17:49:06 -06001438 self.add_outcome(board_selected, arch_list, ok_boards, '',
Simon Glassc05694f2013-04-03 11:07:16 +00001439 self.col.GREEN)
Simon Glassbc74d942023-07-19 17:49:06 -06001440 self.add_outcome(board_selected, arch_list, warn_boards, 'w+',
Simon Glass071a1782018-11-06 16:02:13 -07001441 self.col.YELLOW)
Simon Glassbc74d942023-07-19 17:49:06 -06001442 self.add_outcome(board_selected, arch_list, err_boards, '+',
Simon Glassc05694f2013-04-03 11:07:16 +00001443 self.col.RED)
Simon Glassbc74d942023-07-19 17:49:06 -06001444 self.add_outcome(board_selected, arch_list, new_boards, '*', self.col.BLUE)
Simon Glassc05694f2013-04-03 11:07:16 +00001445 if self._show_unknown:
Simon Glassbc74d942023-07-19 17:49:06 -06001446 self.add_outcome(board_selected, arch_list, unknown_boards, '?',
Simon Glassc05694f2013-04-03 11:07:16 +00001447 self.col.MAGENTA)
Simon Glassc78ed662019-10-31 07:42:53 -06001448 for arch, target_list in arch_list.items():
Simon Glass02811582022-01-29 14:14:18 -07001449 tprint('%10s: %s' % (arch, target_list))
Simon Glassbb4dffb2014-08-09 15:33:06 -06001450 self._error_lines += 1
Simon Glassbc74d942023-07-19 17:49:06 -06001451 _output_err_lines(better_err, colour=self.col.GREEN)
1452 _output_err_lines(worse_err, colour=self.col.RED)
1453 _output_err_lines(better_warn, colour=self.col.CYAN)
1454 _output_err_lines(worse_warn, colour=self.col.YELLOW)
Simon Glassc05694f2013-04-03 11:07:16 +00001455
1456 if show_sizes:
Simon Glassbc74d942023-07-19 17:49:06 -06001457 self.print_size_summary(board_selected, board_dict, show_detail,
Simon Glassc05694f2013-04-03 11:07:16 +00001458 show_bloat)
1459
Alex Kiernan4059e302018-05-31 04:48:34 +00001460 if show_environment and self._base_environment:
1461 lines = []
1462
1463 for target in board_dict:
1464 if target not in board_selected:
1465 continue
1466
1467 tbase = self._base_environment[target]
1468 tenvironment = environment[target]
1469 environment_plus = {}
1470 environment_minus = {}
1471 environment_change = {}
1472 base = tbase.environment
Simon Glassc78ed662019-10-31 07:42:53 -06001473 for key, value in tenvironment.environment.items():
Alex Kiernan4059e302018-05-31 04:48:34 +00001474 if key not in base:
1475 environment_plus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001476 for key, value in base.items():
Alex Kiernan4059e302018-05-31 04:48:34 +00001477 if key not in tenvironment.environment:
1478 environment_minus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001479 for key, value in base.items():
Alex Kiernan4059e302018-05-31 04:48:34 +00001480 new_value = tenvironment.environment.get(key)
1481 if new_value and value != new_value:
1482 desc = '%s -> %s' % (value, new_value)
1483 environment_change[key] = desc
1484
Simon Glassbc74d942023-07-19 17:49:06 -06001485 _add_config(lines, target, environment_plus, environment_minus,
Alex Kiernan4059e302018-05-31 04:48:34 +00001486 environment_change)
1487
Simon Glassbc74d942023-07-19 17:49:06 -06001488 _output_config_info(lines)
Alex Kiernan4059e302018-05-31 04:48:34 +00001489
Simon Glasscad8abf2015-08-25 21:52:14 -06001490 if show_config and self._base_config:
1491 summary = {}
1492 arch_config_plus = {}
1493 arch_config_minus = {}
1494 arch_config_change = {}
1495 arch_list = []
1496
1497 for target in board_dict:
1498 if target not in board_selected:
1499 continue
1500 arch = board_selected[target].arch
1501 if arch not in arch_list:
1502 arch_list.append(arch)
1503
1504 for arch in arch_list:
1505 arch_config_plus[arch] = {}
1506 arch_config_minus[arch] = {}
1507 arch_config_change[arch] = {}
Simon Glasscde5c302016-11-13 14:25:53 -07001508 for name in self.config_filenames:
Simon Glasscad8abf2015-08-25 21:52:14 -06001509 arch_config_plus[arch][name] = {}
1510 arch_config_minus[arch][name] = {}
1511 arch_config_change[arch][name] = {}
1512
1513 for target in board_dict:
1514 if target not in board_selected:
Simon Glassdb17fb82015-02-05 22:06:15 -07001515 continue
Simon Glasscad8abf2015-08-25 21:52:14 -06001516
1517 arch = board_selected[target].arch
1518
1519 all_config_plus = {}
1520 all_config_minus = {}
1521 all_config_change = {}
1522 tbase = self._base_config[target]
1523 tconfig = config[target]
1524 lines = []
Simon Glasscde5c302016-11-13 14:25:53 -07001525 for name in self.config_filenames:
Simon Glasscad8abf2015-08-25 21:52:14 -06001526 if not tconfig.config[name]:
1527 continue
1528 config_plus = {}
1529 config_minus = {}
1530 config_change = {}
1531 base = tbase.config[name]
Simon Glassc78ed662019-10-31 07:42:53 -06001532 for key, value in tconfig.config[name].items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001533 if key not in base:
1534 config_plus[key] = value
1535 all_config_plus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001536 for key, value in base.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001537 if key not in tconfig.config[name]:
1538 config_minus[key] = value
1539 all_config_minus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001540 for key, value in base.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001541 new_value = tconfig.config.get(key)
1542 if new_value and value != new_value:
1543 desc = '%s -> %s' % (value, new_value)
1544 config_change[key] = desc
1545 all_config_change[key] = desc
1546
1547 arch_config_plus[arch][name].update(config_plus)
1548 arch_config_minus[arch][name].update(config_minus)
1549 arch_config_change[arch][name].update(config_change)
1550
Simon Glassbc74d942023-07-19 17:49:06 -06001551 _add_config(lines, name, config_plus, config_minus,
Simon Glasscad8abf2015-08-25 21:52:14 -06001552 config_change)
Simon Glassbc74d942023-07-19 17:49:06 -06001553 _add_config(lines, 'all', all_config_plus, all_config_minus,
Simon Glasscad8abf2015-08-25 21:52:14 -06001554 all_config_change)
1555 summary[target] = '\n'.join(lines)
1556
1557 lines_by_target = {}
Simon Glassc78ed662019-10-31 07:42:53 -06001558 for target, lines in summary.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001559 if lines in lines_by_target:
1560 lines_by_target[lines].append(target)
1561 else:
1562 lines_by_target[lines] = [target]
1563
1564 for arch in arch_list:
1565 lines = []
1566 all_plus = {}
1567 all_minus = {}
1568 all_change = {}
Simon Glasscde5c302016-11-13 14:25:53 -07001569 for name in self.config_filenames:
Simon Glasscad8abf2015-08-25 21:52:14 -06001570 all_plus.update(arch_config_plus[arch][name])
1571 all_minus.update(arch_config_minus[arch][name])
1572 all_change.update(arch_config_change[arch][name])
Simon Glassbc74d942023-07-19 17:49:06 -06001573 _add_config(lines, name, arch_config_plus[arch][name],
Simon Glasscad8abf2015-08-25 21:52:14 -06001574 arch_config_minus[arch][name],
1575 arch_config_change[arch][name])
Simon Glassbc74d942023-07-19 17:49:06 -06001576 _add_config(lines, 'all', all_plus, all_minus, all_change)
Simon Glasscad8abf2015-08-25 21:52:14 -06001577 #arch_summary[target] = '\n'.join(lines)
1578 if lines:
Simon Glass02811582022-01-29 14:14:18 -07001579 tprint('%s:' % arch)
Simon Glassbc74d942023-07-19 17:49:06 -06001580 _output_config_info(lines)
Simon Glasscad8abf2015-08-25 21:52:14 -06001581
Simon Glassc78ed662019-10-31 07:42:53 -06001582 for lines, targets in lines_by_target.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001583 if not lines:
1584 continue
Simon Glass02811582022-01-29 14:14:18 -07001585 tprint('%s :' % ' '.join(sorted(targets)))
Simon Glassbc74d942023-07-19 17:49:06 -06001586 _output_config_info(lines.split('\n'))
Simon Glasscad8abf2015-08-25 21:52:14 -06001587
Simon Glassdb17fb82015-02-05 22:06:15 -07001588
Simon Glassc05694f2013-04-03 11:07:16 +00001589 # Save our updated information for the next call to this function
1590 self._base_board_dict = board_dict
1591 self._base_err_lines = err_lines
Simon Glass03749d42014-08-28 09:43:44 -06001592 self._base_warn_lines = warn_lines
1593 self._base_err_line_boards = err_line_boards
1594 self._base_warn_line_boards = warn_line_boards
Simon Glassdb17fb82015-02-05 22:06:15 -07001595 self._base_config = config
Alex Kiernan4059e302018-05-31 04:48:34 +00001596 self._base_environment = environment
Simon Glassc05694f2013-04-03 11:07:16 +00001597
1598 # Get a list of boards that did not get built, if needed
1599 not_built = []
Simon Glass8132f982022-07-11 19:03:57 -06001600 for brd in board_selected:
1601 if not brd in board_dict:
1602 not_built.append(brd)
Simon Glassc05694f2013-04-03 11:07:16 +00001603 if not_built:
Simon Glass02811582022-01-29 14:14:18 -07001604 tprint("Boards not built (%d): %s" % (len(not_built),
Simon Glass4433aa92014-09-05 19:00:07 -06001605 ', '.join(not_built)))
Simon Glassc05694f2013-04-03 11:07:16 +00001606
Simon Glassbc74d942023-07-19 17:49:06 -06001607 def produce_result_summary(self, commit_upto, commits, board_selected):
Simon Glass03749d42014-08-28 09:43:44 -06001608 (board_dict, err_lines, err_line_boards, warn_lines,
Simon Glassbc74d942023-07-19 17:49:06 -06001609 warn_line_boards, config, environment) = self.get_result_summary(
Simon Glass3394c9f2014-08-28 09:43:43 -06001610 board_selected, commit_upto,
Simon Glassdb17fb82015-02-05 22:06:15 -07001611 read_func_sizes=self._show_bloat,
Alex Kiernan4059e302018-05-31 04:48:34 +00001612 read_config=self._show_config,
1613 read_environment=self._show_environment)
Simon Glasseb48bbc2014-08-09 15:33:02 -06001614 if commits:
1615 msg = '%02d: %s' % (commit_upto + 1,
1616 commits[commit_upto].subject)
Simon Glass02811582022-01-29 14:14:18 -07001617 tprint(msg, colour=self.col.BLUE)
Simon Glassbc74d942023-07-19 17:49:06 -06001618 self.print_result_summary(board_selected, board_dict,
Simon Glass3394c9f2014-08-28 09:43:43 -06001619 err_lines if self._show_errors else [], err_line_boards,
Simon Glass03749d42014-08-28 09:43:44 -06001620 warn_lines if self._show_errors else [], warn_line_boards,
Alex Kiernan4059e302018-05-31 04:48:34 +00001621 config, environment, self._show_sizes, self._show_detail,
1622 self._show_bloat, self._show_config, self._show_environment)
Simon Glassc05694f2013-04-03 11:07:16 +00001623
Simon Glassbc74d942023-07-19 17:49:06 -06001624 def show_summary(self, commits, board_selected):
Simon Glassc05694f2013-04-03 11:07:16 +00001625 """Show a build summary for U-Boot for a given board list.
1626
1627 Reset the result summary, then repeatedly call GetResultSummary on
1628 each commit's results, then display the differences we see.
1629
1630 Args:
1631 commit: Commit objects to summarise
1632 board_selected: Dict containing boards to summarise
Simon Glassc05694f2013-04-03 11:07:16 +00001633 """
Simon Glassd326ad72014-08-09 15:32:59 -06001634 self.commit_count = len(commits) if commits else 1
Simon Glassc05694f2013-04-03 11:07:16 +00001635 self.commits = commits
Simon Glassbc74d942023-07-19 17:49:06 -06001636 self.reset_result_summary(board_selected)
Simon Glassbb4dffb2014-08-09 15:33:06 -06001637 self._error_lines = 0
Simon Glassc05694f2013-04-03 11:07:16 +00001638
1639 for commit_upto in range(0, self.commit_count, self._step):
Simon Glassbc74d942023-07-19 17:49:06 -06001640 self.produce_result_summary(commit_upto, commits, board_selected)
Simon Glassbb4dffb2014-08-09 15:33:06 -06001641 if not self._error_lines:
Simon Glass02811582022-01-29 14:14:18 -07001642 tprint('(no errors to report)', colour=self.col.GREEN)
Simon Glassc05694f2013-04-03 11:07:16 +00001643
1644
Simon Glassbc74d942023-07-19 17:49:06 -06001645 def setup_build(self, board_selected, commits):
Simon Glassc05694f2013-04-03 11:07:16 +00001646 """Set up ready to start a build.
1647
1648 Args:
1649 board_selected: Selected boards to build
1650 commits: Selected commits to build
1651 """
1652 # First work out how many commits we will build
Simon Glassc78ed662019-10-31 07:42:53 -06001653 count = (self.commit_count + self._step - 1) // self._step
Simon Glassc05694f2013-04-03 11:07:16 +00001654 self.count = len(board_selected) * count
1655 self.upto = self.warned = self.fail = 0
1656 self._timestamps = collections.deque()
1657
Simon Glassbc74d942023-07-19 17:49:06 -06001658 def get_thread_dir(self, thread_num):
Simon Glassc05694f2013-04-03 11:07:16 +00001659 """Get the directory path to the working dir for a thread.
1660
1661 Args:
Simon Glassc635d892021-01-30 22:17:46 -07001662 thread_num: Number of thread to check (-1 for main process, which
1663 is treated as 0)
Simon Glassc05694f2013-04-03 11:07:16 +00001664 """
Simon Glassb6eb8cf2020-03-18 09:42:42 -06001665 if self.work_in_output:
1666 return self._working_dir
Simon Glassc635d892021-01-30 22:17:46 -07001667 return os.path.join(self._working_dir, '%02d' % max(thread_num, 0))
Simon Glassc05694f2013-04-03 11:07:16 +00001668
Simon Glassbc74d942023-07-19 17:49:06 -06001669 def _prepare_thread(self, thread_num, setup_git):
Simon Glassc05694f2013-04-03 11:07:16 +00001670 """Prepare the working directory for a thread.
1671
1672 This clones or fetches the repo into the thread's work directory.
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001673 Optionally, it can create a linked working tree of the repo in the
1674 thread's work directory instead.
Simon Glassc05694f2013-04-03 11:07:16 +00001675
1676 Args:
1677 thread_num: Thread number (0, 1, ...)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001678 setup_git:
1679 'clone' to set up a git clone
1680 'worktree' to set up a git worktree
Simon Glassc05694f2013-04-03 11:07:16 +00001681 """
Simon Glassbc74d942023-07-19 17:49:06 -06001682 thread_dir = self.get_thread_dir(thread_num)
Simon Glassc5077c32023-07-19 17:49:08 -06001683 builderthread.mkdir(thread_dir)
Simon Glassc05694f2013-04-03 11:07:16 +00001684 git_dir = os.path.join(thread_dir, '.git')
1685
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001686 # Create a worktree or a git repo clone for this thread if it
1687 # doesn't already exist
Simon Glassd326ad72014-08-09 15:32:59 -06001688 if setup_git and self.git_dir:
Simon Glassc05694f2013-04-03 11:07:16 +00001689 src_dir = os.path.abspath(self.git_dir)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001690 if os.path.isdir(git_dir):
1691 # This is a clone of the src_dir repo, we can keep using
1692 # it but need to fetch from src_dir.
Simon Glass02811582022-01-29 14:14:18 -07001693 tprint('\rFetching repo for thread %d' % thread_num,
Simon Glass43054932020-04-09 15:08:43 -06001694 newline=False)
Simon Glass761648b2022-01-29 14:14:11 -07001695 gitutil.fetch(git_dir, thread_dir)
Simon Glass02811582022-01-29 14:14:18 -07001696 terminal.print_clear()
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001697 elif os.path.isfile(git_dir):
1698 # This is a worktree of the src_dir repo, we don't need to
1699 # create it again or update it in any way.
1700 pass
1701 elif os.path.exists(git_dir):
1702 # Don't know what could trigger this, but we probably
1703 # can't create a git worktree/clone here.
1704 raise ValueError('Git dir %s exists, but is not a file '
1705 'or a directory.' % git_dir)
1706 elif setup_git == 'worktree':
Simon Glass02811582022-01-29 14:14:18 -07001707 tprint('\rChecking out worktree for thread %d' % thread_num,
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001708 newline=False)
Simon Glass761648b2022-01-29 14:14:11 -07001709 gitutil.add_worktree(src_dir, thread_dir)
Simon Glass02811582022-01-29 14:14:18 -07001710 terminal.print_clear()
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001711 elif setup_git == 'clone' or setup_git == True:
Simon Glass02811582022-01-29 14:14:18 -07001712 tprint('\rCloning repo for thread %d' % thread_num,
Simon Glass2e2a6e62016-09-18 16:48:31 -06001713 newline=False)
Simon Glass761648b2022-01-29 14:14:11 -07001714 gitutil.clone(src_dir, thread_dir)
Simon Glass02811582022-01-29 14:14:18 -07001715 terminal.print_clear()
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001716 else:
1717 raise ValueError("Can't setup git repo with %s." % setup_git)
Simon Glassc05694f2013-04-03 11:07:16 +00001718
Simon Glassbc74d942023-07-19 17:49:06 -06001719 def _prepare_working_space(self, max_threads, setup_git):
Simon Glassc05694f2013-04-03 11:07:16 +00001720 """Prepare the working directory for use.
1721
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001722 Set up the git repo for each thread. Creates a linked working tree
1723 if git-worktree is available, or clones the repo if it isn't.
Simon Glassc05694f2013-04-03 11:07:16 +00001724
1725 Args:
Simon Glassc635d892021-01-30 22:17:46 -07001726 max_threads: Maximum number of threads we expect to need. If 0 then
1727 1 is set up, since the main process still needs somewhere to
1728 work
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001729 setup_git: True to set up a git worktree or a git clone
Simon Glassc05694f2013-04-03 11:07:16 +00001730 """
Simon Glassc5077c32023-07-19 17:49:08 -06001731 builderthread.mkdir(self._working_dir)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001732 if setup_git and self.git_dir:
1733 src_dir = os.path.abspath(self.git_dir)
Simon Glass761648b2022-01-29 14:14:11 -07001734 if gitutil.check_worktree_is_available(src_dir):
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001735 setup_git = 'worktree'
1736 # If we previously added a worktree but the directory for it
1737 # got deleted, we need to prune its files from the repo so
1738 # that we can check out another in its place.
Simon Glass761648b2022-01-29 14:14:11 -07001739 gitutil.prune_worktrees(src_dir)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +03001740 else:
1741 setup_git = 'clone'
Simon Glassc635d892021-01-30 22:17:46 -07001742
1743 # Always do at least one thread
1744 for thread in range(max(max_threads, 1)):
Simon Glassbc74d942023-07-19 17:49:06 -06001745 self._prepare_thread(thread, setup_git)
Simon Glassc05694f2013-04-03 11:07:16 +00001746
Simon Glassbc74d942023-07-19 17:49:06 -06001747 def _get_output_space_removals(self):
Simon Glassc05694f2013-04-03 11:07:16 +00001748 """Get the output directories ready to receive files.
1749
Simon Glass5dc1ca72020-03-18 09:42:45 -06001750 Figure out what needs to be deleted in the output directory before it
1751 can be used. We only delete old buildman directories which have the
Simon Glass4cb54682023-07-19 17:49:10 -06001752 expected name pattern. See get_output_dir().
Simon Glass5dc1ca72020-03-18 09:42:45 -06001753
1754 Returns:
1755 List of full paths of directories to remove
Simon Glassc05694f2013-04-03 11:07:16 +00001756 """
Simon Glassb9a9b7c2014-12-01 17:33:53 -07001757 if not self.commits:
1758 return
Simon Glassc05694f2013-04-03 11:07:16 +00001759 dir_list = []
1760 for commit_upto in range(self.commit_count):
Simon Glass4cb54682023-07-19 17:49:10 -06001761 dir_list.append(self.get_output_dir(commit_upto))
Simon Glassc05694f2013-04-03 11:07:16 +00001762
Simon Glass83cb6cc2016-09-18 16:48:32 -06001763 to_remove = []
Simon Glassc05694f2013-04-03 11:07:16 +00001764 for dirname in glob.glob(os.path.join(self.base_dir, '*')):
1765 if dirname not in dir_list:
Simon Glass5dc1ca72020-03-18 09:42:45 -06001766 leaf = dirname[len(self.base_dir) + 1:]
Ovidiu Panaitee8e9cb2020-05-15 09:30:12 +03001767 m = re.match('[0-9]+_g[0-9a-f]+_.*', leaf)
Simon Glass5dc1ca72020-03-18 09:42:45 -06001768 if m:
1769 to_remove.append(dirname)
1770 return to_remove
1771
Simon Glassbc74d942023-07-19 17:49:06 -06001772 def _prepare_output_space(self):
Simon Glass5dc1ca72020-03-18 09:42:45 -06001773 """Get the output directories ready to receive files.
1774
1775 We delete any output directories which look like ones we need to
1776 create. Having left over directories is confusing when the user wants
1777 to check the output manually.
1778 """
Simon Glassbc74d942023-07-19 17:49:06 -06001779 to_remove = self._get_output_space_removals()
Simon Glass83cb6cc2016-09-18 16:48:32 -06001780 if to_remove:
Simon Glass02811582022-01-29 14:14:18 -07001781 tprint('Removing %d old build directories...' % len(to_remove),
Simon Glass83cb6cc2016-09-18 16:48:32 -06001782 newline=False)
1783 for dirname in to_remove:
Simon Glassc05694f2013-04-03 11:07:16 +00001784 shutil.rmtree(dirname)
Simon Glass02811582022-01-29 14:14:18 -07001785 terminal.print_clear()
Simon Glassc05694f2013-04-03 11:07:16 +00001786
Simon Glassbc74d942023-07-19 17:49:06 -06001787 def build_boards(self, commits, board_selected, keep_outputs, verbose):
Simon Glassc05694f2013-04-03 11:07:16 +00001788 """Build all commits for a list of boards
1789
1790 Args:
1791 commits: List of commits to be build, each a Commit object
1792 boards_selected: Dict of selected boards, key is target name,
1793 value is Board object
Simon Glassc05694f2013-04-03 11:07:16 +00001794 keep_outputs: True to save build output files
Simon Glass78e418e2014-08-09 15:33:03 -06001795 verbose: Display build results as they are completed
Simon Glassc2f91072014-08-28 09:43:39 -06001796 Returns:
1797 Tuple containing:
1798 - number of boards that failed to build
1799 - number of boards that issued warnings
Simon Glass9bf9a722021-04-11 16:27:27 +12001800 - list of thread exceptions raised
Simon Glassc05694f2013-04-03 11:07:16 +00001801 """
Simon Glassd326ad72014-08-09 15:32:59 -06001802 self.commit_count = len(commits) if commits else 1
Simon Glassc05694f2013-04-03 11:07:16 +00001803 self.commits = commits
Simon Glass78e418e2014-08-09 15:33:03 -06001804 self._verbose = verbose
Simon Glassc05694f2013-04-03 11:07:16 +00001805
Simon Glassbc74d942023-07-19 17:49:06 -06001806 self.reset_result_summary(board_selected)
Simon Glassc5077c32023-07-19 17:49:08 -06001807 builderthread.mkdir(self.base_dir, parents = True)
Simon Glassbc74d942023-07-19 17:49:06 -06001808 self._prepare_working_space(min(self.num_threads, len(board_selected)),
Simon Glassd326ad72014-08-09 15:32:59 -06001809 commits is not None)
Simon Glassbc74d942023-07-19 17:49:06 -06001810 self._prepare_output_space()
Simon Glass6c435622022-07-11 19:03:56 -06001811 if not self._ide:
1812 tprint('\rStarting build...', newline=False)
Simon Glass7190a172023-09-07 10:00:19 -06001813 self._start_time = datetime.now()
Simon Glassbc74d942023-07-19 17:49:06 -06001814 self.setup_build(board_selected, commits)
1815 self.process_result(None)
Simon Glass9bf9a722021-04-11 16:27:27 +12001816 self.thread_exceptions = []
Simon Glassc05694f2013-04-03 11:07:16 +00001817 # Create jobs to build all commits for each board
Simon Glassc78ed662019-10-31 07:42:53 -06001818 for brd in board_selected.values():
Simon Glass4a1e88b2014-08-09 15:33:00 -06001819 job = builderthread.BuilderJob()
Simon Glass8132f982022-07-11 19:03:57 -06001820 job.brd = brd
Simon Glassc05694f2013-04-03 11:07:16 +00001821 job.commits = commits
1822 job.keep_outputs = keep_outputs
Simon Glassb6eb8cf2020-03-18 09:42:42 -06001823 job.work_in_output = self.work_in_output
Simon Glasse5650a82022-01-22 05:07:33 -07001824 job.adjust_cfg = self.adjust_cfg
Simon Glassc05694f2013-04-03 11:07:16 +00001825 job.step = self._step
Simon Glassc635d892021-01-30 22:17:46 -07001826 if self.num_threads:
1827 self.queue.put(job)
1828 else:
Simon Glassc5077c32023-07-19 17:49:08 -06001829 self._single_builder.run_job(job)
Simon Glassc05694f2013-04-03 11:07:16 +00001830
Simon Glassc635d892021-01-30 22:17:46 -07001831 if self.num_threads:
1832 term = threading.Thread(target=self.queue.join)
1833 term.setDaemon(True)
1834 term.start()
1835 while term.is_alive():
1836 term.join(100)
Simon Glassc05694f2013-04-03 11:07:16 +00001837
Simon Glassc635d892021-01-30 22:17:46 -07001838 # Wait until we have processed all output
1839 self.out_queue.join()
Simon Glass6c435622022-07-11 19:03:56 -06001840 if not self._ide:
1841 tprint()
Simon Glass726ae812020-04-09 15:08:47 -06001842
Simon Glass6c435622022-07-11 19:03:56 -06001843 msg = 'Completed: %d total built' % self.count
1844 if self.already_done:
1845 msg += ' (%d previously' % self.already_done
1846 if self.already_done != self.count:
1847 msg += ', %d newly' % (self.count - self.already_done)
1848 msg += ')'
1849 duration = datetime.now() - self._start_time
1850 if duration > timedelta(microseconds=1000000):
1851 if duration.microseconds >= 500000:
1852 duration = duration + timedelta(seconds=1)
1853 duration = duration - timedelta(microseconds=duration.microseconds)
1854 rate = float(self.count) / duration.total_seconds()
1855 msg += ', duration %s, rate %1.2f' % (duration, rate)
1856 tprint(msg)
1857 if self.thread_exceptions:
1858 tprint('Failed: %d thread exceptions' % len(self.thread_exceptions),
1859 colour=self.col.RED)
Simon Glass726ae812020-04-09 15:08:47 -06001860
Simon Glass9bf9a722021-04-11 16:27:27 +12001861 return (self.fail, self.warned, self.thread_exceptions)