1 # Copyright (c) 2013 The Chromium OS Authors.
3 # Bloat-o-meter code used here Copyright 2004 Matt Mackall <mpm@selenic.com>
5 # SPDX-License-Identifier: GPL-2.0+
9 from datetime import datetime, timedelta
29 Please see README for user documentation, and you should be familiar with
30 that before trying to make sense of this.
32 Buildman works by keeping the machine as busy as possible, building different
33 commits for different boards on multiple CPUs at once.
35 The source repo (self.git_dir) contains all the commits to be built. Each
36 thread works on a single board at a time. It checks out the first commit,
37 configures it for that board, then builds it. Then it checks out the next
38 commit and builds it (typically without re-configuring). When it runs out
39 of commits, it gets another job from the builder and starts again with that
42 Clearly the builder threads could work either way - they could check out a
43 commit and then built it for all boards. Using separate directories for each
44 commit/board pair they could leave their build product around afterwards
47 The intent behind building a single board for multiple commits, is to make
48 use of incremental builds. Since each commit is built incrementally from
49 the previous one, builds are faster. Reconfiguring for a different board
50 removes all intermediate object files.
52 Many threads can be working at once, but each has its own working directory.
53 When a thread finishes a build, it puts the output files into a result
56 The base directory used by buildman is normally '../<branch>', i.e.
57 a directory higher than the source repository and named after the branch
60 Within the base directory, we have one subdirectory for each commit. Within
61 that is one subdirectory for each board. Within that is the build output for
62 that commit/board combination.
64 Buildman also create working directories for each thread, in a .bm-work/
65 subdirectory in the base dir.
67 As an example, say we are building branch 'us-net' for boards 'sandbox' and
68 'seaboard', and say that us-net has two commits. We will have directories
71 us-net/ base directory
72 01_of_02_g4ed4ebc_net--Add-tftp-speed-/
77 02_of_02_g4ed4ebc_net--Check-tftp-comp/
83 00/ working directory for thread 0 (contains source checkout)
85 01/ working directory for thread 1
88 u-boot/ source directory
92 # Possible build outcomes
93 OUTCOME_OK, OUTCOME_WARNING, OUTCOME_ERROR, OUTCOME_UNKNOWN = range(4)
95 # Translate a commit subject into a valid filename
96 trans_valid_chars = string.maketrans("/: ", "---")
100 """Class for building U-Boot for a particular commit.
102 Public members: (many should ->private)
103 active: True if the builder is active and has not been stopped
104 already_done: Number of builds already completed
105 base_dir: Base directory to use for builder
106 checkout: True to check out source, False to skip that step.
107 This is used for testing.
108 col: terminal.Color() object
109 count: Number of commits to build
110 do_make: Method to call to invoke Make
111 fail: Number of builds that failed due to error
112 force_build: Force building even if a build already exists
113 force_config_on_failure: If a commit fails for a board, disable
114 incremental building for the next commit we build for that
115 board, so that we will see all warnings/errors again.
116 force_build_failures: If a previously-built build (i.e. built on
117 a previous run of buildman) is marked as failed, rebuild it.
118 git_dir: Git directory containing source repository
119 last_line_len: Length of the last line we printed (used for erasing
120 it with new progress information)
121 num_jobs: Number of jobs to run at once (passed to make as -j)
122 num_threads: Number of builder threads to run
123 out_queue: Queue of results to process
124 re_make_err: Compiled regular expression for ignore_lines
125 queue: Queue of jobs to run
126 threads: List of active threads
127 toolchains: Toolchains object to use for building
128 upto: Current commit number we are building (0.count-1)
129 warned: Number of builds that produced at least one warning
130 force_reconfig: Reconfigure U-Boot on each comiit. This disables
131 incremental building, where buildman reconfigures on the first
132 commit for a baord, and then just does an incremental build for
133 the following commits. In fact buildman will reconfigure and
134 retry for any failing commits, so generally the only effect of
135 this option is to slow things down.
136 in_tree: Build U-Boot in-tree instead of specifying an output
137 directory separate from the source code. This option is really
138 only useful for testing in-tree builds.
141 _base_board_dict: Last-summarised Dict of boards
142 _base_err_lines: Last-summarised list of errors
143 _build_period_us: Time taken for a single build (float object).
144 _complete_delay: Expected delay until completion (timedelta)
145 _next_delay_update: Next time we plan to display a progress update
147 _show_unknown: Show unknown boards (those not built) in summary
148 _timestamps: List of timestamps for the completion of the last
149 last _timestamp_count builds. Each is a datetime object.
150 _timestamp_count: Number of timestamps to keep in our list.
151 _working_dir: Base working directory containing all threads
154 """Records a build outcome for a single make invocation
157 rc: Outcome value (OUTCOME_...)
158 err_lines: List of error lines or [] if none
159 sizes: Dictionary of image size information, keyed by filename
160 - Each value is itself a dictionary containing
161 values for 'text', 'data' and 'bss', being the integer
162 size in bytes of each section.
163 func_sizes: Dictionary keyed by filename - e.g. 'u-boot'. Each
164 value is itself a dictionary:
166 value: Size of function in bytes
168 def __init__(self, rc, err_lines, sizes, func_sizes):
170 self.err_lines = err_lines
172 self.func_sizes = func_sizes
174 def __init__(self, toolchains, base_dir, git_dir, num_threads, num_jobs,
175 gnu_make='make', checkout=True, show_unknown=True, step=1):
176 """Create a new Builder object
179 toolchains: Toolchains object to use for building
180 base_dir: Base directory to use for builder
181 git_dir: Git directory containing source repository
182 num_threads: Number of builder threads to run
183 num_jobs: Number of jobs to run at once (passed to make as -j)
184 gnu_make: the command name of GNU Make.
185 checkout: True to check out source, False to skip that step.
186 This is used for testing.
187 show_unknown: Show unknown boards (those not built) in summary
188 step: 1 to process every commit, n to process every nth commit
190 self.toolchains = toolchains
191 self.base_dir = base_dir
192 self._working_dir = os.path.join(base_dir, '.bm-work')
195 self.do_make = self.Make
196 self.gnu_make = gnu_make
197 self.checkout = checkout
198 self.num_threads = num_threads
199 self.num_jobs = num_jobs
200 self.already_done = 0
201 self.force_build = False
202 self.git_dir = git_dir
203 self._show_unknown = show_unknown
204 self._timestamp_count = 10
205 self._build_period_us = None
206 self._complete_delay = None
207 self._next_delay_update = datetime.now()
208 self.force_config_on_failure = True
209 self.force_build_failures = False
210 self.force_reconfig = False
214 self.col = terminal.Color()
216 self.queue = Queue.Queue()
217 self.out_queue = Queue.Queue()
218 for i in range(self.num_threads):
219 t = builderthread.BuilderThread(self, i)
222 self.threads.append(t)
224 self.last_line_len = 0
225 t = builderthread.ResultThread(self)
228 self.threads.append(t)
230 ignore_lines = ['(make.*Waiting for unfinished)', '(Segmentation fault)']
231 self.re_make_err = re.compile('|'.join(ignore_lines))
234 """Get rid of all threads created by the builder"""
235 for t in self.threads:
238 def SetDisplayOptions(self, show_errors=False, show_sizes=False,
239 show_detail=False, show_bloat=False):
240 """Setup display options for the builder.
242 show_errors: True to show summarised error/warning info
243 show_sizes: Show size deltas
244 show_detail: Show detail for each board
245 show_bloat: Show detail for each function
247 self._show_errors = show_errors
248 self._show_sizes = show_sizes
249 self._show_detail = show_detail
250 self._show_bloat = show_bloat
252 def _AddTimestamp(self):
253 """Add a new timestamp to the list and record the build period.
255 The build period is the length of time taken to perform a single
256 build (one board, one commit).
259 self._timestamps.append(now)
260 count = len(self._timestamps)
261 delta = self._timestamps[-1] - self._timestamps[0]
262 seconds = delta.total_seconds()
264 # If we have enough data, estimate build period (time taken for a
265 # single build) and therefore completion time.
266 if count > 1 and self._next_delay_update < now:
267 self._next_delay_update = now + timedelta(seconds=2)
269 self._build_period = float(seconds) / count
270 todo = self.count - self.upto
271 self._complete_delay = timedelta(microseconds=
272 self._build_period * todo * 1000000)
274 self._complete_delay -= timedelta(
275 microseconds=self._complete_delay.microseconds)
278 self._timestamps.popleft()
281 def ClearLine(self, length):
282 """Clear any characters on the current line
284 Make way for a new line of length 'length', by outputting enough
285 spaces to clear out the old line. Then remember the new length for
289 length: Length of new line, in characters
291 if length < self.last_line_len:
292 print ' ' * (self.last_line_len - length),
294 self.last_line_len = length
297 def SelectCommit(self, commit, checkout=True):
298 """Checkout the selected commit for this build
301 if checkout and self.checkout:
302 gitutil.Checkout(commit.hash)
304 def Make(self, commit, brd, stage, cwd, *args, **kwargs):
308 commit: Commit object that is being built
309 brd: Board object that is being built
310 stage: Stage that we are at (distclean, config, build)
311 cwd: Directory where make should be run
312 args: Arguments to pass to make
313 kwargs: Arguments to pass to command.RunPipe()
315 cmd = [self.gnu_make] + list(args)
316 result = command.RunPipe([cmd], capture=True, capture_stderr=True,
317 cwd=cwd, raise_on_error=False, **kwargs)
320 def ProcessResult(self, result):
321 """Process the result of a build, showing progress information
324 result: A CommandResult object, which indicates the result for
327 col = terminal.Color()
329 target = result.brd.target
331 if result.return_code < 0:
337 if result.return_code != 0:
341 if result.already_done:
342 self.already_done += 1
346 boards_selected = {target : result.brd}
347 self.ResetResultSummary(boards_selected)
348 self.ProduceResultSummary(result.commit_upto, self.commits,
351 target = '(starting)'
353 # Display separate counts for ok, warned and fail
354 ok = self.upto - self.warned - self.fail
355 line = '\r' + self.col.Color(self.col.GREEN, '%5d' % ok)
356 line += self.col.Color(self.col.YELLOW, '%5d' % self.warned)
357 line += self.col.Color(self.col.RED, '%5d' % self.fail)
359 name = ' /%-5d ' % self.count
361 # Add our current completion time estimate
363 if self._complete_delay:
364 name += '%s : ' % self._complete_delay
365 # When building all boards for a commit, we can print a commit
367 if result and result.commit_upto is None:
368 name += 'commit %2d/%-3d' % (self.commit_upto + 1,
373 length = 14 + len(name)
374 self.ClearLine(length)
376 def _GetOutputDir(self, commit_upto):
377 """Get the name of the output directory for a commit number
379 The output directory is typically .../<branch>/<commit>.
382 commit_upto: Commit number to use (0..self.count-1)
385 commit = self.commits[commit_upto]
386 subject = commit.subject.translate(trans_valid_chars)
387 commit_dir = ('%02d_of_%02d_g%s_%s' % (commit_upto + 1,
388 self.commit_count, commit.hash, subject[:20]))
390 commit_dir = 'current'
391 output_dir = os.path.join(self.base_dir, commit_dir)
394 def GetBuildDir(self, commit_upto, target):
395 """Get the name of the build directory for a commit number
397 The build directory is typically .../<branch>/<commit>/<target>.
400 commit_upto: Commit number to use (0..self.count-1)
403 output_dir = self._GetOutputDir(commit_upto)
404 return os.path.join(output_dir, target)
406 def GetDoneFile(self, commit_upto, target):
407 """Get the name of the done file for a commit number
410 commit_upto: Commit number to use (0..self.count-1)
413 return os.path.join(self.GetBuildDir(commit_upto, target), 'done')
415 def GetSizesFile(self, commit_upto, target):
416 """Get the name of the sizes file for a commit number
419 commit_upto: Commit number to use (0..self.count-1)
422 return os.path.join(self.GetBuildDir(commit_upto, target), 'sizes')
424 def GetFuncSizesFile(self, commit_upto, target, elf_fname):
425 """Get the name of the funcsizes file for a commit number and ELF file
428 commit_upto: Commit number to use (0..self.count-1)
430 elf_fname: Filename of elf image
432 return os.path.join(self.GetBuildDir(commit_upto, target),
433 '%s.sizes' % elf_fname.replace('/', '-'))
435 def GetObjdumpFile(self, commit_upto, target, elf_fname):
436 """Get the name of the objdump file for a commit number and ELF file
439 commit_upto: Commit number to use (0..self.count-1)
441 elf_fname: Filename of elf image
443 return os.path.join(self.GetBuildDir(commit_upto, target),
444 '%s.objdump' % elf_fname.replace('/', '-'))
446 def GetErrFile(self, commit_upto, target):
447 """Get the name of the err file for a commit number
450 commit_upto: Commit number to use (0..self.count-1)
453 output_dir = self.GetBuildDir(commit_upto, target)
454 return os.path.join(output_dir, 'err')
456 def FilterErrors(self, lines):
457 """Filter out errors in which we have no interest
459 We should probably use map().
462 lines: List of error lines, each a string
464 New list with only interesting lines included
468 if not self.re_make_err.search(line):
469 out_lines.append(line)
472 def ReadFuncSizes(self, fname, fd):
473 """Read function sizes from the output of 'nm'
476 fd: File containing data to read
477 fname: Filename we are reading from (just for errors)
480 Dictionary containing size of each function in bytes, indexed by
484 for line in fd.readlines():
486 size, type, name = line[:-1].split()
488 print "Invalid line in file '%s': '%s'" % (fname, line[:-1])
491 # function names begin with '.' on 64-bit powerpc
493 name = 'static.' + name.split('.')[0]
494 sym[name] = sym.get(name, 0) + int(size, 16)
497 def GetBuildOutcome(self, commit_upto, target, read_func_sizes):
498 """Work out the outcome of a build.
501 commit_upto: Commit number to check (0..n-1)
502 target: Target board to check
503 read_func_sizes: True to read function size information
508 done_file = self.GetDoneFile(commit_upto, target)
509 sizes_file = self.GetSizesFile(commit_upto, target)
512 if os.path.exists(done_file):
513 with open(done_file, 'r') as fd:
514 return_code = int(fd.readline())
516 err_file = self.GetErrFile(commit_upto, target)
517 if os.path.exists(err_file):
518 with open(err_file, 'r') as fd:
519 err_lines = self.FilterErrors(fd.readlines())
521 # Decide whether the build was ok, failed or created warnings
529 # Convert size information to our simple format
530 if os.path.exists(sizes_file):
531 with open(sizes_file, 'r') as fd:
532 for line in fd.readlines():
533 values = line.split()
536 rodata = int(values[6], 16)
538 'all' : int(values[0]) + int(values[1]) +
540 'text' : int(values[0]) - rodata,
541 'data' : int(values[1]),
542 'bss' : int(values[2]),
545 sizes[values[5]] = size_dict
548 pattern = self.GetFuncSizesFile(commit_upto, target, '*')
549 for fname in glob.glob(pattern):
550 with open(fname, 'r') as fd:
551 dict_name = os.path.basename(fname).replace('.sizes',
553 func_sizes[dict_name] = self.ReadFuncSizes(fname, fd)
555 return Builder.Outcome(rc, err_lines, sizes, func_sizes)
557 return Builder.Outcome(OUTCOME_UNKNOWN, [], {}, {})
559 def GetResultSummary(self, boards_selected, commit_upto, read_func_sizes):
560 """Calculate a summary of the results of building a commit.
563 board_selected: Dict containing boards to summarise
564 commit_upto: Commit number to summarize (0..self.count-1)
565 read_func_sizes: True to read function size information
569 Dict containing boards which passed building this commit.
570 keyed by board.target
571 List containing a summary of error/warning lines
574 err_lines_summary = []
576 for board in boards_selected.itervalues():
577 outcome = self.GetBuildOutcome(commit_upto, board.target,
579 board_dict[board.target] = outcome
580 for err in outcome.err_lines:
581 if err and not err.rstrip() in err_lines_summary:
582 err_lines_summary.append(err.rstrip())
583 return board_dict, err_lines_summary
585 def AddOutcome(self, board_dict, arch_list, changes, char, color):
586 """Add an output to our list of outcomes for each architecture
588 This simple function adds failing boards (changes) to the
589 relevant architecture string, so we can print the results out
590 sorted by architecture.
593 board_dict: Dict containing all boards
594 arch_list: Dict keyed by arch name. Value is a string containing
595 a list of board names which failed for that arch.
596 changes: List of boards to add to arch_list
597 color: terminal.Colour object
600 for target in changes:
601 if target in board_dict:
602 arch = board_dict[target].arch
605 str = self.col.Color(color, ' ' + target)
606 if not arch in done_arch:
607 str = self.col.Color(color, char) + ' ' + str
608 done_arch[arch] = True
609 if not arch in arch_list:
610 arch_list[arch] = str
612 arch_list[arch] += str
615 def ColourNum(self, num):
616 color = self.col.RED if num > 0 else self.col.GREEN
619 return self.col.Color(color, str(num))
621 def ResetResultSummary(self, board_selected):
622 """Reset the results summary ready for use.
624 Set up the base board list to be all those selected, and set the
625 error lines to empty.
627 Following this, calls to PrintResultSummary() will use this
628 information to work out what has changed.
631 board_selected: Dict containing boards to summarise, keyed by
634 self._base_board_dict = {}
635 for board in board_selected:
636 self._base_board_dict[board] = Builder.Outcome(0, [], [], {})
637 self._base_err_lines = []
639 def PrintFuncSizeDetail(self, fname, old, new):
640 grow, shrink, add, remove, up, down = 0, 0, 0, 0, 0, 0
641 delta, common = [], {}
648 if name not in common:
651 delta.append([-old[name], name])
654 if name not in common:
657 delta.append([new[name], name])
660 diff = new.get(name, 0) - old.get(name, 0)
662 grow, up = grow + 1, up + diff
664 shrink, down = shrink + 1, down - diff
665 delta.append([diff, name])
670 args = [add, -remove, grow, -shrink, up, -down, up - down]
673 args = [self.ColourNum(x) for x in args]
675 print ('%s%s: add: %s/%s, grow: %s/%s bytes: %s/%s (%s)' %
676 tuple([indent, self.col.Color(self.col.YELLOW, fname)] + args))
677 print '%s %-38s %7s %7s %+7s' % (indent, 'function', 'old', 'new',
679 for diff, name in delta:
681 color = self.col.RED if diff > 0 else self.col.GREEN
682 msg = '%s %-38s %7s %7s %+7d' % (indent, name,
683 old.get(name, '-'), new.get(name,'-'), diff)
684 print self.col.Color(color, msg)
687 def PrintSizeDetail(self, target_list, show_bloat):
688 """Show details size information for each board
691 target_list: List of targets, each a dict containing:
692 'target': Target name
693 'total_diff': Total difference in bytes across all areas
694 <part_name>: Difference for that part
695 show_bloat: Show detail for each function
697 targets_by_diff = sorted(target_list, reverse=True,
698 key=lambda x: x['_total_diff'])
699 for result in targets_by_diff:
700 printed_target = False
701 for name in sorted(result):
703 if name.startswith('_'):
706 color = self.col.RED if diff > 0 else self.col.GREEN
707 msg = ' %s %+d' % (name, diff)
708 if not printed_target:
709 print '%10s %-15s:' % ('', result['_target']),
710 printed_target = True
711 print self.col.Color(color, msg),
715 target = result['_target']
716 outcome = result['_outcome']
717 base_outcome = self._base_board_dict[target]
718 for fname in outcome.func_sizes:
719 self.PrintFuncSizeDetail(fname,
720 base_outcome.func_sizes[fname],
721 outcome.func_sizes[fname])
724 def PrintSizeSummary(self, board_selected, board_dict, show_detail,
726 """Print a summary of image sizes broken down by section.
728 The summary takes the form of one line per architecture. The
729 line contains deltas for each of the sections (+ means the section
730 got bigger, - means smaller). The nunmbers are the average number
731 of bytes that a board in this section increased by.
734 powerpc: (622 boards) text -0.0
735 arm: (285 boards) text -0.0
736 nds32: (3 boards) text -8.0
739 board_selected: Dict containing boards to summarise, keyed by
741 board_dict: Dict containing boards for which we built this
742 commit, keyed by board.target. The value is an Outcome object.
743 show_detail: Show detail for each board
744 show_bloat: Show detail for each function
749 # Calculate changes in size for different image parts
750 # The previous sizes are in Board.sizes, for each board
751 for target in board_dict:
752 if target not in board_selected:
754 base_sizes = self._base_board_dict[target].sizes
755 outcome = board_dict[target]
756 sizes = outcome.sizes
758 # Loop through the list of images, creating a dict of size
759 # changes for each image/part. We end up with something like
760 # {'target' : 'snapper9g45, 'data' : 5, 'u-boot-spl:text' : -4}
761 # which means that U-Boot data increased by 5 bytes and SPL
762 # text decreased by 4.
763 err = {'_target' : target}
765 if image in base_sizes:
766 base_image = base_sizes[image]
767 # Loop through the text, data, bss parts
768 for part in sorted(sizes[image]):
769 diff = sizes[image][part] - base_image[part]
772 if image == 'u-boot':
775 name = image + ':' + part
777 arch = board_selected[target].arch
778 if not arch in arch_count:
781 arch_count[arch] += 1
783 pass # Only add to our list when we have some stats
784 elif not arch in arch_list:
785 arch_list[arch] = [err]
787 arch_list[arch].append(err)
789 # We now have a list of image size changes sorted by arch
790 # Print out a summary of these
791 for arch, target_list in arch_list.iteritems():
792 # Get total difference for each type
794 for result in target_list:
796 for name, diff in result.iteritems():
797 if name.startswith('_'):
804 result['_total_diff'] = total
805 result['_outcome'] = board_dict[result['_target']]
807 count = len(target_list)
809 for name in sorted(totals):
812 # Display the average difference in this name for this
814 avg_diff = float(diff) / count
815 color = self.col.RED if avg_diff > 0 else self.col.GREEN
816 msg = ' %s %+1.1f' % (name, avg_diff)
818 print '%10s: (for %d/%d boards)' % (arch, count,
821 print self.col.Color(color, msg),
826 self.PrintSizeDetail(target_list, show_bloat)
829 def PrintResultSummary(self, board_selected, board_dict, err_lines,
830 show_sizes, show_detail, show_bloat):
831 """Compare results with the base results and display delta.
833 Only boards mentioned in board_selected will be considered. This
834 function is intended to be called repeatedly with the results of
835 each commit. It therefore shows a 'diff' between what it saw in
836 the last call and what it sees now.
839 board_selected: Dict containing boards to summarise, keyed by
841 board_dict: Dict containing boards for which we built this
842 commit, keyed by board.target. The value is an Outcome object.
843 err_lines: A list of errors for this commit, or [] if there is
844 none, or we don't want to print errors
845 show_sizes: Show image size deltas
846 show_detail: Show detail for each board
847 show_bloat: Show detail for each function
849 better = [] # List of boards fixed since last commit
850 worse = [] # List of new broken boards since last commit
851 new = [] # List of boards that didn't exist last time
852 unknown = [] # List of boards that were not built
854 for target in board_dict:
855 if target not in board_selected:
858 # If the board was built last time, add its outcome to a list
859 if target in self._base_board_dict:
860 base_outcome = self._base_board_dict[target].rc
861 outcome = board_dict[target]
862 if outcome.rc == OUTCOME_UNKNOWN:
863 unknown.append(target)
864 elif outcome.rc < base_outcome:
865 better.append(target)
866 elif outcome.rc > base_outcome:
871 # Get a list of errors that have appeared, and disappeared
874 for line in err_lines:
875 if line not in self._base_err_lines:
876 worse_err.append('+' + line)
877 for line in self._base_err_lines:
878 if line not in err_lines:
879 better_err.append('-' + line)
881 # Display results by arch
882 if better or worse or unknown or new or worse_err or better_err:
884 self.AddOutcome(board_selected, arch_list, better, '',
886 self.AddOutcome(board_selected, arch_list, worse, '+',
888 self.AddOutcome(board_selected, arch_list, new, '*', self.col.BLUE)
889 if self._show_unknown:
890 self.AddOutcome(board_selected, arch_list, unknown, '?',
892 for arch, target_list in arch_list.iteritems():
893 print '%10s: %s' % (arch, target_list)
895 print self.col.Color(self.col.GREEN, '\n'.join(better_err))
897 print self.col.Color(self.col.RED, '\n'.join(worse_err))
900 self.PrintSizeSummary(board_selected, board_dict, show_detail,
903 # Save our updated information for the next call to this function
904 self._base_board_dict = board_dict
905 self._base_err_lines = err_lines
907 # Get a list of boards that did not get built, if needed
909 for board in board_selected:
910 if not board in board_dict:
911 not_built.append(board)
913 print "Boards not built (%d): %s" % (len(not_built),
914 ', '.join(not_built))
916 def ProduceResultSummary(self, commit_upto, commits, board_selected):
917 board_dict, err_lines = self.GetResultSummary(board_selected,
918 commit_upto, read_func_sizes=self._show_bloat)
920 msg = '%02d: %s' % (commit_upto + 1,
921 commits[commit_upto].subject)
922 print self.col.Color(self.col.BLUE, msg)
923 self.PrintResultSummary(board_selected, board_dict,
924 err_lines if self._show_errors else [],
925 self._show_sizes, self._show_detail, self._show_bloat)
927 def ShowSummary(self, commits, board_selected):
928 """Show a build summary for U-Boot for a given board list.
930 Reset the result summary, then repeatedly call GetResultSummary on
931 each commit's results, then display the differences we see.
934 commit: Commit objects to summarise
935 board_selected: Dict containing boards to summarise
937 self.commit_count = len(commits) if commits else 1
938 self.commits = commits
939 self.ResetResultSummary(board_selected)
941 for commit_upto in range(0, self.commit_count, self._step):
942 self.ProduceResultSummary(commit_upto, commits, board_selected)
945 def SetupBuild(self, board_selected, commits):
946 """Set up ready to start a build.
949 board_selected: Selected boards to build
950 commits: Selected commits to build
952 # First work out how many commits we will build
953 count = (self.commit_count + self._step - 1) / self._step
954 self.count = len(board_selected) * count
955 self.upto = self.warned = self.fail = 0
956 self._timestamps = collections.deque()
958 def BuildBoardsForCommit(self, board_selected, keep_outputs):
959 """Build all boards for a single commit"""
960 self.SetupBuild(board_selected)
961 self.count = len(board_selected)
962 for brd in board_selected.itervalues():
966 job.keep_outputs = keep_outputs
970 self.out_queue.join()
974 def BuildCommits(self, commits, board_selected, show_errors, keep_outputs):
975 """Build all boards for all commits (non-incremental)"""
976 self.commit_count = len(commits)
978 self.ResetResultSummary(board_selected)
979 for self.commit_upto in range(self.commit_count):
980 self.SelectCommit(commits[self.commit_upto])
981 self.SelectOutputDir()
982 builderthread.Mkdir(self.output_dir)
984 self.BuildBoardsForCommit(board_selected, keep_outputs)
985 board_dict, err_lines = self.GetResultSummary()
986 self.PrintResultSummary(board_selected, board_dict,
987 err_lines if show_errors else [])
989 if self.already_done:
990 print '%d builds already done' % self.already_done
992 def GetThreadDir(self, thread_num):
993 """Get the directory path to the working dir for a thread.
996 thread_num: Number of thread to check.
998 return os.path.join(self._working_dir, '%02d' % thread_num)
1000 def _PrepareThread(self, thread_num, setup_git):
1001 """Prepare the working directory for a thread.
1003 This clones or fetches the repo into the thread's work directory.
1006 thread_num: Thread number (0, 1, ...)
1007 setup_git: True to set up a git repo clone
1009 thread_dir = self.GetThreadDir(thread_num)
1010 builderthread.Mkdir(thread_dir)
1011 git_dir = os.path.join(thread_dir, '.git')
1013 # Clone the repo if it doesn't already exist
1014 # TODO(sjg@chromium): Perhaps some git hackery to symlink instead, so
1015 # we have a private index but uses the origin repo's contents?
1016 if setup_git and self.git_dir:
1017 src_dir = os.path.abspath(self.git_dir)
1018 if os.path.exists(git_dir):
1019 gitutil.Fetch(git_dir, thread_dir)
1021 print 'Cloning repo for thread %d' % thread_num
1022 gitutil.Clone(src_dir, thread_dir)
1024 def _PrepareWorkingSpace(self, max_threads, setup_git):
1025 """Prepare the working directory for use.
1027 Set up the git repo for each thread.
1030 max_threads: Maximum number of threads we expect to need.
1031 setup_git: True to set up a git repo clone
1033 builderthread.Mkdir(self._working_dir)
1034 for thread in range(max_threads):
1035 self._PrepareThread(thread, setup_git)
1037 def _PrepareOutputSpace(self):
1038 """Get the output directories ready to receive files.
1040 We delete any output directories which look like ones we need to
1041 create. Having left over directories is confusing when the user wants
1042 to check the output manually.
1045 for commit_upto in range(self.commit_count):
1046 dir_list.append(self._GetOutputDir(commit_upto))
1048 for dirname in glob.glob(os.path.join(self.base_dir, '*')):
1049 if dirname not in dir_list:
1050 shutil.rmtree(dirname)
1052 def BuildBoards(self, commits, board_selected, keep_outputs, verbose):
1053 """Build all commits for a list of boards
1056 commits: List of commits to be build, each a Commit object
1057 boards_selected: Dict of selected boards, key is target name,
1058 value is Board object
1059 keep_outputs: True to save build output files
1060 verbose: Display build results as they are completed
1062 self.commit_count = len(commits) if commits else 1
1063 self.commits = commits
1064 self._verbose = verbose
1066 self.ResetResultSummary(board_selected)
1067 builderthread.Mkdir(self.base_dir)
1068 self._PrepareWorkingSpace(min(self.num_threads, len(board_selected)),
1069 commits is not None)
1070 self._PrepareOutputSpace()
1071 self.SetupBuild(board_selected, commits)
1072 self.ProcessResult(None)
1074 # Create jobs to build all commits for each board
1075 for brd in board_selected.itervalues():
1076 job = builderthread.BuilderJob()
1078 job.commits = commits
1079 job.keep_outputs = keep_outputs
1080 job.step = self._step
1083 # Wait until all jobs are started
1086 # Wait until we have processed all output
1087 self.out_queue.join()