blob: 081c1d09016da814d06de02c8df787fb4ee6fd6c [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
340 show_detail: Show detail for each board
341 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)
488 commit_dir = ('%02d_of_%02d_g%s_%s' % (commit_upto + 1,
489 self.commit_count, commit.hash, subject[:20]))
Simon Glass5971ab52014-12-01 17:33:55 -0700490 elif not self.no_subdirs:
Simon Glassfea58582014-08-09 15:32:59 -0600491 commit_dir = 'current'
Simon Glass5971ab52014-12-01 17:33:55 -0700492 if not commit_dir:
493 return self.base_dir
494 return os.path.join(self.base_dir, commit_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000495
496 def GetBuildDir(self, commit_upto, target):
497 """Get the name of the build directory for a commit number
498
499 The build directory is typically .../<branch>/<commit>/<target>.
500
501 Args:
502 commit_upto: Commit number to use (0..self.count-1)
503 target: Target name
504 """
505 output_dir = self._GetOutputDir(commit_upto)
506 return os.path.join(output_dir, target)
507
508 def GetDoneFile(self, commit_upto, target):
509 """Get the name of the done file for a commit number
510
511 Args:
512 commit_upto: Commit number to use (0..self.count-1)
513 target: Target name
514 """
515 return os.path.join(self.GetBuildDir(commit_upto, target), 'done')
516
517 def GetSizesFile(self, commit_upto, target):
518 """Get the name of the sizes file for a commit number
519
520 Args:
521 commit_upto: Commit number to use (0..self.count-1)
522 target: Target name
523 """
524 return os.path.join(self.GetBuildDir(commit_upto, target), 'sizes')
525
526 def GetFuncSizesFile(self, commit_upto, target, elf_fname):
527 """Get the name of the funcsizes file for a commit number and ELF file
528
529 Args:
530 commit_upto: Commit number to use (0..self.count-1)
531 target: Target name
532 elf_fname: Filename of elf image
533 """
534 return os.path.join(self.GetBuildDir(commit_upto, target),
535 '%s.sizes' % elf_fname.replace('/', '-'))
536
537 def GetObjdumpFile(self, commit_upto, target, elf_fname):
538 """Get the name of the objdump file for a commit number and ELF file
539
540 Args:
541 commit_upto: Commit number to use (0..self.count-1)
542 target: Target name
543 elf_fname: Filename of elf image
544 """
545 return os.path.join(self.GetBuildDir(commit_upto, target),
546 '%s.objdump' % elf_fname.replace('/', '-'))
547
548 def GetErrFile(self, commit_upto, target):
549 """Get the name of the err file for a commit number
550
551 Args:
552 commit_upto: Commit number to use (0..self.count-1)
553 target: Target name
554 """
555 output_dir = self.GetBuildDir(commit_upto, target)
556 return os.path.join(output_dir, 'err')
557
558 def FilterErrors(self, lines):
559 """Filter out errors in which we have no interest
560
561 We should probably use map().
562
563 Args:
564 lines: List of error lines, each a string
565 Returns:
566 New list with only interesting lines included
567 """
568 out_lines = []
569 for line in lines:
570 if not self.re_make_err.search(line):
571 out_lines.append(line)
572 return out_lines
573
574 def ReadFuncSizes(self, fname, fd):
575 """Read function sizes from the output of 'nm'
576
577 Args:
578 fd: File containing data to read
579 fname: Filename we are reading from (just for errors)
580
581 Returns:
582 Dictionary containing size of each function in bytes, indexed by
583 function name.
584 """
585 sym = {}
586 for line in fd.readlines():
587 try:
Tom Rinid08c38c2019-12-06 15:31:31 -0500588 if line.strip():
589 size, type, name = line[:-1].split()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000590 except:
Simon Glass4653a882014-09-05 19:00:07 -0600591 Print("Invalid line in file '%s': '%s'" % (fname, line[:-1]))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000592 continue
593 if type in 'tTdDbB':
594 # function names begin with '.' on 64-bit powerpc
595 if '.' in name[1:]:
596 name = 'static.' + name.split('.')[0]
597 sym[name] = sym.get(name, 0) + int(size, 16)
598 return sym
599
Simon Glass843312d2015-02-05 22:06:15 -0700600 def _ProcessConfig(self, fname):
601 """Read in a .config, autoconf.mk or autoconf.h file
602
603 This function handles all config file types. It ignores comments and
604 any #defines which don't start with CONFIG_.
605
606 Args:
607 fname: Filename to read
608
609 Returns:
610 Dictionary:
611 key: Config name (e.g. CONFIG_DM)
612 value: Config value (e.g. 1)
613 """
614 config = {}
615 if os.path.exists(fname):
616 with open(fname) as fd:
617 for line in fd:
618 line = line.strip()
619 if line.startswith('#define'):
620 values = line[8:].split(' ', 1)
621 if len(values) > 1:
622 key, value = values
623 else:
624 key = values[0]
Simon Glassb464f8e2016-11-13 14:25:53 -0700625 value = '1' if self.squash_config_y else ''
Simon Glass843312d2015-02-05 22:06:15 -0700626 if not key.startswith('CONFIG_'):
627 continue
628 elif not line or line[0] in ['#', '*', '/']:
629 continue
630 else:
631 key, value = line.split('=', 1)
Simon Glassb464f8e2016-11-13 14:25:53 -0700632 if self.squash_config_y and value == 'y':
633 value = '1'
Simon Glass843312d2015-02-05 22:06:15 -0700634 config[key] = value
635 return config
636
Alex Kiernan48ae4122018-05-31 04:48:34 +0000637 def _ProcessEnvironment(self, fname):
638 """Read in a uboot.env file
639
640 This function reads in environment variables from a file.
641
642 Args:
643 fname: Filename to read
644
645 Returns:
646 Dictionary:
647 key: environment variable (e.g. bootlimit)
648 value: value of environment variable (e.g. 1)
649 """
650 environment = {}
651 if os.path.exists(fname):
652 with open(fname) as fd:
653 for line in fd.read().split('\0'):
654 try:
655 key, value = line.split('=', 1)
656 environment[key] = value
657 except ValueError:
658 # ignore lines we can't parse
659 pass
660 return environment
661
Simon Glass843312d2015-02-05 22:06:15 -0700662 def GetBuildOutcome(self, commit_upto, target, read_func_sizes,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000663 read_config, read_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000664 """Work out the outcome of a build.
665
666 Args:
667 commit_upto: Commit number to check (0..n-1)
668 target: Target board to check
669 read_func_sizes: True to read function size information
Simon Glass843312d2015-02-05 22:06:15 -0700670 read_config: True to read .config and autoconf.h files
Alex Kiernan48ae4122018-05-31 04:48:34 +0000671 read_environment: True to read uboot.env files
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000672
673 Returns:
674 Outcome object
675 """
676 done_file = self.GetDoneFile(commit_upto, target)
677 sizes_file = self.GetSizesFile(commit_upto, target)
678 sizes = {}
679 func_sizes = {}
Simon Glass843312d2015-02-05 22:06:15 -0700680 config = {}
Alex Kiernan48ae4122018-05-31 04:48:34 +0000681 environment = {}
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000682 if os.path.exists(done_file):
683 with open(done_file, 'r') as fd:
Simon Glass347ea0b2019-04-26 19:02:23 -0600684 try:
685 return_code = int(fd.readline())
686 except ValueError:
687 # The file may be empty due to running out of disk space.
688 # Try a rebuild
689 return_code = 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000690 err_lines = []
691 err_file = self.GetErrFile(commit_upto, target)
692 if os.path.exists(err_file):
693 with open(err_file, 'r') as fd:
694 err_lines = self.FilterErrors(fd.readlines())
695
696 # Decide whether the build was ok, failed or created warnings
697 if return_code:
698 rc = OUTCOME_ERROR
699 elif len(err_lines):
700 rc = OUTCOME_WARNING
701 else:
702 rc = OUTCOME_OK
703
704 # Convert size information to our simple format
705 if os.path.exists(sizes_file):
706 with open(sizes_file, 'r') as fd:
707 for line in fd.readlines():
708 values = line.split()
709 rodata = 0
710 if len(values) > 6:
711 rodata = int(values[6], 16)
712 size_dict = {
713 'all' : int(values[0]) + int(values[1]) +
714 int(values[2]),
715 'text' : int(values[0]) - rodata,
716 'data' : int(values[1]),
717 'bss' : int(values[2]),
718 'rodata' : rodata,
719 }
720 sizes[values[5]] = size_dict
721
722 if read_func_sizes:
723 pattern = self.GetFuncSizesFile(commit_upto, target, '*')
724 for fname in glob.glob(pattern):
725 with open(fname, 'r') as fd:
726 dict_name = os.path.basename(fname).replace('.sizes',
727 '')
728 func_sizes[dict_name] = self.ReadFuncSizes(fname, fd)
729
Simon Glass843312d2015-02-05 22:06:15 -0700730 if read_config:
731 output_dir = self.GetBuildDir(commit_upto, target)
Simon Glassb464f8e2016-11-13 14:25:53 -0700732 for name in self.config_filenames:
Simon Glass843312d2015-02-05 22:06:15 -0700733 fname = os.path.join(output_dir, name)
734 config[name] = self._ProcessConfig(fname)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000735
Alex Kiernan48ae4122018-05-31 04:48:34 +0000736 if read_environment:
737 output_dir = self.GetBuildDir(commit_upto, target)
738 fname = os.path.join(output_dir, 'uboot.env')
739 environment = self._ProcessEnvironment(fname)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000740
Alex Kiernan48ae4122018-05-31 04:48:34 +0000741 return Builder.Outcome(rc, err_lines, sizes, func_sizes, config,
742 environment)
743
744 return Builder.Outcome(OUTCOME_UNKNOWN, [], {}, {}, {}, {})
Simon Glass843312d2015-02-05 22:06:15 -0700745
746 def GetResultSummary(self, boards_selected, commit_upto, read_func_sizes,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000747 read_config, read_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000748 """Calculate a summary of the results of building a commit.
749
750 Args:
751 board_selected: Dict containing boards to summarise
752 commit_upto: Commit number to summarize (0..self.count-1)
753 read_func_sizes: True to read function size information
Simon Glass843312d2015-02-05 22:06:15 -0700754 read_config: True to read .config and autoconf.h files
Alex Kiernan48ae4122018-05-31 04:48:34 +0000755 read_environment: True to read uboot.env files
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000756
757 Returns:
758 Tuple:
759 Dict containing boards which passed building this commit.
760 keyed by board.target
Simon Glasse30965d2014-08-28 09:43:44 -0600761 List containing a summary of error lines
Simon Glassed966652014-08-28 09:43:43 -0600762 Dict keyed by error line, containing a list of the Board
763 objects with that error
Simon Glasse30965d2014-08-28 09:43:44 -0600764 List containing a summary of warning lines
765 Dict keyed by error line, containing a list of the Board
766 objects with that warning
Simon Glass8270e3c2015-08-25 21:52:14 -0600767 Dictionary keyed by board.target. Each value is a dictionary:
768 key: filename - e.g. '.config'
Simon Glass843312d2015-02-05 22:06:15 -0700769 value is itself a dictionary:
770 key: config name
771 value: config value
Alex Kiernan48ae4122018-05-31 04:48:34 +0000772 Dictionary keyed by board.target. Each value is a dictionary:
773 key: environment variable
774 value: value of environment variable
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000775 """
Simon Glasse30965d2014-08-28 09:43:44 -0600776 def AddLine(lines_summary, lines_boards, line, board):
777 line = line.rstrip()
778 if line in lines_boards:
779 lines_boards[line].append(board)
780 else:
781 lines_boards[line] = [board]
782 lines_summary.append(line)
783
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000784 board_dict = {}
785 err_lines_summary = []
Simon Glassed966652014-08-28 09:43:43 -0600786 err_lines_boards = {}
Simon Glasse30965d2014-08-28 09:43:44 -0600787 warn_lines_summary = []
788 warn_lines_boards = {}
Simon Glass843312d2015-02-05 22:06:15 -0700789 config = {}
Alex Kiernan48ae4122018-05-31 04:48:34 +0000790 environment = {}
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000791
Simon Glassc05aa032019-10-31 07:42:53 -0600792 for board in boards_selected.values():
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000793 outcome = self.GetBuildOutcome(commit_upto, board.target,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000794 read_func_sizes, read_config,
795 read_environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000796 board_dict[board.target] = outcome
Simon Glasse30965d2014-08-28 09:43:44 -0600797 last_func = None
798 last_was_warning = False
799 for line in outcome.err_lines:
800 if line:
801 if (self._re_function.match(line) or
802 self._re_files.match(line)):
803 last_func = line
Simon Glassed966652014-08-28 09:43:43 -0600804 else:
Simon Glass2d483332018-11-06 16:02:11 -0700805 is_warning = (self._re_warning.match(line) or
806 self._re_dtb_warning.match(line))
Simon Glasse30965d2014-08-28 09:43:44 -0600807 is_note = self._re_note.match(line)
808 if is_warning or (last_was_warning and is_note):
809 if last_func:
810 AddLine(warn_lines_summary, warn_lines_boards,
811 last_func, board)
812 AddLine(warn_lines_summary, warn_lines_boards,
813 line, board)
814 else:
815 if last_func:
816 AddLine(err_lines_summary, err_lines_boards,
817 last_func, board)
818 AddLine(err_lines_summary, err_lines_boards,
819 line, board)
820 last_was_warning = is_warning
821 last_func = None
Simon Glassb464f8e2016-11-13 14:25:53 -0700822 tconfig = Config(self.config_filenames, board.target)
823 for fname in self.config_filenames:
Simon Glass843312d2015-02-05 22:06:15 -0700824 if outcome.config:
Simon Glassc05aa032019-10-31 07:42:53 -0600825 for key, value in outcome.config[fname].items():
Simon Glass8270e3c2015-08-25 21:52:14 -0600826 tconfig.Add(fname, key, value)
827 config[board.target] = tconfig
Simon Glass843312d2015-02-05 22:06:15 -0700828
Alex Kiernan48ae4122018-05-31 04:48:34 +0000829 tenvironment = Environment(board.target)
830 if outcome.environment:
Simon Glassc05aa032019-10-31 07:42:53 -0600831 for key, value in outcome.environment.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +0000832 tenvironment.Add(key, value)
833 environment[board.target] = tenvironment
834
Simon Glasse30965d2014-08-28 09:43:44 -0600835 return (board_dict, err_lines_summary, err_lines_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000836 warn_lines_summary, warn_lines_boards, config, environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000837
838 def AddOutcome(self, board_dict, arch_list, changes, char, color):
839 """Add an output to our list of outcomes for each architecture
840
841 This simple function adds failing boards (changes) to the
842 relevant architecture string, so we can print the results out
843 sorted by architecture.
844
845 Args:
846 board_dict: Dict containing all boards
847 arch_list: Dict keyed by arch name. Value is a string containing
848 a list of board names which failed for that arch.
849 changes: List of boards to add to arch_list
850 color: terminal.Colour object
851 """
852 done_arch = {}
853 for target in changes:
854 if target in board_dict:
855 arch = board_dict[target].arch
856 else:
857 arch = 'unknown'
858 str = self.col.Color(color, ' ' + target)
859 if not arch in done_arch:
Simon Glass63c619e2015-02-05 22:06:11 -0700860 str = ' %s %s' % (self.col.Color(color, char), str)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000861 done_arch[arch] = True
862 if not arch in arch_list:
863 arch_list[arch] = str
864 else:
865 arch_list[arch] += str
866
867
868 def ColourNum(self, num):
869 color = self.col.RED if num > 0 else self.col.GREEN
870 if num == 0:
871 return '0'
872 return self.col.Color(color, str(num))
873
874 def ResetResultSummary(self, board_selected):
875 """Reset the results summary ready for use.
876
877 Set up the base board list to be all those selected, and set the
878 error lines to empty.
879
880 Following this, calls to PrintResultSummary() will use this
881 information to work out what has changed.
882
883 Args:
884 board_selected: Dict containing boards to summarise, keyed by
885 board.target
886 """
887 self._base_board_dict = {}
888 for board in board_selected:
Alex Kiernan48ae4122018-05-31 04:48:34 +0000889 self._base_board_dict[board] = Builder.Outcome(0, [], [], {}, {},
890 {})
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000891 self._base_err_lines = []
Simon Glasse30965d2014-08-28 09:43:44 -0600892 self._base_warn_lines = []
893 self._base_err_line_boards = {}
894 self._base_warn_line_boards = {}
Simon Glass8270e3c2015-08-25 21:52:14 -0600895 self._base_config = None
Alex Kiernan48ae4122018-05-31 04:48:34 +0000896 self._base_environment = None
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000897
898 def PrintFuncSizeDetail(self, fname, old, new):
899 grow, shrink, add, remove, up, down = 0, 0, 0, 0, 0, 0
900 delta, common = [], {}
901
902 for a in old:
903 if a in new:
904 common[a] = 1
905
906 for name in old:
907 if name not in common:
908 remove += 1
909 down += old[name]
910 delta.append([-old[name], name])
911
912 for name in new:
913 if name not in common:
914 add += 1
915 up += new[name]
916 delta.append([new[name], name])
917
918 for name in common:
919 diff = new.get(name, 0) - old.get(name, 0)
920 if diff > 0:
921 grow, up = grow + 1, up + diff
922 elif diff < 0:
923 shrink, down = shrink + 1, down - diff
924 delta.append([diff, name])
925
926 delta.sort()
927 delta.reverse()
928
929 args = [add, -remove, grow, -shrink, up, -down, up - down]
Tom Rinid5686a62017-05-22 13:48:52 -0400930 if max(args) == 0 and min(args) == 0:
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000931 return
932 args = [self.ColourNum(x) for x in args]
933 indent = ' ' * 15
Simon Glass4653a882014-09-05 19:00:07 -0600934 Print('%s%s: add: %s/%s, grow: %s/%s bytes: %s/%s (%s)' %
935 tuple([indent, self.col.Color(self.col.YELLOW, fname)] + args))
936 Print('%s %-38s %7s %7s %+7s' % (indent, 'function', 'old', 'new',
937 'delta'))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000938 for diff, name in delta:
939 if diff:
940 color = self.col.RED if diff > 0 else self.col.GREEN
941 msg = '%s %-38s %7s %7s %+7d' % (indent, name,
942 old.get(name, '-'), new.get(name,'-'), diff)
Simon Glass4653a882014-09-05 19:00:07 -0600943 Print(msg, colour=color)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000944
945
946 def PrintSizeDetail(self, target_list, show_bloat):
947 """Show details size information for each board
948
949 Args:
950 target_list: List of targets, each a dict containing:
951 'target': Target name
952 'total_diff': Total difference in bytes across all areas
953 <part_name>: Difference for that part
954 show_bloat: Show detail for each function
955 """
956 targets_by_diff = sorted(target_list, reverse=True,
957 key=lambda x: x['_total_diff'])
958 for result in targets_by_diff:
959 printed_target = False
960 for name in sorted(result):
961 diff = result[name]
962 if name.startswith('_'):
963 continue
964 if diff != 0:
965 color = self.col.RED if diff > 0 else self.col.GREEN
966 msg = ' %s %+d' % (name, diff)
967 if not printed_target:
Simon Glass4653a882014-09-05 19:00:07 -0600968 Print('%10s %-15s:' % ('', result['_target']),
969 newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000970 printed_target = True
Simon Glass4653a882014-09-05 19:00:07 -0600971 Print(msg, colour=color, newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000972 if printed_target:
Simon Glass4653a882014-09-05 19:00:07 -0600973 Print()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000974 if show_bloat:
975 target = result['_target']
976 outcome = result['_outcome']
977 base_outcome = self._base_board_dict[target]
978 for fname in outcome.func_sizes:
979 self.PrintFuncSizeDetail(fname,
980 base_outcome.func_sizes[fname],
981 outcome.func_sizes[fname])
982
983
984 def PrintSizeSummary(self, board_selected, board_dict, show_detail,
985 show_bloat):
986 """Print a summary of image sizes broken down by section.
987
988 The summary takes the form of one line per architecture. The
989 line contains deltas for each of the sections (+ means the section
Flavio Suligoi9de5c392020-01-29 09:56:05 +0100990 got bigger, - means smaller). The numbers are the average number
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000991 of bytes that a board in this section increased by.
992
993 For example:
994 powerpc: (622 boards) text -0.0
995 arm: (285 boards) text -0.0
996 nds32: (3 boards) text -8.0
997
998 Args:
999 board_selected: Dict containing boards to summarise, keyed by
1000 board.target
1001 board_dict: Dict containing boards for which we built this
1002 commit, keyed by board.target. The value is an Outcome object.
1003 show_detail: Show detail for each board
1004 show_bloat: Show detail for each function
1005 """
1006 arch_list = {}
1007 arch_count = {}
1008
1009 # Calculate changes in size for different image parts
1010 # The previous sizes are in Board.sizes, for each board
1011 for target in board_dict:
1012 if target not in board_selected:
1013 continue
1014 base_sizes = self._base_board_dict[target].sizes
1015 outcome = board_dict[target]
1016 sizes = outcome.sizes
1017
1018 # Loop through the list of images, creating a dict of size
1019 # changes for each image/part. We end up with something like
1020 # {'target' : 'snapper9g45, 'data' : 5, 'u-boot-spl:text' : -4}
1021 # which means that U-Boot data increased by 5 bytes and SPL
1022 # text decreased by 4.
1023 err = {'_target' : target}
1024 for image in sizes:
1025 if image in base_sizes:
1026 base_image = base_sizes[image]
1027 # Loop through the text, data, bss parts
1028 for part in sorted(sizes[image]):
1029 diff = sizes[image][part] - base_image[part]
1030 col = None
1031 if diff:
1032 if image == 'u-boot':
1033 name = part
1034 else:
1035 name = image + ':' + part
1036 err[name] = diff
1037 arch = board_selected[target].arch
1038 if not arch in arch_count:
1039 arch_count[arch] = 1
1040 else:
1041 arch_count[arch] += 1
1042 if not sizes:
1043 pass # Only add to our list when we have some stats
1044 elif not arch in arch_list:
1045 arch_list[arch] = [err]
1046 else:
1047 arch_list[arch].append(err)
1048
1049 # We now have a list of image size changes sorted by arch
1050 # Print out a summary of these
Simon Glassc05aa032019-10-31 07:42:53 -06001051 for arch, target_list in arch_list.items():
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001052 # Get total difference for each type
1053 totals = {}
1054 for result in target_list:
1055 total = 0
Simon Glassc05aa032019-10-31 07:42:53 -06001056 for name, diff in result.items():
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001057 if name.startswith('_'):
1058 continue
1059 total += diff
1060 if name in totals:
1061 totals[name] += diff
1062 else:
1063 totals[name] = diff
1064 result['_total_diff'] = total
1065 result['_outcome'] = board_dict[result['_target']]
1066
1067 count = len(target_list)
1068 printed_arch = False
1069 for name in sorted(totals):
1070 diff = totals[name]
1071 if diff:
1072 # Display the average difference in this name for this
1073 # architecture
1074 avg_diff = float(diff) / count
1075 color = self.col.RED if avg_diff > 0 else self.col.GREEN
1076 msg = ' %s %+1.1f' % (name, avg_diff)
1077 if not printed_arch:
Simon Glass4653a882014-09-05 19:00:07 -06001078 Print('%10s: (for %d/%d boards)' % (arch, count,
1079 arch_count[arch]), newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001080 printed_arch = True
Simon Glass4653a882014-09-05 19:00:07 -06001081 Print(msg, colour=color, newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001082
1083 if printed_arch:
Simon Glass4653a882014-09-05 19:00:07 -06001084 Print()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001085 if show_detail:
1086 self.PrintSizeDetail(target_list, show_bloat)
1087
1088
1089 def PrintResultSummary(self, board_selected, board_dict, err_lines,
Simon Glasse30965d2014-08-28 09:43:44 -06001090 err_line_boards, warn_lines, warn_line_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001091 config, environment, show_sizes, show_detail,
1092 show_bloat, show_config, show_environment):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001093 """Compare results with the base results and display delta.
1094
1095 Only boards mentioned in board_selected will be considered. This
1096 function is intended to be called repeatedly with the results of
1097 each commit. It therefore shows a 'diff' between what it saw in
1098 the last call and what it sees now.
1099
1100 Args:
1101 board_selected: Dict containing boards to summarise, keyed by
1102 board.target
1103 board_dict: Dict containing boards for which we built this
1104 commit, keyed by board.target. The value is an Outcome object.
1105 err_lines: A list of errors for this commit, or [] if there is
1106 none, or we don't want to print errors
Simon Glassed966652014-08-28 09:43:43 -06001107 err_line_boards: Dict keyed by error line, containing a list of
1108 the Board objects with that error
Simon Glasse30965d2014-08-28 09:43:44 -06001109 warn_lines: A list of warnings for this commit, or [] if there is
1110 none, or we don't want to print errors
1111 warn_line_boards: Dict keyed by warning line, containing a list of
1112 the Board objects with that warning
Simon Glass843312d2015-02-05 22:06:15 -07001113 config: Dictionary keyed by filename - e.g. '.config'. Each
1114 value is itself a dictionary:
1115 key: config name
1116 value: config value
Alex Kiernan48ae4122018-05-31 04:48:34 +00001117 environment: Dictionary keyed by environment variable, Each
1118 value is the value of environment variable.
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001119 show_sizes: Show image size deltas
1120 show_detail: Show detail for each board
1121 show_bloat: Show detail for each function
Simon Glass843312d2015-02-05 22:06:15 -07001122 show_config: Show config changes
Alex Kiernan48ae4122018-05-31 04:48:34 +00001123 show_environment: Show environment changes
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001124 """
Simon Glasse30965d2014-08-28 09:43:44 -06001125 def _BoardList(line, line_boards):
Simon Glassed966652014-08-28 09:43:43 -06001126 """Helper function to get a line of boards containing a line
1127
1128 Args:
1129 line: Error line to search for
1130 Return:
1131 String containing a list of boards with that error line, or
1132 '' if the user has not requested such a list
1133 """
1134 if self._list_error_boards:
1135 names = []
Simon Glasse30965d2014-08-28 09:43:44 -06001136 for board in line_boards[line]:
Simon Glassf66153b2014-10-16 01:05:55 -06001137 if not board.target in names:
1138 names.append(board.target)
Simon Glassed966652014-08-28 09:43:43 -06001139 names_str = '(%s) ' % ','.join(names)
1140 else:
1141 names_str = ''
1142 return names_str
1143
Simon Glasse30965d2014-08-28 09:43:44 -06001144 def _CalcErrorDelta(base_lines, base_line_boards, lines, line_boards,
1145 char):
1146 better_lines = []
1147 worse_lines = []
1148 for line in lines:
1149 if line not in base_lines:
1150 worse_lines.append(char + '+' +
1151 _BoardList(line, line_boards) + line)
1152 for line in base_lines:
1153 if line not in lines:
1154 better_lines.append(char + '-' +
1155 _BoardList(line, base_line_boards) + line)
1156 return better_lines, worse_lines
1157
Simon Glass843312d2015-02-05 22:06:15 -07001158 def _CalcConfig(delta, name, config):
1159 """Calculate configuration changes
1160
1161 Args:
1162 delta: Type of the delta, e.g. '+'
1163 name: name of the file which changed (e.g. .config)
1164 config: configuration change dictionary
1165 key: config name
1166 value: config value
1167 Returns:
1168 String containing the configuration changes which can be
1169 printed
1170 """
1171 out = ''
1172 for key in sorted(config.keys()):
1173 out += '%s=%s ' % (key, config[key])
Simon Glass8270e3c2015-08-25 21:52:14 -06001174 return '%s %s: %s' % (delta, name, out)
Simon Glass843312d2015-02-05 22:06:15 -07001175
Simon Glass8270e3c2015-08-25 21:52:14 -06001176 def _AddConfig(lines, name, config_plus, config_minus, config_change):
1177 """Add changes in configuration to a list
Simon Glass843312d2015-02-05 22:06:15 -07001178
1179 Args:
Simon Glass8270e3c2015-08-25 21:52:14 -06001180 lines: list to add to
1181 name: config file name
Simon Glass843312d2015-02-05 22:06:15 -07001182 config_plus: configurations added, dictionary
1183 key: config name
1184 value: config value
1185 config_minus: configurations removed, dictionary
1186 key: config name
1187 value: config value
1188 config_change: configurations changed, dictionary
1189 key: config name
1190 value: config value
1191 """
1192 if config_plus:
Simon Glass8270e3c2015-08-25 21:52:14 -06001193 lines.append(_CalcConfig('+', name, config_plus))
Simon Glass843312d2015-02-05 22:06:15 -07001194 if config_minus:
Simon Glass8270e3c2015-08-25 21:52:14 -06001195 lines.append(_CalcConfig('-', name, config_minus))
Simon Glass843312d2015-02-05 22:06:15 -07001196 if config_change:
Simon Glass8270e3c2015-08-25 21:52:14 -06001197 lines.append(_CalcConfig('c', name, config_change))
1198
1199 def _OutputConfigInfo(lines):
1200 for line in lines:
1201 if not line:
1202 continue
1203 if line[0] == '+':
1204 col = self.col.GREEN
1205 elif line[0] == '-':
1206 col = self.col.RED
1207 elif line[0] == 'c':
1208 col = self.col.YELLOW
1209 Print(' ' + line, newline=True, colour=col)
1210
Simon Glass843312d2015-02-05 22:06:15 -07001211
Simon Glass4cf2b222018-11-06 16:02:12 -07001212 ok_boards = [] # List of boards fixed since last commit
Simon Glass6af71012018-11-06 16:02:13 -07001213 warn_boards = [] # List of boards with warnings since last commit
Simon Glass4cf2b222018-11-06 16:02:12 -07001214 err_boards = [] # List of new broken boards since last commit
1215 new_boards = [] # List of boards that didn't exist last time
1216 unknown_boards = [] # List of boards that were not built
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001217
1218 for target in board_dict:
1219 if target not in board_selected:
1220 continue
1221
1222 # If the board was built last time, add its outcome to a list
1223 if target in self._base_board_dict:
1224 base_outcome = self._base_board_dict[target].rc
1225 outcome = board_dict[target]
1226 if outcome.rc == OUTCOME_UNKNOWN:
Simon Glass4cf2b222018-11-06 16:02:12 -07001227 unknown_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001228 elif outcome.rc < base_outcome:
Simon Glass6af71012018-11-06 16:02:13 -07001229 if outcome.rc == OUTCOME_WARNING:
1230 warn_boards.append(target)
1231 else:
1232 ok_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001233 elif outcome.rc > base_outcome:
Simon Glass6af71012018-11-06 16:02:13 -07001234 if outcome.rc == OUTCOME_WARNING:
1235 warn_boards.append(target)
1236 else:
1237 err_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001238 else:
Simon Glass4cf2b222018-11-06 16:02:12 -07001239 new_boards.append(target)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001240
1241 # Get a list of errors that have appeared, and disappeared
Simon Glasse30965d2014-08-28 09:43:44 -06001242 better_err, worse_err = _CalcErrorDelta(self._base_err_lines,
1243 self._base_err_line_boards, err_lines, err_line_boards, '')
1244 better_warn, worse_warn = _CalcErrorDelta(self._base_warn_lines,
1245 self._base_warn_line_boards, warn_lines, warn_line_boards, 'w')
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001246
1247 # Display results by arch
Simon Glass6af71012018-11-06 16:02:13 -07001248 if any((ok_boards, warn_boards, err_boards, unknown_boards, new_boards,
1249 worse_err, better_err, worse_warn, better_warn)):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001250 arch_list = {}
Simon Glass4cf2b222018-11-06 16:02:12 -07001251 self.AddOutcome(board_selected, arch_list, ok_boards, '',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001252 self.col.GREEN)
Simon Glass6af71012018-11-06 16:02:13 -07001253 self.AddOutcome(board_selected, arch_list, warn_boards, 'w+',
1254 self.col.YELLOW)
Simon Glass4cf2b222018-11-06 16:02:12 -07001255 self.AddOutcome(board_selected, arch_list, err_boards, '+',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001256 self.col.RED)
Simon Glass4cf2b222018-11-06 16:02:12 -07001257 self.AddOutcome(board_selected, arch_list, new_boards, '*', self.col.BLUE)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001258 if self._show_unknown:
Simon Glass4cf2b222018-11-06 16:02:12 -07001259 self.AddOutcome(board_selected, arch_list, unknown_boards, '?',
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001260 self.col.MAGENTA)
Simon Glassc05aa032019-10-31 07:42:53 -06001261 for arch, target_list in arch_list.items():
Simon Glass4653a882014-09-05 19:00:07 -06001262 Print('%10s: %s' % (arch, target_list))
Simon Glass28370c12014-08-09 15:33:06 -06001263 self._error_lines += 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001264 if better_err:
Simon Glass4653a882014-09-05 19:00:07 -06001265 Print('\n'.join(better_err), colour=self.col.GREEN)
Simon Glass28370c12014-08-09 15:33:06 -06001266 self._error_lines += 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001267 if worse_err:
Simon Glass4653a882014-09-05 19:00:07 -06001268 Print('\n'.join(worse_err), colour=self.col.RED)
Simon Glass28370c12014-08-09 15:33:06 -06001269 self._error_lines += 1
Simon Glasse30965d2014-08-28 09:43:44 -06001270 if better_warn:
Simon Glass4653a882014-09-05 19:00:07 -06001271 Print('\n'.join(better_warn), colour=self.col.CYAN)
Simon Glasse30965d2014-08-28 09:43:44 -06001272 self._error_lines += 1
1273 if worse_warn:
Simon Glass4653a882014-09-05 19:00:07 -06001274 Print('\n'.join(worse_warn), colour=self.col.MAGENTA)
Simon Glasse30965d2014-08-28 09:43:44 -06001275 self._error_lines += 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001276
1277 if show_sizes:
1278 self.PrintSizeSummary(board_selected, board_dict, show_detail,
1279 show_bloat)
1280
Alex Kiernan48ae4122018-05-31 04:48:34 +00001281 if show_environment and self._base_environment:
1282 lines = []
1283
1284 for target in board_dict:
1285 if target not in board_selected:
1286 continue
1287
1288 tbase = self._base_environment[target]
1289 tenvironment = environment[target]
1290 environment_plus = {}
1291 environment_minus = {}
1292 environment_change = {}
1293 base = tbase.environment
Simon Glassc05aa032019-10-31 07:42:53 -06001294 for key, value in tenvironment.environment.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001295 if key not in base:
1296 environment_plus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001297 for key, value in base.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001298 if key not in tenvironment.environment:
1299 environment_minus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001300 for key, value in base.items():
Alex Kiernan48ae4122018-05-31 04:48:34 +00001301 new_value = tenvironment.environment.get(key)
1302 if new_value and value != new_value:
1303 desc = '%s -> %s' % (value, new_value)
1304 environment_change[key] = desc
1305
1306 _AddConfig(lines, target, environment_plus, environment_minus,
1307 environment_change)
1308
1309 _OutputConfigInfo(lines)
1310
Simon Glass8270e3c2015-08-25 21:52:14 -06001311 if show_config and self._base_config:
1312 summary = {}
1313 arch_config_plus = {}
1314 arch_config_minus = {}
1315 arch_config_change = {}
1316 arch_list = []
1317
1318 for target in board_dict:
1319 if target not in board_selected:
Simon Glass843312d2015-02-05 22:06:15 -07001320 continue
Simon Glass8270e3c2015-08-25 21:52:14 -06001321 arch = board_selected[target].arch
1322 if arch not in arch_list:
1323 arch_list.append(arch)
1324
1325 for arch in arch_list:
1326 arch_config_plus[arch] = {}
1327 arch_config_minus[arch] = {}
1328 arch_config_change[arch] = {}
Simon Glassb464f8e2016-11-13 14:25:53 -07001329 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001330 arch_config_plus[arch][name] = {}
1331 arch_config_minus[arch][name] = {}
1332 arch_config_change[arch][name] = {}
1333
1334 for target in board_dict:
1335 if target not in board_selected:
1336 continue
1337
1338 arch = board_selected[target].arch
1339
1340 all_config_plus = {}
1341 all_config_minus = {}
1342 all_config_change = {}
1343 tbase = self._base_config[target]
1344 tconfig = config[target]
1345 lines = []
Simon Glassb464f8e2016-11-13 14:25:53 -07001346 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001347 if not tconfig.config[name]:
1348 continue
1349 config_plus = {}
1350 config_minus = {}
1351 config_change = {}
1352 base = tbase.config[name]
Simon Glassc05aa032019-10-31 07:42:53 -06001353 for key, value in tconfig.config[name].items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001354 if key not in base:
1355 config_plus[key] = value
1356 all_config_plus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001357 for key, value in base.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001358 if key not in tconfig.config[name]:
1359 config_minus[key] = value
1360 all_config_minus[key] = value
Simon Glassc05aa032019-10-31 07:42:53 -06001361 for key, value in base.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001362 new_value = tconfig.config.get(key)
1363 if new_value and value != new_value:
1364 desc = '%s -> %s' % (value, new_value)
1365 config_change[key] = desc
1366 all_config_change[key] = desc
1367
1368 arch_config_plus[arch][name].update(config_plus)
1369 arch_config_minus[arch][name].update(config_minus)
1370 arch_config_change[arch][name].update(config_change)
1371
1372 _AddConfig(lines, name, config_plus, config_minus,
1373 config_change)
1374 _AddConfig(lines, 'all', all_config_plus, all_config_minus,
1375 all_config_change)
1376 summary[target] = '\n'.join(lines)
1377
1378 lines_by_target = {}
Simon Glassc05aa032019-10-31 07:42:53 -06001379 for target, lines in summary.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001380 if lines in lines_by_target:
1381 lines_by_target[lines].append(target)
1382 else:
1383 lines_by_target[lines] = [target]
1384
1385 for arch in arch_list:
1386 lines = []
1387 all_plus = {}
1388 all_minus = {}
1389 all_change = {}
Simon Glassb464f8e2016-11-13 14:25:53 -07001390 for name in self.config_filenames:
Simon Glass8270e3c2015-08-25 21:52:14 -06001391 all_plus.update(arch_config_plus[arch][name])
1392 all_minus.update(arch_config_minus[arch][name])
1393 all_change.update(arch_config_change[arch][name])
1394 _AddConfig(lines, name, arch_config_plus[arch][name],
1395 arch_config_minus[arch][name],
1396 arch_config_change[arch][name])
1397 _AddConfig(lines, 'all', all_plus, all_minus, all_change)
1398 #arch_summary[target] = '\n'.join(lines)
1399 if lines:
1400 Print('%s:' % arch)
1401 _OutputConfigInfo(lines)
1402
Simon Glassc05aa032019-10-31 07:42:53 -06001403 for lines, targets in lines_by_target.items():
Simon Glass8270e3c2015-08-25 21:52:14 -06001404 if not lines:
1405 continue
1406 Print('%s :' % ' '.join(sorted(targets)))
1407 _OutputConfigInfo(lines.split('\n'))
1408
Simon Glass843312d2015-02-05 22:06:15 -07001409
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001410 # Save our updated information for the next call to this function
1411 self._base_board_dict = board_dict
1412 self._base_err_lines = err_lines
Simon Glasse30965d2014-08-28 09:43:44 -06001413 self._base_warn_lines = warn_lines
1414 self._base_err_line_boards = err_line_boards
1415 self._base_warn_line_boards = warn_line_boards
Simon Glass843312d2015-02-05 22:06:15 -07001416 self._base_config = config
Alex Kiernan48ae4122018-05-31 04:48:34 +00001417 self._base_environment = environment
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001418
1419 # Get a list of boards that did not get built, if needed
1420 not_built = []
1421 for board in board_selected:
1422 if not board in board_dict:
1423 not_built.append(board)
1424 if not_built:
Simon Glass4653a882014-09-05 19:00:07 -06001425 Print("Boards not built (%d): %s" % (len(not_built),
1426 ', '.join(not_built)))
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001427
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001428 def ProduceResultSummary(self, commit_upto, commits, board_selected):
Simon Glasse30965d2014-08-28 09:43:44 -06001429 (board_dict, err_lines, err_line_boards, warn_lines,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001430 warn_line_boards, config, environment) = self.GetResultSummary(
Simon Glassed966652014-08-28 09:43:43 -06001431 board_selected, commit_upto,
Simon Glass843312d2015-02-05 22:06:15 -07001432 read_func_sizes=self._show_bloat,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001433 read_config=self._show_config,
1434 read_environment=self._show_environment)
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001435 if commits:
1436 msg = '%02d: %s' % (commit_upto + 1,
1437 commits[commit_upto].subject)
Simon Glass4653a882014-09-05 19:00:07 -06001438 Print(msg, colour=self.col.BLUE)
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001439 self.PrintResultSummary(board_selected, board_dict,
Simon Glassed966652014-08-28 09:43:43 -06001440 err_lines if self._show_errors else [], err_line_boards,
Simon Glasse30965d2014-08-28 09:43:44 -06001441 warn_lines if self._show_errors else [], warn_line_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +00001442 config, environment, self._show_sizes, self._show_detail,
1443 self._show_bloat, self._show_config, self._show_environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001444
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001445 def ShowSummary(self, commits, board_selected):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001446 """Show a build summary for U-Boot for a given board list.
1447
1448 Reset the result summary, then repeatedly call GetResultSummary on
1449 each commit's results, then display the differences we see.
1450
1451 Args:
1452 commit: Commit objects to summarise
1453 board_selected: Dict containing boards to summarise
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001454 """
Simon Glassfea58582014-08-09 15:32:59 -06001455 self.commit_count = len(commits) if commits else 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001456 self.commits = commits
1457 self.ResetResultSummary(board_selected)
Simon Glass28370c12014-08-09 15:33:06 -06001458 self._error_lines = 0
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001459
1460 for commit_upto in range(0, self.commit_count, self._step):
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001461 self.ProduceResultSummary(commit_upto, commits, board_selected)
Simon Glass28370c12014-08-09 15:33:06 -06001462 if not self._error_lines:
Simon Glass4653a882014-09-05 19:00:07 -06001463 Print('(no errors to report)', colour=self.col.GREEN)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001464
1465
1466 def SetupBuild(self, board_selected, commits):
1467 """Set up ready to start a build.
1468
1469 Args:
1470 board_selected: Selected boards to build
1471 commits: Selected commits to build
1472 """
1473 # First work out how many commits we will build
Simon Glassc05aa032019-10-31 07:42:53 -06001474 count = (self.commit_count + self._step - 1) // self._step
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001475 self.count = len(board_selected) * count
1476 self.upto = self.warned = self.fail = 0
1477 self._timestamps = collections.deque()
1478
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001479 def GetThreadDir(self, thread_num):
1480 """Get the directory path to the working dir for a thread.
1481
1482 Args:
1483 thread_num: Number of thread to check.
1484 """
Simon Glassd829f122020-03-18 09:42:42 -06001485 if self.work_in_output:
1486 return self._working_dir
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001487 return os.path.join(self._working_dir, '%02d' % thread_num)
1488
Simon Glassfea58582014-08-09 15:32:59 -06001489 def _PrepareThread(self, thread_num, setup_git):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001490 """Prepare the working directory for a thread.
1491
1492 This clones or fetches the repo into the thread's work directory.
1493
1494 Args:
1495 thread_num: Thread number (0, 1, ...)
Simon Glassfea58582014-08-09 15:32:59 -06001496 setup_git: True to set up a git repo clone
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001497 """
1498 thread_dir = self.GetThreadDir(thread_num)
Simon Glass190064b2014-08-09 15:33:00 -06001499 builderthread.Mkdir(thread_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001500 git_dir = os.path.join(thread_dir, '.git')
1501
1502 # Clone the repo if it doesn't already exist
1503 # TODO(sjg@chromium): Perhaps some git hackery to symlink instead, so
1504 # we have a private index but uses the origin repo's contents?
Simon Glassfea58582014-08-09 15:32:59 -06001505 if setup_git and self.git_dir:
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001506 src_dir = os.path.abspath(self.git_dir)
1507 if os.path.exists(git_dir):
1508 gitutil.Fetch(git_dir, thread_dir)
1509 else:
Simon Glass21f0eb32016-09-18 16:48:31 -06001510 Print('\rCloning repo for thread %d' % thread_num,
1511 newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001512 gitutil.Clone(src_dir, thread_dir)
Simon Glass21f0eb32016-09-18 16:48:31 -06001513 Print('\r%s\r' % (' ' * 30), newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001514
Simon Glassfea58582014-08-09 15:32:59 -06001515 def _PrepareWorkingSpace(self, max_threads, setup_git):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001516 """Prepare the working directory for use.
1517
1518 Set up the git repo for each thread.
1519
1520 Args:
1521 max_threads: Maximum number of threads we expect to need.
Simon Glassfea58582014-08-09 15:32:59 -06001522 setup_git: True to set up a git repo clone
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001523 """
Simon Glass190064b2014-08-09 15:33:00 -06001524 builderthread.Mkdir(self._working_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001525 for thread in range(max_threads):
Simon Glassfea58582014-08-09 15:32:59 -06001526 self._PrepareThread(thread, setup_git)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001527
1528 def _PrepareOutputSpace(self):
1529 """Get the output directories ready to receive files.
1530
1531 We delete any output directories which look like ones we need to
1532 create. Having left over directories is confusing when the user wants
1533 to check the output manually.
1534 """
Simon Glass1a915672014-12-01 17:33:53 -07001535 if not self.commits:
1536 return
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001537 dir_list = []
1538 for commit_upto in range(self.commit_count):
1539 dir_list.append(self._GetOutputDir(commit_upto))
1540
Simon Glassb222abe2016-09-18 16:48:32 -06001541 to_remove = []
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001542 for dirname in glob.glob(os.path.join(self.base_dir, '*')):
1543 if dirname not in dir_list:
Simon Glassb222abe2016-09-18 16:48:32 -06001544 to_remove.append(dirname)
1545 if to_remove:
1546 Print('Removing %d old build directories' % len(to_remove),
1547 newline=False)
1548 for dirname in to_remove:
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001549 shutil.rmtree(dirname)
1550
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001551 def BuildBoards(self, commits, board_selected, keep_outputs, verbose):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001552 """Build all commits for a list of boards
1553
1554 Args:
1555 commits: List of commits to be build, each a Commit object
1556 boards_selected: Dict of selected boards, key is target name,
1557 value is Board object
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001558 keep_outputs: True to save build output files
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001559 verbose: Display build results as they are completed
Simon Glass2c3deb92014-08-28 09:43:39 -06001560 Returns:
1561 Tuple containing:
1562 - number of boards that failed to build
1563 - number of boards that issued warnings
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001564 """
Simon Glassfea58582014-08-09 15:32:59 -06001565 self.commit_count = len(commits) if commits else 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001566 self.commits = commits
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001567 self._verbose = verbose
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001568
1569 self.ResetResultSummary(board_selected)
Thierry Redingf3d015c2014-08-19 10:22:39 +02001570 builderthread.Mkdir(self.base_dir, parents = True)
Simon Glassfea58582014-08-09 15:32:59 -06001571 self._PrepareWorkingSpace(min(self.num_threads, len(board_selected)),
1572 commits is not None)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001573 self._PrepareOutputSpace()
Simon Glass745b3952016-09-18 16:48:33 -06001574 Print('\rStarting build...', newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001575 self.SetupBuild(board_selected, commits)
1576 self.ProcessResult(None)
1577
1578 # Create jobs to build all commits for each board
Simon Glassc05aa032019-10-31 07:42:53 -06001579 for brd in board_selected.values():
Simon Glass190064b2014-08-09 15:33:00 -06001580 job = builderthread.BuilderJob()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001581 job.board = brd
1582 job.commits = commits
1583 job.keep_outputs = keep_outputs
Simon Glassd829f122020-03-18 09:42:42 -06001584 job.work_in_output = self.work_in_output
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001585 job.step = self._step
1586 self.queue.put(job)
1587
Simon Glassd436e382016-09-18 16:48:35 -06001588 term = threading.Thread(target=self.queue.join)
1589 term.setDaemon(True)
1590 term.start()
1591 while term.isAlive():
1592 term.join(100)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001593
1594 # Wait until we have processed all output
1595 self.out_queue.join()
Simon Glass4653a882014-09-05 19:00:07 -06001596 Print()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001597 self.ClearLine(0)
Simon Glass2c3deb92014-08-28 09:43:39 -06001598 return (self.fail, self.warned)