blob: cfbe4c26b1a99b4ebce4ded5c8fab2df00921854 [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glassc05694f2013-04-03 11:07:16 +00002# Copyright (c) 2013 The Chromium OS Authors.
3#
4# Bloat-o-meter code used here Copyright 2004 Matt Mackall <mpm@selenic.com>
5#
Simon Glassc05694f2013-04-03 11:07:16 +00006
7import collections
Simon Glassc05694f2013-04-03 11:07:16 +00008from datetime import datetime, timedelta
9import glob
10import os
11import re
Simon Glassc78ed662019-10-31 07:42:53 -060012import queue
Simon Glassc05694f2013-04-03 11:07:16 +000013import shutil
Simon Glass205ac042016-09-18 16:48:37 -060014import signal
Simon Glassc05694f2013-04-03 11:07:16 +000015import string
16import sys
Simon Glassd26e1442016-09-18 16:48:35 -060017import threading
Simon Glassc05694f2013-04-03 11:07:16 +000018import time
19
Simon Glass4a1e88b2014-08-09 15:33:00 -060020import builderthread
Simon Glassc05694f2013-04-03 11:07:16 +000021import command
22import gitutil
23import terminal
Simon Glass4433aa92014-09-05 19:00:07 -060024from terminal import Print
Simon Glassc05694f2013-04-03 11:07:16 +000025import toolchain
26
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 Glassc78ed662019-10-31 07:42:53 -060095OUTCOME_OK, OUTCOME_WARNING, OUTCOME_ERROR, OUTCOME_UNKNOWN = list(range(4))
Simon Glassc05694f2013-04-03 11:07:16 +000096
Simon Glassd214bef2017-04-12 18:23:26 -060097# Translate a commit subject into a valid filename (and handle unicode)
Simon Glassc78ed662019-10-31 07:42:53 -060098trans_valid_chars = str.maketrans('/: ', '---')
Simon Glassc05694f2013-04-03 11:07:16 +000099
Simon Glasscde5c302016-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 Glassdb17fb82015-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 Glassdb17fb82015-02-05 22:06:15 -0700108]
109
Simon Glasscad8abf2015-08-25 21:52:14 -0600110class Config:
111 """Holds information about configuration settings for a board."""
Simon Glasscde5c302016-11-13 14:25:53 -0700112 def __init__(self, config_filename, target):
Simon Glasscad8abf2015-08-25 21:52:14 -0600113 self.target = target
114 self.config = {}
Simon Glasscde5c302016-11-13 14:25:53 -0700115 for fname in config_filename:
Simon Glasscad8abf2015-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 Glassc78ed662019-10-31 07:42:53 -0600124 for key, value in self.config[fname].items():
125 print(key, value)
Simon Glasscad8abf2015-08-25 21:52:14 -0600126 val = val ^ hash(key) & hash(value)
127 return val
Simon Glassc05694f2013-04-03 11:07:16 +0000128
Alex Kiernan4059e302018-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 Glassc05694f2013-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 Glassc05694f2013-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 Glass7041c392014-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 Glassc05694f2013-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 Glassf3018b7a2014-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 Glass38df2e22014-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 Glassc05694f2013-04-03 11:07:16 +0000177
178 Private members:
179 _base_board_dict: Last-summarised Dict of boards
180 _base_err_lines: Last-summarised list of errors
Simon Glass03749d42014-08-28 09:43:44 -0600181 _base_warn_lines: Last-summarised list of warnings
Simon Glassc05694f2013-04-03 11:07:16 +0000182 _build_period_us: Time taken for a single build (float object).
183 _complete_delay: Expected delay until completion (timedelta)
184 _next_delay_update: Next time we plan to display a progress update
185 (datatime)
186 _show_unknown: Show unknown boards (those not built) in summary
187 _timestamps: List of timestamps for the completion of the last
188 last _timestamp_count builds. Each is a datetime object.
189 _timestamp_count: Number of timestamps to keep in our list.
190 _working_dir: Base working directory containing all threads
191 """
192 class Outcome:
193 """Records a build outcome for a single make invocation
194
195 Public Members:
196 rc: Outcome value (OUTCOME_...)
197 err_lines: List of error lines or [] if none
198 sizes: Dictionary of image size information, keyed by filename
199 - Each value is itself a dictionary containing
200 values for 'text', 'data' and 'bss', being the integer
201 size in bytes of each section.
202 func_sizes: Dictionary keyed by filename - e.g. 'u-boot'. Each
203 value is itself a dictionary:
204 key: function name
205 value: Size of function in bytes
Simon Glassdb17fb82015-02-05 22:06:15 -0700206 config: Dictionary keyed by filename - e.g. '.config'. Each
207 value is itself a dictionary:
208 key: config name
209 value: config value
Alex Kiernan4059e302018-05-31 04:48:34 +0000210 environment: Dictionary keyed by environment variable, Each
211 value is the value of environment variable.
Simon Glassc05694f2013-04-03 11:07:16 +0000212 """
Alex Kiernan4059e302018-05-31 04:48:34 +0000213 def __init__(self, rc, err_lines, sizes, func_sizes, config,
214 environment):
Simon Glassc05694f2013-04-03 11:07:16 +0000215 self.rc = rc
216 self.err_lines = err_lines
217 self.sizes = sizes
218 self.func_sizes = func_sizes
Simon Glassdb17fb82015-02-05 22:06:15 -0700219 self.config = config
Alex Kiernan4059e302018-05-31 04:48:34 +0000220 self.environment = environment
Simon Glassc05694f2013-04-03 11:07:16 +0000221
222 def __init__(self, toolchains, base_dir, git_dir, num_threads, num_jobs,
Simon Glasse87bde12014-12-01 17:33:55 -0700223 gnu_make='make', checkout=True, show_unknown=True, step=1,
Stephen Warren97c96902016-04-11 10:48:44 -0600224 no_subdirs=False, full_path=False, verbose_build=False,
Simon Glass739e8512016-11-13 14:25:51 -0700225 incremental=False, per_board_out_dir=False,
Daniel Schwierzeck20e2ea92018-01-26 16:31:05 +0100226 config_only=False, squash_config_y=False,
227 warnings_as_errors=False):
Simon Glassc05694f2013-04-03 11:07:16 +0000228 """Create a new Builder object
229
230 Args:
231 toolchains: Toolchains object to use for building
232 base_dir: Base directory to use for builder
233 git_dir: Git directory containing source repository
234 num_threads: Number of builder threads to run
235 num_jobs: Number of jobs to run at once (passed to make as -j)
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900236 gnu_make: the command name of GNU Make.
Simon Glassc05694f2013-04-03 11:07:16 +0000237 checkout: True to check out source, False to skip that step.
238 This is used for testing.
239 show_unknown: Show unknown boards (those not built) in summary
240 step: 1 to process every commit, n to process every nth commit
Simon Glassd48a46c2014-12-01 17:34:00 -0700241 no_subdirs: Don't create subdirectories when building current
242 source for a single board
243 full_path: Return the full path in CROSS_COMPILE and don't set
244 PATH
Simon Glass655b6102014-12-01 17:34:07 -0700245 verbose_build: Run build with V=1 and don't use 'make -s'
Stephen Warren97c96902016-04-11 10:48:44 -0600246 incremental: Always perform incremental builds; don't run make
247 mrproper when configuring
248 per_board_out_dir: Build in a separate persistent directory per
249 board rather than a thread-specific directory
Simon Glass739e8512016-11-13 14:25:51 -0700250 config_only: Only configure each build, don't build it
Simon Glasscde5c302016-11-13 14:25:53 -0700251 squash_config_y: Convert CONFIG options with the value 'y' to '1'
Daniel Schwierzeck20e2ea92018-01-26 16:31:05 +0100252 warnings_as_errors: Treat all compiler warnings as errors
Simon Glassc05694f2013-04-03 11:07:16 +0000253 """
254 self.toolchains = toolchains
255 self.base_dir = base_dir
256 self._working_dir = os.path.join(base_dir, '.bm-work')
257 self.threads = []
Simon Glassc05694f2013-04-03 11:07:16 +0000258 self.do_make = self.Make
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900259 self.gnu_make = gnu_make
Simon Glassc05694f2013-04-03 11:07:16 +0000260 self.checkout = checkout
261 self.num_threads = num_threads
262 self.num_jobs = num_jobs
263 self.already_done = 0
264 self.force_build = False
265 self.git_dir = git_dir
266 self._show_unknown = show_unknown
267 self._timestamp_count = 10
268 self._build_period_us = None
269 self._complete_delay = None
270 self._next_delay_update = datetime.now()
271 self.force_config_on_failure = True
Simon Glass7041c392014-07-13 12:22:31 -0600272 self.force_build_failures = False
Simon Glassf3018b7a2014-07-14 17:51:02 -0600273 self.force_reconfig = False
Simon Glassc05694f2013-04-03 11:07:16 +0000274 self._step = step
Simon Glass38df2e22014-07-14 17:51:03 -0600275 self.in_tree = False
Simon Glassbb4dffb2014-08-09 15:33:06 -0600276 self._error_lines = 0
Simon Glasse87bde12014-12-01 17:33:55 -0700277 self.no_subdirs = no_subdirs
Simon Glassd48a46c2014-12-01 17:34:00 -0700278 self.full_path = full_path
Simon Glass655b6102014-12-01 17:34:07 -0700279 self.verbose_build = verbose_build
Simon Glass739e8512016-11-13 14:25:51 -0700280 self.config_only = config_only
Simon Glasscde5c302016-11-13 14:25:53 -0700281 self.squash_config_y = squash_config_y
282 self.config_filenames = BASE_CONFIG_FILENAMES
283 if not self.squash_config_y:
284 self.config_filenames += EXTRA_CONFIG_FILENAMES
Simon Glassc05694f2013-04-03 11:07:16 +0000285
Daniel Schwierzeck20e2ea92018-01-26 16:31:05 +0100286 self.warnings_as_errors = warnings_as_errors
Simon Glassc05694f2013-04-03 11:07:16 +0000287 self.col = terminal.Color()
288
Simon Glass03749d42014-08-28 09:43:44 -0600289 self._re_function = re.compile('(.*): In function.*')
290 self._re_files = re.compile('In file included from.*')
291 self._re_warning = re.compile('(.*):(\d*):(\d*): warning: .*')
Simon Glass0db94432018-11-06 16:02:11 -0700292 self._re_dtb_warning = re.compile('(.*): Warning .*')
Simon Glass03749d42014-08-28 09:43:44 -0600293 self._re_note = re.compile('(.*):(\d*):(\d*): note: this is the location of the previous.*')
294
Simon Glassc78ed662019-10-31 07:42:53 -0600295 self.queue = queue.Queue()
296 self.out_queue = queue.Queue()
Simon Glassc05694f2013-04-03 11:07:16 +0000297 for i in range(self.num_threads):
Stephen Warren97c96902016-04-11 10:48:44 -0600298 t = builderthread.BuilderThread(self, i, incremental,
299 per_board_out_dir)
Simon Glassc05694f2013-04-03 11:07:16 +0000300 t.setDaemon(True)
301 t.start()
302 self.threads.append(t)
303
304 self.last_line_len = 0
Simon Glass4a1e88b2014-08-09 15:33:00 -0600305 t = builderthread.ResultThread(self)
Simon Glassc05694f2013-04-03 11:07:16 +0000306 t.setDaemon(True)
307 t.start()
308 self.threads.append(t)
309
310 ignore_lines = ['(make.*Waiting for unfinished)', '(Segmentation fault)']
311 self.re_make_err = re.compile('|'.join(ignore_lines))
312
Simon Glass205ac042016-09-18 16:48:37 -0600313 # Handle existing graceful with SIGINT / Ctrl-C
314 signal.signal(signal.SIGINT, self.signal_handler)
315
Simon Glassc05694f2013-04-03 11:07:16 +0000316 def __del__(self):
317 """Get rid of all threads created by the builder"""
318 for t in self.threads:
319 del t
320
Simon Glass205ac042016-09-18 16:48:37 -0600321 def signal_handler(self, signal, frame):
322 sys.exit(1)
323
Simon Glasseb48bbc2014-08-09 15:33:02 -0600324 def SetDisplayOptions(self, show_errors=False, show_sizes=False,
Simon Glass3394c9f2014-08-28 09:43:43 -0600325 show_detail=False, show_bloat=False,
Alex Kiernan4059e302018-05-31 04:48:34 +0000326 list_error_boards=False, show_config=False,
327 show_environment=False):
Simon Glasseb48bbc2014-08-09 15:33:02 -0600328 """Setup display options for the builder.
329
330 show_errors: True to show summarised error/warning info
331 show_sizes: Show size deltas
332 show_detail: Show detail for each board
333 show_bloat: Show detail for each function
Simon Glass3394c9f2014-08-28 09:43:43 -0600334 list_error_boards: Show the boards which caused each error/warning
Simon Glassdb17fb82015-02-05 22:06:15 -0700335 show_config: Show config deltas
Alex Kiernan4059e302018-05-31 04:48:34 +0000336 show_environment: Show environment deltas
Simon Glasseb48bbc2014-08-09 15:33:02 -0600337 """
338 self._show_errors = show_errors
339 self._show_sizes = show_sizes
340 self._show_detail = show_detail
341 self._show_bloat = show_bloat
Simon Glass3394c9f2014-08-28 09:43:43 -0600342 self._list_error_boards = list_error_boards
Simon Glassdb17fb82015-02-05 22:06:15 -0700343 self._show_config = show_config
Alex Kiernan4059e302018-05-31 04:48:34 +0000344 self._show_environment = show_environment
Simon Glasseb48bbc2014-08-09 15:33:02 -0600345
Simon Glassc05694f2013-04-03 11:07:16 +0000346 def _AddTimestamp(self):
347 """Add a new timestamp to the list and record the build period.
348
349 The build period is the length of time taken to perform a single
350 build (one board, one commit).
351 """
352 now = datetime.now()
353 self._timestamps.append(now)
354 count = len(self._timestamps)
355 delta = self._timestamps[-1] - self._timestamps[0]
356 seconds = delta.total_seconds()
357
358 # If we have enough data, estimate build period (time taken for a
359 # single build) and therefore completion time.
360 if count > 1 and self._next_delay_update < now:
361 self._next_delay_update = now + timedelta(seconds=2)
362 if seconds > 0:
363 self._build_period = float(seconds) / count
364 todo = self.count - self.upto
365 self._complete_delay = timedelta(microseconds=
366 self._build_period * todo * 1000000)
367 # Round it
368 self._complete_delay -= timedelta(
369 microseconds=self._complete_delay.microseconds)
370
371 if seconds > 60:
372 self._timestamps.popleft()
373 count -= 1
374
375 def ClearLine(self, length):
376 """Clear any characters on the current line
377
378 Make way for a new line of length 'length', by outputting enough
379 spaces to clear out the old line. Then remember the new length for
380 next time.
381
382 Args:
383 length: Length of new line, in characters
384 """
385 if length < self.last_line_len:
Simon Glass4433aa92014-09-05 19:00:07 -0600386 Print(' ' * (self.last_line_len - length), newline=False)
387 Print('\r', newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +0000388 self.last_line_len = length
389 sys.stdout.flush()
390
391 def SelectCommit(self, commit, checkout=True):
392 """Checkout the selected commit for this build
393 """
394 self.commit = commit
395 if checkout and self.checkout:
396 gitutil.Checkout(commit.hash)
397
398 def Make(self, commit, brd, stage, cwd, *args, **kwargs):
399 """Run make
400
401 Args:
402 commit: Commit object that is being built
403 brd: Board object that is being built
Roger Meiere0a0e552014-08-20 22:10:29 +0200404 stage: Stage that we are at (mrproper, config, build)
Simon Glassc05694f2013-04-03 11:07:16 +0000405 cwd: Directory where make should be run
406 args: Arguments to pass to make
407 kwargs: Arguments to pass to command.RunPipe()
408 """
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900409 cmd = [self.gnu_make] + list(args)
Simon Glassc05694f2013-04-03 11:07:16 +0000410 result = command.RunPipe([cmd], capture=True, capture_stderr=True,
Simon Glassaa09b362018-09-17 23:55:42 -0600411 cwd=cwd, raise_on_error=False, infile='/dev/null', **kwargs)
Simon Glass413f91a2015-02-05 22:06:12 -0700412 if self.verbose_build:
413 result.stdout = '%s\n' % (' '.join(cmd)) + result.stdout
414 result.combined = '%s\n' % (' '.join(cmd)) + result.combined
Simon Glassc05694f2013-04-03 11:07:16 +0000415 return result
416
417 def ProcessResult(self, result):
418 """Process the result of a build, showing progress information
419
420 Args:
Simon Glass78e418e2014-08-09 15:33:03 -0600421 result: A CommandResult object, which indicates the result for
422 a single build
Simon Glassc05694f2013-04-03 11:07:16 +0000423 """
424 col = terminal.Color()
425 if result:
426 target = result.brd.target
427
Simon Glassc05694f2013-04-03 11:07:16 +0000428 self.upto += 1
429 if result.return_code != 0:
430 self.fail += 1
431 elif result.stderr:
432 self.warned += 1
433 if result.already_done:
434 self.already_done += 1
Simon Glass78e418e2014-08-09 15:33:03 -0600435 if self._verbose:
Simon Glass4433aa92014-09-05 19:00:07 -0600436 Print('\r', newline=False)
Simon Glass78e418e2014-08-09 15:33:03 -0600437 self.ClearLine(0)
438 boards_selected = {target : result.brd}
439 self.ResetResultSummary(boards_selected)
440 self.ProduceResultSummary(result.commit_upto, self.commits,
441 boards_selected)
Simon Glassc05694f2013-04-03 11:07:16 +0000442 else:
443 target = '(starting)'
444
445 # Display separate counts for ok, warned and fail
446 ok = self.upto - self.warned - self.fail
447 line = '\r' + self.col.Color(self.col.GREEN, '%5d' % ok)
448 line += self.col.Color(self.col.YELLOW, '%5d' % self.warned)
449 line += self.col.Color(self.col.RED, '%5d' % self.fail)
450
451 name = ' /%-5d ' % self.count
452
453 # Add our current completion time estimate
454 self._AddTimestamp()
455 if self._complete_delay:
456 name += '%s : ' % self._complete_delay
457 # When building all boards for a commit, we can print a commit
458 # progress message.
459 if result and result.commit_upto is None:
460 name += 'commit %2d/%-3d' % (self.commit_upto + 1,
461 self.commit_count)
462
463 name += target
Simon Glass4433aa92014-09-05 19:00:07 -0600464 Print(line + name, newline=False)
Simon Glassb45bd6b2016-11-15 15:32:59 -0700465 length = 16 + len(name)
Simon Glassc05694f2013-04-03 11:07:16 +0000466 self.ClearLine(length)
467
468 def _GetOutputDir(self, commit_upto):
469 """Get the name of the output directory for a commit number
470
471 The output directory is typically .../<branch>/<commit>.
472
473 Args:
474 commit_upto: Commit number to use (0..self.count-1)
475 """
Simon Glasse87bde12014-12-01 17:33:55 -0700476 commit_dir = None
Simon Glassd326ad72014-08-09 15:32:59 -0600477 if self.commits:
478 commit = self.commits[commit_upto]
479 subject = commit.subject.translate(trans_valid_chars)
480 commit_dir = ('%02d_of_%02d_g%s_%s' % (commit_upto + 1,
481 self.commit_count, commit.hash, subject[:20]))
Simon Glasse87bde12014-12-01 17:33:55 -0700482 elif not self.no_subdirs:
Simon Glassd326ad72014-08-09 15:32:59 -0600483 commit_dir = 'current'
Simon Glasse87bde12014-12-01 17:33:55 -0700484 if not commit_dir:
485 return self.base_dir
486 return os.path.join(self.base_dir, commit_dir)
Simon Glassc05694f2013-04-03 11:07:16 +0000487
488 def GetBuildDir(self, commit_upto, target):
489 """Get the name of the build directory for a commit number
490
491 The build directory is typically .../<branch>/<commit>/<target>.
492
493 Args:
494 commit_upto: Commit number to use (0..self.count-1)
495 target: Target name
496 """
497 output_dir = self._GetOutputDir(commit_upto)
498 return os.path.join(output_dir, target)
499
500 def GetDoneFile(self, commit_upto, target):
501 """Get the name of the done file for a commit number
502
503 Args:
504 commit_upto: Commit number to use (0..self.count-1)
505 target: Target name
506 """
507 return os.path.join(self.GetBuildDir(commit_upto, target), 'done')
508
509 def GetSizesFile(self, commit_upto, target):
510 """Get the name of the sizes 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), 'sizes')
517
518 def GetFuncSizesFile(self, commit_upto, target, elf_fname):
519 """Get the name of the funcsizes file for a commit number and ELF file
520
521 Args:
522 commit_upto: Commit number to use (0..self.count-1)
523 target: Target name
524 elf_fname: Filename of elf image
525 """
526 return os.path.join(self.GetBuildDir(commit_upto, target),
527 '%s.sizes' % elf_fname.replace('/', '-'))
528
529 def GetObjdumpFile(self, commit_upto, target, elf_fname):
530 """Get the name of the objdump file for a commit number and ELF file
531
532 Args:
533 commit_upto: Commit number to use (0..self.count-1)
534 target: Target name
535 elf_fname: Filename of elf image
536 """
537 return os.path.join(self.GetBuildDir(commit_upto, target),
538 '%s.objdump' % elf_fname.replace('/', '-'))
539
540 def GetErrFile(self, commit_upto, target):
541 """Get the name of the err file for a commit number
542
543 Args:
544 commit_upto: Commit number to use (0..self.count-1)
545 target: Target name
546 """
547 output_dir = self.GetBuildDir(commit_upto, target)
548 return os.path.join(output_dir, 'err')
549
550 def FilterErrors(self, lines):
551 """Filter out errors in which we have no interest
552
553 We should probably use map().
554
555 Args:
556 lines: List of error lines, each a string
557 Returns:
558 New list with only interesting lines included
559 """
560 out_lines = []
561 for line in lines:
562 if not self.re_make_err.search(line):
563 out_lines.append(line)
564 return out_lines
565
566 def ReadFuncSizes(self, fname, fd):
567 """Read function sizes from the output of 'nm'
568
569 Args:
570 fd: File containing data to read
571 fname: Filename we are reading from (just for errors)
572
573 Returns:
574 Dictionary containing size of each function in bytes, indexed by
575 function name.
576 """
577 sym = {}
578 for line in fd.readlines():
579 try:
580 size, type, name = line[:-1].split()
581 except:
Simon Glass4433aa92014-09-05 19:00:07 -0600582 Print("Invalid line in file '%s': '%s'" % (fname, line[:-1]))
Simon Glassc05694f2013-04-03 11:07:16 +0000583 continue
584 if type in 'tTdDbB':
585 # function names begin with '.' on 64-bit powerpc
586 if '.' in name[1:]:
587 name = 'static.' + name.split('.')[0]
588 sym[name] = sym.get(name, 0) + int(size, 16)
589 return sym
590
Simon Glassdb17fb82015-02-05 22:06:15 -0700591 def _ProcessConfig(self, fname):
592 """Read in a .config, autoconf.mk or autoconf.h file
593
594 This function handles all config file types. It ignores comments and
595 any #defines which don't start with CONFIG_.
596
597 Args:
598 fname: Filename to read
599
600 Returns:
601 Dictionary:
602 key: Config name (e.g. CONFIG_DM)
603 value: Config value (e.g. 1)
604 """
605 config = {}
606 if os.path.exists(fname):
607 with open(fname) as fd:
608 for line in fd:
609 line = line.strip()
610 if line.startswith('#define'):
611 values = line[8:].split(' ', 1)
612 if len(values) > 1:
613 key, value = values
614 else:
615 key = values[0]
Simon Glasscde5c302016-11-13 14:25:53 -0700616 value = '1' if self.squash_config_y else ''
Simon Glassdb17fb82015-02-05 22:06:15 -0700617 if not key.startswith('CONFIG_'):
618 continue
619 elif not line or line[0] in ['#', '*', '/']:
620 continue
621 else:
622 key, value = line.split('=', 1)
Simon Glasscde5c302016-11-13 14:25:53 -0700623 if self.squash_config_y and value == 'y':
624 value = '1'
Simon Glassdb17fb82015-02-05 22:06:15 -0700625 config[key] = value
626 return config
627
Alex Kiernan4059e302018-05-31 04:48:34 +0000628 def _ProcessEnvironment(self, fname):
629 """Read in a uboot.env file
630
631 This function reads in environment variables from a file.
632
633 Args:
634 fname: Filename to read
635
636 Returns:
637 Dictionary:
638 key: environment variable (e.g. bootlimit)
639 value: value of environment variable (e.g. 1)
640 """
641 environment = {}
642 if os.path.exists(fname):
643 with open(fname) as fd:
644 for line in fd.read().split('\0'):
645 try:
646 key, value = line.split('=', 1)
647 environment[key] = value
648 except ValueError:
649 # ignore lines we can't parse
650 pass
651 return environment
652
Simon Glassdb17fb82015-02-05 22:06:15 -0700653 def GetBuildOutcome(self, commit_upto, target, read_func_sizes,
Alex Kiernan4059e302018-05-31 04:48:34 +0000654 read_config, read_environment):
Simon Glassc05694f2013-04-03 11:07:16 +0000655 """Work out the outcome of a build.
656
657 Args:
658 commit_upto: Commit number to check (0..n-1)
659 target: Target board to check
660 read_func_sizes: True to read function size information
Simon Glassdb17fb82015-02-05 22:06:15 -0700661 read_config: True to read .config and autoconf.h files
Alex Kiernan4059e302018-05-31 04:48:34 +0000662 read_environment: True to read uboot.env files
Simon Glassc05694f2013-04-03 11:07:16 +0000663
664 Returns:
665 Outcome object
666 """
667 done_file = self.GetDoneFile(commit_upto, target)
668 sizes_file = self.GetSizesFile(commit_upto, target)
669 sizes = {}
670 func_sizes = {}
Simon Glassdb17fb82015-02-05 22:06:15 -0700671 config = {}
Alex Kiernan4059e302018-05-31 04:48:34 +0000672 environment = {}
Simon Glassc05694f2013-04-03 11:07:16 +0000673 if os.path.exists(done_file):
674 with open(done_file, 'r') as fd:
Simon Glass91c54a42019-04-26 19:02:23 -0600675 try:
676 return_code = int(fd.readline())
677 except ValueError:
678 # The file may be empty due to running out of disk space.
679 # Try a rebuild
680 return_code = 1
Simon Glassc05694f2013-04-03 11:07:16 +0000681 err_lines = []
682 err_file = self.GetErrFile(commit_upto, target)
683 if os.path.exists(err_file):
684 with open(err_file, 'r') as fd:
685 err_lines = self.FilterErrors(fd.readlines())
686
687 # Decide whether the build was ok, failed or created warnings
688 if return_code:
689 rc = OUTCOME_ERROR
690 elif len(err_lines):
691 rc = OUTCOME_WARNING
692 else:
693 rc = OUTCOME_OK
694
695 # Convert size information to our simple format
696 if os.path.exists(sizes_file):
697 with open(sizes_file, 'r') as fd:
698 for line in fd.readlines():
699 values = line.split()
700 rodata = 0
701 if len(values) > 6:
702 rodata = int(values[6], 16)
703 size_dict = {
704 'all' : int(values[0]) + int(values[1]) +
705 int(values[2]),
706 'text' : int(values[0]) - rodata,
707 'data' : int(values[1]),
708 'bss' : int(values[2]),
709 'rodata' : rodata,
710 }
711 sizes[values[5]] = size_dict
712
713 if read_func_sizes:
714 pattern = self.GetFuncSizesFile(commit_upto, target, '*')
715 for fname in glob.glob(pattern):
716 with open(fname, 'r') as fd:
717 dict_name = os.path.basename(fname).replace('.sizes',
718 '')
719 func_sizes[dict_name] = self.ReadFuncSizes(fname, fd)
720
Simon Glassdb17fb82015-02-05 22:06:15 -0700721 if read_config:
722 output_dir = self.GetBuildDir(commit_upto, target)
Simon Glasscde5c302016-11-13 14:25:53 -0700723 for name in self.config_filenames:
Simon Glassdb17fb82015-02-05 22:06:15 -0700724 fname = os.path.join(output_dir, name)
725 config[name] = self._ProcessConfig(fname)
726
Alex Kiernan4059e302018-05-31 04:48:34 +0000727 if read_environment:
728 output_dir = self.GetBuildDir(commit_upto, target)
729 fname = os.path.join(output_dir, 'uboot.env')
730 environment = self._ProcessEnvironment(fname)
731
732 return Builder.Outcome(rc, err_lines, sizes, func_sizes, config,
733 environment)
Simon Glassc05694f2013-04-03 11:07:16 +0000734
Alex Kiernan4059e302018-05-31 04:48:34 +0000735 return Builder.Outcome(OUTCOME_UNKNOWN, [], {}, {}, {}, {})
Simon Glassc05694f2013-04-03 11:07:16 +0000736
Simon Glassdb17fb82015-02-05 22:06:15 -0700737 def GetResultSummary(self, boards_selected, commit_upto, read_func_sizes,
Alex Kiernan4059e302018-05-31 04:48:34 +0000738 read_config, read_environment):
Simon Glassc05694f2013-04-03 11:07:16 +0000739 """Calculate a summary of the results of building a commit.
740
741 Args:
742 board_selected: Dict containing boards to summarise
743 commit_upto: Commit number to summarize (0..self.count-1)
744 read_func_sizes: True to read function size information
Simon Glassdb17fb82015-02-05 22:06:15 -0700745 read_config: True to read .config and autoconf.h files
Alex Kiernan4059e302018-05-31 04:48:34 +0000746 read_environment: True to read uboot.env files
Simon Glassc05694f2013-04-03 11:07:16 +0000747
748 Returns:
749 Tuple:
750 Dict containing boards which passed building this commit.
751 keyed by board.target
Simon Glass03749d42014-08-28 09:43:44 -0600752 List containing a summary of error lines
Simon Glass3394c9f2014-08-28 09:43:43 -0600753 Dict keyed by error line, containing a list of the Board
754 objects with that error
Simon Glass03749d42014-08-28 09:43:44 -0600755 List containing a summary of warning lines
756 Dict keyed by error line, containing a list of the Board
757 objects with that warning
Simon Glasscad8abf2015-08-25 21:52:14 -0600758 Dictionary keyed by board.target. Each value is a dictionary:
759 key: filename - e.g. '.config'
Simon Glassdb17fb82015-02-05 22:06:15 -0700760 value is itself a dictionary:
761 key: config name
762 value: config value
Alex Kiernan4059e302018-05-31 04:48:34 +0000763 Dictionary keyed by board.target. Each value is a dictionary:
764 key: environment variable
765 value: value of environment variable
Simon Glassc05694f2013-04-03 11:07:16 +0000766 """
Simon Glass03749d42014-08-28 09:43:44 -0600767 def AddLine(lines_summary, lines_boards, line, board):
768 line = line.rstrip()
769 if line in lines_boards:
770 lines_boards[line].append(board)
771 else:
772 lines_boards[line] = [board]
773 lines_summary.append(line)
774
Simon Glassc05694f2013-04-03 11:07:16 +0000775 board_dict = {}
776 err_lines_summary = []
Simon Glass3394c9f2014-08-28 09:43:43 -0600777 err_lines_boards = {}
Simon Glass03749d42014-08-28 09:43:44 -0600778 warn_lines_summary = []
779 warn_lines_boards = {}
Simon Glassdb17fb82015-02-05 22:06:15 -0700780 config = {}
Alex Kiernan4059e302018-05-31 04:48:34 +0000781 environment = {}
Simon Glassc05694f2013-04-03 11:07:16 +0000782
Simon Glassc78ed662019-10-31 07:42:53 -0600783 for board in boards_selected.values():
Simon Glassc05694f2013-04-03 11:07:16 +0000784 outcome = self.GetBuildOutcome(commit_upto, board.target,
Alex Kiernan4059e302018-05-31 04:48:34 +0000785 read_func_sizes, read_config,
786 read_environment)
Simon Glassc05694f2013-04-03 11:07:16 +0000787 board_dict[board.target] = outcome
Simon Glass03749d42014-08-28 09:43:44 -0600788 last_func = None
789 last_was_warning = False
790 for line in outcome.err_lines:
791 if line:
792 if (self._re_function.match(line) or
793 self._re_files.match(line)):
794 last_func = line
Simon Glass3394c9f2014-08-28 09:43:43 -0600795 else:
Simon Glass0db94432018-11-06 16:02:11 -0700796 is_warning = (self._re_warning.match(line) or
797 self._re_dtb_warning.match(line))
Simon Glass03749d42014-08-28 09:43:44 -0600798 is_note = self._re_note.match(line)
799 if is_warning or (last_was_warning and is_note):
800 if last_func:
801 AddLine(warn_lines_summary, warn_lines_boards,
802 last_func, board)
803 AddLine(warn_lines_summary, warn_lines_boards,
804 line, board)
805 else:
806 if last_func:
807 AddLine(err_lines_summary, err_lines_boards,
808 last_func, board)
809 AddLine(err_lines_summary, err_lines_boards,
810 line, board)
811 last_was_warning = is_warning
812 last_func = None
Simon Glasscde5c302016-11-13 14:25:53 -0700813 tconfig = Config(self.config_filenames, board.target)
814 for fname in self.config_filenames:
Simon Glassdb17fb82015-02-05 22:06:15 -0700815 if outcome.config:
Simon Glassc78ed662019-10-31 07:42:53 -0600816 for key, value in outcome.config[fname].items():
Simon Glasscad8abf2015-08-25 21:52:14 -0600817 tconfig.Add(fname, key, value)
818 config[board.target] = tconfig
Simon Glassdb17fb82015-02-05 22:06:15 -0700819
Alex Kiernan4059e302018-05-31 04:48:34 +0000820 tenvironment = Environment(board.target)
821 if outcome.environment:
Simon Glassc78ed662019-10-31 07:42:53 -0600822 for key, value in outcome.environment.items():
Alex Kiernan4059e302018-05-31 04:48:34 +0000823 tenvironment.Add(key, value)
824 environment[board.target] = tenvironment
825
Simon Glass03749d42014-08-28 09:43:44 -0600826 return (board_dict, err_lines_summary, err_lines_boards,
Alex Kiernan4059e302018-05-31 04:48:34 +0000827 warn_lines_summary, warn_lines_boards, config, environment)
Simon Glassc05694f2013-04-03 11:07:16 +0000828
829 def AddOutcome(self, board_dict, arch_list, changes, char, color):
830 """Add an output to our list of outcomes for each architecture
831
832 This simple function adds failing boards (changes) to the
833 relevant architecture string, so we can print the results out
834 sorted by architecture.
835
836 Args:
837 board_dict: Dict containing all boards
838 arch_list: Dict keyed by arch name. Value is a string containing
839 a list of board names which failed for that arch.
840 changes: List of boards to add to arch_list
841 color: terminal.Colour object
842 """
843 done_arch = {}
844 for target in changes:
845 if target in board_dict:
846 arch = board_dict[target].arch
847 else:
848 arch = 'unknown'
849 str = self.col.Color(color, ' ' + target)
850 if not arch in done_arch:
Simon Glass26f72372015-02-05 22:06:11 -0700851 str = ' %s %s' % (self.col.Color(color, char), str)
Simon Glassc05694f2013-04-03 11:07:16 +0000852 done_arch[arch] = True
853 if not arch in arch_list:
854 arch_list[arch] = str
855 else:
856 arch_list[arch] += str
857
858
859 def ColourNum(self, num):
860 color = self.col.RED if num > 0 else self.col.GREEN
861 if num == 0:
862 return '0'
863 return self.col.Color(color, str(num))
864
865 def ResetResultSummary(self, board_selected):
866 """Reset the results summary ready for use.
867
868 Set up the base board list to be all those selected, and set the
869 error lines to empty.
870
871 Following this, calls to PrintResultSummary() will use this
872 information to work out what has changed.
873
874 Args:
875 board_selected: Dict containing boards to summarise, keyed by
876 board.target
877 """
878 self._base_board_dict = {}
879 for board in board_selected:
Alex Kiernan4059e302018-05-31 04:48:34 +0000880 self._base_board_dict[board] = Builder.Outcome(0, [], [], {}, {},
881 {})
Simon Glassc05694f2013-04-03 11:07:16 +0000882 self._base_err_lines = []
Simon Glass03749d42014-08-28 09:43:44 -0600883 self._base_warn_lines = []
884 self._base_err_line_boards = {}
885 self._base_warn_line_boards = {}
Simon Glasscad8abf2015-08-25 21:52:14 -0600886 self._base_config = None
Alex Kiernan4059e302018-05-31 04:48:34 +0000887 self._base_environment = None
Simon Glassc05694f2013-04-03 11:07:16 +0000888
889 def PrintFuncSizeDetail(self, fname, old, new):
890 grow, shrink, add, remove, up, down = 0, 0, 0, 0, 0, 0
891 delta, common = [], {}
892
893 for a in old:
894 if a in new:
895 common[a] = 1
896
897 for name in old:
898 if name not in common:
899 remove += 1
900 down += old[name]
901 delta.append([-old[name], name])
902
903 for name in new:
904 if name not in common:
905 add += 1
906 up += new[name]
907 delta.append([new[name], name])
908
909 for name in common:
910 diff = new.get(name, 0) - old.get(name, 0)
911 if diff > 0:
912 grow, up = grow + 1, up + diff
913 elif diff < 0:
914 shrink, down = shrink + 1, down - diff
915 delta.append([diff, name])
916
917 delta.sort()
918 delta.reverse()
919
920 args = [add, -remove, grow, -shrink, up, -down, up - down]
Tom Rini0b48cd62017-05-22 13:48:52 -0400921 if max(args) == 0 and min(args) == 0:
Simon Glassc05694f2013-04-03 11:07:16 +0000922 return
923 args = [self.ColourNum(x) for x in args]
924 indent = ' ' * 15
Simon Glass4433aa92014-09-05 19:00:07 -0600925 Print('%s%s: add: %s/%s, grow: %s/%s bytes: %s/%s (%s)' %
926 tuple([indent, self.col.Color(self.col.YELLOW, fname)] + args))
927 Print('%s %-38s %7s %7s %+7s' % (indent, 'function', 'old', 'new',
928 'delta'))
Simon Glassc05694f2013-04-03 11:07:16 +0000929 for diff, name in delta:
930 if diff:
931 color = self.col.RED if diff > 0 else self.col.GREEN
932 msg = '%s %-38s %7s %7s %+7d' % (indent, name,
933 old.get(name, '-'), new.get(name,'-'), diff)
Simon Glass4433aa92014-09-05 19:00:07 -0600934 Print(msg, colour=color)
Simon Glassc05694f2013-04-03 11:07:16 +0000935
936
937 def PrintSizeDetail(self, target_list, show_bloat):
938 """Show details size information for each board
939
940 Args:
941 target_list: List of targets, each a dict containing:
942 'target': Target name
943 'total_diff': Total difference in bytes across all areas
944 <part_name>: Difference for that part
945 show_bloat: Show detail for each function
946 """
947 targets_by_diff = sorted(target_list, reverse=True,
948 key=lambda x: x['_total_diff'])
949 for result in targets_by_diff:
950 printed_target = False
951 for name in sorted(result):
952 diff = result[name]
953 if name.startswith('_'):
954 continue
955 if diff != 0:
956 color = self.col.RED if diff > 0 else self.col.GREEN
957 msg = ' %s %+d' % (name, diff)
958 if not printed_target:
Simon Glass4433aa92014-09-05 19:00:07 -0600959 Print('%10s %-15s:' % ('', result['_target']),
960 newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +0000961 printed_target = True
Simon Glass4433aa92014-09-05 19:00:07 -0600962 Print(msg, colour=color, newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +0000963 if printed_target:
Simon Glass4433aa92014-09-05 19:00:07 -0600964 Print()
Simon Glassc05694f2013-04-03 11:07:16 +0000965 if show_bloat:
966 target = result['_target']
967 outcome = result['_outcome']
968 base_outcome = self._base_board_dict[target]
969 for fname in outcome.func_sizes:
970 self.PrintFuncSizeDetail(fname,
971 base_outcome.func_sizes[fname],
972 outcome.func_sizes[fname])
973
974
975 def PrintSizeSummary(self, board_selected, board_dict, show_detail,
976 show_bloat):
977 """Print a summary of image sizes broken down by section.
978
979 The summary takes the form of one line per architecture. The
980 line contains deltas for each of the sections (+ means the section
981 got bigger, - means smaller). The nunmbers are the average number
982 of bytes that a board in this section increased by.
983
984 For example:
985 powerpc: (622 boards) text -0.0
986 arm: (285 boards) text -0.0
987 nds32: (3 boards) text -8.0
988
989 Args:
990 board_selected: Dict containing boards to summarise, keyed by
991 board.target
992 board_dict: Dict containing boards for which we built this
993 commit, keyed by board.target. The value is an Outcome object.
994 show_detail: Show detail for each board
995 show_bloat: Show detail for each function
996 """
997 arch_list = {}
998 arch_count = {}
999
1000 # Calculate changes in size for different image parts
1001 # The previous sizes are in Board.sizes, for each board
1002 for target in board_dict:
1003 if target not in board_selected:
1004 continue
1005 base_sizes = self._base_board_dict[target].sizes
1006 outcome = board_dict[target]
1007 sizes = outcome.sizes
1008
1009 # Loop through the list of images, creating a dict of size
1010 # changes for each image/part. We end up with something like
1011 # {'target' : 'snapper9g45, 'data' : 5, 'u-boot-spl:text' : -4}
1012 # which means that U-Boot data increased by 5 bytes and SPL
1013 # text decreased by 4.
1014 err = {'_target' : target}
1015 for image in sizes:
1016 if image in base_sizes:
1017 base_image = base_sizes[image]
1018 # Loop through the text, data, bss parts
1019 for part in sorted(sizes[image]):
1020 diff = sizes[image][part] - base_image[part]
1021 col = None
1022 if diff:
1023 if image == 'u-boot':
1024 name = part
1025 else:
1026 name = image + ':' + part
1027 err[name] = diff
1028 arch = board_selected[target].arch
1029 if not arch in arch_count:
1030 arch_count[arch] = 1
1031 else:
1032 arch_count[arch] += 1
1033 if not sizes:
1034 pass # Only add to our list when we have some stats
1035 elif not arch in arch_list:
1036 arch_list[arch] = [err]
1037 else:
1038 arch_list[arch].append(err)
1039
1040 # We now have a list of image size changes sorted by arch
1041 # Print out a summary of these
Simon Glassc78ed662019-10-31 07:42:53 -06001042 for arch, target_list in arch_list.items():
Simon Glassc05694f2013-04-03 11:07:16 +00001043 # Get total difference for each type
1044 totals = {}
1045 for result in target_list:
1046 total = 0
Simon Glassc78ed662019-10-31 07:42:53 -06001047 for name, diff in result.items():
Simon Glassc05694f2013-04-03 11:07:16 +00001048 if name.startswith('_'):
1049 continue
1050 total += diff
1051 if name in totals:
1052 totals[name] += diff
1053 else:
1054 totals[name] = diff
1055 result['_total_diff'] = total
1056 result['_outcome'] = board_dict[result['_target']]
1057
1058 count = len(target_list)
1059 printed_arch = False
1060 for name in sorted(totals):
1061 diff = totals[name]
1062 if diff:
1063 # Display the average difference in this name for this
1064 # architecture
1065 avg_diff = float(diff) / count
1066 color = self.col.RED if avg_diff > 0 else self.col.GREEN
1067 msg = ' %s %+1.1f' % (name, avg_diff)
1068 if not printed_arch:
Simon Glass4433aa92014-09-05 19:00:07 -06001069 Print('%10s: (for %d/%d boards)' % (arch, count,
1070 arch_count[arch]), newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001071 printed_arch = True
Simon Glass4433aa92014-09-05 19:00:07 -06001072 Print(msg, colour=color, newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001073
1074 if printed_arch:
Simon Glass4433aa92014-09-05 19:00:07 -06001075 Print()
Simon Glassc05694f2013-04-03 11:07:16 +00001076 if show_detail:
1077 self.PrintSizeDetail(target_list, show_bloat)
1078
1079
1080 def PrintResultSummary(self, board_selected, board_dict, err_lines,
Simon Glass03749d42014-08-28 09:43:44 -06001081 err_line_boards, warn_lines, warn_line_boards,
Alex Kiernan4059e302018-05-31 04:48:34 +00001082 config, environment, show_sizes, show_detail,
1083 show_bloat, show_config, show_environment):
Simon Glassc05694f2013-04-03 11:07:16 +00001084 """Compare results with the base results and display delta.
1085
1086 Only boards mentioned in board_selected will be considered. This
1087 function is intended to be called repeatedly with the results of
1088 each commit. It therefore shows a 'diff' between what it saw in
1089 the last call and what it sees now.
1090
1091 Args:
1092 board_selected: Dict containing boards to summarise, keyed by
1093 board.target
1094 board_dict: Dict containing boards for which we built this
1095 commit, keyed by board.target. The value is an Outcome object.
1096 err_lines: A list of errors for this commit, or [] if there is
1097 none, or we don't want to print errors
Simon Glass3394c9f2014-08-28 09:43:43 -06001098 err_line_boards: Dict keyed by error line, containing a list of
1099 the Board objects with that error
Simon Glass03749d42014-08-28 09:43:44 -06001100 warn_lines: A list of warnings for this commit, or [] if there is
1101 none, or we don't want to print errors
1102 warn_line_boards: Dict keyed by warning line, containing a list of
1103 the Board objects with that warning
Simon Glassdb17fb82015-02-05 22:06:15 -07001104 config: Dictionary keyed by filename - e.g. '.config'. Each
1105 value is itself a dictionary:
1106 key: config name
1107 value: config value
Alex Kiernan4059e302018-05-31 04:48:34 +00001108 environment: Dictionary keyed by environment variable, Each
1109 value is the value of environment variable.
Simon Glassc05694f2013-04-03 11:07:16 +00001110 show_sizes: Show image size deltas
1111 show_detail: Show detail for each board
1112 show_bloat: Show detail for each function
Simon Glassdb17fb82015-02-05 22:06:15 -07001113 show_config: Show config changes
Alex Kiernan4059e302018-05-31 04:48:34 +00001114 show_environment: Show environment changes
Simon Glassc05694f2013-04-03 11:07:16 +00001115 """
Simon Glass03749d42014-08-28 09:43:44 -06001116 def _BoardList(line, line_boards):
Simon Glass3394c9f2014-08-28 09:43:43 -06001117 """Helper function to get a line of boards containing a line
1118
1119 Args:
1120 line: Error line to search for
1121 Return:
1122 String containing a list of boards with that error line, or
1123 '' if the user has not requested such a list
1124 """
1125 if self._list_error_boards:
1126 names = []
Simon Glass03749d42014-08-28 09:43:44 -06001127 for board in line_boards[line]:
Simon Glass85620bb2014-10-16 01:05:55 -06001128 if not board.target in names:
1129 names.append(board.target)
Simon Glass3394c9f2014-08-28 09:43:43 -06001130 names_str = '(%s) ' % ','.join(names)
1131 else:
1132 names_str = ''
1133 return names_str
1134
Simon Glass03749d42014-08-28 09:43:44 -06001135 def _CalcErrorDelta(base_lines, base_line_boards, lines, line_boards,
1136 char):
1137 better_lines = []
1138 worse_lines = []
1139 for line in lines:
1140 if line not in base_lines:
1141 worse_lines.append(char + '+' +
1142 _BoardList(line, line_boards) + line)
1143 for line in base_lines:
1144 if line not in lines:
1145 better_lines.append(char + '-' +
1146 _BoardList(line, base_line_boards) + line)
1147 return better_lines, worse_lines
1148
Simon Glassdb17fb82015-02-05 22:06:15 -07001149 def _CalcConfig(delta, name, config):
1150 """Calculate configuration changes
1151
1152 Args:
1153 delta: Type of the delta, e.g. '+'
1154 name: name of the file which changed (e.g. .config)
1155 config: configuration change dictionary
1156 key: config name
1157 value: config value
1158 Returns:
1159 String containing the configuration changes which can be
1160 printed
1161 """
1162 out = ''
1163 for key in sorted(config.keys()):
1164 out += '%s=%s ' % (key, config[key])
Simon Glasscad8abf2015-08-25 21:52:14 -06001165 return '%s %s: %s' % (delta, name, out)
Simon Glassdb17fb82015-02-05 22:06:15 -07001166
Simon Glasscad8abf2015-08-25 21:52:14 -06001167 def _AddConfig(lines, name, config_plus, config_minus, config_change):
1168 """Add changes in configuration to a list
Simon Glassdb17fb82015-02-05 22:06:15 -07001169
1170 Args:
Simon Glasscad8abf2015-08-25 21:52:14 -06001171 lines: list to add to
1172 name: config file name
Simon Glassdb17fb82015-02-05 22:06:15 -07001173 config_plus: configurations added, dictionary
1174 key: config name
1175 value: config value
1176 config_minus: configurations removed, dictionary
1177 key: config name
1178 value: config value
1179 config_change: configurations changed, dictionary
1180 key: config name
1181 value: config value
1182 """
1183 if config_plus:
Simon Glasscad8abf2015-08-25 21:52:14 -06001184 lines.append(_CalcConfig('+', name, config_plus))
Simon Glassdb17fb82015-02-05 22:06:15 -07001185 if config_minus:
Simon Glasscad8abf2015-08-25 21:52:14 -06001186 lines.append(_CalcConfig('-', name, config_minus))
Simon Glassdb17fb82015-02-05 22:06:15 -07001187 if config_change:
Simon Glasscad8abf2015-08-25 21:52:14 -06001188 lines.append(_CalcConfig('c', name, config_change))
1189
1190 def _OutputConfigInfo(lines):
1191 for line in lines:
1192 if not line:
1193 continue
1194 if line[0] == '+':
1195 col = self.col.GREEN
1196 elif line[0] == '-':
1197 col = self.col.RED
1198 elif line[0] == 'c':
1199 col = self.col.YELLOW
1200 Print(' ' + line, newline=True, colour=col)
1201
Simon Glassdb17fb82015-02-05 22:06:15 -07001202
Simon Glass454507f2018-11-06 16:02:12 -07001203 ok_boards = [] # List of boards fixed since last commit
Simon Glass071a1782018-11-06 16:02:13 -07001204 warn_boards = [] # List of boards with warnings since last commit
Simon Glass454507f2018-11-06 16:02:12 -07001205 err_boards = [] # List of new broken boards since last commit
1206 new_boards = [] # List of boards that didn't exist last time
1207 unknown_boards = [] # List of boards that were not built
Simon Glassc05694f2013-04-03 11:07:16 +00001208
1209 for target in board_dict:
1210 if target not in board_selected:
1211 continue
1212
1213 # If the board was built last time, add its outcome to a list
1214 if target in self._base_board_dict:
1215 base_outcome = self._base_board_dict[target].rc
1216 outcome = board_dict[target]
1217 if outcome.rc == OUTCOME_UNKNOWN:
Simon Glass454507f2018-11-06 16:02:12 -07001218 unknown_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001219 elif outcome.rc < base_outcome:
Simon Glass071a1782018-11-06 16:02:13 -07001220 if outcome.rc == OUTCOME_WARNING:
1221 warn_boards.append(target)
1222 else:
1223 ok_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001224 elif outcome.rc > base_outcome:
Simon Glass071a1782018-11-06 16:02:13 -07001225 if outcome.rc == OUTCOME_WARNING:
1226 warn_boards.append(target)
1227 else:
1228 err_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001229 else:
Simon Glass454507f2018-11-06 16:02:12 -07001230 new_boards.append(target)
Simon Glassc05694f2013-04-03 11:07:16 +00001231
1232 # Get a list of errors that have appeared, and disappeared
Simon Glass03749d42014-08-28 09:43:44 -06001233 better_err, worse_err = _CalcErrorDelta(self._base_err_lines,
1234 self._base_err_line_boards, err_lines, err_line_boards, '')
1235 better_warn, worse_warn = _CalcErrorDelta(self._base_warn_lines,
1236 self._base_warn_line_boards, warn_lines, warn_line_boards, 'w')
Simon Glassc05694f2013-04-03 11:07:16 +00001237
1238 # Display results by arch
Simon Glass071a1782018-11-06 16:02:13 -07001239 if any((ok_boards, warn_boards, err_boards, unknown_boards, new_boards,
1240 worse_err, better_err, worse_warn, better_warn)):
Simon Glassc05694f2013-04-03 11:07:16 +00001241 arch_list = {}
Simon Glass454507f2018-11-06 16:02:12 -07001242 self.AddOutcome(board_selected, arch_list, ok_boards, '',
Simon Glassc05694f2013-04-03 11:07:16 +00001243 self.col.GREEN)
Simon Glass071a1782018-11-06 16:02:13 -07001244 self.AddOutcome(board_selected, arch_list, warn_boards, 'w+',
1245 self.col.YELLOW)
Simon Glass454507f2018-11-06 16:02:12 -07001246 self.AddOutcome(board_selected, arch_list, err_boards, '+',
Simon Glassc05694f2013-04-03 11:07:16 +00001247 self.col.RED)
Simon Glass454507f2018-11-06 16:02:12 -07001248 self.AddOutcome(board_selected, arch_list, new_boards, '*', self.col.BLUE)
Simon Glassc05694f2013-04-03 11:07:16 +00001249 if self._show_unknown:
Simon Glass454507f2018-11-06 16:02:12 -07001250 self.AddOutcome(board_selected, arch_list, unknown_boards, '?',
Simon Glassc05694f2013-04-03 11:07:16 +00001251 self.col.MAGENTA)
Simon Glassc78ed662019-10-31 07:42:53 -06001252 for arch, target_list in arch_list.items():
Simon Glass4433aa92014-09-05 19:00:07 -06001253 Print('%10s: %s' % (arch, target_list))
Simon Glassbb4dffb2014-08-09 15:33:06 -06001254 self._error_lines += 1
Simon Glassc05694f2013-04-03 11:07:16 +00001255 if better_err:
Simon Glass4433aa92014-09-05 19:00:07 -06001256 Print('\n'.join(better_err), colour=self.col.GREEN)
Simon Glassbb4dffb2014-08-09 15:33:06 -06001257 self._error_lines += 1
Simon Glassc05694f2013-04-03 11:07:16 +00001258 if worse_err:
Simon Glass4433aa92014-09-05 19:00:07 -06001259 Print('\n'.join(worse_err), colour=self.col.RED)
Simon Glassbb4dffb2014-08-09 15:33:06 -06001260 self._error_lines += 1
Simon Glass03749d42014-08-28 09:43:44 -06001261 if better_warn:
Simon Glass4433aa92014-09-05 19:00:07 -06001262 Print('\n'.join(better_warn), colour=self.col.CYAN)
Simon Glass03749d42014-08-28 09:43:44 -06001263 self._error_lines += 1
1264 if worse_warn:
Simon Glass4433aa92014-09-05 19:00:07 -06001265 Print('\n'.join(worse_warn), colour=self.col.MAGENTA)
Simon Glass03749d42014-08-28 09:43:44 -06001266 self._error_lines += 1
Simon Glassc05694f2013-04-03 11:07:16 +00001267
1268 if show_sizes:
1269 self.PrintSizeSummary(board_selected, board_dict, show_detail,
1270 show_bloat)
1271
Alex Kiernan4059e302018-05-31 04:48:34 +00001272 if show_environment and self._base_environment:
1273 lines = []
1274
1275 for target in board_dict:
1276 if target not in board_selected:
1277 continue
1278
1279 tbase = self._base_environment[target]
1280 tenvironment = environment[target]
1281 environment_plus = {}
1282 environment_minus = {}
1283 environment_change = {}
1284 base = tbase.environment
Simon Glassc78ed662019-10-31 07:42:53 -06001285 for key, value in tenvironment.environment.items():
Alex Kiernan4059e302018-05-31 04:48:34 +00001286 if key not in base:
1287 environment_plus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001288 for key, value in base.items():
Alex Kiernan4059e302018-05-31 04:48:34 +00001289 if key not in tenvironment.environment:
1290 environment_minus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001291 for key, value in base.items():
Alex Kiernan4059e302018-05-31 04:48:34 +00001292 new_value = tenvironment.environment.get(key)
1293 if new_value and value != new_value:
1294 desc = '%s -> %s' % (value, new_value)
1295 environment_change[key] = desc
1296
1297 _AddConfig(lines, target, environment_plus, environment_minus,
1298 environment_change)
1299
1300 _OutputConfigInfo(lines)
1301
Simon Glasscad8abf2015-08-25 21:52:14 -06001302 if show_config and self._base_config:
1303 summary = {}
1304 arch_config_plus = {}
1305 arch_config_minus = {}
1306 arch_config_change = {}
1307 arch_list = []
1308
1309 for target in board_dict:
1310 if target not in board_selected:
1311 continue
1312 arch = board_selected[target].arch
1313 if arch not in arch_list:
1314 arch_list.append(arch)
1315
1316 for arch in arch_list:
1317 arch_config_plus[arch] = {}
1318 arch_config_minus[arch] = {}
1319 arch_config_change[arch] = {}
Simon Glasscde5c302016-11-13 14:25:53 -07001320 for name in self.config_filenames:
Simon Glasscad8abf2015-08-25 21:52:14 -06001321 arch_config_plus[arch][name] = {}
1322 arch_config_minus[arch][name] = {}
1323 arch_config_change[arch][name] = {}
1324
1325 for target in board_dict:
1326 if target not in board_selected:
Simon Glassdb17fb82015-02-05 22:06:15 -07001327 continue
Simon Glasscad8abf2015-08-25 21:52:14 -06001328
1329 arch = board_selected[target].arch
1330
1331 all_config_plus = {}
1332 all_config_minus = {}
1333 all_config_change = {}
1334 tbase = self._base_config[target]
1335 tconfig = config[target]
1336 lines = []
Simon Glasscde5c302016-11-13 14:25:53 -07001337 for name in self.config_filenames:
Simon Glasscad8abf2015-08-25 21:52:14 -06001338 if not tconfig.config[name]:
1339 continue
1340 config_plus = {}
1341 config_minus = {}
1342 config_change = {}
1343 base = tbase.config[name]
Simon Glassc78ed662019-10-31 07:42:53 -06001344 for key, value in tconfig.config[name].items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001345 if key not in base:
1346 config_plus[key] = value
1347 all_config_plus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001348 for key, value in base.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001349 if key not in tconfig.config[name]:
1350 config_minus[key] = value
1351 all_config_minus[key] = value
Simon Glassc78ed662019-10-31 07:42:53 -06001352 for key, value in base.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001353 new_value = tconfig.config.get(key)
1354 if new_value and value != new_value:
1355 desc = '%s -> %s' % (value, new_value)
1356 config_change[key] = desc
1357 all_config_change[key] = desc
1358
1359 arch_config_plus[arch][name].update(config_plus)
1360 arch_config_minus[arch][name].update(config_minus)
1361 arch_config_change[arch][name].update(config_change)
1362
1363 _AddConfig(lines, name, config_plus, config_minus,
1364 config_change)
1365 _AddConfig(lines, 'all', all_config_plus, all_config_minus,
1366 all_config_change)
1367 summary[target] = '\n'.join(lines)
1368
1369 lines_by_target = {}
Simon Glassc78ed662019-10-31 07:42:53 -06001370 for target, lines in summary.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001371 if lines in lines_by_target:
1372 lines_by_target[lines].append(target)
1373 else:
1374 lines_by_target[lines] = [target]
1375
1376 for arch in arch_list:
1377 lines = []
1378 all_plus = {}
1379 all_minus = {}
1380 all_change = {}
Simon Glasscde5c302016-11-13 14:25:53 -07001381 for name in self.config_filenames:
Simon Glasscad8abf2015-08-25 21:52:14 -06001382 all_plus.update(arch_config_plus[arch][name])
1383 all_minus.update(arch_config_minus[arch][name])
1384 all_change.update(arch_config_change[arch][name])
1385 _AddConfig(lines, name, arch_config_plus[arch][name],
1386 arch_config_minus[arch][name],
1387 arch_config_change[arch][name])
1388 _AddConfig(lines, 'all', all_plus, all_minus, all_change)
1389 #arch_summary[target] = '\n'.join(lines)
1390 if lines:
1391 Print('%s:' % arch)
1392 _OutputConfigInfo(lines)
1393
Simon Glassc78ed662019-10-31 07:42:53 -06001394 for lines, targets in lines_by_target.items():
Simon Glasscad8abf2015-08-25 21:52:14 -06001395 if not lines:
1396 continue
1397 Print('%s :' % ' '.join(sorted(targets)))
1398 _OutputConfigInfo(lines.split('\n'))
1399
Simon Glassdb17fb82015-02-05 22:06:15 -07001400
Simon Glassc05694f2013-04-03 11:07:16 +00001401 # Save our updated information for the next call to this function
1402 self._base_board_dict = board_dict
1403 self._base_err_lines = err_lines
Simon Glass03749d42014-08-28 09:43:44 -06001404 self._base_warn_lines = warn_lines
1405 self._base_err_line_boards = err_line_boards
1406 self._base_warn_line_boards = warn_line_boards
Simon Glassdb17fb82015-02-05 22:06:15 -07001407 self._base_config = config
Alex Kiernan4059e302018-05-31 04:48:34 +00001408 self._base_environment = environment
Simon Glassc05694f2013-04-03 11:07:16 +00001409
1410 # Get a list of boards that did not get built, if needed
1411 not_built = []
1412 for board in board_selected:
1413 if not board in board_dict:
1414 not_built.append(board)
1415 if not_built:
Simon Glass4433aa92014-09-05 19:00:07 -06001416 Print("Boards not built (%d): %s" % (len(not_built),
1417 ', '.join(not_built)))
Simon Glassc05694f2013-04-03 11:07:16 +00001418
Simon Glasseb48bbc2014-08-09 15:33:02 -06001419 def ProduceResultSummary(self, commit_upto, commits, board_selected):
Simon Glass03749d42014-08-28 09:43:44 -06001420 (board_dict, err_lines, err_line_boards, warn_lines,
Alex Kiernan4059e302018-05-31 04:48:34 +00001421 warn_line_boards, config, environment) = self.GetResultSummary(
Simon Glass3394c9f2014-08-28 09:43:43 -06001422 board_selected, commit_upto,
Simon Glassdb17fb82015-02-05 22:06:15 -07001423 read_func_sizes=self._show_bloat,
Alex Kiernan4059e302018-05-31 04:48:34 +00001424 read_config=self._show_config,
1425 read_environment=self._show_environment)
Simon Glasseb48bbc2014-08-09 15:33:02 -06001426 if commits:
1427 msg = '%02d: %s' % (commit_upto + 1,
1428 commits[commit_upto].subject)
Simon Glass4433aa92014-09-05 19:00:07 -06001429 Print(msg, colour=self.col.BLUE)
Simon Glasseb48bbc2014-08-09 15:33:02 -06001430 self.PrintResultSummary(board_selected, board_dict,
Simon Glass3394c9f2014-08-28 09:43:43 -06001431 err_lines if self._show_errors else [], err_line_boards,
Simon Glass03749d42014-08-28 09:43:44 -06001432 warn_lines if self._show_errors else [], warn_line_boards,
Alex Kiernan4059e302018-05-31 04:48:34 +00001433 config, environment, self._show_sizes, self._show_detail,
1434 self._show_bloat, self._show_config, self._show_environment)
Simon Glassc05694f2013-04-03 11:07:16 +00001435
Simon Glasseb48bbc2014-08-09 15:33:02 -06001436 def ShowSummary(self, commits, board_selected):
Simon Glassc05694f2013-04-03 11:07:16 +00001437 """Show a build summary for U-Boot for a given board list.
1438
1439 Reset the result summary, then repeatedly call GetResultSummary on
1440 each commit's results, then display the differences we see.
1441
1442 Args:
1443 commit: Commit objects to summarise
1444 board_selected: Dict containing boards to summarise
Simon Glassc05694f2013-04-03 11:07:16 +00001445 """
Simon Glassd326ad72014-08-09 15:32:59 -06001446 self.commit_count = len(commits) if commits else 1
Simon Glassc05694f2013-04-03 11:07:16 +00001447 self.commits = commits
1448 self.ResetResultSummary(board_selected)
Simon Glassbb4dffb2014-08-09 15:33:06 -06001449 self._error_lines = 0
Simon Glassc05694f2013-04-03 11:07:16 +00001450
1451 for commit_upto in range(0, self.commit_count, self._step):
Simon Glasseb48bbc2014-08-09 15:33:02 -06001452 self.ProduceResultSummary(commit_upto, commits, board_selected)
Simon Glassbb4dffb2014-08-09 15:33:06 -06001453 if not self._error_lines:
Simon Glass4433aa92014-09-05 19:00:07 -06001454 Print('(no errors to report)', colour=self.col.GREEN)
Simon Glassc05694f2013-04-03 11:07:16 +00001455
1456
1457 def SetupBuild(self, board_selected, commits):
1458 """Set up ready to start a build.
1459
1460 Args:
1461 board_selected: Selected boards to build
1462 commits: Selected commits to build
1463 """
1464 # First work out how many commits we will build
Simon Glassc78ed662019-10-31 07:42:53 -06001465 count = (self.commit_count + self._step - 1) // self._step
Simon Glassc05694f2013-04-03 11:07:16 +00001466 self.count = len(board_selected) * count
1467 self.upto = self.warned = self.fail = 0
1468 self._timestamps = collections.deque()
1469
Simon Glassc05694f2013-04-03 11:07:16 +00001470 def GetThreadDir(self, thread_num):
1471 """Get the directory path to the working dir for a thread.
1472
1473 Args:
1474 thread_num: Number of thread to check.
1475 """
1476 return os.path.join(self._working_dir, '%02d' % thread_num)
1477
Simon Glassd326ad72014-08-09 15:32:59 -06001478 def _PrepareThread(self, thread_num, setup_git):
Simon Glassc05694f2013-04-03 11:07:16 +00001479 """Prepare the working directory for a thread.
1480
1481 This clones or fetches the repo into the thread's work directory.
1482
1483 Args:
1484 thread_num: Thread number (0, 1, ...)
Simon Glassd326ad72014-08-09 15:32:59 -06001485 setup_git: True to set up a git repo clone
Simon Glassc05694f2013-04-03 11:07:16 +00001486 """
1487 thread_dir = self.GetThreadDir(thread_num)
Simon Glass4a1e88b2014-08-09 15:33:00 -06001488 builderthread.Mkdir(thread_dir)
Simon Glassc05694f2013-04-03 11:07:16 +00001489 git_dir = os.path.join(thread_dir, '.git')
1490
1491 # Clone the repo if it doesn't already exist
1492 # TODO(sjg@chromium): Perhaps some git hackery to symlink instead, so
1493 # we have a private index but uses the origin repo's contents?
Simon Glassd326ad72014-08-09 15:32:59 -06001494 if setup_git and self.git_dir:
Simon Glassc05694f2013-04-03 11:07:16 +00001495 src_dir = os.path.abspath(self.git_dir)
1496 if os.path.exists(git_dir):
1497 gitutil.Fetch(git_dir, thread_dir)
1498 else:
Simon Glass2e2a6e62016-09-18 16:48:31 -06001499 Print('\rCloning repo for thread %d' % thread_num,
1500 newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001501 gitutil.Clone(src_dir, thread_dir)
Simon Glass2e2a6e62016-09-18 16:48:31 -06001502 Print('\r%s\r' % (' ' * 30), newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001503
Simon Glassd326ad72014-08-09 15:32:59 -06001504 def _PrepareWorkingSpace(self, max_threads, setup_git):
Simon Glassc05694f2013-04-03 11:07:16 +00001505 """Prepare the working directory for use.
1506
1507 Set up the git repo for each thread.
1508
1509 Args:
1510 max_threads: Maximum number of threads we expect to need.
Simon Glassd326ad72014-08-09 15:32:59 -06001511 setup_git: True to set up a git repo clone
Simon Glassc05694f2013-04-03 11:07:16 +00001512 """
Simon Glass4a1e88b2014-08-09 15:33:00 -06001513 builderthread.Mkdir(self._working_dir)
Simon Glassc05694f2013-04-03 11:07:16 +00001514 for thread in range(max_threads):
Simon Glassd326ad72014-08-09 15:32:59 -06001515 self._PrepareThread(thread, setup_git)
Simon Glassc05694f2013-04-03 11:07:16 +00001516
1517 def _PrepareOutputSpace(self):
1518 """Get the output directories ready to receive files.
1519
1520 We delete any output directories which look like ones we need to
1521 create. Having left over directories is confusing when the user wants
1522 to check the output manually.
1523 """
Simon Glassb9a9b7c2014-12-01 17:33:53 -07001524 if not self.commits:
1525 return
Simon Glassc05694f2013-04-03 11:07:16 +00001526 dir_list = []
1527 for commit_upto in range(self.commit_count):
1528 dir_list.append(self._GetOutputDir(commit_upto))
1529
Simon Glass83cb6cc2016-09-18 16:48:32 -06001530 to_remove = []
Simon Glassc05694f2013-04-03 11:07:16 +00001531 for dirname in glob.glob(os.path.join(self.base_dir, '*')):
1532 if dirname not in dir_list:
Simon Glass83cb6cc2016-09-18 16:48:32 -06001533 to_remove.append(dirname)
1534 if to_remove:
1535 Print('Removing %d old build directories' % len(to_remove),
1536 newline=False)
1537 for dirname in to_remove:
Simon Glassc05694f2013-04-03 11:07:16 +00001538 shutil.rmtree(dirname)
1539
Simon Glass78e418e2014-08-09 15:33:03 -06001540 def BuildBoards(self, commits, board_selected, keep_outputs, verbose):
Simon Glassc05694f2013-04-03 11:07:16 +00001541 """Build all commits for a list of boards
1542
1543 Args:
1544 commits: List of commits to be build, each a Commit object
1545 boards_selected: Dict of selected boards, key is target name,
1546 value is Board object
Simon Glassc05694f2013-04-03 11:07:16 +00001547 keep_outputs: True to save build output files
Simon Glass78e418e2014-08-09 15:33:03 -06001548 verbose: Display build results as they are completed
Simon Glassc2f91072014-08-28 09:43:39 -06001549 Returns:
1550 Tuple containing:
1551 - number of boards that failed to build
1552 - number of boards that issued warnings
Simon Glassc05694f2013-04-03 11:07:16 +00001553 """
Simon Glassd326ad72014-08-09 15:32:59 -06001554 self.commit_count = len(commits) if commits else 1
Simon Glassc05694f2013-04-03 11:07:16 +00001555 self.commits = commits
Simon Glass78e418e2014-08-09 15:33:03 -06001556 self._verbose = verbose
Simon Glassc05694f2013-04-03 11:07:16 +00001557
1558 self.ResetResultSummary(board_selected)
Thierry Reding336d5bd2014-08-19 10:22:39 +02001559 builderthread.Mkdir(self.base_dir, parents = True)
Simon Glassd326ad72014-08-09 15:32:59 -06001560 self._PrepareWorkingSpace(min(self.num_threads, len(board_selected)),
1561 commits is not None)
Simon Glassc05694f2013-04-03 11:07:16 +00001562 self._PrepareOutputSpace()
Simon Glasse0f23602016-09-18 16:48:33 -06001563 Print('\rStarting build...', newline=False)
Simon Glassc05694f2013-04-03 11:07:16 +00001564 self.SetupBuild(board_selected, commits)
1565 self.ProcessResult(None)
1566
1567 # Create jobs to build all commits for each board
Simon Glassc78ed662019-10-31 07:42:53 -06001568 for brd in board_selected.values():
Simon Glass4a1e88b2014-08-09 15:33:00 -06001569 job = builderthread.BuilderJob()
Simon Glassc05694f2013-04-03 11:07:16 +00001570 job.board = brd
1571 job.commits = commits
1572 job.keep_outputs = keep_outputs
1573 job.step = self._step
1574 self.queue.put(job)
1575
Simon Glassd26e1442016-09-18 16:48:35 -06001576 term = threading.Thread(target=self.queue.join)
1577 term.setDaemon(True)
1578 term.start()
1579 while term.isAlive():
1580 term.join(100)
Simon Glassc05694f2013-04-03 11:07:16 +00001581
1582 # Wait until we have processed all output
1583 self.out_queue.join()
Simon Glass4433aa92014-09-05 19:00:07 -06001584 Print()
Simon Glassc05694f2013-04-03 11:07:16 +00001585 self.ClearLine(0)
Simon Glassc2f91072014-08-28 09:43:39 -06001586 return (self.fail, self.warned)