blob: 54f3292208a9f0ccf64a71758b2040f912880fce [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,
Simon Glassd2ce6582014-12-01 17:34:07 -0700178 no_subdirs=False, full_path=False, verbose_build=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
Simon Glassbb1501f2014-12-01 17:34:00 -0700192 no_subdirs: Don't create subdirectories when building current
193 source for a single board
194 full_path: Return the full path in CROSS_COMPILE and don't set
195 PATH
Simon Glassd2ce6582014-12-01 17:34:07 -0700196 verbose_build: Run build with V=1 and don't use 'make -s'
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000197 """
198 self.toolchains = toolchains
199 self.base_dir = base_dir
200 self._working_dir = os.path.join(base_dir, '.bm-work')
201 self.threads = []
202 self.active = True
203 self.do_make = self.Make
Masahiro Yamada99796922014-07-22 11:19:09 +0900204 self.gnu_make = gnu_make
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000205 self.checkout = checkout
206 self.num_threads = num_threads
207 self.num_jobs = num_jobs
208 self.already_done = 0
209 self.force_build = False
210 self.git_dir = git_dir
211 self._show_unknown = show_unknown
212 self._timestamp_count = 10
213 self._build_period_us = None
214 self._complete_delay = None
215 self._next_delay_update = datetime.now()
216 self.force_config_on_failure = True
Simon Glass4266dc22014-07-13 12:22:31 -0600217 self.force_build_failures = False
Simon Glass97e91522014-07-14 17:51:02 -0600218 self.force_reconfig = False
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000219 self._step = step
Simon Glass189a4962014-07-14 17:51:03 -0600220 self.in_tree = False
Simon Glass28370c12014-08-09 15:33:06 -0600221 self._error_lines = 0
Simon Glass5971ab52014-12-01 17:33:55 -0700222 self.no_subdirs = no_subdirs
Simon Glassbb1501f2014-12-01 17:34:00 -0700223 self.full_path = full_path
Simon Glassd2ce6582014-12-01 17:34:07 -0700224 self.verbose_build = verbose_build
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000225
226 self.col = terminal.Color()
227
Simon Glasse30965d2014-08-28 09:43:44 -0600228 self._re_function = re.compile('(.*): In function.*')
229 self._re_files = re.compile('In file included from.*')
230 self._re_warning = re.compile('(.*):(\d*):(\d*): warning: .*')
231 self._re_note = re.compile('(.*):(\d*):(\d*): note: this is the location of the previous.*')
232
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000233 self.queue = Queue.Queue()
234 self.out_queue = Queue.Queue()
235 for i in range(self.num_threads):
Simon Glass190064b2014-08-09 15:33:00 -0600236 t = builderthread.BuilderThread(self, i)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000237 t.setDaemon(True)
238 t.start()
239 self.threads.append(t)
240
241 self.last_line_len = 0
Simon Glass190064b2014-08-09 15:33:00 -0600242 t = builderthread.ResultThread(self)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000243 t.setDaemon(True)
244 t.start()
245 self.threads.append(t)
246
247 ignore_lines = ['(make.*Waiting for unfinished)', '(Segmentation fault)']
248 self.re_make_err = re.compile('|'.join(ignore_lines))
249
250 def __del__(self):
251 """Get rid of all threads created by the builder"""
252 for t in self.threads:
253 del t
254
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600255 def SetDisplayOptions(self, show_errors=False, show_sizes=False,
Simon Glassed966652014-08-28 09:43:43 -0600256 show_detail=False, show_bloat=False,
257 list_error_boards=False):
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600258 """Setup display options for the builder.
259
260 show_errors: True to show summarised error/warning info
261 show_sizes: Show size deltas
262 show_detail: Show detail for each board
263 show_bloat: Show detail for each function
Simon Glassed966652014-08-28 09:43:43 -0600264 list_error_boards: Show the boards which caused each error/warning
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600265 """
266 self._show_errors = show_errors
267 self._show_sizes = show_sizes
268 self._show_detail = show_detail
269 self._show_bloat = show_bloat
Simon Glassed966652014-08-28 09:43:43 -0600270 self._list_error_boards = list_error_boards
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600271
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000272 def _AddTimestamp(self):
273 """Add a new timestamp to the list and record the build period.
274
275 The build period is the length of time taken to perform a single
276 build (one board, one commit).
277 """
278 now = datetime.now()
279 self._timestamps.append(now)
280 count = len(self._timestamps)
281 delta = self._timestamps[-1] - self._timestamps[0]
282 seconds = delta.total_seconds()
283
284 # If we have enough data, estimate build period (time taken for a
285 # single build) and therefore completion time.
286 if count > 1 and self._next_delay_update < now:
287 self._next_delay_update = now + timedelta(seconds=2)
288 if seconds > 0:
289 self._build_period = float(seconds) / count
290 todo = self.count - self.upto
291 self._complete_delay = timedelta(microseconds=
292 self._build_period * todo * 1000000)
293 # Round it
294 self._complete_delay -= timedelta(
295 microseconds=self._complete_delay.microseconds)
296
297 if seconds > 60:
298 self._timestamps.popleft()
299 count -= 1
300
301 def ClearLine(self, length):
302 """Clear any characters on the current line
303
304 Make way for a new line of length 'length', by outputting enough
305 spaces to clear out the old line. Then remember the new length for
306 next time.
307
308 Args:
309 length: Length of new line, in characters
310 """
311 if length < self.last_line_len:
Simon Glass4653a882014-09-05 19:00:07 -0600312 Print(' ' * (self.last_line_len - length), newline=False)
313 Print('\r', newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000314 self.last_line_len = length
315 sys.stdout.flush()
316
317 def SelectCommit(self, commit, checkout=True):
318 """Checkout the selected commit for this build
319 """
320 self.commit = commit
321 if checkout and self.checkout:
322 gitutil.Checkout(commit.hash)
323
324 def Make(self, commit, brd, stage, cwd, *args, **kwargs):
325 """Run make
326
327 Args:
328 commit: Commit object that is being built
329 brd: Board object that is being built
Roger Meierfd18a892014-08-20 22:10:29 +0200330 stage: Stage that we are at (mrproper, config, build)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000331 cwd: Directory where make should be run
332 args: Arguments to pass to make
333 kwargs: Arguments to pass to command.RunPipe()
334 """
Masahiro Yamada99796922014-07-22 11:19:09 +0900335 cmd = [self.gnu_make] + list(args)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000336 result = command.RunPipe([cmd], capture=True, capture_stderr=True,
337 cwd=cwd, raise_on_error=False, **kwargs)
338 return result
339
340 def ProcessResult(self, result):
341 """Process the result of a build, showing progress information
342
343 Args:
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600344 result: A CommandResult object, which indicates the result for
345 a single build
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000346 """
347 col = terminal.Color()
348 if result:
349 target = result.brd.target
350
351 if result.return_code < 0:
352 self.active = False
353 command.StopAll()
354 return
355
356 self.upto += 1
357 if result.return_code != 0:
358 self.fail += 1
359 elif result.stderr:
360 self.warned += 1
361 if result.already_done:
362 self.already_done += 1
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600363 if self._verbose:
Simon Glass4653a882014-09-05 19:00:07 -0600364 Print('\r', newline=False)
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600365 self.ClearLine(0)
366 boards_selected = {target : result.brd}
367 self.ResetResultSummary(boards_selected)
368 self.ProduceResultSummary(result.commit_upto, self.commits,
369 boards_selected)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000370 else:
371 target = '(starting)'
372
373 # Display separate counts for ok, warned and fail
374 ok = self.upto - self.warned - self.fail
375 line = '\r' + self.col.Color(self.col.GREEN, '%5d' % ok)
376 line += self.col.Color(self.col.YELLOW, '%5d' % self.warned)
377 line += self.col.Color(self.col.RED, '%5d' % self.fail)
378
379 name = ' /%-5d ' % self.count
380
381 # Add our current completion time estimate
382 self._AddTimestamp()
383 if self._complete_delay:
384 name += '%s : ' % self._complete_delay
385 # When building all boards for a commit, we can print a commit
386 # progress message.
387 if result and result.commit_upto is None:
388 name += 'commit %2d/%-3d' % (self.commit_upto + 1,
389 self.commit_count)
390
391 name += target
Simon Glass4653a882014-09-05 19:00:07 -0600392 Print(line + name, newline=False)
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600393 length = 14 + len(name)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000394 self.ClearLine(length)
395
396 def _GetOutputDir(self, commit_upto):
397 """Get the name of the output directory for a commit number
398
399 The output directory is typically .../<branch>/<commit>.
400
401 Args:
402 commit_upto: Commit number to use (0..self.count-1)
403 """
Simon Glass5971ab52014-12-01 17:33:55 -0700404 commit_dir = None
Simon Glassfea58582014-08-09 15:32:59 -0600405 if self.commits:
406 commit = self.commits[commit_upto]
407 subject = commit.subject.translate(trans_valid_chars)
408 commit_dir = ('%02d_of_%02d_g%s_%s' % (commit_upto + 1,
409 self.commit_count, commit.hash, subject[:20]))
Simon Glass5971ab52014-12-01 17:33:55 -0700410 elif not self.no_subdirs:
Simon Glassfea58582014-08-09 15:32:59 -0600411 commit_dir = 'current'
Simon Glass5971ab52014-12-01 17:33:55 -0700412 if not commit_dir:
413 return self.base_dir
414 return os.path.join(self.base_dir, commit_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000415
416 def GetBuildDir(self, commit_upto, target):
417 """Get the name of the build directory for a commit number
418
419 The build directory is typically .../<branch>/<commit>/<target>.
420
421 Args:
422 commit_upto: Commit number to use (0..self.count-1)
423 target: Target name
424 """
425 output_dir = self._GetOutputDir(commit_upto)
426 return os.path.join(output_dir, target)
427
428 def GetDoneFile(self, commit_upto, target):
429 """Get the name of the done file for a commit number
430
431 Args:
432 commit_upto: Commit number to use (0..self.count-1)
433 target: Target name
434 """
435 return os.path.join(self.GetBuildDir(commit_upto, target), 'done')
436
437 def GetSizesFile(self, commit_upto, target):
438 """Get the name of the sizes file for a commit number
439
440 Args:
441 commit_upto: Commit number to use (0..self.count-1)
442 target: Target name
443 """
444 return os.path.join(self.GetBuildDir(commit_upto, target), 'sizes')
445
446 def GetFuncSizesFile(self, commit_upto, target, elf_fname):
447 """Get the name of the funcsizes file for a commit number and ELF file
448
449 Args:
450 commit_upto: Commit number to use (0..self.count-1)
451 target: Target name
452 elf_fname: Filename of elf image
453 """
454 return os.path.join(self.GetBuildDir(commit_upto, target),
455 '%s.sizes' % elf_fname.replace('/', '-'))
456
457 def GetObjdumpFile(self, commit_upto, target, elf_fname):
458 """Get the name of the objdump file for a commit number and ELF file
459
460 Args:
461 commit_upto: Commit number to use (0..self.count-1)
462 target: Target name
463 elf_fname: Filename of elf image
464 """
465 return os.path.join(self.GetBuildDir(commit_upto, target),
466 '%s.objdump' % elf_fname.replace('/', '-'))
467
468 def GetErrFile(self, commit_upto, target):
469 """Get the name of the err file for a commit number
470
471 Args:
472 commit_upto: Commit number to use (0..self.count-1)
473 target: Target name
474 """
475 output_dir = self.GetBuildDir(commit_upto, target)
476 return os.path.join(output_dir, 'err')
477
478 def FilterErrors(self, lines):
479 """Filter out errors in which we have no interest
480
481 We should probably use map().
482
483 Args:
484 lines: List of error lines, each a string
485 Returns:
486 New list with only interesting lines included
487 """
488 out_lines = []
489 for line in lines:
490 if not self.re_make_err.search(line):
491 out_lines.append(line)
492 return out_lines
493
494 def ReadFuncSizes(self, fname, fd):
495 """Read function sizes from the output of 'nm'
496
497 Args:
498 fd: File containing data to read
499 fname: Filename we are reading from (just for errors)
500
501 Returns:
502 Dictionary containing size of each function in bytes, indexed by
503 function name.
504 """
505 sym = {}
506 for line in fd.readlines():
507 try:
508 size, type, name = line[:-1].split()
509 except:
Simon Glass4653a882014-09-05 19:00:07 -0600510 Print("Invalid line in file '%s': '%s'" % (fname, line[:-1]))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000511 continue
512 if type in 'tTdDbB':
513 # function names begin with '.' on 64-bit powerpc
514 if '.' in name[1:]:
515 name = 'static.' + name.split('.')[0]
516 sym[name] = sym.get(name, 0) + int(size, 16)
517 return sym
518
519 def GetBuildOutcome(self, commit_upto, target, read_func_sizes):
520 """Work out the outcome of a build.
521
522 Args:
523 commit_upto: Commit number to check (0..n-1)
524 target: Target board to check
525 read_func_sizes: True to read function size information
526
527 Returns:
528 Outcome object
529 """
530 done_file = self.GetDoneFile(commit_upto, target)
531 sizes_file = self.GetSizesFile(commit_upto, target)
532 sizes = {}
533 func_sizes = {}
534 if os.path.exists(done_file):
535 with open(done_file, 'r') as fd:
536 return_code = int(fd.readline())
537 err_lines = []
538 err_file = self.GetErrFile(commit_upto, target)
539 if os.path.exists(err_file):
540 with open(err_file, 'r') as fd:
541 err_lines = self.FilterErrors(fd.readlines())
542
543 # Decide whether the build was ok, failed or created warnings
544 if return_code:
545 rc = OUTCOME_ERROR
546 elif len(err_lines):
547 rc = OUTCOME_WARNING
548 else:
549 rc = OUTCOME_OK
550
551 # Convert size information to our simple format
552 if os.path.exists(sizes_file):
553 with open(sizes_file, 'r') as fd:
554 for line in fd.readlines():
555 values = line.split()
556 rodata = 0
557 if len(values) > 6:
558 rodata = int(values[6], 16)
559 size_dict = {
560 'all' : int(values[0]) + int(values[1]) +
561 int(values[2]),
562 'text' : int(values[0]) - rodata,
563 'data' : int(values[1]),
564 'bss' : int(values[2]),
565 'rodata' : rodata,
566 }
567 sizes[values[5]] = size_dict
568
569 if read_func_sizes:
570 pattern = self.GetFuncSizesFile(commit_upto, target, '*')
571 for fname in glob.glob(pattern):
572 with open(fname, 'r') as fd:
573 dict_name = os.path.basename(fname).replace('.sizes',
574 '')
575 func_sizes[dict_name] = self.ReadFuncSizes(fname, fd)
576
577 return Builder.Outcome(rc, err_lines, sizes, func_sizes)
578
579 return Builder.Outcome(OUTCOME_UNKNOWN, [], {}, {})
580
581 def GetResultSummary(self, boards_selected, commit_upto, read_func_sizes):
582 """Calculate a summary of the results of building a commit.
583
584 Args:
585 board_selected: Dict containing boards to summarise
586 commit_upto: Commit number to summarize (0..self.count-1)
587 read_func_sizes: True to read function size information
588
589 Returns:
590 Tuple:
591 Dict containing boards which passed building this commit.
592 keyed by board.target
Simon Glasse30965d2014-08-28 09:43:44 -0600593 List containing a summary of error lines
Simon Glassed966652014-08-28 09:43:43 -0600594 Dict keyed by error line, containing a list of the Board
595 objects with that error
Simon Glasse30965d2014-08-28 09:43:44 -0600596 List containing a summary of warning lines
597 Dict keyed by error line, containing a list of the Board
598 objects with that warning
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000599 """
Simon Glasse30965d2014-08-28 09:43:44 -0600600 def AddLine(lines_summary, lines_boards, line, board):
601 line = line.rstrip()
602 if line in lines_boards:
603 lines_boards[line].append(board)
604 else:
605 lines_boards[line] = [board]
606 lines_summary.append(line)
607
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000608 board_dict = {}
609 err_lines_summary = []
Simon Glassed966652014-08-28 09:43:43 -0600610 err_lines_boards = {}
Simon Glasse30965d2014-08-28 09:43:44 -0600611 warn_lines_summary = []
612 warn_lines_boards = {}
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000613
614 for board in boards_selected.itervalues():
615 outcome = self.GetBuildOutcome(commit_upto, board.target,
616 read_func_sizes)
617 board_dict[board.target] = outcome
Simon Glasse30965d2014-08-28 09:43:44 -0600618 last_func = None
619 last_was_warning = False
620 for line in outcome.err_lines:
621 if line:
622 if (self._re_function.match(line) or
623 self._re_files.match(line)):
624 last_func = line
Simon Glassed966652014-08-28 09:43:43 -0600625 else:
Simon Glasse30965d2014-08-28 09:43:44 -0600626 is_warning = self._re_warning.match(line)
627 is_note = self._re_note.match(line)
628 if is_warning or (last_was_warning and is_note):
629 if last_func:
630 AddLine(warn_lines_summary, warn_lines_boards,
631 last_func, board)
632 AddLine(warn_lines_summary, warn_lines_boards,
633 line, board)
634 else:
635 if last_func:
636 AddLine(err_lines_summary, err_lines_boards,
637 last_func, board)
638 AddLine(err_lines_summary, err_lines_boards,
639 line, board)
640 last_was_warning = is_warning
641 last_func = None
642 return (board_dict, err_lines_summary, err_lines_boards,
643 warn_lines_summary, warn_lines_boards)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000644
645 def AddOutcome(self, board_dict, arch_list, changes, char, color):
646 """Add an output to our list of outcomes for each architecture
647
648 This simple function adds failing boards (changes) to the
649 relevant architecture string, so we can print the results out
650 sorted by architecture.
651
652 Args:
653 board_dict: Dict containing all boards
654 arch_list: Dict keyed by arch name. Value is a string containing
655 a list of board names which failed for that arch.
656 changes: List of boards to add to arch_list
657 color: terminal.Colour object
658 """
659 done_arch = {}
660 for target in changes:
661 if target in board_dict:
662 arch = board_dict[target].arch
663 else:
664 arch = 'unknown'
665 str = self.col.Color(color, ' ' + target)
666 if not arch in done_arch:
Simon Glass63c619e2015-02-05 22:06:11 -0700667 str = ' %s %s' % (self.col.Color(color, char), str)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000668 done_arch[arch] = True
669 if not arch in arch_list:
670 arch_list[arch] = str
671 else:
672 arch_list[arch] += str
673
674
675 def ColourNum(self, num):
676 color = self.col.RED if num > 0 else self.col.GREEN
677 if num == 0:
678 return '0'
679 return self.col.Color(color, str(num))
680
681 def ResetResultSummary(self, board_selected):
682 """Reset the results summary ready for use.
683
684 Set up the base board list to be all those selected, and set the
685 error lines to empty.
686
687 Following this, calls to PrintResultSummary() will use this
688 information to work out what has changed.
689
690 Args:
691 board_selected: Dict containing boards to summarise, keyed by
692 board.target
693 """
694 self._base_board_dict = {}
695 for board in board_selected:
696 self._base_board_dict[board] = Builder.Outcome(0, [], [], {})
697 self._base_err_lines = []
Simon Glasse30965d2014-08-28 09:43:44 -0600698 self._base_warn_lines = []
699 self._base_err_line_boards = {}
700 self._base_warn_line_boards = {}
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000701
702 def PrintFuncSizeDetail(self, fname, old, new):
703 grow, shrink, add, remove, up, down = 0, 0, 0, 0, 0, 0
704 delta, common = [], {}
705
706 for a in old:
707 if a in new:
708 common[a] = 1
709
710 for name in old:
711 if name not in common:
712 remove += 1
713 down += old[name]
714 delta.append([-old[name], name])
715
716 for name in new:
717 if name not in common:
718 add += 1
719 up += new[name]
720 delta.append([new[name], name])
721
722 for name in common:
723 diff = new.get(name, 0) - old.get(name, 0)
724 if diff > 0:
725 grow, up = grow + 1, up + diff
726 elif diff < 0:
727 shrink, down = shrink + 1, down - diff
728 delta.append([diff, name])
729
730 delta.sort()
731 delta.reverse()
732
733 args = [add, -remove, grow, -shrink, up, -down, up - down]
734 if max(args) == 0:
735 return
736 args = [self.ColourNum(x) for x in args]
737 indent = ' ' * 15
Simon Glass4653a882014-09-05 19:00:07 -0600738 Print('%s%s: add: %s/%s, grow: %s/%s bytes: %s/%s (%s)' %
739 tuple([indent, self.col.Color(self.col.YELLOW, fname)] + args))
740 Print('%s %-38s %7s %7s %+7s' % (indent, 'function', 'old', 'new',
741 'delta'))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000742 for diff, name in delta:
743 if diff:
744 color = self.col.RED if diff > 0 else self.col.GREEN
745 msg = '%s %-38s %7s %7s %+7d' % (indent, name,
746 old.get(name, '-'), new.get(name,'-'), diff)
Simon Glass4653a882014-09-05 19:00:07 -0600747 Print(msg, colour=color)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000748
749
750 def PrintSizeDetail(self, target_list, show_bloat):
751 """Show details size information for each board
752
753 Args:
754 target_list: List of targets, each a dict containing:
755 'target': Target name
756 'total_diff': Total difference in bytes across all areas
757 <part_name>: Difference for that part
758 show_bloat: Show detail for each function
759 """
760 targets_by_diff = sorted(target_list, reverse=True,
761 key=lambda x: x['_total_diff'])
762 for result in targets_by_diff:
763 printed_target = False
764 for name in sorted(result):
765 diff = result[name]
766 if name.startswith('_'):
767 continue
768 if diff != 0:
769 color = self.col.RED if diff > 0 else self.col.GREEN
770 msg = ' %s %+d' % (name, diff)
771 if not printed_target:
Simon Glass4653a882014-09-05 19:00:07 -0600772 Print('%10s %-15s:' % ('', result['_target']),
773 newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000774 printed_target = True
Simon Glass4653a882014-09-05 19:00:07 -0600775 Print(msg, colour=color, newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000776 if printed_target:
Simon Glass4653a882014-09-05 19:00:07 -0600777 Print()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000778 if show_bloat:
779 target = result['_target']
780 outcome = result['_outcome']
781 base_outcome = self._base_board_dict[target]
782 for fname in outcome.func_sizes:
783 self.PrintFuncSizeDetail(fname,
784 base_outcome.func_sizes[fname],
785 outcome.func_sizes[fname])
786
787
788 def PrintSizeSummary(self, board_selected, board_dict, show_detail,
789 show_bloat):
790 """Print a summary of image sizes broken down by section.
791
792 The summary takes the form of one line per architecture. The
793 line contains deltas for each of the sections (+ means the section
794 got bigger, - means smaller). The nunmbers are the average number
795 of bytes that a board in this section increased by.
796
797 For example:
798 powerpc: (622 boards) text -0.0
799 arm: (285 boards) text -0.0
800 nds32: (3 boards) text -8.0
801
802 Args:
803 board_selected: Dict containing boards to summarise, keyed by
804 board.target
805 board_dict: Dict containing boards for which we built this
806 commit, keyed by board.target. The value is an Outcome object.
807 show_detail: Show detail for each board
808 show_bloat: Show detail for each function
809 """
810 arch_list = {}
811 arch_count = {}
812
813 # Calculate changes in size for different image parts
814 # The previous sizes are in Board.sizes, for each board
815 for target in board_dict:
816 if target not in board_selected:
817 continue
818 base_sizes = self._base_board_dict[target].sizes
819 outcome = board_dict[target]
820 sizes = outcome.sizes
821
822 # Loop through the list of images, creating a dict of size
823 # changes for each image/part. We end up with something like
824 # {'target' : 'snapper9g45, 'data' : 5, 'u-boot-spl:text' : -4}
825 # which means that U-Boot data increased by 5 bytes and SPL
826 # text decreased by 4.
827 err = {'_target' : target}
828 for image in sizes:
829 if image in base_sizes:
830 base_image = base_sizes[image]
831 # Loop through the text, data, bss parts
832 for part in sorted(sizes[image]):
833 diff = sizes[image][part] - base_image[part]
834 col = None
835 if diff:
836 if image == 'u-boot':
837 name = part
838 else:
839 name = image + ':' + part
840 err[name] = diff
841 arch = board_selected[target].arch
842 if not arch in arch_count:
843 arch_count[arch] = 1
844 else:
845 arch_count[arch] += 1
846 if not sizes:
847 pass # Only add to our list when we have some stats
848 elif not arch in arch_list:
849 arch_list[arch] = [err]
850 else:
851 arch_list[arch].append(err)
852
853 # We now have a list of image size changes sorted by arch
854 # Print out a summary of these
855 for arch, target_list in arch_list.iteritems():
856 # Get total difference for each type
857 totals = {}
858 for result in target_list:
859 total = 0
860 for name, diff in result.iteritems():
861 if name.startswith('_'):
862 continue
863 total += diff
864 if name in totals:
865 totals[name] += diff
866 else:
867 totals[name] = diff
868 result['_total_diff'] = total
869 result['_outcome'] = board_dict[result['_target']]
870
871 count = len(target_list)
872 printed_arch = False
873 for name in sorted(totals):
874 diff = totals[name]
875 if diff:
876 # Display the average difference in this name for this
877 # architecture
878 avg_diff = float(diff) / count
879 color = self.col.RED if avg_diff > 0 else self.col.GREEN
880 msg = ' %s %+1.1f' % (name, avg_diff)
881 if not printed_arch:
Simon Glass4653a882014-09-05 19:00:07 -0600882 Print('%10s: (for %d/%d boards)' % (arch, count,
883 arch_count[arch]), newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000884 printed_arch = True
Simon Glass4653a882014-09-05 19:00:07 -0600885 Print(msg, colour=color, newline=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000886
887 if printed_arch:
Simon Glass4653a882014-09-05 19:00:07 -0600888 Print()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000889 if show_detail:
890 self.PrintSizeDetail(target_list, show_bloat)
891
892
893 def PrintResultSummary(self, board_selected, board_dict, err_lines,
Simon Glasse30965d2014-08-28 09:43:44 -0600894 err_line_boards, warn_lines, warn_line_boards,
895 show_sizes, show_detail, show_bloat):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000896 """Compare results with the base results and display delta.
897
898 Only boards mentioned in board_selected will be considered. This
899 function is intended to be called repeatedly with the results of
900 each commit. It therefore shows a 'diff' between what it saw in
901 the last call and what it sees now.
902
903 Args:
904 board_selected: Dict containing boards to summarise, keyed by
905 board.target
906 board_dict: Dict containing boards for which we built this
907 commit, keyed by board.target. The value is an Outcome object.
908 err_lines: A list of errors for this commit, or [] if there is
909 none, or we don't want to print errors
Simon Glassed966652014-08-28 09:43:43 -0600910 err_line_boards: Dict keyed by error line, containing a list of
911 the Board objects with that error
Simon Glasse30965d2014-08-28 09:43:44 -0600912 warn_lines: A list of warnings for this commit, or [] if there is
913 none, or we don't want to print errors
914 warn_line_boards: Dict keyed by warning line, containing a list of
915 the Board objects with that warning
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000916 show_sizes: Show image size deltas
917 show_detail: Show detail for each board
918 show_bloat: Show detail for each function
919 """
Simon Glasse30965d2014-08-28 09:43:44 -0600920 def _BoardList(line, line_boards):
Simon Glassed966652014-08-28 09:43:43 -0600921 """Helper function to get a line of boards containing a line
922
923 Args:
924 line: Error line to search for
925 Return:
926 String containing a list of boards with that error line, or
927 '' if the user has not requested such a list
928 """
929 if self._list_error_boards:
930 names = []
Simon Glasse30965d2014-08-28 09:43:44 -0600931 for board in line_boards[line]:
Simon Glassf66153b2014-10-16 01:05:55 -0600932 if not board.target in names:
933 names.append(board.target)
Simon Glassed966652014-08-28 09:43:43 -0600934 names_str = '(%s) ' % ','.join(names)
935 else:
936 names_str = ''
937 return names_str
938
Simon Glasse30965d2014-08-28 09:43:44 -0600939 def _CalcErrorDelta(base_lines, base_line_boards, lines, line_boards,
940 char):
941 better_lines = []
942 worse_lines = []
943 for line in lines:
944 if line not in base_lines:
945 worse_lines.append(char + '+' +
946 _BoardList(line, line_boards) + line)
947 for line in base_lines:
948 if line not in lines:
949 better_lines.append(char + '-' +
950 _BoardList(line, base_line_boards) + line)
951 return better_lines, worse_lines
952
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000953 better = [] # List of boards fixed since last commit
954 worse = [] # List of new broken boards since last commit
955 new = [] # List of boards that didn't exist last time
956 unknown = [] # List of boards that were not built
957
958 for target in board_dict:
959 if target not in board_selected:
960 continue
961
962 # If the board was built last time, add its outcome to a list
963 if target in self._base_board_dict:
964 base_outcome = self._base_board_dict[target].rc
965 outcome = board_dict[target]
966 if outcome.rc == OUTCOME_UNKNOWN:
967 unknown.append(target)
968 elif outcome.rc < base_outcome:
969 better.append(target)
970 elif outcome.rc > base_outcome:
971 worse.append(target)
972 else:
973 new.append(target)
974
975 # Get a list of errors that have appeared, and disappeared
Simon Glasse30965d2014-08-28 09:43:44 -0600976 better_err, worse_err = _CalcErrorDelta(self._base_err_lines,
977 self._base_err_line_boards, err_lines, err_line_boards, '')
978 better_warn, worse_warn = _CalcErrorDelta(self._base_warn_lines,
979 self._base_warn_line_boards, warn_lines, warn_line_boards, 'w')
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000980
981 # Display results by arch
Simon Glasse30965d2014-08-28 09:43:44 -0600982 if (better or worse or unknown or new or worse_err or better_err
983 or worse_warn or better_warn):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000984 arch_list = {}
985 self.AddOutcome(board_selected, arch_list, better, '',
986 self.col.GREEN)
987 self.AddOutcome(board_selected, arch_list, worse, '+',
988 self.col.RED)
989 self.AddOutcome(board_selected, arch_list, new, '*', self.col.BLUE)
990 if self._show_unknown:
991 self.AddOutcome(board_selected, arch_list, unknown, '?',
992 self.col.MAGENTA)
993 for arch, target_list in arch_list.iteritems():
Simon Glass4653a882014-09-05 19:00:07 -0600994 Print('%10s: %s' % (arch, target_list))
Simon Glass28370c12014-08-09 15:33:06 -0600995 self._error_lines += 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000996 if better_err:
Simon Glass4653a882014-09-05 19:00:07 -0600997 Print('\n'.join(better_err), colour=self.col.GREEN)
Simon Glass28370c12014-08-09 15:33:06 -0600998 self._error_lines += 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000999 if worse_err:
Simon Glass4653a882014-09-05 19:00:07 -06001000 Print('\n'.join(worse_err), colour=self.col.RED)
Simon Glass28370c12014-08-09 15:33:06 -06001001 self._error_lines += 1
Simon Glasse30965d2014-08-28 09:43:44 -06001002 if better_warn:
Simon Glass4653a882014-09-05 19:00:07 -06001003 Print('\n'.join(better_warn), colour=self.col.CYAN)
Simon Glasse30965d2014-08-28 09:43:44 -06001004 self._error_lines += 1
1005 if worse_warn:
Simon Glass4653a882014-09-05 19:00:07 -06001006 Print('\n'.join(worse_warn), colour=self.col.MAGENTA)
Simon Glasse30965d2014-08-28 09:43:44 -06001007 self._error_lines += 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001008
1009 if show_sizes:
1010 self.PrintSizeSummary(board_selected, board_dict, show_detail,
1011 show_bloat)
1012
1013 # Save our updated information for the next call to this function
1014 self._base_board_dict = board_dict
1015 self._base_err_lines = err_lines
Simon Glasse30965d2014-08-28 09:43:44 -06001016 self._base_warn_lines = warn_lines
1017 self._base_err_line_boards = err_line_boards
1018 self._base_warn_line_boards = warn_line_boards
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001019
1020 # Get a list of boards that did not get built, if needed
1021 not_built = []
1022 for board in board_selected:
1023 if not board in board_dict:
1024 not_built.append(board)
1025 if not_built:
Simon Glass4653a882014-09-05 19:00:07 -06001026 Print("Boards not built (%d): %s" % (len(not_built),
1027 ', '.join(not_built)))
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001028
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001029 def ProduceResultSummary(self, commit_upto, commits, board_selected):
Simon Glasse30965d2014-08-28 09:43:44 -06001030 (board_dict, err_lines, err_line_boards, warn_lines,
1031 warn_line_boards) = self.GetResultSummary(
Simon Glassed966652014-08-28 09:43:43 -06001032 board_selected, commit_upto,
1033 read_func_sizes=self._show_bloat)
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001034 if commits:
1035 msg = '%02d: %s' % (commit_upto + 1,
1036 commits[commit_upto].subject)
Simon Glass4653a882014-09-05 19:00:07 -06001037 Print(msg, colour=self.col.BLUE)
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001038 self.PrintResultSummary(board_selected, board_dict,
Simon Glassed966652014-08-28 09:43:43 -06001039 err_lines if self._show_errors else [], err_line_boards,
Simon Glasse30965d2014-08-28 09:43:44 -06001040 warn_lines if self._show_errors else [], warn_line_boards,
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001041 self._show_sizes, self._show_detail, self._show_bloat)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001042
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001043 def ShowSummary(self, commits, board_selected):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001044 """Show a build summary for U-Boot for a given board list.
1045
1046 Reset the result summary, then repeatedly call GetResultSummary on
1047 each commit's results, then display the differences we see.
1048
1049 Args:
1050 commit: Commit objects to summarise
1051 board_selected: Dict containing boards to summarise
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001052 """
Simon Glassfea58582014-08-09 15:32:59 -06001053 self.commit_count = len(commits) if commits else 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001054 self.commits = commits
1055 self.ResetResultSummary(board_selected)
Simon Glass28370c12014-08-09 15:33:06 -06001056 self._error_lines = 0
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001057
1058 for commit_upto in range(0, self.commit_count, self._step):
Simon Glassb2ea7ab2014-08-09 15:33:02 -06001059 self.ProduceResultSummary(commit_upto, commits, board_selected)
Simon Glass28370c12014-08-09 15:33:06 -06001060 if not self._error_lines:
Simon Glass4653a882014-09-05 19:00:07 -06001061 Print('(no errors to report)', colour=self.col.GREEN)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001062
1063
1064 def SetupBuild(self, board_selected, commits):
1065 """Set up ready to start a build.
1066
1067 Args:
1068 board_selected: Selected boards to build
1069 commits: Selected commits to build
1070 """
1071 # First work out how many commits we will build
Simon Glassfea58582014-08-09 15:32:59 -06001072 count = (self.commit_count + self._step - 1) / self._step
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001073 self.count = len(board_selected) * count
1074 self.upto = self.warned = self.fail = 0
1075 self._timestamps = collections.deque()
1076
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001077 def GetThreadDir(self, thread_num):
1078 """Get the directory path to the working dir for a thread.
1079
1080 Args:
1081 thread_num: Number of thread to check.
1082 """
1083 return os.path.join(self._working_dir, '%02d' % thread_num)
1084
Simon Glassfea58582014-08-09 15:32:59 -06001085 def _PrepareThread(self, thread_num, setup_git):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001086 """Prepare the working directory for a thread.
1087
1088 This clones or fetches the repo into the thread's work directory.
1089
1090 Args:
1091 thread_num: Thread number (0, 1, ...)
Simon Glassfea58582014-08-09 15:32:59 -06001092 setup_git: True to set up a git repo clone
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001093 """
1094 thread_dir = self.GetThreadDir(thread_num)
Simon Glass190064b2014-08-09 15:33:00 -06001095 builderthread.Mkdir(thread_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001096 git_dir = os.path.join(thread_dir, '.git')
1097
1098 # Clone the repo if it doesn't already exist
1099 # TODO(sjg@chromium): Perhaps some git hackery to symlink instead, so
1100 # we have a private index but uses the origin repo's contents?
Simon Glassfea58582014-08-09 15:32:59 -06001101 if setup_git and self.git_dir:
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001102 src_dir = os.path.abspath(self.git_dir)
1103 if os.path.exists(git_dir):
1104 gitutil.Fetch(git_dir, thread_dir)
1105 else:
Simon Glass4653a882014-09-05 19:00:07 -06001106 Print('Cloning repo for thread %d' % thread_num)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001107 gitutil.Clone(src_dir, thread_dir)
1108
Simon Glassfea58582014-08-09 15:32:59 -06001109 def _PrepareWorkingSpace(self, max_threads, setup_git):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001110 """Prepare the working directory for use.
1111
1112 Set up the git repo for each thread.
1113
1114 Args:
1115 max_threads: Maximum number of threads we expect to need.
Simon Glassfea58582014-08-09 15:32:59 -06001116 setup_git: True to set up a git repo clone
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001117 """
Simon Glass190064b2014-08-09 15:33:00 -06001118 builderthread.Mkdir(self._working_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001119 for thread in range(max_threads):
Simon Glassfea58582014-08-09 15:32:59 -06001120 self._PrepareThread(thread, setup_git)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001121
1122 def _PrepareOutputSpace(self):
1123 """Get the output directories ready to receive files.
1124
1125 We delete any output directories which look like ones we need to
1126 create. Having left over directories is confusing when the user wants
1127 to check the output manually.
1128 """
Simon Glass1a915672014-12-01 17:33:53 -07001129 if not self.commits:
1130 return
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001131 dir_list = []
1132 for commit_upto in range(self.commit_count):
1133 dir_list.append(self._GetOutputDir(commit_upto))
1134
1135 for dirname in glob.glob(os.path.join(self.base_dir, '*')):
1136 if dirname not in dir_list:
1137 shutil.rmtree(dirname)
1138
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001139 def BuildBoards(self, commits, board_selected, keep_outputs, verbose):
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001140 """Build all commits for a list of boards
1141
1142 Args:
1143 commits: List of commits to be build, each a Commit object
1144 boards_selected: Dict of selected boards, key is target name,
1145 value is Board object
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001146 keep_outputs: True to save build output files
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001147 verbose: Display build results as they are completed
Simon Glass2c3deb92014-08-28 09:43:39 -06001148 Returns:
1149 Tuple containing:
1150 - number of boards that failed to build
1151 - number of boards that issued warnings
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001152 """
Simon Glassfea58582014-08-09 15:32:59 -06001153 self.commit_count = len(commits) if commits else 1
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001154 self.commits = commits
Simon Glasse5a0e5d2014-08-09 15:33:03 -06001155 self._verbose = verbose
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001156
1157 self.ResetResultSummary(board_selected)
Thierry Redingf3d015c2014-08-19 10:22:39 +02001158 builderthread.Mkdir(self.base_dir, parents = True)
Simon Glassfea58582014-08-09 15:32:59 -06001159 self._PrepareWorkingSpace(min(self.num_threads, len(board_selected)),
1160 commits is not None)
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001161 self._PrepareOutputSpace()
1162 self.SetupBuild(board_selected, commits)
1163 self.ProcessResult(None)
1164
1165 # Create jobs to build all commits for each board
1166 for brd in board_selected.itervalues():
Simon Glass190064b2014-08-09 15:33:00 -06001167 job = builderthread.BuilderJob()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001168 job.board = brd
1169 job.commits = commits
1170 job.keep_outputs = keep_outputs
1171 job.step = self._step
1172 self.queue.put(job)
1173
1174 # Wait until all jobs are started
1175 self.queue.join()
1176
1177 # Wait until we have processed all output
1178 self.out_queue.join()
Simon Glass4653a882014-09-05 19:00:07 -06001179 Print()
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001180 self.ClearLine(0)
Simon Glass2c3deb92014-08-28 09:43:39 -06001181 return (self.fail, self.warned)