blob: ae8eee16e35475af2afd96bcf9f827766d62426a [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 Glass4a1e88b2014-08-09 15:33:00 -060020import builderthread
Simon Glassc05694f2013-04-03 11:07:16 +000021import command
22import gitutil
23import terminal
Simon Glass4433aa92014-09-05 19:00:07 -060024from terminal import Print
Simon Glassc05694f2013-04-03 11:07:16 +000025import toolchain
26
Simon Glassc05694f2013-04-03 11:07:16 +000027"""
28Theory of Operation
29
30Please see README for user documentation, and you should be familiar with
31that before trying to make sense of this.
32
33Buildman works by keeping the machine as busy as possible, building different
34commits for different boards on multiple CPUs at once.
35
36The source repo (self.git_dir) contains all the commits to be built. Each
37thread works on a single board at a time. It checks out the first commit,
38configures it for that board, then builds it. Then it checks out the next
39commit and builds it (typically without re-configuring). When it runs out
40of commits, it gets another job from the builder and starts again with that
41board.
42
43Clearly the builder threads could work either way - they could check out a
44commit and then built it for all boards. Using separate directories for each
45commit/board pair they could leave their build product around afterwards
46also.
47
48The intent behind building a single board for multiple commits, is to make
49use of incremental builds. Since each commit is built incrementally from
50the previous one, builds are faster. Reconfiguring for a different board
51removes all intermediate object files.
52
53Many threads can be working at once, but each has its own working directory.
54When a thread finishes a build, it puts the output files into a result
55directory.
56
57The base directory used by buildman is normally '../<branch>', i.e.
58a directory higher than the source repository and named after the branch
59being built.
60
61Within the base directory, we have one subdirectory for each commit. Within
62that is one subdirectory for each board. Within that is the build output for
63that commit/board combination.
64
65Buildman also create working directories for each thread, in a .bm-work/
66subdirectory in the base dir.
67
68As an example, say we are building branch 'us-net' for boards 'sandbox' and
69'seaboard', and say that us-net has two commits. We will have directories
70like this:
71
72us-net/ base directory
73 01_of_02_g4ed4ebc_net--Add-tftp-speed-/
74 sandbox/
75 u-boot.bin
76 seaboard/
77 u-boot.bin
78 02_of_02_g4ed4ebc_net--Check-tftp-comp/
79 sandbox/
80 u-boot.bin
81 seaboard/
82 u-boot.bin
83 .bm-work/
84 00/ working directory for thread 0 (contains source checkout)
85 build/ build output
86 01/ working directory for thread 1
87 build/ build output
88 ...
89u-boot/ source directory
90 .git/ repository
91"""
92
Simon Glassde0fefc2020-04-09 15:08:36 -060093"""Holds information about a particular error line we are outputing
94
95 char: Character representation: '+': error, '-': fixed error, 'w+': warning,
96 'w-' = fixed warning
97 boards: List of Board objects which have line in the error/warning output
98 errline: The text of the error line
99"""
100ErrLine = collections.namedtuple('ErrLine', 'char,boards,errline')
101
Simon Glassc05694f2013-04-03 11:07:16 +0000102# Possible build outcomes
Simon Glassc78ed662019-10-31 07:42:53 -0600103OUTCOME_OK, OUTCOME_WARNING, OUTCOME_ERROR, OUTCOME_UNKNOWN = list(range(4))
Simon Glassc05694f2013-04-03 11:07:16 +0000104
Simon Glassd214bef2017-04-12 18:23:26 -0600105# Translate a commit subject into a valid filename (and handle unicode)
Simon Glassc78ed662019-10-31 07:42:53 -0600106trans_valid_chars = str.maketrans('/: ', '---')
Simon Glassc05694f2013-04-03 11:07:16 +0000107
Simon Glasscde5c302016-11-13 14:25:53 -0700108BASE_CONFIG_FILENAMES = [
109 'u-boot.cfg', 'u-boot-spl.cfg', 'u-boot-tpl.cfg'
110]
111
112EXTRA_CONFIG_FILENAMES = [
Simon Glassdb17fb82015-02-05 22:06:15 -0700113 '.config', '.config-spl', '.config-tpl',
114 'autoconf.mk', 'autoconf-spl.mk', 'autoconf-tpl.mk',
115 'autoconf.h', 'autoconf-spl.h','autoconf-tpl.h',
Simon Glassdb17fb82015-02-05 22:06:15 -0700116]
117
Simon Glasscad8abf2015-08-25 21:52:14 -0600118class Config:
119 """Holds information about configuration settings for a board."""
Simon Glasscde5c302016-11-13 14:25:53 -0700120 def __init__(self, config_filename, target):
Simon Glasscad8abf2015-08-25 21:52:14 -0600121 self.target = target
122 self.config = {}
Simon Glasscde5c302016-11-13 14:25:53 -0700123 for fname in config_filename:
Simon Glasscad8abf2015-08-25 21:52:14 -0600124 self.config[fname] = {}
125
126 def Add(self, fname, key, value):
127 self.config[fname][key] = value
128
129 def __hash__(self):
130 val = 0
131 for fname in self.config:
Simon Glassc78ed662019-10-31 07:42:53 -0600132 for key, value in self.config[fname].items():
133 print(key, value)
Simon Glasscad8abf2015-08-25 21:52:14 -0600134 val = val ^ hash(key) & hash(value)
135 return val
Simon Glassc05694f2013-04-03 11:07:16 +0000136
Alex Kiernan4059e302018-05-31 04:48:34 +0000137class Environment:
138 """Holds information about environment variables for a board."""
139 def __init__(self, target):
140 self.target = target
141 self.environment = {}
142
143 def Add(self, key, value):
144 self.environment[key] = value
145
Simon Glassc05694f2013-04-03 11:07:16 +0000146class Builder:
147 """Class for building U-Boot for a particular commit.
148
149 Public members: (many should ->private)
Simon Glassc05694f2013-04-03 11:07:16 +0000150 already_done: Number of builds already completed
151 base_dir: Base directory to use for builder
152 checkout: True to check out source, False to skip that step.
153 This is used for testing.
154 col: terminal.Color() object
155 count: Number of commits to build
156 do_make: Method to call to invoke Make
157 fail: Number of builds that failed due to error
158 force_build: Force building even if a build already exists
159 force_config_on_failure: If a commit fails for a board, disable
160 incremental building for the next commit we build for that
161 board, so that we will see all warnings/errors again.
Simon Glass7041c392014-07-13 12:22:31 -0600162 force_build_failures: If a previously-built build (i.e. built on
163 a previous run of buildman) is marked as failed, rebuild it.
Simon Glassc05694f2013-04-03 11:07:16 +0000164 git_dir: Git directory containing source repository
Simon Glassc05694f2013-04-03 11:07:16 +0000165 num_jobs: Number of jobs to run at once (passed to make as -j)
166 num_threads: Number of builder threads to run
167 out_queue: Queue of results to process
168 re_make_err: Compiled regular expression for ignore_lines
169 queue: Queue of jobs to run
170 threads: List of active threads
171 toolchains: Toolchains object to use for building
172 upto: Current commit number we are building (0.count-1)
173 warned: Number of builds that produced at least one warning
Simon Glassf3018b7a2014-07-14 17:51:02 -0600174 force_reconfig: Reconfigure U-Boot on each comiit. This disables
175 incremental building, where buildman reconfigures on the first
176 commit for a baord, and then just does an incremental build for
177 the following commits. In fact buildman will reconfigure and
178 retry for any failing commits, so generally the only effect of
179 this option is to slow things down.
Simon Glass38df2e22014-07-14 17:51:03 -0600180 in_tree: Build U-Boot in-tree instead of specifying an output
181 directory separate from the source code. This option is really
182 only useful for testing in-tree builds.
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600183 work_in_output: Use the output directory as the work directory and
184 don't write to a separate output directory.
Simon Glassc05694f2013-04-03 11:07:16 +0000185
186 Private members:
187 _base_board_dict: Last-summarised Dict of boards
188 _base_err_lines: Last-summarised list of errors
Simon Glass03749d42014-08-28 09:43:44 -0600189 _base_warn_lines: Last-summarised list of warnings
Simon Glassc05694f2013-04-03 11:07:16 +0000190 _build_period_us: Time taken for a single build (float object).
191 _complete_delay: Expected delay until completion (timedelta)
192 _next_delay_update: Next time we plan to display a progress update
193 (datatime)
194 _show_unknown: Show unknown boards (those not built) in summary
195 _timestamps: List of timestamps for the completion of the last
196 last _timestamp_count builds. Each is a datetime object.
197 _timestamp_count: Number of timestamps to keep in our list.
198 _working_dir: Base working directory containing all threads
199 """
200 class Outcome:
201 """Records a build outcome for a single make invocation
202
203 Public Members:
204 rc: Outcome value (OUTCOME_...)
205 err_lines: List of error lines or [] if none
206 sizes: Dictionary of image size information, keyed by filename
207 - Each value is itself a dictionary containing
208 values for 'text', 'data' and 'bss', being the integer
209 size in bytes of each section.
210 func_sizes: Dictionary keyed by filename - e.g. 'u-boot'. Each
211 value is itself a dictionary:
212 key: function name
213 value: Size of function in bytes
Simon Glassdb17fb82015-02-05 22:06:15 -0700214 config: Dictionary keyed by filename - e.g. '.config'. Each
215 value is itself a dictionary:
216 key: config name
217 value: config value
Alex Kiernan4059e302018-05-31 04:48:34 +0000218 environment: Dictionary keyed by environment variable, Each
219 value is the value of environment variable.
Simon Glassc05694f2013-04-03 11:07:16 +0000220 """
Alex Kiernan4059e302018-05-31 04:48:34 +0000221 def __init__(self, rc, err_lines, sizes, func_sizes, config,
222 environment):
Simon Glassc05694f2013-04-03 11:07:16 +0000223 self.rc = rc
224 self.err_lines = err_lines
225 self.sizes = sizes
226 self.func_sizes = func_sizes
Simon Glassdb17fb82015-02-05 22:06:15 -0700227 self.config = config
Alex Kiernan4059e302018-05-31 04:48:34 +0000228 self.environment = environment
Simon Glassc05694f2013-04-03 11:07:16 +0000229
230 def __init__(self, toolchains, base_dir, git_dir, num_threads, num_jobs,
Simon Glasse87bde12014-12-01 17:33:55 -0700231 gnu_make='make', checkout=True, show_unknown=True, step=1,
Stephen Warren97c96902016-04-11 10:48:44 -0600232 no_subdirs=False, full_path=False, verbose_build=False,
Simon Glass739e8512016-11-13 14:25:51 -0700233 incremental=False, per_board_out_dir=False,
Daniel Schwierzeck20e2ea92018-01-26 16:31:05 +0100234 config_only=False, squash_config_y=False,
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600235 warnings_as_errors=False, work_in_output=False):
Simon Glassc05694f2013-04-03 11:07:16 +0000236 """Create a new Builder object
237
238 Args:
239 toolchains: Toolchains object to use for building
240 base_dir: Base directory to use for builder
241 git_dir: Git directory containing source repository
242 num_threads: Number of builder threads to run
243 num_jobs: Number of jobs to run at once (passed to make as -j)
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900244 gnu_make: the command name of GNU Make.
Simon Glassc05694f2013-04-03 11:07:16 +0000245 checkout: True to check out source, False to skip that step.
246 This is used for testing.
247 show_unknown: Show unknown boards (those not built) in summary
248 step: 1 to process every commit, n to process every nth commit
Simon Glassd48a46c2014-12-01 17:34:00 -0700249 no_subdirs: Don't create subdirectories when building current
250 source for a single board
251 full_path: Return the full path in CROSS_COMPILE and don't set
252 PATH
Simon Glass655b6102014-12-01 17:34:07 -0700253 verbose_build: Run build with V=1 and don't use 'make -s'
Stephen Warren97c96902016-04-11 10:48:44 -0600254 incremental: Always perform incremental builds; don't run make
255 mrproper when configuring
256 per_board_out_dir: Build in a separate persistent directory per
257 board rather than a thread-specific directory
Simon Glass739e8512016-11-13 14:25:51 -0700258 config_only: Only configure each build, don't build it
Simon Glasscde5c302016-11-13 14:25:53 -0700259 squash_config_y: Convert CONFIG options with the value 'y' to '1'
Daniel Schwierzeck20e2ea92018-01-26 16:31:05 +0100260 warnings_as_errors: Treat all compiler warnings as errors
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600261 work_in_output: Use the output directory as the work directory and
262 don't write to a separate output directory.
Simon Glassc05694f2013-04-03 11:07:16 +0000263 """
264 self.toolchains = toolchains
265 self.base_dir = base_dir
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600266 if work_in_output:
267 self._working_dir = base_dir
268 else:
269 self._working_dir = os.path.join(base_dir, '.bm-work')
Simon Glassc05694f2013-04-03 11:07:16 +0000270 self.threads = []
Simon Glassc05694f2013-04-03 11:07:16 +0000271 self.do_make = self.Make
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900272 self.gnu_make = gnu_make
Simon Glassc05694f2013-04-03 11:07:16 +0000273 self.checkout = checkout
274 self.num_threads = num_threads
275 self.num_jobs = num_jobs
276 self.already_done = 0
277 self.force_build = False
278 self.git_dir = git_dir
279 self._show_unknown = show_unknown
280 self._timestamp_count = 10
281 self._build_period_us = None
282 self._complete_delay = None
283 self._next_delay_update = datetime.now()
284 self.force_config_on_failure = True
Simon Glass7041c392014-07-13 12:22:31 -0600285 self.force_build_failures = False
Simon Glassf3018b7a2014-07-14 17:51:02 -0600286 self.force_reconfig = False
Simon Glassc05694f2013-04-03 11:07:16 +0000287 self._step = step
Simon Glass38df2e22014-07-14 17:51:03 -0600288 self.in_tree = False
Simon Glassbb4dffb2014-08-09 15:33:06 -0600289 self._error_lines = 0
Simon Glasse87bde12014-12-01 17:33:55 -0700290 self.no_subdirs = no_subdirs
Simon Glassd48a46c2014-12-01 17:34:00 -0700291 self.full_path = full_path
Simon Glass655b6102014-12-01 17:34:07 -0700292 self.verbose_build = verbose_build
Simon Glass739e8512016-11-13 14:25:51 -0700293 self.config_only = config_only
Simon Glasscde5c302016-11-13 14:25:53 -0700294 self.squash_config_y = squash_config_y
295 self.config_filenames = BASE_CONFIG_FILENAMES
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600296 self.work_in_output = work_in_output
Simon Glasscde5c302016-11-13 14:25:53 -0700297 if not self.squash_config_y:
298 self.config_filenames += EXTRA_CONFIG_FILENAMES
Simon Glassc05694f2013-04-03 11:07:16 +0000299
Daniel Schwierzeck20e2ea92018-01-26 16:31:05 +0100300 self.warnings_as_errors = warnings_as_errors
Simon Glassc05694f2013-04-03 11:07:16 +0000301 self.col = terminal.Color()
302
Simon Glass03749d42014-08-28 09:43:44 -0600303 self._re_function = re.compile('(.*): In function.*')
304 self._re_files = re.compile('In file included from.*')
305 self._re_warning = re.compile('(.*):(\d*):(\d*): warning: .*')
Simon Glass0db94432018-11-06 16:02:11 -0700306 self._re_dtb_warning = re.compile('(.*): Warning .*')
Simon Glass03749d42014-08-28 09:43:44 -0600307 self._re_note = re.compile('(.*):(\d*):(\d*): note: this is the location of the previous.*')
308
Simon Glassc78ed662019-10-31 07:42:53 -0600309 self.queue = queue.Queue()
310 self.out_queue = queue.Queue()
Simon Glassc05694f2013-04-03 11:07:16 +0000311 for i in range(self.num_threads):
Stephen Warren97c96902016-04-11 10:48:44 -0600312 t = builderthread.BuilderThread(self, i, incremental,
313 per_board_out_dir)
Simon Glassc05694f2013-04-03 11:07:16 +0000314 t.setDaemon(True)
315 t.start()
316 self.threads.append(t)
317
Simon Glass4a1e88b2014-08-09 15:33:00 -0600318 t = builderthread.ResultThread(self)
Simon Glassc05694f2013-04-03 11:07:16 +0000319 t.setDaemon(True)
320 t.start()
321 self.threads.append(t)
322
323 ignore_lines = ['(make.*Waiting for unfinished)', '(Segmentation fault)']
324 self.re_make_err = re.compile('|'.join(ignore_lines))
325
Simon Glass205ac042016-09-18 16:48:37 -0600326 # Handle existing graceful with SIGINT / Ctrl-C
327 signal.signal(signal.SIGINT, self.signal_handler)
328
Simon Glassc05694f2013-04-03 11:07:16 +0000329 def __del__(self):
330 """Get rid of all threads created by the builder"""
331 for t in self.threads:
332 del t
333
Simon Glass205ac042016-09-18 16:48:37 -0600334 def signal_handler(self, signal, frame):
335 sys.exit(1)
336
Simon Glasseb48bbc2014-08-09 15:33:02 -0600337 def SetDisplayOptions(self, show_errors=False, show_sizes=False,
Simon Glass3394c9f2014-08-28 09:43:43 -0600338 show_detail=False, show_bloat=False,
Alex Kiernan4059e302018-05-31 04:48:34 +0000339 list_error_boards=False, show_config=False,
340 show_environment=False):
Simon Glasseb48bbc2014-08-09 15:33:02 -0600341 """Setup display options for the builder.
342
343 show_errors: True to show summarised error/warning info
344 show_sizes: Show size deltas
Simon Glassb4002462020-03-18 09:42:43 -0600345 show_detail: Show size delta detail for each board if show_sizes
Simon Glasseb48bbc2014-08-09 15:33:02 -0600346 show_bloat: Show detail for each function
Simon Glass3394c9f2014-08-28 09:43:43 -0600347 list_error_boards: Show the boards which caused each error/warning
Simon Glassdb17fb82015-02-05 22:06:15 -0700348 show_config: Show config deltas
Alex Kiernan4059e302018-05-31 04:48:34 +0000349 show_environment: Show environment deltas
Simon Glasseb48bbc2014-08-09 15:33:02 -0600350 """
351 self._show_errors = show_errors
352 self._show_sizes = show_sizes
353 self._show_detail = show_detail
354 self._show_bloat = show_bloat
Simon Glass3394c9f2014-08-28 09:43:43 -0600355 self._list_error_boards = list_error_boards
Simon Glassdb17fb82015-02-05 22:06:15 -0700356 self._show_config = show_config
Alex Kiernan4059e302018-05-31 04:48:34 +0000357 self._show_environment = show_environment
Simon Glasseb48bbc2014-08-09 15:33:02 -0600358
Simon Glassc05694f2013-04-03 11:07:16 +0000359 def _AddTimestamp(self):
360 """Add a new timestamp to the list and record the build period.
361
362 The build period is the length of time taken to perform a single
363 build (one board, one commit).
364 """
365 now = datetime.now()
366 self._timestamps.append(now)
367 count = len(self._timestamps)
368 delta = self._timestamps[-1] - self._timestamps[0]
369 seconds = delta.total_seconds()
370
371 # If we have enough data, estimate build period (time taken for a
372 # single build) and therefore completion time.
373 if count > 1 and self._next_delay_update < now:
374 self._next_delay_update = now + timedelta(seconds=2)
375 if seconds > 0:
376 self._build_period = float(seconds) / count
377 todo = self.count - self.upto
378 self._complete_delay = timedelta(microseconds=
379 self._build_period * todo * 1000000)
380 # Round it
381 self._complete_delay -= timedelta(
382 microseconds=self._complete_delay.microseconds)
383
384 if seconds > 60:
385 self._timestamps.popleft()
386 count -= 1
387
Simon Glassc05694f2013-04-03 11:07:16 +0000388 def SelectCommit(self, commit, checkout=True):
389 """Checkout the selected commit for this build
390 """
391 self.commit = commit
392 if checkout and self.checkout:
393 gitutil.Checkout(commit.hash)
394
395 def Make(self, commit, brd, stage, cwd, *args, **kwargs):
396 """Run make
397
398 Args:
399 commit: Commit object that is being built
400 brd: Board object that is being built
Roger Meiere0a0e552014-08-20 22:10:29 +0200401 stage: Stage that we are at (mrproper, config, build)
Simon Glassc05694f2013-04-03 11:07:16 +0000402 cwd: Directory where make should be run
403 args: Arguments to pass to make
404 kwargs: Arguments to pass to command.RunPipe()
405 """
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900406 cmd = [self.gnu_make] + list(args)
Simon Glassc05694f2013-04-03 11:07:16 +0000407 result = command.RunPipe([cmd], capture=True, capture_stderr=True,
Simon Glassaa09b362018-09-17 23:55:42 -0600408 cwd=cwd, raise_on_error=False, infile='/dev/null', **kwargs)
Simon Glass413f91a2015-02-05 22:06:12 -0700409 if self.verbose_build:
410 result.stdout = '%s\n' % (' '.join(cmd)) + result.stdout
411 result.combined = '%s\n' % (' '.join(cmd)) + result.combined
Simon Glassc05694f2013-04-03 11:07:16 +0000412 return result
413
414 def ProcessResult(self, result):
415 """Process the result of a build, showing progress information
416
417 Args:
Simon Glass78e418e2014-08-09 15:33:03 -0600418 result: A CommandResult object, which indicates the result for
419 a single build
Simon Glassc05694f2013-04-03 11:07:16 +0000420 """
421 col = terminal.Color()
422 if result:
423 target = result.brd.target
424
Simon Glassc05694f2013-04-03 11:07:16 +0000425 self.upto += 1
426 if result.return_code != 0:
427 self.fail += 1
428 elif result.stderr:
429 self.warned += 1
430 if result.already_done:
431 self.already_done += 1
Simon Glass78e418e2014-08-09 15:33:03 -0600432 if self._verbose:
Simon Glassdd95b0b2020-04-09 15:08:42 -0600433 terminal.PrintClear()
Simon Glass78e418e2014-08-09 15:33:03 -0600434 boards_selected = {target : result.brd}
435 self.ResetResultSummary(boards_selected)
436 self.ProduceResultSummary(result.commit_upto, self.commits,
437 boards_selected)
Simon Glassc05694f2013-04-03 11:07:16 +0000438 else:
439 target = '(starting)'
440
441 # Display separate counts for ok, warned and fail
442 ok = self.upto - self.warned - self.fail
443 line = '\r' + self.col.Color(self.col.GREEN, '%5d' % ok)
444 line += self.col.Color(self.col.YELLOW, '%5d' % self.warned)
445 line += self.col.Color(self.col.RED, '%5d' % self.fail)
446
447 name = ' /%-5d ' % self.count
448
449 # Add our current completion time estimate
450 self._AddTimestamp()
451 if self._complete_delay:
452 name += '%s : ' % self._complete_delay
Simon Glassc05694f2013-04-03 11:07:16 +0000453
454 name += target
Simon Glassdd95b0b2020-04-09 15:08:42 -0600455 terminal.PrintClear()
Simon Glass4433aa92014-09-05 19:00:07 -0600456 Print(line + name, newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +0000457
458 def _GetOutputDir(self, commit_upto):
459 """Get the name of the output directory for a commit number
460
461 The output directory is typically .../<branch>/<commit>.
462
463 Args:
464 commit_upto: Commit number to use (0..self.count-1)
465 """
Simon Glasse87bde12014-12-01 17:33:55 -0700466 commit_dir = None
Simon Glassd326ad72014-08-09 15:32:59 -0600467 if self.commits:
468 commit = self.commits[commit_upto]
469 subject = commit.subject.translate(trans_valid_chars)
Simon Glass5dc1ca72020-03-18 09:42:45 -0600470 # See _GetOutputSpaceRemovals() which parses this name
Simon Glassd326ad72014-08-09 15:32:59 -0600471 commit_dir = ('%02d_of_%02d_g%s_%s' % (commit_upto + 1,
472 self.commit_count, commit.hash, subject[:20]))
Simon Glasse87bde12014-12-01 17:33:55 -0700473 elif not self.no_subdirs:
Simon Glassd326ad72014-08-09 15:32:59 -0600474 commit_dir = 'current'
Simon Glasse87bde12014-12-01 17:33:55 -0700475 if not commit_dir:
476 return self.base_dir
477 return os.path.join(self.base_dir, commit_dir)
Simon Glassc05694f2013-04-03 11:07:16 +0000478
479 def GetBuildDir(self, commit_upto, target):
480 """Get the name of the build directory for a commit number
481
482 The build directory is typically .../<branch>/<commit>/<target>.
483
484 Args:
485 commit_upto: Commit number to use (0..self.count-1)
486 target: Target name
487 """
488 output_dir = self._GetOutputDir(commit_upto)
489 return os.path.join(output_dir, target)
490
491 def GetDoneFile(self, commit_upto, target):
492 """Get the name of the done file for a commit number
493
494 Args:
495 commit_upto: Commit number to use (0..self.count-1)
496 target: Target name
497 """
498 return os.path.join(self.GetBuildDir(commit_upto, target), 'done')
499
500 def GetSizesFile(self, commit_upto, target):
501 """Get the name of the sizes file for a commit number
502
503 Args:
504 commit_upto: Commit number to use (0..self.count-1)
505 target: Target name
506 """
507 return os.path.join(self.GetBuildDir(commit_upto, target), 'sizes')
508
509 def GetFuncSizesFile(self, commit_upto, target, elf_fname):
510 """Get the name of the funcsizes file for a commit number and ELF file
511
512 Args:
513 commit_upto: Commit number to use (0..self.count-1)
514 target: Target name
515 elf_fname: Filename of elf image
516 """
517 return os.path.join(self.GetBuildDir(commit_upto, target),
518 '%s.sizes' % elf_fname.replace('/', '-'))
519
520 def GetObjdumpFile(self, commit_upto, target, elf_fname):
521 """Get the name of the objdump file for a commit number and ELF file
522
523 Args:
524 commit_upto: Commit number to use (0..self.count-1)
525 target: Target name
526 elf_fname: Filename of elf image
527 """
528 return os.path.join(self.GetBuildDir(commit_upto, target),
529 '%s.objdump' % elf_fname.replace('/', '-'))
530
531 def GetErrFile(self, commit_upto, target):
532 """Get the name of the err file for a commit number
533
534 Args:
535 commit_upto: Commit number to use (0..self.count-1)
536 target: Target name
537 """
538 output_dir = self.GetBuildDir(commit_upto, target)
539 return os.path.join(output_dir, 'err')
540
541 def FilterErrors(self, lines):
542 """Filter out errors in which we have no interest
543
544 We should probably use map().
545
546 Args:
547 lines: List of error lines, each a string
548 Returns:
549 New list with only interesting lines included
550 """
551 out_lines = []
552 for line in lines:
553 if not self.re_make_err.search(line):
554 out_lines.append(line)
555 return out_lines
556
557 def ReadFuncSizes(self, fname, fd):
558 """Read function sizes from the output of 'nm'
559
560 Args:
561 fd: File containing data to read
562 fname: Filename we are reading from (just for errors)
563
564 Returns:
565 Dictionary containing size of each function in bytes, indexed by
566 function name.
567 """
568 sym = {}
569 for line in fd.readlines():
570 try:
Tom Rini08d34fc2019-12-06 15:31:31 -0500571 if line.strip():
572 size, type, name = line[:-1].split()
Simon Glassc05694f2013-04-03 11:07:16 +0000573 except:
Simon Glass4433aa92014-09-05 19:00:07 -0600574 Print("Invalid line in file '%s': '%s'" % (fname, line[:-1]))
Simon Glassc05694f2013-04-03 11:07:16 +0000575 continue
576 if type in 'tTdDbB':
577 # function names begin with '.' on 64-bit powerpc
578 if '.' in name[1:]:
579 name = 'static.' + name.split('.')[0]
580 sym[name] = sym.get(name, 0) + int(size, 16)
581 return sym
582
Simon Glassdb17fb82015-02-05 22:06:15 -0700583 def _ProcessConfig(self, fname):
584 """Read in a .config, autoconf.mk or autoconf.h file
585
586 This function handles all config file types. It ignores comments and
587 any #defines which don't start with CONFIG_.
588
589 Args:
590 fname: Filename to read
591
592 Returns:
593 Dictionary:
594 key: Config name (e.g. CONFIG_DM)
595 value: Config value (e.g. 1)
596 """
597 config = {}
598 if os.path.exists(fname):
599 with open(fname) as fd:
600 for line in fd:
601 line = line.strip()
602 if line.startswith('#define'):
603 values = line[8:].split(' ', 1)
604 if len(values) > 1:
605 key, value = values
606 else:
607 key = values[0]
Simon Glasscde5c302016-11-13 14:25:53 -0700608 value = '1' if self.squash_config_y else ''
Simon Glassdb17fb82015-02-05 22:06:15 -0700609 if not key.startswith('CONFIG_'):
610 continue
611 elif not line or line[0] in ['#', '*', '/']:
612 continue
613 else:
614 key, value = line.split('=', 1)
Simon Glasscde5c302016-11-13 14:25:53 -0700615 if self.squash_config_y and value == 'y':
616 value = '1'
Simon Glassdb17fb82015-02-05 22:06:15 -0700617 config[key] = value
618 return config
619
Alex Kiernan4059e302018-05-31 04:48:34 +0000620 def _ProcessEnvironment(self, fname):
621 """Read in a uboot.env file
622
623 This function reads in environment variables from a file.
624
625 Args:
626 fname: Filename to read
627
628 Returns:
629 Dictionary:
630 key: environment variable (e.g. bootlimit)
631 value: value of environment variable (e.g. 1)
632 """
633 environment = {}
634 if os.path.exists(fname):
635 with open(fname) as fd:
636 for line in fd.read().split('\0'):
637 try:
638 key, value = line.split('=', 1)
639 environment[key] = value
640 except ValueError:
641 # ignore lines we can't parse
642 pass
643 return environment
644
Simon Glassdb17fb82015-02-05 22:06:15 -0700645 def GetBuildOutcome(self, commit_upto, target, read_func_sizes,
Alex Kiernan4059e302018-05-31 04:48:34 +0000646 read_config, read_environment):
Simon Glassc05694f2013-04-03 11:07:16 +0000647 """Work out the outcome of a build.
648
649 Args:
650 commit_upto: Commit number to check (0..n-1)
651 target: Target board to check
652 read_func_sizes: True to read function size information
Simon Glassdb17fb82015-02-05 22:06:15 -0700653 read_config: True to read .config and autoconf.h files
Alex Kiernan4059e302018-05-31 04:48:34 +0000654 read_environment: True to read uboot.env files
Simon Glassc05694f2013-04-03 11:07:16 +0000655
656 Returns:
657 Outcome object
658 """
659 done_file = self.GetDoneFile(commit_upto, target)
660 sizes_file = self.GetSizesFile(commit_upto, target)
661 sizes = {}
662 func_sizes = {}
Simon Glassdb17fb82015-02-05 22:06:15 -0700663 config = {}
Alex Kiernan4059e302018-05-31 04:48:34 +0000664 environment = {}
Simon Glassc05694f2013-04-03 11:07:16 +0000665 if os.path.exists(done_file):
666 with open(done_file, 'r') as fd:
Simon Glass91c54a42019-04-26 19:02:23 -0600667 try:
668 return_code = int(fd.readline())
669 except ValueError:
670 # The file may be empty due to running out of disk space.
671 # Try a rebuild
672 return_code = 1
Simon Glassc05694f2013-04-03 11:07:16 +0000673 err_lines = []
674 err_file = self.GetErrFile(commit_upto, target)
675 if os.path.exists(err_file):
676 with open(err_file, 'r') as fd:
677 err_lines = self.FilterErrors(fd.readlines())
678
679 # Decide whether the build was ok, failed or created warnings
680 if return_code:
681 rc = OUTCOME_ERROR
682 elif len(err_lines):
683 rc = OUTCOME_WARNING
684 else:
685 rc = OUTCOME_OK
686
687 # Convert size information to our simple format
688 if os.path.exists(sizes_file):
689 with open(sizes_file, 'r') as fd:
690 for line in fd.readlines():
691 values = line.split()
692 rodata = 0
693 if len(values) > 6:
694 rodata = int(values[6], 16)
695 size_dict = {
696 'all' : int(values[0]) + int(values[1]) +
697 int(values[2]),
698 'text' : int(values[0]) - rodata,
699 'data' : int(values[1]),
700 'bss' : int(values[2]),
701 'rodata' : rodata,
702 }
703 sizes[values[5]] = size_dict
704
705 if read_func_sizes:
706 pattern = self.GetFuncSizesFile(commit_upto, target, '*')
707 for fname in glob.glob(pattern):
708 with open(fname, 'r') as fd:
709 dict_name = os.path.basename(fname).replace('.sizes',
710 '')
711 func_sizes[dict_name] = self.ReadFuncSizes(fname, fd)
712
Simon Glassdb17fb82015-02-05 22:06:15 -0700713 if read_config:
714 output_dir = self.GetBuildDir(commit_upto, target)
Simon Glasscde5c302016-11-13 14:25:53 -0700715 for name in self.config_filenames:
Simon Glassdb17fb82015-02-05 22:06:15 -0700716 fname = os.path.join(output_dir, name)
717 config[name] = self._ProcessConfig(fname)
718
Alex Kiernan4059e302018-05-31 04:48:34 +0000719 if read_environment:
720 output_dir = self.GetBuildDir(commit_upto, target)
721 fname = os.path.join(output_dir, 'uboot.env')
722 environment = self._ProcessEnvironment(fname)
723
724 return Builder.Outcome(rc, err_lines, sizes, func_sizes, config,
725 environment)
Simon Glassc05694f2013-04-03 11:07:16 +0000726
Alex Kiernan4059e302018-05-31 04:48:34 +0000727 return Builder.Outcome(OUTCOME_UNKNOWN, [], {}, {}, {}, {})
Simon Glassc05694f2013-04-03 11:07:16 +0000728
Simon Glassdb17fb82015-02-05 22:06:15 -0700729 def GetResultSummary(self, boards_selected, commit_upto, read_func_sizes,
Alex Kiernan4059e302018-05-31 04:48:34 +0000730 read_config, read_environment):
Simon Glassc05694f2013-04-03 11:07:16 +0000731 """Calculate a summary of the results of building a commit.
732
733 Args:
734 board_selected: Dict containing boards to summarise
735 commit_upto: Commit number to summarize (0..self.count-1)
736 read_func_sizes: True to read function size information
Simon Glassdb17fb82015-02-05 22:06:15 -0700737 read_config: True to read .config and autoconf.h files
Alex Kiernan4059e302018-05-31 04:48:34 +0000738 read_environment: True to read uboot.env files
Simon Glassc05694f2013-04-03 11:07:16 +0000739
740 Returns:
741 Tuple:
742 Dict containing boards which passed building this commit.
743 keyed by board.target
Simon Glass03749d42014-08-28 09:43:44 -0600744 List containing a summary of error lines
Simon Glass3394c9f2014-08-28 09:43:43 -0600745 Dict keyed by error line, containing a list of the Board
746 objects with that error
Simon Glass03749d42014-08-28 09:43:44 -0600747 List containing a summary of warning lines
748 Dict keyed by error line, containing a list of the Board
749 objects with that warning
Simon Glasscad8abf2015-08-25 21:52:14 -0600750 Dictionary keyed by board.target. Each value is a dictionary:
751 key: filename - e.g. '.config'
Simon Glassdb17fb82015-02-05 22:06:15 -0700752 value is itself a dictionary:
753 key: config name
754 value: config value
Alex Kiernan4059e302018-05-31 04:48:34 +0000755 Dictionary keyed by board.target. Each value is a dictionary:
756 key: environment variable
757 value: value of environment variable
Simon Glassc05694f2013-04-03 11:07:16 +0000758 """
Simon Glass03749d42014-08-28 09:43:44 -0600759 def AddLine(lines_summary, lines_boards, line, board):
760 line = line.rstrip()
761 if line in lines_boards:
762 lines_boards[line].append(board)
763 else:
764 lines_boards[line] = [board]
765 lines_summary.append(line)
766
Simon Glassc05694f2013-04-03 11:07:16 +0000767 board_dict = {}
768 err_lines_summary = []
Simon Glass3394c9f2014-08-28 09:43:43 -0600769 err_lines_boards = {}
Simon Glass03749d42014-08-28 09:43:44 -0600770 warn_lines_summary = []
771 warn_lines_boards = {}
Simon Glassdb17fb82015-02-05 22:06:15 -0700772 config = {}
Alex Kiernan4059e302018-05-31 04:48:34 +0000773 environment = {}
Simon Glassc05694f2013-04-03 11:07:16 +0000774
Simon Glassc78ed662019-10-31 07:42:53 -0600775 for board in boards_selected.values():
Simon Glassc05694f2013-04-03 11:07:16 +0000776 outcome = self.GetBuildOutcome(commit_upto, board.target,
Alex Kiernan4059e302018-05-31 04:48:34 +0000777 read_func_sizes, read_config,
778 read_environment)
Simon Glassc05694f2013-04-03 11:07:16 +0000779 board_dict[board.target] = outcome
Simon Glass03749d42014-08-28 09:43:44 -0600780 last_func = None
781 last_was_warning = False
782 for line in outcome.err_lines:
783 if line:
784 if (self._re_function.match(line) or
785 self._re_files.match(line)):
786 last_func = line
Simon Glass3394c9f2014-08-28 09:43:43 -0600787 else:
Simon Glass0db94432018-11-06 16:02:11 -0700788 is_warning = (self._re_warning.match(line) or
789 self._re_dtb_warning.match(line))
Simon Glass03749d42014-08-28 09:43:44 -0600790 is_note = self._re_note.match(line)
791 if is_warning or (last_was_warning and is_note):
792 if last_func:
793 AddLine(warn_lines_summary, warn_lines_boards,
794 last_func, board)
795 AddLine(warn_lines_summary, warn_lines_boards,
796 line, board)
797 else:
798 if last_func:
799 AddLine(err_lines_summary, err_lines_boards,
800 last_func, board)
801 AddLine(err_lines_summary, err_lines_boards,
802 line, board)
803 last_was_warning = is_warning
804 last_func = None
Simon Glasscde5c302016-11-13 14:25:53 -0700805 tconfig = Config(self.config_filenames, board.target)
806 for fname in self.config_filenames:
Simon Glassdb17fb82015-02-05 22:06:15 -0700807 if outcome.config:
Simon Glassc78ed662019-10-31 07:42:53 -0600808 for key, value in outcome.config[fname].items():
Simon Glasscad8abf2015-08-25 21:52:14 -0600809 tconfig.Add(fname, key, value)
810 config[board.target] = tconfig
Simon Glassdb17fb82015-02-05 22:06:15 -0700811
Alex Kiernan4059e302018-05-31 04:48:34 +0000812 tenvironment = Environment(board.target)
813 if outcome.environment:
Simon Glassc78ed662019-10-31 07:42:53 -0600814 for key, value in outcome.environment.items():
Alex Kiernan4059e302018-05-31 04:48:34 +0000815 tenvironment.Add(key, value)
816 environment[board.target] = tenvironment
817
Simon Glass03749d42014-08-28 09:43:44 -0600818 return (board_dict, err_lines_summary, err_lines_boards,
Alex Kiernan4059e302018-05-31 04:48:34 +0000819 warn_lines_summary, warn_lines_boards, config, environment)
Simon Glassc05694f2013-04-03 11:07:16 +0000820
821 def AddOutcome(self, board_dict, arch_list, changes, char, color):
822 """Add an output to our list of outcomes for each architecture
823
824 This simple function adds failing boards (changes) to the
825 relevant architecture string, so we can print the results out
826 sorted by architecture.
827
828 Args:
829 board_dict: Dict containing all boards
830 arch_list: Dict keyed by arch name. Value is a string containing
831 a list of board names which failed for that arch.
832 changes: List of boards to add to arch_list
833 color: terminal.Colour object
834 """
835 done_arch = {}
836 for target in changes:
837 if target in board_dict:
838 arch = board_dict[target].arch
839 else:
840 arch = 'unknown'
841 str = self.col.Color(color, ' ' + target)
842 if not arch in done_arch:
Simon Glass26f72372015-02-05 22:06:11 -0700843 str = ' %s %s' % (self.col.Color(color, char), str)
Simon Glassc05694f2013-04-03 11:07:16 +0000844 done_arch[arch] = True
845 if not arch in arch_list:
846 arch_list[arch] = str
847 else:
848 arch_list[arch] += str
849
850
851 def ColourNum(self, num):
852 color = self.col.RED if num > 0 else self.col.GREEN
853 if num == 0:
854 return '0'
855 return self.col.Color(color, str(num))
856
857 def ResetResultSummary(self, board_selected):
858 """Reset the results summary ready for use.
859
860 Set up the base board list to be all those selected, and set the
861 error lines to empty.
862
863 Following this, calls to PrintResultSummary() will use this
864 information to work out what has changed.
865
866 Args:
867 board_selected: Dict containing boards to summarise, keyed by
868 board.target
869 """
870 self._base_board_dict = {}
871 for board in board_selected:
Alex Kiernan4059e302018-05-31 04:48:34 +0000872 self._base_board_dict[board] = Builder.Outcome(0, [], [], {}, {},
873 {})
Simon Glassc05694f2013-04-03 11:07:16 +0000874 self._base_err_lines = []
Simon Glass03749d42014-08-28 09:43:44 -0600875 self._base_warn_lines = []
876 self._base_err_line_boards = {}
877 self._base_warn_line_boards = {}
Simon Glasscad8abf2015-08-25 21:52:14 -0600878 self._base_config = None
Alex Kiernan4059e302018-05-31 04:48:34 +0000879 self._base_environment = None
Simon Glassc05694f2013-04-03 11:07:16 +0000880
881 def PrintFuncSizeDetail(self, fname, old, new):
882 grow, shrink, add, remove, up, down = 0, 0, 0, 0, 0, 0
883 delta, common = [], {}
884
885 for a in old:
886 if a in new:
887 common[a] = 1
888
889 for name in old:
890 if name not in common:
891 remove += 1
892 down += old[name]
893 delta.append([-old[name], name])
894
895 for name in new:
896 if name not in common:
897 add += 1
898 up += new[name]
899 delta.append([new[name], name])
900
901 for name in common:
902 diff = new.get(name, 0) - old.get(name, 0)
903 if diff > 0:
904 grow, up = grow + 1, up + diff
905 elif diff < 0:
906 shrink, down = shrink + 1, down - diff
907 delta.append([diff, name])
908
909 delta.sort()
910 delta.reverse()
911
912 args = [add, -remove, grow, -shrink, up, -down, up - down]
Tom Rini0b48cd62017-05-22 13:48:52 -0400913 if max(args) == 0 and min(args) == 0:
Simon Glassc05694f2013-04-03 11:07:16 +0000914 return
915 args = [self.ColourNum(x) for x in args]
916 indent = ' ' * 15
Simon Glass4433aa92014-09-05 19:00:07 -0600917 Print('%s%s: add: %s/%s, grow: %s/%s bytes: %s/%s (%s)' %
918 tuple([indent, self.col.Color(self.col.YELLOW, fname)] + args))
919 Print('%s %-38s %7s %7s %+7s' % (indent, 'function', 'old', 'new',
920 'delta'))
Simon Glassc05694f2013-04-03 11:07:16 +0000921 for diff, name in delta:
922 if diff:
923 color = self.col.RED if diff > 0 else self.col.GREEN
924 msg = '%s %-38s %7s %7s %+7d' % (indent, name,
925 old.get(name, '-'), new.get(name,'-'), diff)
Simon Glass4433aa92014-09-05 19:00:07 -0600926 Print(msg, colour=color)
Simon Glassc05694f2013-04-03 11:07:16 +0000927
928
929 def PrintSizeDetail(self, target_list, show_bloat):
930 """Show details size information for each board
931
932 Args:
933 target_list: List of targets, each a dict containing:
934 'target': Target name
935 'total_diff': Total difference in bytes across all areas
936 <part_name>: Difference for that part
937 show_bloat: Show detail for each function
938 """
939 targets_by_diff = sorted(target_list, reverse=True,
940 key=lambda x: x['_total_diff'])
941 for result in targets_by_diff:
942 printed_target = False
943 for name in sorted(result):
944 diff = result[name]
945 if name.startswith('_'):
946 continue
947 if diff != 0:
948 color = self.col.RED if diff > 0 else self.col.GREEN
949 msg = ' %s %+d' % (name, diff)
950 if not printed_target:
Simon Glass4433aa92014-09-05 19:00:07 -0600951 Print('%10s %-15s:' % ('', result['_target']),
952 newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +0000953 printed_target = True
Simon Glass4433aa92014-09-05 19:00:07 -0600954 Print(msg, colour=color, newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +0000955 if printed_target:
Simon Glass4433aa92014-09-05 19:00:07 -0600956 Print()
Simon Glassc05694f2013-04-03 11:07:16 +0000957 if show_bloat:
958 target = result['_target']
959 outcome = result['_outcome']
960 base_outcome = self._base_board_dict[target]
961 for fname in outcome.func_sizes:
962 self.PrintFuncSizeDetail(fname,
963 base_outcome.func_sizes[fname],
964 outcome.func_sizes[fname])
965
966
967 def PrintSizeSummary(self, board_selected, board_dict, show_detail,
968 show_bloat):
969 """Print a summary of image sizes broken down by section.
970
971 The summary takes the form of one line per architecture. The
972 line contains deltas for each of the sections (+ means the section
Flavio Suligoie5e3c112020-01-29 09:56:05 +0100973 got bigger, - means smaller). The numbers are the average number
Simon Glassc05694f2013-04-03 11:07:16 +0000974 of bytes that a board in this section increased by.
975
976 For example:
977 powerpc: (622 boards) text -0.0
978 arm: (285 boards) text -0.0
979 nds32: (3 boards) text -8.0
980
981 Args:
982 board_selected: Dict containing boards to summarise, keyed by
983 board.target
984 board_dict: Dict containing boards for which we built this
985 commit, keyed by board.target. The value is an Outcome object.
Simon Glassb4002462020-03-18 09:42:43 -0600986 show_detail: Show size delta detail for each board
Simon Glassc05694f2013-04-03 11:07:16 +0000987 show_bloat: Show detail for each function
988 """
989 arch_list = {}
990 arch_count = {}
991
992 # Calculate changes in size for different image parts
993 # The previous sizes are in Board.sizes, for each board
994 for target in board_dict:
995 if target not in board_selected:
996 continue
997 base_sizes = self._base_board_dict[target].sizes
998 outcome = board_dict[target]
999 sizes = outcome.sizes
1000
1001 # Loop through the list of images, creating a dict of size
1002 # changes for each image/part. We end up with something like
1003 # {'target' : 'snapper9g45, 'data' : 5, 'u-boot-spl:text' : -4}
1004 # which means that U-Boot data increased by 5 bytes and SPL
1005 # text decreased by 4.
1006 err = {'_target' : target}
1007 for image in sizes:
1008 if image in base_sizes:
1009 base_image = base_sizes[image]
1010 # Loop through the text, data, bss parts
1011 for part in sorted(sizes[image]):
1012 diff = sizes[image][part] - base_image[part]
1013 col = None
1014 if diff:
1015 if image == 'u-boot':
1016 name = part
1017 else:
1018 name = image + ':' + part
1019 err[name] = diff
1020 arch = board_selected[target].arch
1021 if not arch in arch_count:
1022 arch_count[arch] = 1
1023 else:
1024 arch_count[arch] += 1
1025 if not sizes:
1026 pass # Only add to our list when we have some stats
1027 elif not arch in arch_list:
1028 arch_list[arch] = [err]
1029 else:
1030 arch_list[arch].append(err)
1031
1032 # We now have a list of image size changes sorted by arch
1033 # Print out a summary of these
Simon Glassc78ed662019-10-31 07:42:53 -06001034 for arch, target_list in arch_list.items():
Simon Glassc05694f2013-04-03 11:07:16 +00001035 # Get total difference for each type
1036 totals = {}
1037 for result in target_list:
1038 total = 0
Simon Glassc78ed662019-10-31 07:42:53 -06001039 for name, diff in result.items():
Simon Glassc05694f2013-04-03 11:07:16 +00001040 if name.startswith('_'):
1041 continue
1042 total += diff
1043 if name in totals:
1044 totals[name] += diff
1045 else:
1046 totals[name] = diff
1047 result['_total_diff'] = total
1048 result['_outcome'] = board_dict[result['_target']]
1049
1050 count = len(target_list)
1051 printed_arch = False
1052 for name in sorted(totals):
1053 diff = totals[name]
1054 if diff:
1055 # Display the average difference in this name for this
1056 # architecture
1057 avg_diff = float(diff) / count
1058 color = self.col.RED if avg_diff > 0 else self.col.GREEN
1059 msg = ' %s %+1.1f' % (name, avg_diff)
1060 if not printed_arch:
Simon Glass4433aa92014-09-05 19:00:07 -06001061 Print('%10s: (for %d/%d boards)' % (arch, count,
1062 arch_count[arch]), newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001063 printed_arch = True
Simon Glass4433aa92014-09-05 19:00:07 -06001064 Print(msg, colour=color, newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001065
1066 if printed_arch:
Simon Glass4433aa92014-09-05 19:00:07 -06001067 Print()
Simon Glassc05694f2013-04-03 11:07:16 +00001068 if show_detail:
1069 self.PrintSizeDetail(target_list, show_bloat)
1070
1071
1072 def PrintResultSummary(self, board_selected, board_dict, err_lines,
Simon Glass03749d42014-08-28 09:43:44 -06001073 err_line_boards, warn_lines, warn_line_boards,
Alex Kiernan4059e302018-05-31 04:48:34 +00001074 config, environment, show_sizes, show_detail,
1075 show_bloat, show_config, show_environment):
Simon Glassc05694f2013-04-03 11:07:16 +00001076 """Compare results with the base results and display delta.
1077
1078 Only boards mentioned in board_selected will be considered. This
1079 function is intended to be called repeatedly with the results of
1080 each commit. It therefore shows a 'diff' between what it saw in
1081 the last call and what it sees now.
1082
1083 Args:
1084 board_selected: Dict containing boards to summarise, keyed by
1085 board.target
1086 board_dict: Dict containing boards for which we built this
1087 commit, keyed by board.target. The value is an Outcome object.
1088 err_lines: A list of errors for this commit, or [] if there is
1089 none, or we don't want to print errors
Simon Glass3394c9f2014-08-28 09:43:43 -06001090 err_line_boards: Dict keyed by error line, containing a list of
1091 the Board objects with that error
Simon Glass03749d42014-08-28 09:43:44 -06001092 warn_lines: A list of warnings for this commit, or [] if there is
1093 none, or we don't want to print errors
1094 warn_line_boards: Dict keyed by warning line, containing a list of
1095 the Board objects with that warning
Simon Glassdb17fb82015-02-05 22:06:15 -07001096 config: Dictionary keyed by filename - e.g. '.config'. Each
1097 value is itself a dictionary:
1098 key: config name
1099 value: config value
Alex Kiernan4059e302018-05-31 04:48:34 +00001100 environment: Dictionary keyed by environment variable, Each
1101 value is the value of environment variable.
Simon Glassc05694f2013-04-03 11:07:16 +00001102 show_sizes: Show image size deltas
Simon Glassb4002462020-03-18 09:42:43 -06001103 show_detail: Show size delta detail for each board if show_sizes
Simon Glassc05694f2013-04-03 11:07:16 +00001104 show_bloat: Show detail for each function
Simon Glassdb17fb82015-02-05 22:06:15 -07001105 show_config: Show config changes
Alex Kiernan4059e302018-05-31 04:48:34 +00001106 show_environment: Show environment changes
Simon Glassc05694f2013-04-03 11:07:16 +00001107 """
Simon Glass03749d42014-08-28 09:43:44 -06001108 def _BoardList(line, line_boards):
Simon Glass3394c9f2014-08-28 09:43:43 -06001109 """Helper function to get a line of boards containing a line
1110
1111 Args:
1112 line: Error line to search for
Simon Glassde0fefc2020-04-09 15:08:36 -06001113 line_boards: boards to search, each a Board
Simon Glass3394c9f2014-08-28 09:43:43 -06001114 Return:
Simon Glassde0fefc2020-04-09 15:08:36 -06001115 List of boards with that error line, or [] if the user has not
1116 requested such a list
Simon Glass3394c9f2014-08-28 09:43:43 -06001117 """
Simon Glassde0fefc2020-04-09 15:08:36 -06001118 boards = []
1119 board_set = set()
Simon Glass3394c9f2014-08-28 09:43:43 -06001120 if self._list_error_boards:
Simon Glass03749d42014-08-28 09:43:44 -06001121 for board in line_boards[line]:
Simon Glassde0fefc2020-04-09 15:08:36 -06001122 if not board in board_set:
1123 boards.append(board)
1124 board_set.add(board)
1125 return boards
Simon Glass3394c9f2014-08-28 09:43:43 -06001126
Simon Glass03749d42014-08-28 09:43:44 -06001127 def _CalcErrorDelta(base_lines, base_line_boards, lines, line_boards,
1128 char):
Simon Glassde0fefc2020-04-09 15:08:36 -06001129 """Calculate the required output based on changes in errors
1130
1131 Args:
1132 base_lines: List of errors/warnings for previous commit
1133 base_line_boards: Dict keyed by error line, containing a list
1134 of the Board objects with that error in the previous commit
1135 lines: List of errors/warning for this commit, each a str
1136 line_boards: Dict keyed by error line, containing a list
1137 of the Board objects with that error in this commit
1138 char: Character representing error ('') or warning ('w'). The
1139 broken ('+') or fixed ('-') characters are added in this
1140 function
1141
1142 Returns:
1143 Tuple
1144 List of ErrLine objects for 'better' lines
1145 List of ErrLine objects for 'worse' lines
1146 """
Simon Glass03749d42014-08-28 09:43:44 -06001147 better_lines = []
1148 worse_lines = []
1149 for line in lines:
1150 if line not in base_lines:
Simon Glassde0fefc2020-04-09 15:08:36 -06001151 errline = ErrLine(char + '+', _BoardList(line, line_boards),
1152 line)
1153 worse_lines.append(errline)
Simon Glass03749d42014-08-28 09:43:44 -06001154 for line in base_lines:
1155 if line not in lines:
Simon Glassde0fefc2020-04-09 15:08:36 -06001156 errline = ErrLine(char + '-',
1157 _BoardList(line, base_line_boards), line)
1158 better_lines.append(errline)
Simon Glass03749d42014-08-28 09:43:44 -06001159 return better_lines, worse_lines
1160
Simon Glassdb17fb82015-02-05 22:06:15 -07001161 def _CalcConfig(delta, name, config):
1162 """Calculate configuration changes
1163
1164 Args:
1165 delta: Type of the delta, e.g. '+'
1166 name: name of the file which changed (e.g. .config)
1167 config: configuration change dictionary
1168 key: config name
1169 value: config value
1170 Returns:
1171 String containing the configuration changes which can be
1172 printed
1173 """
1174 out = ''
1175 for key in sorted(config.keys()):
1176 out += '%s=%s ' % (key, config[key])
Simon Glasscad8abf2015-08-25 21:52:14 -06001177 return '%s %s: %s' % (delta, name, out)
Simon Glassdb17fb82015-02-05 22:06:15 -07001178
Simon Glasscad8abf2015-08-25 21:52:14 -06001179 def _AddConfig(lines, name, config_plus, config_minus, config_change):
1180 """Add changes in configuration to a list
Simon Glassdb17fb82015-02-05 22:06:15 -07001181
1182 Args:
Simon Glasscad8abf2015-08-25 21:52:14 -06001183 lines: list to add to
1184 name: config file name
Simon Glassdb17fb82015-02-05 22:06:15 -07001185 config_plus: configurations added, dictionary
1186 key: config name
1187 value: config value
1188 config_minus: configurations removed, dictionary
1189 key: config name
1190 value: config value
1191 config_change: configurations changed, dictionary
1192 key: config name
1193 value: config value
1194 """
1195 if config_plus:
Simon Glasscad8abf2015-08-25 21:52:14 -06001196 lines.append(_CalcConfig('+', name, config_plus))
Simon Glassdb17fb82015-02-05 22:06:15 -07001197 if config_minus:
Simon Glasscad8abf2015-08-25 21:52:14 -06001198 lines.append(_CalcConfig('-', name, config_minus))
Simon Glassdb17fb82015-02-05 22:06:15 -07001199 if config_change:
Simon Glasscad8abf2015-08-25 21:52:14 -06001200 lines.append(_CalcConfig('c', name, config_change))
1201
1202 def _OutputConfigInfo(lines):
1203 for line in lines:
1204 if not line:
1205 continue
1206 if line[0] == '+':
1207 col = self.col.GREEN
1208 elif line[0] == '-':
1209 col = self.col.RED
1210 elif line[0] == 'c':
1211 col = self.col.YELLOW
1212 Print(' ' + line, newline=True, colour=col)
1213
Simon Glassac500222020-04-09 15:08:28 -06001214 def _OutputErrLines(err_lines, colour):
1215 """Output the line of error/warning lines, if not empty
1216
1217 Also increments self._error_lines if err_lines not empty
1218
1219 Args:
Simon Glassde0fefc2020-04-09 15:08:36 -06001220 err_lines: List of ErrLine objects, each an error or warning
1221 line, possibly including a list of boards with that
1222 error/warning
Simon Glassac500222020-04-09 15:08:28 -06001223 colour: Colour to use for output
1224 """
1225 if err_lines:
Simon Glassea49f9b2020-04-09 15:08:37 -06001226 out_list = []
Simon Glassde0fefc2020-04-09 15:08:36 -06001227 for line in err_lines:
1228 boards = ''
1229 names = [board.target for board in line.boards]
Simon Glass070589b2020-04-09 15:08:38 -06001230 board_str = ' '.join(names) if names else ''
Simon Glassea49f9b2020-04-09 15:08:37 -06001231 if board_str:
1232 out = self.col.Color(colour, line.char + '(')
1233 out += self.col.Color(self.col.MAGENTA, board_str,
1234 bright=False)
1235 out += self.col.Color(colour, ') %s' % line.errline)
1236 else:
1237 out = self.col.Color(colour, line.char + line.errline)
1238 out_list.append(out)
1239 Print('\n'.join(out_list))
Simon Glassac500222020-04-09 15:08:28 -06001240 self._error_lines += 1
1241
Simon Glassdb17fb82015-02-05 22:06:15 -07001242
Simon Glass454507f2018-11-06 16:02:12 -07001243 ok_boards = [] # List of boards fixed since last commit
Simon Glass071a1782018-11-06 16:02:13 -07001244 warn_boards = [] # List of boards with warnings since last commit
Simon Glass454507f2018-11-06 16:02:12 -07001245 err_boards = [] # List of new broken boards since last commit
1246 new_boards = [] # List of boards that didn't exist last time
1247 unknown_boards = [] # List of boards that were not built
Simon Glassc05694f2013-04-03 11:07:16 +00001248
1249 for target in board_dict:
1250 if target not in board_selected:
1251 continue
1252
1253 # If the board was built last time, add its outcome to a list
1254 if target in self._base_board_dict:
1255 base_outcome = self._base_board_dict[target].rc
1256 outcome = board_dict[target]
1257 if outcome.rc == OUTCOME_UNKNOWN:
Simon Glass454507f2018-11-06 16:02:12 -07001258 unknown_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001259 elif outcome.rc < base_outcome:
Simon Glass071a1782018-11-06 16:02:13 -07001260 if outcome.rc == OUTCOME_WARNING:
1261 warn_boards.append(target)
1262 else:
1263 ok_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001264 elif outcome.rc > base_outcome:
Simon Glass071a1782018-11-06 16:02:13 -07001265 if outcome.rc == OUTCOME_WARNING:
1266 warn_boards.append(target)
1267 else:
1268 err_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001269 else:
Simon Glass454507f2018-11-06 16:02:12 -07001270 new_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001271
Simon Glassac500222020-04-09 15:08:28 -06001272 # Get a list of errors and warnings that have appeared, and disappeared
Simon Glass03749d42014-08-28 09:43:44 -06001273 better_err, worse_err = _CalcErrorDelta(self._base_err_lines,
1274 self._base_err_line_boards, err_lines, err_line_boards, '')
1275 better_warn, worse_warn = _CalcErrorDelta(self._base_warn_lines,
1276 self._base_warn_line_boards, warn_lines, warn_line_boards, 'w')
Simon Glassc05694f2013-04-03 11:07:16 +00001277
1278 # Display results by arch
Simon Glass071a1782018-11-06 16:02:13 -07001279 if any((ok_boards, warn_boards, err_boards, unknown_boards, new_boards,
1280 worse_err, better_err, worse_warn, better_warn)):
Simon Glassc05694f2013-04-03 11:07:16 +00001281 arch_list = {}
Simon Glass454507f2018-11-06 16:02:12 -07001282 self.AddOutcome(board_selected, arch_list, ok_boards, '',
Simon Glassc05694f2013-04-03 11:07:16 +00001283 self.col.GREEN)
Simon Glass071a1782018-11-06 16:02:13 -07001284 self.AddOutcome(board_selected, arch_list, warn_boards, 'w+',
1285 self.col.YELLOW)
Simon Glass454507f2018-11-06 16:02:12 -07001286 self.AddOutcome(board_selected, arch_list, err_boards, '+',
Simon Glassc05694f2013-04-03 11:07:16 +00001287 self.col.RED)
Simon Glass454507f2018-11-06 16:02:12 -07001288 self.AddOutcome(board_selected, arch_list, new_boards, '*', self.col.BLUE)
Simon Glassc05694f2013-04-03 11:07:16 +00001289 if self._show_unknown:
Simon Glass454507f2018-11-06 16:02:12 -07001290 self.AddOutcome(board_selected, arch_list, unknown_boards, '?',
Simon Glassc05694f2013-04-03 11:07:16 +00001291 self.col.MAGENTA)
Simon Glassc78ed662019-10-31 07:42:53 -06001292 for arch, target_list in arch_list.items():
Simon Glass4433aa92014-09-05 19:00:07 -06001293 Print('%10s: %s' % (arch, target_list))
Simon Glassbb4dffb2014-08-09 15:33:06 -06001294 self._error_lines += 1
Simon Glassac500222020-04-09 15:08:28 -06001295 _OutputErrLines(better_err, colour=self.col.GREEN)
1296 _OutputErrLines(worse_err, colour=self.col.RED)
1297 _OutputErrLines(better_warn, colour=self.col.CYAN)
Simon Glass564ddac2020-04-09 15:08:35 -06001298 _OutputErrLines(worse_warn, colour=self.col.YELLOW)
Simon Glassc05694f2013-04-03 11:07:16 +00001299
1300 if show_sizes:
1301 self.PrintSizeSummary(board_selected, board_dict, show_detail,
1302 show_bloat)
1303
Alex Kiernan4059e302018-05-31 04:48:34 +00001304 if show_environment and self._base_environment:
1305 lines = []
1306
1307 for target in board_dict:
1308 if target not in board_selected:
1309 continue
1310
1311 tbase = self._base_environment[target]
1312 tenvironment = environment[target]
1313 environment_plus = {}
1314 environment_minus = {}
1315 environment_change = {}
1316 base = tbase.environment
Simon Glassc78ed662019-10-31 07:42:53 -06001317 for key, value in tenvironment.environment.items():
Alex Kiernan4059e302018-05-31 04:48:34 +00001318 if key not in base:
1319 environment_plus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001320 for key, value in base.items():
Alex Kiernan4059e302018-05-31 04:48:34 +00001321 if key not in tenvironment.environment:
1322 environment_minus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001323 for key, value in base.items():
Alex Kiernan4059e302018-05-31 04:48:34 +00001324 new_value = tenvironment.environment.get(key)
1325 if new_value and value != new_value:
1326 desc = '%s -> %s' % (value, new_value)
1327 environment_change[key] = desc
1328
1329 _AddConfig(lines, target, environment_plus, environment_minus,
1330 environment_change)
1331
1332 _OutputConfigInfo(lines)
1333
Simon Glasscad8abf2015-08-25 21:52:14 -06001334 if show_config and self._base_config:
1335 summary = {}
1336 arch_config_plus = {}
1337 arch_config_minus = {}
1338 arch_config_change = {}
1339 arch_list = []
1340
1341 for target in board_dict:
1342 if target not in board_selected:
1343 continue
1344 arch = board_selected[target].arch
1345 if arch not in arch_list:
1346 arch_list.append(arch)
1347
1348 for arch in arch_list:
1349 arch_config_plus[arch] = {}
1350 arch_config_minus[arch] = {}
1351 arch_config_change[arch] = {}
Simon Glasscde5c302016-11-13 14:25:53 -07001352 for name in self.config_filenames:
Simon Glasscad8abf2015-08-25 21:52:14 -06001353 arch_config_plus[arch][name] = {}
1354 arch_config_minus[arch][name] = {}
1355 arch_config_change[arch][name] = {}
1356
1357 for target in board_dict:
1358 if target not in board_selected:
Simon Glassdb17fb82015-02-05 22:06:15 -07001359 continue
Simon Glasscad8abf2015-08-25 21:52:14 -06001360
1361 arch = board_selected[target].arch
1362
1363 all_config_plus = {}
1364 all_config_minus = {}
1365 all_config_change = {}
1366 tbase = self._base_config[target]
1367 tconfig = config[target]
1368 lines = []
Simon Glasscde5c302016-11-13 14:25:53 -07001369 for name in self.config_filenames:
Simon Glasscad8abf2015-08-25 21:52:14 -06001370 if not tconfig.config[name]:
1371 continue
1372 config_plus = {}
1373 config_minus = {}
1374 config_change = {}
1375 base = tbase.config[name]
Simon Glassc78ed662019-10-31 07:42:53 -06001376 for key, value in tconfig.config[name].items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001377 if key not in base:
1378 config_plus[key] = value
1379 all_config_plus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001380 for key, value in base.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001381 if key not in tconfig.config[name]:
1382 config_minus[key] = value
1383 all_config_minus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001384 for key, value in base.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001385 new_value = tconfig.config.get(key)
1386 if new_value and value != new_value:
1387 desc = '%s -> %s' % (value, new_value)
1388 config_change[key] = desc
1389 all_config_change[key] = desc
1390
1391 arch_config_plus[arch][name].update(config_plus)
1392 arch_config_minus[arch][name].update(config_minus)
1393 arch_config_change[arch][name].update(config_change)
1394
1395 _AddConfig(lines, name, config_plus, config_minus,
1396 config_change)
1397 _AddConfig(lines, 'all', all_config_plus, all_config_minus,
1398 all_config_change)
1399 summary[target] = '\n'.join(lines)
1400
1401 lines_by_target = {}
Simon Glassc78ed662019-10-31 07:42:53 -06001402 for target, lines in summary.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001403 if lines in lines_by_target:
1404 lines_by_target[lines].append(target)
1405 else:
1406 lines_by_target[lines] = [target]
1407
1408 for arch in arch_list:
1409 lines = []
1410 all_plus = {}
1411 all_minus = {}
1412 all_change = {}
Simon Glasscde5c302016-11-13 14:25:53 -07001413 for name in self.config_filenames:
Simon Glasscad8abf2015-08-25 21:52:14 -06001414 all_plus.update(arch_config_plus[arch][name])
1415 all_minus.update(arch_config_minus[arch][name])
1416 all_change.update(arch_config_change[arch][name])
1417 _AddConfig(lines, name, arch_config_plus[arch][name],
1418 arch_config_minus[arch][name],
1419 arch_config_change[arch][name])
1420 _AddConfig(lines, 'all', all_plus, all_minus, all_change)
1421 #arch_summary[target] = '\n'.join(lines)
1422 if lines:
1423 Print('%s:' % arch)
1424 _OutputConfigInfo(lines)
1425
Simon Glassc78ed662019-10-31 07:42:53 -06001426 for lines, targets in lines_by_target.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001427 if not lines:
1428 continue
1429 Print('%s :' % ' '.join(sorted(targets)))
1430 _OutputConfigInfo(lines.split('\n'))
1431
Simon Glassdb17fb82015-02-05 22:06:15 -07001432
Simon Glassc05694f2013-04-03 11:07:16 +00001433 # Save our updated information for the next call to this function
1434 self._base_board_dict = board_dict
1435 self._base_err_lines = err_lines
Simon Glass03749d42014-08-28 09:43:44 -06001436 self._base_warn_lines = warn_lines
1437 self._base_err_line_boards = err_line_boards
1438 self._base_warn_line_boards = warn_line_boards
Simon Glassdb17fb82015-02-05 22:06:15 -07001439 self._base_config = config
Alex Kiernan4059e302018-05-31 04:48:34 +00001440 self._base_environment = environment
Simon Glassc05694f2013-04-03 11:07:16 +00001441
1442 # Get a list of boards that did not get built, if needed
1443 not_built = []
1444 for board in board_selected:
1445 if not board in board_dict:
1446 not_built.append(board)
1447 if not_built:
Simon Glass4433aa92014-09-05 19:00:07 -06001448 Print("Boards not built (%d): %s" % (len(not_built),
1449 ', '.join(not_built)))
Simon Glassc05694f2013-04-03 11:07:16 +00001450
Simon Glasseb48bbc2014-08-09 15:33:02 -06001451 def ProduceResultSummary(self, commit_upto, commits, board_selected):
Simon Glass03749d42014-08-28 09:43:44 -06001452 (board_dict, err_lines, err_line_boards, warn_lines,
Alex Kiernan4059e302018-05-31 04:48:34 +00001453 warn_line_boards, config, environment) = self.GetResultSummary(
Simon Glass3394c9f2014-08-28 09:43:43 -06001454 board_selected, commit_upto,
Simon Glassdb17fb82015-02-05 22:06:15 -07001455 read_func_sizes=self._show_bloat,
Alex Kiernan4059e302018-05-31 04:48:34 +00001456 read_config=self._show_config,
1457 read_environment=self._show_environment)
Simon Glasseb48bbc2014-08-09 15:33:02 -06001458 if commits:
1459 msg = '%02d: %s' % (commit_upto + 1,
1460 commits[commit_upto].subject)
Simon Glass4433aa92014-09-05 19:00:07 -06001461 Print(msg, colour=self.col.BLUE)
Simon Glasseb48bbc2014-08-09 15:33:02 -06001462 self.PrintResultSummary(board_selected, board_dict,
Simon Glass3394c9f2014-08-28 09:43:43 -06001463 err_lines if self._show_errors else [], err_line_boards,
Simon Glass03749d42014-08-28 09:43:44 -06001464 warn_lines if self._show_errors else [], warn_line_boards,
Alex Kiernan4059e302018-05-31 04:48:34 +00001465 config, environment, self._show_sizes, self._show_detail,
1466 self._show_bloat, self._show_config, self._show_environment)
Simon Glassc05694f2013-04-03 11:07:16 +00001467
Simon Glasseb48bbc2014-08-09 15:33:02 -06001468 def ShowSummary(self, commits, board_selected):
Simon Glassc05694f2013-04-03 11:07:16 +00001469 """Show a build summary for U-Boot for a given board list.
1470
1471 Reset the result summary, then repeatedly call GetResultSummary on
1472 each commit's results, then display the differences we see.
1473
1474 Args:
1475 commit: Commit objects to summarise
1476 board_selected: Dict containing boards to summarise
Simon Glassc05694f2013-04-03 11:07:16 +00001477 """
Simon Glassd326ad72014-08-09 15:32:59 -06001478 self.commit_count = len(commits) if commits else 1
Simon Glassc05694f2013-04-03 11:07:16 +00001479 self.commits = commits
1480 self.ResetResultSummary(board_selected)
Simon Glassbb4dffb2014-08-09 15:33:06 -06001481 self._error_lines = 0
Simon Glassc05694f2013-04-03 11:07:16 +00001482
1483 for commit_upto in range(0, self.commit_count, self._step):
Simon Glasseb48bbc2014-08-09 15:33:02 -06001484 self.ProduceResultSummary(commit_upto, commits, board_selected)
Simon Glassbb4dffb2014-08-09 15:33:06 -06001485 if not self._error_lines:
Simon Glass4433aa92014-09-05 19:00:07 -06001486 Print('(no errors to report)', colour=self.col.GREEN)
Simon Glassc05694f2013-04-03 11:07:16 +00001487
1488
1489 def SetupBuild(self, board_selected, commits):
1490 """Set up ready to start a build.
1491
1492 Args:
1493 board_selected: Selected boards to build
1494 commits: Selected commits to build
1495 """
1496 # First work out how many commits we will build
Simon Glassc78ed662019-10-31 07:42:53 -06001497 count = (self.commit_count + self._step - 1) // self._step
Simon Glassc05694f2013-04-03 11:07:16 +00001498 self.count = len(board_selected) * count
1499 self.upto = self.warned = self.fail = 0
1500 self._timestamps = collections.deque()
1501
Simon Glassc05694f2013-04-03 11:07:16 +00001502 def GetThreadDir(self, thread_num):
1503 """Get the directory path to the working dir for a thread.
1504
1505 Args:
1506 thread_num: Number of thread to check.
1507 """
Simon Glassb6eb8cf2020-03-18 09:42:42 -06001508 if self.work_in_output:
1509 return self._working_dir
Simon Glassc05694f2013-04-03 11:07:16 +00001510 return os.path.join(self._working_dir, '%02d' % thread_num)
1511
Simon Glassd326ad72014-08-09 15:32:59 -06001512 def _PrepareThread(self, thread_num, setup_git):
Simon Glassc05694f2013-04-03 11:07:16 +00001513 """Prepare the working directory for a thread.
1514
1515 This clones or fetches the repo into the thread's work directory.
1516
1517 Args:
1518 thread_num: Thread number (0, 1, ...)
Simon Glassd326ad72014-08-09 15:32:59 -06001519 setup_git: True to set up a git repo clone
Simon Glassc05694f2013-04-03 11:07:16 +00001520 """
1521 thread_dir = self.GetThreadDir(thread_num)
Simon Glass4a1e88b2014-08-09 15:33:00 -06001522 builderthread.Mkdir(thread_dir)
Simon Glassc05694f2013-04-03 11:07:16 +00001523 git_dir = os.path.join(thread_dir, '.git')
1524
1525 # Clone the repo if it doesn't already exist
1526 # TODO(sjg@chromium): Perhaps some git hackery to symlink instead, so
1527 # we have a private index but uses the origin repo's contents?
Simon Glassd326ad72014-08-09 15:32:59 -06001528 if setup_git and self.git_dir:
Simon Glassc05694f2013-04-03 11:07:16 +00001529 src_dir = os.path.abspath(self.git_dir)
1530 if os.path.exists(git_dir):
Simon Glass43054932020-04-09 15:08:43 -06001531 Print('\rFetching repo for thread %d' % thread_num,
1532 newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001533 gitutil.Fetch(git_dir, thread_dir)
Simon Glass43054932020-04-09 15:08:43 -06001534 terminal.PrintClear()
Simon Glassc05694f2013-04-03 11:07:16 +00001535 else:
Simon Glass2e2a6e62016-09-18 16:48:31 -06001536 Print('\rCloning repo for thread %d' % thread_num,
1537 newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001538 gitutil.Clone(src_dir, thread_dir)
Simon Glassdd95b0b2020-04-09 15:08:42 -06001539 terminal.PrintClear()
Simon Glassc05694f2013-04-03 11:07:16 +00001540
Simon Glassd326ad72014-08-09 15:32:59 -06001541 def _PrepareWorkingSpace(self, max_threads, setup_git):
Simon Glassc05694f2013-04-03 11:07:16 +00001542 """Prepare the working directory for use.
1543
1544 Set up the git repo for each thread.
1545
1546 Args:
1547 max_threads: Maximum number of threads we expect to need.
Simon Glassd326ad72014-08-09 15:32:59 -06001548 setup_git: True to set up a git repo clone
Simon Glassc05694f2013-04-03 11:07:16 +00001549 """
Simon Glass4a1e88b2014-08-09 15:33:00 -06001550 builderthread.Mkdir(self._working_dir)
Simon Glassc05694f2013-04-03 11:07:16 +00001551 for thread in range(max_threads):
Simon Glassd326ad72014-08-09 15:32:59 -06001552 self._PrepareThread(thread, setup_git)
Simon Glassc05694f2013-04-03 11:07:16 +00001553
Simon Glass5dc1ca72020-03-18 09:42:45 -06001554 def _GetOutputSpaceRemovals(self):
Simon Glassc05694f2013-04-03 11:07:16 +00001555 """Get the output directories ready to receive files.
1556
Simon Glass5dc1ca72020-03-18 09:42:45 -06001557 Figure out what needs to be deleted in the output directory before it
1558 can be used. We only delete old buildman directories which have the
1559 expected name pattern. See _GetOutputDir().
1560
1561 Returns:
1562 List of full paths of directories to remove
Simon Glassc05694f2013-04-03 11:07:16 +00001563 """
Simon Glassb9a9b7c2014-12-01 17:33:53 -07001564 if not self.commits:
1565 return
Simon Glassc05694f2013-04-03 11:07:16 +00001566 dir_list = []
1567 for commit_upto in range(self.commit_count):
1568 dir_list.append(self._GetOutputDir(commit_upto))
1569
Simon Glass83cb6cc2016-09-18 16:48:32 -06001570 to_remove = []
Simon Glassc05694f2013-04-03 11:07:16 +00001571 for dirname in glob.glob(os.path.join(self.base_dir, '*')):
1572 if dirname not in dir_list:
Simon Glass5dc1ca72020-03-18 09:42:45 -06001573 leaf = dirname[len(self.base_dir) + 1:]
1574 m = re.match('[0-9]+_of_[0-9]+_g[0-9a-f]+_.*', leaf)
1575 if m:
1576 to_remove.append(dirname)
1577 return to_remove
1578
1579 def _PrepareOutputSpace(self):
1580 """Get the output directories ready to receive files.
1581
1582 We delete any output directories which look like ones we need to
1583 create. Having left over directories is confusing when the user wants
1584 to check the output manually.
1585 """
1586 to_remove = self._GetOutputSpaceRemovals()
Simon Glass83cb6cc2016-09-18 16:48:32 -06001587 if to_remove:
Simon Glass44028272020-03-18 09:42:46 -06001588 Print('Removing %d old build directories...' % len(to_remove),
Simon Glass83cb6cc2016-09-18 16:48:32 -06001589 newline=False)
1590 for dirname in to_remove:
Simon Glassc05694f2013-04-03 11:07:16 +00001591 shutil.rmtree(dirname)
Simon Glass43054932020-04-09 15:08:43 -06001592 terminal.PrintClear()
Simon Glassc05694f2013-04-03 11:07:16 +00001593
Simon Glass78e418e2014-08-09 15:33:03 -06001594 def BuildBoards(self, commits, board_selected, keep_outputs, verbose):
Simon Glassc05694f2013-04-03 11:07:16 +00001595 """Build all commits for a list of boards
1596
1597 Args:
1598 commits: List of commits to be build, each a Commit object
1599 boards_selected: Dict of selected boards, key is target name,
1600 value is Board object
Simon Glassc05694f2013-04-03 11:07:16 +00001601 keep_outputs: True to save build output files
Simon Glass78e418e2014-08-09 15:33:03 -06001602 verbose: Display build results as they are completed
Simon Glassc2f91072014-08-28 09:43:39 -06001603 Returns:
1604 Tuple containing:
1605 - number of boards that failed to build
1606 - number of boards that issued warnings
Simon Glassc05694f2013-04-03 11:07:16 +00001607 """
Simon Glassd326ad72014-08-09 15:32:59 -06001608 self.commit_count = len(commits) if commits else 1
Simon Glassc05694f2013-04-03 11:07:16 +00001609 self.commits = commits
Simon Glass78e418e2014-08-09 15:33:03 -06001610 self._verbose = verbose
Simon Glassc05694f2013-04-03 11:07:16 +00001611
1612 self.ResetResultSummary(board_selected)
Thierry Reding336d5bd2014-08-19 10:22:39 +02001613 builderthread.Mkdir(self.base_dir, parents = True)
Simon Glassd326ad72014-08-09 15:32:59 -06001614 self._PrepareWorkingSpace(min(self.num_threads, len(board_selected)),
1615 commits is not None)
Simon Glassc05694f2013-04-03 11:07:16 +00001616 self._PrepareOutputSpace()
Simon Glasse0f23602016-09-18 16:48:33 -06001617 Print('\rStarting build...', newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001618 self.SetupBuild(board_selected, commits)
1619 self.ProcessResult(None)
1620
1621 # Create jobs to build all commits for each board
Simon Glassc78ed662019-10-31 07:42:53 -06001622 for brd in board_selected.values():
Simon Glass4a1e88b2014-08-09 15:33:00 -06001623 job = builderthread.BuilderJob()
Simon Glassc05694f2013-04-03 11:07:16 +00001624 job.board = brd
1625 job.commits = commits
1626 job.keep_outputs = keep_outputs
Simon Glassb6eb8cf2020-03-18 09:42:42 -06001627 job.work_in_output = self.work_in_output
Simon Glassc05694f2013-04-03 11:07:16 +00001628 job.step = self._step
1629 self.queue.put(job)
1630
Simon Glassd26e1442016-09-18 16:48:35 -06001631 term = threading.Thread(target=self.queue.join)
1632 term.setDaemon(True)
1633 term.start()
1634 while term.isAlive():
1635 term.join(100)
Simon Glassc05694f2013-04-03 11:07:16 +00001636
1637 # Wait until we have processed all output
1638 self.out_queue.join()
Simon Glass4433aa92014-09-05 19:00:07 -06001639 Print()
Simon Glassc2f91072014-08-28 09:43:39 -06001640 return (self.fail, self.warned)