blob: 01d8bf46e45a17a40c9c4ba7ef51cda87e657fca [file] [log] [blame]
Tom Rini83d290c2018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glassfc3fe1c2013-04-03 11:07:16 +00002# Copyright (c) 2013 The Chromium OS Authors.
3#
4# Bloat-o-meter code used here Copyright 2004 Matt Mackall <mpm@selenic.com>
5#
Simon Glassfc3fe1c2013-04-03 11:07:16 +00006
7import collections
Simon Glassfc3fe1c2013-04-03 11:07:16 +00008from datetime import datetime, timedelta
9import glob
10import os
11import re
Simon Glassc05aa032019-10-31 07:42:53 -060012import queue
Simon Glassfc3fe1c2013-04-03 11:07:16 +000013import shutil
Simon Glass2f256642016-09-18 16:48:37 -060014import signal
Simon Glassfc3fe1c2013-04-03 11:07:16 +000015import string
16import sys
Simon Glassd436e382016-09-18 16:48:35 -060017import threading
Simon Glassfc3fe1c2013-04-03 11:07:16 +000018import time
19
Simon Glass190064b2014-08-09 15:33:00 -060020import builderthread
Simon Glassfc3fe1c2013-04-03 11:07:16 +000021import command
22import gitutil
23import terminal
Simon Glass4653a882014-09-05 19:00:07 -060024from terminal import Print
Simon Glassfc3fe1c2013-04-03 11:07:16 +000025import toolchain
26
27
28"""
29Theory of Operation
30
31Please see README for user documentation, and you should be familiar with
32that before trying to make sense of this.
33
34Buildman works by keeping the machine as busy as possible, building different
35commits for different boards on multiple CPUs at once.
36
37The source repo (self.git_dir) contains all the commits to be built. Each
38thread works on a single board at a time. It checks out the first commit,
39configures it for that board, then builds it. Then it checks out the next
40commit and builds it (typically without re-configuring). When it runs out
41of commits, it gets another job from the builder and starts again with that
42board.
43
44Clearly the builder threads could work either way - they could check out a
45commit and then built it for all boards. Using separate directories for each
46commit/board pair they could leave their build product around afterwards
47also.
48
49The intent behind building a single board for multiple commits, is to make
50use of incremental builds. Since each commit is built incrementally from
51the previous one, builds are faster. Reconfiguring for a different board
52removes all intermediate object files.
53
54Many threads can be working at once, but each has its own working directory.
55When a thread finishes a build, it puts the output files into a result
56directory.
57
58The base directory used by buildman is normally '../<branch>', i.e.
59a directory higher than the source repository and named after the branch
60being built.
61
62Within the base directory, we have one subdirectory for each commit. Within
63that is one subdirectory for each board. Within that is the build output for
64that commit/board combination.
65
66Buildman also create working directories for each thread, in a .bm-work/
67subdirectory in the base dir.
68
69As an example, say we are building branch 'us-net' for boards 'sandbox' and
70'seaboard', and say that us-net has two commits. We will have directories
71like this:
72
73us-net/ base directory
74 01_of_02_g4ed4ebc_net--Add-tftp-speed-/
75 sandbox/
76 u-boot.bin
77 seaboard/
78 u-boot.bin
79 02_of_02_g4ed4ebc_net--Check-tftp-comp/
80 sandbox/
81 u-boot.bin
82 seaboard/
83 u-boot.bin
84 .bm-work/
85 00/ working directory for thread 0 (contains source checkout)
86 build/ build output
87 01/ working directory for thread 1
88 build/ build output
89 ...
90u-boot/ source directory
91 .git/ repository
92"""
93
94# Possible build outcomes
Simon Glassc05aa032019-10-31 07:42:53 -060095OUTCOME_OK, OUTCOME_WARNING, OUTCOME_ERROR, OUTCOME_UNKNOWN = list(range(4))
Simon Glassfc3fe1c2013-04-03 11:07:16 +000096
Simon Glass9a6d2e22017-04-12 18:23:26 -060097# Translate a commit subject into a valid filename (and handle unicode)
Simon Glassc05aa032019-10-31 07:42:53 -060098trans_valid_chars = str.maketrans('/: ', '---')
Simon Glassfc3fe1c2013-04-03 11:07:16 +000099
Simon Glassb464f8e2016-11-13 14:25:53 -0700100BASE_CONFIG_FILENAMES = [
101 'u-boot.cfg', 'u-boot-spl.cfg', 'u-boot-tpl.cfg'
102]
103
104EXTRA_CONFIG_FILENAMES = [
Simon Glass843312d2015-02-05 22:06:15 -0700105 '.config', '.config-spl', '.config-tpl',
106 'autoconf.mk', 'autoconf-spl.mk', 'autoconf-tpl.mk',
107 'autoconf.h', 'autoconf-spl.h','autoconf-tpl.h',
Simon Glass843312d2015-02-05 22:06:15 -0700108]
109
Simon Glass8270e3c2015-08-25 21:52:14 -0600110class Config:
111 """Holds information about configuration settings for a board."""
Simon Glassb464f8e2016-11-13 14:25:53 -0700112 def __init__(self, config_filename, target):
Simon Glass8270e3c2015-08-25 21:52:14 -0600113 self.target = target
114 self.config = {}
Simon Glassb464f8e2016-11-13 14:25:53 -0700115 for fname in config_filename:
Simon Glass8270e3c2015-08-25 21:52:14 -0600116 self.config[fname] = {}
117
118 def Add(self, fname, key, value):
119 self.config[fname][key] = value
120
121 def __hash__(self):
122 val = 0
123 for fname in self.config:
Simon Glassc05aa032019-10-31 07:42:53 -0600124 for key, value in self.config[fname].items():
125 print(key, value)
Simon Glass8270e3c2015-08-25 21:52:14 -0600126 val = val ^ hash(key) & hash(value)
127 return val
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000128
Alex Kiernan48ae4122018-05-31 04:48:34 +0000129class Environment:
130 """Holds information about environment variables for a board."""
131 def __init__(self, target):
132 self.target = target
133 self.environment = {}
134
135 def Add(self, key, value):
136 self.environment[key] = value
137
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000138class Builder:
139 """Class for building U-Boot for a particular commit.
140
141 Public members: (many should ->private)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000142 already_done: Number of builds already completed
143 base_dir: Base directory to use for builder
144 checkout: True to check out source, False to skip that step.
145 This is used for testing.
146 col: terminal.Color() object
147 count: Number of commits to build
148 do_make: Method to call to invoke Make
149 fail: Number of builds that failed due to error
150 force_build: Force building even if a build already exists
151 force_config_on_failure: If a commit fails for a board, disable
152 incremental building for the next commit we build for that
153 board, so that we will see all warnings/errors again.
Simon Glass4266dc22014-07-13 12:22:31 -0600154 force_build_failures: If a previously-built build (i.e. built on
155 a previous run of buildman) is marked as failed, rebuild it.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000156 git_dir: Git directory containing source repository
157 last_line_len: Length of the last line we printed (used for erasing
158 it with new progress information)
159 num_jobs: Number of jobs to run at once (passed to make as -j)
160 num_threads: Number of builder threads to run
161 out_queue: Queue of results to process
162 re_make_err: Compiled regular expression for ignore_lines
163 queue: Queue of jobs to run
164 threads: List of active threads
165 toolchains: Toolchains object to use for building
166 upto: Current commit number we are building (0.count-1)
167 warned: Number of builds that produced at least one warning
Simon Glass97e91522014-07-14 17:51:02 -0600168 force_reconfig: Reconfigure U-Boot on each comiit. This disables
169 incremental building, where buildman reconfigures on the first
170 commit for a baord, and then just does an incremental build for
171 the following commits. In fact buildman will reconfigure and
172 retry for any failing commits, so generally the only effect of
173 this option is to slow things down.
Simon Glass189a4962014-07-14 17:51:03 -0600174 in_tree: Build U-Boot in-tree instead of specifying an output
175 directory separate from the source code. This option is really
176 only useful for testing in-tree builds.
Simon Glassd829f122020-03-18 09:42:42 -0600177 work_in_output: Use the output directory as the work directory and
178 don't write to a separate output directory.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000179
180 Private members:
181 _base_board_dict: Last-summarised Dict of boards
182 _base_err_lines: Last-summarised list of errors
Simon Glasse30965d2014-08-28 09:43:44 -0600183 _base_warn_lines: Last-summarised list of warnings
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000184 _build_period_us: Time taken for a single build (float object).
185 _complete_delay: Expected delay until completion (timedelta)
186 _next_delay_update: Next time we plan to display a progress update
187 (datatime)
188 _show_unknown: Show unknown boards (those not built) in summary
189 _timestamps: List of timestamps for the completion of the last
190 last _timestamp_count builds. Each is a datetime object.
191 _timestamp_count: Number of timestamps to keep in our list.
192 _working_dir: Base working directory containing all threads
193 """
194 class Outcome:
195 """Records a build outcome for a single make invocation
196
197 Public Members:
198 rc: Outcome value (OUTCOME_...)
199 err_lines: List of error lines or [] if none
200 sizes: Dictionary of image size information, keyed by filename
201 - Each value is itself a dictionary containing
202 values for 'text', 'data' and 'bss', being the integer
203 size in bytes of each section.
204 func_sizes: Dictionary keyed by filename - e.g. 'u-boot'. Each
205 value is itself a dictionary:
206 key: function name
207 value: Size of function in bytes
Simon Glass843312d2015-02-05 22:06:15 -0700208 config: Dictionary keyed by filename - e.g. '.config'. Each
209 value is itself a dictionary:
210 key: config name
211 value: config value
Alex Kiernan48ae4122018-05-31 04:48:34 +0000212 environment: Dictionary keyed by environment variable, Each
213 value is the value of environment variable.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000214 """
Alex Kiernan48ae4122018-05-31 04:48:34 +0000215 def __init__(self, rc, err_lines, sizes, func_sizes, config,
216 environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000217 self.rc = rc
218 self.err_lines = err_lines
219 self.sizes = sizes
220 self.func_sizes = func_sizes
Simon Glass843312d2015-02-05 22:06:15 -0700221 self.config = config
Alex Kiernan48ae4122018-05-31 04:48:34 +0000222 self.environment = environment
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000223
224 def __init__(self, toolchains, base_dir, git_dir, num_threads, num_jobs,
Simon Glass5971ab52014-12-01 17:33:55 -0700225 gnu_make='make', checkout=True, show_unknown=True, step=1,
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600226 no_subdirs=False, full_path=False, verbose_build=False,
Simon Glassb50113f2016-11-13 14:25:51 -0700227 incremental=False, per_board_out_dir=False,
Daniel Schwierzeck2371d1b2018-01-26 16:31:05 +0100228 config_only=False, squash_config_y=False,
Simon Glassd829f122020-03-18 09:42:42 -0600229 warnings_as_errors=False, work_in_output=False):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000230 """Create a new Builder object
231
232 Args:
233 toolchains: Toolchains object to use for building
234 base_dir: Base directory to use for builder
235 git_dir: Git directory containing source repository
236 num_threads: Number of builder threads to run
237 num_jobs: Number of jobs to run at once (passed to make as -j)
Masahiro Yamada99796922014-07-22 11:19:09 +0900238 gnu_make: the command name of GNU Make.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000239 checkout: True to check out source, False to skip that step.
240 This is used for testing.
241 show_unknown: Show unknown boards (those not built) in summary
242 step: 1 to process every commit, n to process every nth commit
Simon Glassbb1501f2014-12-01 17:34:00 -0700243 no_subdirs: Don't create subdirectories when building current
244 source for a single board
245 full_path: Return the full path in CROSS_COMPILE and don't set
246 PATH
Simon Glassd2ce6582014-12-01 17:34:07 -0700247 verbose_build: Run build with V=1 and don't use 'make -s'
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600248 incremental: Always perform incremental builds; don't run make
249 mrproper when configuring
250 per_board_out_dir: Build in a separate persistent directory per
251 board rather than a thread-specific directory
Simon Glassb50113f2016-11-13 14:25:51 -0700252 config_only: Only configure each build, don't build it
Simon Glassb464f8e2016-11-13 14:25:53 -0700253 squash_config_y: Convert CONFIG options with the value 'y' to '1'
Daniel Schwierzeck2371d1b2018-01-26 16:31:05 +0100254 warnings_as_errors: Treat all compiler warnings as errors
Simon Glassd829f122020-03-18 09:42:42 -0600255 work_in_output: Use the output directory as the work directory and
256 don't write to a separate output directory.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000257 """
258 self.toolchains = toolchains
259 self.base_dir = base_dir
Simon Glassd829f122020-03-18 09:42:42 -0600260 if work_in_output:
261 self._working_dir = base_dir
262 else:
263 self._working_dir = os.path.join(base_dir, '.bm-work')
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000264 self.threads = []
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000265 self.do_make = self.Make
Masahiro Yamada99796922014-07-22 11:19:09 +0900266 self.gnu_make = gnu_make
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000267 self.checkout = checkout
268 self.num_threads = num_threads
269 self.num_jobs = num_jobs
270 self.already_done = 0
271 self.force_build = False
272 self.git_dir = git_dir
273 self._show_unknown = show_unknown
274 self._timestamp_count = 10
275 self._build_period_us = None
276 self._complete_delay = None
277 self._next_delay_update = datetime.now()
278 self.force_config_on_failure = True
Simon Glass4266dc22014-07-13 12:22:31 -0600279 self.force_build_failures = False
Simon Glass97e91522014-07-14 17:51:02 -0600280 self.force_reconfig = False
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000281 self._step = step
Simon Glass189a4962014-07-14 17:51:03 -0600282 self.in_tree = False
Simon Glass28370c12014-08-09 15:33:06 -0600283 self._error_lines = 0
Simon Glass5971ab52014-12-01 17:33:55 -0700284 self.no_subdirs = no_subdirs
Simon Glassbb1501f2014-12-01 17:34:00 -0700285 self.full_path = full_path
Simon Glassd2ce6582014-12-01 17:34:07 -0700286 self.verbose_build = verbose_build
Simon Glassb50113f2016-11-13 14:25:51 -0700287 self.config_only = config_only
Simon Glassb464f8e2016-11-13 14:25:53 -0700288 self.squash_config_y = squash_config_y
289 self.config_filenames = BASE_CONFIG_FILENAMES
Simon Glassd829f122020-03-18 09:42:42 -0600290 self.work_in_output = work_in_output
Simon Glassb464f8e2016-11-13 14:25:53 -0700291 if not self.squash_config_y:
292 self.config_filenames += EXTRA_CONFIG_FILENAMES
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000293
Daniel Schwierzeck2371d1b2018-01-26 16:31:05 +0100294 self.warnings_as_errors = warnings_as_errors
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000295 self.col = terminal.Color()
296
Simon Glasse30965d2014-08-28 09:43:44 -0600297 self._re_function = re.compile('(.*): In function.*')
298 self._re_files = re.compile('In file included from.*')
299 self._re_warning = re.compile('(.*):(\d*):(\d*): warning: .*')
Simon Glass2d483332018-11-06 16:02:11 -0700300 self._re_dtb_warning = re.compile('(.*): Warning .*')
Simon Glasse30965d2014-08-28 09:43:44 -0600301 self._re_note = re.compile('(.*):(\d*):(\d*): note: this is the location of the previous.*')
302
Simon Glassc05aa032019-10-31 07:42:53 -0600303 self.queue = queue.Queue()
304 self.out_queue = queue.Queue()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000305 for i in range(self.num_threads):
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600306 t = builderthread.BuilderThread(self, i, incremental,
307 per_board_out_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000308 t.setDaemon(True)
309 t.start()
310 self.threads.append(t)
311
312 self.last_line_len = 0
Simon Glass190064b2014-08-09 15:33:00 -0600313 t = builderthread.ResultThread(self)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000314 t.setDaemon(True)
315 t.start()
316 self.threads.append(t)
317
318 ignore_lines = ['(make.*Waiting for unfinished)', '(Segmentation fault)']
319 self.re_make_err = re.compile('|'.join(ignore_lines))
320
Simon Glass2f256642016-09-18 16:48:37 -0600321 # Handle existing graceful with SIGINT / Ctrl-C
322 signal.signal(signal.SIGINT, self.signal_handler)
323
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000324 def __del__(self):
325 """Get rid of all threads created by the builder"""
326 for t in self.threads:
327 del t
328
Simon Glass2f256642016-09-18 16:48:37 -0600329 def signal_handler(self, signal, frame):
330 sys.exit(1)
331
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600332 def SetDisplayOptions(self, show_errors=False, show_sizes=False,
Simon Glassed966652014-08-28 09:43:43 -0600333 show_detail=False, show_bloat=False,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000334 list_error_boards=False, show_config=False,
335 show_environment=False):
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600336 """Setup display options for the builder.
337
338 show_errors: True to show summarised error/warning info
339 show_sizes: Show size deltas
Simon Glassf9c094b2020-03-18 09:42:43 -0600340 show_detail: Show size delta detail for each board if show_sizes
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600341 show_bloat: Show detail for each function
Simon Glassed966652014-08-28 09:43:43 -0600342 list_error_boards: Show the boards which caused each error/warning
Simon Glass843312d2015-02-05 22:06:15 -0700343 show_config: Show config deltas
Alex Kiernan48ae4122018-05-31 04:48:34 +0000344 show_environment: Show environment deltas
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600345 """
346 self._show_errors = show_errors
347 self._show_sizes = show_sizes
348 self._show_detail = show_detail
349 self._show_bloat = show_bloat
Simon Glassed966652014-08-28 09:43:43 -0600350 self._list_error_boards = list_error_boards
Simon Glass843312d2015-02-05 22:06:15 -0700351 self._show_config = show_config
Alex Kiernan48ae4122018-05-31 04:48:34 +0000352 self._show_environment = show_environment
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600353
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000354 def _AddTimestamp(self):
355 """Add a new timestamp to the list and record the build period.
356
357 The build period is the length of time taken to perform a single
358 build (one board, one commit).
359 """
360 now = datetime.now()
361 self._timestamps.append(now)
362 count = len(self._timestamps)
363 delta = self._timestamps[-1] - self._timestamps[0]
364 seconds = delta.total_seconds()
365
366 # If we have enough data, estimate build period (time taken for a
367 # single build) and therefore completion time.
368 if count > 1 and self._next_delay_update < now:
369 self._next_delay_update = now + timedelta(seconds=2)
370 if seconds > 0:
371 self._build_period = float(seconds) / count
372 todo = self.count - self.upto
373 self._complete_delay = timedelta(microseconds=
374 self._build_period * todo * 1000000)
375 # Round it
376 self._complete_delay -= timedelta(
377 microseconds=self._complete_delay.microseconds)
378
379 if seconds > 60:
380 self._timestamps.popleft()
381 count -= 1
382
383 def ClearLine(self, length):
384 """Clear any characters on the current line
385
386 Make way for a new line of length 'length', by outputting enough
387 spaces to clear out the old line. Then remember the new length for
388 next time.
389
390 Args:
391 length: Length of new line, in characters
392 """
393 if length < self.last_line_len:
Simon Glass4653a882014-09-05 19:00:07 -0600394 Print(' ' * (self.last_line_len - length), newline=False)
395 Print('\r', newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000396 self.last_line_len = length
397 sys.stdout.flush()
398
399 def SelectCommit(self, commit, checkout=True):
400 """Checkout the selected commit for this build
401 """
402 self.commit = commit
403 if checkout and self.checkout:
404 gitutil.Checkout(commit.hash)
405
406 def Make(self, commit, brd, stage, cwd, *args, **kwargs):
407 """Run make
408
409 Args:
410 commit: Commit object that is being built
411 brd: Board object that is being built
Roger Meierfd18a892014-08-20 22:10:29 +0200412 stage: Stage that we are at (mrproper, config, build)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000413 cwd: Directory where make should be run
414 args: Arguments to pass to make
415 kwargs: Arguments to pass to command.RunPipe()
416 """
Masahiro Yamada99796922014-07-22 11:19:09 +0900417 cmd = [self.gnu_make] + list(args)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000418 result = command.RunPipe([cmd], capture=True, capture_stderr=True,
Simon Glasse62a24c2018-09-17 23:55:42 -0600419 cwd=cwd, raise_on_error=False, infile='/dev/null', **kwargs)
Simon Glass40f11fc2015-02-05 22:06:12 -0700420 if self.verbose_build:
421 result.stdout = '%s\n' % (' '.join(cmd)) + result.stdout
422 result.combined = '%s\n' % (' '.join(cmd)) + result.combined
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000423 return result
424
425 def ProcessResult(self, result):
426 """Process the result of a build, showing progress information
427
428 Args:
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600429 result: A CommandResult object, which indicates the result for
430 a single build
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000431 """
432 col = terminal.Color()
433 if result:
434 target = result.brd.target
435
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000436 self.upto += 1
437 if result.return_code != 0:
438 self.fail += 1
439 elif result.stderr:
440 self.warned += 1
441 if result.already_done:
442 self.already_done += 1
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600443 if self._verbose:
Simon Glass4653a882014-09-05 19:00:07 -0600444 Print('\r', newline=False)
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600445 self.ClearLine(0)
446 boards_selected = {target : result.brd}
447 self.ResetResultSummary(boards_selected)
448 self.ProduceResultSummary(result.commit_upto, self.commits,
449 boards_selected)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000450 else:
451 target = '(starting)'
452
453 # Display separate counts for ok, warned and fail
454 ok = self.upto - self.warned - self.fail
455 line = '\r' + self.col.Color(self.col.GREEN, '%5d' % ok)
456 line += self.col.Color(self.col.YELLOW, '%5d' % self.warned)
457 line += self.col.Color(self.col.RED, '%5d' % self.fail)
458
459 name = ' /%-5d ' % self.count
460
461 # Add our current completion time estimate
462 self._AddTimestamp()
463 if self._complete_delay:
464 name += '%s : ' % self._complete_delay
465 # When building all boards for a commit, we can print a commit
466 # progress message.
467 if result and result.commit_upto is None:
468 name += 'commit %2d/%-3d' % (self.commit_upto + 1,
469 self.commit_count)
470
471 name += target
Simon Glass4653a882014-09-05 19:00:07 -0600472 Print(line + name, newline=False)
Simon Glass960421e2016-11-15 15:32:59 -0700473 length = 16 + len(name)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000474 self.ClearLine(length)
475
476 def _GetOutputDir(self, commit_upto):
477 """Get the name of the output directory for a commit number
478
479 The output directory is typically .../<branch>/<commit>.
480
481 Args:
482 commit_upto: Commit number to use (0..self.count-1)
483 """
Simon Glass5971ab52014-12-01 17:33:55 -0700484 commit_dir = None
Simon Glassfea58582014-08-09 15:32:59 -0600485 if self.commits:
486 commit = self.commits[commit_upto]
487 subject = commit.subject.translate(trans_valid_chars)
Simon Glass925f6ad2020-03-18 09:42:45 -0600488 # See _GetOutputSpaceRemovals() which parses this name
Simon Glassfea58582014-08-09 15:32:59 -0600489 commit_dir = ('%02d_of_%02d_g%s_%s' % (commit_upto + 1,
490 self.commit_count, commit.hash, subject[:20]))
Simon Glass5971ab52014-12-01 17:33:55 -0700491 elif not self.no_subdirs:
Simon Glassfea58582014-08-09 15:32:59 -0600492 commit_dir = 'current'
Simon Glass5971ab52014-12-01 17:33:55 -0700493 if not commit_dir:
494 return self.base_dir
495 return os.path.join(self.base_dir, commit_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000496
497 def GetBuildDir(self, commit_upto, target):
498 """Get the name of the build directory for a commit number
499
500 The build directory is typically .../<branch>/<commit>/<target>.
501
502 Args:
503 commit_upto: Commit number to use (0..self.count-1)
504 target: Target name
505 """
506 output_dir = self._GetOutputDir(commit_upto)
507 return os.path.join(output_dir, target)
508
509 def GetDoneFile(self, commit_upto, target):
510 """Get the name of the done file for a commit number
511
512 Args:
513 commit_upto: Commit number to use (0..self.count-1)
514 target: Target name
515 """
516 return os.path.join(self.GetBuildDir(commit_upto, target), 'done')
517
518 def GetSizesFile(self, commit_upto, target):
519 """Get the name of the sizes file for a commit number
520
521 Args:
522 commit_upto: Commit number to use (0..self.count-1)
523 target: Target name
524 """
525 return os.path.join(self.GetBuildDir(commit_upto, target), 'sizes')
526
527 def GetFuncSizesFile(self, commit_upto, target, elf_fname):
528 """Get the name of the funcsizes file for a commit number and ELF file
529
530 Args:
531 commit_upto: Commit number to use (0..self.count-1)
532 target: Target name
533 elf_fname: Filename of elf image
534 """
535 return os.path.join(self.GetBuildDir(commit_upto, target),
536 '%s.sizes' % elf_fname.replace('/', '-'))
537
538 def GetObjdumpFile(self, commit_upto, target, elf_fname):
539 """Get the name of the objdump file for a commit number and ELF file
540
541 Args:
542 commit_upto: Commit number to use (0..self.count-1)
543 target: Target name
544 elf_fname: Filename of elf image
545 """
546 return os.path.join(self.GetBuildDir(commit_upto, target),
547 '%s.objdump' % elf_fname.replace('/', '-'))
548
549 def GetErrFile(self, commit_upto, target):
550 """Get the name of the err file for a commit number
551
552 Args:
553 commit_upto: Commit number to use (0..self.count-1)
554 target: Target name
555 """
556 output_dir = self.GetBuildDir(commit_upto, target)
557 return os.path.join(output_dir, 'err')
558
559 def FilterErrors(self, lines):
560 """Filter out errors in which we have no interest
561
562 We should probably use map().
563
564 Args:
565 lines: List of error lines, each a string
566 Returns:
567 New list with only interesting lines included
568 """
569 out_lines = []
570 for line in lines:
571 if not self.re_make_err.search(line):
572 out_lines.append(line)
573 return out_lines
574
575 def ReadFuncSizes(self, fname, fd):
576 """Read function sizes from the output of 'nm'
577
578 Args:
579 fd: File containing data to read
580 fname: Filename we are reading from (just for errors)
581
582 Returns:
583 Dictionary containing size of each function in bytes, indexed by
584 function name.
585 """
586 sym = {}
587 for line in fd.readlines():
588 try:
Tom Rinid08c38c2019-12-06 15:31:31 -0500589 if line.strip():
590 size, type, name = line[:-1].split()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000591 except:
Simon Glass4653a882014-09-05 19:00:07 -0600592 Print("Invalid line in file '%s': '%s'" % (fname, line[:-1]))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000593 continue
594 if type in 'tTdDbB':
595 # function names begin with '.' on 64-bit powerpc
596 if '.' in name[1:]:
597 name = 'static.' + name.split('.')[0]
598 sym[name] = sym.get(name, 0) + int(size, 16)
599 return sym
600
Simon Glass843312d2015-02-05 22:06:15 -0700601 def _ProcessConfig(self, fname):
602 """Read in a .config, autoconf.mk or autoconf.h file
603
604 This function handles all config file types. It ignores comments and
605 any #defines which don't start with CONFIG_.
606
607 Args:
608 fname: Filename to read
609
610 Returns:
611 Dictionary:
612 key: Config name (e.g. CONFIG_DM)
613 value: Config value (e.g. 1)
614 """
615 config = {}
616 if os.path.exists(fname):
617 with open(fname) as fd:
618 for line in fd:
619 line = line.strip()
620 if line.startswith('#define'):
621 values = line[8:].split(' ', 1)
622 if len(values) > 1:
623 key, value = values
624 else:
625 key = values[0]
Simon Glassb464f8e2016-11-13 14:25:53 -0700626 value = '1' if self.squash_config_y else ''
Simon Glass843312d2015-02-05 22:06:15 -0700627 if not key.startswith('CONFIG_'):
628 continue
629 elif not line or line[0] in ['#', '*', '/']:
630 continue
631 else:
632 key, value = line.split('=', 1)
Simon Glassb464f8e2016-11-13 14:25:53 -0700633 if self.squash_config_y and value == 'y':
634 value = '1'
Simon Glass843312d2015-02-05 22:06:15 -0700635 config[key] = value
636 return config
637
Alex Kiernan48ae4122018-05-31 04:48:34 +0000638 def _ProcessEnvironment(self, fname):
639 """Read in a uboot.env file
640
641 This function reads in environment variables from a file.
642
643 Args:
644 fname: Filename to read
645
646 Returns:
647 Dictionary:
648 key: environment variable (e.g. bootlimit)
649 value: value of environment variable (e.g. 1)
650 """
651 environment = {}
652 if os.path.exists(fname):
653 with open(fname) as fd:
654 for line in fd.read().split('\0'):
655 try:
656 key, value = line.split('=', 1)
657 environment[key] = value
658 except ValueError:
659 # ignore lines we can't parse
660 pass
661 return environment
662
Simon Glass843312d2015-02-05 22:06:15 -0700663 def GetBuildOutcome(self, commit_upto, target, read_func_sizes,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000664 read_config, read_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000665 """Work out the outcome of a build.
666
667 Args:
668 commit_upto: Commit number to check (0..n-1)
669 target: Target board to check
670 read_func_sizes: True to read function size information
Simon Glass843312d2015-02-05 22:06:15 -0700671 read_config: True to read .config and autoconf.h files
Alex Kiernan48ae4122018-05-31 04:48:34 +0000672 read_environment: True to read uboot.env files
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000673
674 Returns:
675 Outcome object
676 """
677 done_file = self.GetDoneFile(commit_upto, target)
678 sizes_file = self.GetSizesFile(commit_upto, target)
679 sizes = {}
680 func_sizes = {}
Simon Glass843312d2015-02-05 22:06:15 -0700681 config = {}
Alex Kiernan48ae4122018-05-31 04:48:34 +0000682 environment = {}
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000683 if os.path.exists(done_file):
684 with open(done_file, 'r') as fd:
Simon Glass347ea0b2019-04-26 19:02:23 -0600685 try:
686 return_code = int(fd.readline())
687 except ValueError:
688 # The file may be empty due to running out of disk space.
689 # Try a rebuild
690 return_code = 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000691 err_lines = []
692 err_file = self.GetErrFile(commit_upto, target)
693 if os.path.exists(err_file):
694 with open(err_file, 'r') as fd:
695 err_lines = self.FilterErrors(fd.readlines())
696
697 # Decide whether the build was ok, failed or created warnings
698 if return_code:
699 rc = OUTCOME_ERROR
700 elif len(err_lines):
701 rc = OUTCOME_WARNING
702 else:
703 rc = OUTCOME_OK
704
705 # Convert size information to our simple format
706 if os.path.exists(sizes_file):
707 with open(sizes_file, 'r') as fd:
708 for line in fd.readlines():
709 values = line.split()
710 rodata = 0
711 if len(values) > 6:
712 rodata = int(values[6], 16)
713 size_dict = {
714 'all' : int(values[0]) + int(values[1]) +
715 int(values[2]),
716 'text' : int(values[0]) - rodata,
717 'data' : int(values[1]),
718 'bss' : int(values[2]),
719 'rodata' : rodata,
720 }
721 sizes[values[5]] = size_dict
722
723 if read_func_sizes:
724 pattern = self.GetFuncSizesFile(commit_upto, target, '*')
725 for fname in glob.glob(pattern):
726 with open(fname, 'r') as fd:
727 dict_name = os.path.basename(fname).replace('.sizes',
728 '')
729 func_sizes[dict_name] = self.ReadFuncSizes(fname, fd)
730
Simon Glass843312d2015-02-05 22:06:15 -0700731 if read_config:
732 output_dir = self.GetBuildDir(commit_upto, target)
Simon Glassb464f8e2016-11-13 14:25:53 -0700733 for name in self.config_filenames:
Simon Glass843312d2015-02-05 22:06:15 -0700734 fname = os.path.join(output_dir, name)
735 config[name] = self._ProcessConfig(fname)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000736
Alex Kiernan48ae4122018-05-31 04:48:34 +0000737 if read_environment:
738 output_dir = self.GetBuildDir(commit_upto, target)
739 fname = os.path.join(output_dir, 'uboot.env')
740 environment = self._ProcessEnvironment(fname)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000741
Alex Kiernan48ae4122018-05-31 04:48:34 +0000742 return Builder.Outcome(rc, err_lines, sizes, func_sizes, config,
743 environment)
744
745 return Builder.Outcome(OUTCOME_UNKNOWN, [], {}, {}, {}, {})
Simon Glass843312d2015-02-05 22:06:15 -0700746
747 def GetResultSummary(self, boards_selected, commit_upto, read_func_sizes,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000748 read_config, read_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000749 """Calculate a summary of the results of building a commit.
750
751 Args:
752 board_selected: Dict containing boards to summarise
753 commit_upto: Commit number to summarize (0..self.count-1)
754 read_func_sizes: True to read function size information
Simon Glass843312d2015-02-05 22:06:15 -0700755 read_config: True to read .config and autoconf.h files
Alex Kiernan48ae4122018-05-31 04:48:34 +0000756 read_environment: True to read uboot.env files
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000757
758 Returns:
759 Tuple:
760 Dict containing boards which passed building this commit.
761 keyed by board.target
Simon Glasse30965d2014-08-28 09:43:44 -0600762 List containing a summary of error lines
Simon Glassed966652014-08-28 09:43:43 -0600763 Dict keyed by error line, containing a list of the Board
764 objects with that error
Simon Glasse30965d2014-08-28 09:43:44 -0600765 List containing a summary of warning lines
766 Dict keyed by error line, containing a list of the Board
767 objects with that warning
Simon Glass8270e3c2015-08-25 21:52:14 -0600768 Dictionary keyed by board.target. Each value is a dictionary:
769 key: filename - e.g. '.config'
Simon Glass843312d2015-02-05 22:06:15 -0700770 value is itself a dictionary:
771 key: config name
772 value: config value
Alex Kiernan48ae4122018-05-31 04:48:34 +0000773 Dictionary keyed by board.target. Each value is a dictionary:
774 key: environment variable
775 value: value of environment variable
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000776 """
Simon Glasse30965d2014-08-28 09:43:44 -0600777 def AddLine(lines_summary, lines_boards, line, board):
778 line = line.rstrip()
779 if line in lines_boards:
780 lines_boards[line].append(board)
781 else:
782 lines_boards[line] = [board]
783 lines_summary.append(line)
784
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000785 board_dict = {}
786 err_lines_summary = []
Simon Glassed966652014-08-28 09:43:43 -0600787 err_lines_boards = {}
Simon Glasse30965d2014-08-28 09:43:44 -0600788 warn_lines_summary = []
789 warn_lines_boards = {}
Simon Glass843312d2015-02-05 22:06:15 -0700790 config = {}
Alex Kiernan48ae4122018-05-31 04:48:34 +0000791 environment = {}
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000792
Simon Glassc05aa032019-10-31 07:42:53 -0600793 for board in boards_selected.values():
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000794 outcome = self.GetBuildOutcome(commit_upto, board.target,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000795 read_func_sizes, read_config,
796 read_environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000797 board_dict[board.target] = outcome
Simon Glasse30965d2014-08-28 09:43:44 -0600798 last_func = None
799 last_was_warning = False
800 for line in outcome.err_lines:
801 if line:
802 if (self._re_function.match(line) or
803 self._re_files.match(line)):
804 last_func = line
Simon Glassed966652014-08-28 09:43:43 -0600805 else:
Simon Glass2d483332018-11-06 16:02:11 -0700806 is_warning = (self._re_warning.match(line) or
807 self._re_dtb_warning.match(line))
Simon Glasse30965d2014-08-28 09:43:44 -0600808 is_note = self._re_note.match(line)
809 if is_warning or (last_was_warning and is_note):
810 if last_func:
811 AddLine(warn_lines_summary, warn_lines_boards,
812 last_func, board)
813 AddLine(warn_lines_summary, warn_lines_boards,
814 line, board)
815 else:
816 if last_func:
817 AddLine(err_lines_summary, err_lines_boards,
818 last_func, board)
819 AddLine(err_lines_summary, err_lines_boards,
820 line, board)
821 last_was_warning = is_warning
822 last_func = None
Simon Glassb464f8e2016-11-13 14:25:53 -0700823 tconfig = Config(self.config_filenames, board.target)
824 for fname in self.config_filenames:
Simon Glass843312d2015-02-05 22:06:15 -0700825 if outcome.config:
Simon Glassc05aa032019-10-31 07:42:53 -0600826 for key, value in outcome.config[fname].items():
Simon Glass8270e3c2015-08-25 21:52:14 -0600827 tconfig.Add(fname, key, value)
828 config[board.target] = tconfig
Simon Glass843312d2015-02-05 22:06:15 -0700829
Alex Kiernan48ae4122018-05-31 04:48:34 +0000830 tenvironment = Environment(board.target)
831 if outcome.environment:
Simon Glassc05aa032019-10-31 07:42:53 -0600832 for key, value in outcome.environment.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +0000833 tenvironment.Add(key, value)
834 environment[board.target] = tenvironment
835
Simon Glasse30965d2014-08-28 09:43:44 -0600836 return (board_dict, err_lines_summary, err_lines_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000837 warn_lines_summary, warn_lines_boards, config, environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000838
839 def AddOutcome(self, board_dict, arch_list, changes, char, color):
840 """Add an output to our list of outcomes for each architecture
841
842 This simple function adds failing boards (changes) to the
843 relevant architecture string, so we can print the results out
844 sorted by architecture.
845
846 Args:
847 board_dict: Dict containing all boards
848 arch_list: Dict keyed by arch name. Value is a string containing
849 a list of board names which failed for that arch.
850 changes: List of boards to add to arch_list
851 color: terminal.Colour object
852 """
853 done_arch = {}
854 for target in changes:
855 if target in board_dict:
856 arch = board_dict[target].arch
857 else:
858 arch = 'unknown'
859 str = self.col.Color(color, ' ' + target)
860 if not arch in done_arch:
Simon Glass63c619e2015-02-05 22:06:11 -0700861 str = ' %s %s' % (self.col.Color(color, char), str)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000862 done_arch[arch] = True
863 if not arch in arch_list:
864 arch_list[arch] = str
865 else:
866 arch_list[arch] += str
867
868
869 def ColourNum(self, num):
870 color = self.col.RED if num > 0 else self.col.GREEN
871 if num == 0:
872 return '0'
873 return self.col.Color(color, str(num))
874
875 def ResetResultSummary(self, board_selected):
876 """Reset the results summary ready for use.
877
878 Set up the base board list to be all those selected, and set the
879 error lines to empty.
880
881 Following this, calls to PrintResultSummary() will use this
882 information to work out what has changed.
883
884 Args:
885 board_selected: Dict containing boards to summarise, keyed by
886 board.target
887 """
888 self._base_board_dict = {}
889 for board in board_selected:
Alex Kiernan48ae4122018-05-31 04:48:34 +0000890 self._base_board_dict[board] = Builder.Outcome(0, [], [], {}, {},
891 {})
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000892 self._base_err_lines = []
Simon Glasse30965d2014-08-28 09:43:44 -0600893 self._base_warn_lines = []
894 self._base_err_line_boards = {}
895 self._base_warn_line_boards = {}
Simon Glass8270e3c2015-08-25 21:52:14 -0600896 self._base_config = None
Alex Kiernan48ae4122018-05-31 04:48:34 +0000897 self._base_environment = None
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000898
899 def PrintFuncSizeDetail(self, fname, old, new):
900 grow, shrink, add, remove, up, down = 0, 0, 0, 0, 0, 0
901 delta, common = [], {}
902
903 for a in old:
904 if a in new:
905 common[a] = 1
906
907 for name in old:
908 if name not in common:
909 remove += 1
910 down += old[name]
911 delta.append([-old[name], name])
912
913 for name in new:
914 if name not in common:
915 add += 1
916 up += new[name]
917 delta.append([new[name], name])
918
919 for name in common:
920 diff = new.get(name, 0) - old.get(name, 0)
921 if diff > 0:
922 grow, up = grow + 1, up + diff
923 elif diff < 0:
924 shrink, down = shrink + 1, down - diff
925 delta.append([diff, name])
926
927 delta.sort()
928 delta.reverse()
929
930 args = [add, -remove, grow, -shrink, up, -down, up - down]
Tom Rinid5686a62017-05-22 13:48:52 -0400931 if max(args) == 0 and min(args) == 0:
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000932 return
933 args = [self.ColourNum(x) for x in args]
934 indent = ' ' * 15
Simon Glass4653a882014-09-05 19:00:07 -0600935 Print('%s%s: add: %s/%s, grow: %s/%s bytes: %s/%s (%s)' %
936 tuple([indent, self.col.Color(self.col.YELLOW, fname)] + args))
937 Print('%s %-38s %7s %7s %+7s' % (indent, 'function', 'old', 'new',
938 'delta'))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000939 for diff, name in delta:
940 if diff:
941 color = self.col.RED if diff > 0 else self.col.GREEN
942 msg = '%s %-38s %7s %7s %+7d' % (indent, name,
943 old.get(name, '-'), new.get(name,'-'), diff)
Simon Glass4653a882014-09-05 19:00:07 -0600944 Print(msg, colour=color)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000945
946
947 def PrintSizeDetail(self, target_list, show_bloat):
948 """Show details size information for each board
949
950 Args:
951 target_list: List of targets, each a dict containing:
952 'target': Target name
953 'total_diff': Total difference in bytes across all areas
954 <part_name>: Difference for that part
955 show_bloat: Show detail for each function
956 """
957 targets_by_diff = sorted(target_list, reverse=True,
958 key=lambda x: x['_total_diff'])
959 for result in targets_by_diff:
960 printed_target = False
961 for name in sorted(result):
962 diff = result[name]
963 if name.startswith('_'):
964 continue
965 if diff != 0:
966 color = self.col.RED if diff > 0 else self.col.GREEN
967 msg = ' %s %+d' % (name, diff)
968 if not printed_target:
Simon Glass4653a882014-09-05 19:00:07 -0600969 Print('%10s %-15s:' % ('', result['_target']),
970 newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000971 printed_target = True
Simon Glass4653a882014-09-05 19:00:07 -0600972 Print(msg, colour=color, newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000973 if printed_target:
Simon Glass4653a882014-09-05 19:00:07 -0600974 Print()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000975 if show_bloat:
976 target = result['_target']
977 outcome = result['_outcome']
978 base_outcome = self._base_board_dict[target]
979 for fname in outcome.func_sizes:
980 self.PrintFuncSizeDetail(fname,
981 base_outcome.func_sizes[fname],
982 outcome.func_sizes[fname])
983
984
985 def PrintSizeSummary(self, board_selected, board_dict, show_detail,
986 show_bloat):
987 """Print a summary of image sizes broken down by section.
988
989 The summary takes the form of one line per architecture. The
990 line contains deltas for each of the sections (+ means the section
Flavio Suligoi9de5c392020-01-29 09:56:05 +0100991 got bigger, - means smaller). The numbers are the average number
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000992 of bytes that a board in this section increased by.
993
994 For example:
995 powerpc: (622 boards) text -0.0
996 arm: (285 boards) text -0.0
997 nds32: (3 boards) text -8.0
998
999 Args:
1000 board_selected: Dict containing boards to summarise, keyed by
1001 board.target
1002 board_dict: Dict containing boards for which we built this
1003 commit, keyed by board.target. The value is an Outcome object.
Simon Glassf9c094b2020-03-18 09:42:43 -06001004 show_detail: Show size delta detail for each board
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001005 show_bloat: Show detail for each function
1006 """
1007 arch_list = {}
1008 arch_count = {}
1009
1010 # Calculate changes in size for different image parts
1011 # The previous sizes are in Board.sizes, for each board
1012 for target in board_dict:
1013 if target not in board_selected:
1014 continue
1015 base_sizes = self._base_board_dict[target].sizes
1016 outcome = board_dict[target]
1017 sizes = outcome.sizes
1018
1019 # Loop through the list of images, creating a dict of size
1020 # changes for each image/part. We end up with something like
1021 # {'target' : 'snapper9g45, 'data' : 5, 'u-boot-spl:text' : -4}
1022 # which means that U-Boot data increased by 5 bytes and SPL
1023 # text decreased by 4.
1024 err = {'_target' : target}
1025 for image in sizes:
1026 if image in base_sizes:
1027 base_image = base_sizes[image]
1028 # Loop through the text, data, bss parts
1029 for part in sorted(sizes[image]):
1030 diff = sizes[image][part] - base_image[part]
1031 col = None
1032 if diff:
1033 if image == 'u-boot':
1034 name = part
1035 else:
1036 name = image + ':' + part
1037 err[name] = diff
1038 arch = board_selected[target].arch
1039 if not arch in arch_count:
1040 arch_count[arch] = 1
1041 else:
1042 arch_count[arch] += 1
1043 if not sizes:
1044 pass # Only add to our list when we have some stats
1045 elif not arch in arch_list:
1046 arch_list[arch] = [err]
1047 else:
1048 arch_list[arch].append(err)
1049
1050 # We now have a list of image size changes sorted by arch
1051 # Print out a summary of these
Simon Glassc05aa032019-10-31 07:42:53 -06001052 for arch, target_list in arch_list.items():
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001053 # Get total difference for each type
1054 totals = {}
1055 for result in target_list:
1056 total = 0
Simon Glassc05aa032019-10-31 07:42:53 -06001057 for name, diff in result.items():
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001058 if name.startswith('_'):
1059 continue
1060 total += diff
1061 if name in totals:
1062 totals[name] += diff
1063 else:
1064 totals[name] = diff
1065 result['_total_diff'] = total
1066 result['_outcome'] = board_dict[result['_target']]
1067
1068 count = len(target_list)
1069 printed_arch = False
1070 for name in sorted(totals):
1071 diff = totals[name]
1072 if diff:
1073 # Display the average difference in this name for this
1074 # architecture
1075 avg_diff = float(diff) / count
1076 color = self.col.RED if avg_diff > 0 else self.col.GREEN
1077 msg = ' %s %+1.1f' % (name, avg_diff)
1078 if not printed_arch:
Simon Glass4653a882014-09-05 19:00:07 -06001079 Print('%10s: (for %d/%d boards)' % (arch, count,
1080 arch_count[arch]), newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001081 printed_arch = True
Simon Glass4653a882014-09-05 19:00:07 -06001082 Print(msg, colour=color, newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001083
1084 if printed_arch:
Simon Glass4653a882014-09-05 19:00:07 -06001085 Print()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001086 if show_detail:
1087 self.PrintSizeDetail(target_list, show_bloat)
1088
1089
1090 def PrintResultSummary(self, board_selected, board_dict, err_lines,
Simon Glasse30965d2014-08-28 09:43:44 -06001091 err_line_boards, warn_lines, warn_line_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001092 config, environment, show_sizes, show_detail,
1093 show_bloat, show_config, show_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001094 """Compare results with the base results and display delta.
1095
1096 Only boards mentioned in board_selected will be considered. This
1097 function is intended to be called repeatedly with the results of
1098 each commit. It therefore shows a 'diff' between what it saw in
1099 the last call and what it sees now.
1100
1101 Args:
1102 board_selected: Dict containing boards to summarise, keyed by
1103 board.target
1104 board_dict: Dict containing boards for which we built this
1105 commit, keyed by board.target. The value is an Outcome object.
1106 err_lines: A list of errors for this commit, or [] if there is
1107 none, or we don't want to print errors
Simon Glassed966652014-08-28 09:43:43 -06001108 err_line_boards: Dict keyed by error line, containing a list of
1109 the Board objects with that error
Simon Glasse30965d2014-08-28 09:43:44 -06001110 warn_lines: A list of warnings for this commit, or [] if there is
1111 none, or we don't want to print errors
1112 warn_line_boards: Dict keyed by warning line, containing a list of
1113 the Board objects with that warning
Simon Glass843312d2015-02-05 22:06:15 -07001114 config: Dictionary keyed by filename - e.g. '.config'. Each
1115 value is itself a dictionary:
1116 key: config name
1117 value: config value
Alex Kiernan48ae4122018-05-31 04:48:34 +00001118 environment: Dictionary keyed by environment variable, Each
1119 value is the value of environment variable.
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001120 show_sizes: Show image size deltas
Simon Glassf9c094b2020-03-18 09:42:43 -06001121 show_detail: Show size delta detail for each board if show_sizes
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001122 show_bloat: Show detail for each function
Simon Glass843312d2015-02-05 22:06:15 -07001123 show_config: Show config changes
Alex Kiernan48ae4122018-05-31 04:48:34 +00001124 show_environment: Show environment changes
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001125 """
Simon Glasse30965d2014-08-28 09:43:44 -06001126 def _BoardList(line, line_boards):
Simon Glassed966652014-08-28 09:43:43 -06001127 """Helper function to get a line of boards containing a line
1128
1129 Args:
1130 line: Error line to search for
1131 Return:
1132 String containing a list of boards with that error line, or
1133 '' if the user has not requested such a list
1134 """
1135 if self._list_error_boards:
1136 names = []
Simon Glasse30965d2014-08-28 09:43:44 -06001137 for board in line_boards[line]:
Simon Glassf66153b2014-10-16 01:05:55 -06001138 if not board.target in names:
1139 names.append(board.target)
Simon Glassed966652014-08-28 09:43:43 -06001140 names_str = '(%s) ' % ','.join(names)
1141 else:
1142 names_str = ''
1143 return names_str
1144
Simon Glasse30965d2014-08-28 09:43:44 -06001145 def _CalcErrorDelta(base_lines, base_line_boards, lines, line_boards,
1146 char):
1147 better_lines = []
1148 worse_lines = []
1149 for line in lines:
1150 if line not in base_lines:
1151 worse_lines.append(char + '+' +
1152 _BoardList(line, line_boards) + line)
1153 for line in base_lines:
1154 if line not in lines:
1155 better_lines.append(char + '-' +
1156 _BoardList(line, base_line_boards) + line)
1157 return better_lines, worse_lines
1158
Simon Glass843312d2015-02-05 22:06:15 -07001159 def _CalcConfig(delta, name, config):
1160 """Calculate configuration changes
1161
1162 Args:
1163 delta: Type of the delta, e.g. '+'
1164 name: name of the file which changed (e.g. .config)
1165 config: configuration change dictionary
1166 key: config name
1167 value: config value
1168 Returns:
1169 String containing the configuration changes which can be
1170 printed
1171 """
1172 out = ''
1173 for key in sorted(config.keys()):
1174 out += '%s=%s ' % (key, config[key])
Simon Glass8270e3c2015-08-25 21:52:14 -06001175 return '%s %s: %s' % (delta, name, out)
Simon Glass843312d2015-02-05 22:06:15 -07001176
Simon Glass8270e3c2015-08-25 21:52:14 -06001177 def _AddConfig(lines, name, config_plus, config_minus, config_change):
1178 """Add changes in configuration to a list
Simon Glass843312d2015-02-05 22:06:15 -07001179
1180 Args:
Simon Glass8270e3c2015-08-25 21:52:14 -06001181 lines: list to add to
1182 name: config file name
Simon Glass843312d2015-02-05 22:06:15 -07001183 config_plus: configurations added, dictionary
1184 key: config name
1185 value: config value
1186 config_minus: configurations removed, dictionary
1187 key: config name
1188 value: config value
1189 config_change: configurations changed, dictionary
1190 key: config name
1191 value: config value
1192 """
1193 if config_plus:
Simon Glass8270e3c2015-08-25 21:52:14 -06001194 lines.append(_CalcConfig('+', name, config_plus))
Simon Glass843312d2015-02-05 22:06:15 -07001195 if config_minus:
Simon Glass8270e3c2015-08-25 21:52:14 -06001196 lines.append(_CalcConfig('-', name, config_minus))
Simon Glass843312d2015-02-05 22:06:15 -07001197 if config_change:
Simon Glass8270e3c2015-08-25 21:52:14 -06001198 lines.append(_CalcConfig('c', name, config_change))
1199
1200 def _OutputConfigInfo(lines):
1201 for line in lines:
1202 if not line:
1203 continue
1204 if line[0] == '+':
1205 col = self.col.GREEN
1206 elif line[0] == '-':
1207 col = self.col.RED
1208 elif line[0] == 'c':
1209 col = self.col.YELLOW
1210 Print(' ' + line, newline=True, colour=col)
1211
Simon Glassb206d872020-04-09 15:08:28 -06001212 def _OutputErrLines(err_lines, colour):
1213 """Output the line of error/warning lines, if not empty
1214
1215 Also increments self._error_lines if err_lines not empty
1216
1217 Args:
1218 err_lines: List of strings, each an error or warning line,
1219 possibly including a list of boards with that error/warning
1220 colour: Colour to use for output
1221 """
1222 if err_lines:
1223 Print('\n'.join(err_lines), colour=colour)
1224 self._error_lines += 1
1225
Simon Glass843312d2015-02-05 22:06:15 -07001226
Simon Glass4cf2b222018-11-06 16:02:12 -07001227 ok_boards = [] # List of boards fixed since last commit
Simon Glass6af71012018-11-06 16:02:13 -07001228 warn_boards = [] # List of boards with warnings since last commit
Simon Glass4cf2b222018-11-06 16:02:12 -07001229 err_boards = [] # List of new broken boards since last commit
1230 new_boards = [] # List of boards that didn't exist last time
1231 unknown_boards = [] # List of boards that were not built
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001232
1233 for target in board_dict:
1234 if target not in board_selected:
1235 continue
1236
1237 # If the board was built last time, add its outcome to a list
1238 if target in self._base_board_dict:
1239 base_outcome = self._base_board_dict[target].rc
1240 outcome = board_dict[target]
1241 if outcome.rc == OUTCOME_UNKNOWN:
Simon Glass4cf2b222018-11-06 16:02:12 -07001242 unknown_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001243 elif outcome.rc < base_outcome:
Simon Glass6af71012018-11-06 16:02:13 -07001244 if outcome.rc == OUTCOME_WARNING:
1245 warn_boards.append(target)
1246 else:
1247 ok_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001248 elif outcome.rc > base_outcome:
Simon Glass6af71012018-11-06 16:02:13 -07001249 if outcome.rc == OUTCOME_WARNING:
1250 warn_boards.append(target)
1251 else:
1252 err_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001253 else:
Simon Glass4cf2b222018-11-06 16:02:12 -07001254 new_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001255
Simon Glassb206d872020-04-09 15:08:28 -06001256 # Get a list of errors and warnings that have appeared, and disappeared
Simon Glasse30965d2014-08-28 09:43:44 -06001257 better_err, worse_err = _CalcErrorDelta(self._base_err_lines,
1258 self._base_err_line_boards, err_lines, err_line_boards, '')
1259 better_warn, worse_warn = _CalcErrorDelta(self._base_warn_lines,
1260 self._base_warn_line_boards, warn_lines, warn_line_boards, 'w')
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001261
1262 # Display results by arch
Simon Glass6af71012018-11-06 16:02:13 -07001263 if any((ok_boards, warn_boards, err_boards, unknown_boards, new_boards,
1264 worse_err, better_err, worse_warn, better_warn)):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001265 arch_list = {}
Simon Glass4cf2b222018-11-06 16:02:12 -07001266 self.AddOutcome(board_selected, arch_list, ok_boards, '',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001267 self.col.GREEN)
Simon Glass6af71012018-11-06 16:02:13 -07001268 self.AddOutcome(board_selected, arch_list, warn_boards, 'w+',
1269 self.col.YELLOW)
Simon Glass4cf2b222018-11-06 16:02:12 -07001270 self.AddOutcome(board_selected, arch_list, err_boards, '+',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001271 self.col.RED)
Simon Glass4cf2b222018-11-06 16:02:12 -07001272 self.AddOutcome(board_selected, arch_list, new_boards, '*', self.col.BLUE)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001273 if self._show_unknown:
Simon Glass4cf2b222018-11-06 16:02:12 -07001274 self.AddOutcome(board_selected, arch_list, unknown_boards, '?',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001275 self.col.MAGENTA)
Simon Glassc05aa032019-10-31 07:42:53 -06001276 for arch, target_list in arch_list.items():
Simon Glass4653a882014-09-05 19:00:07 -06001277 Print('%10s: %s' % (arch, target_list))
Simon Glass28370c12014-08-09 15:33:06 -06001278 self._error_lines += 1
Simon Glassb206d872020-04-09 15:08:28 -06001279 _OutputErrLines(better_err, colour=self.col.GREEN)
1280 _OutputErrLines(worse_err, colour=self.col.RED)
1281 _OutputErrLines(better_warn, colour=self.col.CYAN)
Simon Glass5627bd92020-04-09 15:08:35 -06001282 _OutputErrLines(worse_warn, colour=self.col.YELLOW)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001283
1284 if show_sizes:
1285 self.PrintSizeSummary(board_selected, board_dict, show_detail,
1286 show_bloat)
1287
Alex Kiernan48ae4122018-05-31 04:48:34 +00001288 if show_environment and self._base_environment:
1289 lines = []
1290
1291 for target in board_dict:
1292 if target not in board_selected:
1293 continue
1294
1295 tbase = self._base_environment[target]
1296 tenvironment = environment[target]
1297 environment_plus = {}
1298 environment_minus = {}
1299 environment_change = {}
1300 base = tbase.environment
Simon Glassc05aa032019-10-31 07:42:53 -06001301 for key, value in tenvironment.environment.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001302 if key not in base:
1303 environment_plus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001304 for key, value in base.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001305 if key not in tenvironment.environment:
1306 environment_minus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001307 for key, value in base.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001308 new_value = tenvironment.environment.get(key)
1309 if new_value and value != new_value:
1310 desc = '%s -> %s' % (value, new_value)
1311 environment_change[key] = desc
1312
1313 _AddConfig(lines, target, environment_plus, environment_minus,
1314 environment_change)
1315
1316 _OutputConfigInfo(lines)
1317
Simon Glass8270e3c2015-08-25 21:52:14 -06001318 if show_config and self._base_config:
1319 summary = {}
1320 arch_config_plus = {}
1321 arch_config_minus = {}
1322 arch_config_change = {}
1323 arch_list = []
1324
1325 for target in board_dict:
1326 if target not in board_selected:
Simon Glass843312d2015-02-05 22:06:15 -07001327 continue
Simon Glass8270e3c2015-08-25 21:52:14 -06001328 arch = board_selected[target].arch
1329 if arch not in arch_list:
1330 arch_list.append(arch)
1331
1332 for arch in arch_list:
1333 arch_config_plus[arch] = {}
1334 arch_config_minus[arch] = {}
1335 arch_config_change[arch] = {}
Simon Glassb464f8e2016-11-13 14:25:53 -07001336 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001337 arch_config_plus[arch][name] = {}
1338 arch_config_minus[arch][name] = {}
1339 arch_config_change[arch][name] = {}
1340
1341 for target in board_dict:
1342 if target not in board_selected:
1343 continue
1344
1345 arch = board_selected[target].arch
1346
1347 all_config_plus = {}
1348 all_config_minus = {}
1349 all_config_change = {}
1350 tbase = self._base_config[target]
1351 tconfig = config[target]
1352 lines = []
Simon Glassb464f8e2016-11-13 14:25:53 -07001353 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001354 if not tconfig.config[name]:
1355 continue
1356 config_plus = {}
1357 config_minus = {}
1358 config_change = {}
1359 base = tbase.config[name]
Simon Glassc05aa032019-10-31 07:42:53 -06001360 for key, value in tconfig.config[name].items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001361 if key not in base:
1362 config_plus[key] = value
1363 all_config_plus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001364 for key, value in base.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001365 if key not in tconfig.config[name]:
1366 config_minus[key] = value
1367 all_config_minus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001368 for key, value in base.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001369 new_value = tconfig.config.get(key)
1370 if new_value and value != new_value:
1371 desc = '%s -> %s' % (value, new_value)
1372 config_change[key] = desc
1373 all_config_change[key] = desc
1374
1375 arch_config_plus[arch][name].update(config_plus)
1376 arch_config_minus[arch][name].update(config_minus)
1377 arch_config_change[arch][name].update(config_change)
1378
1379 _AddConfig(lines, name, config_plus, config_minus,
1380 config_change)
1381 _AddConfig(lines, 'all', all_config_plus, all_config_minus,
1382 all_config_change)
1383 summary[target] = '\n'.join(lines)
1384
1385 lines_by_target = {}
Simon Glassc05aa032019-10-31 07:42:53 -06001386 for target, lines in summary.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001387 if lines in lines_by_target:
1388 lines_by_target[lines].append(target)
1389 else:
1390 lines_by_target[lines] = [target]
1391
1392 for arch in arch_list:
1393 lines = []
1394 all_plus = {}
1395 all_minus = {}
1396 all_change = {}
Simon Glassb464f8e2016-11-13 14:25:53 -07001397 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001398 all_plus.update(arch_config_plus[arch][name])
1399 all_minus.update(arch_config_minus[arch][name])
1400 all_change.update(arch_config_change[arch][name])
1401 _AddConfig(lines, name, arch_config_plus[arch][name],
1402 arch_config_minus[arch][name],
1403 arch_config_change[arch][name])
1404 _AddConfig(lines, 'all', all_plus, all_minus, all_change)
1405 #arch_summary[target] = '\n'.join(lines)
1406 if lines:
1407 Print('%s:' % arch)
1408 _OutputConfigInfo(lines)
1409
Simon Glassc05aa032019-10-31 07:42:53 -06001410 for lines, targets in lines_by_target.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001411 if not lines:
1412 continue
1413 Print('%s :' % ' '.join(sorted(targets)))
1414 _OutputConfigInfo(lines.split('\n'))
1415
Simon Glass843312d2015-02-05 22:06:15 -07001416
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001417 # Save our updated information for the next call to this function
1418 self._base_board_dict = board_dict
1419 self._base_err_lines = err_lines
Simon Glasse30965d2014-08-28 09:43:44 -06001420 self._base_warn_lines = warn_lines
1421 self._base_err_line_boards = err_line_boards
1422 self._base_warn_line_boards = warn_line_boards
Simon Glass843312d2015-02-05 22:06:15 -07001423 self._base_config = config
Alex Kiernan48ae4122018-05-31 04:48:34 +00001424 self._base_environment = environment
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001425
1426 # Get a list of boards that did not get built, if needed
1427 not_built = []
1428 for board in board_selected:
1429 if not board in board_dict:
1430 not_built.append(board)
1431 if not_built:
Simon Glass4653a882014-09-05 19:00:07 -06001432 Print("Boards not built (%d): %s" % (len(not_built),
1433 ', '.join(not_built)))
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001434
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001435 def ProduceResultSummary(self, commit_upto, commits, board_selected):
Simon Glasse30965d2014-08-28 09:43:44 -06001436 (board_dict, err_lines, err_line_boards, warn_lines,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001437 warn_line_boards, config, environment) = self.GetResultSummary(
Simon Glassed966652014-08-28 09:43:43 -06001438 board_selected, commit_upto,
Simon Glass843312d2015-02-05 22:06:15 -07001439 read_func_sizes=self._show_bloat,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001440 read_config=self._show_config,
1441 read_environment=self._show_environment)
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001442 if commits:
1443 msg = '%02d: %s' % (commit_upto + 1,
1444 commits[commit_upto].subject)
Simon Glass4653a882014-09-05 19:00:07 -06001445 Print(msg, colour=self.col.BLUE)
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001446 self.PrintResultSummary(board_selected, board_dict,
Simon Glassed966652014-08-28 09:43:43 -06001447 err_lines if self._show_errors else [], err_line_boards,
Simon Glasse30965d2014-08-28 09:43:44 -06001448 warn_lines if self._show_errors else [], warn_line_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001449 config, environment, self._show_sizes, self._show_detail,
1450 self._show_bloat, self._show_config, self._show_environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001451
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001452 def ShowSummary(self, commits, board_selected):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001453 """Show a build summary for U-Boot for a given board list.
1454
1455 Reset the result summary, then repeatedly call GetResultSummary on
1456 each commit's results, then display the differences we see.
1457
1458 Args:
1459 commit: Commit objects to summarise
1460 board_selected: Dict containing boards to summarise
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001461 """
Simon Glassfea58582014-08-09 15:32:59 -06001462 self.commit_count = len(commits) if commits else 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001463 self.commits = commits
1464 self.ResetResultSummary(board_selected)
Simon Glass28370c12014-08-09 15:33:06 -06001465 self._error_lines = 0
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001466
1467 for commit_upto in range(0, self.commit_count, self._step):
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001468 self.ProduceResultSummary(commit_upto, commits, board_selected)
Simon Glass28370c12014-08-09 15:33:06 -06001469 if not self._error_lines:
Simon Glass4653a882014-09-05 19:00:07 -06001470 Print('(no errors to report)', colour=self.col.GREEN)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001471
1472
1473 def SetupBuild(self, board_selected, commits):
1474 """Set up ready to start a build.
1475
1476 Args:
1477 board_selected: Selected boards to build
1478 commits: Selected commits to build
1479 """
1480 # First work out how many commits we will build
Simon Glassc05aa032019-10-31 07:42:53 -06001481 count = (self.commit_count + self._step - 1) // self._step
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001482 self.count = len(board_selected) * count
1483 self.upto = self.warned = self.fail = 0
1484 self._timestamps = collections.deque()
1485
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001486 def GetThreadDir(self, thread_num):
1487 """Get the directory path to the working dir for a thread.
1488
1489 Args:
1490 thread_num: Number of thread to check.
1491 """
Simon Glassd829f122020-03-18 09:42:42 -06001492 if self.work_in_output:
1493 return self._working_dir
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001494 return os.path.join(self._working_dir, '%02d' % thread_num)
1495
Simon Glassfea58582014-08-09 15:32:59 -06001496 def _PrepareThread(self, thread_num, setup_git):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001497 """Prepare the working directory for a thread.
1498
1499 This clones or fetches the repo into the thread's work directory.
1500
1501 Args:
1502 thread_num: Thread number (0, 1, ...)
Simon Glassfea58582014-08-09 15:32:59 -06001503 setup_git: True to set up a git repo clone
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001504 """
1505 thread_dir = self.GetThreadDir(thread_num)
Simon Glass190064b2014-08-09 15:33:00 -06001506 builderthread.Mkdir(thread_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001507 git_dir = os.path.join(thread_dir, '.git')
1508
1509 # Clone the repo if it doesn't already exist
1510 # TODO(sjg@chromium): Perhaps some git hackery to symlink instead, so
1511 # we have a private index but uses the origin repo's contents?
Simon Glassfea58582014-08-09 15:32:59 -06001512 if setup_git and self.git_dir:
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001513 src_dir = os.path.abspath(self.git_dir)
1514 if os.path.exists(git_dir):
1515 gitutil.Fetch(git_dir, thread_dir)
1516 else:
Simon Glass21f0eb32016-09-18 16:48:31 -06001517 Print('\rCloning repo for thread %d' % thread_num,
1518 newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001519 gitutil.Clone(src_dir, thread_dir)
Simon Glass21f0eb32016-09-18 16:48:31 -06001520 Print('\r%s\r' % (' ' * 30), newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001521
Simon Glassfea58582014-08-09 15:32:59 -06001522 def _PrepareWorkingSpace(self, max_threads, setup_git):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001523 """Prepare the working directory for use.
1524
1525 Set up the git repo for each thread.
1526
1527 Args:
1528 max_threads: Maximum number of threads we expect to need.
Simon Glassfea58582014-08-09 15:32:59 -06001529 setup_git: True to set up a git repo clone
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001530 """
Simon Glass190064b2014-08-09 15:33:00 -06001531 builderthread.Mkdir(self._working_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001532 for thread in range(max_threads):
Simon Glassfea58582014-08-09 15:32:59 -06001533 self._PrepareThread(thread, setup_git)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001534
Simon Glass925f6ad2020-03-18 09:42:45 -06001535 def _GetOutputSpaceRemovals(self):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001536 """Get the output directories ready to receive files.
1537
Simon Glass925f6ad2020-03-18 09:42:45 -06001538 Figure out what needs to be deleted in the output directory before it
1539 can be used. We only delete old buildman directories which have the
1540 expected name pattern. See _GetOutputDir().
1541
1542 Returns:
1543 List of full paths of directories to remove
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001544 """
Simon Glass1a915672014-12-01 17:33:53 -07001545 if not self.commits:
1546 return
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001547 dir_list = []
1548 for commit_upto in range(self.commit_count):
1549 dir_list.append(self._GetOutputDir(commit_upto))
1550
Simon Glassb222abe2016-09-18 16:48:32 -06001551 to_remove = []
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001552 for dirname in glob.glob(os.path.join(self.base_dir, '*')):
1553 if dirname not in dir_list:
Simon Glass925f6ad2020-03-18 09:42:45 -06001554 leaf = dirname[len(self.base_dir) + 1:]
1555 m = re.match('[0-9]+_of_[0-9]+_g[0-9a-f]+_.*', leaf)
1556 if m:
1557 to_remove.append(dirname)
1558 return to_remove
1559
1560 def _PrepareOutputSpace(self):
1561 """Get the output directories ready to receive files.
1562
1563 We delete any output directories which look like ones we need to
1564 create. Having left over directories is confusing when the user wants
1565 to check the output manually.
1566 """
1567 to_remove = self._GetOutputSpaceRemovals()
Simon Glassb222abe2016-09-18 16:48:32 -06001568 if to_remove:
Simon Glassb2d89bc2020-03-18 09:42:46 -06001569 Print('Removing %d old build directories...' % len(to_remove),
Simon Glassb222abe2016-09-18 16:48:32 -06001570 newline=False)
1571 for dirname in to_remove:
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001572 shutil.rmtree(dirname)
Simon Glassb2d89bc2020-03-18 09:42:46 -06001573 Print('done')
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001574
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001575 def BuildBoards(self, commits, board_selected, keep_outputs, verbose):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001576 """Build all commits for a list of boards
1577
1578 Args:
1579 commits: List of commits to be build, each a Commit object
1580 boards_selected: Dict of selected boards, key is target name,
1581 value is Board object
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001582 keep_outputs: True to save build output files
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001583 verbose: Display build results as they are completed
Simon Glass2c3deb92014-08-28 09:43:39 -06001584 Returns:
1585 Tuple containing:
1586 - number of boards that failed to build
1587 - number of boards that issued warnings
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001588 """
Simon Glassfea58582014-08-09 15:32:59 -06001589 self.commit_count = len(commits) if commits else 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001590 self.commits = commits
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001591 self._verbose = verbose
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001592
1593 self.ResetResultSummary(board_selected)
Thierry Redingf3d015c2014-08-19 10:22:39 +02001594 builderthread.Mkdir(self.base_dir, parents = True)
Simon Glassfea58582014-08-09 15:32:59 -06001595 self._PrepareWorkingSpace(min(self.num_threads, len(board_selected)),
1596 commits is not None)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001597 self._PrepareOutputSpace()
Simon Glass745b3952016-09-18 16:48:33 -06001598 Print('\rStarting build...', newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001599 self.SetupBuild(board_selected, commits)
1600 self.ProcessResult(None)
1601
1602 # Create jobs to build all commits for each board
Simon Glassc05aa032019-10-31 07:42:53 -06001603 for brd in board_selected.values():
Simon Glass190064b2014-08-09 15:33:00 -06001604 job = builderthread.BuilderJob()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001605 job.board = brd
1606 job.commits = commits
1607 job.keep_outputs = keep_outputs
Simon Glassd829f122020-03-18 09:42:42 -06001608 job.work_in_output = self.work_in_output
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001609 job.step = self._step
1610 self.queue.put(job)
1611
Simon Glassd436e382016-09-18 16:48:35 -06001612 term = threading.Thread(target=self.queue.join)
1613 term.setDaemon(True)
1614 term.start()
1615 while term.isAlive():
1616 term.join(100)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001617
1618 # Wait until we have processed all output
1619 self.out_queue.join()
Simon Glass4653a882014-09-05 19:00:07 -06001620 Print()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001621 self.ClearLine(0)
Simon Glass2c3deb92014-08-28 09:43:39 -06001622 return (self.fail, self.warned)