blob: ca74c3645ef3e6a7d0f774989c8483650fb55e90 [file] [log] [blame]
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001# Copyright (c) 2013 The Chromium OS Authors.
2#
3# Bloat-o-meter code used here Copyright 2004 Matt Mackall <mpm@selenic.com>
4#
Wolfgang Denk1a459662013-07-08 09:37:19 +02005# SPDX-License-Identifier: GPL-2.0+
Simon Glassfc3fe1c2013-04-03 11:07:16 +00006#
7
8import collections
Simon Glassfc3fe1c2013-04-03 11:07:16 +00009from datetime import datetime, timedelta
10import glob
11import os
12import re
13import Queue
14import shutil
15import string
16import sys
Simon Glassfc3fe1c2013-04-03 11:07:16 +000017import time
18
Simon Glass190064b2014-08-09 15:33:00 -060019import builderthread
Simon Glassfc3fe1c2013-04-03 11:07:16 +000020import command
21import gitutil
22import terminal
Simon Glass4653a882014-09-05 19:00:07 -060023from terminal import Print
Simon Glassfc3fe1c2013-04-03 11:07:16 +000024import toolchain
25
26
27"""
28Theory of Operation
29
30Please see README for user documentation, and you should be familiar with
31that before trying to make sense of this.
32
33Buildman works by keeping the machine as busy as possible, building different
34commits for different boards on multiple CPUs at once.
35
36The source repo (self.git_dir) contains all the commits to be built. Each
37thread works on a single board at a time. It checks out the first commit,
38configures it for that board, then builds it. Then it checks out the next
39commit and builds it (typically without re-configuring). When it runs out
40of commits, it gets another job from the builder and starts again with that
41board.
42
43Clearly the builder threads could work either way - they could check out a
44commit and then built it for all boards. Using separate directories for each
45commit/board pair they could leave their build product around afterwards
46also.
47
48The intent behind building a single board for multiple commits, is to make
49use of incremental builds. Since each commit is built incrementally from
50the previous one, builds are faster. Reconfiguring for a different board
51removes all intermediate object files.
52
53Many threads can be working at once, but each has its own working directory.
54When a thread finishes a build, it puts the output files into a result
55directory.
56
57The base directory used by buildman is normally '../<branch>', i.e.
58a directory higher than the source repository and named after the branch
59being built.
60
61Within the base directory, we have one subdirectory for each commit. Within
62that is one subdirectory for each board. Within that is the build output for
63that commit/board combination.
64
65Buildman also create working directories for each thread, in a .bm-work/
66subdirectory in the base dir.
67
68As an example, say we are building branch 'us-net' for boards 'sandbox' and
69'seaboard', and say that us-net has two commits. We will have directories
70like this:
71
72us-net/ base directory
73 01_of_02_g4ed4ebc_net--Add-tftp-speed-/
74 sandbox/
75 u-boot.bin
76 seaboard/
77 u-boot.bin
78 02_of_02_g4ed4ebc_net--Check-tftp-comp/
79 sandbox/
80 u-boot.bin
81 seaboard/
82 u-boot.bin
83 .bm-work/
84 00/ working directory for thread 0 (contains source checkout)
85 build/ build output
86 01/ working directory for thread 1
87 build/ build output
88 ...
89u-boot/ source directory
90 .git/ repository
91"""
92
93# Possible build outcomes
94OUTCOME_OK, OUTCOME_WARNING, OUTCOME_ERROR, OUTCOME_UNKNOWN = range(4)
95
96# Translate a commit subject into a valid filename
97trans_valid_chars = string.maketrans("/: ", "---")
98
99
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000100class Builder:
101 """Class for building U-Boot for a particular commit.
102
103 Public members: (many should ->private)
104 active: True if the builder is active and has not been stopped
105 already_done: Number of builds already completed
106 base_dir: Base directory to use for builder
107 checkout: True to check out source, False to skip that step.
108 This is used for testing.
109 col: terminal.Color() object
110 count: Number of commits to build
111 do_make: Method to call to invoke Make
112 fail: Number of builds that failed due to error
113 force_build: Force building even if a build already exists
114 force_config_on_failure: If a commit fails for a board, disable
115 incremental building for the next commit we build for that
116 board, so that we will see all warnings/errors again.
Simon Glass4266dc22014-07-13 12:22:31 -0600117 force_build_failures: If a previously-built build (i.e. built on
118 a previous run of buildman) is marked as failed, rebuild it.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000119 git_dir: Git directory containing source repository
120 last_line_len: Length of the last line we printed (used for erasing
121 it with new progress information)
122 num_jobs: Number of jobs to run at once (passed to make as -j)
123 num_threads: Number of builder threads to run
124 out_queue: Queue of results to process
125 re_make_err: Compiled regular expression for ignore_lines
126 queue: Queue of jobs to run
127 threads: List of active threads
128 toolchains: Toolchains object to use for building
129 upto: Current commit number we are building (0.count-1)
130 warned: Number of builds that produced at least one warning
Simon Glass97e91522014-07-14 17:51:02 -0600131 force_reconfig: Reconfigure U-Boot on each comiit. This disables
132 incremental building, where buildman reconfigures on the first
133 commit for a baord, and then just does an incremental build for
134 the following commits. In fact buildman will reconfigure and
135 retry for any failing commits, so generally the only effect of
136 this option is to slow things down.
Simon Glass189a4962014-07-14 17:51:03 -0600137 in_tree: Build U-Boot in-tree instead of specifying an output
138 directory separate from the source code. This option is really
139 only useful for testing in-tree builds.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000140
141 Private members:
142 _base_board_dict: Last-summarised Dict of boards
143 _base_err_lines: Last-summarised list of errors
Simon Glasse30965d2014-08-28 09:43:44 -0600144 _base_warn_lines: Last-summarised list of warnings
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000145 _build_period_us: Time taken for a single build (float object).
146 _complete_delay: Expected delay until completion (timedelta)
147 _next_delay_update: Next time we plan to display a progress update
148 (datatime)
149 _show_unknown: Show unknown boards (those not built) in summary
150 _timestamps: List of timestamps for the completion of the last
151 last _timestamp_count builds. Each is a datetime object.
152 _timestamp_count: Number of timestamps to keep in our list.
153 _working_dir: Base working directory containing all threads
154 """
155 class Outcome:
156 """Records a build outcome for a single make invocation
157
158 Public Members:
159 rc: Outcome value (OUTCOME_...)
160 err_lines: List of error lines or [] if none
161 sizes: Dictionary of image size information, keyed by filename
162 - Each value is itself a dictionary containing
163 values for 'text', 'data' and 'bss', being the integer
164 size in bytes of each section.
165 func_sizes: Dictionary keyed by filename - e.g. 'u-boot'. Each
166 value is itself a dictionary:
167 key: function name
168 value: Size of function in bytes
169 """
170 def __init__(self, rc, err_lines, sizes, func_sizes):
171 self.rc = rc
172 self.err_lines = err_lines
173 self.sizes = sizes
174 self.func_sizes = func_sizes
175
176 def __init__(self, toolchains, base_dir, git_dir, num_threads, num_jobs,
Simon Glass5971ab52014-12-01 17:33:55 -0700177 gnu_make='make', checkout=True, show_unknown=True, step=1,
178 no_subdirs=False):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000179 """Create a new Builder object
180
181 Args:
182 toolchains: Toolchains object to use for building
183 base_dir: Base directory to use for builder
184 git_dir: Git directory containing source repository
185 num_threads: Number of builder threads to run
186 num_jobs: Number of jobs to run at once (passed to make as -j)
Masahiro Yamada99796922014-07-22 11:19:09 +0900187 gnu_make: the command name of GNU Make.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000188 checkout: True to check out source, False to skip that step.
189 This is used for testing.
190 show_unknown: Show unknown boards (those not built) in summary
191 step: 1 to process every commit, n to process every nth commit
192 """
193 self.toolchains = toolchains
194 self.base_dir = base_dir
195 self._working_dir = os.path.join(base_dir, '.bm-work')
196 self.threads = []
197 self.active = True
198 self.do_make = self.Make
Masahiro Yamada99796922014-07-22 11:19:09 +0900199 self.gnu_make = gnu_make
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000200 self.checkout = checkout
201 self.num_threads = num_threads
202 self.num_jobs = num_jobs
203 self.already_done = 0
204 self.force_build = False
205 self.git_dir = git_dir
206 self._show_unknown = show_unknown
207 self._timestamp_count = 10
208 self._build_period_us = None
209 self._complete_delay = None
210 self._next_delay_update = datetime.now()
211 self.force_config_on_failure = True
Simon Glass4266dc22014-07-13 12:22:31 -0600212 self.force_build_failures = False
Simon Glass97e91522014-07-14 17:51:02 -0600213 self.force_reconfig = False
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000214 self._step = step
Simon Glass189a4962014-07-14 17:51:03 -0600215 self.in_tree = False
Simon Glass28370c12014-08-09 15:33:06 -0600216 self._error_lines = 0
Simon Glass5971ab52014-12-01 17:33:55 -0700217 self.no_subdirs = no_subdirs
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000218
219 self.col = terminal.Color()
220
Simon Glasse30965d2014-08-28 09:43:44 -0600221 self._re_function = re.compile('(.*): In function.*')
222 self._re_files = re.compile('In file included from.*')
223 self._re_warning = re.compile('(.*):(\d*):(\d*): warning: .*')
224 self._re_note = re.compile('(.*):(\d*):(\d*): note: this is the location of the previous.*')
225
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000226 self.queue = Queue.Queue()
227 self.out_queue = Queue.Queue()
228 for i in range(self.num_threads):
Simon Glass190064b2014-08-09 15:33:00 -0600229 t = builderthread.BuilderThread(self, i)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000230 t.setDaemon(True)
231 t.start()
232 self.threads.append(t)
233
234 self.last_line_len = 0
Simon Glass190064b2014-08-09 15:33:00 -0600235 t = builderthread.ResultThread(self)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000236 t.setDaemon(True)
237 t.start()
238 self.threads.append(t)
239
240 ignore_lines = ['(make.*Waiting for unfinished)', '(Segmentation fault)']
241 self.re_make_err = re.compile('|'.join(ignore_lines))
242
243 def __del__(self):
244 """Get rid of all threads created by the builder"""
245 for t in self.threads:
246 del t
247
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600248 def SetDisplayOptions(self, show_errors=False, show_sizes=False,
Simon Glassed966652014-08-28 09:43:43 -0600249 show_detail=False, show_bloat=False,
250 list_error_boards=False):
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600251 """Setup display options for the builder.
252
253 show_errors: True to show summarised error/warning info
254 show_sizes: Show size deltas
255 show_detail: Show detail for each board
256 show_bloat: Show detail for each function
Simon Glassed966652014-08-28 09:43:43 -0600257 list_error_boards: Show the boards which caused each error/warning
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600258 """
259 self._show_errors = show_errors
260 self._show_sizes = show_sizes
261 self._show_detail = show_detail
262 self._show_bloat = show_bloat
Simon Glassed966652014-08-28 09:43:43 -0600263 self._list_error_boards = list_error_boards
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600264
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000265 def _AddTimestamp(self):
266 """Add a new timestamp to the list and record the build period.
267
268 The build period is the length of time taken to perform a single
269 build (one board, one commit).
270 """
271 now = datetime.now()
272 self._timestamps.append(now)
273 count = len(self._timestamps)
274 delta = self._timestamps[-1] - self._timestamps[0]
275 seconds = delta.total_seconds()
276
277 # If we have enough data, estimate build period (time taken for a
278 # single build) and therefore completion time.
279 if count > 1 and self._next_delay_update < now:
280 self._next_delay_update = now + timedelta(seconds=2)
281 if seconds > 0:
282 self._build_period = float(seconds) / count
283 todo = self.count - self.upto
284 self._complete_delay = timedelta(microseconds=
285 self._build_period * todo * 1000000)
286 # Round it
287 self._complete_delay -= timedelta(
288 microseconds=self._complete_delay.microseconds)
289
290 if seconds > 60:
291 self._timestamps.popleft()
292 count -= 1
293
294 def ClearLine(self, length):
295 """Clear any characters on the current line
296
297 Make way for a new line of length 'length', by outputting enough
298 spaces to clear out the old line. Then remember the new length for
299 next time.
300
301 Args:
302 length: Length of new line, in characters
303 """
304 if length < self.last_line_len:
Simon Glass4653a882014-09-05 19:00:07 -0600305 Print(' ' * (self.last_line_len - length), newline=False)
306 Print('\r', newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000307 self.last_line_len = length
308 sys.stdout.flush()
309
310 def SelectCommit(self, commit, checkout=True):
311 """Checkout the selected commit for this build
312 """
313 self.commit = commit
314 if checkout and self.checkout:
315 gitutil.Checkout(commit.hash)
316
317 def Make(self, commit, brd, stage, cwd, *args, **kwargs):
318 """Run make
319
320 Args:
321 commit: Commit object that is being built
322 brd: Board object that is being built
Roger Meierfd18a892014-08-20 22:10:29 +0200323 stage: Stage that we are at (mrproper, config, build)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000324 cwd: Directory where make should be run
325 args: Arguments to pass to make
326 kwargs: Arguments to pass to command.RunPipe()
327 """
Masahiro Yamada99796922014-07-22 11:19:09 +0900328 cmd = [self.gnu_make] + list(args)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000329 result = command.RunPipe([cmd], capture=True, capture_stderr=True,
330 cwd=cwd, raise_on_error=False, **kwargs)
331 return result
332
333 def ProcessResult(self, result):
334 """Process the result of a build, showing progress information
335
336 Args:
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600337 result: A CommandResult object, which indicates the result for
338 a single build
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000339 """
340 col = terminal.Color()
341 if result:
342 target = result.brd.target
343
344 if result.return_code < 0:
345 self.active = False
346 command.StopAll()
347 return
348
349 self.upto += 1
350 if result.return_code != 0:
351 self.fail += 1
352 elif result.stderr:
353 self.warned += 1
354 if result.already_done:
355 self.already_done += 1
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600356 if self._verbose:
Simon Glass4653a882014-09-05 19:00:07 -0600357 Print('\r', newline=False)
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600358 self.ClearLine(0)
359 boards_selected = {target : result.brd}
360 self.ResetResultSummary(boards_selected)
361 self.ProduceResultSummary(result.commit_upto, self.commits,
362 boards_selected)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000363 else:
364 target = '(starting)'
365
366 # Display separate counts for ok, warned and fail
367 ok = self.upto - self.warned - self.fail
368 line = '\r' + self.col.Color(self.col.GREEN, '%5d' % ok)
369 line += self.col.Color(self.col.YELLOW, '%5d' % self.warned)
370 line += self.col.Color(self.col.RED, '%5d' % self.fail)
371
372 name = ' /%-5d ' % self.count
373
374 # Add our current completion time estimate
375 self._AddTimestamp()
376 if self._complete_delay:
377 name += '%s : ' % self._complete_delay
378 # When building all boards for a commit, we can print a commit
379 # progress message.
380 if result and result.commit_upto is None:
381 name += 'commit %2d/%-3d' % (self.commit_upto + 1,
382 self.commit_count)
383
384 name += target
Simon Glass4653a882014-09-05 19:00:07 -0600385 Print(line + name, newline=False)
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600386 length = 14 + len(name)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000387 self.ClearLine(length)
388
389 def _GetOutputDir(self, commit_upto):
390 """Get the name of the output directory for a commit number
391
392 The output directory is typically .../<branch>/<commit>.
393
394 Args:
395 commit_upto: Commit number to use (0..self.count-1)
396 """
Simon Glass5971ab52014-12-01 17:33:55 -0700397 commit_dir = None
Simon Glassfea58582014-08-09 15:32:59 -0600398 if self.commits:
399 commit = self.commits[commit_upto]
400 subject = commit.subject.translate(trans_valid_chars)
401 commit_dir = ('%02d_of_%02d_g%s_%s' % (commit_upto + 1,
402 self.commit_count, commit.hash, subject[:20]))
Simon Glass5971ab52014-12-01 17:33:55 -0700403 elif not self.no_subdirs:
Simon Glassfea58582014-08-09 15:32:59 -0600404 commit_dir = 'current'
Simon Glass5971ab52014-12-01 17:33:55 -0700405 if not commit_dir:
406 return self.base_dir
407 return os.path.join(self.base_dir, commit_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000408
409 def GetBuildDir(self, commit_upto, target):
410 """Get the name of the build directory for a commit number
411
412 The build directory is typically .../<branch>/<commit>/<target>.
413
414 Args:
415 commit_upto: Commit number to use (0..self.count-1)
416 target: Target name
417 """
418 output_dir = self._GetOutputDir(commit_upto)
419 return os.path.join(output_dir, target)
420
421 def GetDoneFile(self, commit_upto, target):
422 """Get the name of the done file for a commit number
423
424 Args:
425 commit_upto: Commit number to use (0..self.count-1)
426 target: Target name
427 """
428 return os.path.join(self.GetBuildDir(commit_upto, target), 'done')
429
430 def GetSizesFile(self, commit_upto, target):
431 """Get the name of the sizes file for a commit number
432
433 Args:
434 commit_upto: Commit number to use (0..self.count-1)
435 target: Target name
436 """
437 return os.path.join(self.GetBuildDir(commit_upto, target), 'sizes')
438
439 def GetFuncSizesFile(self, commit_upto, target, elf_fname):
440 """Get the name of the funcsizes file for a commit number and ELF file
441
442 Args:
443 commit_upto: Commit number to use (0..self.count-1)
444 target: Target name
445 elf_fname: Filename of elf image
446 """
447 return os.path.join(self.GetBuildDir(commit_upto, target),
448 '%s.sizes' % elf_fname.replace('/', '-'))
449
450 def GetObjdumpFile(self, commit_upto, target, elf_fname):
451 """Get the name of the objdump file for a commit number and ELF file
452
453 Args:
454 commit_upto: Commit number to use (0..self.count-1)
455 target: Target name
456 elf_fname: Filename of elf image
457 """
458 return os.path.join(self.GetBuildDir(commit_upto, target),
459 '%s.objdump' % elf_fname.replace('/', '-'))
460
461 def GetErrFile(self, commit_upto, target):
462 """Get the name of the err file for a commit number
463
464 Args:
465 commit_upto: Commit number to use (0..self.count-1)
466 target: Target name
467 """
468 output_dir = self.GetBuildDir(commit_upto, target)
469 return os.path.join(output_dir, 'err')
470
471 def FilterErrors(self, lines):
472 """Filter out errors in which we have no interest
473
474 We should probably use map().
475
476 Args:
477 lines: List of error lines, each a string
478 Returns:
479 New list with only interesting lines included
480 """
481 out_lines = []
482 for line in lines:
483 if not self.re_make_err.search(line):
484 out_lines.append(line)
485 return out_lines
486
487 def ReadFuncSizes(self, fname, fd):
488 """Read function sizes from the output of 'nm'
489
490 Args:
491 fd: File containing data to read
492 fname: Filename we are reading from (just for errors)
493
494 Returns:
495 Dictionary containing size of each function in bytes, indexed by
496 function name.
497 """
498 sym = {}
499 for line in fd.readlines():
500 try:
501 size, type, name = line[:-1].split()
502 except:
Simon Glass4653a882014-09-05 19:00:07 -0600503 Print("Invalid line in file '%s': '%s'" % (fname, line[:-1]))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000504 continue
505 if type in 'tTdDbB':
506 # function names begin with '.' on 64-bit powerpc
507 if '.' in name[1:]:
508 name = 'static.' + name.split('.')[0]
509 sym[name] = sym.get(name, 0) + int(size, 16)
510 return sym
511
512 def GetBuildOutcome(self, commit_upto, target, read_func_sizes):
513 """Work out the outcome of a build.
514
515 Args:
516 commit_upto: Commit number to check (0..n-1)
517 target: Target board to check
518 read_func_sizes: True to read function size information
519
520 Returns:
521 Outcome object
522 """
523 done_file = self.GetDoneFile(commit_upto, target)
524 sizes_file = self.GetSizesFile(commit_upto, target)
525 sizes = {}
526 func_sizes = {}
527 if os.path.exists(done_file):
528 with open(done_file, 'r') as fd:
529 return_code = int(fd.readline())
530 err_lines = []
531 err_file = self.GetErrFile(commit_upto, target)
532 if os.path.exists(err_file):
533 with open(err_file, 'r') as fd:
534 err_lines = self.FilterErrors(fd.readlines())
535
536 # Decide whether the build was ok, failed or created warnings
537 if return_code:
538 rc = OUTCOME_ERROR
539 elif len(err_lines):
540 rc = OUTCOME_WARNING
541 else:
542 rc = OUTCOME_OK
543
544 # Convert size information to our simple format
545 if os.path.exists(sizes_file):
546 with open(sizes_file, 'r') as fd:
547 for line in fd.readlines():
548 values = line.split()
549 rodata = 0
550 if len(values) > 6:
551 rodata = int(values[6], 16)
552 size_dict = {
553 'all' : int(values[0]) + int(values[1]) +
554 int(values[2]),
555 'text' : int(values[0]) - rodata,
556 'data' : int(values[1]),
557 'bss' : int(values[2]),
558 'rodata' : rodata,
559 }
560 sizes[values[5]] = size_dict
561
562 if read_func_sizes:
563 pattern = self.GetFuncSizesFile(commit_upto, target, '*')
564 for fname in glob.glob(pattern):
565 with open(fname, 'r') as fd:
566 dict_name = os.path.basename(fname).replace('.sizes',
567 '')
568 func_sizes[dict_name] = self.ReadFuncSizes(fname, fd)
569
570 return Builder.Outcome(rc, err_lines, sizes, func_sizes)
571
572 return Builder.Outcome(OUTCOME_UNKNOWN, [], {}, {})
573
574 def GetResultSummary(self, boards_selected, commit_upto, read_func_sizes):
575 """Calculate a summary of the results of building a commit.
576
577 Args:
578 board_selected: Dict containing boards to summarise
579 commit_upto: Commit number to summarize (0..self.count-1)
580 read_func_sizes: True to read function size information
581
582 Returns:
583 Tuple:
584 Dict containing boards which passed building this commit.
585 keyed by board.target
Simon Glasse30965d2014-08-28 09:43:44 -0600586 List containing a summary of error lines
Simon Glassed966652014-08-28 09:43:43 -0600587 Dict keyed by error line, containing a list of the Board
588 objects with that error
Simon Glasse30965d2014-08-28 09:43:44 -0600589 List containing a summary of warning lines
590 Dict keyed by error line, containing a list of the Board
591 objects with that warning
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000592 """
Simon Glasse30965d2014-08-28 09:43:44 -0600593 def AddLine(lines_summary, lines_boards, line, board):
594 line = line.rstrip()
595 if line in lines_boards:
596 lines_boards[line].append(board)
597 else:
598 lines_boards[line] = [board]
599 lines_summary.append(line)
600
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000601 board_dict = {}
602 err_lines_summary = []
Simon Glassed966652014-08-28 09:43:43 -0600603 err_lines_boards = {}
Simon Glasse30965d2014-08-28 09:43:44 -0600604 warn_lines_summary = []
605 warn_lines_boards = {}
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000606
607 for board in boards_selected.itervalues():
608 outcome = self.GetBuildOutcome(commit_upto, board.target,
609 read_func_sizes)
610 board_dict[board.target] = outcome
Simon Glasse30965d2014-08-28 09:43:44 -0600611 last_func = None
612 last_was_warning = False
613 for line in outcome.err_lines:
614 if line:
615 if (self._re_function.match(line) or
616 self._re_files.match(line)):
617 last_func = line
Simon Glassed966652014-08-28 09:43:43 -0600618 else:
Simon Glasse30965d2014-08-28 09:43:44 -0600619 is_warning = self._re_warning.match(line)
620 is_note = self._re_note.match(line)
621 if is_warning or (last_was_warning and is_note):
622 if last_func:
623 AddLine(warn_lines_summary, warn_lines_boards,
624 last_func, board)
625 AddLine(warn_lines_summary, warn_lines_boards,
626 line, board)
627 else:
628 if last_func:
629 AddLine(err_lines_summary, err_lines_boards,
630 last_func, board)
631 AddLine(err_lines_summary, err_lines_boards,
632 line, board)
633 last_was_warning = is_warning
634 last_func = None
635 return (board_dict, err_lines_summary, err_lines_boards,
636 warn_lines_summary, warn_lines_boards)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000637
638 def AddOutcome(self, board_dict, arch_list, changes, char, color):
639 """Add an output to our list of outcomes for each architecture
640
641 This simple function adds failing boards (changes) to the
642 relevant architecture string, so we can print the results out
643 sorted by architecture.
644
645 Args:
646 board_dict: Dict containing all boards
647 arch_list: Dict keyed by arch name. Value is a string containing
648 a list of board names which failed for that arch.
649 changes: List of boards to add to arch_list
650 color: terminal.Colour object
651 """
652 done_arch = {}
653 for target in changes:
654 if target in board_dict:
655 arch = board_dict[target].arch
656 else:
657 arch = 'unknown'
658 str = self.col.Color(color, ' ' + target)
659 if not arch in done_arch:
660 str = self.col.Color(color, char) + ' ' + str
661 done_arch[arch] = True
662 if not arch in arch_list:
663 arch_list[arch] = str
664 else:
665 arch_list[arch] += str
666
667
668 def ColourNum(self, num):
669 color = self.col.RED if num > 0 else self.col.GREEN
670 if num == 0:
671 return '0'
672 return self.col.Color(color, str(num))
673
674 def ResetResultSummary(self, board_selected):
675 """Reset the results summary ready for use.
676
677 Set up the base board list to be all those selected, and set the
678 error lines to empty.
679
680 Following this, calls to PrintResultSummary() will use this
681 information to work out what has changed.
682
683 Args:
684 board_selected: Dict containing boards to summarise, keyed by
685 board.target
686 """
687 self._base_board_dict = {}
688 for board in board_selected:
689 self._base_board_dict[board] = Builder.Outcome(0, [], [], {})
690 self._base_err_lines = []
Simon Glasse30965d2014-08-28 09:43:44 -0600691 self._base_warn_lines = []
692 self._base_err_line_boards = {}
693 self._base_warn_line_boards = {}
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000694
695 def PrintFuncSizeDetail(self, fname, old, new):
696 grow, shrink, add, remove, up, down = 0, 0, 0, 0, 0, 0
697 delta, common = [], {}
698
699 for a in old:
700 if a in new:
701 common[a] = 1
702
703 for name in old:
704 if name not in common:
705 remove += 1
706 down += old[name]
707 delta.append([-old[name], name])
708
709 for name in new:
710 if name not in common:
711 add += 1
712 up += new[name]
713 delta.append([new[name], name])
714
715 for name in common:
716 diff = new.get(name, 0) - old.get(name, 0)
717 if diff > 0:
718 grow, up = grow + 1, up + diff
719 elif diff < 0:
720 shrink, down = shrink + 1, down - diff
721 delta.append([diff, name])
722
723 delta.sort()
724 delta.reverse()
725
726 args = [add, -remove, grow, -shrink, up, -down, up - down]
727 if max(args) == 0:
728 return
729 args = [self.ColourNum(x) for x in args]
730 indent = ' ' * 15
Simon Glass4653a882014-09-05 19:00:07 -0600731 Print('%s%s: add: %s/%s, grow: %s/%s bytes: %s/%s (%s)' %
732 tuple([indent, self.col.Color(self.col.YELLOW, fname)] + args))
733 Print('%s %-38s %7s %7s %+7s' % (indent, 'function', 'old', 'new',
734 'delta'))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000735 for diff, name in delta:
736 if diff:
737 color = self.col.RED if diff > 0 else self.col.GREEN
738 msg = '%s %-38s %7s %7s %+7d' % (indent, name,
739 old.get(name, '-'), new.get(name,'-'), diff)
Simon Glass4653a882014-09-05 19:00:07 -0600740 Print(msg, colour=color)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000741
742
743 def PrintSizeDetail(self, target_list, show_bloat):
744 """Show details size information for each board
745
746 Args:
747 target_list: List of targets, each a dict containing:
748 'target': Target name
749 'total_diff': Total difference in bytes across all areas
750 <part_name>: Difference for that part
751 show_bloat: Show detail for each function
752 """
753 targets_by_diff = sorted(target_list, reverse=True,
754 key=lambda x: x['_total_diff'])
755 for result in targets_by_diff:
756 printed_target = False
757 for name in sorted(result):
758 diff = result[name]
759 if name.startswith('_'):
760 continue
761 if diff != 0:
762 color = self.col.RED if diff > 0 else self.col.GREEN
763 msg = ' %s %+d' % (name, diff)
764 if not printed_target:
Simon Glass4653a882014-09-05 19:00:07 -0600765 Print('%10s %-15s:' % ('', result['_target']),
766 newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000767 printed_target = True
Simon Glass4653a882014-09-05 19:00:07 -0600768 Print(msg, colour=color, newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000769 if printed_target:
Simon Glass4653a882014-09-05 19:00:07 -0600770 Print()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000771 if show_bloat:
772 target = result['_target']
773 outcome = result['_outcome']
774 base_outcome = self._base_board_dict[target]
775 for fname in outcome.func_sizes:
776 self.PrintFuncSizeDetail(fname,
777 base_outcome.func_sizes[fname],
778 outcome.func_sizes[fname])
779
780
781 def PrintSizeSummary(self, board_selected, board_dict, show_detail,
782 show_bloat):
783 """Print a summary of image sizes broken down by section.
784
785 The summary takes the form of one line per architecture. The
786 line contains deltas for each of the sections (+ means the section
787 got bigger, - means smaller). The nunmbers are the average number
788 of bytes that a board in this section increased by.
789
790 For example:
791 powerpc: (622 boards) text -0.0
792 arm: (285 boards) text -0.0
793 nds32: (3 boards) text -8.0
794
795 Args:
796 board_selected: Dict containing boards to summarise, keyed by
797 board.target
798 board_dict: Dict containing boards for which we built this
799 commit, keyed by board.target. The value is an Outcome object.
800 show_detail: Show detail for each board
801 show_bloat: Show detail for each function
802 """
803 arch_list = {}
804 arch_count = {}
805
806 # Calculate changes in size for different image parts
807 # The previous sizes are in Board.sizes, for each board
808 for target in board_dict:
809 if target not in board_selected:
810 continue
811 base_sizes = self._base_board_dict[target].sizes
812 outcome = board_dict[target]
813 sizes = outcome.sizes
814
815 # Loop through the list of images, creating a dict of size
816 # changes for each image/part. We end up with something like
817 # {'target' : 'snapper9g45, 'data' : 5, 'u-boot-spl:text' : -4}
818 # which means that U-Boot data increased by 5 bytes and SPL
819 # text decreased by 4.
820 err = {'_target' : target}
821 for image in sizes:
822 if image in base_sizes:
823 base_image = base_sizes[image]
824 # Loop through the text, data, bss parts
825 for part in sorted(sizes[image]):
826 diff = sizes[image][part] - base_image[part]
827 col = None
828 if diff:
829 if image == 'u-boot':
830 name = part
831 else:
832 name = image + ':' + part
833 err[name] = diff
834 arch = board_selected[target].arch
835 if not arch in arch_count:
836 arch_count[arch] = 1
837 else:
838 arch_count[arch] += 1
839 if not sizes:
840 pass # Only add to our list when we have some stats
841 elif not arch in arch_list:
842 arch_list[arch] = [err]
843 else:
844 arch_list[arch].append(err)
845
846 # We now have a list of image size changes sorted by arch
847 # Print out a summary of these
848 for arch, target_list in arch_list.iteritems():
849 # Get total difference for each type
850 totals = {}
851 for result in target_list:
852 total = 0
853 for name, diff in result.iteritems():
854 if name.startswith('_'):
855 continue
856 total += diff
857 if name in totals:
858 totals[name] += diff
859 else:
860 totals[name] = diff
861 result['_total_diff'] = total
862 result['_outcome'] = board_dict[result['_target']]
863
864 count = len(target_list)
865 printed_arch = False
866 for name in sorted(totals):
867 diff = totals[name]
868 if diff:
869 # Display the average difference in this name for this
870 # architecture
871 avg_diff = float(diff) / count
872 color = self.col.RED if avg_diff > 0 else self.col.GREEN
873 msg = ' %s %+1.1f' % (name, avg_diff)
874 if not printed_arch:
Simon Glass4653a882014-09-05 19:00:07 -0600875 Print('%10s: (for %d/%d boards)' % (arch, count,
876 arch_count[arch]), newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000877 printed_arch = True
Simon Glass4653a882014-09-05 19:00:07 -0600878 Print(msg, colour=color, newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000879
880 if printed_arch:
Simon Glass4653a882014-09-05 19:00:07 -0600881 Print()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000882 if show_detail:
883 self.PrintSizeDetail(target_list, show_bloat)
884
885
886 def PrintResultSummary(self, board_selected, board_dict, err_lines,
Simon Glasse30965d2014-08-28 09:43:44 -0600887 err_line_boards, warn_lines, warn_line_boards,
888 show_sizes, show_detail, show_bloat):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000889 """Compare results with the base results and display delta.
890
891 Only boards mentioned in board_selected will be considered. This
892 function is intended to be called repeatedly with the results of
893 each commit. It therefore shows a 'diff' between what it saw in
894 the last call and what it sees now.
895
896 Args:
897 board_selected: Dict containing boards to summarise, keyed by
898 board.target
899 board_dict: Dict containing boards for which we built this
900 commit, keyed by board.target. The value is an Outcome object.
901 err_lines: A list of errors for this commit, or [] if there is
902 none, or we don't want to print errors
Simon Glassed966652014-08-28 09:43:43 -0600903 err_line_boards: Dict keyed by error line, containing a list of
904 the Board objects with that error
Simon Glasse30965d2014-08-28 09:43:44 -0600905 warn_lines: A list of warnings for this commit, or [] if there is
906 none, or we don't want to print errors
907 warn_line_boards: Dict keyed by warning line, containing a list of
908 the Board objects with that warning
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000909 show_sizes: Show image size deltas
910 show_detail: Show detail for each board
911 show_bloat: Show detail for each function
912 """
Simon Glasse30965d2014-08-28 09:43:44 -0600913 def _BoardList(line, line_boards):
Simon Glassed966652014-08-28 09:43:43 -0600914 """Helper function to get a line of boards containing a line
915
916 Args:
917 line: Error line to search for
918 Return:
919 String containing a list of boards with that error line, or
920 '' if the user has not requested such a list
921 """
922 if self._list_error_boards:
923 names = []
Simon Glasse30965d2014-08-28 09:43:44 -0600924 for board in line_boards[line]:
Simon Glassf66153b2014-10-16 01:05:55 -0600925 if not board.target in names:
926 names.append(board.target)
Simon Glassed966652014-08-28 09:43:43 -0600927 names_str = '(%s) ' % ','.join(names)
928 else:
929 names_str = ''
930 return names_str
931
Simon Glasse30965d2014-08-28 09:43:44 -0600932 def _CalcErrorDelta(base_lines, base_line_boards, lines, line_boards,
933 char):
934 better_lines = []
935 worse_lines = []
936 for line in lines:
937 if line not in base_lines:
938 worse_lines.append(char + '+' +
939 _BoardList(line, line_boards) + line)
940 for line in base_lines:
941 if line not in lines:
942 better_lines.append(char + '-' +
943 _BoardList(line, base_line_boards) + line)
944 return better_lines, worse_lines
945
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000946 better = [] # List of boards fixed since last commit
947 worse = [] # List of new broken boards since last commit
948 new = [] # List of boards that didn't exist last time
949 unknown = [] # List of boards that were not built
950
951 for target in board_dict:
952 if target not in board_selected:
953 continue
954
955 # If the board was built last time, add its outcome to a list
956 if target in self._base_board_dict:
957 base_outcome = self._base_board_dict[target].rc
958 outcome = board_dict[target]
959 if outcome.rc == OUTCOME_UNKNOWN:
960 unknown.append(target)
961 elif outcome.rc < base_outcome:
962 better.append(target)
963 elif outcome.rc > base_outcome:
964 worse.append(target)
965 else:
966 new.append(target)
967
968 # Get a list of errors that have appeared, and disappeared
Simon Glasse30965d2014-08-28 09:43:44 -0600969 better_err, worse_err = _CalcErrorDelta(self._base_err_lines,
970 self._base_err_line_boards, err_lines, err_line_boards, '')
971 better_warn, worse_warn = _CalcErrorDelta(self._base_warn_lines,
972 self._base_warn_line_boards, warn_lines, warn_line_boards, 'w')
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000973
974 # Display results by arch
Simon Glasse30965d2014-08-28 09:43:44 -0600975 if (better or worse or unknown or new or worse_err or better_err
976 or worse_warn or better_warn):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000977 arch_list = {}
978 self.AddOutcome(board_selected, arch_list, better, '',
979 self.col.GREEN)
980 self.AddOutcome(board_selected, arch_list, worse, '+',
981 self.col.RED)
982 self.AddOutcome(board_selected, arch_list, new, '*', self.col.BLUE)
983 if self._show_unknown:
984 self.AddOutcome(board_selected, arch_list, unknown, '?',
985 self.col.MAGENTA)
986 for arch, target_list in arch_list.iteritems():
Simon Glass4653a882014-09-05 19:00:07 -0600987 Print('%10s: %s' % (arch, target_list))
Simon Glass28370c12014-08-09 15:33:06 -0600988 self._error_lines += 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000989 if better_err:
Simon Glass4653a882014-09-05 19:00:07 -0600990 Print('\n'.join(better_err), colour=self.col.GREEN)
Simon Glass28370c12014-08-09 15:33:06 -0600991 self._error_lines += 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000992 if worse_err:
Simon Glass4653a882014-09-05 19:00:07 -0600993 Print('\n'.join(worse_err), colour=self.col.RED)
Simon Glass28370c12014-08-09 15:33:06 -0600994 self._error_lines += 1
Simon Glasse30965d2014-08-28 09:43:44 -0600995 if better_warn:
Simon Glass4653a882014-09-05 19:00:07 -0600996 Print('\n'.join(better_warn), colour=self.col.CYAN)
Simon Glasse30965d2014-08-28 09:43:44 -0600997 self._error_lines += 1
998 if worse_warn:
Simon Glass4653a882014-09-05 19:00:07 -0600999 Print('\n'.join(worse_warn), colour=self.col.MAGENTA)
Simon Glasse30965d2014-08-28 09:43:44 -06001000 self._error_lines += 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001001
1002 if show_sizes:
1003 self.PrintSizeSummary(board_selected, board_dict, show_detail,
1004 show_bloat)
1005
1006 # Save our updated information for the next call to this function
1007 self._base_board_dict = board_dict
1008 self._base_err_lines = err_lines
Simon Glasse30965d2014-08-28 09:43:44 -06001009 self._base_warn_lines = warn_lines
1010 self._base_err_line_boards = err_line_boards
1011 self._base_warn_line_boards = warn_line_boards
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001012
1013 # Get a list of boards that did not get built, if needed
1014 not_built = []
1015 for board in board_selected:
1016 if not board in board_dict:
1017 not_built.append(board)
1018 if not_built:
Simon Glass4653a882014-09-05 19:00:07 -06001019 Print("Boards not built (%d): %s" % (len(not_built),
1020 ', '.join(not_built)))
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001021
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001022 def ProduceResultSummary(self, commit_upto, commits, board_selected):
Simon Glasse30965d2014-08-28 09:43:44 -06001023 (board_dict, err_lines, err_line_boards, warn_lines,
1024 warn_line_boards) = self.GetResultSummary(
Simon Glassed966652014-08-28 09:43:43 -06001025 board_selected, commit_upto,
1026 read_func_sizes=self._show_bloat)
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001027 if commits:
1028 msg = '%02d: %s' % (commit_upto + 1,
1029 commits[commit_upto].subject)
Simon Glass4653a882014-09-05 19:00:07 -06001030 Print(msg, colour=self.col.BLUE)
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001031 self.PrintResultSummary(board_selected, board_dict,
Simon Glassed966652014-08-28 09:43:43 -06001032 err_lines if self._show_errors else [], err_line_boards,
Simon Glasse30965d2014-08-28 09:43:44 -06001033 warn_lines if self._show_errors else [], warn_line_boards,
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001034 self._show_sizes, self._show_detail, self._show_bloat)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001035
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001036 def ShowSummary(self, commits, board_selected):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001037 """Show a build summary for U-Boot for a given board list.
1038
1039 Reset the result summary, then repeatedly call GetResultSummary on
1040 each commit's results, then display the differences we see.
1041
1042 Args:
1043 commit: Commit objects to summarise
1044 board_selected: Dict containing boards to summarise
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001045 """
Simon Glassfea58582014-08-09 15:32:59 -06001046 self.commit_count = len(commits) if commits else 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001047 self.commits = commits
1048 self.ResetResultSummary(board_selected)
Simon Glass28370c12014-08-09 15:33:06 -06001049 self._error_lines = 0
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001050
1051 for commit_upto in range(0, self.commit_count, self._step):
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001052 self.ProduceResultSummary(commit_upto, commits, board_selected)
Simon Glass28370c12014-08-09 15:33:06 -06001053 if not self._error_lines:
Simon Glass4653a882014-09-05 19:00:07 -06001054 Print('(no errors to report)', colour=self.col.GREEN)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001055
1056
1057 def SetupBuild(self, board_selected, commits):
1058 """Set up ready to start a build.
1059
1060 Args:
1061 board_selected: Selected boards to build
1062 commits: Selected commits to build
1063 """
1064 # First work out how many commits we will build
Simon Glassfea58582014-08-09 15:32:59 -06001065 count = (self.commit_count + self._step - 1) / self._step
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001066 self.count = len(board_selected) * count
1067 self.upto = self.warned = self.fail = 0
1068 self._timestamps = collections.deque()
1069
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001070 def GetThreadDir(self, thread_num):
1071 """Get the directory path to the working dir for a thread.
1072
1073 Args:
1074 thread_num: Number of thread to check.
1075 """
1076 return os.path.join(self._working_dir, '%02d' % thread_num)
1077
Simon Glassfea58582014-08-09 15:32:59 -06001078 def _PrepareThread(self, thread_num, setup_git):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001079 """Prepare the working directory for a thread.
1080
1081 This clones or fetches the repo into the thread's work directory.
1082
1083 Args:
1084 thread_num: Thread number (0, 1, ...)
Simon Glassfea58582014-08-09 15:32:59 -06001085 setup_git: True to set up a git repo clone
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001086 """
1087 thread_dir = self.GetThreadDir(thread_num)
Simon Glass190064b2014-08-09 15:33:00 -06001088 builderthread.Mkdir(thread_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001089 git_dir = os.path.join(thread_dir, '.git')
1090
1091 # Clone the repo if it doesn't already exist
1092 # TODO(sjg@chromium): Perhaps some git hackery to symlink instead, so
1093 # we have a private index but uses the origin repo's contents?
Simon Glassfea58582014-08-09 15:32:59 -06001094 if setup_git and self.git_dir:
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001095 src_dir = os.path.abspath(self.git_dir)
1096 if os.path.exists(git_dir):
1097 gitutil.Fetch(git_dir, thread_dir)
1098 else:
Simon Glass4653a882014-09-05 19:00:07 -06001099 Print('Cloning repo for thread %d' % thread_num)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001100 gitutil.Clone(src_dir, thread_dir)
1101
Simon Glassfea58582014-08-09 15:32:59 -06001102 def _PrepareWorkingSpace(self, max_threads, setup_git):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001103 """Prepare the working directory for use.
1104
1105 Set up the git repo for each thread.
1106
1107 Args:
1108 max_threads: Maximum number of threads we expect to need.
Simon Glassfea58582014-08-09 15:32:59 -06001109 setup_git: True to set up a git repo clone
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001110 """
Simon Glass190064b2014-08-09 15:33:00 -06001111 builderthread.Mkdir(self._working_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001112 for thread in range(max_threads):
Simon Glassfea58582014-08-09 15:32:59 -06001113 self._PrepareThread(thread, setup_git)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001114
1115 def _PrepareOutputSpace(self):
1116 """Get the output directories ready to receive files.
1117
1118 We delete any output directories which look like ones we need to
1119 create. Having left over directories is confusing when the user wants
1120 to check the output manually.
1121 """
Simon Glass1a915672014-12-01 17:33:53 -07001122 if not self.commits:
1123 return
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001124 dir_list = []
1125 for commit_upto in range(self.commit_count):
1126 dir_list.append(self._GetOutputDir(commit_upto))
1127
1128 for dirname in glob.glob(os.path.join(self.base_dir, '*')):
1129 if dirname not in dir_list:
1130 shutil.rmtree(dirname)
1131
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001132 def BuildBoards(self, commits, board_selected, keep_outputs, verbose):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001133 """Build all commits for a list of boards
1134
1135 Args:
1136 commits: List of commits to be build, each a Commit object
1137 boards_selected: Dict of selected boards, key is target name,
1138 value is Board object
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001139 keep_outputs: True to save build output files
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001140 verbose: Display build results as they are completed
Simon Glass2c3deb92014-08-28 09:43:39 -06001141 Returns:
1142 Tuple containing:
1143 - number of boards that failed to build
1144 - number of boards that issued warnings
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001145 """
Simon Glassfea58582014-08-09 15:32:59 -06001146 self.commit_count = len(commits) if commits else 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001147 self.commits = commits
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001148 self._verbose = verbose
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001149
1150 self.ResetResultSummary(board_selected)
Thierry Redingf3d015c2014-08-19 10:22:39 +02001151 builderthread.Mkdir(self.base_dir, parents = True)
Simon Glassfea58582014-08-09 15:32:59 -06001152 self._PrepareWorkingSpace(min(self.num_threads, len(board_selected)),
1153 commits is not None)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001154 self._PrepareOutputSpace()
1155 self.SetupBuild(board_selected, commits)
1156 self.ProcessResult(None)
1157
1158 # Create jobs to build all commits for each board
1159 for brd in board_selected.itervalues():
Simon Glass190064b2014-08-09 15:33:00 -06001160 job = builderthread.BuilderJob()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001161 job.board = brd
1162 job.commits = commits
1163 job.keep_outputs = keep_outputs
1164 job.step = self._step
1165 self.queue.put(job)
1166
1167 # Wait until all jobs are started
1168 self.queue.join()
1169
1170 # Wait until we have processed all output
1171 self.out_queue.join()
Simon Glass4653a882014-09-05 19:00:07 -06001172 Print()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001173 self.ClearLine(0)
Simon Glass2c3deb92014-08-28 09:43:39 -06001174 return (self.fail, self.warned)